From 2ae8798acee6e0924279c1c958c20b5e9579628b Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Wed, 31 Jul 2019 09:10:05 +0000 Subject: [PATCH 001/207] Merged PR 42: Python package structure Created Python package structure --- deepseismic/__init__.py | 0 pyproject.toml | 2 ++ setup.cfg | 2 ++ setup.py | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 deepseismic/__init__.py create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/deepseismic/__init__.py b/deepseismic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..a8f43fef --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 79 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..b7e47898 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..12a99803 --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( + author="DeepSeismic Maintainers", + author_email="deepseismic@microsoft.com", + classifiers=[ + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Scientific/Engineering", + "Topic :: Software Development", + ], + description="DeepSeismic", + install_requires=[], + license="MIT", + long_description=long_description, + long_description_content_type="text/markdown", + name="deepseismic", + packages=setuptools.find_packages( + include=["deepseismic", "deepseismic.*"] + ), + platforms="any", + python_requires=">= 3.5", + setup_requires=["pytest-runner"], + tests_require=["pytest"], + url="https://github.com/microsoft/deepseismic", + version="0.0.1", +) From 85e24f737daede5da7e5a01d4d9be91af95744bd Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Fri, 2 Aug 2019 09:02:21 +0000 Subject: [PATCH 002/207] =?UTF-8?q?Merged=20PR=2050:=20R=C3=B6th-Tarantola?= =?UTF-8?q?=20generative=20model=20for=20velocities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created Python package structure for generative models for velocities - Implemented the [Röth-Tarantola model](https://doi.org/10.1029/93JB01563) --- deepseismic/__init__.py | 3 ++ deepseismic/velocity/__init__.py | 4 +++ deepseismic/velocity/generator.py | 14 +++++++++ deepseismic/velocity/roeth_tarantola.py | 40 +++++++++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 deepseismic/velocity/__init__.py create mode 100644 deepseismic/velocity/generator.py create mode 100644 deepseismic/velocity/roeth_tarantola.py diff --git a/deepseismic/__init__.py b/deepseismic/__init__.py index e69de29b..f4aa6df8 100644 --- a/deepseismic/__init__.py +++ b/deepseismic/__init__.py @@ -0,0 +1,3 @@ +from . import velocity + +__all__ = ["velocity"] diff --git a/deepseismic/velocity/__init__.py b/deepseismic/velocity/__init__.py new file mode 100644 index 00000000..98225180 --- /dev/null +++ b/deepseismic/velocity/__init__.py @@ -0,0 +1,4 @@ +from .generator import Generator +from .roeth_tarantola import RoethTarantolaGenerator + +__all__ = ["Generator", "RoethTarantolaGenerator"] diff --git a/deepseismic/velocity/generator.py b/deepseismic/velocity/generator.py new file mode 100644 index 00000000..26403ea8 --- /dev/null +++ b/deepseismic/velocity/generator.py @@ -0,0 +1,14 @@ +from typing import Optional, Tuple + +import numpy as np + + +class Generator(object): + def __init__( + self, shape: Tuple[int, ...], dtype: Optional[type] = np.float32 + ): + self.shape = shape + self.dtype = dtype + + def generate(self, seed: Optional[int]) -> np.ndarray: + raise NotImplementedError diff --git a/deepseismic/velocity/roeth_tarantola.py b/deepseismic/velocity/roeth_tarantola.py new file mode 100644 index 00000000..a4c8cfbe --- /dev/null +++ b/deepseismic/velocity/roeth_tarantola.py @@ -0,0 +1,40 @@ +from typing import Optional, Tuple + +import numpy as np + +from .generator import Generator + + +class RoethTarantolaGenerator(Generator): + def __init__( + self, + shape: Tuple[int, ...], + dtype: Optional[type] = np.float32, + horizontal_dim: Optional[int] = -1, + nlayers: Optional[int] = 8, + initial_vp: Optional[Tuple[float, float]] = (1350.0, 1650.0), + vp_perturbation: Optional[Tuple[float, float]] = (-190.0, 570.0), + ): + super().__init__(shape, dtype) + self.horizontal_dim = horizontal_dim + self.nlayers = nlayers + self.initial_vp = initial_vp + self.vp_perturbation = vp_perturbation + + def generate(self) -> np.ndarray: + vp = np.zeros(self.shape, dtype=self.dtype) + dim = self.horizontal_dim + layer_idx = np.round( + np.linspace(0, self.shape[dim], self.nlayers + 1) + ).astype(np.int) + vp_idx = [slice(0, x) for x in vp.shape] + layer_vp = None + for i in range(self.nlayers): + vp_idx[dim] = slice(layer_idx[i], layer_idx[i + 1]) + layer_vp = ( + np.random.uniform(*self.initial_vp) + if layer_vp is None + else layer_vp + np.random.uniform(*self.vp_perturbation) + ) + vp[tuple(vp_idx)] = layer_vp + return vp From 741a217f9c7761ed2e03486d3bc787ea5c45a935 Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Fri, 2 Aug 2019 09:07:30 +0000 Subject: [PATCH 003/207] Merged PR 51: Isotropic AWE forward modelling using Devito Implemented forward modelling for the isotropic acoustic wave equation using [Devito](https://www.devitoproject.org/) --- deepseismic/__init__.py | 4 +- deepseismic/forward/__init__.py | 14 +++ deepseismic/forward/models.py | 171 ++++++++++++++++++++++++++++++ deepseismic/forward/sources.py | 132 +++++++++++++++++++++++ deepseismic/forward/subdomains.py | 17 +++ deepseismic/forward/time.py | 34 ++++++ deepseismic/forward/types.py | 6 ++ 7 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 deepseismic/forward/__init__.py create mode 100644 deepseismic/forward/models.py create mode 100644 deepseismic/forward/sources.py create mode 100644 deepseismic/forward/subdomains.py create mode 100644 deepseismic/forward/time.py create mode 100644 deepseismic/forward/types.py diff --git a/deepseismic/__init__.py b/deepseismic/__init__.py index f4aa6df8..1e7381a2 100644 --- a/deepseismic/__init__.py +++ b/deepseismic/__init__.py @@ -1,3 +1,3 @@ -from . import velocity +from . import forward, velocity -__all__ = ["velocity"] +__all__ = ["forward", "velocity"] diff --git a/deepseismic/forward/__init__.py b/deepseismic/forward/__init__.py new file mode 100644 index 00000000..f9a9083f --- /dev/null +++ b/deepseismic/forward/__init__.py @@ -0,0 +1,14 @@ +from .models import Model, VelocityModel +from .sources import Receiver, RickerSource, WaveletSource +from .time import TimeAxis +from .types import Kernel + +__all__ = [ + "Kernel", + "Model", + "Receiver", + "RickerSource", + "TimeAxis", + "VelocityModel", + "WaveletSource", +] diff --git a/deepseismic/forward/models.py b/deepseismic/forward/models.py new file mode 100644 index 00000000..752e0fcd --- /dev/null +++ b/deepseismic/forward/models.py @@ -0,0 +1,171 @@ +from typing import Optional, Tuple, Union + +import numpy as np +from devito import ( + Constant, + Eq, + Function, + Grid, + Operator, + SubDomain, + TimeFunction, + mmax, + mmin, + solve, +) + +from .sources import PointSource +from .subdomains import PhysicalDomain +from .time import TimeAxis +from .types import Kernel + + +class Model(object): + def __init__( + self, + shape: Tuple[int, ...], + origin: Tuple[float, ...], + spacing: Tuple[float, ...], + npml: Optional[int] = 0, + dtype: Optional[type] = np.float32, + subdomains: Optional[Tuple[SubDomain]] = (), + ): + shape = tuple(int(x) for x in shape) + origin = tuple(dtype(x) for x in origin) + npml = int(npml) + subdomains = tuple(subdomains) + (PhysicalDomain(npml),) + shape_pml = tuple(x + 2 * npml for x in shape) + extent_pml = tuple(s * (d - 1) for s, d in zip(spacing, shape_pml)) + origin_pml = tuple( + dtype(o - s * npml) for o, s in zip(origin, spacing) + ) + self.grid = Grid( + shape=shape_pml, + extent=extent_pml, + origin=origin_pml, + dtype=dtype, + subdomains=subdomains, + ) + self.npml = npml + self.pml = Function(name="pml", grid=self.grid) + pml_data = np.pad( + np.zeros(shape, dtype=dtype), + [(npml,) * 2 for _ in range(self.pml.ndim)], + mode="edge", + ) + pml_coef = 1.5 * np.log(1000.0) / 40.0 + for d in range(self.pml.ndim): + for i in range(npml): + pos = np.abs((npml - i + 1) / npml) + val = pml_coef * (pos - np.sin(2 * np.pi * pos) / (2 * np.pi)) + idx = [slice(0, x) for x in pml_data.shape] + idx[d] = slice(i, i + 1) + pml_data[tuple(idx)] += val / self.grid.spacing[d] + idx[d] = slice( + pml_data.shape[d] - i, pml_data.shape[d] - i + 1 + ) + pml_data[tuple(idx)] += val / self.grid.spacing[d] + pml_data = np.pad( + pml_data, + [(i.left, i.right) for i in self.pml._size_halo], + mode="edge", + ) + self.pml.data_with_halo[:] = pml_data + self.shape = shape + + @property + def dtype(self) -> type: + return self.grid.dtype + + @property + def npml(self) -> int: + return self.npml + + @property + def spacing(self): + return self.grid.spacing + + @property + def spacing_map(self): + return self.grid.spacing_map + + @property + def time_spacing(self): + return self.grid.stepping_dim.spacing + + +class VelocityModel(Model): + def __init__( + self, + shape: Tuple[int, ...], + origin: Tuple[float, ...], + spacing: Tuple[float, ...], + vp: Union[float, np.ndarray], + space_order: Optional[int] = None, + npml: Optional[int] = 0, + dtype: Optional[type] = np.float32, + subdomains: Optional[Tuple[SubDomain]] = (), + ): + super().__init__(shape, origin, spacing, npml, dtype, subdomains) + if isinstance(vp, np.ndarray): + assert space_order is not None + self.m = Function( + name="m", grid=self.grid, space_order=int(space_order) + ) + else: + self.m = Constant(name="m", value=1.0 / float(vp) ** 2.0) + self.vp = vp + + @property + def critical_dt(self): + coef = 0.38 if len(self.shape) == 3 else 0.42 + return self.dtype(coef * mmin(self.spacing) / mmax(self.vp)) + + @property + def vp(self) -> Union[float, np.ndarray]: + return self._vp + + @vp.setter + def vp(self, vp: Union[float, np.ndarray]) -> None: + self._vp = vp + if isinstance(vp, np.ndarray): + pad_widths = [ + (self.npml + i.left, self.npml + i.right) + for i in self.m._size_halo + ] + self.m.data_with_halo[:] = np.pad( + 1 / self.vp ** 2, pad_widths, mode="edge" + ) + else: + self.m.data = 1.0 / float(vp) ** 2.0 + + def solve( + self, + source: PointSource, + receivers: PointSource, + start: Optional[float] = 0.0, + stop: Optional[float] = 1000.0, + step: Optional[float] = None, + space_order: Optional[int] = 4, + kernel: Optional[Kernel] = Kernel.OT2, + ) -> np.ndarray: + assert kernel in Kernel.__members__ + if step is None: + step = self.critical_dt + u = TimeFunction( + name="u", grid=self.grid, time_order=2, space_order=space_order + ) + H = u.laplace + if kernel is Kernel.OT4: + H += self.time_spacing ** 2 / 12 * u.laplace2(1 / self.m) + eq = Eq( + u.forward, solve(self.m * u.dt2 - H + self.pml * u.dt, u.forward) + ) + src_term = source.inject( + field=u.forward, expr=source * self.time_spacing ** 2 / self.m + ) + rec_term = receivers.interpolate(expr=u) + op = Operator([eq] + src_term + rec_term, subs=self.spacing_map) + time_range = TimeAxis(start=start, stop=stop, step=step) + op(time=time_range.num - 1, dt=time_range.step) + return receivers.data diff --git a/deepseismic/forward/sources.py b/deepseismic/forward/sources.py new file mode 100644 index 00000000..5a0470e2 --- /dev/null +++ b/deepseismic/forward/sources.py @@ -0,0 +1,132 @@ +from typing import Optional + +import numpy as np +import sympy +from devito.types import Dimension, SparseTimeFunction +from devito.types.basic import _SymbolCache +from scipy import interpolate + +from .time import TimeAxis + + +class PointSource(SparseTimeFunction): + def __new__(cls, *args, **kwargs): + if cls in _SymbolCache: + options = kwargs.get("options", {}) + obj = sympy.Function.__new__(cls, *args, **options) + obj._cached_init() + return obj + name = kwargs.pop("name") + grid = kwargs.pop("grid") + time_range = kwargs.pop("time_range") + time_order = kwargs.pop("time_order", 2) + p_dim = kwargs.pop("dimension", Dimension(name="p_%s" % name)) + npoint = kwargs.pop("npoint", None) + coordinates = kwargs.pop( + "coordinates", kwargs.pop("coordinates_data", None) + ) + if npoint is None: + assert ( + coordinates is not None + ), "Either `npoint` or `coordinates` must be provided" + npoint = coordinates.shape[0] + obj = SparseTimeFunction.__new__( + cls, + name=name, + grid=grid, + dimensions=(grid.time_dim, p_dim), + npoint=npoint, + nt=time_range.num, + time_order=time_order, + coordinates=coordinates, + **kwargs + ) + obj._time_range = time_range + data = kwargs.get("data") + if data is not None: + obj.data[:] = data + return obj + + @property + def time_range(self) -> TimeAxis: + return self._time_range + + @property + def time_values(self) -> np.ndarray: + return self._time_range.time_values + + def resample( + self, + dt: Optional[float] = None, + num: Optional[int] = None, + rtol: Optional[float] = 1.0e-5, + order: Optional[int] = 3, + ): + assert (dt is not None) ^ ( + num is not None + ), "Exactly one of `dt` or `num` must be provided" + start = self._time_range.start + stop = self._time_range.stop + dt0 = self._time_range.step + if dt is not None: + new_time_range = TimeAxis(start=start, stop=stop, step=dt) + else: + new_time_range = TimeAxis(start=start, stop=stop, num=num) + dt = new_time_range.step + if np.isclose(dt0, dt, rtol=rtol): + return self + n_traces = self.data.shape[1] + new_traces = np.zeros( + (new_time_range.num, n_traces), dtype=self.data.dtype + ) + for j in range(n_traces): + tck = interpolate.splrep( + self._time_range.time_values, self.data[:, j], k=order + ) + new_traces[:, j] = interpolate.splev( + new_time_range.time_values, tck + ) + return PointSource( + name=self.name, + grid=self.grid, + time_range=new_time_range, + coordinates=self.coordinates.data, + data=new_traces, + ) + + _pickle_kwargs = SparseTimeFunction._pickle_kwargs + ["time_range"] + _pickle_kwargs.remove("nt") # Inferred from time_range + + +class Receiver(PointSource): + pass + + +class WaveletSource(PointSource): + def __new__(cls, *args, **kwargs): + if cls in _SymbolCache: + options = kwargs.get("options", {}) + obj = sympy.Function.__new__(cls, *args, **options) + obj._cached_init() + return obj + npoint = kwargs.pop("npoint", 1) + obj = PointSource.__new__(cls, npoint=npoint, **kwargs) + obj.f0 = kwargs.get("f0") + for p in range(npoint): + obj.data[:, p] = obj.wavelet(obj.f0, obj.time_values) + return obj + + def __init__(self, *args, **kwargs): + if not self._cached(): + super(WaveletSource, self).__init__(*args, **kwargs) + + def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: + raise NotImplementedError + + _pickle_kwargs = PointSource._pickle_kwargs + ["f0"] + + +class RickerSource(WaveletSource): + def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: + r = np.pi * f0 * (t - 1.0 / f0) + return (1.0 - 2.0 * r ** 2.0) * np.exp(-r ** 2.0) diff --git a/deepseismic/forward/subdomains.py b/deepseismic/forward/subdomains.py new file mode 100644 index 00000000..6cfe3aa3 --- /dev/null +++ b/deepseismic/forward/subdomains.py @@ -0,0 +1,17 @@ +from typing import Dict, Iterable, Tuple + +from devito import Dimension, SubDomain + + +class PhysicalDomain(SubDomain): + + name = "physical_domain" + + def __init__(self, npml: int): + super().__init__() + self.npml = npml + + def define( + self, dimensions: Iterable[Dimension] + ) -> Dict[Dimension, Tuple[str, int, int]]: + return {d: ("middle", self.npml, self.npml) for d in dimensions} diff --git a/deepseismic/forward/time.py b/deepseismic/forward/time.py new file mode 100644 index 00000000..d3dfc00d --- /dev/null +++ b/deepseismic/forward/time.py @@ -0,0 +1,34 @@ +from typing import Optional + +import numpy as np + + +class TimeAxis(object): + def __init__( + self, + start: Optional[float] = None, + stop: Optional[float] = None, + num: Optional[int] = None, + step: Optional[float] = None, + dtype: Optional[type] = np.float32, + ): + if start is None: + start = step * (1 - num) + stop + elif stop is None: + stop = step * (num - 1) + start + elif num is None: + num = int(np.ceil((stop - start + step) / step)) + stop = step * (num - 1) + start + elif step is None: + step = (stop - start) / (num - 1) + else: + raise ValueError + self.start = start + self.stop = stop + self.num = num + self.step = step + self.dtype = dtype + + @property + def time_values(self) -> np.ndarray: + return np.linspace(self.start, self.stop, self.num, dtype=self.dtype) diff --git a/deepseismic/forward/types.py b/deepseismic/forward/types.py new file mode 100644 index 00000000..772f67b7 --- /dev/null +++ b/deepseismic/forward/types.py @@ -0,0 +1,6 @@ +from enum import Enum, auto + + +class Kernel(Enum): + OT2 = auto() + OT4 = auto() From b8e71f9ea28bf2a7b08dfb1d7f70c94658d3f4f5 Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Fri, 2 Aug 2019 09:59:35 +0000 Subject: [PATCH 004/207] Merged PR 52: PRNG seed Exposed PRNG seed in generative models for velocities --- deepseismic/velocity/generator.py | 6 +++++- deepseismic/velocity/roeth_tarantola.py | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/deepseismic/velocity/generator.py b/deepseismic/velocity/generator.py index 26403ea8..9714dde1 100644 --- a/deepseismic/velocity/generator.py +++ b/deepseismic/velocity/generator.py @@ -5,10 +5,14 @@ class Generator(object): def __init__( - self, shape: Tuple[int, ...], dtype: Optional[type] = np.float32 + self, + shape: Tuple[int, ...], + dtype: Optional[type] = np.float32, + seed: Optional[int] = None, ): self.shape = shape self.dtype = dtype + self._prng = np.random.RandomState(seed) def generate(self, seed: Optional[int]) -> np.ndarray: raise NotImplementedError diff --git a/deepseismic/velocity/roeth_tarantola.py b/deepseismic/velocity/roeth_tarantola.py index a4c8cfbe..db4ca207 100644 --- a/deepseismic/velocity/roeth_tarantola.py +++ b/deepseismic/velocity/roeth_tarantola.py @@ -10,12 +10,13 @@ def __init__( self, shape: Tuple[int, ...], dtype: Optional[type] = np.float32, + seed: Optional[int] = None, horizontal_dim: Optional[int] = -1, nlayers: Optional[int] = 8, initial_vp: Optional[Tuple[float, float]] = (1350.0, 1650.0), vp_perturbation: Optional[Tuple[float, float]] = (-190.0, 570.0), ): - super().__init__(shape, dtype) + super().__init__(shape, dtype, seed) self.horizontal_dim = horizontal_dim self.nlayers = nlayers self.initial_vp = initial_vp @@ -32,9 +33,9 @@ def generate(self) -> np.ndarray: for i in range(self.nlayers): vp_idx[dim] = slice(layer_idx[i], layer_idx[i + 1]) layer_vp = ( - np.random.uniform(*self.initial_vp) + self._prng.uniform(*self.initial_vp) if layer_vp is None - else layer_vp + np.random.uniform(*self.vp_perturbation) + else layer_vp + self._prng.uniform(*self.vp_perturbation) ) vp[tuple(vp_idx)] = layer_vp return vp From 7f1fcd3fb3238f5ed55cb6b5bccb7b07ba07bc45 Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Fri, 2 Aug 2019 11:03:43 +0000 Subject: [PATCH 005/207] Merged PR 53: Docs update - Updated LICENSE - Added Microsoft Open Source Code of Conduct - Added Contributing section to README --- CODE_OF_CONDUCT.md | 9 +++++++++ LICENSE.txt => LICENSE | 2 +- README.md | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 CODE_OF_CONDUCT.md rename LICENSE.txt => LICENSE (94%) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..c72a5749 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/LICENSE.txt b/LICENSE similarity index 94% rename from LICENSE.txt rename to LICENSE index 88040d88..22aed37e 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) Microsoft Corporation. All rights reserved. +Copyright (c) Microsoft Corporation. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 914a049b..2fdc39a0 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ # DeepSeismic + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From 1359737755569d2dd612956c25e60e4a1e19f074 Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Fri, 2 Aug 2019 13:47:45 +0000 Subject: [PATCH 006/207] Merged PR 54: CLI for velocity generators Implemented CLI for velocity generators --- bin/vpgen | 68 +++++++++++++++++++++++++++++++ deepseismic/velocity/generator.py | 6 ++- setup.py | 12 +++++- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100755 bin/vpgen diff --git a/bin/vpgen b/bin/vpgen new file mode 100755 index 00000000..33cb4236 --- /dev/null +++ b/bin/vpgen @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +import argparse +from itertools import islice + +import h5py +import numpy as np + +from deepseismic import velocity + +GENERATORS = {"rt": velocity.RoethTarantolaGenerator} + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Simulate velocity distributions.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "-g", + "--generator", + default="rt", + type=str, + choices=GENERATORS.keys(), + help="generative model", + metavar="MODEL", + ) + parser.add_argument( + "-n", default=1, type=int, help="number of simulations", metavar="N" + ) + parser.add_argument( + "-nx", + default=1000, + type=int, + help="number of grid points along the first dimension", + metavar="N", + ) + parser.add_argument( + "-ny", + default=1000, + type=int, + help="number of grid points along the second dimension", + metavar="N", + ) + parser.add_argument( + "-nz", + type=int, + help="number of grid points along the third dimension", + metavar="N", + ) + parser.add_argument( + "-o", "--output", type=str, required=True, help="output file" + ) + parser.add_argument( + "-s", "--seed", default=42, type=int, help="random seed" + ) + return parser.parse_args() + + +if __name__ == "__main__": + args = parse_args() + shape = (args.nx, args.ny) + if args.nz is not None: + shape += (args.nz,) + generator = GENERATORS[args.generator](shape, seed=args.seed) + with h5py.File(args.output, "w") as f: + for i, data in enumerate(islice(generator.generate_many(), args.n)): + f.create_dataset(str(i), data=data, compression="gzip") diff --git a/deepseismic/velocity/generator.py b/deepseismic/velocity/generator.py index 9714dde1..ddc2eb4a 100644 --- a/deepseismic/velocity/generator.py +++ b/deepseismic/velocity/generator.py @@ -14,5 +14,9 @@ def __init__( self.dtype = dtype self._prng = np.random.RandomState(seed) - def generate(self, seed: Optional[int]) -> np.ndarray: + def generate(self) -> np.ndarray: raise NotImplementedError + + def generate_many(self) -> np.ndarray: + while True: + yield self.generate() diff --git a/setup.py b/setup.py index 12a99803..195cd7fe 100644 --- a/setup.py +++ b/setup.py @@ -18,8 +18,17 @@ "Topic :: Scientific/Engineering", "Topic :: Software Development", ], + dependency_links=[ + "https://github.com/opesci/devito/archive/v3.4.tar.gz#egg=devito-3.4" + ], description="DeepSeismic", - install_requires=[], + install_requires=[ + "devito==3.4", + "h5py==2.9.0", + "numpy==1.17.0", + "scipy==1.3.0", + "sympy==1.4", + ], license="MIT", long_description=long_description, long_description_content_type="text/markdown", @@ -29,6 +38,7 @@ ), platforms="any", python_requires=">= 3.5", + scripts=["bin/vpgen"], setup_requires=["pytest-runner"], tests_require=["pytest"], url="https://github.com/microsoft/deepseismic", From 410b87624dc4e79db120900611075eb24a75dc8f Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Wed, 7 Aug 2019 07:55:57 +0000 Subject: [PATCH 007/207] Merged PR 69: CLI subpackage using Click Reimplemented CLI as subpackage using Click --- bin/ds | 6 +++ bin/vpgen | 68 --------------------------------- deepseismic/__init__.py | 4 +- deepseismic/cli/__init__.py | 20 ++++++++++ deepseismic/cli/velocity.py | 75 +++++++++++++++++++++++++++++++++++++ setup.py | 7 ++-- 6 files changed, 107 insertions(+), 73 deletions(-) create mode 100644 bin/ds delete mode 100755 bin/vpgen create mode 100644 deepseismic/cli/__init__.py create mode 100644 deepseismic/cli/velocity.py diff --git a/bin/ds b/bin/ds new file mode 100644 index 00000000..3bd01081 --- /dev/null +++ b/bin/ds @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from deepseismic import cli + +if __name__ == "__main__": + cli.main() diff --git a/bin/vpgen b/bin/vpgen deleted file mode 100755 index 33cb4236..00000000 --- a/bin/vpgen +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python - -import argparse -from itertools import islice - -import h5py -import numpy as np - -from deepseismic import velocity - -GENERATORS = {"rt": velocity.RoethTarantolaGenerator} - - -def parse_args(): - parser = argparse.ArgumentParser( - description="Simulate velocity distributions.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - parser.add_argument( - "-g", - "--generator", - default="rt", - type=str, - choices=GENERATORS.keys(), - help="generative model", - metavar="MODEL", - ) - parser.add_argument( - "-n", default=1, type=int, help="number of simulations", metavar="N" - ) - parser.add_argument( - "-nx", - default=1000, - type=int, - help="number of grid points along the first dimension", - metavar="N", - ) - parser.add_argument( - "-ny", - default=1000, - type=int, - help="number of grid points along the second dimension", - metavar="N", - ) - parser.add_argument( - "-nz", - type=int, - help="number of grid points along the third dimension", - metavar="N", - ) - parser.add_argument( - "-o", "--output", type=str, required=True, help="output file" - ) - parser.add_argument( - "-s", "--seed", default=42, type=int, help="random seed" - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - shape = (args.nx, args.ny) - if args.nz is not None: - shape += (args.nz,) - generator = GENERATORS[args.generator](shape, seed=args.seed) - with h5py.File(args.output, "w") as f: - for i, data in enumerate(islice(generator.generate_many(), args.n)): - f.create_dataset(str(i), data=data, compression="gzip") diff --git a/deepseismic/__init__.py b/deepseismic/__init__.py index 1e7381a2..8dc07e06 100644 --- a/deepseismic/__init__.py +++ b/deepseismic/__init__.py @@ -1,3 +1,3 @@ -from . import forward, velocity +from . import cli, forward, velocity -__all__ = ["forward", "velocity"] +__all__ = ["cli", "forward", "velocity"] diff --git a/deepseismic/cli/__init__.py b/deepseismic/cli/__init__.py new file mode 100644 index 00000000..4f9a617d --- /dev/null +++ b/deepseismic/cli/__init__.py @@ -0,0 +1,20 @@ +from functools import partial + +import click + +from . import velocity + +click.option = partial(click.option, show_default=True) + + +@click.group() +@click.pass_context +def cli(ctx): + ctx.ensure_object(dict) + + +cli.add_command(velocity.vp) + + +def main(): + cli(obj={}) diff --git a/deepseismic/cli/velocity.py b/deepseismic/cli/velocity.py new file mode 100644 index 00000000..084a4734 --- /dev/null +++ b/deepseismic/cli/velocity.py @@ -0,0 +1,75 @@ +from itertools import islice + +import click +import h5py + +from ..velocity import RoethTarantolaGenerator + + +@click.group() +@click.argument("output", type=click.Path()) +@click.option("--append/--no-append", default=False) +@click.option("-n", default=1, type=int, help="Number of simulations") +@click.option( + "-nx", + default=100, + type=int, + help="Number of grid points along the first dimension", +) +@click.option( + "-ny", + default=100, + type=int, + help="Number of grid points along the second dimension", +) +@click.option( + "-nz", type=int, help="Number of grid points along the third dimension" +) +@click.option("-s", "--seed", default=42, type=int, help="Random seed") +@click.pass_context +def vp( + ctx, + append: bool, + n: int, + nx: int, + ny: int, + nz: int, + output: str, + seed: int, +): + """Vp simulation""" + shape = (nx, ny) + if nz is not None: + shape += (nz,) + ctx.obj["n"] = n + ctx.obj["output"] = h5py.File(output, mode=("a" if append else "w")) + ctx.obj["seed"] = seed + ctx.obj["shape"] = shape + + +@vp.command() +@click.option("-l", "--layers", default=8, type=int, help="Number of layers") +@click.option( + "--initial-vp", + default=(1350.0, 1650.0), + type=(float, float), + help="Initial Vp", +) +@click.option( + "--vp-perturbation", + default=(-190.0, 570.0), + type=(float, float), + help="Per-layer Vp perturbation", +) +@click.pass_context +def rt(ctx, layers, initial_vp, vp_perturbation): + """Röth-Tarantola model""" + model = RoethTarantolaGenerator( + shape=ctx.obj["shape"], + seed=ctx.obj["seed"], + nlayers=layers, + initial_vp=initial_vp, + vp_perturbation=vp_perturbation, + ) + for i, data in enumerate(islice(model.generate_many(), ctx.obj["n"])): + ctx.obj["output"].create_dataset(str(i), data=data, compression="gzip") diff --git a/setup.py b/setup.py index 195cd7fe..80522e42 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ import setuptools -with open("README.md", "r") as fh: - long_description = fh.read() +with open("README.md", "r") as f: + long_description = f.read() setuptools.setup( author="DeepSeismic Maintainers", @@ -23,6 +23,7 @@ ], description="DeepSeismic", install_requires=[ + "click==7.0", "devito==3.4", "h5py==2.9.0", "numpy==1.17.0", @@ -38,7 +39,7 @@ ), platforms="any", python_requires=">= 3.5", - scripts=["bin/vpgen"], + scripts=["bin/ds"], setup_requires=["pytest-runner"], tests_require=["pytest"], url="https://github.com/microsoft/deepseismic", From f485ead18c556afdce5223bbb1e8666e58459d6e Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Wed, 7 Aug 2019 09:06:04 +0000 Subject: [PATCH 008/207] Merged PR 70: VS Code settings Added VS Code settings --- .vscode/settings.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..85fbc9f8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.formatting.provider": "black", + "python.linting.enabled": true, + "python.linting.flake8Enabled": true, + "python.linting.pylintEnabled": false, +} \ No newline at end of file From fe49b96fe20767784aefc3f75051082eb1106622 Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Wed, 7 Aug 2019 16:48:38 +0000 Subject: [PATCH 009/207] Merged PR 73: CLI for forward modelling Implemented CLI for forward modelling --- deepseismic/cli/__init__.py | 3 +- deepseismic/cli/forward.py | 119 ++++++++++++++++++++++++++++++++++ deepseismic/cli/velocity.py | 31 +++++++-- deepseismic/forward/models.py | 20 +++--- 4 files changed, 156 insertions(+), 17 deletions(-) create mode 100644 deepseismic/cli/forward.py diff --git a/deepseismic/cli/__init__.py b/deepseismic/cli/__init__.py index 4f9a617d..1b0db11d 100644 --- a/deepseismic/cli/__init__.py +++ b/deepseismic/cli/__init__.py @@ -2,7 +2,7 @@ import click -from . import velocity +from . import forward, velocity click.option = partial(click.option, show_default=True) @@ -13,6 +13,7 @@ def cli(ctx): ctx.ensure_object(dict) +cli.add_command(forward.fwd) cli.add_command(velocity.vp) diff --git a/deepseismic/cli/forward.py b/deepseismic/cli/forward.py new file mode 100644 index 00000000..cad0ff7b --- /dev/null +++ b/deepseismic/cli/forward.py @@ -0,0 +1,119 @@ +from functools import partial + +import click +import h5py +import numpy as np + +from ..forward import Receiver, RickerSource, TimeAxis, VelocityModel + +click.option = partial(click.option, show_default=True) + + +@click.group() +@click.argument("input", type=click.Path()) +@click.argument("output", type=click.Path()) +@click.option( + "-d", "--duration", default=1000.0, type=float, help="Simulation duration" +) +@click.option("-dt", type=float, help="Time increment") +@click.option( + "--n-pml", default=10, type=int, help="PML size (in grid points)" +) +@click.option("--space-order", default=2, type=int, help="Space order") +@click.option( + "--spacing", default=10.0, type=float, help="Spacing between grid points" +) +@click.pass_context +def fwd( + ctx, + dt: float, + duration: float, + input: str, + n_pml: int, + output: str, + space_order: int, + spacing: float, +): + """Forward modelling""" + if dt: + ctx.obj["dt"] = dt + ctx.obj["duration"] = duration + ctx.obj["input_file"] = h5py.File(input, mode="r") + ctx.obj["npml"] = n_pml + ctx.obj["output_file"] = h5py.File(output, mode="w") + ctx.obj["space_order"] = space_order + ctx.obj["spacing"] = spacing + + +@fwd.command() +@click.option( + "-f0", default=0.01, type=float, help="Source peak frequency (in kHz)" +) +@click.pass_context +def ricker(ctx, f0: float): + """Ricker source""" + input_file = ctx.obj["input_file"] + output_file = ctx.obj["output_file"] + n = sum(len(x.values()) for x in input_file.values()) + with click.progressbar(length=n) as bar: + for input_group_name, input_group in input_file.items(): + for dataset in input_group.values(): + first_dataset = dataset + break + model = None + output_group = output_file.create_group(input_group_name) + for input_dataset_name, vp in input_group.items(): + if model is None: + model = VelocityModel( + shape=first_dataset.shape, + origin=tuple(0.0 for _ in first_dataset.shape), + spacing=tuple( + ctx.obj["spacing"] for _ in first_dataset.shape + ), + vp=vp[()], + space_order=ctx.obj["space_order"], + npml=ctx.obj["npml"], + ) + else: + model.vp = vp[()] + time_range = TimeAxis( + start=0.0, + stop=ctx.obj["duration"], + step=ctx.obj.get("dt", model.critical_dt), + ) + source = RickerSource( + name="source", + grid=model.grid, + f0=f0, + npoint=1, + time_range=time_range, + ) + source.coordinates.data[0, :] = ( + np.array(model.domain_size) * 0.5 + ) + source.coordinates.data[0, -1] = 0.0 + n_receivers = 11 ** (len(model.domain_size) - 1) + receivers = Receiver( + name="receivers", + grid=model.grid, + npoint=n_receivers, + time_range=time_range, + ) + receivers_coords = np.meshgrid( + *( + np.linspace(start=0, stop=s, num=11) + for s in model.domain_size + ) + ) + for d in range(len(receivers_coords)): + receivers.coordinates.data[:, d] = receivers_coords[ + d + ].flatten() + receivers.coordinates.data[:, -1] = 0.0 + seismograms = model.solve( + source=source, receivers=receivers, time_range=time_range + ) + output_group.create_dataset( + input_dataset_name, data=seismograms + ) + bar.update(1) diff --git a/deepseismic/cli/velocity.py b/deepseismic/cli/velocity.py index 084a4734..b00e8b19 100644 --- a/deepseismic/cli/velocity.py +++ b/deepseismic/cli/velocity.py @@ -1,14 +1,22 @@ +from functools import partial from itertools import islice +from typing import Tuple import click import h5py from ..velocity import RoethTarantolaGenerator +click.option = partial(click.option, show_default=True) + @click.group() @click.argument("output", type=click.Path()) -@click.option("--append/--no-append", default=False) +@click.option( + "--append/--no-append", + default=False, + help="Whether to append to output file", +) @click.option("-n", default=1, type=int, help="Number of simulations") @click.option( "-nx", @@ -41,8 +49,13 @@ def vp( shape = (nx, ny) if nz is not None: shape += (nz,) + output_file = h5py.File(output, mode=("a" if append else "w")) + output_group = output_file.create_group( + str(max((int(x) for x in output_file.keys()), default=-1) + 1) + ) ctx.obj["n"] = n - ctx.obj["output"] = h5py.File(output, mode=("a" if append else "w")) + ctx.obj["output_file"] = output_file + ctx.obj["output_group"] = output_group ctx.obj["seed"] = seed ctx.obj["shape"] = shape @@ -62,7 +75,12 @@ def vp( help="Per-layer Vp perturbation", ) @click.pass_context -def rt(ctx, layers, initial_vp, vp_perturbation): +def rt( + ctx, + layers: int, + initial_vp: Tuple[float, float], + vp_perturbation: Tuple[float, float], +): """Röth-Tarantola model""" model = RoethTarantolaGenerator( shape=ctx.obj["shape"], @@ -71,5 +89,8 @@ def rt(ctx, layers, initial_vp, vp_perturbation): initial_vp=initial_vp, vp_perturbation=vp_perturbation, ) - for i, data in enumerate(islice(model.generate_many(), ctx.obj["n"])): - ctx.obj["output"].create_dataset(str(i), data=data, compression="gzip") + group = ctx.obj["output_group"] + with click.progressbar(length=ctx.obj["n"]) as bar: + for i, data in enumerate(islice(model.generate_many(), ctx.obj["n"])): + group.create_dataset(str(i), data=data, compression="gzip") + bar.update(1) diff --git a/deepseismic/forward/models.py b/deepseismic/forward/models.py index 752e0fcd..080a50ce 100644 --- a/deepseismic/forward/models.py +++ b/deepseismic/forward/models.py @@ -9,6 +9,7 @@ Operator, SubDomain, TimeFunction, + logger, mmax, mmin, solve, @@ -19,6 +20,8 @@ from .time import TimeAxis from .types import Kernel +logger.set_log_level("WARNING") + class Model(object): def __init__( @@ -74,12 +77,12 @@ def __init__( self.shape = shape @property - def dtype(self) -> type: - return self.grid.dtype + def domain_size(self) -> Tuple[float, ...]: + return tuple((d - 1) * s for d, s in zip(self.shape, self.spacing)) @property - def npml(self) -> int: - return self.npml + def dtype(self) -> type: + return self.grid.dtype @property def spacing(self): @@ -143,15 +146,11 @@ def solve( self, source: PointSource, receivers: PointSource, - start: Optional[float] = 0.0, - stop: Optional[float] = 1000.0, - step: Optional[float] = None, + time_range: TimeAxis, space_order: Optional[int] = 4, kernel: Optional[Kernel] = Kernel.OT2, ) -> np.ndarray: - assert kernel in Kernel.__members__ - if step is None: - step = self.critical_dt + assert isinstance(kernel, Kernel) u = TimeFunction( name="u", grid=self.grid, time_order=2, space_order=space_order ) @@ -166,6 +165,5 @@ def solve( ) rec_term = receivers.interpolate(expr=u) op = Operator([eq] + src_term + rec_term, subs=self.spacing_map) - time_range = TimeAxis(start=start, stop=stop, step=step) op(time=time_range.num - 1, dt=time_range.step) return receivers.data From 985651b05aaa56464269a0c5713e777fe6d8b8a5 Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Fri, 9 Aug 2019 11:31:22 +0000 Subject: [PATCH 010/207] Merged PR 76: Unit fixes - Changed to use km/s instead of m/s for velocities - Fixed CLI interface --- deepseismic/cli/forward.py | 29 ++++++++++++++++------ deepseismic/cli/velocity.py | 10 ++++---- deepseismic/forward/models.py | 33 ++++++++++--------------- deepseismic/forward/subdomains.py | 7 +++--- deepseismic/velocity/roeth_tarantola.py | 18 +++++++------- 5 files changed, 51 insertions(+), 46 deletions(-) diff --git a/deepseismic/cli/forward.py b/deepseismic/cli/forward.py index cad0ff7b..84763451 100644 --- a/deepseismic/cli/forward.py +++ b/deepseismic/cli/forward.py @@ -13,12 +13,22 @@ @click.argument("input", type=click.Path()) @click.argument("output", type=click.Path()) @click.option( - "-d", "--duration", default=1000.0, type=float, help="Simulation duration" + "-d", + "--duration", + default=1000.0, + type=float, + help="Simulation duration (in ms)", ) -@click.option("-dt", type=float, help="Time increment") +@click.option("-dt", type=float, help="Time increment (in ms)") @click.option( "--n-pml", default=10, type=int, help="PML size (in grid points)" ) +@click.option( + "--n-receivers", + default=11, + type=int, + help="Number of receivers per horizontal dimension", +) @click.option("--space-order", default=2, type=int, help="Space order") @click.option( "--spacing", default=10.0, type=float, help="Spacing between grid points" @@ -30,6 +40,7 @@ def fwd( duration: float, input: str, n_pml: int, + n_receivers: int, output: str, space_order: int, spacing: float, @@ -39,7 +50,8 @@ def fwd( ctx.obj["dt"] = dt ctx.obj["duration"] = duration ctx.obj["input_file"] = h5py.File(input, mode="r") - ctx.obj["npml"] = n_pml + ctx.obj["n_pml"] = n_pml + ctx.obj["n_receivers"] = n_receivers ctx.obj["output_file"] = h5py.File(output, mode="w") ctx.obj["space_order"] = space_order ctx.obj["spacing"] = spacing @@ -72,7 +84,7 @@ def ricker(ctx, f0: float): ), vp=vp[()], space_order=ctx.obj["space_order"], - npml=ctx.obj["npml"], + n_pml=ctx.obj["n_pml"], ) else: model.vp = vp[()] @@ -92,17 +104,18 @@ def ricker(ctx, f0: float): np.array(model.domain_size) * 0.5 ) source.coordinates.data[0, -1] = 0.0 - n_receivers = 11 ** (len(model.domain_size) - 1) + n_receivers = ctx.obj["n_receivers"] + total_receivers = n_receivers ** (len(model.shape) - 1) receivers = Receiver( name="receivers", grid=model.grid, - npoint=n_receivers, + npoint=total_receivers, time_range=time_range, ) receivers_coords = np.meshgrid( *( - np.linspace(start=0, stop=s, num=11) - for s in model.domain_size + np.linspace(start=0, stop=s, num=n_receivers + 2)[1:-1] + for s in model.domain_size[:-1] ) ) for d in range(len(receivers_coords)): diff --git a/deepseismic/cli/velocity.py b/deepseismic/cli/velocity.py index b00e8b19..1c87c340 100644 --- a/deepseismic/cli/velocity.py +++ b/deepseismic/cli/velocity.py @@ -61,31 +61,31 @@ def vp( @vp.command() -@click.option("-l", "--layers", default=8, type=int, help="Number of layers") +@click.option("--n-layers", default=8, type=int, help="Number of layers") @click.option( "--initial-vp", default=(1350.0, 1650.0), type=(float, float), - help="Initial Vp", + help="Initial Vp (in km/s)", ) @click.option( "--vp-perturbation", default=(-190.0, 570.0), type=(float, float), - help="Per-layer Vp perturbation", + help="Per-layer Vp perturbation (in km/s)", ) @click.pass_context def rt( ctx, - layers: int, initial_vp: Tuple[float, float], + n_layers: int, vp_perturbation: Tuple[float, float], ): """Röth-Tarantola model""" model = RoethTarantolaGenerator( shape=ctx.obj["shape"], seed=ctx.obj["seed"], - nlayers=layers, + n_layers=n_layers, initial_vp=initial_vp, vp_perturbation=vp_perturbation, ) diff --git a/deepseismic/forward/models.py b/deepseismic/forward/models.py index 080a50ce..f07b7a1c 100644 --- a/deepseismic/forward/models.py +++ b/deepseismic/forward/models.py @@ -10,8 +10,6 @@ SubDomain, TimeFunction, logger, - mmax, - mmin, solve, ) @@ -29,18 +27,18 @@ def __init__( shape: Tuple[int, ...], origin: Tuple[float, ...], spacing: Tuple[float, ...], - npml: Optional[int] = 0, + n_pml: Optional[int] = 0, dtype: Optional[type] = np.float32, subdomains: Optional[Tuple[SubDomain]] = (), ): shape = tuple(int(x) for x in shape) origin = tuple(dtype(x) for x in origin) - npml = int(npml) - subdomains = tuple(subdomains) + (PhysicalDomain(npml),) - shape_pml = tuple(x + 2 * npml for x in shape) + n_pml = int(n_pml) + subdomains = tuple(subdomains) + (PhysicalDomain(n_pml),) + shape_pml = tuple(x + 2 * n_pml for x in shape) extent_pml = tuple(s * (d - 1) for s, d in zip(spacing, shape_pml)) origin_pml = tuple( - dtype(o - s * npml) for o, s in zip(origin, spacing) + dtype(o - s * n_pml) for o, s in zip(origin, spacing) ) self.grid = Grid( shape=shape_pml, @@ -49,17 +47,17 @@ def __init__( dtype=dtype, subdomains=subdomains, ) - self.npml = npml + self.n_pml = n_pml self.pml = Function(name="pml", grid=self.grid) pml_data = np.pad( np.zeros(shape, dtype=dtype), - [(npml,) * 2 for _ in range(self.pml.ndim)], + [(n_pml,) * 2 for _ in range(self.pml.ndim)], mode="edge", ) pml_coef = 1.5 * np.log(1000.0) / 40.0 for d in range(self.pml.ndim): - for i in range(npml): - pos = np.abs((npml - i + 1) / npml) + for i in range(n_pml): + pos = np.abs((n_pml - i + 1) / n_pml) val = pml_coef * (pos - np.sin(2 * np.pi * pos) / (2 * np.pi)) idx = [slice(0, x) for x in pml_data.shape] idx[d] = slice(i, i + 1) @@ -105,11 +103,11 @@ def __init__( spacing: Tuple[float, ...], vp: Union[float, np.ndarray], space_order: Optional[int] = None, - npml: Optional[int] = 0, + n_pml: Optional[int] = 0, dtype: Optional[type] = np.float32, subdomains: Optional[Tuple[SubDomain]] = (), ): - super().__init__(shape, origin, spacing, npml, dtype, subdomains) + super().__init__(shape, origin, spacing, n_pml, dtype, subdomains) if isinstance(vp, np.ndarray): assert space_order is not None self.m = Function( @@ -119,11 +117,6 @@ def __init__( self.m = Constant(name="m", value=1.0 / float(vp) ** 2.0) self.vp = vp - @property - def critical_dt(self): - coef = 0.38 if len(self.shape) == 3 else 0.42 - return self.dtype(coef * mmin(self.spacing) / mmax(self.vp)) - @property def vp(self) -> Union[float, np.ndarray]: return self._vp @@ -133,11 +126,11 @@ def vp(self, vp: Union[float, np.ndarray]) -> None: self._vp = vp if isinstance(vp, np.ndarray): pad_widths = [ - (self.npml + i.left, self.npml + i.right) + (self.n_pml + i.left, self.n_pml + i.right) for i in self.m._size_halo ] self.m.data_with_halo[:] = np.pad( - 1 / self.vp ** 2, pad_widths, mode="edge" + 1.0 / self.vp ** 2.0, pad_widths, mode="edge" ) else: self.m.data = 1.0 / float(vp) ** 2.0 diff --git a/deepseismic/forward/subdomains.py b/deepseismic/forward/subdomains.py index 6cfe3aa3..2ed6cedb 100644 --- a/deepseismic/forward/subdomains.py +++ b/deepseismic/forward/subdomains.py @@ -4,14 +4,13 @@ class PhysicalDomain(SubDomain): - name = "physical_domain" - def __init__(self, npml: int): + def __init__(self, n_pml: int): super().__init__() - self.npml = npml + self.n_pml = n_pml def define( self, dimensions: Iterable[Dimension] ) -> Dict[Dimension, Tuple[str, int, int]]: - return {d: ("middle", self.npml, self.npml) for d in dimensions} + return {d: ("middle", self.n_pml, self.n_pml) for d in dimensions} diff --git a/deepseismic/velocity/roeth_tarantola.py b/deepseismic/velocity/roeth_tarantola.py index db4ca207..6c3c0cc4 100644 --- a/deepseismic/velocity/roeth_tarantola.py +++ b/deepseismic/velocity/roeth_tarantola.py @@ -11,26 +11,26 @@ def __init__( shape: Tuple[int, ...], dtype: Optional[type] = np.float32, seed: Optional[int] = None, - horizontal_dim: Optional[int] = -1, - nlayers: Optional[int] = 8, - initial_vp: Optional[Tuple[float, float]] = (1350.0, 1650.0), - vp_perturbation: Optional[Tuple[float, float]] = (-190.0, 570.0), + depth_dim: Optional[int] = -1, + n_layers: Optional[int] = 8, + initial_vp: Optional[Tuple[float, float]] = (1.35, 1.65), + vp_perturbation: Optional[Tuple[float, float]] = (-0.19, 0.57), ): super().__init__(shape, dtype, seed) - self.horizontal_dim = horizontal_dim - self.nlayers = nlayers + self.depth_dim = depth_dim + self.n_layers = n_layers self.initial_vp = initial_vp self.vp_perturbation = vp_perturbation def generate(self) -> np.ndarray: vp = np.zeros(self.shape, dtype=self.dtype) - dim = self.horizontal_dim + dim = self.depth_dim layer_idx = np.round( - np.linspace(0, self.shape[dim], self.nlayers + 1) + np.linspace(0, self.shape[dim], self.n_layers + 1) ).astype(np.int) vp_idx = [slice(0, x) for x in vp.shape] layer_vp = None - for i in range(self.nlayers): + for i in range(self.n_layers): vp_idx[dim] = slice(layer_idx[i], layer_idx[i + 1]) layer_vp = ( self._prng.uniform(*self.initial_vp) From 73f060a6f9d5bc4b048ce902da37f130cecb1e43 Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Fri, 9 Aug 2019 14:19:55 +0000 Subject: [PATCH 011/207] Merged PR 78: Forward modelling CLI fix --- deepseismic/cli/forward.py | 91 +++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/deepseismic/cli/forward.py b/deepseismic/cli/forward.py index 84763451..0ef69d39 100644 --- a/deepseismic/cli/forward.py +++ b/deepseismic/cli/forward.py @@ -19,7 +19,7 @@ type=float, help="Simulation duration (in ms)", ) -@click.option("-dt", type=float, help="Time increment (in ms)") +@click.option("-dt", default=2.0, type=float, help="Time increment (in ms)") @click.option( "--n-pml", default=10, type=int, help="PML size (in grid points)" ) @@ -72,57 +72,48 @@ def ricker(ctx, f0: float): for dataset in input_group.values(): first_dataset = dataset break - model = None + model = VelocityModel( + shape=first_dataset.shape, + origin=tuple(0.0 for _ in first_dataset.shape), + spacing=tuple(ctx.obj["spacing"] for _ in first_dataset.shape), + vp=first_dataset[()], + space_order=ctx.obj["space_order"], + n_pml=ctx.obj["n_pml"], + ) + time_range = TimeAxis( + start=0.0, stop=ctx.obj["duration"], step=ctx.obj["dt"] + ) + source = RickerSource( + name="source", + grid=model.grid, + f0=f0, + npoint=1, + time_range=time_range, + ) + source.coordinates.data[0, :] = np.array(model.domain_size) * 0.5 + source.coordinates.data[0, -1] = 0.0 + n_receivers = ctx.obj["n_receivers"] + total_receivers = n_receivers ** (len(model.shape) - 1) + receivers = Receiver( + name="receivers", + grid=model.grid, + npoint=total_receivers, + time_range=time_range, + ) + receivers_coords = np.meshgrid( + *( + np.linspace(start=0, stop=s, num=n_receivers + 2)[1:-1] + for s in model.domain_size[:-1] + ) + ) + for d in range(len(receivers_coords)): + receivers.coordinates.data[:, d] = receivers_coords[ + d + ].flatten() + receivers.coordinates.data[:, -1] = 0.0 output_group = output_file.create_group(input_group_name) for input_dataset_name, vp in input_group.items(): - if model is None: - model = VelocityModel( - shape=first_dataset.shape, - origin=tuple(0.0 for _ in first_dataset.shape), - spacing=tuple( - ctx.obj["spacing"] for _ in first_dataset.shape - ), - vp=vp[()], - space_order=ctx.obj["space_order"], - n_pml=ctx.obj["n_pml"], - ) - else: - model.vp = vp[()] - time_range = TimeAxis( - start=0.0, - stop=ctx.obj["duration"], - step=ctx.obj.get("dt", model.critical_dt), - ) - source = RickerSource( - name="source", - grid=model.grid, - f0=f0, - npoint=1, - time_range=time_range, - ) - source.coordinates.data[0, :] = ( - np.array(model.domain_size) * 0.5 - ) - source.coordinates.data[0, -1] = 0.0 - n_receivers = ctx.obj["n_receivers"] - total_receivers = n_receivers ** (len(model.shape) - 1) - receivers = Receiver( - name="receivers", - grid=model.grid, - npoint=total_receivers, - time_range=time_range, - ) - receivers_coords = np.meshgrid( - *( - np.linspace(start=0, stop=s, num=n_receivers + 2)[1:-1] - for s in model.domain_size[:-1] - ) - ) - for d in range(len(receivers_coords)): - receivers.coordinates.data[:, d] = receivers_coords[ - d - ].flatten() - receivers.coordinates.data[:, -1] = 0.0 + model.vp = vp[()] seismograms = model.solve( source=source, receivers=receivers, time_range=time_range ) From efc19f32c152d1b60e5994ebefa26cc6756de0e6 Mon Sep 17 00:00:00 2001 From: Gianluca Campanella Date: Wed, 14 Aug 2019 11:21:18 +0000 Subject: [PATCH 012/207] Merged PR 85: Version 0.1.0 --- setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 80522e42..3d2cc7b3 100644 --- a/setup.py +++ b/setup.py @@ -19,12 +19,12 @@ "Topic :: Software Development", ], dependency_links=[ - "https://github.com/opesci/devito/archive/v3.4.tar.gz#egg=devito-3.4" + "https://github.com/opesci/devito/archive/v3.5.tar.gz#egg=devito-3.5" ], description="DeepSeismic", install_requires=[ "click==7.0", - "devito==3.4", + "devito==3.5", "h5py==2.9.0", "numpy==1.17.0", "scipy==1.3.0", @@ -43,5 +43,6 @@ setup_requires=["pytest-runner"], tests_require=["pytest"], url="https://github.com/microsoft/deepseismic", - version="0.0.1", + version="0.1.0", + zip_safe=False, ) From 358d992e02e778e6c40357d75f08ded49fb2957a Mon Sep 17 00:00:00 2001 From: msalvaris Date: Mon, 19 Aug 2019 09:39:22 +0100 Subject: [PATCH 013/207] Merging work on salt dataset --- .idea/vcs.xml | 6 + .../anaconda/local/environment.yml | 24 ++ .../environment/docker/apex/Makefile | 51 +++ .../environment/docker/apex/dockerfile | 16 + .../environment/docker/horovod/Makefile | 55 ++++ .../environment/docker/horovod/dockerfile | 126 ++++++++ .../tgs_salt/apex/configs/hrnet.yaml | 76 +++++ .../tgs_salt/apex/configs/unet.yaml | 34 ++ .../segmentation/tgs_salt/apex/default.py | 89 ++++++ .../segmentation/tgs_salt/apex/run.sh | 3 + .../segmentation/tgs_salt/apex/train.py | 290 +++++++++++++++++ .../tgs_salt/distributed/configs/hrnet.yaml | 76 +++++ .../tgs_salt/distributed/configs/unet.yaml | 34 ++ .../tgs_salt/distributed/default.py | 89 ++++++ .../segmentation/tgs_salt/distributed/run.sh | 3 + .../tgs_salt/distributed/train.py | 286 +++++++++++++++++ .../tgs_salt/horovod/configs/hrnet.yaml | 76 +++++ .../tgs_salt/horovod/configs/unet.yaml | 34 ++ .../segmentation/tgs_salt/horovod/default.py | 94 ++++++ .../segmentation/tgs_salt/horovod/run.sh | 3 + .../segmentation/tgs_salt/horovod/train.py | 291 ++++++++++++++++++ .../tgs_salt/local/configs/hrnet.yaml | 73 +++++ .../tgs_salt/local/configs/unet.yaml | 32 ++ .../segmentation/tgs_salt/local/default.py | 81 +++++ .../segmentation/tgs_salt/local/run.sh | 5 + .../segmentation/tgs_salt/local/train.py | 168 ++++++++++ .../segmentation/tgs_salt/tasks.py | 0 logging.conf | 34 ++ 28 files changed, 2149 insertions(+) create mode 100644 .idea/vcs.xml create mode 100644 interpretation/environment/anaconda/local/environment.yml create mode 100644 interpretation/environment/docker/apex/Makefile create mode 100644 interpretation/environment/docker/apex/dockerfile create mode 100644 interpretation/environment/docker/horovod/Makefile create mode 100644 interpretation/environment/docker/horovod/dockerfile create mode 100644 interpretation/experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml create mode 100644 interpretation/experiments/segmentation/tgs_salt/apex/configs/unet.yaml create mode 100644 interpretation/experiments/segmentation/tgs_salt/apex/default.py create mode 100644 interpretation/experiments/segmentation/tgs_salt/apex/run.sh create mode 100644 interpretation/experiments/segmentation/tgs_salt/apex/train.py create mode 100644 interpretation/experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml create mode 100644 interpretation/experiments/segmentation/tgs_salt/distributed/configs/unet.yaml create mode 100644 interpretation/experiments/segmentation/tgs_salt/distributed/default.py create mode 100644 interpretation/experiments/segmentation/tgs_salt/distributed/run.sh create mode 100644 interpretation/experiments/segmentation/tgs_salt/distributed/train.py create mode 100644 interpretation/experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml create mode 100644 interpretation/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml create mode 100644 interpretation/experiments/segmentation/tgs_salt/horovod/default.py create mode 100644 interpretation/experiments/segmentation/tgs_salt/horovod/run.sh create mode 100644 interpretation/experiments/segmentation/tgs_salt/horovod/train.py create mode 100644 interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml create mode 100644 interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml create mode 100644 interpretation/experiments/segmentation/tgs_salt/local/default.py create mode 100644 interpretation/experiments/segmentation/tgs_salt/local/run.sh create mode 100644 interpretation/experiments/segmentation/tgs_salt/local/train.py create mode 100644 interpretation/experiments/segmentation/tgs_salt/tasks.py create mode 100644 logging.conf diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/interpretation/environment/anaconda/local/environment.yml b/interpretation/environment/anaconda/local/environment.yml new file mode 100644 index 00000000..341089e4 --- /dev/null +++ b/interpretation/environment/anaconda/local/environment.yml @@ -0,0 +1,24 @@ +name: cvseg +channels: + - conda-forge +dependencies: + - python=3.6 + - jupyter + - ipykernel + - torchvision + - pandas + - opencv-python + - scikit-learn + - pip: + - pytorch-ignite + - fire + - toolz + - tabulate==0.8.2 + - Jinja2 + - gitpython + - tensorflow # Installing for Tensorboard + - tensorboard + - tensorboardx + - tqdm + - invoke + - yacs diff --git a/interpretation/environment/docker/apex/Makefile b/interpretation/environment/docker/apex/Makefile new file mode 100644 index 00000000..57d2dabc --- /dev/null +++ b/interpretation/environment/docker/apex/Makefile @@ -0,0 +1,51 @@ +define PROJECT_HELP_MSG +Makefile to control project aml_dist +Usage: + help show this message + build build docker image to use as control plane + bash run bash inside runnin docker container + stop stop running docker container +endef +export PROJECT_HELP_MSG +PWD:=$(shell pwd) +PORT:=9999 +TBOARD_PORT:=6006 +IMAGE_NAME:=ignite_image +NAME:=ignite_container # Name of running container +DATA:=/mnt + +BASEDIR:=$(shell dirname $(shell dirname ${PWD})) + +local_code_volume:=-v $(BASEDIR):/workspace +volumes:=-v $(DATA):/data \ + -v ${HOME}/.bash_history:/root/.bash_history + + +help: + echo "$$PROJECT_HELP_MSG" | less + +build: + docker build -t $(IMAGE_NAME) -f dockerfile . + +run: + # Start docker running as daemon + docker run $(local_code_volume) $(volumes) $(setup_environment_file) \ + --shm-size="4g" \ + --runtime=nvidia \ + --name $(NAME) \ + -d \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e HIST_FILE=/root/.bash_history \ + -it $(IMAGE_NAME) + + docker exec -it $(NAME) bash + + +bash: + docker exec -it $(NAME) bash + +stop: + docker stop $(NAME) + docker rm $(NAME) + +.PHONY: help build run bash stop \ No newline at end of file diff --git a/interpretation/environment/docker/apex/dockerfile b/interpretation/environment/docker/apex/dockerfile new file mode 100644 index 00000000..3becd3c4 --- /dev/null +++ b/interpretation/environment/docker/apex/dockerfile @@ -0,0 +1,16 @@ +FROM pytorch/pytorch:nightly-devel-cuda10.0-cudnn7 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libglib2.0-0 \ + libsm6 \ + libxext6 \ + libxrender-dev + +RUN git clone https://github.com/NVIDIA/apex && \ + cd apex && \ + pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./ + +RUN pip install toolz pytorch-ignite torchvision pandas opencv-python fire tensorboardx scikit-learn yacs + +WORKDIR /workspace +CMD /bin/bash \ No newline at end of file diff --git a/interpretation/environment/docker/horovod/Makefile b/interpretation/environment/docker/horovod/Makefile new file mode 100644 index 00000000..0ea5ba7d --- /dev/null +++ b/interpretation/environment/docker/horovod/Makefile @@ -0,0 +1,55 @@ +define PROJECT_HELP_MSG +Makefile to control project aml_dist +Usage: + help show this message + build build docker image to use as control plane + bash run bash inside runnin docker container + stop stop running docker container +endef +export PROJECT_HELP_MSG +PWD:=$(shell pwd) +PORT:=9999 +TBOARD_PORT:=6006 +IMAGE_NAME:=horovod_image +NAME:=horovod_container # Name of running container +DATA:=/mnt + +BASEDIR:=$(shell dirname $(shell dirname ${PWD})) + +local_code_volume:=-v $(BASEDIR):/workspace +volumes:=-v $(DATA):/data \ + -v ${HOME}/.bash_history:/root/.bash_history + +help: + echo "$$PROJECT_HELP_MSG" | less + +build: + docker build -t $(IMAGE_NAME) -f dockerfile . + +run: + @echo ${BASEDIR} + # Start docker running as daemon + docker run $(local_code_volume) $(volumes) $(setup_environment_file) \ + --privileged \ + --shm-size="4g" \ + --runtime=nvidia \ + --name $(NAME) \ + -d \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e HIST_FILE=/root/.bash_history \ + -it $(IMAGE_NAME) + + docker exec -it $(NAME) bash + + +run-horovod: + docker exec -it $(NAME) mpirun -np 2 -bind-to none -map-by slot -x NCCL_DEBUG=INFO -x LD_LIBRARY_PATH -x PATH -mca pml ob1 -mca btl ^openib python train_horovod.py + +bash: + docker exec -it $(NAME) bash + +stop: + docker stop $(NAME) + docker rm $(NAME) + +.PHONY: help build run bash stop \ No newline at end of file diff --git a/interpretation/environment/docker/horovod/dockerfile b/interpretation/environment/docker/horovod/dockerfile new file mode 100644 index 00000000..947c4089 --- /dev/null +++ b/interpretation/environment/docker/horovod/dockerfile @@ -0,0 +1,126 @@ +FROM nvidia/cuda:10.0-devel-ubuntu18.04 +# Based on default horovod image + +ENV PYTORCH_VERSION=1.1.0 +ENV TORCHVISION_VERSION=0.3.0 +ENV CUDNN_VERSION=7.6.0.64-1+cuda10.0 +ENV NCCL_VERSION=2.4.7-1+cuda10.0 + +# Python 2.7 or 3.6 is supported by Ubuntu Bionic out of the box +ARG python=3.6 +ENV PYTHON_VERSION=${python} + +# Set default shell to /bin/bash +SHELL ["/bin/bash", "-cu"] + +# We need gcc-4.9 to build plugins for TensorFlow & PyTorch, which is only available in Ubuntu Xenial +RUN echo deb http://archive.ubuntu.com/ubuntu xenial main universe | tee -a /etc/apt/sources.list +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends --allow-change-held-packages --allow-downgrades \ + build-essential \ + cmake \ + gcc-4.9 \ + g++-4.9 \ + gcc-4.9-base \ + software-properties-common \ + git \ + curl \ + wget \ + ca-certificates \ + libcudnn7=${CUDNN_VERSION} \ + libnccl2=${NCCL_VERSION} \ + libnccl-dev=${NCCL_VERSION} \ + libjpeg-dev \ + libpng-dev \ + python${PYTHON_VERSION} \ + python${PYTHON_VERSION}-dev \ + librdmacm1 \ + libibverbs1 \ + ibverbs-utils\ + ibutils \ + net-tools \ + ibverbs-providers \ + libglib2.0-0 \ + libsm6 \ + libxext6 \ + libxrender-dev + + +RUN if [[ "${PYTHON_VERSION}" == "3.6" ]]; then \ + apt-get install -y python${PYTHON_VERSION}-distutils; \ + fi +RUN ln -s /usr/bin/python${PYTHON_VERSION} /usr/bin/python + +RUN curl -O https://bootstrap.pypa.io/get-pip.py && \ + python get-pip.py && \ + rm get-pip.py + +# Install PyTorch +RUN pip install future typing +RUN pip install numpy +RUN pip install https://download.pytorch.org/whl/cu100/torch-${PYTORCH_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl \ + https://download.pytorch.org/whl/cu100/torchvision-${TORCHVISION_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl +RUN pip install --no-cache-dir torchvision h5py toolz pytorch-ignite pandas opencv-python fire tensorboardx scikit-learn tqdm yacs + +# Install Open MPI +RUN mkdir /tmp/openmpi && \ + cd /tmp/openmpi && \ + wget https://www.open-mpi.org/software/ompi/v4.0/downloads/openmpi-4.0.0.tar.gz && \ + tar zxf openmpi-4.0.0.tar.gz && \ + cd openmpi-4.0.0 && \ + ./configure --enable-orterun-prefix-by-default && \ + make -j $(nproc) all && \ + make install && \ + ldconfig && \ + rm -rf /tmp/openmpi + +# Pin GCC to 4.9 (priority 200) to compile correctly against TensorFlow, PyTorch, and MXNet. +# Backup existing GCC installation as priority 100, so that it can be recovered later. +RUN update-alternatives --install /usr/bin/gcc gcc $(readlink -f $(which gcc)) 100 && \ + update-alternatives --install /usr/bin/x86_64-linux-gnu-gcc x86_64-linux-gnu-gcc $(readlink -f $(which gcc)) 100 && \ + update-alternatives --install /usr/bin/g++ g++ $(readlink -f $(which g++)) 100 && \ + update-alternatives --install /usr/bin/x86_64-linux-gnu-g++ x86_64-linux-gnu-g++ $(readlink -f $(which g++)) 100 +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 200 && \ + update-alternatives --install /usr/bin/x86_64-linux-gnu-gcc x86_64-linux-gnu-gcc /usr/bin/gcc-4.9 200 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.9 200 && \ + update-alternatives --install /usr/bin/x86_64-linux-gnu-g++ x86_64-linux-gnu-g++ /usr/bin/g++-4.9 200 + + +# Install Horovod, temporarily using CUDA stubs +RUN ldconfig /usr/local/cuda/targets/x86_64-linux/lib/stubs && \ + HOROVOD_GPU_ALLREDUCE=NCCL HOROVOD_WITH_PYTORCH=1 pip install --no-cache-dir horovod && \ +ldconfig + +# Remove GCC pinning +RUN update-alternatives --remove gcc /usr/bin/gcc-4.9 && \ + update-alternatives --remove x86_64-linux-gnu-gcc /usr/bin/gcc-4.9 && \ + update-alternatives --remove g++ /usr/bin/g++-4.9 && \ + update-alternatives --remove x86_64-linux-gnu-g++ /usr/bin/g++-4.9 + +# Create a wrapper for OpenMPI to allow running as root by default +RUN mv /usr/local/bin/mpirun /usr/local/bin/mpirun.real && \ + echo '#!/bin/bash' > /usr/local/bin/mpirun && \ + echo 'mpirun.real --allow-run-as-root "$@"' >> /usr/local/bin/mpirun && \ + chmod a+x /usr/local/bin/mpirun + +# Configure OpenMPI to run good defaults: +# --bind-to none --map-by slot --mca btl_tcp_if_exclude lo,docker0 +RUN echo "hwloc_base_binding_policy = none" >> /usr/local/etc/openmpi-mca-params.conf && \ + echo "rmaps_base_mapping_policy = slot" >> /usr/local/etc/openmpi-mca-params.conf + # echo "btl_tcp_if_exclude = lo,docker0" >> /usr/local/etc/openmpi-mca-params.conf + +# Set default NCCL parameters +RUN echo NCCL_DEBUG=INFO >> /etc/nccl.conf && \ + echo NCCL_SOCKET_IFNAME=^docker0 >> /etc/nccl.conf + +# Install OpenSSH for MPI to communicate between containers +RUN apt-get install -y --no-install-recommends openssh-client openssh-server && \ + mkdir -p /var/run/sshd + +# Allow OpenSSH to talk to containers without asking for confirmation +RUN cat /etc/ssh/ssh_config | grep -v StrictHostKeyChecking > /etc/ssh/ssh_config.new && \ + echo " StrictHostKeyChecking no" >> /etc/ssh/ssh_config.new && \ + mv /etc/ssh/ssh_config.new /etc/ssh/ssh_config + +WORKDIR /workspace +CMD /bin/bash diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml b/interpretation/experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml new file mode 100644 index 00000000..b6082ed8 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml @@ -0,0 +1,76 @@ +# HRNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /workspace/logging.conf + +DATASET: + NUM_CLASSES: 1 + ROOT: /data/tgssalt + +MODEL: + NAME: seg_hrnet + PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 64 diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/configs/unet.yaml b/interpretation/experiments/segmentation/tgs_salt/apex/configs/unet.yaml new file mode 100644 index 00000000..858bc587 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/apex/configs/unet.yaml @@ -0,0 +1,34 @@ +# UNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /workspace/logging.conf + +DATASET: + NUM_CLASSES: 1 + ROOT: /data/tgssalt + +MODEL: + NAME: resnet_unet + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 32 diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/default.py b/interpretation/experiments/segmentation/tgs_salt/apex/default.py new file mode 100644 index 00000000..79e679b7 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/apex/default.py @@ -0,0 +1,89 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from yacs.config import CfgNode as CN + + +_C = CN() + +_C.OUTPUT_DIR = "" +_C.LOG_DIR = "" +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +_C.LOG_CONFIG = "logging.conf" + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "/mnt/tgssalt" +_C.DATASET.NUM_CLASSES = 1 + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "seg_hrnet" +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + +_C.LOSS = CN() +_C.LOSS.WEIGHTS = (0.01, 1) +_C.LOSS.ADJUST_EPOCH = 50 +_C.LOSS.ADJUSTED_WEIGHTS = (0.3, 0.7) + + +# training +_C.TRAIN = CN() +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.PAD_LEFT = 27 +_C.TRAIN.PAD_RIGHT = 27 +_C.TRAIN.FINE_SIZE = 202 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.SAVE_LOCATION = "/tmp/models" + +# testing +_C.TEST = CN() +_C.TEST.BATCH_SIZE_PER_GPU = 32 +_C.TEST.CV = CN() +_C.TEST.CV.N_SPLITS = 5 +_C.TEST.CV.SEED = 42 +_C.TEST.CV.SHUFFLE = True + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/run.sh b/interpretation/experiments/segmentation/tgs_salt/apex/run.sh new file mode 100644 index 00000000..c9f6ba98 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/apex/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +export PYTHONPATH=/workspace:$PYTHONPATH +python -m torch.distributed.launch --nproc_per_node=2 train.py --cfg configs/unet.yaml \ No newline at end of file diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/train.py b/interpretation/experiments/segmentation/tgs_salt/apex/train.py new file mode 100644 index 00000000..0f187474 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/apex/train.py @@ -0,0 +1,290 @@ +"""Train models on TGS salt dataset + +Trains models using PyTorch DistributedDataParallel +Uses a warmup schedule that then goes into a cyclic learning rate +Uses a weighted combination of Lovasz and BCE loss +""" + +import logging +import logging.config +import os + +import fire +import torch +import torch.nn.functional as F +from apex import amp +from apex.parallel import DistributedDataParallel +from default import _C as config +from default import update_config +from ignite.contrib.handlers import ( + CustomPeriodicEvent, + CosineAnnealingScheduler, + LinearCyclicalScheduler, + ConcatScheduler, +) +from ignite.engine import Events +from toolz import curry + +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) + get_distributed_data_loaders, + kfold_split, + prepare_train_batch, + prepare_val_batch, +) + create_supervised_evaluator, + create_supervised_trainer_apex) + + +@curry +def update_sampler_epoch(data_loader, engine): + data_loader.sampler.epoch = engine.state.epoch + + +class CombinedLoss: + """Creates a function that calculates weighted combined loss + """ + + def __init__(self, loss_functions, weights): + """Initialise CombinedLoss + + Args: + loss_functions (list): A list of PyTorch loss functions + weights (list[int]): A list of weights to use when combining loss functions + """ + self._losses = loss_functions + self.weights = weights + + def __call__(self, input, target): + # if weight is zero remove loss from calculations + loss_functions_and_weights = filter( + lambda l_w: l_w[1] > 0, zip(self._losses, self.weights) + ) + loss_list = [ + weight * loss(input, target) for loss, weight in loss_functions_and_weights + ] + combined_loss = torch.stack(loss_list).sum() + return combined_loss + + +@curry +def adjust_loss(loss_obj, weights, engine): + loss_obj.weights = weights + + +def run(*options, cfg=None, local_rank=0): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + silence_other_ranks = True + logging.config.fileConfig(config.LOG_CONFIG) + world_size = int(os.environ.get("WORLD_SIZE", 1)) + distributed = world_size > 1 + + if distributed: + # FOR DISTRIBUTED: Set the device according to local_rank. + torch.cuda.set_device(local_rank) + + # FOR DISTRIBUTED: Initialize the backend. torch.distributed.launch will provide + # environment variables, and requires that you use init_method=`env://`. + torch.distributed.init_process_group(backend="nccl", init_method="env://") + + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + train_ids, _, _ = get_data_ids( + config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" + ) + fold_generator = kfold_split( + train_ids, + n_splits=config.TEST.CV.N_SPLITS, + random_state=config.TEST.CV.SEED, + shuffle=config.TEST.CV.SHUFFLE, + ) + train_idx, val_idx = next(fold_generator) + val_ids = train_ids[val_idx] + train_ids = train_ids[train_idx] + + train_loader, val_loader = get_distributed_data_loaders( + train_ids, + val_ids, + config.TRAIN.BATCH_SIZE_PER_GPU, + config.TEST.BATCH_SIZE_PER_GPU, + config.TRAIN.FINE_SIZE, + config.TRAIN.PAD_LEFT, + config.TRAIN.PAD_RIGHT, + local_rank, + world_size, + config.DATASET.ROOT, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + criterion = CombinedLoss( + (lovasz_hinge, F.binary_cross_entropy_with_logits), config.LOSS.WEIGHTS + ) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model.to(device) + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + model, optimizer = amp.initialize(model, optimizer, opt_level="O0") + # FOR DISTRIBUTED: After amp.initialize, wrap the model with + # apex.parallel.DistributedDataParallel. + model = DistributedDataParallel(model, delay_allreduce=True) + + summary_writer = create_summary_writer(log_dir=config.LOG_DIR) + snapshot_duration = scheduler_step * len(train_loader) + warmup_duration = 5 * len(train_loader) + warmup_scheduler = LinearCyclicalScheduler( + optimizer, + "lr", + start_value=config.TRAIN.MAX_LR, + end_value=config.TRAIN.MAX_LR * world_size, + cycle_size=10 * len(train_loader), + ) + cosine_scheduler = CosineAnnealingScheduler( + optimizer, + "lr", + config.TRAIN.MAX_LR * world_size, + config.TRAIN.MIN_LR * world_size, + snapshot_duration, + ) + + scheduler = ConcatScheduler( + schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] + ) + + trainer = create_supervised_trainer_apex( + model, optimizer, criterion, prepare_train_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles + trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) + + adjust_loss_event = CustomPeriodicEvent(n_epochs=config.LOSS.ADJUST_EPOCH) + adjust_loss_event.attach(trainer) + trainer.add_event_handler( + getattr(adjust_loss_event.Events, "EPOCHS_{}_COMPLETED".format(config.LOSS.ADJUST_EPOCH)), + adjust_loss(criterion, config.LOSS.ADJUSTED_WEIGHTS), + ) + + if silence_other_ranks & local_rank != 0: + logging.getLogger("ignite.engine.engine.Engine").setLevel(logging.WARNING) + + evaluator = create_supervised_evaluator( + model, + prepare_val_batch, + metrics={ + "kaggle": apex.KaggleMetric( + output_transform=lambda x: (x["y_pred"], x["mask"]) + ), + "nll": apex.LossMetric( + lovasz_hinge, + world_size, + config.TEST.BATCH_SIZE_PER_GPU, + output_transform=lambda x: (x["y_pred"], x["mask"]), + ), + }, + device=device, + output_transform=padded_val_transform( + config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE + ), + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + + if local_rank == 0: # Run only on master process + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Mask", "mask"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Pred", "y_pred"), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + config.OUTPUT_DIR, + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger = logging.getLogger(__name__) + logger.info("Starting training") + + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml b/interpretation/experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml new file mode 100644 index 00000000..738bc9f3 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml @@ -0,0 +1,76 @@ +# HRNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf + +DATASET: + NUM_CLASSES: 1 + ROOT: /mnt/tgssalt + +MODEL: + NAME: seg_hrnet + PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 64 diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/configs/unet.yaml b/interpretation/experiments/segmentation/tgs_salt/distributed/configs/unet.yaml new file mode 100644 index 00000000..86415d38 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/distributed/configs/unet.yaml @@ -0,0 +1,34 @@ +# UNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf + +DATASET: + NUM_CLASSES: 1 + ROOT: /mnt/tgssalt + +MODEL: + NAME: resnet_unet + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 32 diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/default.py b/interpretation/experiments/segmentation/tgs_salt/distributed/default.py new file mode 100644 index 00000000..79e679b7 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/distributed/default.py @@ -0,0 +1,89 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from yacs.config import CfgNode as CN + + +_C = CN() + +_C.OUTPUT_DIR = "" +_C.LOG_DIR = "" +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +_C.LOG_CONFIG = "logging.conf" + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "/mnt/tgssalt" +_C.DATASET.NUM_CLASSES = 1 + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "seg_hrnet" +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + +_C.LOSS = CN() +_C.LOSS.WEIGHTS = (0.01, 1) +_C.LOSS.ADJUST_EPOCH = 50 +_C.LOSS.ADJUSTED_WEIGHTS = (0.3, 0.7) + + +# training +_C.TRAIN = CN() +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.PAD_LEFT = 27 +_C.TRAIN.PAD_RIGHT = 27 +_C.TRAIN.FINE_SIZE = 202 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.SAVE_LOCATION = "/tmp/models" + +# testing +_C.TEST = CN() +_C.TEST.BATCH_SIZE_PER_GPU = 32 +_C.TEST.CV = CN() +_C.TEST.CV.N_SPLITS = 5 +_C.TEST.CV.SEED = 42 +_C.TEST.CV.SHUFFLE = True + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/run.sh b/interpretation/experiments/segmentation/tgs_salt/distributed/run.sh new file mode 100644 index 00000000..c5b667c3 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/distributed/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +export PYTHONPATH=/data/home/mat/repos/ignite_test:$PYTHONPATH +python -m torch.distributed.launch --nproc_per_node=8 train.py --cfg configs/hrnet.yaml \ No newline at end of file diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/train.py b/interpretation/experiments/segmentation/tgs_salt/distributed/train.py new file mode 100644 index 00000000..01eb0561 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/distributed/train.py @@ -0,0 +1,286 @@ +"""Train models on TGS salt dataset + +Trains models using PyTorch DistributedDataParallel +Uses a warmup schedule that then goes into a cyclic learning rate +Uses a weighted combination of Lovasz and BCE loss +""" + +import logging +import logging.config +import os + +import fire +import torch +import torch.nn.functional as F +from default import _C as config +from default import update_config +from ignite.contrib.handlers import ( + CustomPeriodicEvent, + CosineAnnealingScheduler, + LinearCyclicalScheduler, + ConcatScheduler, +) +from ignite.engine import Events +from toolz import curry + +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) + get_distributed_data_loaders, + kfold_split, + prepare_train_batch, + prepare_val_batch, +) + create_supervised_evaluator, + create_supervised_trainer, +) + + +@curry +def update_sampler_epoch(data_loader, engine): + data_loader.sampler.epoch = engine.state.epoch + + +class CombinedLoss: + """Creates a function that calculates weighted combined loss + """ + + def __init__(self, loss_functions, weights): + """Initialise CombinedLoss + + Args: + loss_functions (list): A list of PyTorch loss functions + weights (list[int]): A list of weights to use when combining loss functions + """ + self._losses = loss_functions + self.weights = weights + + def __call__(self, input, target): + # if weight is zero remove loss from calculations + loss_functions_and_weights = filter( + lambda l_w: l_w[1] > 0, zip(self._losses, self.weights) + ) + loss_list = [ + weight * loss(input, target) for loss, weight in loss_functions_and_weights + ] + combined_loss = torch.stack(loss_list).sum() + return combined_loss + + +@curry +def adjust_loss(loss_obj, weights, engine): + loss_obj.weights = weights + + +def run(*options, cfg=None, local_rank=0): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + silence_other_ranks = True + logging.config.fileConfig(config.LOG_CONFIG) + world_size = int(os.environ.get("WORLD_SIZE", 1)) + distributed = world_size > 1 + + if distributed: + # FOR DISTRIBUTED: Set the device according to local_rank. + torch.cuda.set_device(local_rank) + + # FOR DISTRIBUTED: Initialize the backend. torch.distributed.launch will provide + # environment variables, and requires that you use init_method=`env://`. + torch.distributed.init_process_group(backend="nccl", init_method="env://") + + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + train_ids, _, _ = get_data_ids( + config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" + ) + fold_generator = kfold_split( + train_ids, + n_splits=config.TEST.CV.N_SPLITS, + random_state=config.TEST.CV.SEED, + shuffle=config.TEST.CV.SHUFFLE, + ) + train_idx, val_idx = next(fold_generator) + val_ids = train_ids[val_idx] + train_ids = train_ids[train_idx] + + train_loader, val_loader = get_distributed_data_loaders( + train_ids, + val_ids, + config.TRAIN.BATCH_SIZE_PER_GPU, + config.TEST.BATCH_SIZE_PER_GPU, + config.TRAIN.FINE_SIZE, + config.TRAIN.PAD_LEFT, + config.TRAIN.PAD_RIGHT, + local_rank, + world_size, + config.DATASET.ROOT, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + criterion = CombinedLoss( + (lovasz_hinge, F.binary_cross_entropy_with_logits), config.LOSS.WEIGHTS + ) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model.to(device) + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[device], find_unused_parameters=True) + + summary_writer = create_summary_writer(log_dir=config.LOG_DIR) + snapshot_duration = scheduler_step * len(train_loader) + warmup_duration = 5 * len(train_loader) + warmup_scheduler = LinearCyclicalScheduler( + optimizer, + "lr", + start_value=config.TRAIN.MAX_LR, + end_value=config.TRAIN.MAX_LR * world_size, + cycle_size=10 * len(train_loader), + ) + cosine_scheduler = CosineAnnealingScheduler( + optimizer, + "lr", + config.TRAIN.MAX_LR * world_size, + config.TRAIN.MIN_LR * world_size, + snapshot_duration, + ) + + scheduler = ConcatScheduler( + schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] + ) + + trainer = create_supervised_trainer( + model, optimizer, criterion, prepare_train_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles + trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) + + adjust_loss_event = CustomPeriodicEvent(n_epochs=config.LOSS.ADJUST_EPOCH) + adjust_loss_event.attach(trainer) + trainer.add_event_handler( + getattr(adjust_loss_event.Events, "EPOCHS_{}_COMPLETED".format(config.LOSS.ADJUST_EPOCH)), + adjust_loss(criterion, config.LOSS.ADJUSTED_WEIGHTS), + ) + + if silence_other_ranks & local_rank != 0: + logging.getLogger("ignite.engine.engine.Engine").setLevel(logging.WARNING) + + evaluator = create_supervised_evaluator( + model, + prepare_val_batch, + metrics={ + "kaggle": apex.KaggleMetric( + output_transform=lambda x: (x["y_pred"], x["mask"]) + ), + "nll": apex.LossMetric( + lovasz_hinge, + world_size, + config.TEST.BATCH_SIZE_PER_GPU, + output_transform=lambda x: (x["y_pred"], x["mask"]), + ), + }, + device=device, + output_transform=padded_val_transform( + config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE + ), + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + + if local_rank == 0: # Run only on master process + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Mask", "mask"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Pred", "y_pred"), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + config.OUTPUT_DIR, + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger = logging.getLogger(__name__) + logger.info("Starting training") + + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml b/interpretation/experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml new file mode 100644 index 00000000..b6082ed8 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml @@ -0,0 +1,76 @@ +# HRNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /workspace/logging.conf + +DATASET: + NUM_CLASSES: 1 + ROOT: /data/tgssalt + +MODEL: + NAME: seg_hrnet + PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 64 diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml b/interpretation/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml new file mode 100644 index 00000000..858bc587 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml @@ -0,0 +1,34 @@ +# UNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /workspace/logging.conf + +DATASET: + NUM_CLASSES: 1 + ROOT: /data/tgssalt + +MODEL: + NAME: resnet_unet + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 32 diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/default.py b/interpretation/experiments/segmentation/tgs_salt/horovod/default.py new file mode 100644 index 00000000..0bfb6faf --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/horovod/default.py @@ -0,0 +1,94 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from yacs.config import CfgNode as CN + + +_C = CN() + +_C.OUTPUT_DIR = "" +_C.LOG_DIR = "" +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +_C.LOG_CONFIG = "logging.conf" +_C.SEED = 42 + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# HOROVOD related params +_C.HOROVOD = CN() +_C.HOROVOD.FP16 = False + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "/mnt/tgssalt" +_C.DATASET.NUM_CLASSES = 1 + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "seg_hrnet" +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + +_C.LOSS = CN() +_C.LOSS.WEIGHTS = (0.01, 1) +_C.LOSS.ADJUST_EPOCH = 50 +_C.LOSS.ADJUSTED_WEIGHTS = (0.3, 0.7) + + +# training +_C.TRAIN = CN() +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.PAD_LEFT = 27 +_C.TRAIN.PAD_RIGHT = 27 +_C.TRAIN.FINE_SIZE = 202 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.SAVE_LOCATION = "/tmp/models" + +# testing +_C.TEST = CN() +_C.TEST.BATCH_SIZE_PER_GPU = 32 +_C.TEST.CV = CN() +_C.TEST.CV.N_SPLITS = 5 +_C.TEST.CV.SEED = 42 +_C.TEST.CV.SHUFFLE = True + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/run.sh b/interpretation/experiments/segmentation/tgs_salt/horovod/run.sh new file mode 100644 index 00000000..5fa42adb --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/horovod/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +export PYTHONPATH=/workspace:$PYTHONPATH +mpirun -np 8 -bind-to none -map-by slot -x NCCL_DEBUG=INFO -x LD_LIBRARY_PATH -x PATH -mca pml ob1 -mca btl ^openib python train.py --cfg "/workspace/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/train.py b/interpretation/experiments/segmentation/tgs_salt/horovod/train.py new file mode 100644 index 00000000..26a6ecf1 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/horovod/train.py @@ -0,0 +1,291 @@ +"""Train models on TGS salt dataset + +Trains models using PyTorch DistributedDataParallel +Uses a warmup schedule that then goes into a cyclic learning rate +Uses a weighted combination of Lovasz and BCE loss +""" + +import logging +import logging.config + +import fire +import horovod.torch as hvd +import torch +import torch.nn.functional as F +from default import _C as config +from default import update_config +from ignite.contrib.handlers import ( + CustomPeriodicEvent, + CosineAnnealingScheduler, + LinearCyclicalScheduler, + ConcatScheduler, +) +from ignite.engine import Events +from toolz import curry + +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) + get_distributed_data_loaders, + kfold_split, + prepare_train_batch, + prepare_val_batch, +) + create_supervised_evaluator, + create_supervised_trainer, +) + + +@curry +def update_sampler_epoch(data_loader, engine): + data_loader.sampler.epoch = engine.state.epoch + + +class CombinedLoss: + """Creates a function that calculates weighted combined loss + """ + + def __init__(self, loss_functions, weights): + """Initialise CombinedLoss + + Args: + loss_functions (list): A list of PyTorch loss functions + weights (list[int]): A list of weights to use when combining loss functions + """ + self._losses = loss_functions + self.weights = weights + + def __call__(self, input, target): + # if weight is zero remove loss from calculations + loss_functions_and_weights = filter( + lambda l_w: l_w[1] > 0, zip(self._losses, self.weights) + ) + loss_list = [ + weight * loss(input, target) for loss, weight in loss_functions_and_weights + ] + combined_loss = torch.stack(loss_list).sum() + return combined_loss + + +@curry +def adjust_loss(loss_obj, weights, engine): + loss_obj.weights = weights + + +def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + hvd.init() + silence_other_ranks = True + logging.config.fileConfig(config.LOG_CONFIG) + + torch.manual_seed(config.SEED) + torch.cuda.set_device(hvd.local_rank()) + torch.cuda.manual_seed(config.SEED) + rank, world_size = hvd.rank(), hvd.size() + + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + train_ids, _, _ = get_data_ids( + config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" + ) + fold_generator = kfold_split( + train_ids, + n_splits=config.TEST.CV.N_SPLITS, + random_state=config.TEST.CV.SEED, + shuffle=config.TEST.CV.SHUFFLE, + ) + train_idx, val_idx = next(fold_generator) + val_ids = train_ids[val_idx] + train_ids = train_ids[train_idx] + + train_loader, val_loader = get_distributed_data_loaders( + train_ids, + val_ids, + config.TRAIN.BATCH_SIZE_PER_GPU, + config.TEST.BATCH_SIZE_PER_GPU, + config.TRAIN.FINE_SIZE, + config.TRAIN.PAD_LEFT, + config.TRAIN.PAD_RIGHT, + rank, + world_size, + config.DATASET.ROOT, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + criterion = CombinedLoss( + (lovasz_hinge, F.binary_cross_entropy_with_logits), config.LOSS.WEIGHTS + ) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + # Horovod: broadcast parameters & optimizer state. + hvd.broadcast_parameters(model.state_dict(), root_rank=0) + hvd.broadcast_optimizer_state(optimizer, root_rank=0) + + # Horovod: (optional) compression algorithm. + compression = hvd.Compression.fp16 if config.HOROVOD.FP16 else hvd.Compression.none + + # Horovod: wrap optimizer with DistributedOptimizer. + optimizer = hvd.DistributedOptimizer(optimizer, + named_parameters=model.named_parameters(), + compression=compression) + + summary_writer = create_summary_writer(log_dir=config.LOG_DIR) + snapshot_duration = scheduler_step * len(train_loader) + warmup_duration = 5 * len(train_loader) + warmup_scheduler = LinearCyclicalScheduler( + optimizer, + "lr", + start_value=config.TRAIN.MAX_LR, + end_value=config.TRAIN.MAX_LR * world_size, + cycle_size=10 * len(train_loader), + ) + cosine_scheduler = CosineAnnealingScheduler( + optimizer, + "lr", + config.TRAIN.MAX_LR * world_size, + config.TRAIN.MIN_LR * world_size, + snapshot_duration, + ) + + scheduler = ConcatScheduler( + schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] + ) + + trainer = create_supervised_trainer( + model, optimizer, criterion, prepare_train_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles + trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) + + adjust_loss_event = CustomPeriodicEvent(n_epochs=config.LOSS.ADJUST_EPOCH) + adjust_loss_event.attach(trainer) + trainer.add_event_handler( + getattr(adjust_loss_event.Events, "EPOCHS_{}_COMPLETED".format(config.LOSS.ADJUST_EPOCH)), + adjust_loss(criterion, config.LOSS.ADJUSTED_WEIGHTS), + ) + + if silence_other_ranks & rank != 0: + logging.getLogger("ignite.engine.engine.Engine").setLevel(logging.WARNING) + + evaluator = create_supervised_evaluator( + model, + prepare_val_batch, + metrics={ + "kaggle": horovod.KaggleMetric( + output_transform=lambda x: (x["y_pred"], x["mask"]) + ), + "nll": horovod.LossMetric( + lovasz_hinge, + world_size, + config.TEST.BATCH_SIZE_PER_GPU, + output_transform=lambda x: (x["y_pred"], x["mask"]), + ), + }, + device=device, + output_transform=padded_val_transform( + config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE + ), + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + + if rank == 0: # Run only on master process + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Mask", "mask"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Pred", "y_pred"), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + config.OUTPUT_DIR, + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger = logging.getLogger(__name__) + logger.info("Starting training") + + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml b/interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml new file mode 100644 index 00000000..7188d33f --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml @@ -0,0 +1,73 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 + +DATASET: + NUM_CLASSES: 1 + +MODEL: + NAME: seg_hrnet + PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 64 diff --git a/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml b/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml new file mode 100644 index 00000000..f418e783 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml @@ -0,0 +1,32 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 + +DATASET: + NUM_CLASSES: 1 + +MODEL: + NAME: unet + + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 64 diff --git a/interpretation/experiments/segmentation/tgs_salt/local/default.py b/interpretation/experiments/segmentation/tgs_salt/local/default.py new file mode 100644 index 00000000..888cc13d --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/local/default.py @@ -0,0 +1,81 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from yacs.config import CfgNode as CN + + +_C = CN() + +_C.OUTPUT_DIR = "" +_C.LOG_DIR = "" +_C.GPUS = (0,) +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +_C.RANK = 0 + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.NUM_CLASSES = 1 + + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "seg_hrnet" +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + + +# training +_C.TRAIN = CN() +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.PAD_LEFT = 27 +_C.TRAIN.PAD_RIGHT = 27 +_C.TRAIN.FINE_SIZE = 202 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.SAVE_LOCATION = "/tmp/models" + +# testing +_C.TEST = CN() +_C.TEST.BATCH_SIZE_PER_GPU = 32 + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/interpretation/experiments/segmentation/tgs_salt/local/run.sh b/interpretation/experiments/segmentation/tgs_salt/local/run.sh new file mode 100644 index 00000000..4c6de8b9 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/local/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash +export DATA=/mnt/tgssalt +export LOG_CONFIG=/data/home/mat/repos/ignite_test/logging.conf +export PYTHONPATH=/data/home/mat/repos/ignite_test:$PYTHONPATH +python train.py --cfg "/data/home/mat/repos/ignite_test/experiments/segmentation/tgs_salt/local/configs/unet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/tgs_salt/local/train.py b/interpretation/experiments/segmentation/tgs_salt/local/train.py new file mode 100644 index 00000000..a04774f9 --- /dev/null +++ b/interpretation/experiments/segmentation/tgs_salt/local/train.py @@ -0,0 +1,168 @@ +""" Train model on TGS Salt Dataset + +""" + +import logging +import logging.config + +import fire +import torch +from default import _C as config +from default import update_config +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss + +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) + get_data_loaders, + kfold_split, + prepare_train_batch, + prepare_val_batch, +) + create_supervised_evaluator, + create_supervised_trainer, +) + + +def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + fold_generator = kfold_split( + ) + train_idx, val_idx = next(fold_generator) + train_loader, val_loader = get_data_loaders( + train_ids, + val_ids, + config.TRAIN.BATCH_SIZE_PER_GPU, + config.TEST.BATCH_SIZE_PER_GPU, + config.TRAIN.FINE_SIZE, + config.TRAIN.PAD_LEFT, + config.TRAIN.PAD_RIGHT, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + criterion = torch.nn.BCEWithLogitsLoss() + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + summary_writer = create_summary_writer(log_dir=config.LOG_DIR) + snapshot_duration = scheduler_step * len(train_loader) + scheduler = CosineAnnealingScheduler( + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration + ) + + trainer = create_supervised_trainer( + model, optimizer, criterion, prepare_train_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + evaluator = create_supervised_evaluator( + model, + prepare_val_batch, + metrics={ + "kaggle": KaggleMetric(output_transform=lambda x: (x["y_pred"], x["mask"])), + "nll": Loss(criterion, output_transform=lambda x: (x["y_pred"], x["mask"])), + }, + device=device, + output_transform=padded_val_transform(config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE), + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Mask", "mask"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Pred", "y_pred"), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + config.OUTPUT_DIR, + "hrnet", + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger = logging.getLogger(__name__) + logger.info("Starting training") + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/interpretation/experiments/segmentation/tgs_salt/tasks.py b/interpretation/experiments/segmentation/tgs_salt/tasks.py new file mode 100644 index 00000000..e69de29b diff --git a/logging.conf b/logging.conf new file mode 100644 index 00000000..56334fc4 --- /dev/null +++ b/logging.conf @@ -0,0 +1,34 @@ +[loggers] +keys=root,__main__,event_handlers + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[logger___main__] +level=INFO +handlers=consoleHandler +qualname=__main__ +propagate=0 + +[logger_event_handlers] +level=INFO +handlers=consoleHandler +qualname=event_handlers +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + From 96c54a341404458913942fbba2d2506c1d927f13 Mon Sep 17 00:00:00 2001 From: msalvaris Date: Mon, 19 Aug 2019 10:15:02 +0100 Subject: [PATCH 014/207] Adds computer vision to dependencies --- README.md | 6 ++++++ interpretation/environment/anaconda/local/environment.yml | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2fdc39a0..e9133c4f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # DeepSeismic + +## Interpretation + +### Setting up Env + + ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. diff --git a/interpretation/environment/anaconda/local/environment.yml b/interpretation/environment/anaconda/local/environment.yml index 341089e4..d28570ed 100644 --- a/interpretation/environment/anaconda/local/environment.yml +++ b/interpretation/environment/anaconda/local/environment.yml @@ -1,4 +1,4 @@ -name: cvseg +name: seismic-interpetation channels: - conda-forge dependencies: @@ -11,6 +11,7 @@ dependencies: - scikit-learn - pip: - pytorch-ignite + - pytorch - fire - toolz - tabulate==0.8.2 @@ -22,3 +23,4 @@ dependencies: - tqdm - invoke - yacs + - "--editable=git+https://masalvar@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork/mat/dev@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib" From 84ca5e81420ad2b5d9f6d764aa6844f0d09a73d8 Mon Sep 17 00:00:00 2001 From: msalvaris Date: Mon, 19 Aug 2019 10:18:07 +0100 Subject: [PATCH 015/207] Updates dependecies --- interpretation/environment/anaconda/local/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interpretation/environment/anaconda/local/environment.yml b/interpretation/environment/anaconda/local/environment.yml index d28570ed..d9230e87 100644 --- a/interpretation/environment/anaconda/local/environment.yml +++ b/interpretation/environment/anaconda/local/environment.yml @@ -7,7 +7,7 @@ dependencies: - ipykernel - torchvision - pandas - - opencv-python + - opencv - scikit-learn - pip: - pytorch-ignite From 68e67c8925dea51505003bea9aabce8fbfa8a690 Mon Sep 17 00:00:00 2001 From: msalvaris Date: Mon, 19 Aug 2019 10:51:56 +0100 Subject: [PATCH 016/207] Update --- .../segmentation/tgs_salt/distributed/run.sh | 2 +- .../segmentation/tgs_salt/distributed/train.py | 7 ++++++- .../tgs_salt/local/configs/hrnet.yaml | 2 ++ .../segmentation/tgs_salt/local/configs/unet.yaml | 4 +++- .../segmentation/tgs_salt/local/default.py | 9 ++++++++- .../segmentation/tgs_salt/local/run.sh | 2 -- .../segmentation/tgs_salt/local/train.py | 15 +++++++++++++-- 7 files changed, 33 insertions(+), 8 deletions(-) diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/run.sh b/interpretation/experiments/segmentation/tgs_salt/distributed/run.sh index c5b667c3..d3f9b57a 100644 --- a/interpretation/experiments/segmentation/tgs_salt/distributed/run.sh +++ b/interpretation/experiments/segmentation/tgs_salt/distributed/run.sh @@ -1,3 +1,3 @@ #!/bin/bash export PYTHONPATH=/data/home/mat/repos/ignite_test:$PYTHONPATH -python -m torch.distributed.launch --nproc_per_node=8 train.py --cfg configs/hrnet.yaml \ No newline at end of file +python -m torch.distributed.launch --nproc_per_node=8 train.py --cfg configs/unet.yaml \ No newline at end of file diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/train.py b/interpretation/experiments/segmentation/tgs_salt/distributed/train.py index 01eb0561..5955ba89 100644 --- a/interpretation/experiments/segmentation/tgs_salt/distributed/train.py +++ b/interpretation/experiments/segmentation/tgs_salt/distributed/train.py @@ -207,6 +207,11 @@ def run(*options, cfg=None, local_rank=0): config.TEST.BATCH_SIZE_PER_GPU, output_transform=lambda x: (x["y_pred"], x["mask"]), ), + "pixa": apex.PixelwiseAccuracyMetric( + world_size, + config.TEST.BATCH_SIZE_PER_GPU, + output_transform=lambda x: (x["y_pred"], x["mask"]) + ), }, device=device, output_transform=padded_val_transform( @@ -239,7 +244,7 @@ def run(*options, cfg=None, local_rank=0): Events.EPOCH_COMPLETED, logging_handlers.log_metrics( "Validation results", - metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :"}, + metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :"}, ), ) evaluator.add_event_handler( diff --git a/interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml b/interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml index 7188d33f..81e8aca0 100644 --- a/interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml +++ b/interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml @@ -7,9 +7,11 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf DATASET: NUM_CLASSES: 1 + ROOT: /mnt/tgssalt MODEL: NAME: seg_hrnet diff --git a/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml b/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml index f418e783..1fdcd49c 100644 --- a/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml +++ b/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml @@ -7,9 +7,11 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf DATASET: NUM_CLASSES: 1 + ROOT: /mnt/tgssalt MODEL: NAME: unet @@ -20,7 +22,7 @@ TRAIN: BEGIN_EPOCH: 0 END_EPOCH: 300 MIN_LR: 0.001 - MAX_LR: 0.01 + MAX_LR: 0.1 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 PAD_LEFT: 27 diff --git a/interpretation/experiments/segmentation/tgs_salt/local/default.py b/interpretation/experiments/segmentation/tgs_salt/local/default.py index 888cc13d..535ad03b 100644 --- a/interpretation/experiments/segmentation/tgs_salt/local/default.py +++ b/interpretation/experiments/segmentation/tgs_salt/local/default.py @@ -21,7 +21,8 @@ _C.PRINT_FREQ = 20 _C.AUTO_RESUME = False _C.PIN_MEMORY = True -_C.RANK = 0 +_C.LOG_CONFIG = "logging.conf" +_C.SEED = 42 # Cudnn related params _C.CUDNN = CN() @@ -31,6 +32,7 @@ # DATASET related params _C.DATASET = CN() +_C.DATASET.ROOT = "/mnt/tgssalt" _C.DATASET.NUM_CLASSES = 1 @@ -59,6 +61,11 @@ # testing _C.TEST = CN() _C.TEST.BATCH_SIZE_PER_GPU = 32 +_C.TEST.CV = CN() +_C.TEST.CV.N_SPLITS = 5 +_C.TEST.CV.SEED = 42 +_C.TEST.CV.SHUFFLE = True + def update_config(cfg, options=None, config_file=None): diff --git a/interpretation/experiments/segmentation/tgs_salt/local/run.sh b/interpretation/experiments/segmentation/tgs_salt/local/run.sh index 4c6de8b9..1b209cea 100644 --- a/interpretation/experiments/segmentation/tgs_salt/local/run.sh +++ b/interpretation/experiments/segmentation/tgs_salt/local/run.sh @@ -1,5 +1,3 @@ #!/bin/bash -export DATA=/mnt/tgssalt -export LOG_CONFIG=/data/home/mat/repos/ignite_test/logging.conf export PYTHONPATH=/data/home/mat/repos/ignite_test:$PYTHONPATH python train.py --cfg "/data/home/mat/repos/ignite_test/experiments/segmentation/tgs_salt/local/configs/unet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/tgs_salt/local/train.py b/interpretation/experiments/segmentation/tgs_salt/local/train.py index a04774f9..3329f841 100644 --- a/interpretation/experiments/segmentation/tgs_salt/local/train.py +++ b/interpretation/experiments/segmentation/tgs_salt/local/train.py @@ -51,9 +51,18 @@ def run(*options, cfg=None): logging.config.fileConfig(config.LOG_CONFIG) scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + train_ids, _, _ = get_data_ids( + config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" + ) fold_generator = kfold_split( + train_ids, + n_splits=config.TEST.CV.N_SPLITS, + random_state=config.TEST.CV.SEED, + shuffle=config.TEST.CV.SHUFFLE, ) train_idx, val_idx = next(fold_generator) + val_ids = train_ids[val_idx] + train_ids = train_ids[train_idx] train_loader, val_loader = get_data_loaders( train_ids, val_ids, @@ -62,6 +71,7 @@ def run(*options, cfg=None): config.TRAIN.FINE_SIZE, config.TRAIN.PAD_LEFT, config.TRAIN.PAD_RIGHT, + config.DATASET.ROOT, ) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -110,6 +120,7 @@ def run(*options, cfg=None): metrics={ "kaggle": KaggleMetric(output_transform=lambda x: (x["y_pred"], x["mask"])), "nll": Loss(criterion, output_transform=lambda x: (x["y_pred"], x["mask"])), + "pixa": PixelwiseAccuracy(output_transform=lambda x: (x["y_pred"], x["mask"])) }, device=device, output_transform=padded_val_transform(config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE), @@ -122,7 +133,7 @@ def run(*options, cfg=None): Events.EPOCH_COMPLETED, logging_handlers.log_metrics( "Validation results", - metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :"}, + metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :"}, ), ) evaluator.add_event_handler( @@ -152,7 +163,7 @@ def snapshot_function(): checkpoint_handler = SnapshotHandler( config.OUTPUT_DIR, - "hrnet", + config.MODEL.NAME, snapshot_function, ) evaluator.add_event_handler( From 4aefd849cf7f8e0ba73216924d0c1cea022d81e0 Mon Sep 17 00:00:00 2001 From: masalvar Date: Mon, 19 Aug 2019 10:52:41 +0000 Subject: [PATCH 017/207] Updates the environemnt files --- .gitignore | 17 +++++++++++++++++ README.md | 14 ++++++++++++-- .../environment/anaconda/local/environment.yml | 8 +++++--- .../environment/anaconda/local/src/cv-lib | 1 + .../environment/docker/apex/dockerfile | 1 + .../environment/docker/horovod/dockerfile | 1 + .../segmentation/tgs_salt/local/run.sh | 4 ++-- .../experiments/segmentation/tgs_salt/tasks.py | 0 8 files changed, 39 insertions(+), 7 deletions(-) create mode 160000 interpretation/environment/anaconda/local/src/cv-lib mode change 100644 => 100755 interpretation/experiments/segmentation/tgs_salt/local/run.sh delete mode 100644 interpretation/experiments/segmentation/tgs_salt/tasks.py diff --git a/.gitignore b/.gitignore index e9e503ee..d702485c 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,20 @@ var/ venv/ venv.bak/ wheels/ + +*/.vscode/* +.vscode/settings.json +.vscode/launch.json + +.dev_env + +.azureml + +# Logs +*.tfevents.* +**/runs +**/log +**/output + +# +interpetation/anaconda/local/src \ No newline at end of file diff --git a/README.md b/README.md index e9133c4f..780a2828 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,20 @@ # DeepSeismic - ## Interpretation ### Setting up Env - +Go to interpretation/environments/anaconda/local/environment.yml +Edit the final line to add your Git username for the ComputerVision repo +Then run +```bash +conda env create -f environment.yml +``` +This will create the appropriate environment to run experiments + +The compute vision repo will be downloaded into the src folder under interpretation/environments/anaconda/local/ + +### Data +The scripts expect the data to be contained in /mnt/tgssalt ## Contributing diff --git a/interpretation/environment/anaconda/local/environment.yml b/interpretation/environment/anaconda/local/environment.yml index d9230e87..daecdef8 100644 --- a/interpretation/environment/anaconda/local/environment.yml +++ b/interpretation/environment/anaconda/local/environment.yml @@ -1,8 +1,11 @@ -name: seismic-interpetation +name: seismic-interpretation channels: - conda-forge + - pytorch dependencies: - python=3.6 + - pip + - pytorch - jupyter - ipykernel - torchvision @@ -11,7 +14,6 @@ dependencies: - scikit-learn - pip: - pytorch-ignite - - pytorch - fire - toolz - tabulate==0.8.2 @@ -23,4 +25,4 @@ dependencies: - tqdm - invoke - yacs - - "--editable=git+https://masalvar@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork/mat/dev@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib" + - "--editable=git+https://masalvar@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib" diff --git a/interpretation/environment/anaconda/local/src/cv-lib b/interpretation/environment/anaconda/local/src/cv-lib new file mode 160000 index 00000000..c4939283 --- /dev/null +++ b/interpretation/environment/anaconda/local/src/cv-lib @@ -0,0 +1 @@ +Subproject commit c4939283ae7a187047f4e1a1b2f8ea5266d14731 diff --git a/interpretation/environment/docker/apex/dockerfile b/interpretation/environment/docker/apex/dockerfile index 3becd3c4..9ae06d5c 100644 --- a/interpretation/environment/docker/apex/dockerfile +++ b/interpretation/environment/docker/apex/dockerfile @@ -11,6 +11,7 @@ RUN git clone https://github.com/NVIDIA/apex && \ pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./ RUN pip install toolz pytorch-ignite torchvision pandas opencv-python fire tensorboardx scikit-learn yacs +RUN pip install --editable=git+https://masalvar@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib WORKDIR /workspace CMD /bin/bash \ No newline at end of file diff --git a/interpretation/environment/docker/horovod/dockerfile b/interpretation/environment/docker/horovod/dockerfile index 947c4089..5af37d1a 100644 --- a/interpretation/environment/docker/horovod/dockerfile +++ b/interpretation/environment/docker/horovod/dockerfile @@ -61,6 +61,7 @@ RUN pip install numpy RUN pip install https://download.pytorch.org/whl/cu100/torch-${PYTORCH_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl \ https://download.pytorch.org/whl/cu100/torchvision-${TORCHVISION_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl RUN pip install --no-cache-dir torchvision h5py toolz pytorch-ignite pandas opencv-python fire tensorboardx scikit-learn tqdm yacs +RUN pip install --editable=git+https://masalvar@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib # Install Open MPI RUN mkdir /tmp/openmpi && \ diff --git a/interpretation/experiments/segmentation/tgs_salt/local/run.sh b/interpretation/experiments/segmentation/tgs_salt/local/run.sh old mode 100644 new mode 100755 index 1b209cea..df3fd99e --- a/interpretation/experiments/segmentation/tgs_salt/local/run.sh +++ b/interpretation/experiments/segmentation/tgs_salt/local/run.sh @@ -1,3 +1,3 @@ #!/bin/bash -export PYTHONPATH=/data/home/mat/repos/ignite_test:$PYTHONPATH -python train.py --cfg "/data/home/mat/repos/ignite_test/experiments/segmentation/tgs_salt/local/configs/unet.yaml" \ No newline at end of file +export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH +python train.py --cfg "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/tgs_salt/tasks.py b/interpretation/experiments/segmentation/tgs_salt/tasks.py deleted file mode 100644 index e69de29b..00000000 From d4fd649ef417efc914fd4f841c4f51962de3e553 Mon Sep 17 00:00:00 2001 From: masalvar Date: Mon, 19 Aug 2019 11:14:28 +0000 Subject: [PATCH 018/207] Updates readme and envs --- README.md | 6 ++---- interpretation/environment/anaconda/local/environment.yml | 2 +- interpretation/environment/docker/apex/dockerfile | 1 - interpretation/environment/docker/horovod/dockerfile | 1 - 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 780a2828..c89383d8 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,9 @@ ## Interpretation ### Setting up Env -Go to interpretation/environments/anaconda/local/environment.yml -Edit the final line to add your Git username for the ComputerVision repo -Then run +Run ```bash -conda env create -f environment.yml +conda env create -f DeepSeismic/interpretation/environments/anaconda/local/environment.yml ``` This will create the appropriate environment to run experiments diff --git a/interpretation/environment/anaconda/local/environment.yml b/interpretation/environment/anaconda/local/environment.yml index daecdef8..a243d2c9 100644 --- a/interpretation/environment/anaconda/local/environment.yml +++ b/interpretation/environment/anaconda/local/environment.yml @@ -25,4 +25,4 @@ dependencies: - tqdm - invoke - yacs - - "--editable=git+https://masalvar@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib" + - "--editable=git+https://dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib" diff --git a/interpretation/environment/docker/apex/dockerfile b/interpretation/environment/docker/apex/dockerfile index 9ae06d5c..3becd3c4 100644 --- a/interpretation/environment/docker/apex/dockerfile +++ b/interpretation/environment/docker/apex/dockerfile @@ -11,7 +11,6 @@ RUN git clone https://github.com/NVIDIA/apex && \ pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./ RUN pip install toolz pytorch-ignite torchvision pandas opencv-python fire tensorboardx scikit-learn yacs -RUN pip install --editable=git+https://masalvar@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib WORKDIR /workspace CMD /bin/bash \ No newline at end of file diff --git a/interpretation/environment/docker/horovod/dockerfile b/interpretation/environment/docker/horovod/dockerfile index 5af37d1a..947c4089 100644 --- a/interpretation/environment/docker/horovod/dockerfile +++ b/interpretation/environment/docker/horovod/dockerfile @@ -61,7 +61,6 @@ RUN pip install numpy RUN pip install https://download.pytorch.org/whl/cu100/torch-${PYTORCH_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl \ https://download.pytorch.org/whl/cu100/torchvision-${TORCHVISION_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl RUN pip install --no-cache-dir torchvision h5py toolz pytorch-ignite pandas opencv-python fire tensorboardx scikit-learn tqdm yacs -RUN pip install --editable=git+https://masalvar@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib # Install Open MPI RUN mkdir /tmp/openmpi && \ From b12c01e76e6b8906bf00a36cad8368c5ff55893b Mon Sep 17 00:00:00 2001 From: masalvar Date: Wed, 21 Aug 2019 08:00:34 +0000 Subject: [PATCH 019/207] Initial running version of dutchf3 --- .gitignore | 5 +- .vscode/settings.json | 23 +- .../environment/anaconda/local/src/cv-lib | 2 +- .../segmentation/dutchf3/local/__init__.py | 0 .../dutchf3/local/augmentations.py | 183 +++++ .../dutchf3/local/configs/hrnet.yaml | 75 +++ .../dutchf3/local/configs/unet.yaml | 34 + .../segmentation/dutchf3/local/data.py | 76 +++ .../segmentation/dutchf3/local/default.py | 91 +++ .../dutchf3/local/loader/__init__.py | 9 + .../dutchf3/local/loader/data_loader.py | 202 ++++++ .../segmentation/dutchf3/local/loss.py | 12 + .../segmentation/dutchf3/local/metrics.py | 45 ++ .../dutchf3/local/models/__init__.py | 26 + .../dutchf3/local/models/patch_deconvnet.py | 632 ++++++++++++++++++ .../dutchf3/local/models/section_deconvnet.py | 632 ++++++++++++++++++ .../dutchf3/local/models/utils.py | 72 ++ .../segmentation/dutchf3/local/orig_train.py | 179 +++++ .../segmentation/dutchf3/local/run.sh | 3 + .../segmentation/dutchf3/local/train.py | 154 +++++ .../segmentation/dutchf3/local/utils.py | 20 + 21 files changed, 2470 insertions(+), 5 deletions(-) create mode 100644 interpretation/experiments/segmentation/dutchf3/local/__init__.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/augmentations.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml create mode 100644 interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml create mode 100644 interpretation/experiments/segmentation/dutchf3/local/data.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/default.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/loader/__init__.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/loader/data_loader.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/loss.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/metrics.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/models/__init__.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/models/patch_deconvnet.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/models/section_deconvnet.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/models/utils.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/orig_train.py create mode 100755 interpretation/experiments/segmentation/dutchf3/local/run.sh create mode 100644 interpretation/experiments/segmentation/dutchf3/local/train.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/utils.py diff --git a/.gitignore b/.gitignore index d702485c..206d6b04 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,7 @@ wheels/ **/output # -interpetation/anaconda/local/src \ No newline at end of file +interpretation/environment/anaconda/local/src/* +interpretation/environment/anaconda/local/src/cv-lib +.code-workspace.code-workspace +.vscode diff --git a/.vscode/settings.json b/.vscode/settings.json index 85fbc9f8..7cd20b62 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,23 @@ { + "python.jediEnabled": false, "[python]": { + "editor.defaultFormatter": "ms-python.python" + }, "python.formatting.provider": "black", - "python.linting.enabled": true, - "python.linting.flake8Enabled": true, - "python.linting.pylintEnabled": false, + "git.autofetch": true, + "python.linting.pylintEnabled": true, + "python.pythonPath": "/anaconda/envs/seismic-interpretation/bin/python", + "python.linting.pylintArgs": [ + "--enable=W0614" + ], + "window.zoomLevel": 0, + "autoDocstring.docstringFormat": "google", + "files.autoSave": "afterDelay", + "files.autoSaveDelay": 100, + "editor.suggestSelection": "first", + "vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue", + "remote.SSH.showLoginTerminal": true, + "docker.host": "tcp://msnarwal.eastus.cloudapp.azure.com:2375", + "terminal.integrated.env.linux":{ + "PYTHONPATH": "${env:PYTHONPATH}" + } } \ No newline at end of file diff --git a/interpretation/environment/anaconda/local/src/cv-lib b/interpretation/environment/anaconda/local/src/cv-lib index c4939283..2a3ebbba 160000 --- a/interpretation/environment/anaconda/local/src/cv-lib +++ b/interpretation/environment/anaconda/local/src/cv-lib @@ -1 +1 @@ -Subproject commit c4939283ae7a187047f4e1a1b2f8ea5266d14731 +Subproject commit 2a3ebbba65cd05f6db7796d367735184abdfdc50 diff --git a/interpretation/experiments/segmentation/dutchf3/local/__init__.py b/interpretation/experiments/segmentation/dutchf3/local/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/interpretation/experiments/segmentation/dutchf3/local/augmentations.py b/interpretation/experiments/segmentation/dutchf3/local/augmentations.py new file mode 100644 index 00000000..ba9c86ae --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/augmentations.py @@ -0,0 +1,183 @@ +import math +import numbers +import random +import numpy as np + +from PIL import Image, ImageOps, ImageChops + +class Compose(object): + def __init__(self, augmentations): + self.augmentations = augmentations + + def __call__(self, img, mask): + + img, mask = Image.fromarray(img, mode=None), Image.fromarray(mask, mode='L') + assert img.size == mask.size + + for a in self.augmentations: + img, mask = a(img, mask) + return np.array(img), np.array(mask, dtype=np.uint8) + +class AddNoise(object): + def __call__(self, img, mask): + noise = np.random.normal(loc=0,scale=0.02,size=(img.size[1], img.size[0])) + return img + noise, mask + +class RandomCrop(object): + def __init__(self, size, padding=0): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + self.padding = padding + + def __call__(self, img, mask): + if self.padding > 0: + img = ImageOps.expand(img, border=self.padding, fill=0) + mask = ImageOps.expand(mask, border=self.padding, fill=0) + + assert img.size == mask.size + w, h = img.size + th, tw = self.size + if w == tw and h == th: + return img, mask + if w < tw or h < th: + return img.resize((tw, th), Image.BILINEAR), mask.resize((tw, th), Image.NEAREST) + + x1 = random.randint(0, w - tw) + y1 = random.randint(0, h - th) + return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) + + +class CenterCrop(object): + def __init__(self, size): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + + def __call__(self, img, mask): + assert img.size == mask.size + w, h = img.size + th, tw = self.size + x1 = int(round((w - tw) / 2.)) + y1 = int(round((h - th) / 2.)) + return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) + + +class RandomHorizontallyFlip(object): + def __call__(self, img, mask): + if random.random() < 0.5: + #Note: we use FLIP_TOP_BOTTOM here intentionaly. Due to the dimensions of the image, + # it ends up being a horizontal flip. + return img.transpose(Image.FLIP_TOP_BOTTOM), mask.transpose(Image.FLIP_TOP_BOTTOM) + return img, mask + +class RandomVerticallyFlip(object): + def __call__(self, img, mask): + if random.random() < 0.5: + return img.transpose(Image.FLIP_LEFT_RIGHT), mask.transpose(Image.FLIP_LEFT_RIGHT) + return img, mask + +class FreeScale(object): + def __init__(self, size): + self.size = tuple(reversed(size)) # size: (h, w) + + def __call__(self, img, mask): + assert img.size == mask.size + return img.resize(self.size, Image.BILINEAR), mask.resize(self.size, Image.NEAREST) + + +class Scale(object): + def __init__(self, size): + self.size = size + + def __call__(self, img, mask): + assert img.size == mask.size + w, h = img.size + if (w >= h and w == self.size) or (h >= w and h == self.size): + return img, mask + if w > h: + ow = self.size + oh = int(self.size * h / w) + return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) + else: + oh = self.size + ow = int(self.size * w / h) + return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) + + +class RandomSizedCrop(object): + def __init__(self, size): + self.size = size + + def __call__(self, img, mask): + assert img.size == mask.size + for attempt in range(10): + area = img.size[0] * img.size[1] + target_area = random.uniform(0.45, 1.0) * area + aspect_ratio = random.uniform(0.5, 2) + + w = int(round(math.sqrt(target_area * aspect_ratio))) + h = int(round(math.sqrt(target_area / aspect_ratio))) + + if random.random() < 0.5: + w, h = h, w + + if w <= img.size[0] and h <= img.size[1]: + x1 = random.randint(0, img.size[0] - w) + y1 = random.randint(0, img.size[1] - h) + + img = img.crop((x1, y1, x1 + w, y1 + h)) + mask = mask.crop((x1, y1, x1 + w, y1 + h)) + assert (img.size == (w, h)) + + return img.resize((self.size, self.size), Image.BILINEAR), mask.resize((self.size, self.size), + Image.NEAREST) + + # Fallback + scale = Scale(self.size) + crop = CenterCrop(self.size) + return crop(*scale(img, mask)) + + +class RandomRotate(object): + def __init__(self, degree): + self.degree = degree + + def __call__(self, img, mask): + ''' + PIL automatically adds zeros to the borders of images that rotated. To fix this + issue, the code in the botton sets anywhere in the labels (mask) that is zero to + 255 (the value used for ignore_index). + ''' + rotate_degree = random.random() * 2 * self.degree - self.degree + + img = img.rotate(rotate_degree, Image.BILINEAR) + mask = mask.rotate(rotate_degree, Image.NEAREST) + + binary_mask = Image.fromarray(np.ones([mask.size[1], mask.size[0]])) + binary_mask = binary_mask.rotate(rotate_degree, Image.NEAREST) + binary_mask = np.array(binary_mask) + + mask_arr = np.array(mask) + mask_arr[binary_mask==0] = 255 + mask = Image.fromarray(mask_arr) + + return img, mask + +class RandomSized(object): + def __init__(self, size): + self.size = size + self.scale = Scale(self.size) + self.crop = RandomCrop(self.size) + + def __call__(self, img, mask): + assert img.size == mask.size + + w = int(random.uniform(0.5, 2) * img.size[0]) + h = int(random.uniform(0.5, 2) * img.size[1]) + + img, mask = img.resize((w, h), Image.BILINEAR), mask.resize((w, h), Image.NEAREST) + + return self.crop(*self.scale(img, mask)) \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml new file mode 100644 index 00000000..81e8aca0 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml @@ -0,0 +1,75 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf + +DATASET: + NUM_CLASSES: 1 + ROOT: /mnt/tgssalt + +MODEL: + NAME: seg_hrnet + PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 64 diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml new file mode 100644 index 00000000..70e8c56a --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml @@ -0,0 +1,34 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf + +DATASET: + NUM_CLASSES: 1 + ROOT: /mnt/tgssalt + +MODEL: + NAME: unet + + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.1 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 64 diff --git a/interpretation/experiments/segmentation/dutchf3/local/data.py b/interpretation/experiments/segmentation/dutchf3/local/data.py new file mode 100644 index 00000000..3738571b --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/data.py @@ -0,0 +1,76 @@ +from os import path +import numpy as np +import itertools +from sklearn.model_selection import train_test_split +import logging + +DATA_ROOT = path.join("/mnt", "alaudah") +SPLITS = path.join(DATA_ROOT, "splits") +LABELS = path.join(DATA_ROOT, "train", "train_labels.npy") + + + +def split_train_val(stride, per_val=0.2, loader_type="patch", labels_path=LABELS): + # create inline and crossline pacthes for training and validation: + print("hey") + logger = logging.getLogger(__name__) + + labels = np.load(labels_path) + iline, xline, depth = labels.shape + logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") + # INLINE PATCHES: ------------------------------------------------ + i_list = [] + horz_locations = range(0, xline - stride, stride) + vert_locations = range(0, depth - stride, stride) + logger.debug("Generating Inline patches") + logger.debug(horz_locations) + logger.debug(vert_locations) + for i in range(iline): + # for every inline: + # images are references by top-left corner: + locations = [[j, k] for j in horz_locations for k in vert_locations] + patches_list = [ + "i_" + str(i) + "_" + str(j) + "_" + str(k) for j, k in locations + ] + i_list.append(patches_list) + + # flatten the list + i_list = list(itertools.chain(*i_list)) + + # XLINE PATCHES: ------------------------------------------------ + x_list = [] + horz_locations = range(0, iline - stride, stride) + vert_locations = range(0, depth - stride, stride) + for j in range(xline): + # for every xline: + # images are references by top-left corner: + locations = [[i, k] for i in horz_locations for k in vert_locations] + patches_list = [ + "x_" + str(i) + "_" + str(j) + "_" + str(k) for i, k in locations + ] + x_list.append(patches_list) + + # flatten the list + x_list = list(itertools.chain(*x_list)) + + list_train_val = i_list + x_list + + # create train and test splits: + list_train, list_val = train_test_split( + list_train_val, test_size=per_val, shuffle=True + ) + + # write to files to disk: + file_object = open( + path.join(SPLITS, loader_type + "_train_val.txt"), "w" + ) + file_object.write("\n".join(list_train_val)) + file_object.close() + file_object = open( + path.join(SPLITS, loader_type + "_train.txt"), "w" + ) + file_object.write("\n".join(list_train)) + file_object.close() + file_object = open(path.join(SPLITS, loader_type + "_val.txt"), "w") + file_object.write("\n".join(list_val)) + file_object.close() diff --git a/interpretation/experiments/segmentation/dutchf3/local/default.py b/interpretation/experiments/segmentation/dutchf3/local/default.py new file mode 100644 index 00000000..9bef9f74 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/default.py @@ -0,0 +1,91 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from yacs.config import CfgNode as CN + + +_C = CN() + +_C.OUTPUT_DIR = "" +_C.LOG_DIR = "" +_C.GPUS = (0,) +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +_C.LOG_CONFIG = "/data/home/mat/repos/DeepSeismic/logging.conf" +_C.SEED = 42 + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "" +_C.DATASET.NUM_CLASSES = 1 +_C.DATASET.STRIDE = 50 +_C.DATASET.PATCH_SIZE = 99 +_C.DATASET.AUGMENTATION = True + + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "seg_hrnet" +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + + +# training +_C.TRAIN = CN() +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.PAD_LEFT = 27 +_C.TRAIN.PAD_RIGHT = 27 +_C.TRAIN.FINE_SIZE = 202 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.SAVE_LOCATION = "/tmp/models" + +# testing +_C.TEST = CN() +_C.TEST.BATCH_SIZE_PER_GPU = 32 +_C.TEST.CV = CN() +_C.TEST.CV.N_SPLITS = 5 +_C.TEST.CV.SEED = 42 +_C.TEST.CV.SHUFFLE = True + + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/interpretation/experiments/segmentation/dutchf3/local/loader/__init__.py b/interpretation/experiments/segmentation/dutchf3/local/loader/__init__.py new file mode 100644 index 00000000..fe189126 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/loader/__init__.py @@ -0,0 +1,9 @@ +from .data_loader import * + +def get_loader(arch): + if 'patch' in arch: + return patch_loader + elif 'section' in arch: + return section_loader + else: + NotImplementedError() \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/loader/data_loader.py b/interpretation/experiments/segmentation/dutchf3/local/loader/data_loader.py new file mode 100644 index 00000000..fe29e537 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/loader/data_loader.py @@ -0,0 +1,202 @@ +import os +import collections +import json +import torch +import numpy as np +import matplotlib.pyplot as plt +from os import path +from torch.utils import data + +DATA_ROOT = path.join("/mnt", "alaudah") +TRAIN_PATH = path.join(DATA_ROOT, "train") +TEST_PATH = path.join(DATA_ROOT, "test_once") +TRAIN_SEISMIC = path.join(TRAIN_PATH, "train_seismic.npy") +TRAIN_LABELS = path.join(TRAIN_PATH, "train_labels.npy") + +TEST1_SEISMIC = path.join(TEST_PATH, "test1_seismic.npy") +TEST2_SEISMIC = path.join(TEST_PATH, "test2_seismic.npy") + +TEST1_LABELS = path.join(TEST_PATH, "test1_labels.npy") +TEST2_LABELS = path.join(TEST_PATH, "test2_labels.npy") + + + +class PatchLoader(data.Dataset): + """ + Data loader for the patch-based deconvnet + """ + + def __init__( + self, stride=30, patch_size=99, is_transform=True, augmentations=None + ): + self.root = DATA_ROOT + self.is_transform = is_transform + self.augmentations = augmentations + self.n_classes = 6 + self.mean = 0.000941 # average of the training data + self.patches = collections.defaultdict(list) + self.patch_size = patch_size + self.stride = stride + + def pad_volume(self, volume): + """ + Only used for train/val!! Not test. + """ + return np.pad( + volume, + pad_width=self.patch_size, + mode="constant", + constant_values=255, + ) + + def __len__(self): + return len(self.patches[self.split]) + + def __getitem__(self, index): + + patch_name = self.patches[self.split][index] + direction, idx, xdx, ddx = patch_name.split(sep="_") + + shift = self.patch_size if "test" not in self.split else 0 + idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift + + if direction == "i": + im = self.seismic[ + idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size + ] + lbl = self.labels[ + idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size + ] + elif direction == "x": + im = self.seismic[ + idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size + ] + lbl = self.labels[ + idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size + ] + + if self.augmentations is not None: + im, lbl = self.augmentations(im, lbl) + + if self.is_transform: + im, lbl = self.transform(im, lbl) + return im, lbl + + def transform(self, img, lbl): + img -= self.mean + + # to be in the BxCxHxW that PyTorch uses: + img, lbl = img.T, lbl.T + + img = np.expand_dims(img, 0) + lbl = np.expand_dims(lbl, 0) + + img = torch.from_numpy(img) + img = img.float() + lbl = torch.from_numpy(lbl) + lbl = lbl.long() + + return img, lbl + + + +class TestPatchLoader(PatchLoader): + def __init__( + self, + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=TEST1_SEISMIC, + labels_path=TEST1_LABELS, + ): + super(TestPatchLoader, self).__init__( + stride=stride, + patch_size=patch_size, + is_transform=is_transform, + augmentations=augmentations, + ) + self.seismic = np.load(seismic_path) + self.labels = np.load(labels_path) + print(self.seismic.shape) + print(self.labels.shape) + # We are in test mode. Only read the given split. The other one might not + # be available. + self.split="test1" #TODO: Fix this can also be test2 + txt_path = path.join(DATA_ROOT, "splits", "patch_" + self.split + ".txt") + patch_list = tuple(open(txt_path, "r")) + self.patches[split] = patch_list + + + +class TrainPatchLoader(PatchLoader): + def __init__( + self, + split="train", + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=TRAIN_SEISMIC, + labels_path=TRAIN_LABELS, + ): + super(TrainPatchLoader, self).__init__( + stride=stride, + patch_size=patch_size, + is_transform=is_transform, + augmentations=augmentations, + ) + self.seismic = self.pad_volume(np.load(seismic_path)) + self.labels = self.pad_volume(np.load(labels_path)) + print(self.seismic.shape) + print(self.labels.shape) + # We are in train/val mode. Most likely the test splits are not saved yet, + # so don't attempt to load them. + self.split=split + for split in ["train", "val", "train_val"]: + # reading the file names for 'train', 'val', 'trainval'"" + txt_path = path.join(DATA_ROOT, "splits", "patch_" + split + ".txt") + patch_list = tuple(open(txt_path, "r")) + self.patches[split] = patch_list + +def get_seismic_labels(): + return np.asarray( + [ + [69, 117, 180], + [145, 191, 219], + [224, 243, 248], + [254, 224, 144], + [252, 141, 89], + [215, 48, 39], + ] + ) + + +def decode_segmap(label_mask, n_classes, plot=False): + """Decode segmentation class labels into a color image + Args: + label_mask (np.ndarray): an (M,N) array of integer values denoting + the class label at each spatial location. + plot (bool, optional): whether to show the resulting color image + in a figure. + Returns: + (np.ndarray, optional): the resulting decoded color image. + """ + label_colours = get_seismic_labels() + r = label_mask.copy() + g = label_mask.copy() + b = label_mask.copy() + for ll in range(0, n_classes): + r[label_mask == ll] = label_colours[ll, 0] + g[label_mask == ll] = label_colours[ll, 1] + b[label_mask == ll] = label_colours[ll, 2] + rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], 3)) + rgb[:, :, 0] = r / 255.0 + rgb[:, :, 1] = g / 255.0 + rgb[:, :, 2] = b / 255.0 + if plot: + plt.imshow(rgb) + plt.show() + else: + return rgb + diff --git a/interpretation/experiments/segmentation/dutchf3/local/loss.py b/interpretation/experiments/segmentation/dutchf3/local/loss.py new file mode 100644 index 00000000..248a9e36 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/loss.py @@ -0,0 +1,12 @@ +import torch +import torch.nn.functional as F +from toolz import curry + +@curry +def cross_entropy(prediction, target, weight=None, ignore_index=255): + ''' + Use 255 to fill empty values when padding or doing any augmentation operations + like rotation. + ''' + loss = F.cross_entropy(prediction, target, weight, reduction='mean', ignore_index=255) + return loss diff --git a/interpretation/experiments/segmentation/dutchf3/local/metrics.py b/interpretation/experiments/segmentation/dutchf3/local/metrics.py new file mode 100644 index 00000000..c096537c --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/metrics.py @@ -0,0 +1,45 @@ +import numpy as np + +class runningScore(object): + + def __init__(self, n_classes): + self.n_classes = n_classes + self.confusion_matrix = np.zeros((n_classes, n_classes)) + + def _fast_hist(self, label_true, label_pred, n_class): + mask = (label_true >= 0) & (label_true < n_class) + hist = np.bincount( + n_class * label_true[mask].astype(int) + + label_pred[mask], minlength=n_class**2).reshape(n_class, n_class) + return hist + + def update(self, label_trues, label_preds): + for lt, lp in zip(label_trues, label_preds): + self.confusion_matrix += self._fast_hist(lt.flatten(), lp.flatten(), self.n_classes) + + def get_scores(self): + """Returns accuracy score evaluation result. + - overall accuracy + - mean accuracy + - mean IU + - fwavacc + """ + hist = self.confusion_matrix + acc = np.diag(hist).sum() / hist.sum() + acc_cls = np.diag(hist) / hist.sum(axis=1) + mean_acc_cls = np.nanmean(acc_cls) + iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) + mean_iu = np.nanmean(iu) + freq = hist.sum(axis=1) / hist.sum() # fraction of the pixels that come from each class + fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() + cls_iu = dict(zip(range(self.n_classes), iu)) + + return {'Pixel Acc: ': acc, + 'Class Accuracy: ': acc_cls, + 'Mean Class Acc: ': mean_acc_cls, + 'Freq Weighted IoU: ': fwavacc, + 'Mean IoU: ': mean_iu, + 'confusion_matrix': self.confusion_matrix}, cls_iu + + def reset(self): + self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) diff --git a/interpretation/experiments/segmentation/dutchf3/local/models/__init__.py b/interpretation/experiments/segmentation/dutchf3/local/models/__init__.py new file mode 100644 index 00000000..7e582634 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/models/__init__.py @@ -0,0 +1,26 @@ +import torchvision.models as models +from .patch_deconvnet import * +from .section_deconvnet import * + +def get_model(name, pretrained, n_classes): + model = _get_model_instance(name) + + if name in ['section_deconvnet','patch_deconvnet']: + model = model(n_classes=n_classes) + vgg16 = models.vgg16(pretrained=pretrained) + model.init_vgg16_params(vgg16) + else: + model = model(n_classes=n_classes) + + return model + +def _get_model_instance(name): + try: + return { + 'section_deconvnet': section_deconvnet, + 'patch_deconvnet':patch_deconvnet, + 'section_deconvnet_skip': section_deconvnet_skip, + 'patch_deconvnet_skip':patch_deconvnet_skip, + }[name] + except: + print(f'Model {name} not available') diff --git a/interpretation/experiments/segmentation/dutchf3/local/models/patch_deconvnet.py b/interpretation/experiments/segmentation/dutchf3/local/models/patch_deconvnet.py new file mode 100644 index 00000000..d16b2356 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/models/patch_deconvnet.py @@ -0,0 +1,632 @@ +import torch.nn as nn + +class patch_deconvnet(nn.Module): + + def __init__(self, n_classes=4, learned_billinear=False): + super(patch_deconvnet, self).__init__() + self.learned_billinear = learned_billinear + self.n_classes = n_classes + self.unpool = nn.MaxUnpool2d(2, stride=2) + self.conv_block1 = nn.Sequential( + + # conv1_1 + nn.Conv2d(1, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv1_2 + nn.Conv2d(64, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool1 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_1 + + # 48*48 + + self.conv_block2 = nn.Sequential( + + # conv2_1 + nn.Conv2d(64, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv2_2 + nn.Conv2d(128, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool2 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_2 + + # 24*24 + + self.conv_block3 = nn.Sequential( + + # conv3_1 + nn.Conv2d(128, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_2 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_3 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool3 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_3 + + # 12*12 + + self.conv_block4 = nn.Sequential( + + # conv4_1 + nn.Conv2d(256, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool4 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_4 + + # 6*6 + + self.conv_block5 = nn.Sequential( + + # conv5_1 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool5 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_5 + + # 3*3 + + self.conv_block6 = nn.Sequential( + + # fc6 + nn.Conv2d(512, 4096, 3), + # set the filter size and nor padding to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 1*1 + + self.conv_block7 = nn.Sequential( + + # fc7 + nn.Conv2d(4096, 4096, 1), + # set the filter size to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.deconv_block8 = nn.Sequential( + + # fc6-deconv + nn.ConvTranspose2d(4096, 512, 3, stride=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 3*3 + + self.unpool_block9 = nn.Sequential( + + # unpool5 + nn.MaxUnpool2d(2, stride=2), ) + # usage unpool(output, indices) + + # 6*6 + + self.deconv_block10 = nn.Sequential( + + # deconv5_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_3 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block11 = nn.Sequential( + + # unpool4 + nn.MaxUnpool2d(2, stride=2), ) + + # 12*12 + + self.deconv_block12 = nn.Sequential( + + # deconv4_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_3 + nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block13 = nn.Sequential( + + # unpool3 + nn.MaxUnpool2d(2, stride=2), ) + + # 24*24 + + self.deconv_block14 = nn.Sequential( + + # deconv3_1 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_2 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_3 + nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block15 = nn.Sequential( + + # unpool2 + nn.MaxUnpool2d(2, stride=2), ) + + # 48*48 + + self.deconv_block16 = nn.Sequential( + + # deconv2_1 + nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv2_2 + nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block17 = nn.Sequential( + + # unpool1 + nn.MaxUnpool2d(2, stride=2), ) + + # 96*96 + + self.deconv_block18 = nn.Sequential( + + # deconv1_1 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv1_2 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.seg_score19 = nn.Sequential( + + # seg-score + nn.Conv2d(64, self.n_classes, 1), ) + + if self.learned_billinear: + raise NotImplementedError + + def forward(self, x): + size0 = x.size() + conv1, indices1 = self.conv_block1(x) + size1 = conv1.size() + conv2, indices2 = self.conv_block2(conv1) + size2 = conv2.size() + conv3, indices3 = self.conv_block3(conv2) + size3 = conv3.size() + conv4, indices4 = self.conv_block4(conv3) + size4 = conv4.size() + conv5, indices5 = self.conv_block5(conv4) + + conv6 = self.conv_block6(conv5) + conv7 = self.conv_block7(conv6) + conv8 = self.deconv_block8(conv7) + conv9 = self.unpool(conv8, indices5, output_size=size4) + conv10 = self.deconv_block10(conv9) + conv11 = self.unpool(conv10, indices4, output_size=size3) + conv12 = self.deconv_block12(conv11) + conv13 = self.unpool(conv12, indices3, output_size=size2) + conv14 = self.deconv_block14(conv13) + conv15 = self.unpool(conv14, indices2, output_size=size1) + conv16 = self.deconv_block16(conv15) + conv17 = self.unpool(conv16, indices1, output_size=size0) + conv18 = self.deconv_block18(conv17) + out = self.seg_score19(conv18) + + return out + + def init_vgg16_params(self, vgg16, copy_fc8=True): + blocks = [self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5] + + ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] + features = list(vgg16.features.children()) + i_layer = 0; + # copy convolutional filters from vgg16 + for idx, conv_block in enumerate(blocks): + for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): + if i_layer == 0: + l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, + 2, :, + :]) / 3.0).view( + l2.weight.size()) + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + else: + assert l1.weight.size() == l2.weight.size() + assert l1.bias.size() == l2.bias.size() + l2.weight.data = l1.weight.data + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + + +class patch_deconvnet_skip(nn.Module): + + def __init__(self, n_classes=4, learned_billinear=False): + super(patch_deconvnet_skip, self).__init__() + self.learned_billinear = learned_billinear + self.n_classes = n_classes + self.unpool = nn.MaxUnpool2d(2, stride=2) + self.conv_block1 = nn.Sequential( + + # conv1_1 + nn.Conv2d(1, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv1_2 + nn.Conv2d(64, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool1 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_1 + + # 48*48 + + self.conv_block2 = nn.Sequential( + + # conv2_1 + nn.Conv2d(64, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv2_2 + nn.Conv2d(128, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool2 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_2 + + # 24*24 + + self.conv_block3 = nn.Sequential( + + # conv3_1 + nn.Conv2d(128, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_2 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_3 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool3 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_3 + + # 12*12 + + self.conv_block4 = nn.Sequential( + + # conv4_1 + nn.Conv2d(256, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool4 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_4 + + # 6*6 + + self.conv_block5 = nn.Sequential( + + # conv5_1 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool5 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_5 + + # 3*3 + + self.conv_block6 = nn.Sequential( + + # fc6 + nn.Conv2d(512, 4096, 3), + # set the filter size and nor padding to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 1*1 + + self.conv_block7 = nn.Sequential( + + # fc7 + nn.Conv2d(4096, 4096, 1), + # set the filter size to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.deconv_block8 = nn.Sequential( + + # fc6-deconv + nn.ConvTranspose2d(4096, 512, 3, stride=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 3*3 + + self.unpool_block9 = nn.Sequential( + + # unpool5 + nn.MaxUnpool2d(2, stride=2), ) + # usage unpool(output, indices) + + # 6*6 + + self.deconv_block10 = nn.Sequential( + + # deconv5_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_3 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block11 = nn.Sequential( + + # unpool4 + nn.MaxUnpool2d(2, stride=2), ) + + # 12*12 + + self.deconv_block12 = nn.Sequential( + + # deconv4_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_3 + nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block13 = nn.Sequential( + + # unpool3 + nn.MaxUnpool2d(2, stride=2), ) + + # 24*24 + + self.deconv_block14 = nn.Sequential( + + # deconv3_1 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_2 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_3 + nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block15 = nn.Sequential( + + # unpool2 + nn.MaxUnpool2d(2, stride=2), ) + + # 48*48 + + self.deconv_block16 = nn.Sequential( + + # deconv2_1 + nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv2_2 + nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block17 = nn.Sequential( + + # unpool1 + nn.MaxUnpool2d(2, stride=2), ) + + # 96*96 + + self.deconv_block18 = nn.Sequential( + + # deconv1_1 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv1_2 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.seg_score19 = nn.Sequential( + + # seg-score + nn.Conv2d(64, self.n_classes, 1), ) + + if self.learned_billinear: + raise NotImplementedError + + def forward(self, x): + size0 = x.size() + conv1, indices1 = self.conv_block1(x) + size1 = conv1.size() + conv2, indices2 = self.conv_block2(conv1) + size2 = conv2.size() + conv3, indices3 = self.conv_block3(conv2) + size3 = conv3.size() + conv4, indices4 = self.conv_block4(conv3) + size4 = conv4.size() + conv5, indices5 = self.conv_block5(conv4) + + conv6 = self.conv_block6(conv5) + conv7 = self.conv_block7(conv6) + conv8 = self.deconv_block8(conv7) + conv5 + conv9 = self.unpool(conv8,indices5, output_size=size4) + conv10 = self.deconv_block10(conv9) + conv4 + conv11 = self.unpool(conv10,indices4, output_size=size3) + conv12 = self.deconv_block12(conv11) + conv3 + conv13 = self.unpool(conv12,indices3, output_size=size2) + conv14 = self.deconv_block14(conv13) + conv2 + conv15 = self.unpool(conv14,indices2, output_size=size1) + conv16 = self.deconv_block16(conv15) + conv1 + conv17 = self.unpool(conv16,indices1, output_size=size0) + conv18 = self.deconv_block18(conv17) + out = self.seg_score19(conv18) + + return out + + def init_vgg16_params(self, vgg16, copy_fc8=True): + blocks = [self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5] + + ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] + features = list(vgg16.features.children()) + i_layer = 0; + # copy convolutional filters from vgg16 + for idx, conv_block in enumerate(blocks): + for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): + if i_layer == 0: + l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, + 2, :, + :]) / 3.0).view( + l2.weight.size()) + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + else: + assert l1.weight.size() == l2.weight.size() + assert l1.bias.size() == l2.bias.size() + l2.weight.data = l1.weight.data + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 diff --git a/interpretation/experiments/segmentation/dutchf3/local/models/section_deconvnet.py b/interpretation/experiments/segmentation/dutchf3/local/models/section_deconvnet.py new file mode 100644 index 00000000..d95df346 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/models/section_deconvnet.py @@ -0,0 +1,632 @@ +import torch.nn as nn + +class section_deconvnet(nn.Module): + + def __init__(self, n_classes=4, learned_billinear=False): + super(section_deconvnet, self).__init__() + self.learned_billinear = learned_billinear + self.n_classes = n_classes + self.unpool = nn.MaxUnpool2d(2, stride=2) + self.conv_block1 = nn.Sequential( + + # conv1_1 + nn.Conv2d(1, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv1_2 + nn.Conv2d(64, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool1 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_1 + + # 48*48 + + self.conv_block2 = nn.Sequential( + + # conv2_1 + nn.Conv2d(64, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv2_2 + nn.Conv2d(128, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool2 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_2 + + # 24*24 + + self.conv_block3 = nn.Sequential( + + # conv3_1 + nn.Conv2d(128, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_2 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_3 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool3 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_3 + + # 12*12 + + self.conv_block4 = nn.Sequential( + + # conv4_1 + nn.Conv2d(256, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool4 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_4 + + # 6*6 + + self.conv_block5 = nn.Sequential( + + # conv5_1 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool5 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_5 + + # 3*3 + + self.conv_block6 = nn.Sequential( + + # fc6 + nn.Conv2d(512, 4096, 3), + # set the filter size and nor padding to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 1*1 + + self.conv_block7 = nn.Sequential( + + # fc7 + nn.Conv2d(4096, 4096, 1), + # set the filter size to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.deconv_block8 = nn.Sequential( + + # fc6-deconv + nn.ConvTranspose2d(4096, 512, 3, stride=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 3*3 + + self.unpool_block9 = nn.Sequential( + + # unpool5 + nn.MaxUnpool2d(2, stride=2), ) + # usage unpool(output, indices) + + # 6*6 + + self.deconv_block10 = nn.Sequential( + + # deconv5_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_3 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block11 = nn.Sequential( + + # unpool4 + nn.MaxUnpool2d(2, stride=2), ) + + # 12*12 + + self.deconv_block12 = nn.Sequential( + + # deconv4_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_3 + nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block13 = nn.Sequential( + + # unpool3 + nn.MaxUnpool2d(2, stride=2), ) + + # 24*24 + + self.deconv_block14 = nn.Sequential( + + # deconv3_1 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_2 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_3 + nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block15 = nn.Sequential( + + # unpool2 + nn.MaxUnpool2d(2, stride=2), ) + + # 48*48 + + self.deconv_block16 = nn.Sequential( + + # deconv2_1 + nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv2_2 + nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block17 = nn.Sequential( + + # unpool1 + nn.MaxUnpool2d(2, stride=2), ) + + # 96*96 + + self.deconv_block18 = nn.Sequential( + + # deconv1_1 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv1_2 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.seg_score19 = nn.Sequential( + + # seg-score + nn.Conv2d(64, self.n_classes, 1), ) + + if self.learned_billinear: + raise NotImplementedError + + def forward(self, x): + size0 = x.size() + conv1, indices1 = self.conv_block1(x) + size1 = conv1.size() + conv2, indices2 = self.conv_block2(conv1) + size2 = conv2.size() + conv3, indices3 = self.conv_block3(conv2) + size3 = conv3.size() + conv4, indices4 = self.conv_block4(conv3) + size4 = conv4.size() + conv5, indices5 = self.conv_block5(conv4) + + conv6 = self.conv_block6(conv5) + conv7 = self.conv_block7(conv6) + conv8 = self.deconv_block8(conv7) + conv9 = self.unpool(conv8,indices5, output_size=size4) + conv10 = self.deconv_block10(conv9) + conv11 = self.unpool(conv10,indices4, output_size=size3) + conv12 = self.deconv_block12(conv11) + conv13 = self.unpool(conv12,indices3, output_size=size2) + conv14 = self.deconv_block14(conv13) + conv15 = self.unpool(conv14,indices2, output_size=size1) + conv16 = self.deconv_block16(conv15) + conv17 = self.unpool(conv16,indices1, output_size=size0) + conv18 = self.deconv_block18(conv17) + out = self.seg_score19(conv18) + + return out + + def init_vgg16_params(self, vgg16, copy_fc8=True): + blocks = [self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5] + + ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] + features = list(vgg16.features.children()) + i_layer = 0; + # copy convolutional filters from vgg16 + for idx, conv_block in enumerate(blocks): + for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): + if i_layer == 0: + l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, + 2, :, + :]) / 3.0).view( + l2.weight.size()) + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + else: + assert l1.weight.size() == l2.weight.size() + assert l1.bias.size() == l2.bias.size() + l2.weight.data = l1.weight.data + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + + +class section_deconvnet_skip(nn.Module): + + def __init__(self, n_classes=4, learned_billinear=False): + super(section_deconvnet_skip, self).__init__() + self.learned_billinear = learned_billinear + self.n_classes = n_classes + self.unpool = nn.MaxUnpool2d(2, stride=2) + self.conv_block1 = nn.Sequential( + + # conv1_1 + nn.Conv2d(1, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv1_2 + nn.Conv2d(64, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool1 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_1 + + # 48*48 + + self.conv_block2 = nn.Sequential( + + # conv2_1 + nn.Conv2d(64, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv2_2 + nn.Conv2d(128, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool2 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_2 + + # 24*24 + + self.conv_block3 = nn.Sequential( + + # conv3_1 + nn.Conv2d(128, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_2 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_3 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool3 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_3 + + # 12*12 + + self.conv_block4 = nn.Sequential( + + # conv4_1 + nn.Conv2d(256, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool4 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_4 + + # 6*6 + + self.conv_block5 = nn.Sequential( + + # conv5_1 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool5 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_5 + + # 3*3 + + self.conv_block6 = nn.Sequential( + + # fc6 + nn.Conv2d(512, 4096, 3), + # set the filter size and nor padding to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 1*1 + + self.conv_block7 = nn.Sequential( + + # fc7 + nn.Conv2d(4096, 4096, 1), + # set the filter size to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.deconv_block8 = nn.Sequential( + + # fc6-deconv + nn.ConvTranspose2d(4096, 512, 3, stride=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 3*3 + + self.unpool_block9 = nn.Sequential( + + # unpool5 + nn.MaxUnpool2d(2, stride=2), ) + # usage unpool(output, indices) + + # 6*6 + + self.deconv_block10 = nn.Sequential( + + # deconv5_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_3 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block11 = nn.Sequential( + + # unpool4 + nn.MaxUnpool2d(2, stride=2), ) + + # 12*12 + + self.deconv_block12 = nn.Sequential( + + # deconv4_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_3 + nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block13 = nn.Sequential( + + # unpool3 + nn.MaxUnpool2d(2, stride=2), ) + + # 24*24 + + self.deconv_block14 = nn.Sequential( + + # deconv3_1 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_2 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_3 + nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block15 = nn.Sequential( + + # unpool2 + nn.MaxUnpool2d(2, stride=2), ) + + # 48*48 + + self.deconv_block16 = nn.Sequential( + + # deconv2_1 + nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv2_2 + nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block17 = nn.Sequential( + + # unpool1 + nn.MaxUnpool2d(2, stride=2), ) + + # 96*96 + + self.deconv_block18 = nn.Sequential( + + # deconv1_1 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv1_2 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.seg_score19 = nn.Sequential( + + # seg-score + nn.Conv2d(64, self.n_classes, 1), ) + + if self.learned_billinear: + raise NotImplementedError + + def forward(self, x): + size0 = x.size() + conv1, indices1 = self.conv_block1(x) + size1 = conv1.size() + conv2, indices2 = self.conv_block2(conv1) + size2 = conv2.size() + conv3, indices3 = self.conv_block3(conv2) + size3 = conv3.size() + conv4, indices4 = self.conv_block4(conv3) + size4 = conv4.size() + conv5, indices5 = self.conv_block5(conv4) + + conv6 = self.conv_block6(conv5) + conv7 = self.conv_block7(conv6) + conv8 = self.deconv_block8(conv7) + conv5 + conv9 = self.unpool(conv8,indices5, output_size=size4) + conv10 = self.deconv_block10(conv9) + conv4 + conv11 = self.unpool(conv10,indices4, output_size=size3) + conv12 = self.deconv_block12(conv11) + conv3 + conv13 = self.unpool(conv12,indices3, output_size=size2) + conv14 = self.deconv_block14(conv13) + conv2 + conv15 = self.unpool(conv14,indices2, output_size=size1) + conv16 = self.deconv_block16(conv15) + conv1 + conv17 = self.unpool(conv16,indices1, output_size=size0) + conv18 = self.deconv_block18(conv17) + out = self.seg_score19(conv18) + + return out + + def init_vgg16_params(self, vgg16, copy_fc8=True): + blocks = [self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5] + + ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] + features = list(vgg16.features.children()) + i_layer = 0; + # copy convolutional filters from vgg16 + for idx, conv_block in enumerate(blocks): + for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): + if i_layer == 0: + l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, + 2, :, + :]) / 3.0).view( + l2.weight.size()) + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + else: + assert l1.weight.size() == l2.weight.size() + assert l1.bias.size() == l2.bias.size() + l2.weight.data = l1.weight.data + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 diff --git a/interpretation/experiments/segmentation/dutchf3/local/models/utils.py b/interpretation/experiments/segmentation/dutchf3/local/models/utils.py new file mode 100644 index 00000000..359544b9 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/models/utils.py @@ -0,0 +1,72 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class conv2DBatchNorm(nn.Module): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): + super(conv2DBatchNorm, self).__init__() + + if dilation > 1: + conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias, dilation=dilation) + + else: + conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias, dilation=1) + + + self.cb_unit = nn.Sequential(conv_mod, + nn.BatchNorm2d(int(n_filters)),) + + def forward(self, inputs): + outputs = self.cb_unit(inputs) + return outputs + + +class deconv2DBatchNorm(nn.Module): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): + super(deconv2DBatchNorm, self).__init__() + + self.dcb_unit = nn.Sequential(nn.ConvTranspose2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias), + nn.BatchNorm2d(int(n_filters)),) + + def forward(self, inputs): + outputs = self.dcb_unit(inputs) + return outputs + + +class conv2DBatchNormRelu(nn.Module): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): + super(conv2DBatchNormRelu, self).__init__() + + if dilation > 1: + conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias, dilation=dilation) + + else: + conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias, dilation=1) + + self.cbr_unit = nn.Sequential(conv_mod, + nn.BatchNorm2d(int(n_filters)), + nn.ReLU(inplace=True),) + + def forward(self, inputs): + outputs = self.cbr_unit(inputs) + return outputs + + +class deconv2DBatchNormRelu(nn.Module): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): + super(deconv2DBatchNormRelu, self).__init__() + + self.dcbr_unit = nn.Sequential(nn.ConvTranspose2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias), + nn.BatchNorm2d(int(n_filters)), + nn.ReLU(inplace=True),) + + def forward(self, inputs): + outputs = self.dcbr_unit(inputs) + return outputs diff --git a/interpretation/experiments/segmentation/dutchf3/local/orig_train.py b/interpretation/experiments/segmentation/dutchf3/local/orig_train.py new file mode 100644 index 00000000..3329f841 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/orig_train.py @@ -0,0 +1,179 @@ +""" Train model on TGS Salt Dataset + +""" + +import logging +import logging.config + +import fire +import torch +from default import _C as config +from default import update_config +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss + +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) + get_data_loaders, + kfold_split, + prepare_train_batch, + prepare_val_batch, +) + create_supervised_evaluator, + create_supervised_trainer, +) + + +def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + train_ids, _, _ = get_data_ids( + config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" + ) + fold_generator = kfold_split( + train_ids, + n_splits=config.TEST.CV.N_SPLITS, + random_state=config.TEST.CV.SEED, + shuffle=config.TEST.CV.SHUFFLE, + ) + train_idx, val_idx = next(fold_generator) + val_ids = train_ids[val_idx] + train_ids = train_ids[train_idx] + train_loader, val_loader = get_data_loaders( + train_ids, + val_ids, + config.TRAIN.BATCH_SIZE_PER_GPU, + config.TEST.BATCH_SIZE_PER_GPU, + config.TRAIN.FINE_SIZE, + config.TRAIN.PAD_LEFT, + config.TRAIN.PAD_RIGHT, + config.DATASET.ROOT, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + criterion = torch.nn.BCEWithLogitsLoss() + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + summary_writer = create_summary_writer(log_dir=config.LOG_DIR) + snapshot_duration = scheduler_step * len(train_loader) + scheduler = CosineAnnealingScheduler( + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration + ) + + trainer = create_supervised_trainer( + model, optimizer, criterion, prepare_train_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + evaluator = create_supervised_evaluator( + model, + prepare_val_batch, + metrics={ + "kaggle": KaggleMetric(output_transform=lambda x: (x["y_pred"], x["mask"])), + "nll": Loss(criterion, output_transform=lambda x: (x["y_pred"], x["mask"])), + "pixa": PixelwiseAccuracy(output_transform=lambda x: (x["y_pred"], x["mask"])) + }, + device=device, + output_transform=padded_val_transform(config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE), + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Mask", "mask"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Pred", "y_pred"), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + config.OUTPUT_DIR, + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger = logging.getLogger(__name__) + logger.info("Starting training") + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/interpretation/experiments/segmentation/dutchf3/local/run.sh b/interpretation/experiments/segmentation/dutchf3/local/run.sh new file mode 100755 index 00000000..7a321950 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH +python train.py --cfg "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/train.py b/interpretation/experiments/segmentation/dutchf3/local/train.py new file mode 100644 index 00000000..5e67afbd --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/train.py @@ -0,0 +1,154 @@ +import logging +import logging.config +import fire +import os +from datetime import datetime + +import itertools + +import numpy as np +import torch +import torch.nn.functional as F +from sklearn.model_selection import train_test_split +from tensorboardX import SummaryWriter +from torch.utils import data +from tqdm import tqdm +from default import _C as config +from default import update_config +from loss import cross_entropy +import torchvision.utils as vutils +from augmentations import ( + Compose, + RandomHorizontallyFlip, + RandomRotate, + AddNoise, +) +from loader.data_loader import TrainPatchLoader +from metrics import runningScore +from models import get_model +from utils import np_to_tb +from data import split_train_val +from ignite.contrib.handlers import CosineAnnealingScheduler +from cv_lib.segmentation.dutchf3.engine import ( + create_supervised_trainer, +) +from ignite.utils import convert_tensor +from ignite.engine import Events +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from models import get_model + +def prepare_train_batch(batch, device=None, non_blocking=False): + x, y = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ) + + +def prepare_val_batch(batch, device=None, non_blocking=False): + x, y = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ) + +def run(*options, cfg=None): + fraction_validation=0.2 + update_config(config, options=options, config_file=cfg) + print(config.LOG_CONFIG) + logging.config.fileConfig(config.LOG_CONFIG) + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + + torch.manual_seed(2019) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(2019) + np.random.seed(seed=2019) + + # Generate the train and validation sets for the model: + split_train_val(config.DATASET.STRIDE, per_val=fraction_validation, loader_type="patch") + + # Setup Augmentations + if config.DATASET.AUGMENTATION: + data_aug = Compose( + [RandomRotate(10), RandomHorizontallyFlip(), AddNoise()] + ) + else: + data_aug = None + + train_set = TrainPatchLoader( + split="train", + is_transform=True, + stride=config.DATASET.STRIDE, + patch_size=config.DATASET.PATCH_SIZE, + augmentations=data_aug, + ) + + # Without Augmentation: + val_set = TrainPatchLoader( + split="train", + is_transform=True, + stride=config.DATASET.STRIDE, + patch_size=config.DATASET.PATCH_SIZE, + ) + + n_classes = train_set.n_classes + + train_loader = data.DataLoader( + train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=0, shuffle=True + ) + val_loader = data.DataLoader( + train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=0 + ) + + model = get_model("patch_deconvnet", False, n_classes) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model = model.to(device) # Send to GPU + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + # summary_writer = create_summary_writer(log_dir=config.LOG_DIR) + snapshot_duration = scheduler_step * len(train_set) + scheduler = CosineAnnealingScheduler( + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration + ) + + # weights are inversely proportional to the frequency of the classes in the training set + class_weights = torch.tensor( + [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852], + device=device, + requires_grad=False, + ) + + criterion = cross_entropy(weight=class_weights) + + trainer = create_supervised_trainer( + model, optimizer, criterion, prepare_train_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + + logger = logging.getLogger(__name__) + logger.info("Starting training") + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + +if __name__ == "__main__": + fire.Fire(run) diff --git a/interpretation/experiments/segmentation/dutchf3/local/utils.py b/interpretation/experiments/segmentation/dutchf3/local/utils.py new file mode 100644 index 00000000..85ad7e96 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/utils.py @@ -0,0 +1,20 @@ +import numpy as np +import torch +import torchvision.utils as vutils + +def np_to_tb(array): + # if 2D : + if array.ndim == 2: + # HW => CHW + array = np.expand_dims(array,axis=0) + # CHW => NCHW + array = np.expand_dims(array,axis=0) + elif array.ndim == 3: + # HWC => CHW + array = array.transpose(2, 0, 1) + # CHW => NCHW + array = np.expand_dims(array,axis=0) + + array = torch.from_numpy(array) + array = vutils.make_grid(array, normalize=True, scale_each=True) + return array From 3f08d75aada9152ae2922b3c54e0870472b60e37 Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Thu, 22 Aug 2019 16:21:56 -0400 Subject: [PATCH 020/207] INFRA: added structure templates. --- AUTHORS.md | 25 +++++++++++ CONTRIBUTING.md | 76 ++++++++++++++++++++++++++++++++++ LICENSE | 35 ++++++++-------- SETUP.md | 6 +++ benchmarks/README.md | 6 +++ benchmarks/benchmark_utils.py | 0 docs/README.md | 6 +++ setup.cfg => scripts/setup.cfg | 0 setup.py => scripts/setup.py | 0 scripts/setup.sh | 0 tests/README.md | 9 ++++ 11 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 AUTHORS.md create mode 100644 CONTRIBUTING.md create mode 100644 SETUP.md create mode 100644 benchmarks/README.md create mode 100644 benchmarks/benchmark_utils.py create mode 100644 docs/README.md rename setup.cfg => scripts/setup.cfg (100%) rename setup.py => scripts/setup.py (100%) create mode 100644 scripts/setup.sh create mode 100644 tests/README.md diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 00000000..f6dc11f3 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,25 @@ +Contributors to DeepSeismic +=========================== + +TODO: add why we're working on Deep Learning for Seismic Imaging and Interpretation. + +Maintainers (sorted alphabetically) +--------------------------------------- +Maintainers are actively supporting the project and have made substantial contributions to the repository.
+They have admin access to the repo and provide support reviewing issues and pull requests. + +* **[Name](github account URL)** + * what you're responsible for #1 + * what you're responsible for #2 + * etc + +Contributors (sorted alphabetically) +------------------------------------- + +To contributors: please add your name to the list when you submit a patch to the project. + +* **[Name](github account URL)** + * contribution #1 + * contribution #2 + * etc + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..52e268c4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,76 @@ +# Contribution Guidelines + +Contributions are welcomed! Here's a few things to know: + +* [Steps to Contributing](#steps-to-contributing) +* [Coding Guidelines](#TODO) +* [Microsoft Contributor License Agreement](#microsoft-contributor-license-agreement) +* [Code of Conduct](#code-of-conduct) + +## Steps to Contributing + +**TL;DR for contributing: We use the staging branch to land all new features and fixes. To make a contribution, please create a branch from staging, make a modification in the code and create a PR to staging.** + +Here are the basic steps to get started with your first contribution. Please reach out with any questions. +1. Use [open issues](https://github.com/Microsoft/DeepSeismic/issues) to discuss the proposed changes. Create an issue describing changes if necessary to collect feedback. Also, please use provided labels to tag issues so everyone can easily sort issues of interest. +2. [Fork the repo](https://help.github.com/articles/fork-a-repo/) so you can make and test local changes. +3. Create a new branch **from staging branch** for the issue (please do not create a branch from master). We suggest prefixing the branch with your username and then a descriptive title: (e.g. username/update_contributing_docs) +4. Create a test that replicates the issue. +5. Make code changes. +6. Ensure unit tests pass and code style / formatting is consistent TODO: add docstring links. +7. Create a pull request against **staging** branch. + +Once the features included in a [milestone](https://github.com/Microsoft/DeepSeismic/milestones) are completed, we will merge contrib into staging. TODO: make a wiki with coding guidelines. + +## Coding Guidelines + +We strive to maintain high quality code to make the utilities in the repository easy to understand, use, and extend. We also work hard to maintain a friendly and constructive environment. We've found that having clear expectations on the development process and consistent style helps to ensure everyone can contribute and collaborate effectively. + +## Microsoft Contributor License Agreement + +Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. + +TODO: add CLA-bot + +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +Apart from the official Code of Conduct developed by Microsoft, in the Computer Vision team we adopt the following behaviors, to ensure a great working environment: + +#### Do not point fingers +Let’s be constructive. + +
+Click here to see some examples + +"This method is missing docstrings" instead of "YOU forgot to put docstrings". + +
+ +#### Provide code feedback based on evidence + +When making code reviews, try to support your ideas based on evidence (papers, library documentation, stackoverflow, etc) rather than your personal preferences. + +
+Click here to see some examples + +"When reviewing this code, I saw that the Python implementation the metrics are based on classes, however, [scikit-learn](https://scikit-learn.org/stable/modules/classes.html#sklearn-metrics-metrics) and [tensorflow](https://www.tensorflow.org/api_docs/python/tf/metrics) use functions. We should follow the standard in the industry." + +
+ + +#### Ask questions - do not give answers +Try to be empathic. + +
+Click here to see some examples + +* Would it make more sense if ...? +* Have you considered this ... ? + +
+ + diff --git a/LICENSE b/LICENSE index 22aed37e..cbf2717d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,22 @@ -MIT License + MIT License -Copyright (c) Microsoft Corporation. + Copyright (c) Microsoft Corporation. All rights reserved. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 00000000..70d41949 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,6 @@ +# Setup guide + +This document describes how to setup all the dependencies to run the code in this repository in following platforms: + +* Local (Linux, MacOS or Windows) or [DSVM](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/) (Linux or Windows) +* TODO: add more diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 00000000..14a86937 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,6 @@ +# Benchmarks + +In this folder we show benchmarks using different algorithms. To facilitate the benchmark computation, we provide a set of wrapper functions that can be found in the file [benchmark_utils.py](benchmark_utils.py). + +TODO + diff --git a/benchmarks/benchmark_utils.py b/benchmarks/benchmark_utils.py new file mode 100644 index 00000000..e69de29b diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..f85ae6a2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,6 @@ +# Documentation + +To setup the documentation, first you need to install the dependencies of the full environment. For it please follow the [SETUP.md](../SETUP.md). + +TODO: add more text + diff --git a/setup.cfg b/scripts/setup.cfg similarity index 100% rename from setup.cfg rename to scripts/setup.cfg diff --git a/setup.py b/scripts/setup.py similarity index 100% rename from setup.py rename to scripts/setup.py diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100644 index 00000000..e69de29b diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..dfe2ceca --- /dev/null +++ b/tests/README.md @@ -0,0 +1,9 @@ +# Tests + +This project uses unit and integration tests only with Python files and notebooks: + + * In the unit tests we just make sure the notebook runs. + * In the integration tests we use a bigger dataset for more epochs and we test that the metrics are what we expect. + + TODO: add more info + From cabfe3ce5e6c0a2ba545db94427834f832689e2f Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Thu, 22 Aug 2019 16:57:27 -0400 Subject: [PATCH 021/207] VOXEL: initial rough code push - need to clean up before PRing. --- .../voxel2pixel/F3/train/inline_339.png | Bin 0 -> 12482 bytes .../voxel2pixel/F3/val/inline_405.png | Bin 0 -> 22661 bytes interpretation/voxel2pixel/README.md | 53 ++ interpretation/voxel2pixel/batch.py | 212 ++++++++ interpretation/voxel2pixel/data.py | 241 ++++++++++ interpretation/voxel2pixel/tb_logger.py | 133 +++++ interpretation/voxel2pixel/test_parallel.py | 455 ++++++++++++++++++ interpretation/voxel2pixel/texture_net.py | 77 +++ interpretation/voxel2pixel/train.py | 125 +++++ interpretation/voxel2pixel/utils.py | 244 ++++++++++ scripts/get_F3_voxel.sh | 5 + 11 files changed, 1545 insertions(+) create mode 100644 interpretation/voxel2pixel/F3/train/inline_339.png create mode 100644 interpretation/voxel2pixel/F3/val/inline_405.png create mode 100644 interpretation/voxel2pixel/README.md create mode 100644 interpretation/voxel2pixel/batch.py create mode 100644 interpretation/voxel2pixel/data.py create mode 100644 interpretation/voxel2pixel/tb_logger.py create mode 100644 interpretation/voxel2pixel/test_parallel.py create mode 100644 interpretation/voxel2pixel/texture_net.py create mode 100644 interpretation/voxel2pixel/train.py create mode 100644 interpretation/voxel2pixel/utils.py create mode 100644 scripts/get_F3_voxel.sh diff --git a/interpretation/voxel2pixel/F3/train/inline_339.png b/interpretation/voxel2pixel/F3/train/inline_339.png new file mode 100644 index 0000000000000000000000000000000000000000..8128c686ad3322249855f8315ffcc1889b9afe22 GIT binary patch literal 12482 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 Jc;EQt{{c|Q1`GfI literal 0 HcmV?d00001 diff --git a/interpretation/voxel2pixel/README.md b/interpretation/voxel2pixel/README.md new file mode 100644 index 00000000..3fd2d891 --- /dev/null +++ b/interpretation/voxel2pixel/README.md @@ -0,0 +1,53 @@ +# CNN for ASI +Code for the paper:
+**Convolutional Neural Networks for Automated Seismic Interpretation**,
+A. U. Waldeland, A. C. Jensen, L. Gelius and A. H. S. Solberg
+[*The Leading Edge, July 2018*](https://library.seg.org/doi/abs/10.1190/tle37070529.1) + +EAGE E-lecture: [*Seismic interpretation with deep learning*](https://www.youtube.com/watch?v=lm85Ap4OstM) (YouTube) + +This repository contains python/pytorch code for applying Convolutional Neural Networks (CNN) on seismic data. The input is a segy-file containing post-stack seismic amplitude data. The training labels are given as images of the training slices, colored to indicate the classes. + +### Setup to get started +CNNs requires time-demanding computations, consider using a computer with a fast NVIDIA GPU with at least 6GB ram. +- Make sure you have all the required python packages installed:
+-- pytorch
+-- tensorflow (GPU enabled version is not needed - we only use this package for plotting)
+-- tensorflow-tensorboard
+-- numpy
+-- scipy
+-- segyio
+-- matplotlib
+- Clone this repository
+- Download the demo data set [here](https://drive.google.com/drive/folders/0B7brcf-eGK8CbGhBdmZoUnhiTWs). This is the F3 Netherlands dataset - originally made available by OpenDTect - and available via the [MalenoV Project](https://github.com/bolgebrygg/MalenoV). +- Locate the '.segy'-file, rename it to 'data.segy' and put it in the 'F3'-folder. + +### How to use tensorboard (for visualization) +- Open a terminal
+- cd to the code-folder
+- run: tensorboard --logdir='log'
+- Open a web-browser and go to localhost:6006
+More information can be found [here](https://www.tensorflow.org/get_started/summaries_and_tensorboard#launching_tensorboard). + +### Usage +- train.py - train the CNN
+- test.py - Example of how the trained CNN can be applied to predict salt in a slice or the full cube. In addition it shows how learned attributes can be extracted.
+ +### Files +In addition, it may be useful to have a look on these files
+- texture_net.py - this is where the network is defined
+- batch.py - provide functionality to generate training batches with random augmentation
+- data.py - load/save data sets with segy-format and labeled slices as images
+- tb_logger.py - connects to the tensorboard functionality
+- utils.py - some help functions
+- test_parallel.py - An implemenation of test.py supporting multi-gpu prediction (thanks to Max Kaznady).
+ +### Using a different data set and custom training labels +If you want to use a different data set, do the following: +- Make a new folder where you place the segy-file +- Make a folder for the training labels +- Save images of the slices you want to train on as 'SLICETYPE_SLICENO.png' (or jpg), where SLICETYPE is either 'inline', 'crossline', or 'timeslice' and SLICENO is the slice number. +- Draw the classes on top of the seismic data, using a simple image editing program with the class colors. Currently up to six classes are supported, indicated by the colors: red, blue, green, cyan, magenta and yellow. + +# Contact +Email: anders.u.waldeland@gmail.com diff --git a/interpretation/voxel2pixel/batch.py b/interpretation/voxel2pixel/batch.py new file mode 100644 index 00000000..e75f7b08 --- /dev/null +++ b/interpretation/voxel2pixel/batch.py @@ -0,0 +1,212 @@ +import numpy as np + +""" Returns a batch of augmented samples with center pixels randomly drawn from label_coordinates""" +def get_random_batch(data_cube, label_coordinates, im_size, batch_size, + random_flip = False, + random_stretch = None, + random_rot_xy=None, + random_rot_z=None): + + #Make 3 im_size elements + if type(im_size) == type(1): + im_size = [im_size,im_size,im_size] + + #Output arrays + batch = np.zeros([batch_size, 1, im_size[0], im_size[1], im_size[2]]) + labels = np.zeros([batch_size]) + + class_keys = list(label_coordinates) + n_classes = len(class_keys) + + #Loop through batch + n_for_class = 0; + class_ind = 0 + for i in range(batch_size): + + #Start by getting a grid centered around (0,0,0) + grid = getGrid(im_size) + + #Apply random flip + if random_flip: + grid = augment_flip(grid) + + # Apply random rotations + if random_rot_xy: + grid = augment_rot_xy(grid, random_rot_xy) + if random_rot_z: + grid = augment_rot_z(grid, random_rot_z) + + #Apply random stretch + if random_stretch: + grid = augment_stretch(grid, random_stretch) + + #Pick random location from the label_coordinates for this class: + coords_for_class = label_coordinates[class_keys[class_ind]] + random_index = rand_int(0, coords_for_class.shape[1]) + coord = coords_for_class[:,random_index:random_index+1] + + #Move grid to be centered around this location + grid += coord + + #Interpolate samples at grid from the data: + sample = trilinear_interpolation(data_cube, grid) + + #Insert in output arrays + labels[i] = class_ind + batch[i, 0, :, :, :] = np.reshape( sample, (im_size[0], im_size[1], im_size[2])) + + # We seek to have a balanced batch with equally many samples from each class. + n_for_class += 1 + if n_for_class+1 > int(.5+batch_size / float(n_classes) ): + if class_ind < n_classes-1: + class_ind += 1 + n_for_class = 0 + + + + return batch, labels + +""" Get x,y,z grid for sample """ +def getGrid(im_size): + """ + getGrid returns z,x,y coordinates centered around (0,0,0) + :param im_size: size of window + :return: numpy int array with size: 3 x im_size**3 + """ + win0 = np.linspace(-im_size[0] // 2, im_size[0] // 2, im_size[0]) + win1 = np.linspace(-im_size[1] // 2, im_size[1] // 2, im_size[1]) + win2 = np.linspace(-im_size[2] // 2, im_size[2] // 2, im_size[2]) + + x0,x1,x2 = np.meshgrid(win0, win1, win2, indexing='ij') + x0 = np.expand_dims(x0.ravel(), 0) + x1 = np.expand_dims(x1.ravel(), 0) + x2 = np.expand_dims(x2.ravel(), 0) + grid = np.concatenate((x0, x1, x2), axis=0) + + return grid + +""" Random flip of non-depth axes """ +def augment_flip(grid): + #Flip x axis + if rand_bool(): + grid[1,:] = -grid[1,:] + + #Flip y axis + if rand_bool(): + grid[2, :] = -grid[2, :] + + return grid + +""" Random stretch/scale """ +def augment_stretch(grid, stretch_factor): + stretch = rand_float(-stretch_factor, stretch_factor) + grid *= (1+stretch) + return grid + +""" Random rotation """ +def augment_rot_xy(grid, random_rot_xy): + theta = np.deg2rad(rand_float(- random_rot_xy, random_rot_xy)) + x = grid[2,:] * np.cos(theta) - grid[1,:] * np.sin(theta) + y = grid[2,:] * np.sin(theta) + grid[1,:] * np.cos(theta) + grid[1, :] = x + grid[2, :] = y + return grid + +""" Random tilt """ +def augment_rot_z(grid, random_rot_z): + theta = np.deg2rad( rand_float( - random_rot_z, random_rot_z) ) + z = grid[0,:] * np.cos(theta) - grid[1,:] * np.sin(theta) + x = grid[0,:] * np.sin(theta) + grid[1,:] * np.cos(theta) + grid[0, :] = z + grid[1, :] = x + return grid + +""" Linear interpolation """ +def trilinear_interpolation(input_array, indices): + + # http://stackoverflow.com/questions/6427276/3d-interpolation-of-numpy-arrays-without-scipy + output = np.empty(indices[0].shape) + x_indices = indices[0] + y_indices = indices[1] + z_indices = indices[2] + + N0, N1, N2 = input_array.shape + + x0 = x_indices.astype(np.integer) + y0 = y_indices.astype(np.integer) + z0 = z_indices.astype(np.integer) + x1 = x0 + 1 + y1 = y0 + 1 + z1 = z0 + 1 + + #put all samples outside datacube to 0 + inds_out_of_range = (x0 < 0) | (x1 < 0) | (y0 < 0) | (y1 < 0) | (z0 < 0) | (z1 < 0) | \ + (x0 >= N0) | (x1 >= N0) | (y0 >= N1) | (y1 >= N1) | (z0 >= N2) | (z1 >= N2) + + x0[inds_out_of_range] = 0 + y0[inds_out_of_range] = 0 + z0[inds_out_of_range] = 0 + x1[inds_out_of_range] = 0 + y1[inds_out_of_range] = 0 + z1[inds_out_of_range] = 0 + + x = x_indices - x0 + y = y_indices - y0 + z = z_indices - z0 + output = (input_array[x0, y0, z0] * (1 - x) * (1 - y) * (1 - z) + + input_array[x1, y0, z0] * x * (1 - y) * (1 - z) + + input_array[x0, y1, z0] * (1 - x) * y * (1 - z) + + input_array[x0, y0, z1] * (1 - x) * (1 - y) * z + + input_array[x1, y0, z1] * x * (1 - y) * z + + input_array[x0, y1, z1] * (1 - x) * y * z + + input_array[x1, y1, z0] * x * y * (1 - z) + + input_array[x1, y1, z1] * x * y * z) + + output[inds_out_of_range] = 0 + return output + +""" Functions to get random variables: """ +def rand_float(low, high): + return (high - low) * np.random.random_sample() + low + +def rand_int(low, high): + return np.random.randint(low, high) + +def rand_bool(): + return bool(np.random.randint(0, 2)) + + +#Test the batch-functions +if __name__ == '__main__': + from data import readSEGY, readLabels, get_slice + import tb_logger + import numpy as np + + data, data_info = readSEGY(join('F3','data.segy')) + + train_coordinates = {'1':np.expand_dims( np.array([50,50,50]), 1)} + + + logger = tb_logger.TBLogger('log', 'batch test') + + [batch, labels] = get_random_batch(data, train_coordinates, 65, 32) + logger.log_images('normal',batch) + + [batch, labels] = get_random_batch(data, train_coordinates, 65, 32,random_flip=True) + logger.log_images('flipping', batch) + + [batch, labels] = get_random_batch(data, train_coordinates, 65, 32, random_stretch=.50) + logger.log_images('stretching', batch) + + [batch, labels] = get_random_batch(data, train_coordinates, 65, 32, random_rot_xy=180) + logger.log_images('rot', batch) + + [batch, labels] = get_random_batch(data, train_coordinates, 65, 32, random_rot_z=15) + logger.log_images('dip', batch) + + train_cls_imgs, train_coordinates = readLabels(join('F3','train'), data_info) + [batch, labels] = get_random_batch(data, train_coordinates, 65, 32) + logger.log_images('salt', batch[:16,:,:,:,:]) + logger.log_images('not salt', batch[16:, :, :, :, :]) + + logger.log_images('data', data[:,:,50]) diff --git a/interpretation/voxel2pixel/data.py b/interpretation/voxel2pixel/data.py new file mode 100644 index 00000000..4333607b --- /dev/null +++ b/interpretation/voxel2pixel/data.py @@ -0,0 +1,241 @@ +# Compatability Imports +from __future__ import print_function +from os.path import isfile, join + +import segyio +from os import listdir +import numpy as np +import scipy.misc + + +def readSEGY(filename): + print('Loading data cube from',filename,'with:') + + # Read full data cube + data = segyio.tools.cube(filename) + + # Put temporal axis first + data = np.moveaxis(data, -1, 0) + + #Make data cube fast to acess + data = np.ascontiguousarray(data,'float32') + + #Read meta data + segyfile = segyio.open(filename, "r") + print(' Crosslines: ', segyfile.xlines[0], ':', segyfile.xlines[-1]) + print(' Inlines: ', segyfile.ilines[0], ':', segyfile.ilines[-1]) + print(' Timeslices: ', '1', ':', data.shape[0]) + + #Make dict with cube-info + data_info = {} + data_info['crossline_start'] = segyfile.xlines[0] + data_info['inline_start'] = segyfile.ilines[0] + data_info['timeslice_start'] = 1 #Todo: read this from segy + data_info['shape'] = data.shape + #Read dt and other params needed to do create a new + + + return data, data_info + +# Writes out_cube to a segy-file (out_filename) with same header/size as in_filename +def writeSEGY(out_filename, in_filename, out_cube): + #Select last channel + if type(out_cube) is list: + out_cube = out_cube[-1] + + print('Writing interpretation to ' + out_filename) + #Copy segy file + from shutil import copyfile + copyfile(in_filename, out_filename) + + # Moving temporal axis back again + out_cube = np.moveaxis(out_cube, 0,-1) + + #Open out-file + with segyio.open(out_filename, "r+") as src: + iline_start = src.ilines[0] + dtype = src.iline[iline_start].dtype + # loop through inlines and insert output + for i in src.ilines: + iline = out_cube[i-iline_start,:,:] + src.iline[i] = np.ascontiguousarray(iline.astype(dtype)) + + # Moving temporal axis first again - just in case the user want to keep working on it + out_cube = np.moveaxis(out_cube, -1, 0) + + print('Writing interpretation - Finished') + return + +#Alternative writings for slice-type +inline_alias = ['inline','in-line','iline','y'] +crossline_alias = ['crossline','cross-line','xline','x'] +timeslice_alias = ['timeslice','time-slice','t','z','depthslice','depth'] + +# Read labels from an image +def readLabels(foldername, data_info): + files = [f for f in listdir(foldername) if isfile(join(foldername, f))] + + label_imgs = [] + label_coordinates = {} + + + #Find image files in folder + for file in files: + if file[-3:].lower() in ['jpg','png','peg', 'bmp','gif'] and file[0] !='.': + if True: + tmp = file.split('_') + slice_type = tmp[0].lower() + tmp = tmp[1].split('.') + slice_no = int(tmp[0]) + + if slice_type not in inline_alias + crossline_alias + timeslice_alias: + print('File:', file, 'could not be loaded.', 'Unknown slice type') + continue + + if slice_type in inline_alias: + slice_type = 'inline' + if slice_type in crossline_alias: + slice_type = 'crossline' + if slice_type in timeslice_alias: + slice_type = 'timeslice' + + #Read file + print('Loading labels for', slice_type, slice_no, 'with') + img = scipy.misc.imread(join(foldername, file)) + img = interpolate_to_fit_data(img, slice_type, slice_no, data_info) + label_img = parseLabelsInImage(img) + + #Get coordinates for slice + coords = get_coordinates_for_slice(slice_type, slice_no, data_info) + + #Loop through labels in label_img and append to label_coordinates + for cls in np.unique(label_img): + if cls > -1: + if str(cls) not in label_coordinates.keys(): + label_coordinates[str(cls)] = np.array(np.zeros([3,0])) + inds_with_cls = label_img==cls + cords_with_cls = coords[:, inds_with_cls.ravel()] + label_coordinates[str(cls)] = np.concatenate( (label_coordinates[str(cls)] , cords_with_cls), 1) + print(' ', str(np.sum(inds_with_cls)), 'labels for class',str(cls)) + if len(np.unique(label_img)) == 1: + print(' ', 0, 'labels', str(cls)) + + #Add label_img to output + label_imgs.append([label_img, slice_type, slice_no]) + + #except: + # print('File:', file, 'could not be loaded.') + return label_imgs, label_coordinates + +# Add colors to this table to make it possible to have more classes +class_color_coding =[ + [0,0,255], #blue + [0,255,0], #green + [0,255,255], #cyan + [255,0,0], #red + [255,0,255], #blue + [255,255,0] #yellow +] + +#Convert RGB image to class img +def parseLabelsInImage(img): + label_img = np.int16(img[:,:,0])*0 -1 # -1 = no class + + #decompose color channels (#Alpha is ignored) + r = img[:,:,0] + g = img[:,:,1] + b = img[:,:,2] + + #Alpha channel + if img.shape[2] == 4: + a = 1-img.shape[2]//255 + r = r * a + g = g * a + b = b * a + + + tolerance = 1 + #Go through classes and find pixels with this class + cls = 0 + for color in class_color_coding: + #Find pixels with these labels + inds = (np.abs(r - color[0]) < tolerance) & \ + (np.abs(g - color[1]) < tolerance) & \ + (np.abs(b - color[2]) < tolerance) + label_img[inds] = cls + cls +=1 + + return label_img + +# Function to resize image if needed +def interpolate_to_fit_data(img, slice_type, slice_no, data_info): + #Get wanted output size + if slice_type == 'inline': + n0 = data_info['shape'][0] + n1 = data_info['shape'][2] + elif slice_type == 'crossline': + n0 = data_info['shape'][0] + n1 = data_info['shape'][1] + elif slice_type == 'timeslice': + n0 = data_info['shape'][1] + n1 = data_info['shape'][2] + return scipy.misc.imresize(img, (n0,n1), interp='nearest') + +# Get coordinates for slice in the full cube +def get_coordinates_for_slice( slice_type, slice_no, data_info): + ds = data_info['shape'] + + #Coordinates for cube + x0,x1,x2 = np.meshgrid( np.linspace(0, ds[0] - 1, ds[0]), + np.linspace(0, ds[1] - 1, ds[1]), + np.linspace(0, ds[2] - 1, ds[2]), + indexing='ij') + if slice_type == 'inline': + start = data_info['inline_start'] + slice_no = slice_no - start + + x0 = x0[:, slice_no, :] + x1 = x1[:, slice_no, :] + x2 = x2[:, slice_no, :] + elif slice_type == 'crossline': + start = data_info['crossline_start'] + slice_no = slice_no - start + x0 = x0[:, :, slice_no] + x1 = x1[:, :, slice_no] + x2 = x2[:, :, slice_no] + + + elif slice_type == 'timeslice': + start = data_info['timeslice_start'] + slice_no = slice_no - start + x0 = x0[slice_no, :, :] + x1 = x1[slice_no, :, :] + x2 = x2[slice_no, :, :] + + #Collect indexes + x0 = np.expand_dims(x0.ravel(), 0) + x1 = np.expand_dims(x1.ravel(), 0) + x2 = np.expand_dims(x2.ravel(), 0) + coords = np.concatenate((x0,x1,x2), axis=0) + + return coords + +# Return data-slice +def get_slice(data, data_info, slice_type, slice_no, window=0): + + if slice_type == 'inline': + start = data_info['inline_start'] + slice_no = slice_no - start + + slice = data[:, slice_no-window:slice_no+window+1, :] + elif slice_type == 'crossline': + start = data_info['crossline_start'] + slice_no = slice_no - start + slice = data[:, slice_no-window:slice_no+window+1, :] + + elif slice_type == 'timeslice': + start = data_info['timeslice_start'] + slice_no = slice_no - start + slice = data[:, slice_no-window:slice_no+window+1, :] + + return np.squeeze(slice) diff --git a/interpretation/voxel2pixel/tb_logger.py b/interpretation/voxel2pixel/tb_logger.py new file mode 100644 index 00000000..10cf3ee9 --- /dev/null +++ b/interpretation/voxel2pixel/tb_logger.py @@ -0,0 +1,133 @@ +# Compatability Imports +from __future__ import print_function +from os.path import join + +try: + import tensorflow as tf +except: + print('Tensorflow could not be imported, therefore tensorboard cannot be used.') + +from io import BytesIO +import matplotlib.pyplot as plt +import numpy as np +import torch +import datetime + +class TBLogger(object): + + def __init__(self, log_dir, folder_name = '' ): + + self.log_dir = join(log_dir, folder_name + ' ' + datetime.datetime.now().strftime("%I%M%p, %B %d, %Y")) + self.log_dir = self.log_dir.replace('//','/') + self.writer = tf.summary.FileWriter(self.log_dir) + + #Add scalar + def log_scalar(self, tag, value, step=0): + summary = tf.Summary(value=[tf.Summary.Value(tag=tag, + simple_value=value)]) + self.writer.add_summary(summary, step) + + def make_list_of_2D_array(self, im): + if type(im) == type([]): + return im + ims = [] + if len(im.shape) == 2: + ims.append(im) + elif len(im.shape) == 3: + for i in range(im.shape[0]): + ims.append(np.squeeze(im[i,:,:])) + + elif len(im.shape) == 4: + for i in range(im.shape[0]): + ims.append(np.squeeze(im[i, 0, :, :])) + return ims + + def log_images(self, tag, images, step=0, dim = 2, max_imgs = 50,cm='jet'): + + #Make sure images are on numpy format in case the input is a Torch-variable + images = self.convert_to_numpy(images) + + try: + if len(images.shape)>2: + dim = 3 + except: + None + + #Make list of images + if dim == 2: + images = self.make_list_of_2D_array(images) + + #If 3D we make one list for each slice-type + if dim == 3: + new_images_ts, new_images_il, new_images_cl = self.get_slices_from_3D(images) + self.log_images(tag + '_timeslice', new_images_ts, step, 2, max_imgs) + self.log_images(tag + '_inline', new_images_il, step, 2, max_imgs) + self.log_images(tag + '_crossline', new_images_cl, step, 2, max_imgs) + return + + + im_summaries = [] + + for nr, img in enumerate(images): + + #Grayscale + if cm == 'gray' or cm == 'grey': + img = img.astype('float') + img = np.repeat(np.expand_dims(img,2),3,2) + img -= img.min() + img /= img.max() + img *= 255 + img = img.astype('uint8') + + # Write the image to a string + s = BytesIO() + plt.imsave(s, img, format='png') + + # Create an Image object + img_sum = tf.Summary.Image(encoded_image_string=s.getvalue(), + height=img.shape[0], + width=img.shape[1]) + # Create a Summary value + im_summaries.append(tf.Summary.Value(tag='%s/%d' % (tag, nr), + image=img_sum)) + + #if nr == max_imgs-1: + # break + + # Create and write Summary + summary = tf.Summary(value=im_summaries) + self.writer.add_summary(summary, step) + + # Cuts out middle slices from image + def get_slices_from_3D(self, img): + + new_images_ts = [] + new_images_il = [] + new_images_cl = [] + + if len(img.shape) == 3: + new_images_ts.append(np.squeeze(img[img.shape[0] / 2, :, :])) + new_images_il.append(np.squeeze(img[:, img.shape[1] / 2, :])) + new_images_cl.append(np.squeeze(img[:, :, img.shape[2] / 2])) + + elif len(img.shape) == 4: + for i in range(img.shape[0]): + new_images_ts.append(np.squeeze(img[i, img.shape[1] / 2, :, :])) + new_images_il.append(np.squeeze(img[i, :, img.shape[2] / 2, :])) + new_images_cl.append(np.squeeze(img[i, :, :, img.shape[3] / 2])) + + elif len(img.shape) == 5: + for i in range(img.shape[0]): + new_images_ts.append(np.squeeze(img[i, 0, img.shape[2] / 2, :, :])) + new_images_il.append(np.squeeze(img[i, 0, :, img.shape[3] / 2, :])) + new_images_cl.append(np.squeeze(img[i, 0, :, :, img.shape[4] / 2])) + + return new_images_ts, new_images_il, new_images_cl + #Convert torch to numpy + def convert_to_numpy(self,im): + if type(im) == torch.autograd.Variable: + #Put on CPU + im = im.cpu() + #Get np-data + im = im.data.numpy() + return im diff --git a/interpretation/voxel2pixel/test_parallel.py b/interpretation/voxel2pixel/test_parallel.py new file mode 100644 index 00000000..3ecfd26f --- /dev/null +++ b/interpretation/voxel2pixel/test_parallel.py @@ -0,0 +1,455 @@ +# Compatability Imports +from __future__ import print_function + +import os + +# set default number of GPUs which are discoverable +N_GPU = 8 +DEVICE_IDS = list(range(N_GPU)) +os.environ["CUDA_VISIBLE_DEVICES"] = ",".join([str(x) for x in DEVICE_IDS]) + +# static parameters +RESOLUTION = 1 +# these match how the model is trained +N_CLASSES = 2 +IM_SIZE = 65 + +import random +import argparse +import json + +import torch +import torch.nn as nn +import torch.backends.cudnn as cudnn +from torch.utils.data import Dataset, DataLoader +import torch.distributed as dist + +if torch.cuda.is_available(): + device_str = os.environ["CUDA_VISIBLE_DEVICES"] + device = torch.device("cuda:" + device_str) +else: + raise Exception("No GPU detected for parallel scoring!") + +# ability to perform multiprocessing +import multiprocessing + +from os.path import join +from data import readSEGY, get_slice +from texture_net import TextureNet +import itertools +import numpy as np +import tb_logger +from data import writeSEGY + +# graphical progress bar +from tqdm import tqdm + +class ModelWrapper(nn.Module): + """ + Wrap TextureNet for DataParallel to invoke classify method + """ + + def __init__(self, texture_model): + super(ModelWrapper, self).__init__() + self.texture_model = texture_model + + def forward(self, input): + return self.texture_model.classify(input) + + +class MyDataset(Dataset): + def __init__(self, data, window, coord_list): + + # main array + self.data = data + self.coord_list = coord_list + self.window = window + self.len = len(coord_list) + + def __getitem__(self, index): + + # TODO: can we specify a pixel mathematically by index? + pixel = self.coord_list[index] + x, y, z = pixel + # TODO: current bottleneck - can we slice out voxels any faster + small_cube = self.data[ + x - self.window : x + self.window + 1, + y - self.window : y + self.window + 1, + z - self.window : z + self.window + 1, + ] + + return small_cube[np.newaxis, :, :, :], pixel + + def __len__(self): + return self.len + + +def main_worker(gpu, ngpus_per_node, args): + """ + Main worker function, given the gpu parameter and how many GPUs there are per node + it can figure out its rank + + :param gpu: rank of the process if gpu >= ngpus_per_node, otherwise just gpu ID which worker will run on. + :param ngpus_per_node: total number of GPU available on this node. + :param args: various arguments for the code in the worker. + :return: nothing + """ + + print("I got GPU", gpu) + + args.rank = gpu + + # loop around in round-robin fashion if we want to run multiple processes per GPU + args.gpu = gpu % ngpus_per_node + + # initialize the distributed process and join the group + print( + "setting rank", + args.rank, + "world size", + args.world_size, + args.dist_backend, + args.dist_url, + ) + dist.init_process_group( + backend=args.dist_backend, + init_method=args.dist_url, + world_size=args.world_size, + rank=args.rank, + ) + + # set default GPU device for this worker + torch.cuda.set_device(args.gpu) + # set up device for the rest of the code + device = torch.device("cuda:" + str(args.gpu)) + + # Load trained model (run train.py to create trained + network = TextureNet(n_classes=N_CLASSES) + model_state_dict = torch.load( + join(args.data, "saved_model.pt"), map_location=device + ) + network.load_state_dict(model_state_dict) + network.eval() + network.cuda(args.gpu) + + # set the scoring wrapper also to eval mode + model = ModelWrapper(network) + model.eval() + model.cuda(args.gpu) + + # When using a single GPU per process and per + # DistributedDataParallel, we need to divide the batch size + # ourselves based on the total number of GPUs we have. + # Min batch size is 1 + args.batch_size = max(int(args.batch_size / ngpus_per_node), 1) + # obsolete: number of data loading workers - this is only used when reading from disk, which we're not + # args.workers = int((args.workers + ngpus_per_node - 1) / ngpus_per_node) + + # wrap the model for distributed use - for scoring this is not needed + # model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu]) + + # set to benchmark mode because we're running the same workload multiple times + cudnn.benchmark = True + + # Read 3D cube + # NOTE: we cannot pass this data manually as serialization of data into each python process is costly, + # so each worker has to load the data on its own. + data, data_info = readSEGY(join(args.data, "data.segy")) + + # Get half window size + window = IM_SIZE // 2 + + # reduce data size for debugging + if args.debug: + data = data[0 : 3 * window] + + # generate full list of coordinates + # memory footprint of this isn't large yet, so not need to wrap as a generator + nx, ny, nz = data.shape + x_list = range(window, nx - window) + y_list = range(window, ny - window) + z_list = range(window, nz - window) + + print("-- generating coord list --") + # TODO: is there any way to use a generator with pyTorch data loader? + coord_list = list(itertools.product(x_list, y_list, z_list)) + + # we need to map the data manually to each rank - DistributedDataParallel doesn't do this at score time + print("take a subset of coord_list by chunk") + coord_list = list(np.array_split(np.array(coord_list), args.world_size)[args.rank]) + coord_list = [tuple(x) for x in coord_list] + + # we only score first batch in debug mode + if args.debug: + coord_list = coord_list[0 : args.batch_size] + + # prepare the data + print("setup dataset") + # TODO: RuntimeError: cannot pin 'torch.cuda.FloatTensor' only dense CPU tensors can be pinned + data_torch = torch.cuda.FloatTensor(data).cuda(args.gpu, non_blocking=True) + dataset = MyDataset(data_torch, window, coord_list) + + # not sampling like in training + # datasampler = DistributedSampler(dataset) + # just set some default epoch + # datasampler.set_epoch(1) + + # we use 0 workers because we're reading from memory + print("setting up loader") + my_loader = DataLoader( + dataset=dataset, + batch_size=args.batch_size, + shuffle=False, + num_workers=0, + pin_memory=False, + sampler=None + # sampler=datasampler + ) + + print("running loop") + + pixels_x = [] + pixels_y = [] + pixels_z = [] + predictions = [] + + # Loop through center pixels in output cube + with torch.no_grad(): + print("no grad") + for (chunk, pixel) in tqdm(my_loader): + input = chunk.cuda(args.gpu, non_blocking=True) + output = model(input) + # save and deal with it later on CPU + # we want to make sure order is preserved + pixels_x += pixel[0].tolist() + pixels_y += pixel[1].tolist() + pixels_z += pixel[2].tolist() + predictions += output.tolist() + # just score a single batch in debug mode + if args.debug: + break + + # TODO: legacy Queue Manager code from multiprocessing which we left here for illustration purposes + # result_queue.append([deepcopy(coord_list), deepcopy(predictions)]) + # result_queue.append([coord_list, predictions]) + # transform pixels into x, y, z list format + with open("results_{}.json".format(args.rank), "w") as f: + json.dump( + { + "pixels_x": pixels_x, + "pixels_y": pixels_y, + "pixels_z": pixels_z, + "preds": [int(x[0][0][0][0]) for x in predictions], + }, + f, + ) + + # TODO: we cannot use pickle do dump from multiprocess - processes lock up + # with open("result_predictions_{}.pkl".format(args.rank), "wb") as f: + # print ("dumping predictions pickle file") + # pickle.dump(predictions, f) + + +parser = argparse.ArgumentParser(description="PyTorch ImageNet Training") +parser.add_argument( + "-d", "--data", default="F3", type=str, help="default dataset folder name" +) +parser.add_argument( + "-s", + "--slice", + default="inline", + type=str, + choices=["inline", "crossline", "timeslice", "full"], + help="slice type which we want to score on", +) +parser.add_argument( + "-n", + "--slice-num", + default=339, + type=int, + help="slice number which we want to score", +) +parser.add_argument( + "-b", + "--batch-size", + default=2 ** 15, + type=int, + help="batch size which we use for scoring", +) +parser.add_argument( + "-p", + "--n-proc-per-gpu", + default=1, + type=int, + help="number of multiple processes to run per each GPU", +) +parser.add_argument( + "--dist-url", + default="tcp://127.0.0.1:12345", + type=str, + help="url used to set up distributed training", +) +parser.add_argument( + "--dist-backend", default="nccl", type=str, help="distributed backend" +) +parser.add_argument("--seed", default=0, type=int, help="default random number seed") +parser.add_argument( + "--debug", + action="store_true", + help="debug flag - if on we will only process one batch", +) + +def main(): + + # use distributed scoring+ + if RESOLUTION != 1: + raise Exception("Currently we only support pixel-level scoring") + + args = parser.parse_args() + + args.gpu = None + args.rank = 0 + + # world size is the total number of processes we want to run across all nodes and GPUs + args.world_size = N_GPU * args.n_proc_per_gpu + + if args.debug: + args.batch_size = 4 + + # fix away any kind of randomness - although for scoring it should not matter + random.seed(args.seed) + torch.manual_seed(args.seed) + cudnn.deterministic = True + + print("RESOLUTION {}".format(RESOLUTION)) + + ########################################################################## + print("-- scoring on GPU --") + + ngpus_per_node = torch.cuda.device_count() + print("nGPUs per node", ngpus_per_node) + + """ + First, read this: https://thelaziestprogrammer.com/python/a-multiprocessing-pool-pickle + + OK, so there are a few ways in which we can spawn a running process with pyTorch: + 1) Default mp.spawn should work just fine but won't let us access internals + 2) So we copied out the code from mp.spawn below to control how processes get created + 3) One could spawn their own processes but that would not be thread-safe with CUDA, line + "mp = multiprocessing.get_context('spawn')" guarantees we use the proper pyTorch context + + Input data serialization is too costly, in general so is output data serialization as noted here: + https://docs.python.org/3/library/multiprocessing.html + + Feeding data into each process is too costly, so each process loads its own data. + + For deserialization we could try and fail using: + 1) Multiprocessing queue manager + manager = Manager() + return_dict = manager.dict() + OR + result_queue = multiprocessing.Queue() + CALLING + with Manager() as manager: + results_list = manager.list() + mp.spawn(main_worker, nprocs=args.world_size, args=(ngpus_per_node, results_list/dict/queue, args)) + results = deepcopy(results_list) + 2) pickling results to disc. + + Turns out that for the reasons mentioned in the first article both approaches are too costly. + + The only reasonable way to deserialize data from a Python process is to write it to text, in which case + writing to JSON is a saner approach: https://www.datacamp.com/community/tutorials/pickle-python-tutorial + """ + + # invoke processes manually suppressing error queue + mp = multiprocessing.get_context("spawn") + # error_queues = [] + processes = [] + for i in range(args.world_size): + # error_queue = mp.SimpleQueue() + process = mp.Process( + target=main_worker, args=(i, ngpus_per_node, args), daemon=False + ) + process.start() + # error_queues.append(error_queue) + processes.append(process) + + # block on wait + for process in processes: + process.join() + + print("-- aggregating results --") + + # Read 3D cube + data, data_info = readSEGY(join(args.data, "data.segy")) + + # Log to tensorboard - input slice + logger = tb_logger.TBLogger("log", "Test") + logger.log_images( + args.slice + "_" + str(args.slice_num), + get_slice(data, data_info, args.slice, args.slice_num), + cm="gray", + ) + + x_coords = [] + y_coords = [] + z_coords = [] + predictions = [] + for i in range(args.world_size): + with open("results_{}.json".format(i), "r") as f: + dict = json.load(f) + + x_coords += dict["pixels_x"] + y_coords += dict["pixels_y"] + z_coords += dict["pixels_z"] + predictions += dict["preds"] + + """ + So because of Python's GIL having multiple workers write to the same array is not efficient - basically + the only way we can have shared memory is with threading but thanks to GIL only one thread can execute at a time, + so we end up with the overhead of managing multiple threads when writes happen sequentially. + + A much faster alternative is to just invoke underlying compiled code (C) through the use of array indexing. + + So basically instead of the following: + + NUM_CORES = multiprocessing.cpu_count() + print("Post-processing will run on {} CPU cores on your machine.".format(NUM_CORES)) + + def worker(classified_cube, coord): + x, y, z = coord + ind = new_coord_list.index(coord) + # print (coord, ind) + pred_class = predictions[ind] + classified_cube[x, y, z] = pred_class + + # launch workers in parallel with memory sharing ("threading" backend) + _ = Parallel(n_jobs=4*NUM_CORES, backend="threading")( + delayed(worker)(classified_cube, coord) for coord in tqdm(pixels) + ) + + We do this: + """ + + # placeholder for results + classified_cube = np.zeros(data.shape) + # store final results + classified_cube[x_coords, y_coords, z_coords] = predictions + + print("-- writing segy --") + in_file = join(args.data, "data.segy".format(RESOLUTION)) + out_file = join(args.data, "salt_{}.segy".format(RESOLUTION)) + writeSEGY(out_file, in_file, classified_cube) + + print("-- logging prediction --") + # log prediction to tensorboard + logger = tb_logger.TBLogger("log", "Test_scored") + logger.log_images( + args.slice + "_" + str(args.slice_num), + get_slice(classified_cube, data_info, args.slice, args.slice_num), + cm="binary", + ) + +if __name__ == "__main__": + main() diff --git a/interpretation/voxel2pixel/texture_net.py b/interpretation/voxel2pixel/texture_net.py new file mode 100644 index 00000000..bf55215c --- /dev/null +++ b/interpretation/voxel2pixel/texture_net.py @@ -0,0 +1,77 @@ +import torch +from torch import nn + +from utils import gpu_no_of_var + + +class TextureNet(nn.Module): + def __init__(self,n_classes=2): + super(TextureNet,self).__init__() + + # Network definition + self.net = nn.Sequential( + nn.Conv3d(1,50,5,4,padding=2), #Parameters #in_channels, #out_channels, filter_size, stride (downsampling factor) + nn.BatchNorm3d(50), + #nn.Dropout3d() #Droput can be added like this ... + nn.ReLU(), + + nn.Conv3d(50,50,3,2,padding=1, bias=False), + nn.BatchNorm3d(50), + nn.ReLU(), + + nn.Conv3d(50,50,3,2,padding=1, bias=False), + nn.BatchNorm3d(50), + nn.ReLU(), + + nn.Conv3d(50,50,3,2,padding=1, bias=False), + nn.BatchNorm3d(50), + nn.ReLU(), + + nn.Conv3d(50,50,3,3,padding=1, bias=False), + nn.BatchNorm3d(50), + nn.ReLU(), + + nn.Conv3d(50,n_classes,1,1), #This is the equivalent of a fully connected layer since input has width/height/depth = 1 + nn.ReLU(), + + ) + #The filter weights are by default initialized by random + + #Is called to compute network output + def forward(self,x): + return self.net(x) + + + + def classify(self,x): + x = self.net(x) + _, class_no = torch.max(x, 1, keepdim=True) + return class_no + + + # Functions to get output from intermediate feature layers + def f1(self, x,): + return self.getFeatures( x, 0) + def f2(self, x,): + return self.getFeatures( x, 1) + def f3(self, x,): + return self.getFeatures( x, 2) + def f4(self, x,): + return self.getFeatures( x, 3) + def f5(self, x,): + return self.getFeatures( x, 4) + + + def getFeatures(self, x, layer_no): + layer_indexes = [0, 3, 6, 9, 12] + + #Make new network that has the layers up to the requested output + tmp_net = nn.Sequential() + layers = list(self.net.children())[0:layer_indexes[layer_no]+1] + for i in range(len(layers)): + tmp_net.add_module(str(i),layers[i]) + if type(gpu_no_of_var(self)) == int: + tmp_net.cuda(gpu_no_of_var(self)) + return tmp_net(x) + + diff --git a/interpretation/voxel2pixel/train.py b/interpretation/voxel2pixel/train.py new file mode 100644 index 00000000..efe631d2 --- /dev/null +++ b/interpretation/voxel2pixel/train.py @@ -0,0 +1,125 @@ +# Compatability Imports +from __future__ import print_function +from os.path import join + +import torch +from torch import nn +import torch.nn.functional as F +from data import readSEGY, readLabels, get_slice +from batch import get_random_batch +from torch.autograd import Variable +from torch.utils.data import DataLoader, Dataset +import tb_logger + + + + +import numpy as np +from utils import * +#This is the network definition proposed in the paper + +#Parameters +dataset_name = 'F3' +im_size = 65 +batch_size = 32 #If you have a GPU with little memory, try reducing this to 16 (may degrade results) +use_gpu = True #Switch to toggle the use of GPU or not +log_tensorboard = True #Log progress on tensor board +if log_tensorboard: logger = tb_logger.TBLogger('log', 'Train') + +#See the texture_net.py file for the network configuration +from texture_net import TextureNet +network = TextureNet(n_classes=2) + +#Loss function +cross_entropy = nn.CrossEntropyLoss() #Softmax function is included + +#Optimizer to control step size in gradient descent +optimizer = torch.optim.Adam(network.parameters()) + +#Transfer model to gpu +if use_gpu: + network = network.cuda() + +#Load the data cube and labels +data, data_info = readSEGY(join(dataset_name,'data.segy')) +train_class_imgs, train_coordinates = readLabels(join(dataset_name,'train'), data_info) +val_class_imgs, _ = readLabels(join(dataset_name,'val'), data_info) + +#Plot training/validation data with labels +if log_tensorboard: + for class_img in train_class_imgs + val_class_imgs: + logger.log_images(class_img[1] + '_' + str(class_img[2] ), get_slice(data, data_info, class_img[1], class_img[2]), cm='gray') + logger.log_images(class_img[1] + '_' + str(class_img[2]) + '_true_class', class_img[0]) + + +# Training loop +for i in range(2000): + + # Get random training batch with augmentation + # This is the bottle-neck for training and could be done more efficient on the GPU... + [batch, labels] = get_random_batch(data, train_coordinates, im_size, batch_size, + random_flip=True, + random_stretch=.2, + random_rot_xy=180, + random_rot_z=15) + + + #Format data to torch-variable + batch = Variable( torch.Tensor(batch).float() ) + labels = Variable( torch.Tensor(labels).long() ) + + # Transfer data to gpu + if use_gpu: + batch = batch.cuda() + labels = labels.cuda() + + #Set network to training phase + network.train() + + #Run the samples through the network + output = network(batch) + + #Compute loss + loss = cross_entropy( torch.squeeze(output) , labels) + + # Do back-propagation to get gradients of weights w.r.t. loss + loss.backward() + + # Ask the optimizer to adjust the parameters in the direction of lower loss + optimizer.step() + + # Every 10th iteration - print training loss + if i % 10 == 0: + network.eval() + + #Log to training loss/acc + print('Iteration:', i, 'Training loss:', var_to_np(loss)) + if log_tensorboard: + logger.log_scalar('training_loss', var_to_np(loss),i) + for k,v in computeAccuracy(torch.argmax(output,1), labels).items(): + if log_tensorboard: + logger.log_scalar('training_' + k, v, i) + print(' -',k,v,'%') + + #every 100th iteration + if i % 100 == 0 and log_tensorboard: + network.eval() + + # Output predicted train/validation class/probability images + for class_img in train_class_imgs + val_class_imgs: + + slice = class_img[1] + slice_no = class_img[2] + + class_img = interpret(network.classify, data, data_info, slice, slice_no, im_size, 16, return_full_size=True, use_gpu=use_gpu) + logger.log_images( slice + '_' + str(slice_no)+ '_pred_class', class_img, i) + + class_img = interpret(network, data, data_info, slice, slice_no, im_size, 16, return_full_size=True, use_gpu=use_gpu) + logger.log_images( slice + '_' + str(slice_no) + '_pred_prob', class_img, i) + + #Store trained network + torch.save(network.state_dict(), join(dataset_name, 'saved_model.pt')) + + + + diff --git a/interpretation/voxel2pixel/utils.py b/interpretation/voxel2pixel/utils.py new file mode 100644 index 00000000..279dc9fb --- /dev/null +++ b/interpretation/voxel2pixel/utils.py @@ -0,0 +1,244 @@ +# Compatability Imports +from __future__ import print_function + +import torch +import numpy as np +from torch.autograd import Variable +from scipy.interpolate import interpn + +def interpret( network, data, data_info, slice, slice_no, im_size, subsampl, return_full_size=True, use_gpu=True): + # Wrap np.linspace in compact function call + def ls(N): return np.linspace(0, N - 1, N, dtype='int') + + #Size of cube + N0, N1, N2 = data.shape + + #Coords for full cube + x0_range = ls(N0) + x1_range = ls(N1) + x2_range = ls(N2) + + #Coords for subsampled cube + pred_points = (x0_range[::subsampl], x1_range[::subsampl], x2_range[::subsampl]) + + #Select slice + if slice == 'full': + class_cube = data[::subsampl, ::subsampl, ::subsampl] * 0 + + elif slice == 'inline': + slice_no = slice_no - data_info['inline_start'] + class_cube = data[::subsampl, 0:1, ::subsampl] * 0 + x1_range = np.array([slice_no]) + pred_points = (pred_points[0],pred_points[2]) + + elif slice == 'crossline': + slice_no = slice_no - data_info['crossline_start'] + class_cube = data[::subsampl, ::subsampl, 0:1,] * 0 + x2_range = np.array([slice_no]) + pred_points = (pred_points[0], pred_points[1]) + + elif slice == 'timeslice': + slice_no = slice_no - data_info['timeslice_start'] + class_cube = data[0:1, ::subsampl, ::subsampl] * 0 + x0_range = np.array([slice_no]) + pred_points = (pred_points[1], pred_points[2]) + + + #Grid for small class slice/cube + n0,n1,n2 = class_cube.shape + x0_grid, x1_grid, x2_grid = np.meshgrid(ls(n0,), ls(n1), ls(n2), indexing='ij') + + #Grid for full slice/cube + X0_grid, X1_grid, X2_grid = np.meshgrid(x0_range, x1_range, x2_range, indexing='ij') + + #Indexes for large cube at small cube pixels + X0_grid_sub = X0_grid[::subsampl, ::subsampl, ::subsampl] + X1_grid_sub = X1_grid[::subsampl, ::subsampl, ::subsampl] + X2_grid_sub = X2_grid[::subsampl, ::subsampl, ::subsampl] + + #Get half window size + w = im_size//2 + + #Loop through center pixels in output cube + for i in range(X0_grid_sub.size): + + #Get coordinates in small and large cube + x0 = x0_grid.ravel()[i] + x1 = x1_grid.ravel()[i] + x2 = x2_grid.ravel()[i] + + X0 = X0_grid_sub.ravel()[i] + X1 = X1_grid_sub.ravel()[i] + X2 = X2_grid_sub.ravel()[i] + + + #Only compute when a full 65x65x65 cube can be extracted around center pixel + if X0>w and X1>w and X2>w and X0=0: + accuracies['accuracy_class_' + str(cls)] = int(np.mean(predicted_class[labels==cls]==cls)*100) + accuracies['average_class_accuracy'] = np.mean([acc for acc in accuracies.values()]) + return accuracies diff --git a/scripts/get_F3_voxel.sh b/scripts/get_F3_voxel.sh new file mode 100644 index 00000000..3aabe27c --- /dev/null +++ b/scripts/get_F3_voxel.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +TODO: fetch Dutch F3 from Malenov project. + + From e399fb946f973268c98e134d9f0b2275e0c62b5a Mon Sep 17 00:00:00 2001 From: masalvar Date: Mon, 26 Aug 2019 08:04:48 +0000 Subject: [PATCH 022/207] Working version --- .vscode/settings.json | 23 +- .../anaconda/local/environment.yml | 1 + .../dutchf3/distributed/configs/hrnet.yaml | 77 +++ .../configs/patch_deconvnet.yaml} | 13 +- .../configs/patch_deconvnet_skip.yaml | 34 + .../distributed/configs/seresnet_unet.yaml | 37 + .../dutchf3/distributed/configs/unet.yaml | 35 + .../dutchf3/distributed/default.py | 100 +++ .../segmentation/dutchf3/distributed/run.sh | 3 + .../segmentation/dutchf3/distributed/train.py | 380 +++++++++++ .../segmentation/dutchf3/local/__init__.py | 0 .../dutchf3/local/augmentations.py | 183 ----- .../local/configs/patch_deconvnet.yaml | 41 ++ .../segmentation/dutchf3/local/data.py | 76 --- .../segmentation/dutchf3/local/default.py | 29 +- .../dutchf3/local/loader/__init__.py | 9 - .../dutchf3/local/loader/data_loader.py | 202 ------ .../segmentation/dutchf3/local/loss.py | 12 - .../segmentation/dutchf3/local/metrics.py | 45 -- .../dutchf3/local/models/__init__.py | 26 - .../dutchf3/local/models/patch_deconvnet.py | 632 ------------------ .../dutchf3/local/models/section_deconvnet.py | 632 ------------------ .../dutchf3/local/models/utils.py | 72 -- .../segmentation/dutchf3/local/orig_train.py | 179 ----- .../segmentation/dutchf3/local/run.sh | 2 +- .../segmentation/dutchf3/local/test.py | 296 ++++++++ .../segmentation/dutchf3/local/test.sh | 3 + .../segmentation/dutchf3/local/train.py | 195 ++++-- .../segmentation/dutchf3/local/utils.py | 20 - 29 files changed, 1191 insertions(+), 2166 deletions(-) create mode 100644 interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml rename interpretation/experiments/segmentation/dutchf3/{local/configs/unet.yaml => distributed/configs/patch_deconvnet.yaml} (78%) create mode 100644 interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml create mode 100644 interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml create mode 100644 interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml create mode 100644 interpretation/experiments/segmentation/dutchf3/distributed/default.py create mode 100755 interpretation/experiments/segmentation/dutchf3/distributed/run.sh create mode 100644 interpretation/experiments/segmentation/dutchf3/distributed/train.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/__init__.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/augmentations.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/data.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/loader/__init__.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/loader/data_loader.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/loss.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/metrics.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/models/__init__.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/models/patch_deconvnet.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/models/section_deconvnet.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/models/utils.py delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/orig_train.py create mode 100644 interpretation/experiments/segmentation/dutchf3/local/test.py create mode 100755 interpretation/experiments/segmentation/dutchf3/local/test.sh delete mode 100644 interpretation/experiments/segmentation/dutchf3/local/utils.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 7cd20b62..5c682cd8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { - "python.jediEnabled": false, "[python]": { + "python.jediEnabled": false, + "[python]": { "editor.defaultFormatter": "ms-python.python" }, "python.formatting.provider": "black", @@ -8,7 +9,8 @@ "python.pythonPath": "/anaconda/envs/seismic-interpretation/bin/python", "python.linting.pylintArgs": [ "--enable=W0614" - ], + ], + "python.envFile": "${workspaceFolder}/.env", "window.zoomLevel": 0, "autoDocstring.docstringFormat": "google", "files.autoSave": "afterDelay", @@ -18,6 +20,21 @@ "remote.SSH.showLoginTerminal": true, "docker.host": "tcp://msnarwal.eastus.cloudapp.azure.com:2375", "terminal.integrated.env.linux":{ - "PYTHONPATH": "${env:PYTHONPATH}" + "PYTHONPATH": "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local:/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/loader:/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/models:${env:PYTHONPATH}" + }, + "files.exclude": { + "**/__pycache__":true, + "**/bin":true, + "**/deepseismic":true, + }, + "files.watcherExclude":{ + "**/__pycache__":true, + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/bin": true, + "**/deepseismic": true, + "**/log": true, + "**/output": true, + "**/cv_lib.egg-info": true, } } \ No newline at end of file diff --git a/interpretation/environment/anaconda/local/environment.yml b/interpretation/environment/anaconda/local/environment.yml index a243d2c9..a1abf1fd 100644 --- a/interpretation/environment/anaconda/local/environment.yml +++ b/interpretation/environment/anaconda/local/environment.yml @@ -25,4 +25,5 @@ dependencies: - tqdm - invoke - yacs + - albumentations - "--editable=git+https://dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib" diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml new file mode 100644 index 00000000..ef921135 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml @@ -0,0 +1,77 @@ +# HRNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/alaudah + +MODEL: + NAME: seg_hrnet + IN_CHANNELS: 3 + PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 64 diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml similarity index 78% rename from interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml rename to interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml index 70e8c56a..69670909 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml @@ -8,27 +8,26 @@ LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 DATASET: NUM_CLASSES: 1 - ROOT: /mnt/tgssalt + ROOT: /mnt/alaudah MODEL: - NAME: unet + NAME: patch_deconvnet + IN_CHANNELS: 3 TRAIN: - BATCH_SIZE_PER_GPU: 32 + BATCH_SIZE_PER_GPU: 64 BEGIN_EPOCH: 0 END_EPOCH: 300 MIN_LR: 0.001 MAX_LR: 0.1 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 SNAPSHOTS: 5 TEST: - BATCH_SIZE_PER_GPU: 64 + BATCH_SIZE_PER_GPU: 128 diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml new file mode 100644 index 00000000..93f5d5c7 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml @@ -0,0 +1,34 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/alaudah + DEPTH: 'no' + +MODEL: + NAME: patch_deconvnet_skip + IN_CHANNELS: 1 + + +TRAIN: + BATCH_SIZE_PER_GPU: 64 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 128 diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml new file mode 100644 index 00000000..90bcf77b --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml @@ -0,0 +1,37 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/alaudah + DEPTH: "patch" # Options are No, Patch and Section + STRIDE: 128 + PATCH_SIZE: 256 + +MODEL: + NAME: resnet_unet + IN_CHANNELS: 3 + + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: False + +TEST: + BATCH_SIZE_PER_GPU: 32 diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml new file mode 100644 index 00000000..3877d96e --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml @@ -0,0 +1,35 @@ +# UNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf + +DATASET: + NUM_CLASSES: 1 + ROOT: /mnt/alaudah + +MODEL: + NAME: resnet_unet + IN_CHANNELS: 3 + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.01 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + PAD_LEFT: 27 + PAD_RIGHT: 27 + FINE_SIZE: 202 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 32 diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/default.py b/interpretation/experiments/segmentation/dutchf3/distributed/default.py new file mode 100644 index 00000000..7146fd69 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/distributed/default.py @@ -0,0 +1,100 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from yacs.config import CfgNode as CN + + +_C = CN() + +_C.OUTPUT_DIR = "output" +_C.LOG_DIR = "log" +_C.GPUS = (0,) +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +_C.LOG_CONFIG = "/data/home/mat/repos/DeepSeismic/logging.conf" +_C.SEED = 42 + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "/mnt/alaudah" +_C.DATASET.NUM_CLASSES = 1 +_C.DATASET.STRIDE = 50 +_C.DATASET.PATCH_SIZE = 99 +_C.DATASET.AUGMENTATION = True +_C.DATASET.DEPTH = 'no' # Options are None, Patch and Section +# None adds no depth information and the num of channels remains at 1 +# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 +# Section adds depth per section so contains depth information for the whole section, channels=3 + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "patch_deconvnet" +_C.MODEL.IN_CHANNELS = 1 +_C.MODEL.NUM_CLASSES = 6 + +_C.LOSS = CN() +_C.LOSS.WEIGHTS = (0.01, 1) +_C.LOSS.ADJUST_EPOCH = 50 +_C.LOSS.ADJUSTED_WEIGHTS = (0.3, 0.7) + + +# training +_C.TRAIN = CN() +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.SAVE_LOCATION = "/tmp/models" +_C.TRAIN.AUGMENTATION = True + +# validation +_C.VALIDATION = CN() +_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 + +# TEST +_C.TEST = CN() +_C.TEST.MODEL_PATH = "" +_C.TEST.TEST_STRIDE = 10 +_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 +_C.TEST.INLINE = True +_C.TEST.CROSSLINE = True + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/run.sh b/interpretation/experiments/segmentation/dutchf3/distributed/run.sh new file mode 100755 index 00000000..d9be4bd7 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/distributed/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH +python -m torch.distributed.launch --nproc_per_node=8 train.py --cfg configs/seresnet_unet.yaml \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/train.py b/interpretation/experiments/segmentation/dutchf3/distributed/train.py new file mode 100644 index 00000000..c85c6867 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/distributed/train.py @@ -0,0 +1,380 @@ +"""Train models on Dutch F3 salt dataset + +Trains models using PyTorch DistributedDataParallel +Uses a warmup schedule that then goes into a cyclic learning rate +""" +# /* spell-checker: disable */ + +import logging +import logging.config +import os +from datetime import datetime + +import fire +import numpy as np +import torch +import torch.nn.functional as F +import torchvision.utils as vutils +from ignite.contrib.handlers import ( + ConcatScheduler, + CosineAnnealingScheduler, + CustomPeriodicEvent, + LinearCyclicalScheduler, +) +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from sklearn.model_selection import train_test_split +from tensorboardX import SummaryWriter +from toolz import compose, curry +from torch.utils import data +from tqdm import tqdm + +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) +from cv_lib.segmentation.dutchf3.augmentations import ( + AddNoise, + Compose, + RandomHorizontallyFlip, + RandomRotate, +) +from cv_lib.segmentation.dutchf3.data import ( + get_train_loader, + decode_segmap, + split_train_val, + split_non_overlapping_train_val +) +from cv_lib.segmentation.dutchf3.engine import ( + create_supervised_evaluator, + create_supervised_trainer, +) +from cv_lib.segmentation.dutchf3.metrics import MeanIoU, PixelwiseAccuracy +from cv_lib.segmentation import models +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) + get_data_ids, + get_distributed_data_loaders, + kfold_split, +) + create_supervised_evaluator, + create_supervised_trainer, +) +from cv_lib.segmentation.dutchf3.metrics import apex +from default import _C as config +from default import update_config + +CLASS_WEIGHTS = [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + + +def prepare_batch(batch, device=None, non_blocking=False): + x, y = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ) + + +@curry +def update_sampler_epoch(data_loader, engine): + data_loader.sampler.epoch = engine.state.epoch + + +def run(*options, cfg=None, local_rank=0): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + fraction_validation = 0.2 + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + logger.debug(config.WORKERS) + silence_other_ranks = True + world_size = int(os.environ.get("WORLD_SIZE", 1)) + distributed = world_size > 1 + + if distributed: + # FOR DISTRIBUTED: Set the device according to local_rank. + torch.cuda.set_device(local_rank) + + # FOR DISTRIBUTED: Initialize the backend. torch.distributed.launch will provide + # environment variables, and requires that you use init_method=`env://`. + torch.distributed.init_process_group( + backend="nccl", init_method="env://" + ) + + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + if local_rank==0: #Only do this on local rank otherwise overwriting + # Generate the train and validation sets for the model: + split_non_overlapping_train_val( + config.DATASET.STRIDE, per_val=fraction_validation, loader_type="patch" + ) + + # Setup Augmentations + if config.TRAIN.AUGMENTATION: + data_aug = Compose( + # [RandomRotate(10), RandomHorizontallyFlip(), AddNoise()] + [RandomHorizontallyFlip(), AddNoise()] + ) + else: + data_aug = None + + TrainPatchLoader = get_train_loader(config) + + train_set = TrainPatchLoader( + split="train", + is_transform=True, + stride=config.DATASET.STRIDE, + patch_size=config.DATASET.PATCH_SIZE, + augmentations=data_aug, + ) + logger.info(f"Training examples {len(train_set)}") + + # Without Augmentation: + val_set = TrainPatchLoader( + split="val", + is_transform=True, + stride=config.DATASET.STRIDE, + patch_size=config.DATASET.PATCH_SIZE, + ) + logger.info(f"Validation examples {len(val_set)}") + n_classes = train_set.n_classes + + train_sampler = torch.utils.data.distributed.DistributedSampler( + train_set, num_replicas=world_size, rank=local_rank + ) + + train_loader = data.DataLoader( + train_set, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + sampler=train_sampler, + ) + + val_sampler = torch.utils.data.distributed.DistributedSampler( + val_set, num_replicas=world_size, rank=local_rank + ) + + val_loader = data.DataLoader( + val_set, + batch_size=config.TEST.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + sampler=val_sampler, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + # model = get_model(config.MODEL.NAME, False, n_classes) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model = model.to(device) # Send to GPU + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + # weights are inversely proportional to the frequency of the classes in the training set + class_weights = torch.tensor( + CLASS_WEIGHTS, device=device, requires_grad=False + ) + + criterion = torch.nn.CrossEntropyLoss( + weight=class_weights, ignore_index=255, reduction="mean" + ) + + model = torch.nn.parallel.DistributedDataParallel( + model, device_ids=[device], find_unused_parameters=True + ) + + snapshot_duration = scheduler_step * len(train_loader) + warmup_duration = 5 * len(train_loader) + warmup_scheduler = LinearCyclicalScheduler( + optimizer, + "lr", + start_value=config.TRAIN.MAX_LR, + end_value=config.TRAIN.MAX_LR * world_size, + cycle_size=10 * len(train_loader), + ) + cosine_scheduler = CosineAnnealingScheduler( + optimizer, + "lr", + config.TRAIN.MAX_LR * world_size, + config.TRAIN.MIN_LR * world_size, + snapshot_duration, + ) + + scheduler = ConcatScheduler( + schedulers=[warmup_scheduler, cosine_scheduler], + durations=[warmup_duration], + ) + + trainer = create_supervised_trainer( + model, optimizer, criterion, prepare_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles + trainer.add_event_handler( + Events.EPOCH_STARTED, update_sampler_epoch(train_loader) + ) + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + + if silence_other_ranks & local_rank != 0: + logging.getLogger("ignite.engine.engine.Engine").setLevel( + logging.WARNING + ) + + def _select_pred_and_mask(model_out_dict): + return ( + model_out_dict["y_pred"].squeeze(), + model_out_dict["mask"].squeeze(), + ) + + evaluator = create_supervised_evaluator( + model, + prepare_batch, + metrics={ + "IoU": apex.MeanIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "nll": salt_metrics.LossMetric( + criterion, + world_size, + config.TEST.BATCH_SIZE_PER_GPU, + output_transform=_select_pred_and_mask, + ), + "pixa": apex.PixelwiseAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + }, + device=device, + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler( + Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) + ) + + if local_rank == 0: # Run only on master process + tboard_log_dir = generate_path(config.LOG_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime()) + logger.info(f"Logging Tensorboard to {tboard_log_dir}") + summary_writer = create_summary_writer(log_dir=tboard_log_dir) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={ + "IoU": "IoU :", + "nll": "Avg loss :", + "pixa": "Pixelwise Accuracy :", + }, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={ + "IoU": "Validation/IoU", + "nll": "Validation/Loss", + }, + ), + ) + + def _select_max(pred_tensor): + return pred_tensor.max(1)[1] + + def _tensor_to_numpy(pred_tensor): + return pred_tensor.squeeze().cpu().numpy() + + transform_func = compose( + np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy + ) + + transform_pred = compose(transform_func, _select_max) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Mask", + "mask", + transform_func=transform_func, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Pred", + "y_pred", + transform_func=transform_pred, + ), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime()) + checkpoint_handler = SnapshotHandler( + output_dir, + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger = logging.getLogger(__name__) + logger.info("Starting training") + + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/interpretation/experiments/segmentation/dutchf3/local/__init__.py b/interpretation/experiments/segmentation/dutchf3/local/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/interpretation/experiments/segmentation/dutchf3/local/augmentations.py b/interpretation/experiments/segmentation/dutchf3/local/augmentations.py deleted file mode 100644 index ba9c86ae..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/augmentations.py +++ /dev/null @@ -1,183 +0,0 @@ -import math -import numbers -import random -import numpy as np - -from PIL import Image, ImageOps, ImageChops - -class Compose(object): - def __init__(self, augmentations): - self.augmentations = augmentations - - def __call__(self, img, mask): - - img, mask = Image.fromarray(img, mode=None), Image.fromarray(mask, mode='L') - assert img.size == mask.size - - for a in self.augmentations: - img, mask = a(img, mask) - return np.array(img), np.array(mask, dtype=np.uint8) - -class AddNoise(object): - def __call__(self, img, mask): - noise = np.random.normal(loc=0,scale=0.02,size=(img.size[1], img.size[0])) - return img + noise, mask - -class RandomCrop(object): - def __init__(self, size, padding=0): - if isinstance(size, numbers.Number): - self.size = (int(size), int(size)) - else: - self.size = size - self.padding = padding - - def __call__(self, img, mask): - if self.padding > 0: - img = ImageOps.expand(img, border=self.padding, fill=0) - mask = ImageOps.expand(mask, border=self.padding, fill=0) - - assert img.size == mask.size - w, h = img.size - th, tw = self.size - if w == tw and h == th: - return img, mask - if w < tw or h < th: - return img.resize((tw, th), Image.BILINEAR), mask.resize((tw, th), Image.NEAREST) - - x1 = random.randint(0, w - tw) - y1 = random.randint(0, h - th) - return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) - - -class CenterCrop(object): - def __init__(self, size): - if isinstance(size, numbers.Number): - self.size = (int(size), int(size)) - else: - self.size = size - - def __call__(self, img, mask): - assert img.size == mask.size - w, h = img.size - th, tw = self.size - x1 = int(round((w - tw) / 2.)) - y1 = int(round((h - th) / 2.)) - return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) - - -class RandomHorizontallyFlip(object): - def __call__(self, img, mask): - if random.random() < 0.5: - #Note: we use FLIP_TOP_BOTTOM here intentionaly. Due to the dimensions of the image, - # it ends up being a horizontal flip. - return img.transpose(Image.FLIP_TOP_BOTTOM), mask.transpose(Image.FLIP_TOP_BOTTOM) - return img, mask - -class RandomVerticallyFlip(object): - def __call__(self, img, mask): - if random.random() < 0.5: - return img.transpose(Image.FLIP_LEFT_RIGHT), mask.transpose(Image.FLIP_LEFT_RIGHT) - return img, mask - -class FreeScale(object): - def __init__(self, size): - self.size = tuple(reversed(size)) # size: (h, w) - - def __call__(self, img, mask): - assert img.size == mask.size - return img.resize(self.size, Image.BILINEAR), mask.resize(self.size, Image.NEAREST) - - -class Scale(object): - def __init__(self, size): - self.size = size - - def __call__(self, img, mask): - assert img.size == mask.size - w, h = img.size - if (w >= h and w == self.size) or (h >= w and h == self.size): - return img, mask - if w > h: - ow = self.size - oh = int(self.size * h / w) - return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) - else: - oh = self.size - ow = int(self.size * w / h) - return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) - - -class RandomSizedCrop(object): - def __init__(self, size): - self.size = size - - def __call__(self, img, mask): - assert img.size == mask.size - for attempt in range(10): - area = img.size[0] * img.size[1] - target_area = random.uniform(0.45, 1.0) * area - aspect_ratio = random.uniform(0.5, 2) - - w = int(round(math.sqrt(target_area * aspect_ratio))) - h = int(round(math.sqrt(target_area / aspect_ratio))) - - if random.random() < 0.5: - w, h = h, w - - if w <= img.size[0] and h <= img.size[1]: - x1 = random.randint(0, img.size[0] - w) - y1 = random.randint(0, img.size[1] - h) - - img = img.crop((x1, y1, x1 + w, y1 + h)) - mask = mask.crop((x1, y1, x1 + w, y1 + h)) - assert (img.size == (w, h)) - - return img.resize((self.size, self.size), Image.BILINEAR), mask.resize((self.size, self.size), - Image.NEAREST) - - # Fallback - scale = Scale(self.size) - crop = CenterCrop(self.size) - return crop(*scale(img, mask)) - - -class RandomRotate(object): - def __init__(self, degree): - self.degree = degree - - def __call__(self, img, mask): - ''' - PIL automatically adds zeros to the borders of images that rotated. To fix this - issue, the code in the botton sets anywhere in the labels (mask) that is zero to - 255 (the value used for ignore_index). - ''' - rotate_degree = random.random() * 2 * self.degree - self.degree - - img = img.rotate(rotate_degree, Image.BILINEAR) - mask = mask.rotate(rotate_degree, Image.NEAREST) - - binary_mask = Image.fromarray(np.ones([mask.size[1], mask.size[0]])) - binary_mask = binary_mask.rotate(rotate_degree, Image.NEAREST) - binary_mask = np.array(binary_mask) - - mask_arr = np.array(mask) - mask_arr[binary_mask==0] = 255 - mask = Image.fromarray(mask_arr) - - return img, mask - -class RandomSized(object): - def __init__(self, size): - self.size = size - self.scale = Scale(self.size) - self.crop = RandomCrop(self.size) - - def __call__(self, img, mask): - assert img.size == mask.size - - w = int(random.uniform(0.5, 2) * img.size[0]) - h = int(random.uniform(0.5, 2) * img.size[1]) - - img, mask = img.resize((w, h), Image.BILINEAR), mask.resize((w, h), Image.NEAREST) - - return self.crop(*self.scale(img, mask)) \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml new file mode 100644 index 00000000..80d1c65c --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml @@ -0,0 +1,41 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/alaudah + +MODEL: + NAME: patch_deconvnet + IN_CHANNELS: 1 + + +TRAIN: + BATCH_SIZE_PER_GPU: 64 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.1 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + +VALIDATION: + BATCH_SIZE_PER_GPU: 128 + +TEST: + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/188e66a26115760b7cbde7e9f6a626b6519a3bb7/patch_deconvnet/Aug25_102649/patch_deconvnet_running_model_25.pth" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + diff --git a/interpretation/experiments/segmentation/dutchf3/local/data.py b/interpretation/experiments/segmentation/dutchf3/local/data.py deleted file mode 100644 index 3738571b..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/data.py +++ /dev/null @@ -1,76 +0,0 @@ -from os import path -import numpy as np -import itertools -from sklearn.model_selection import train_test_split -import logging - -DATA_ROOT = path.join("/mnt", "alaudah") -SPLITS = path.join(DATA_ROOT, "splits") -LABELS = path.join(DATA_ROOT, "train", "train_labels.npy") - - - -def split_train_val(stride, per_val=0.2, loader_type="patch", labels_path=LABELS): - # create inline and crossline pacthes for training and validation: - print("hey") - logger = logging.getLogger(__name__) - - labels = np.load(labels_path) - iline, xline, depth = labels.shape - logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") - # INLINE PATCHES: ------------------------------------------------ - i_list = [] - horz_locations = range(0, xline - stride, stride) - vert_locations = range(0, depth - stride, stride) - logger.debug("Generating Inline patches") - logger.debug(horz_locations) - logger.debug(vert_locations) - for i in range(iline): - # for every inline: - # images are references by top-left corner: - locations = [[j, k] for j in horz_locations for k in vert_locations] - patches_list = [ - "i_" + str(i) + "_" + str(j) + "_" + str(k) for j, k in locations - ] - i_list.append(patches_list) - - # flatten the list - i_list = list(itertools.chain(*i_list)) - - # XLINE PATCHES: ------------------------------------------------ - x_list = [] - horz_locations = range(0, iline - stride, stride) - vert_locations = range(0, depth - stride, stride) - for j in range(xline): - # for every xline: - # images are references by top-left corner: - locations = [[i, k] for i in horz_locations for k in vert_locations] - patches_list = [ - "x_" + str(i) + "_" + str(j) + "_" + str(k) for i, k in locations - ] - x_list.append(patches_list) - - # flatten the list - x_list = list(itertools.chain(*x_list)) - - list_train_val = i_list + x_list - - # create train and test splits: - list_train, list_val = train_test_split( - list_train_val, test_size=per_val, shuffle=True - ) - - # write to files to disk: - file_object = open( - path.join(SPLITS, loader_type + "_train_val.txt"), "w" - ) - file_object.write("\n".join(list_train_val)) - file_object.close() - file_object = open( - path.join(SPLITS, loader_type + "_train.txt"), "w" - ) - file_object.write("\n".join(list_train)) - file_object.close() - file_object = open(path.join(SPLITS, loader_type + "_val.txt"), "w") - file_object.write("\n".join(list_val)) - file_object.close() diff --git a/interpretation/experiments/segmentation/dutchf3/local/default.py b/interpretation/experiments/segmentation/dutchf3/local/default.py index 9bef9f74..2764fb00 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/default.py +++ b/interpretation/experiments/segmentation/dutchf3/local/default.py @@ -14,7 +14,7 @@ _C = CN() -_C.OUTPUT_DIR = "" +_C.OUTPUT_DIR = "output" _C.LOG_DIR = "" _C.GPUS = (0,) _C.WORKERS = 4 @@ -41,10 +41,8 @@ # common params for NETWORK _C.MODEL = CN() -_C.MODEL.NAME = "seg_hrnet" -_C.MODEL.PRETRAINED = "" -_C.MODEL.EXTRA = CN(new_allowed=True) - +_C.MODEL.NAME = "patch_deconvnet" +_C.MODEL.IN_CHANNELS = 1 # training _C.TRAIN = CN() @@ -55,20 +53,21 @@ _C.TRAIN.END_EPOCH = 484 _C.TRAIN.BATCH_SIZE_PER_GPU = 32 _C.TRAIN.WEIGHT_DECAY = 0.0001 -_C.TRAIN.PAD_LEFT = 27 -_C.TRAIN.PAD_RIGHT = 27 -_C.TRAIN.FINE_SIZE = 202 _C.TRAIN.SNAPSHOTS = 5 _C.TRAIN.SAVE_LOCATION = "/tmp/models" +_C.TRAIN.AUGMENTATION = True -# testing -_C.TEST = CN() -_C.TEST.BATCH_SIZE_PER_GPU = 32 -_C.TEST.CV = CN() -_C.TEST.CV.N_SPLITS = 5 -_C.TEST.CV.SEED = 42 -_C.TEST.CV.SHUFFLE = True +# validation +_C.VALIDATION = CN() +_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 +# TEST +_C.TEST = CN() +_C.TEST.MODEL_PATH = "" +_C.TEST.TEST_STRIDE = 10 +_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 +_C.TEST.INLINE = True +_C.TEST.CROSSLINE = True def update_config(cfg, options=None, config_file=None): diff --git a/interpretation/experiments/segmentation/dutchf3/local/loader/__init__.py b/interpretation/experiments/segmentation/dutchf3/local/loader/__init__.py deleted file mode 100644 index fe189126..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/loader/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from .data_loader import * - -def get_loader(arch): - if 'patch' in arch: - return patch_loader - elif 'section' in arch: - return section_loader - else: - NotImplementedError() \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/loader/data_loader.py b/interpretation/experiments/segmentation/dutchf3/local/loader/data_loader.py deleted file mode 100644 index fe29e537..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/loader/data_loader.py +++ /dev/null @@ -1,202 +0,0 @@ -import os -import collections -import json -import torch -import numpy as np -import matplotlib.pyplot as plt -from os import path -from torch.utils import data - -DATA_ROOT = path.join("/mnt", "alaudah") -TRAIN_PATH = path.join(DATA_ROOT, "train") -TEST_PATH = path.join(DATA_ROOT, "test_once") -TRAIN_SEISMIC = path.join(TRAIN_PATH, "train_seismic.npy") -TRAIN_LABELS = path.join(TRAIN_PATH, "train_labels.npy") - -TEST1_SEISMIC = path.join(TEST_PATH, "test1_seismic.npy") -TEST2_SEISMIC = path.join(TEST_PATH, "test2_seismic.npy") - -TEST1_LABELS = path.join(TEST_PATH, "test1_labels.npy") -TEST2_LABELS = path.join(TEST_PATH, "test2_labels.npy") - - - -class PatchLoader(data.Dataset): - """ - Data loader for the patch-based deconvnet - """ - - def __init__( - self, stride=30, patch_size=99, is_transform=True, augmentations=None - ): - self.root = DATA_ROOT - self.is_transform = is_transform - self.augmentations = augmentations - self.n_classes = 6 - self.mean = 0.000941 # average of the training data - self.patches = collections.defaultdict(list) - self.patch_size = patch_size - self.stride = stride - - def pad_volume(self, volume): - """ - Only used for train/val!! Not test. - """ - return np.pad( - volume, - pad_width=self.patch_size, - mode="constant", - constant_values=255, - ) - - def __len__(self): - return len(self.patches[self.split]) - - def __getitem__(self, index): - - patch_name = self.patches[self.split][index] - direction, idx, xdx, ddx = patch_name.split(sep="_") - - shift = self.patch_size if "test" not in self.split else 0 - idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift - - if direction == "i": - im = self.seismic[ - idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size - ] - lbl = self.labels[ - idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size - ] - elif direction == "x": - im = self.seismic[ - idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size - ] - lbl = self.labels[ - idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size - ] - - if self.augmentations is not None: - im, lbl = self.augmentations(im, lbl) - - if self.is_transform: - im, lbl = self.transform(im, lbl) - return im, lbl - - def transform(self, img, lbl): - img -= self.mean - - # to be in the BxCxHxW that PyTorch uses: - img, lbl = img.T, lbl.T - - img = np.expand_dims(img, 0) - lbl = np.expand_dims(lbl, 0) - - img = torch.from_numpy(img) - img = img.float() - lbl = torch.from_numpy(lbl) - lbl = lbl.long() - - return img, lbl - - - -class TestPatchLoader(PatchLoader): - def __init__( - self, - stride=30, - patch_size=99, - is_transform=True, - augmentations=None, - seismic_path=TEST1_SEISMIC, - labels_path=TEST1_LABELS, - ): - super(TestPatchLoader, self).__init__( - stride=stride, - patch_size=patch_size, - is_transform=is_transform, - augmentations=augmentations, - ) - self.seismic = np.load(seismic_path) - self.labels = np.load(labels_path) - print(self.seismic.shape) - print(self.labels.shape) - # We are in test mode. Only read the given split. The other one might not - # be available. - self.split="test1" #TODO: Fix this can also be test2 - txt_path = path.join(DATA_ROOT, "splits", "patch_" + self.split + ".txt") - patch_list = tuple(open(txt_path, "r")) - self.patches[split] = patch_list - - - -class TrainPatchLoader(PatchLoader): - def __init__( - self, - split="train", - stride=30, - patch_size=99, - is_transform=True, - augmentations=None, - seismic_path=TRAIN_SEISMIC, - labels_path=TRAIN_LABELS, - ): - super(TrainPatchLoader, self).__init__( - stride=stride, - patch_size=patch_size, - is_transform=is_transform, - augmentations=augmentations, - ) - self.seismic = self.pad_volume(np.load(seismic_path)) - self.labels = self.pad_volume(np.load(labels_path)) - print(self.seismic.shape) - print(self.labels.shape) - # We are in train/val mode. Most likely the test splits are not saved yet, - # so don't attempt to load them. - self.split=split - for split in ["train", "val", "train_val"]: - # reading the file names for 'train', 'val', 'trainval'"" - txt_path = path.join(DATA_ROOT, "splits", "patch_" + split + ".txt") - patch_list = tuple(open(txt_path, "r")) - self.patches[split] = patch_list - -def get_seismic_labels(): - return np.asarray( - [ - [69, 117, 180], - [145, 191, 219], - [224, 243, 248], - [254, 224, 144], - [252, 141, 89], - [215, 48, 39], - ] - ) - - -def decode_segmap(label_mask, n_classes, plot=False): - """Decode segmentation class labels into a color image - Args: - label_mask (np.ndarray): an (M,N) array of integer values denoting - the class label at each spatial location. - plot (bool, optional): whether to show the resulting color image - in a figure. - Returns: - (np.ndarray, optional): the resulting decoded color image. - """ - label_colours = get_seismic_labels() - r = label_mask.copy() - g = label_mask.copy() - b = label_mask.copy() - for ll in range(0, n_classes): - r[label_mask == ll] = label_colours[ll, 0] - g[label_mask == ll] = label_colours[ll, 1] - b[label_mask == ll] = label_colours[ll, 2] - rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], 3)) - rgb[:, :, 0] = r / 255.0 - rgb[:, :, 1] = g / 255.0 - rgb[:, :, 2] = b / 255.0 - if plot: - plt.imshow(rgb) - plt.show() - else: - return rgb - diff --git a/interpretation/experiments/segmentation/dutchf3/local/loss.py b/interpretation/experiments/segmentation/dutchf3/local/loss.py deleted file mode 100644 index 248a9e36..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/loss.py +++ /dev/null @@ -1,12 +0,0 @@ -import torch -import torch.nn.functional as F -from toolz import curry - -@curry -def cross_entropy(prediction, target, weight=None, ignore_index=255): - ''' - Use 255 to fill empty values when padding or doing any augmentation operations - like rotation. - ''' - loss = F.cross_entropy(prediction, target, weight, reduction='mean', ignore_index=255) - return loss diff --git a/interpretation/experiments/segmentation/dutchf3/local/metrics.py b/interpretation/experiments/segmentation/dutchf3/local/metrics.py deleted file mode 100644 index c096537c..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/metrics.py +++ /dev/null @@ -1,45 +0,0 @@ -import numpy as np - -class runningScore(object): - - def __init__(self, n_classes): - self.n_classes = n_classes - self.confusion_matrix = np.zeros((n_classes, n_classes)) - - def _fast_hist(self, label_true, label_pred, n_class): - mask = (label_true >= 0) & (label_true < n_class) - hist = np.bincount( - n_class * label_true[mask].astype(int) + - label_pred[mask], minlength=n_class**2).reshape(n_class, n_class) - return hist - - def update(self, label_trues, label_preds): - for lt, lp in zip(label_trues, label_preds): - self.confusion_matrix += self._fast_hist(lt.flatten(), lp.flatten(), self.n_classes) - - def get_scores(self): - """Returns accuracy score evaluation result. - - overall accuracy - - mean accuracy - - mean IU - - fwavacc - """ - hist = self.confusion_matrix - acc = np.diag(hist).sum() / hist.sum() - acc_cls = np.diag(hist) / hist.sum(axis=1) - mean_acc_cls = np.nanmean(acc_cls) - iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) - mean_iu = np.nanmean(iu) - freq = hist.sum(axis=1) / hist.sum() # fraction of the pixels that come from each class - fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() - cls_iu = dict(zip(range(self.n_classes), iu)) - - return {'Pixel Acc: ': acc, - 'Class Accuracy: ': acc_cls, - 'Mean Class Acc: ': mean_acc_cls, - 'Freq Weighted IoU: ': fwavacc, - 'Mean IoU: ': mean_iu, - 'confusion_matrix': self.confusion_matrix}, cls_iu - - def reset(self): - self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) diff --git a/interpretation/experiments/segmentation/dutchf3/local/models/__init__.py b/interpretation/experiments/segmentation/dutchf3/local/models/__init__.py deleted file mode 100644 index 7e582634..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/models/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -import torchvision.models as models -from .patch_deconvnet import * -from .section_deconvnet import * - -def get_model(name, pretrained, n_classes): - model = _get_model_instance(name) - - if name in ['section_deconvnet','patch_deconvnet']: - model = model(n_classes=n_classes) - vgg16 = models.vgg16(pretrained=pretrained) - model.init_vgg16_params(vgg16) - else: - model = model(n_classes=n_classes) - - return model - -def _get_model_instance(name): - try: - return { - 'section_deconvnet': section_deconvnet, - 'patch_deconvnet':patch_deconvnet, - 'section_deconvnet_skip': section_deconvnet_skip, - 'patch_deconvnet_skip':patch_deconvnet_skip, - }[name] - except: - print(f'Model {name} not available') diff --git a/interpretation/experiments/segmentation/dutchf3/local/models/patch_deconvnet.py b/interpretation/experiments/segmentation/dutchf3/local/models/patch_deconvnet.py deleted file mode 100644 index d16b2356..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/models/patch_deconvnet.py +++ /dev/null @@ -1,632 +0,0 @@ -import torch.nn as nn - -class patch_deconvnet(nn.Module): - - def __init__(self, n_classes=4, learned_billinear=False): - super(patch_deconvnet, self).__init__() - self.learned_billinear = learned_billinear - self.n_classes = n_classes - self.unpool = nn.MaxUnpool2d(2, stride=2) - self.conv_block1 = nn.Sequential( - - # conv1_1 - nn.Conv2d(1, 64, 3, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv1_2 - nn.Conv2d(64, 64, 3, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool1 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_1 - - # 48*48 - - self.conv_block2 = nn.Sequential( - - # conv2_1 - nn.Conv2d(64, 128, 3, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv2_2 - nn.Conv2d(128, 128, 3, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool2 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_2 - - # 24*24 - - self.conv_block3 = nn.Sequential( - - # conv3_1 - nn.Conv2d(128, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv3_2 - nn.Conv2d(256, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv3_3 - nn.Conv2d(256, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool3 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_3 - - # 12*12 - - self.conv_block4 = nn.Sequential( - - # conv4_1 - nn.Conv2d(256, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv4_2 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv4_3 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool4 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_4 - - # 6*6 - - self.conv_block5 = nn.Sequential( - - # conv5_1 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv5_2 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv5_3 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool5 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_5 - - # 3*3 - - self.conv_block6 = nn.Sequential( - - # fc6 - nn.Conv2d(512, 4096, 3), - # set the filter size and nor padding to make output into 1*1 - nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - # 1*1 - - self.conv_block7 = nn.Sequential( - - # fc7 - nn.Conv2d(4096, 4096, 1), - # set the filter size to make output into 1*1 - nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.deconv_block8 = nn.Sequential( - - # fc6-deconv - nn.ConvTranspose2d(4096, 512, 3, stride=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - # 3*3 - - self.unpool_block9 = nn.Sequential( - - # unpool5 - nn.MaxUnpool2d(2, stride=2), ) - # usage unpool(output, indices) - - # 6*6 - - self.deconv_block10 = nn.Sequential( - - # deconv5_1 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv5_2 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv5_3 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block11 = nn.Sequential( - - # unpool4 - nn.MaxUnpool2d(2, stride=2), ) - - # 12*12 - - self.deconv_block12 = nn.Sequential( - - # deconv4_1 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv4_2 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv4_3 - nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block13 = nn.Sequential( - - # unpool3 - nn.MaxUnpool2d(2, stride=2), ) - - # 24*24 - - self.deconv_block14 = nn.Sequential( - - # deconv3_1 - nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv3_2 - nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv3_3 - nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block15 = nn.Sequential( - - # unpool2 - nn.MaxUnpool2d(2, stride=2), ) - - # 48*48 - - self.deconv_block16 = nn.Sequential( - - # deconv2_1 - nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv2_2 - nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block17 = nn.Sequential( - - # unpool1 - nn.MaxUnpool2d(2, stride=2), ) - - # 96*96 - - self.deconv_block18 = nn.Sequential( - - # deconv1_1 - nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv1_2 - nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.seg_score19 = nn.Sequential( - - # seg-score - nn.Conv2d(64, self.n_classes, 1), ) - - if self.learned_billinear: - raise NotImplementedError - - def forward(self, x): - size0 = x.size() - conv1, indices1 = self.conv_block1(x) - size1 = conv1.size() - conv2, indices2 = self.conv_block2(conv1) - size2 = conv2.size() - conv3, indices3 = self.conv_block3(conv2) - size3 = conv3.size() - conv4, indices4 = self.conv_block4(conv3) - size4 = conv4.size() - conv5, indices5 = self.conv_block5(conv4) - - conv6 = self.conv_block6(conv5) - conv7 = self.conv_block7(conv6) - conv8 = self.deconv_block8(conv7) - conv9 = self.unpool(conv8, indices5, output_size=size4) - conv10 = self.deconv_block10(conv9) - conv11 = self.unpool(conv10, indices4, output_size=size3) - conv12 = self.deconv_block12(conv11) - conv13 = self.unpool(conv12, indices3, output_size=size2) - conv14 = self.deconv_block14(conv13) - conv15 = self.unpool(conv14, indices2, output_size=size1) - conv16 = self.deconv_block16(conv15) - conv17 = self.unpool(conv16, indices1, output_size=size0) - conv18 = self.deconv_block18(conv17) - out = self.seg_score19(conv18) - - return out - - def init_vgg16_params(self, vgg16, copy_fc8=True): - blocks = [self.conv_block1, - self.conv_block2, - self.conv_block3, - self.conv_block4, - self.conv_block5] - - ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] - features = list(vgg16.features.children()) - i_layer = 0; - # copy convolutional filters from vgg16 - for idx, conv_block in enumerate(blocks): - for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): - if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): - if i_layer == 0: - l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, - 2, :, - :]) / 3.0).view( - l2.weight.size()) - l2.bias.data = l1.bias.data - i_layer = i_layer + 1 - else: - assert l1.weight.size() == l2.weight.size() - assert l1.bias.size() == l2.bias.size() - l2.weight.data = l1.weight.data - l2.bias.data = l1.bias.data - i_layer = i_layer + 1 - - -class patch_deconvnet_skip(nn.Module): - - def __init__(self, n_classes=4, learned_billinear=False): - super(patch_deconvnet_skip, self).__init__() - self.learned_billinear = learned_billinear - self.n_classes = n_classes - self.unpool = nn.MaxUnpool2d(2, stride=2) - self.conv_block1 = nn.Sequential( - - # conv1_1 - nn.Conv2d(1, 64, 3, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv1_2 - nn.Conv2d(64, 64, 3, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool1 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_1 - - # 48*48 - - self.conv_block2 = nn.Sequential( - - # conv2_1 - nn.Conv2d(64, 128, 3, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv2_2 - nn.Conv2d(128, 128, 3, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool2 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_2 - - # 24*24 - - self.conv_block3 = nn.Sequential( - - # conv3_1 - nn.Conv2d(128, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv3_2 - nn.Conv2d(256, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv3_3 - nn.Conv2d(256, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool3 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_3 - - # 12*12 - - self.conv_block4 = nn.Sequential( - - # conv4_1 - nn.Conv2d(256, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv4_2 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv4_3 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool4 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_4 - - # 6*6 - - self.conv_block5 = nn.Sequential( - - # conv5_1 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv5_2 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv5_3 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool5 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_5 - - # 3*3 - - self.conv_block6 = nn.Sequential( - - # fc6 - nn.Conv2d(512, 4096, 3), - # set the filter size and nor padding to make output into 1*1 - nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - # 1*1 - - self.conv_block7 = nn.Sequential( - - # fc7 - nn.Conv2d(4096, 4096, 1), - # set the filter size to make output into 1*1 - nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.deconv_block8 = nn.Sequential( - - # fc6-deconv - nn.ConvTranspose2d(4096, 512, 3, stride=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - # 3*3 - - self.unpool_block9 = nn.Sequential( - - # unpool5 - nn.MaxUnpool2d(2, stride=2), ) - # usage unpool(output, indices) - - # 6*6 - - self.deconv_block10 = nn.Sequential( - - # deconv5_1 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv5_2 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv5_3 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block11 = nn.Sequential( - - # unpool4 - nn.MaxUnpool2d(2, stride=2), ) - - # 12*12 - - self.deconv_block12 = nn.Sequential( - - # deconv4_1 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv4_2 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv4_3 - nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block13 = nn.Sequential( - - # unpool3 - nn.MaxUnpool2d(2, stride=2), ) - - # 24*24 - - self.deconv_block14 = nn.Sequential( - - # deconv3_1 - nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv3_2 - nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv3_3 - nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block15 = nn.Sequential( - - # unpool2 - nn.MaxUnpool2d(2, stride=2), ) - - # 48*48 - - self.deconv_block16 = nn.Sequential( - - # deconv2_1 - nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv2_2 - nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block17 = nn.Sequential( - - # unpool1 - nn.MaxUnpool2d(2, stride=2), ) - - # 96*96 - - self.deconv_block18 = nn.Sequential( - - # deconv1_1 - nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv1_2 - nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.seg_score19 = nn.Sequential( - - # seg-score - nn.Conv2d(64, self.n_classes, 1), ) - - if self.learned_billinear: - raise NotImplementedError - - def forward(self, x): - size0 = x.size() - conv1, indices1 = self.conv_block1(x) - size1 = conv1.size() - conv2, indices2 = self.conv_block2(conv1) - size2 = conv2.size() - conv3, indices3 = self.conv_block3(conv2) - size3 = conv3.size() - conv4, indices4 = self.conv_block4(conv3) - size4 = conv4.size() - conv5, indices5 = self.conv_block5(conv4) - - conv6 = self.conv_block6(conv5) - conv7 = self.conv_block7(conv6) - conv8 = self.deconv_block8(conv7) + conv5 - conv9 = self.unpool(conv8,indices5, output_size=size4) - conv10 = self.deconv_block10(conv9) + conv4 - conv11 = self.unpool(conv10,indices4, output_size=size3) - conv12 = self.deconv_block12(conv11) + conv3 - conv13 = self.unpool(conv12,indices3, output_size=size2) - conv14 = self.deconv_block14(conv13) + conv2 - conv15 = self.unpool(conv14,indices2, output_size=size1) - conv16 = self.deconv_block16(conv15) + conv1 - conv17 = self.unpool(conv16,indices1, output_size=size0) - conv18 = self.deconv_block18(conv17) - out = self.seg_score19(conv18) - - return out - - def init_vgg16_params(self, vgg16, copy_fc8=True): - blocks = [self.conv_block1, - self.conv_block2, - self.conv_block3, - self.conv_block4, - self.conv_block5] - - ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] - features = list(vgg16.features.children()) - i_layer = 0; - # copy convolutional filters from vgg16 - for idx, conv_block in enumerate(blocks): - for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): - if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): - if i_layer == 0: - l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, - 2, :, - :]) / 3.0).view( - l2.weight.size()) - l2.bias.data = l1.bias.data - i_layer = i_layer + 1 - else: - assert l1.weight.size() == l2.weight.size() - assert l1.bias.size() == l2.bias.size() - l2.weight.data = l1.weight.data - l2.bias.data = l1.bias.data - i_layer = i_layer + 1 diff --git a/interpretation/experiments/segmentation/dutchf3/local/models/section_deconvnet.py b/interpretation/experiments/segmentation/dutchf3/local/models/section_deconvnet.py deleted file mode 100644 index d95df346..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/models/section_deconvnet.py +++ /dev/null @@ -1,632 +0,0 @@ -import torch.nn as nn - -class section_deconvnet(nn.Module): - - def __init__(self, n_classes=4, learned_billinear=False): - super(section_deconvnet, self).__init__() - self.learned_billinear = learned_billinear - self.n_classes = n_classes - self.unpool = nn.MaxUnpool2d(2, stride=2) - self.conv_block1 = nn.Sequential( - - # conv1_1 - nn.Conv2d(1, 64, 3, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv1_2 - nn.Conv2d(64, 64, 3, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool1 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_1 - - # 48*48 - - self.conv_block2 = nn.Sequential( - - # conv2_1 - nn.Conv2d(64, 128, 3, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv2_2 - nn.Conv2d(128, 128, 3, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool2 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_2 - - # 24*24 - - self.conv_block3 = nn.Sequential( - - # conv3_1 - nn.Conv2d(128, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv3_2 - nn.Conv2d(256, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv3_3 - nn.Conv2d(256, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool3 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_3 - - # 12*12 - - self.conv_block4 = nn.Sequential( - - # conv4_1 - nn.Conv2d(256, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv4_2 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv4_3 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool4 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_4 - - # 6*6 - - self.conv_block5 = nn.Sequential( - - # conv5_1 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv5_2 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv5_3 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool5 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_5 - - # 3*3 - - self.conv_block6 = nn.Sequential( - - # fc6 - nn.Conv2d(512, 4096, 3), - # set the filter size and nor padding to make output into 1*1 - nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - # 1*1 - - self.conv_block7 = nn.Sequential( - - # fc7 - nn.Conv2d(4096, 4096, 1), - # set the filter size to make output into 1*1 - nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.deconv_block8 = nn.Sequential( - - # fc6-deconv - nn.ConvTranspose2d(4096, 512, 3, stride=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - # 3*3 - - self.unpool_block9 = nn.Sequential( - - # unpool5 - nn.MaxUnpool2d(2, stride=2), ) - # usage unpool(output, indices) - - # 6*6 - - self.deconv_block10 = nn.Sequential( - - # deconv5_1 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv5_2 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv5_3 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block11 = nn.Sequential( - - # unpool4 - nn.MaxUnpool2d(2, stride=2), ) - - # 12*12 - - self.deconv_block12 = nn.Sequential( - - # deconv4_1 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv4_2 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv4_3 - nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block13 = nn.Sequential( - - # unpool3 - nn.MaxUnpool2d(2, stride=2), ) - - # 24*24 - - self.deconv_block14 = nn.Sequential( - - # deconv3_1 - nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv3_2 - nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv3_3 - nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block15 = nn.Sequential( - - # unpool2 - nn.MaxUnpool2d(2, stride=2), ) - - # 48*48 - - self.deconv_block16 = nn.Sequential( - - # deconv2_1 - nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv2_2 - nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block17 = nn.Sequential( - - # unpool1 - nn.MaxUnpool2d(2, stride=2), ) - - # 96*96 - - self.deconv_block18 = nn.Sequential( - - # deconv1_1 - nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv1_2 - nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.seg_score19 = nn.Sequential( - - # seg-score - nn.Conv2d(64, self.n_classes, 1), ) - - if self.learned_billinear: - raise NotImplementedError - - def forward(self, x): - size0 = x.size() - conv1, indices1 = self.conv_block1(x) - size1 = conv1.size() - conv2, indices2 = self.conv_block2(conv1) - size2 = conv2.size() - conv3, indices3 = self.conv_block3(conv2) - size3 = conv3.size() - conv4, indices4 = self.conv_block4(conv3) - size4 = conv4.size() - conv5, indices5 = self.conv_block5(conv4) - - conv6 = self.conv_block6(conv5) - conv7 = self.conv_block7(conv6) - conv8 = self.deconv_block8(conv7) - conv9 = self.unpool(conv8,indices5, output_size=size4) - conv10 = self.deconv_block10(conv9) - conv11 = self.unpool(conv10,indices4, output_size=size3) - conv12 = self.deconv_block12(conv11) - conv13 = self.unpool(conv12,indices3, output_size=size2) - conv14 = self.deconv_block14(conv13) - conv15 = self.unpool(conv14,indices2, output_size=size1) - conv16 = self.deconv_block16(conv15) - conv17 = self.unpool(conv16,indices1, output_size=size0) - conv18 = self.deconv_block18(conv17) - out = self.seg_score19(conv18) - - return out - - def init_vgg16_params(self, vgg16, copy_fc8=True): - blocks = [self.conv_block1, - self.conv_block2, - self.conv_block3, - self.conv_block4, - self.conv_block5] - - ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] - features = list(vgg16.features.children()) - i_layer = 0; - # copy convolutional filters from vgg16 - for idx, conv_block in enumerate(blocks): - for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): - if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): - if i_layer == 0: - l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, - 2, :, - :]) / 3.0).view( - l2.weight.size()) - l2.bias.data = l1.bias.data - i_layer = i_layer + 1 - else: - assert l1.weight.size() == l2.weight.size() - assert l1.bias.size() == l2.bias.size() - l2.weight.data = l1.weight.data - l2.bias.data = l1.bias.data - i_layer = i_layer + 1 - - -class section_deconvnet_skip(nn.Module): - - def __init__(self, n_classes=4, learned_billinear=False): - super(section_deconvnet_skip, self).__init__() - self.learned_billinear = learned_billinear - self.n_classes = n_classes - self.unpool = nn.MaxUnpool2d(2, stride=2) - self.conv_block1 = nn.Sequential( - - # conv1_1 - nn.Conv2d(1, 64, 3, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv1_2 - nn.Conv2d(64, 64, 3, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool1 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_1 - - # 48*48 - - self.conv_block2 = nn.Sequential( - - # conv2_1 - nn.Conv2d(64, 128, 3, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv2_2 - nn.Conv2d(128, 128, 3, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool2 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_2 - - # 24*24 - - self.conv_block3 = nn.Sequential( - - # conv3_1 - nn.Conv2d(128, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv3_2 - nn.Conv2d(256, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv3_3 - nn.Conv2d(256, 256, 3, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool3 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_3 - - # 12*12 - - self.conv_block4 = nn.Sequential( - - # conv4_1 - nn.Conv2d(256, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv4_2 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv4_3 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool4 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_4 - - # 6*6 - - self.conv_block5 = nn.Sequential( - - # conv5_1 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv5_2 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # conv5_3 - nn.Conv2d(512, 512, 3, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # pool5 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) - # it returns outputs and pool_indices_5 - - # 3*3 - - self.conv_block6 = nn.Sequential( - - # fc6 - nn.Conv2d(512, 4096, 3), - # set the filter size and nor padding to make output into 1*1 - nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - # 1*1 - - self.conv_block7 = nn.Sequential( - - # fc7 - nn.Conv2d(4096, 4096, 1), - # set the filter size to make output into 1*1 - nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.deconv_block8 = nn.Sequential( - - # fc6-deconv - nn.ConvTranspose2d(4096, 512, 3, stride=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - # 3*3 - - self.unpool_block9 = nn.Sequential( - - # unpool5 - nn.MaxUnpool2d(2, stride=2), ) - # usage unpool(output, indices) - - # 6*6 - - self.deconv_block10 = nn.Sequential( - - # deconv5_1 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv5_2 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv5_3 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block11 = nn.Sequential( - - # unpool4 - nn.MaxUnpool2d(2, stride=2), ) - - # 12*12 - - self.deconv_block12 = nn.Sequential( - - # deconv4_1 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv4_2 - nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), - nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv4_3 - nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block13 = nn.Sequential( - - # unpool3 - nn.MaxUnpool2d(2, stride=2), ) - - # 24*24 - - self.deconv_block14 = nn.Sequential( - - # deconv3_1 - nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv3_2 - nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), - nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv3_3 - nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block15 = nn.Sequential( - - # unpool2 - nn.MaxUnpool2d(2, stride=2), ) - - # 48*48 - - self.deconv_block16 = nn.Sequential( - - # deconv2_1 - nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), - nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv2_2 - nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.unpool_block17 = nn.Sequential( - - # unpool1 - nn.MaxUnpool2d(2, stride=2), ) - - # 96*96 - - self.deconv_block18 = nn.Sequential( - - # deconv1_1 - nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), - - # deconv1_2 - nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), - nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) - - self.seg_score19 = nn.Sequential( - - # seg-score - nn.Conv2d(64, self.n_classes, 1), ) - - if self.learned_billinear: - raise NotImplementedError - - def forward(self, x): - size0 = x.size() - conv1, indices1 = self.conv_block1(x) - size1 = conv1.size() - conv2, indices2 = self.conv_block2(conv1) - size2 = conv2.size() - conv3, indices3 = self.conv_block3(conv2) - size3 = conv3.size() - conv4, indices4 = self.conv_block4(conv3) - size4 = conv4.size() - conv5, indices5 = self.conv_block5(conv4) - - conv6 = self.conv_block6(conv5) - conv7 = self.conv_block7(conv6) - conv8 = self.deconv_block8(conv7) + conv5 - conv9 = self.unpool(conv8,indices5, output_size=size4) - conv10 = self.deconv_block10(conv9) + conv4 - conv11 = self.unpool(conv10,indices4, output_size=size3) - conv12 = self.deconv_block12(conv11) + conv3 - conv13 = self.unpool(conv12,indices3, output_size=size2) - conv14 = self.deconv_block14(conv13) + conv2 - conv15 = self.unpool(conv14,indices2, output_size=size1) - conv16 = self.deconv_block16(conv15) + conv1 - conv17 = self.unpool(conv16,indices1, output_size=size0) - conv18 = self.deconv_block18(conv17) - out = self.seg_score19(conv18) - - return out - - def init_vgg16_params(self, vgg16, copy_fc8=True): - blocks = [self.conv_block1, - self.conv_block2, - self.conv_block3, - self.conv_block4, - self.conv_block5] - - ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] - features = list(vgg16.features.children()) - i_layer = 0; - # copy convolutional filters from vgg16 - for idx, conv_block in enumerate(blocks): - for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): - if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): - if i_layer == 0: - l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, - 2, :, - :]) / 3.0).view( - l2.weight.size()) - l2.bias.data = l1.bias.data - i_layer = i_layer + 1 - else: - assert l1.weight.size() == l2.weight.size() - assert l1.bias.size() == l2.bias.size() - l2.weight.data = l1.weight.data - l2.bias.data = l1.bias.data - i_layer = i_layer + 1 diff --git a/interpretation/experiments/segmentation/dutchf3/local/models/utils.py b/interpretation/experiments/segmentation/dutchf3/local/models/utils.py deleted file mode 100644 index 359544b9..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/models/utils.py +++ /dev/null @@ -1,72 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class conv2DBatchNorm(nn.Module): - def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): - super(conv2DBatchNorm, self).__init__() - - if dilation > 1: - conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias, dilation=dilation) - - else: - conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias, dilation=1) - - - self.cb_unit = nn.Sequential(conv_mod, - nn.BatchNorm2d(int(n_filters)),) - - def forward(self, inputs): - outputs = self.cb_unit(inputs) - return outputs - - -class deconv2DBatchNorm(nn.Module): - def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): - super(deconv2DBatchNorm, self).__init__() - - self.dcb_unit = nn.Sequential(nn.ConvTranspose2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias), - nn.BatchNorm2d(int(n_filters)),) - - def forward(self, inputs): - outputs = self.dcb_unit(inputs) - return outputs - - -class conv2DBatchNormRelu(nn.Module): - def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): - super(conv2DBatchNormRelu, self).__init__() - - if dilation > 1: - conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias, dilation=dilation) - - else: - conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias, dilation=1) - - self.cbr_unit = nn.Sequential(conv_mod, - nn.BatchNorm2d(int(n_filters)), - nn.ReLU(inplace=True),) - - def forward(self, inputs): - outputs = self.cbr_unit(inputs) - return outputs - - -class deconv2DBatchNormRelu(nn.Module): - def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): - super(deconv2DBatchNormRelu, self).__init__() - - self.dcbr_unit = nn.Sequential(nn.ConvTranspose2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias), - nn.BatchNorm2d(int(n_filters)), - nn.ReLU(inplace=True),) - - def forward(self, inputs): - outputs = self.dcbr_unit(inputs) - return outputs diff --git a/interpretation/experiments/segmentation/dutchf3/local/orig_train.py b/interpretation/experiments/segmentation/dutchf3/local/orig_train.py deleted file mode 100644 index 3329f841..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/orig_train.py +++ /dev/null @@ -1,179 +0,0 @@ -""" Train model on TGS Salt Dataset - -""" - -import logging -import logging.config - -import fire -import torch -from default import _C as config -from default import update_config -from ignite.contrib.handlers import CosineAnnealingScheduler -from ignite.engine import Events -from ignite.metrics import Loss - -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) - get_data_loaders, - kfold_split, - prepare_train_batch, - prepare_val_batch, -) - create_supervised_evaluator, - create_supervised_trainer, -) - - -def run(*options, cfg=None): - """Run training and validation of model - - Notes: - Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file - - Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py - cfg (str, optional): Location of config file to load. Defaults to None. - """ - - update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - train_ids, _, _ = get_data_ids( - config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" - ) - fold_generator = kfold_split( - train_ids, - n_splits=config.TEST.CV.N_SPLITS, - random_state=config.TEST.CV.SEED, - shuffle=config.TEST.CV.SHUFFLE, - ) - train_idx, val_idx = next(fold_generator) - val_ids = train_ids[val_idx] - train_ids = train_ids[train_idx] - train_loader, val_loader = get_data_loaders( - train_ids, - val_ids, - config.TRAIN.BATCH_SIZE_PER_GPU, - config.TEST.BATCH_SIZE_PER_GPU, - config.TRAIN.FINE_SIZE, - config.TRAIN.PAD_LEFT, - config.TRAIN.PAD_RIGHT, - config.DATASET.ROOT, - ) - - model = getattr(models, config.MODEL.NAME).get_seg_model(config) - criterion = torch.nn.BCEWithLogitsLoss() - - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" - - optimizer = torch.optim.SGD( - model.parameters(), - lr=config.TRAIN.MAX_LR, - momentum=config.TRAIN.MOMENTUM, - weight_decay=config.TRAIN.WEIGHT_DECAY, - ) - - summary_writer = create_summary_writer(log_dir=config.LOG_DIR) - snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration - ) - - trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_train_batch, device=device - ) - - trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) - trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), - ) - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), - ) - - evaluator = create_supervised_evaluator( - model, - prepare_val_batch, - metrics={ - "kaggle": KaggleMetric(output_transform=lambda x: (x["y_pred"], x["mask"])), - "nll": Loss(criterion, output_transform=lambda x: (x["y_pred"], x["mask"])), - "pixa": PixelwiseAccuracy(output_transform=lambda x: (x["y_pred"], x["mask"])) - }, - device=device, - output_transform=padded_val_transform(config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE), - ) - - # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - logging_handlers.log_metrics( - "Validation results", - metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - tensorboard_handlers.log_metrics( - summary_writer, - trainer, - "epoch", - metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Mask", "mask"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Pred", "y_pred"), - ) - - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - - checkpoint_handler = SnapshotHandler( - config.OUTPUT_DIR, - config.MODEL.NAME, - snapshot_function, - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) - - logger = logging.getLogger(__name__) - logger.info("Starting training") - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) - - -if __name__ == "__main__": - fire.Fire(run) diff --git a/interpretation/experiments/segmentation/dutchf3/local/run.sh b/interpretation/experiments/segmentation/dutchf3/local/run.sh index 7a321950..c1eb179b 100755 --- a/interpretation/experiments/segmentation/dutchf3/local/run.sh +++ b/interpretation/experiments/segmentation/dutchf3/local/run.sh @@ -1,3 +1,3 @@ #!/bin/bash export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python train.py --cfg "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml" \ No newline at end of file +python train.py --cfg "configs/patch_deconvnet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.py b/interpretation/experiments/segmentation/dutchf3/local/test.py new file mode 100644 index 00000000..235b7ae3 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/test.py @@ -0,0 +1,296 @@ +import argparse +import logging +import logging.config +import os +from datetime import datetime +from os import path +from os.path import join as pjoin + +import fire +import numpy as np +import torch +import torch.nn.functional as F +import torchvision.utils as vutils +from ignite.contrib.handlers import ( + ConcatScheduler, + CosineAnnealingScheduler, + CustomPeriodicEvent, + LinearCyclicalScheduler, +) +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from sklearn.model_selection import train_test_split +from tensorboardX import SummaryWriter +from toolz import compose, curry +from torch.utils import data +from tqdm import tqdm + +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) +from cv_lib.segmentation.dutchf3.augmentations import ( + AddNoise, + Compose, + RandomHorizontallyFlip, + RandomRotate, +) +from cv_lib.segmentation.dutchf3.data import ( + decode_segmap, + get_train_loader, + split_non_overlapping_train_val, + split_train_val, + section_loader, +) +from cv_lib.segmentation.dutchf3.engine import ( + create_supervised_evaluator, + create_supervised_trainer, +) +from cv_lib.segmentation.dutchf3.metrics import ( + MeanIoU, + PixelwiseAccuracy, + apex, +) +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) + get_data_ids, + get_distributed_data_loaders, + kfold_split, +) + create_supervised_evaluator, + create_supervised_trainer, +) +from default import _C as config +from default import update_config +from toolz.itertoolz import take +import itertools +from toolz import itertoolz + + +class_names = [ + "upper_ns", + "middle_ns", + "lower_ns", + "rijnland_chalk", + "scruff", + "zechstein", +] + + +class runningScore(object): + def __init__(self, n_classes): + self.n_classes = n_classes + self.confusion_matrix = np.zeros((n_classes, n_classes)) + + def _fast_hist(self, label_true, label_pred, n_class): + mask = (label_true >= 0) & (label_true < n_class) + hist = np.bincount( + n_class * label_true[mask].astype(int) + label_pred[mask], + minlength=n_class ** 2, + ).reshape(n_class, n_class) + return hist + + def update(self, label_trues, label_preds): + for lt, lp in zip(label_trues, label_preds): + self.confusion_matrix += self._fast_hist( + lt.flatten(), lp.flatten(), self.n_classes + ) + + def get_scores(self): + """Returns accuracy score evaluation result. + - overall accuracy + - mean accuracy + - mean IU + - fwavacc + """ + hist = self.confusion_matrix + acc = np.diag(hist).sum() / hist.sum() + acc_cls = np.diag(hist) / hist.sum(axis=1) + mean_acc_cls = np.nanmean(acc_cls) + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + mean_iu = np.nanmean(iu) + freq = ( + hist.sum(axis=1) / hist.sum() + ) # fraction of the pixels that come from each class + fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() + cls_iu = dict(zip(range(self.n_classes), iu)) + + return ( + { + "Pixel Acc: ": acc, + "Class Accuracy: ": acc_cls, + "Mean Class Acc: ": mean_acc_cls, + "Freq Weighted IoU: ": fwavacc, + "Mean IoU: ": mean_iu, + "confusion_matrix": self.confusion_matrix, + }, + cls_iu, + ) + + def reset(self): + self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) + + +def _extract_patch(img_p, hdx, wdx, ps, patch_size): + patch = img_p[ + hdx + ps : hdx + ps + patch_size, + wdx + ps : wdx + ps + patch_size, + ] + return patch.unsqueeze(dim=0) + +def _generate_batches(img_p, h, w, ps, patch_size, stride, batch_size=64): + hdc_wdx_generator = itertools.product(range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride)) + for batch_indexes in itertoolz.partition_all(batch_size, hdc_wdx_generator): + yield batch_indexes, torch.stack([_extract_patch(img_p, hdx, wdx, ps, patch_size) for hdx, wdx in batch_indexes], dim=0) + +def patch_label_2d(model, img, patch_size, stride, device): + img = torch.squeeze(img) + h, w = img.shape # height and width + + # Pad image with patch_size/2: + ps = int(np.floor(patch_size / 2)) # pad size + img_p = F.pad(img, pad=(ps, ps, ps, ps), mode="constant", value=0) + + num_classes = 6 + output_p = torch.zeros([1, num_classes, h + 2 * ps, w + 2 * ps]) + # generate output: + for batch_indexes, batch in _generate_batches(img_p, h, w, ps, patch_size, stride, batch_size=512): + model_output = model(batch.to(device)) + for (hdx, wdx), output in zip(batch_indexes, model_output.detach().cpu()): + output_p[ + :, + :, + hdx + ps : hdx + ps + patch_size, + wdx + ps : wdx + ps + patch_size, + ] += torch.squeeze(output) + + # crop the output_p in the middke + output = output_p[:, :, ps:-ps, ps:-ps] + return output + + +def test(*options, cfg=None): + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + log_dir, model_name = os.path.split(config.TEST.MODEL_PATH) + + # load model: + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + model.load_state_dict(torch.load(config.TEST.MODEL_PATH)) + model = model.to(device) # Send to GPU if available + + running_metrics_overall = runningScore(6) + + splits = ["test1","test2"] if "Both" in config.TEST.SPLIT else [config.TEST.SPLIT] + for sdx, split in enumerate(splits): + DATA_ROOT = path.join("/mnt", "alaudah") + labels = np.load(path.join(DATA_ROOT, "test_once", split + "_labels.npy")) + section_file = path.join(DATA_ROOT, "splits", "section_" + split + ".txt") + # define indices of the array + irange, xrange, depth = labels.shape + + if config.TEST.INLINE: + i_list = list(range(irange)) + i_list = ["i_" + str(inline) for inline in i_list] + else: + i_list = [] + + if config.TEST.CROSSLINE: + x_list = list(range(xrange)) + x_list = ["x_" + str(crossline) for crossline in x_list] + else: + x_list = [] + + list_test = i_list + x_list + + file_object = open(section_file, "w") + file_object.write("\n".join(list_test)) + file_object.close() + + test_set = section_loader( + is_transform=True, split=split, augmentations=None + ) + n_classes = test_set.n_classes + + test_loader = data.DataLoader( + test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False + ) + + running_metrics_split = runningScore(n_classes) + + # testing mode: + with torch.no_grad(): # operations inside don't track history + model.eval() + total_iteration = 0 + for i, (images, labels) in enumerate(test_loader): + logger.info(f"split: {split}, section: {i}") + total_iteration = total_iteration + 1 + image_original, labels_original = images, labels + + outputs = patch_label_2d( + model=model, + img=images, + patch_size=config.DATASET.PATCH_SIZE, + stride=config.TEST.TEST_STRIDE, + device=device + ) + + pred = outputs.detach().max(1)[1].numpy() + gt = labels.numpy() + running_metrics_split.update(gt, pred) + running_metrics_overall.update(gt, pred) + decoded = decode_segmap(pred) + + # get scores and save in writer() + score, class_iou = running_metrics_split.get_scores() + + # Add split results to TB: + logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') + for cdx, class_name in enumerate(class_names): + logger.info( + f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' + ) + + logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') + logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') + logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') + running_metrics_split.reset() + + # FINAL TEST RESULTS: + score, class_iou = running_metrics_overall.get_scores() + + logger.info("--------------- FINAL RESULTS -----------------") + logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') + for cdx, class_name in enumerate(class_names): + logger.info( + f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' + ) + logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') + logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') + logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') + + # Save confusion matrix: + confusion = score["confusion_matrix"] + np.savetxt(path.join(log_dir, "confusion.csv"), confusion, delimiter=" ") + + +if __name__ == "__main__": + fire.Fire(test) diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.sh b/interpretation/experiments/segmentation/dutchf3/local/test.sh new file mode 100755 index 00000000..4c09072f --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash +export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH +python test.py --cfg "configs/patch_deconvnet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/train.py b/interpretation/experiments/segmentation/dutchf3/local/train.py index 5e67afbd..1d2edd1f 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/train.py +++ b/interpretation/experiments/segmentation/dutchf3/local/train.py @@ -1,55 +1,47 @@ +# /* spell-checker: disable */ + import logging import logging.config -import fire import os from datetime import datetime -import itertools - +import fire import numpy as np import torch import torch.nn.functional as F +import torchvision.utils as vutils +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor from sklearn.model_selection import train_test_split from tensorboardX import SummaryWriter +from toolz import compose from torch.utils import data from tqdm import tqdm + +from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, + tensorboard_handlers) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, + create_summary_writer) +from cv_lib.segmentation.dutchf3.augmentations import (AddNoise, Compose, + RandomHorizontallyFlip, + RandomRotate) +from cv_lib.segmentation.dutchf3.data import (TrainPatchLoader, decode_segmap, + split_train_val, split_non_overlapping_train_val) +from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, + create_supervised_trainer) +from cv_lib.segmentation.dutchf3.metrics import MeanIoU, PixelwiseAccuracy +from cv_lib.segmentation import models +from cv_lib.segmentation.dutchf3.utils import (current_datetime, generate_path, + git_branch, git_hash, np_to_tb) from default import _C as config from default import update_config -from loss import cross_entropy -import torchvision.utils as vutils -from augmentations import ( - Compose, - RandomHorizontallyFlip, - RandomRotate, - AddNoise, -) -from loader.data_loader import TrainPatchLoader -from metrics import runningScore -from models import get_model -from utils import np_to_tb -from data import split_train_val -from ignite.contrib.handlers import CosineAnnealingScheduler -from cv_lib.segmentation.dutchf3.engine import ( - create_supervised_trainer, -) -from ignite.utils import convert_tensor -from ignite.engine import Events -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from models import get_model - -def prepare_train_batch(batch, device=None, non_blocking=False): - x, y = batch - return ( - convert_tensor(x, device=device, non_blocking=non_blocking), - convert_tensor(y, device=device, non_blocking=non_blocking), - ) +CLASS_WEIGHTS=[0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] -def prepare_val_batch(batch, device=None, non_blocking=False): +def prepare_batch(batch, device=None, non_blocking=False): x, y = batch return ( convert_tensor(x, device=device, non_blocking=non_blocking), @@ -57,25 +49,39 @@ def prepare_val_batch(batch, device=None, non_blocking=False): ) def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ fraction_validation=0.2 update_config(config, options=options, config_file=cfg) - print(config.LOG_CONFIG) logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + logger.debug(config.WORKERS) scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - torch.manual_seed(2019) + torch.manual_seed(config.SEED) if torch.cuda.is_available(): - torch.cuda.manual_seed_all(2019) - np.random.seed(seed=2019) + torch.cuda.manual_seed_all(config.SEED) + np.random.seed(seed=config.SEED) # Generate the train and validation sets for the model: split_train_val(config.DATASET.STRIDE, per_val=fraction_validation, loader_type="patch") # Setup Augmentations - if config.DATASET.AUGMENTATION: + if config.TRAIN.AUGMENTATION: data_aug = Compose( - [RandomRotate(10), RandomHorizontallyFlip(), AddNoise()] + # [RandomRotate(10), RandomHorizontallyFlip(), AddNoise()] + [RandomHorizontallyFlip(), AddNoise()] ) else: data_aug = None @@ -90,7 +96,7 @@ def run(*options, cfg=None): # Without Augmentation: val_set = TrainPatchLoader( - split="train", + split="val", is_transform=True, stride=config.DATASET.STRIDE, patch_size=config.DATASET.PATCH_SIZE, @@ -99,13 +105,13 @@ def run(*options, cfg=None): n_classes = train_set.n_classes train_loader = data.DataLoader( - train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=0, shuffle=True + train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True ) val_loader = data.DataLoader( - train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=0 + train_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS ) - - model = get_model("patch_deconvnet", False, n_classes) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) device = "cpu" if torch.cuda.is_available(): @@ -119,7 +125,8 @@ def run(*options, cfg=None): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - # summary_writer = create_summary_writer(log_dir=config.LOG_DIR) + tboard_log_dir = generate_path(config.LOG_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime()) + summary_writer = create_summary_writer(log_dir=tboard_log_dir) snapshot_duration = scheduler_step * len(train_set) scheduler = CosineAnnealingScheduler( optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration @@ -127,15 +134,15 @@ def run(*options, cfg=None): # weights are inversely proportional to the frequency of the classes in the training set class_weights = torch.tensor( - [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852], + CLASS_WEIGHTS, device=device, requires_grad=False, ) - criterion = cross_entropy(weight=class_weights) + criterion = torch.nn.CrossEntropyLoss(weight=class_weights, ignore_index=255, reduction='mean') trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_train_batch, device=device + model, optimizer, criterion, prepare_batch, device=device ) trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) @@ -145,8 +152,92 @@ def run(*options, cfg=None): logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + def _select_pred_and_mask(model_out_dict): + return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) + + evaluator = create_supervised_evaluator( + model, + prepare_batch, + metrics={ + "IoU": MeanIoU(n_classes, device, output_transform=_select_pred_and_mask), + "nll": Loss(criterion, output_transform=_select_pred_and_mask), + "pixa": PixelwiseAccuracy(n_classes, device, output_transform=_select_pred_and_mask) + }, + device=device, + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={"IoU": "IoU :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :"}, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={"IoU": "Validation/IoU", "nll": "Validation/Loss"}, + ), + ) + + def _select_max(pred_tensor): + return pred_tensor.max(1)[1] + + def _tensor_to_numpy(pred_tensor): + return pred_tensor.squeeze().cpu().numpy() + + transform_func = compose( + np_to_tb, + decode_segmap(n_classes=n_classes), + _tensor_to_numpy + ) + + transform_pred = compose( + transform_func, + _select_max + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Mask", "mask", transform_func=transform_func), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime()) + checkpoint_handler = SnapshotHandler( + output_dir, + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) - logger = logging.getLogger(__name__) logger.info("Starting training") trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/interpretation/experiments/segmentation/dutchf3/local/utils.py b/interpretation/experiments/segmentation/dutchf3/local/utils.py deleted file mode 100644 index 85ad7e96..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/utils.py +++ /dev/null @@ -1,20 +0,0 @@ -import numpy as np -import torch -import torchvision.utils as vutils - -def np_to_tb(array): - # if 2D : - if array.ndim == 2: - # HW => CHW - array = np.expand_dims(array,axis=0) - # CHW => NCHW - array = np.expand_dims(array,axis=0) - elif array.ndim == 3: - # HWC => CHW - array = array.transpose(2, 0, 1) - # CHW => NCHW - array = np.expand_dims(array,axis=0) - - array = torch.from_numpy(array) - array = vutils.make_grid(array, normalize=True, scale_each=True) - return array From 2559cd1e0ba4b771068d876b6948f884becd57df Mon Sep 17 00:00:00 2001 From: masalvar Date: Tue, 27 Aug 2019 10:24:41 +0000 Subject: [PATCH 023/207] Working version before refactor --- .../distributed/configs/seresnet_unet.yaml | 27 ++- .../dutchf3/distributed/default.py | 56 +++-- .../segmentation/dutchf3/distributed/train.py | 136 ++++++++--- .../local/configs/patch_deconvnet.yaml | 2 +- .../dutchf3/local/configs/seresnet_unet.yaml | 53 +++++ .../segmentation/dutchf3/local/default.py | 25 +- .../segmentation/dutchf3/local/test.py | 119 +++++++-- .../segmentation/dutchf3/local/test.sh | 2 +- .../segmentation/dutchf3/local/train.py | 225 ++++++++++++++---- 9 files changed, 505 insertions(+), 140 deletions(-) create mode 100644 interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml index 90bcf77b..c4748327 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml @@ -13,9 +13,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 ROOT: /mnt/alaudah - DEPTH: "patch" # Options are No, Patch and Section - STRIDE: 128 - PATCH_SIZE: 256 + MODEL: NAME: resnet_unet @@ -31,7 +29,26 @@ TRAIN: MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 - AUGMENTATION: False + AUGMENTATION: True + DEPTH: "patch" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 -TEST: +VALIDATION: BATCH_SIZE_PER_GPU: 32 + + output/mat/exp/74d07f41ff0dc72920c279cc93f2eb1f42ed8a0d/resnet_unet/Aug26_165055/ + +TEST: + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/188e66a26115760b7cbde7e9f6a626b6519a3bb7/patch_deconvnet/Aug25_102649/patch_deconvnet_running_model_25.pth" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/default.py b/interpretation/experiments/segmentation/dutchf3/distributed/default.py index 7146fd69..46fe9507 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/default.py +++ b/interpretation/experiments/segmentation/dutchf3/distributed/default.py @@ -30,28 +30,17 @@ _C.CUDNN.DETERMINISTIC = False _C.CUDNN.ENABLED = True + # DATASET related params _C.DATASET = CN() -_C.DATASET.ROOT = "/mnt/alaudah" -_C.DATASET.NUM_CLASSES = 1 -_C.DATASET.STRIDE = 50 -_C.DATASET.PATCH_SIZE = 99 -_C.DATASET.AUGMENTATION = True -_C.DATASET.DEPTH = 'no' # Options are None, Patch and Section -# None adds no depth information and the num of channels remains at 1 -# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 -# Section adds depth per section so contains depth information for the whole section, channels=3 +_C.DATASET.ROOT = "" +_C.DATASET.NUM_CLASSES = 6 +_C.DATASET.CLASS_WEIGHTS = [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] # common params for NETWORK _C.MODEL = CN() _C.MODEL.NAME = "patch_deconvnet" _C.MODEL.IN_CHANNELS = 1 -_C.MODEL.NUM_CLASSES = 6 - -_C.LOSS = CN() -_C.LOSS.WEIGHTS = (0.01, 1) -_C.LOSS.ADJUST_EPOCH = 50 -_C.LOSS.ADJUSTED_WEIGHTS = (0.3, 0.7) # training @@ -66,6 +55,22 @@ _C.TRAIN.SNAPSHOTS = 5 _C.TRAIN.SAVE_LOCATION = "/tmp/models" _C.TRAIN.AUGMENTATION = True +_C.TRAIN.STRIDE = 50 +_C.TRAIN.PATCH_SIZE = 99 +_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 +_C.TRAIN.STD = 1 # 0.20976548783479299 +_C.TRAIN.DEPTH = 'None' # Options are None, Patch and Section +# None adds no depth information and the num of channels remains at 1 +# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 +# Section adds depth per section so contains depth information for the whole section, channels=3 +_C.TRAIN.AUGMENTATIONS = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 +_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TRAIN.AUGMENTATIONS.PAD = CN() +_C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 +_C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 + # validation _C.VALIDATION = CN() @@ -98,3 +103,24 @@ def update_config(cfg, options=None, config_file=None): with open(sys.argv[1], "w") as f: print(_C, file=f) + + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/train.py b/interpretation/experiments/segmentation/dutchf3/distributed/train.py index c85c6867..caee149c 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/train.py +++ b/interpretation/experiments/segmentation/dutchf3/distributed/train.py @@ -40,17 +40,12 @@ create_image_writer, create_summary_writer, ) -from cv_lib.segmentation.dutchf3.augmentations import ( - AddNoise, - Compose, - RandomHorizontallyFlip, - RandomRotate, -) + from cv_lib.segmentation.dutchf3.data import ( get_train_loader, decode_segmap, split_train_val, - split_non_overlapping_train_val + split_non_overlapping_train_val, ) from cv_lib.segmentation.dutchf3.engine import ( create_supervised_evaluator, @@ -65,19 +60,23 @@ git_hash, np_to_tb, ) - get_data_ids, - get_distributed_data_loaders, - kfold_split, -) +from cv_lib.segmentation.dutchf3.engine import ( create_supervised_evaluator, create_supervised_trainer, ) from cv_lib.segmentation.dutchf3.metrics import apex from default import _C as config from default import update_config +from albumentations import ( + Compose, + HorizontalFlip, + GaussNoise, + Normalize, + Resize, + PadIfNeeded, +) -CLASS_WEIGHTS = [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - +import cv2 def prepare_batch(batch, device=None, non_blocking=False): x, y = batch @@ -126,44 +125,98 @@ def run(*options, cfg=None, local_rank=0): scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - if local_rank==0: #Only do this on local rank otherwise overwriting + if local_rank == 0: # Only do this on local rank otherwise overwriting # Generate the train and validation sets for the model: split_non_overlapping_train_val( - config.DATASET.STRIDE, per_val=fraction_validation, loader_type="patch" + config.TRAIN.STRIDE, + per_val=fraction_validation, + loader_type="patch", ) # Setup Augmentations if config.TRAIN.AUGMENTATION: - data_aug = Compose( - # [RandomRotate(10), RandomHorizontallyFlip(), AddNoise()] - [RandomHorizontallyFlip(), AddNoise()] + train_aug = Compose( + # TODO: Should we apply std scaling? + [ + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + HorizontalFlip(p=0.5), + # GaussNoise(var_limit=(0.02) ** 2), + # Normalize( + # mean=(config.TRAIN.MEAN,), + # std=(config.TRAIN.STD,), + # max_pixel_value=1, + # ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] + ) + val_aug = Compose( + # TODO: Should we apply std scaling? + [ + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + # Normalize( + # mean=(config.TRAIN.MEAN,), + # std=(config.TRAIN.STD,), + # max_pixel_value=1, + # ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] ) else: - data_aug = None + train_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ) + ] + ) + val_aug = train_aug TrainPatchLoader = get_train_loader(config) train_set = TrainPatchLoader( split="train", is_transform=True, - stride=config.DATASET.STRIDE, - patch_size=config.DATASET.PATCH_SIZE, - augmentations=data_aug, + stride=config.TRAIN.STRIDE, + patch_size=config.TRAIN.PATCH_SIZE, + augmentations=train_aug, ) logger.info(f"Training examples {len(train_set)}") - + # Without Augmentation: val_set = TrainPatchLoader( split="val", is_transform=True, - stride=config.DATASET.STRIDE, - patch_size=config.DATASET.PATCH_SIZE, + stride=config.TRAIN.STRIDE, + patch_size=config.TRAIN.PATCH_SIZE, + augmentations=val_aug ) logger.info(f"Validation examples {len(val_set)}") n_classes = train_set.n_classes train_sampler = torch.utils.data.distributed.DistributedSampler( - train_set, num_replicas=world_size, rank=local_rank + train_set, num_replicas=world_size, rank=local_rank ) train_loader = data.DataLoader( @@ -179,13 +232,12 @@ def run(*options, cfg=None, local_rank=0): val_loader = data.DataLoader( val_set, - batch_size=config.TEST.BATCH_SIZE_PER_GPU, + batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, sampler=val_sampler, ) model = getattr(models, config.MODEL.NAME).get_seg_model(config) - # model = get_model(config.MODEL.NAME, False, n_classes) device = "cpu" if torch.cuda.is_available(): @@ -201,7 +253,7 @@ def run(*options, cfg=None, local_rank=0): # weights are inversely proportional to the frequency of the classes in the training set class_weights = torch.tensor( - CLASS_WEIGHTS, device=device, requires_grad=False + config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False ) criterion = torch.nn.CrossEntropyLoss( @@ -248,7 +300,9 @@ def run(*options, cfg=None, local_rank=0): Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) - trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) if silence_other_ranks & local_rank != 0: logging.getLogger("ignite.engine.engine.Engine").setLevel( @@ -260,7 +314,7 @@ def _select_pred_and_mask(model_out_dict): model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze(), ) - + # Need to fix so that the mask isn't used evaluator = create_supervised_evaluator( model, prepare_batch, @@ -271,7 +325,7 @@ def _select_pred_and_mask(model_out_dict): "nll": salt_metrics.LossMetric( criterion, world_size, - config.TEST.BATCH_SIZE_PER_GPU, + config.VALIDATION.BATCH_SIZE_PER_GPU, output_transform=_select_pred_and_mask, ), "pixa": apex.PixelwiseAccuracy( @@ -287,7 +341,13 @@ def _select_pred_and_mask(model_out_dict): ) if local_rank == 0: # Run only on master process - tboard_log_dir = generate_path(config.LOG_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime()) + tboard_log_dir = generate_path( + config.LOG_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) logger.info(f"Logging Tensorboard to {tboard_log_dir}") summary_writer = create_summary_writer(log_dir=tboard_log_dir) trainer.add_event_handler( @@ -327,7 +387,7 @@ def _select_max(pred_tensor): def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() - + #TODO: Add removal of border on all images transform_func = compose( np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy ) @@ -360,7 +420,13 @@ def _tensor_to_numpy(pred_tensor): def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime()) + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) checkpoint_handler = SnapshotHandler( output_dir, config.MODEL.NAME, diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml index 80d1c65c..513aa149 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml @@ -18,7 +18,6 @@ MODEL: NAME: patch_deconvnet IN_CHANNELS: 1 - TRAIN: BATCH_SIZE_PER_GPU: 64 BEGIN_EPOCH: 0 @@ -28,6 +27,7 @@ TRAIN: MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 + AUGMENTATION: True VALIDATION: BATCH_SIZE_PER_GPU: 128 diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml new file mode 100644 index 00000000..9e4b08ee --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml @@ -0,0 +1,53 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/alaudah + + +MODEL: + NAME: resnet_unet + IN_CHANNELS: 3 + + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "patch" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/distributed/output/mat/exp/74d07f41ff0dc72920c279cc93f2eb1f42ed8a0d/resnet_unet/Aug26_165055/resnet_unet_running_model_31.pth" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/default.py b/interpretation/experiments/segmentation/dutchf3/local/default.py index 2764fb00..c0e1d436 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/default.py +++ b/interpretation/experiments/segmentation/dutchf3/local/default.py @@ -33,17 +33,15 @@ # DATASET related params _C.DATASET = CN() _C.DATASET.ROOT = "" -_C.DATASET.NUM_CLASSES = 1 -_C.DATASET.STRIDE = 50 -_C.DATASET.PATCH_SIZE = 99 -_C.DATASET.AUGMENTATION = True - +_C.DATASET.NUM_CLASSES = 6 +_C.DATASET.CLASS_WEIGHTS = [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] # common params for NETWORK _C.MODEL = CN() _C.MODEL.NAME = "patch_deconvnet" _C.MODEL.IN_CHANNELS = 1 + # training _C.TRAIN = CN() _C.TRAIN.MIN_LR = 0.001 @@ -56,6 +54,23 @@ _C.TRAIN.SNAPSHOTS = 5 _C.TRAIN.SAVE_LOCATION = "/tmp/models" _C.TRAIN.AUGMENTATION = True +_C.TRAIN.STRIDE = 50 +_C.TRAIN.PATCH_SIZE = 99 +_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 +_C.TRAIN.STD = 0.20977 # 0.20976548783479299 +_C.TRAIN.DEPTH = 'no' # Options are None, Patch and Section +# None adds no depth information and the num of channels remains at 1 +# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 +# Section adds depth per section so contains depth information for the whole section, channels=3 +_C.TRAIN.AUGMENTATIONS = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 +_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TRAIN.AUGMENTATIONS.PAD = CN() +_C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 +_C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 + + # validation _C.VALIDATION = CN() diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.py b/interpretation/experiments/segmentation/dutchf3/local/test.py index 235b7ae3..2fc7f913 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/test.py +++ b/interpretation/experiments/segmentation/dutchf3/local/test.py @@ -36,11 +36,13 @@ create_image_writer, create_summary_writer, ) -from cv_lib.segmentation.dutchf3.augmentations import ( - AddNoise, +from albumentations import ( Compose, - RandomHorizontallyFlip, - RandomRotate, + HorizontalFlip, + GaussNoise, + Normalize, + Resize, + PadIfNeeded, ) from cv_lib.segmentation.dutchf3.data import ( decode_segmap, @@ -48,6 +50,7 @@ split_non_overlapping_train_val, split_train_val, section_loader, + add_patch_depth_channels ) from cv_lib.segmentation.dutchf3.engine import ( create_supervised_evaluator, @@ -77,7 +80,7 @@ from toolz.itertoolz import take import itertools from toolz import itertoolz - +import cv2 class_names = [ "upper_ns", @@ -145,19 +148,57 @@ def reset(self): self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) -def _extract_patch(img_p, hdx, wdx, ps, patch_size): +@curry +def _extract_patch(img_p, hdx, wdx, ps, patch_size, aug=None): patch = img_p[ - hdx + ps : hdx + ps + patch_size, - wdx + ps : wdx + ps + patch_size, - ] - return patch.unsqueeze(dim=0) + hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size + ] + if aug is not None: + # TODO: Make depth optional from config + patch = add_patch_depth_channels(aug(image=patch.numpy())['image']) + return torch.from_numpy(patch).to(torch.float32) + else: + return patch.unsqueeze(dim=0) + + +def _generate_batches( + img_p, h, w, ps, patch_size, stride, config, batch_size=64 +): + hdc_wdx_generator = itertools.product( + range(0, h - patch_size + ps, stride), + range(0, w - patch_size + ps, stride), + ) + + test_aug = Compose( + [ + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] + ) + + for batch_indexes in itertoolz.partition_all( + batch_size, hdc_wdx_generator + ): + yield batch_indexes, torch.stack( + [ + _extract_patch(img_p, hdx, wdx, ps, patch_size, test_aug) + for hdx, wdx in batch_indexes + ], + dim=0, + ) -def _generate_batches(img_p, h, w, ps, patch_size, stride, batch_size=64): - hdc_wdx_generator = itertools.product(range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride)) - for batch_indexes in itertoolz.partition_all(batch_size, hdc_wdx_generator): - yield batch_indexes, torch.stack([_extract_patch(img_p, hdx, wdx, ps, patch_size) for hdx, wdx in batch_indexes], dim=0) -def patch_label_2d(model, img, patch_size, stride, device): +def patch_label_2d(model, img, patch_size, stride, config, device): img = torch.squeeze(img) h, w = img.shape # height and width @@ -168,14 +209,31 @@ def patch_label_2d(model, img, patch_size, stride, device): num_classes = 6 output_p = torch.zeros([1, num_classes, h + 2 * ps, w + 2 * ps]) # generate output: - for batch_indexes, batch in _generate_batches(img_p, h, w, ps, patch_size, stride, batch_size=512): + for batch_indexes, batch in _generate_batches( + img_p, h, w, ps, patch_size, stride, config, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU + ): model_output = model(batch.to(device)) - for (hdx, wdx), output in zip(batch_indexes, model_output.detach().cpu()): - output_p[ + for (hdx, wdx), output in zip( + batch_indexes, model_output.detach().cpu() + ): + output=output.unsqueeze(0) + if config.TRAIN.AUGMENTATIONS.PAD.HEIGHT > 0: + height_diff = (config.TRAIN.AUGMENTATIONS.PAD.HEIGHT - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT)//2 + output = output[ :, :, - hdx + ps : hdx + ps + patch_size, - wdx + ps : wdx + ps + patch_size, + height_diff : output.shape[2]-height_diff, + height_diff : output.shape[3]-height_diff, + ] + + if config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT > 0: + output=F.interpolate(output, size=(patch_size, patch_size), mode="bilinear") + + output_p[ + :, + :, + hdx + ps : hdx + ps + patch_size, + wdx + ps : wdx + ps + patch_size, ] += torch.squeeze(output) # crop the output_p in the middke @@ -194,16 +252,24 @@ def test(*options, cfg=None): # load model: model = getattr(models, config.MODEL.NAME).get_seg_model(config) - model.load_state_dict(torch.load(config.TEST.MODEL_PATH)) + model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False) model = model.to(device) # Send to GPU if available running_metrics_overall = runningScore(6) - splits = ["test1","test2"] if "Both" in config.TEST.SPLIT else [config.TEST.SPLIT] + splits = ( + ["test1", "test2"] + if "Both" in config.TEST.SPLIT + else [config.TEST.SPLIT] + ) for sdx, split in enumerate(splits): DATA_ROOT = path.join("/mnt", "alaudah") - labels = np.load(path.join(DATA_ROOT, "test_once", split + "_labels.npy")) - section_file = path.join(DATA_ROOT, "splits", "section_" + split + ".txt") + labels = np.load( + path.join(DATA_ROOT, "test_once", split + "_labels.npy") + ) + section_file = path.join( + DATA_ROOT, "splits", "section_" + split + ".txt" + ) # define indices of the array irange, xrange, depth = labels.shape @@ -248,9 +314,10 @@ def test(*options, cfg=None): outputs = patch_label_2d( model=model, img=images, - patch_size=config.DATASET.PATCH_SIZE, + patch_size=config.TRAIN.PATCH_SIZE, stride=config.TEST.TEST_STRIDE, - device=device + config=config, + device=device, ) pred = outputs.detach().max(1)[1].numpy() diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.sh b/interpretation/experiments/segmentation/dutchf3/local/test.sh index 4c09072f..066e9ad5 100755 --- a/interpretation/experiments/segmentation/dutchf3/local/test.sh +++ b/interpretation/experiments/segmentation/dutchf3/local/test.sh @@ -1,3 +1,3 @@ #!/bin/bash export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python test.py --cfg "configs/patch_deconvnet.yaml" \ No newline at end of file +python test.py --cfg "configs/seresnet_unet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/train.py b/interpretation/experiments/segmentation/dutchf3/local/train.py index 1d2edd1f..d9639674 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/train.py +++ b/interpretation/experiments/segmentation/dutchf3/local/train.py @@ -20,26 +20,49 @@ from torch.utils import data from tqdm import tqdm -from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, - tensorboard_handlers) +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, - create_summary_writer) -from cv_lib.segmentation.dutchf3.augmentations import (AddNoise, Compose, - RandomHorizontallyFlip, - RandomRotate) -from cv_lib.segmentation.dutchf3.data import (TrainPatchLoader, decode_segmap, - split_train_val, split_non_overlapping_train_val) -from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, - create_supervised_trainer) +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) + +from cv_lib.segmentation.dutchf3.data import ( + get_train_loader, + decode_segmap, + split_train_val, + split_non_overlapping_train_val, +) +from cv_lib.segmentation.dutchf3.engine import ( + create_supervised_evaluator, + create_supervised_trainer, +) from cv_lib.segmentation.dutchf3.metrics import MeanIoU, PixelwiseAccuracy from cv_lib.segmentation import models -from cv_lib.segmentation.dutchf3.utils import (current_datetime, generate_path, - git_branch, git_hash, np_to_tb) +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) from default import _C as config from default import update_config +from albumentations import ( + Compose, + HorizontalFlip, + GaussNoise, + Normalize, + Resize, + PadIfNeeded, +) +import cv2 +from toolz import itertoolz -CLASS_WEIGHTS=[0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] def prepare_batch(batch, device=None, non_blocking=False): x, y = batch @@ -48,6 +71,7 @@ def prepare_batch(batch, device=None, non_blocking=False): convert_tensor(y, device=device, non_blocking=non_blocking), ) + def run(*options, cfg=None): """Run training and validation of model @@ -61,7 +85,7 @@ def run(*options, cfg=None): To see what options are available consult default.py cfg (str, optional): Location of config file to load. Defaults to None. """ - fraction_validation=0.2 + fraction_validation = 0.2 update_config(config, options=options, config_file=cfg) logging.config.fileConfig(config.LOG_CONFIG) logger = logging.getLogger(__name__) @@ -75,40 +99,100 @@ def run(*options, cfg=None): np.random.seed(seed=config.SEED) # Generate the train and validation sets for the model: - split_train_val(config.DATASET.STRIDE, per_val=fraction_validation, loader_type="patch") + split_non_overlapping_train_val( + config.TRAIN.STRIDE, per_val=fraction_validation, loader_type="patch" + ) # Setup Augmentations if config.TRAIN.AUGMENTATION: - data_aug = Compose( - # [RandomRotate(10), RandomHorizontallyFlip(), AddNoise()] - [RandomHorizontallyFlip(), AddNoise()] + train_aug = Compose( + # TODO: Should we apply std scaling? + [ + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + HorizontalFlip(p=0.5), + # GaussNoise(var_limit=(0.02) ** 2), + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] + ) + val_aug = Compose( + # TODO: Should we apply std scaling? + [ + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] ) else: - data_aug = None + data_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ), + ] + ) + + TrainPatchLoader = get_train_loader(config) train_set = TrainPatchLoader( split="train", is_transform=True, - stride=config.DATASET.STRIDE, - patch_size=config.DATASET.PATCH_SIZE, - augmentations=data_aug, + stride=config.TRAIN.STRIDE, + patch_size=config.TRAIN.PATCH_SIZE, + augmentations=train_aug, ) # Without Augmentation: val_set = TrainPatchLoader( split="val", is_transform=True, - stride=config.DATASET.STRIDE, - patch_size=config.DATASET.PATCH_SIZE, + stride=config.TRAIN.STRIDE, + patch_size=config.TRAIN.PATCH_SIZE, + augmentations=val_aug ) n_classes = train_set.n_classes train_loader = data.DataLoader( - train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True + train_set, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + shuffle=True, ) val_loader = data.DataLoader( - train_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS + train_set, + batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, ) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -125,21 +209,31 @@ def run(*options, cfg=None): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - tboard_log_dir = generate_path(config.LOG_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime()) + tboard_log_dir = generate_path( + config.LOG_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) summary_writer = create_summary_writer(log_dir=tboard_log_dir) snapshot_duration = scheduler_step * len(train_set) scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration + optimizer, + "lr", + config.TRAIN.MAX_LR, + config.TRAIN.MIN_LR, + snapshot_duration, ) # weights are inversely proportional to the frequency of the classes in the training set class_weights = torch.tensor( - CLASS_WEIGHTS, - device=device, - requires_grad=False, + config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False + ) + + criterion = torch.nn.CrossEntropyLoss( + weight=class_weights, ignore_index=255, reduction="mean" ) - - criterion = torch.nn.CrossEntropyLoss(weight=class_weights, ignore_index=255, reduction='mean') trainer = create_supervised_trainer( model, optimizer, criterion, prepare_batch, device=device @@ -151,7 +245,9 @@ def run(*options, cfg=None): Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) - trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) trainer.add_event_handler( Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), @@ -162,27 +258,40 @@ def run(*options, cfg=None): ) def _select_pred_and_mask(model_out_dict): - return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) + return ( + model_out_dict["y_pred"].squeeze(), + model_out_dict["mask"].squeeze(), + ) evaluator = create_supervised_evaluator( model, prepare_batch, metrics={ - "IoU": MeanIoU(n_classes, device, output_transform=_select_pred_and_mask), + "IoU": MeanIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "pixa": PixelwiseAccuracy(n_classes, device, output_transform=_select_pred_and_mask) + "pixa": PixelwiseAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), }, device=device, ) # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + trainer.add_event_handler( + Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) + ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, logging_handlers.log_metrics( "Validation results", - metrics_dict={"IoU": "IoU :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :"}, + metrics_dict={ + "IoU": "IoU :", + "nll": "Avg loss :", + "pixa": "Pixelwise Accuracy :", + }, ), ) evaluator.add_event_handler( @@ -202,15 +311,10 @@ def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() transform_func = compose( - np_to_tb, - decode_segmap(n_classes=n_classes), - _tensor_to_numpy + np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy ) - transform_pred = compose( - transform_func, - _select_max - ) + transform_pred = compose(transform_func, _select_max) evaluator.add_event_handler( Events.EPOCH_COMPLETED, @@ -218,17 +322,33 @@ def _tensor_to_numpy(pred_tensor): ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Mask", "mask", transform_func=transform_func), + create_image_writer( + summary_writer, + "Validation/Mask", + "mask", + transform_func=transform_func, + ), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred), + create_image_writer( + summary_writer, + "Validation/Pred", + "y_pred", + transform_func=transform_pred, + ), ) def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime()) + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) checkpoint_handler = SnapshotHandler( output_dir, config.MODEL.NAME, @@ -237,9 +357,10 @@ def snapshot_function(): evaluator.add_event_handler( Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} ) - + logger.info("Starting training") - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + trainer.run(itertoolz.take(2, train_loader), max_epochs=config.TRAIN.END_EPOCH) + if __name__ == "__main__": fire.Fire(run) From ee1c7058e0d030033f978a326c896e188fea69ea Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Tue, 27 Aug 2019 15:41:51 +0000 Subject: [PATCH 024/207] adding cgmanifest to staging --- cgmanifest.json | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 cgmanifest.json diff --git a/cgmanifest.json b/cgmanifest.json new file mode 100644 index 00000000..d647c543 --- /dev/null +++ b/cgmanifest.json @@ -0,0 +1,64 @@ +{"Registrations":[ + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/olivesgatech/facies_classification_benchmark", + "commitHash": "12102683a1ae78f8fbc953823c35a43b151194b3" + } + }, + "license": "MIT" + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/waldeland/CNN-for-ASI", + "commitHash": "6f985cccecf9a811565d0b7cd919412569a22b7b" + } + }, + "license": "MIT" + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/opesci/devito", + "commitHash": "f6129286d9c0b3a8bfe07e724ac5b00dc762efee" + } + }, + "license": "MIT" + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/pytorch/ignite", + "commitHash": "38a4f37de759e33bc08441bde99bcb50f3d81f55" + } + }, + "license": "BSD-3-Clause" + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/HRNet/HRNet-Semantic-Segmentation", + "commitHash": "06142dc1c7026e256a7561c3e875b06622b5670f" + } + }, + "license": "MIT" + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/dask/dask", + "commitHash": "54019e9c05134585c9c40e4195206aa78e2ea61a" + } + }, + "license": "IPL-1.0" + } + ], + "Version": 1 +} \ No newline at end of file From 1e3f9f6147f47ff3d86d33552138a58530dc02fc Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Tue, 27 Aug 2019 15:49:26 +0000 Subject: [PATCH 025/207] adding a yml file with CG build task --- azure-pipelines.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..3871283a --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,19 @@ +# Starter pipeline + +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: +- master + +pool: + vmImage: 'ubuntu-latest' + +steps: +- task: ComponentGovernanceComponentDetection@0 + inputs: + scanType: 'Register' + verbosity: 'Verbose' + alertWarningLevel: 'High' + From 4a92e6afacc74019b5ab09b8207fe5f8ebcf5d5c Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Wed, 28 Aug 2019 10:13:07 +0000 Subject: [PATCH 026/207] added prelim NOTICE file --- NOTICE.txt | 2031 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2031 insertions(+) create mode 100755 NOTICE.txt diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100755 index 00000000..dbaf3a7b --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,2031 @@ +NOTICES AND INFORMATION +Do Not Translate or Localize + +This software incorporates material from third parties. +Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com, +or you may send a check or money order for US $5.00, including the product name, +the open source component name, and version number, to: + +Source Code Compliance Team +Microsoft Corporation +One Microsoft Way +Redmond, WA 98052 +USA + +Notwithstanding any other terms, you may reverse engineer this software to the extent +required to debug changes to any libraries licensed under the GNU Lesser General Public License. + + +------------------------------------------------------------------- + +h5py 2.9.0 - BSD-2-Clause +PyTables Copyright Statement +Copyright (c) 2009 Darren Dale +Copyright 2006-2007 by The HDF Group +Copyright (c) 2006-2008 Alexander Chemeris +Copyright (c) 2002, 2003, 2004 Francesc Altet +Copyright 2001-2013 Python Software Foundation +Copyright (c) 2005, 2006, 2007 Carabos Coop. V. +copyright u'2014, Andrew Collette and contributors +Copyright 2008-2013 Andrew Collette and contributors +Copyright 2008-2018 Andrew Collette and contributors +Copyright (c) 2008 Andrew Collette http://h5py.alfven.org +Copyright (c) 2009 Andrew Collette http://h5py.alfven.org +Copyright (c) 2008-2009 Andrew Collette http://h5py.alfven.org +Copyright (c) 2000-2007 Marc Alexander Lehmann +Copyright (c) 2000-2008 Marc Alexander Lehmann +Copyright (c) 2008 Andrew Collette and contributors http://h5py.alfven.org +Copyright 1998-2006 by the Board of Trustees of the University of Illinois. +Copyright (c) 2008-2013 Andrew Collette and contributors http://www.h5py.org + +Copyright (c) 2001, 2002 Enthought, Inc. +All rights reserved. + +Copyright (c) 2003-2019 SciPy Developers. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of Enthought nor the names of the SciPy Developers + may be used to endorse or promote products derived from this software + without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + + +SciPy bundles a number of libraries that are compatibly licensed. We list +these here. + +Name: Numpydoc +Files: doc/sphinxext/numpydoc/* +License: 2-clause BSD + For details, see doc/sphinxext/LICENSE.txt + +Name: scipy-sphinx-theme +Files: doc/scipy-sphinx-theme/* +License: 3-clause BSD, PSF and Apache 2.0 + For details, see doc/sphinxext/LICENSE.txt + +Name: Six +Files: scipy/_lib/six.py +License: MIT + For details, see the header inside scipy/_lib/six.py + +Name: Decorator +Files: scipy/_lib/decorator.py +License: 2-clause BSD + For details, see the header inside scipy/_lib/decorator.py + +Name: ID +Files: scipy/linalg/src/id_dist/* +License: 3-clause BSD + For details, see scipy/linalg/src/id_dist/doc/doc.tex + +Name: L-BFGS-B +Files: scipy/optimize/lbfgsb/* +License: BSD license + For details, see scipy/optimize/lbfgsb/README + +Name: SuperLU +Files: scipy/sparse/linalg/dsolve/SuperLU/* +License: 3-clause BSD + For details, see scipy/sparse/linalg/dsolve/SuperLU/License.txt + +Name: ARPACK +Files: scipy/sparse/linalg/eigen/arpack/ARPACK/* +License: 3-clause BSD + For details, see scipy/sparse/linalg/eigen/arpack/ARPACK/COPYING + +Name: Qhull +Files: scipy/spatial/qhull/* +License: Qhull license (BSD-like) + For details, see scipy/spatial/qhull/COPYING.txt + +Name: Cephes +Files: scipy/special/cephes/* +License: 3-clause BSD + Distributed under 3-clause BSD license with permission from the author, + see https://lists.debian.org/debian-legal/2004/12/msg00295.html + + Cephes Math Library Release 2.8: June, 2000 + Copyright 1984, 1995, 2000 by Stephen L. Moshier + + This software is derived from the Cephes Math Library and is + incorporated herein by permission of the author. + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Name: Faddeeva +Files: scipy/special/Faddeeva.* +License: MIT + Copyright (c) 2012 Massachusetts Institute of Technology + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Name: qd +Files: scipy/special/cephes/dd_*.[ch] +License: modified BSD license ("BSD-LBNL-License.doc") + This work was supported by the Director, Office of Science, Division + of Mathematical, Information, and Computational Sciences of the + U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and + DE-AC02-05CH11231. + + Copyright (c) 2003-2009, The Regents of the University of California, + through Lawrence Berkeley National Laboratory (subject to receipt of + any required approvals from U.S. Dept. of Energy) All rights reserved. + + 1. Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + (1) Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) Neither the name of the University of California, Lawrence + Berkeley National Laboratory, U.S. Dept. of Energy nor the names + of its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + 2. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + 3. You are under no obligation whatsoever to provide any bug fixes, + patches, or upgrades to the features, functionality or performance of + the source code ("Enhancements") to anyone; however, if you choose to + make your Enhancements available either publicly, or directly to + Lawrence Berkeley National Laboratory, without imposing a separate + written license agreement for such Enhancements, then you hereby grant + the following license: a non-exclusive, royalty-free perpetual license + to install, use, modify, prepare derivative works, incorporate into + other computer software, distribute, and sublicense such enhancements + or derivative works thereof, in binary and source code form. + + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +Copyright (C) 2008 Stefan van der Walt , Pauli Virtanen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Copyright (C) 2003-2005 Peter J. Verveer + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org) + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + Qhull, Copyright (c) 1993-2015 + + C.B. Barber + Arlington, MA + + and + + The National Science and Technology Research Center for + Computation and Visualization of Geometric Structures + (The Geometry Center) + University of Minnesota + + email: qhull@qhull.org + +This software includes Qhull from C.B. Barber and The Geometry Center. +Qhull is copyrighted as noted above. Qhull is free software and may +be obtained via http from www.qhull.org. It may be freely copied, modified, +and redistributed under the following conditions: + +1. All copyright notices must remain intact in all files. + +2. A copy of this text file must be distributed along with any copies + of Qhull that you redistribute; this includes copies that you have + modified, or copies of programs or other software products that + include Qhull. + +3. If you modify Qhull, you must include a notice giving the + name of the person performing the modification, the date of + modification, and the reason for such modification. + +4. When distributing modified versions of Qhull, or other software + products that include Qhull, you must provide notice that the original + source code may be obtained as noted above. + +5. There is no warranty or other guarantee of fitness for Qhull, it is + provided solely "as is". Bug reports or fixes may be sent to + qhull_bug@qhull.org; the authors may or may not act on them as + they desire. + + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +scipy 1.3.0 - BSD-2-Clause +(c) Col +(c) KvK +(c) 2011 +(c) 2012 +(c) 2014 +(c) KKOK KkK +(c) B Whether +(c) , (R,1) col +copyright u'2017 +(c) Compute Hessian +Copyright 2014 PSF. +(c) .GT. ZERO .AND. ABS +Copyright Gautam Sewani +(c) KKKKKwKnKK K KQKKKKe +copyrighted by Alan Genz +Copyright 2006 Johan Rade +Copyright Paul A. Bristow +Csp self.spmatrix (c) Dsp +(c) KKKKY KKKKKKKKKKKKKKKKe +(c) KZK8K K9K8K KCKDKCKDK7K +(c) KaKKKQK K KzKkKKKiKKqKK +Copyright 2006 John Maddock +Copyright 2012 Twitter, Inc +Copyright 2000 by Alan Genz. +Copyright 2008 Gautam Sewani +Copyright 2013 Andrea Gavana +Copyright Gautam Sewani 2008 +Copyright John Maddock 2005. +Copyright John Maddock 2006. +Copyright John Maddock 2007. +Copyright John Maddock 2008. +Copyright John Maddock 2009. +Copyright John Maddock 2010. +Copyright John Maddock 2011. +Copyright John Maddock 2012. +Copyright John Maddock 2013. +Copyright Paul Bristow 2007. +Copyright Yosef Meller, 2009 +Copyright (c) 2006 Johan Rade +Copyright (c) 2014 Eric Moore +Copyright (c) Piers Lawrence. +Copyright 2002 Pearu Peterson +Copyright 2014, Eric W. Moore +Copyright Xiaogang Zhang 2006 +copyright Cephes Math Library +(c) KKPSKKtK KWKzKeKzKvK KyKjK +Copyright (c) 2008 Damian Eads +Copyright (c) 2012 Google Inc. +Copyright 1999 Travis Oliphant +Copyright 2002 Gary Strangman. +Copyright 2005 Travis Oliphant +Copyright 2010 Paul A. Bristow +Copyright 2011 Paul A. Bristow +Copyright 2012 Paul A. Bristow +Copyright John Maddock 2006-7. +Copyright John Maddock 2007-8. +Qhull, Copyright (c) 1993-2015 +copyrighted by Enthought, Inc. +(c) KSSKaKoKUKoKeKIKKKKK KKKKKK +Copyright (c) 2006 John Maddock +Copyright (c) 2007 John Maddock +Copyright (c) 2011 John Maddock +Copyright (c) 2016 Adrian Veres +Copyright (c) Tyler Reddy, 2016 +Copyright Paul A. Bristow 2006. +Copyright Paul A. Bristow 2007. +Copyright Paul A. Bristow 2010. +Copyright Paul A. Bristow 2012. +Copyright Paul A. Bristow 2013. +(c) Copyright Hubert Holin 2003. +(c) Copyright John Maddock 2005. +(c) Copyright John Maddock 2006. +(c) Copyright John Maddock 2007. +(c) Copyright John Maddock 2008. +(c) Copyright John Maddock 2010. +Copyright (c) 2007, Damian Eads. +Copyright (c) 2013 Kenneth L. Ho +Copyright 1991 Dieter Kraft, FHM +Copyright Anne M. Archibald 2008 +Copyright Benjamin Sobotta 2012. +(c) Copyright Bruno Lalande 2008. +Copyright (c) 2006 Xiaogang Zhang +Copyright (c) 2009 Pauli Virtanen +Copyright (c) 2009, Motorola, Inc +Copyright (c) 2013 Pauli Virtanen +Copyright 2011 Paul A. Bristow To +Copyright Paul A. Bristow 2006-7. +(c) Copyright John Maddock 2006-7. +(c) Copyright Paul A. Bristow 2011 +Copyright (c) 2002 Travis Oliphant +Copyright (c) 2011 Paul A. Bristow +Copyright (c) 2012 Paul A. Bristow +Copyright John Maddock 2006, 2007. +Copyright John Maddock 2006, 2011. +Copyright John Maddock 2006, 2012. +Copyright John Maddock 2008, 2012. +Copyright Paul Bristow 2006, 2007. +Copyright Paul Bristow 2007, 2011. +Copyright (c) 1988 by Theo Jurriens +Copyright (c) Benjamin Sobotta 2012 +Copyright (c) Pauli Virtanen, 2010. +Copyright 2002 H Lohninger, TU Wein +Copyright 2015 Jon Lund Steffensen. +Copyright Thijs van den Berg, 2008. +Copyright (c) 1993-2015 C.B. Barber. +Copyright (c) 2007 Cybozu Labs, Inc. +Copyright Paul A. Bristow 2009, 2011 +(c) Copyright Hubert Holin 2003-2005. +(c) KyKOKQKOKEK9K8K KFKGKGKJKHKKAKKAK +Copyright (c) 2007 - Sebastien Fabbro +Copyright (c) 2011 Paul A. Bristow To +Copyright (c) 2014 Mathjax Consortium +Copyright (c) 2015-2017 Martin Hensel +Copyright (c) 2016 2017 Felix Lenders +Copyright (c) Damian Eads, 2007-2008. +Copyright Christopher Kormanyos 2013. +Copyright Paul A. Bristow 2007, 2009. +Copyright Paul A. Bristow 2007, 2010. +Copyright Paul A. Bristow 2007, 2012. +Copyright Paul A. Bristow 2008, 2009. +Copyright (c) 2007, 2008, Damian Eads. +Copyright (c) 2012, Jaydeep P. Bardhan +Copyright (c) 2012, Matthew G. Knepley +Copyright (c) 2014, Janani Padmanabhan +Copyright 2004-2005 by Enthought, Inc. +Copyright 2007-2011 by the Sphinx team +Copyright 2007-2018 by the Sphinx team +copyright 2008- s, The SciPy community +(c) Copyright Daryle Walker 2001, 2006. +Copyright (c) 2010 Thomas P. Robitaille +Copyright (c) 1989-2004 Johannes Braams. +Copyright (c) 1994 by Xerox Corporation. +Copyright (c) 1996-2008 Rice University. +Copyright (c) 2001, 2002 Enthought, Inc. +Copyright (c) 2003-2005 Peter J. Verveer +Copyright 2002-2016 The SciPy Developers +Copyright (c) 2003-2019 SciPy Developers. +Copyright (c) 2010-2012 Benjamin Peterson +Copyright (c) 1990-2004 by Johannes Braams +Copyright (c) 2005-2015, Michele Simionato +Copyright (c) 2006-2008 Alexander Chemeris +Copyright 1984, 1995 by Stephen L. Moshier +Copyright 1984, 1996 by Stephen L. Moshier +Copyright 1985 by Stephen L. Moshier Direct +Copyright (c) 1993-2015 The Geometry Center. +Copyright (c) 2001-2011 - Scilab Enterprises +Copyright (c) 2010 - Jordi Gutierrez Hermoso +Copyright (c) 2009-2017 The MathJax Consortium +Copyright (c) 2010-2017 The MathJax Consortium +Copyright (c) 2011-2015 The MathJax Consortium +Copyright (c) 2011-2017 The MathJax Consortium +Copyright (c) 2013-2017 The MathJax Consortium +Copyright (c) 2014-2017 The MathJax Consortium +Copyright (c) 2015-2017 The MathJax Consortium +Copyright (c) 2016-2017 The MathJax Consortium +Copyright J.S. Roy (js@jeannot.org), 2002-2005 +Copyright (c) 2009, Pauli Virtanen +Copyright (c) 2015, Pauli Virtanen +Copyright 1984, 1987, 1995 by Stephen L. Moshier +Copyright 1984, 1987, 2000 by Stephen L. Moshier +Copyright 1984, 1995, 2000 by Stephen L. Moshier +Copyright 1985, 1987, 2000 by Stephen L. Moshier +Copyright 1984, 1987 by Stephen L. Moshier Direct +Copyright 1984, 1991 by Stephen L. Moshier Direct +Copyright 1985, 1987 by Stephen L. Moshier Direct +Copyright Paul A. Bristow 2007, 2009, 2010, 2012. +Copyright (c) 2010 David Fong and Michael Saunders +copyright u'2013, Surya Kasturi and Pauli Virtanen +Copyright (c) 1992-2015 The University of Tennessee +Copyright (c) 2006, Systems Optimization Laboratory +Copyright (c) 2007, John Travers +Copyright (c) 1998-2003 by the University of Florida. +Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier +Copyright 1984, 1987, 1989, 1995 by Stephen L. Moshier +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier +Copyright 1984, 1987, 1988 by Stephen L. Moshier Direct +Copyright 1984, 1987, 1989 by Stephen L. Moshier Direct +Copyright 1984, 1987, 1993 by Stephen L. Moshier Direct +Copyright 1985, 1987, 1989 by Stephen L. Moshier Direct +Copyright (c) 2012 Massachusetts Institute of Technology +Copyright (c) 2006-2007, Robert Hetland +Copyright (c) 2006-2015 The University of Colorado Denver. +Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org) +Copyright (c) 2004-2005, Jean-Sebastien Roy (js@jeannot.org) +Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +Copyright 1984, 1987, 1988, 1992 by Stephen L. Moshier Direct +Copyright 1984, 1987, 1989, 1992 by Stephen L. Moshier Direct +Copyright (c) 2000-2015 The University of California Berkeley. +Copyright (c) Tyler Reddy, Richard Gowers, and Max Linke, 2016 +Copyright (c) 2004 David M. Cooke +Copyright Daryle Walker, Hubert Holin, John Maddock 2006 - 2007 +copyrighted 2004 by David M. Cooke +Copyright (c) 2008 Brian M. Clapper , Gael Varoquaux +Copyright (c) 2011 Kevin Dunn, Surya K, Pauli Virtanen, the Sphinx team +Copyright 2014 by P.-G. Martinsson, V. Rokhlin, Y. Shkolnisky, and M. Tygert. +KQKJKCKGKEKBKMKrK (c) KUKoKoKiKeKeKiKiKiKiKiKiKiKiKiKiKiKiKiKiKoKeKaKiKiKiKiKdKIKAKThKOK +Copyright (c) 2008 Stefan van der Walt , Pauli Virtanen +Copyright (c) 2018 Sylvain Gubian , Yang Xiang +(c) KKdegKoKKKKY KY KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKe +Copyright (c) Tyler Reddy, Ross Hemsley, Edd Edmondson, Nikolai Nowaczyk, Joe Pitt-Francis, 2015. +Copyright (c) 2003, The Regents of the University of California, through Lawrence Berkeley National Laboratory +Copyright (c) 2003-2009, The Regents of the University of California, through Lawrence Berkeley National Laboratory +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Python Software Foundation + +Copyright (c) 2001, 2002 Enthought, Inc. +All rights reserved. + +Copyright (c) 2003-2019 SciPy Developers. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of Enthought nor the names of the SciPy Developers + may be used to endorse or promote products derived from this software + without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + + +SciPy bundles a number of libraries that are compatibly licensed. We list +these here. + +Name: Numpydoc +Files: doc/sphinxext/numpydoc/* +License: 2-clause BSD + For details, see doc/sphinxext/LICENSE.txt + +Name: scipy-sphinx-theme +Files: doc/scipy-sphinx-theme/* +License: 3-clause BSD, PSF and Apache 2.0 + For details, see doc/sphinxext/LICENSE.txt + +Name: Six +Files: scipy/_lib/six.py +License: MIT + For details, see the header inside scipy/_lib/six.py + +Name: Decorator +Files: scipy/_lib/decorator.py +License: 2-clause BSD + For details, see the header inside scipy/_lib/decorator.py + +Name: ID +Files: scipy/linalg/src/id_dist/* +License: 3-clause BSD + For details, see scipy/linalg/src/id_dist/doc/doc.tex + +Name: L-BFGS-B +Files: scipy/optimize/lbfgsb/* +License: BSD license + For details, see scipy/optimize/lbfgsb/README + +Name: SuperLU +Files: scipy/sparse/linalg/dsolve/SuperLU/* +License: 3-clause BSD + For details, see scipy/sparse/linalg/dsolve/SuperLU/License.txt + +Name: ARPACK +Files: scipy/sparse/linalg/eigen/arpack/ARPACK/* +License: 3-clause BSD + For details, see scipy/sparse/linalg/eigen/arpack/ARPACK/COPYING + +Name: Qhull +Files: scipy/spatial/qhull/* +License: Qhull license (BSD-like) + For details, see scipy/spatial/qhull/COPYING.txt + +Name: Cephes +Files: scipy/special/cephes/* +License: 3-clause BSD + Distributed under 3-clause BSD license with permission from the author, + see https://lists.debian.org/debian-legal/2004/12/msg00295.html + + Cephes Math Library Release 2.8: June, 2000 + Copyright 1984, 1995, 2000 by Stephen L. Moshier + + This software is derived from the Cephes Math Library and is + incorporated herein by permission of the author. + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Name: Faddeeva +Files: scipy/special/Faddeeva.* +License: MIT + Copyright (c) 2012 Massachusetts Institute of Technology + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Name: qd +Files: scipy/special/cephes/dd_*.[ch] +License: modified BSD license ("BSD-LBNL-License.doc") + This work was supported by the Director, Office of Science, Division + of Mathematical, Information, and Computational Sciences of the + U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and + DE-AC02-05CH11231. + + Copyright (c) 2003-2009, The Regents of the University of California, + through Lawrence Berkeley National Laboratory (subject to receipt of + any required approvals from U.S. Dept. of Energy) All rights reserved. + + 1. Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + (1) Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) Neither the name of the University of California, Lawrence + Berkeley National Laboratory, U.S. Dept. of Energy nor the names + of its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + 2. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + 3. You are under no obligation whatsoever to provide any bug fixes, + patches, or upgrades to the features, functionality or performance of + the source code ("Enhancements") to anyone; however, if you choose to + make your Enhancements available either publicly, or directly to + Lawrence Berkeley National Laboratory, without imposing a separate + written license agreement for such Enhancements, then you hereby grant + the following license: a non-exclusive, royalty-free perpetual license + to install, use, modify, prepare derivative works, incorporate into + other computer software, distribute, and sublicense such enhancements + or derivative works thereof, in binary and source code form. + + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +Copyright (C) 2008 Stefan van der Walt , Pauli Virtanen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Copyright (C) 2003-2005 Peter J. Verveer + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org) + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + Qhull, Copyright (c) 1993-2015 + + C.B. Barber + Arlington, MA + + and + + The National Science and Technology Research Center for + Computation and Visualization of Geometric Structures + (The Geometry Center) + University of Minnesota + + email: qhull@qhull.org + +This software includes Qhull from C.B. Barber and The Geometry Center. +Qhull is copyrighted as noted above. Qhull is free software and may +be obtained via http from www.qhull.org. It may be freely copied, modified, +and redistributed under the following conditions: + +1. All copyright notices must remain intact in all files. + +2. A copy of this text file must be distributed along with any copies + of Qhull that you redistribute; this includes copies that you have + modified, or copies of programs or other software products that + include Qhull. + +3. If you modify Qhull, you must include a notice giving the + name of the person performing the modification, the date of + modification, and the reason for such modification. + +4. When distributing modified versions of Qhull, or other software + products that include Qhull, you must provide notice that the original + source code may be obtained as noted above. + +5. There is no warranty or other guarantee of fitness for Qhull, it is + provided solely "as is". Bug reports or fixes may be sent to + qhull_bug@qhull.org; the authors may or may not act on them as + they desire. + + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +sympy 1.4 - BSD-2-Clause +(c) A. B +(c), cos +(c), cot +(c) + cos +(c) (-1) cos +(c) Fix Qasm +(c) Matrix I +Copyright 2016 +(c) + cos(a) cos +(c) , sin(a) cos +(c), -sin(a) cos +(c) tan(b) + a cos +(c), sin(a) sin(b) cos +(c) + sin(b) cos(a) cos +(c) tan(b) a + sin(b) cos +(c) G PermutationGroup Permutation +Copyright (c) 2014 Matthew Rocklin +(c) cos(b), -sin(b) sin(a) sin(b) cos +copyright 2019 SymPy Development Team +Copyright 2007-2013 by the Sphinx team +copyright 2015, SymPy Development Team +Copyright (c) 2006-2014 SymPy developers +(c) + (sin(a) cos(b) + sin(b) cos(a)) cos +Copyright (c) 2001, 2002 Vasil Yaroshevich +Copyright 2014 by the SymPy Development Team +Copyright (c) 2006-2019 SymPy Development Team +Copyright (c) 2008 The IPython Development Team +Copyright (c) 2008 Jens Rasch +CoprimeQ, Distribute, ProductLog, Floor, PolyGamma +Copyright (c) 2006-2017 SymPy Development Team, 2013-2017 Sergey B Kirpichev +(c) Copyright 2000-2003 Symbolic Computation Laboratory, University of Western Ontario, London, Canada N6A + +Copyright (c) 2001, 2002 Enthought, Inc. +All rights reserved. + +Copyright (c) 2003-2019 SciPy Developers. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of Enthought nor the names of the SciPy Developers + may be used to endorse or promote products derived from this software + without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + + +SciPy bundles a number of libraries that are compatibly licensed. We list +these here. + +Name: Numpydoc +Files: doc/sphinxext/numpydoc/* +License: 2-clause BSD + For details, see doc/sphinxext/LICENSE.txt + +Name: scipy-sphinx-theme +Files: doc/scipy-sphinx-theme/* +License: 3-clause BSD, PSF and Apache 2.0 + For details, see doc/sphinxext/LICENSE.txt + +Name: Six +Files: scipy/_lib/six.py +License: MIT + For details, see the header inside scipy/_lib/six.py + +Name: Decorator +Files: scipy/_lib/decorator.py +License: 2-clause BSD + For details, see the header inside scipy/_lib/decorator.py + +Name: ID +Files: scipy/linalg/src/id_dist/* +License: 3-clause BSD + For details, see scipy/linalg/src/id_dist/doc/doc.tex + +Name: L-BFGS-B +Files: scipy/optimize/lbfgsb/* +License: BSD license + For details, see scipy/optimize/lbfgsb/README + +Name: SuperLU +Files: scipy/sparse/linalg/dsolve/SuperLU/* +License: 3-clause BSD + For details, see scipy/sparse/linalg/dsolve/SuperLU/License.txt + +Name: ARPACK +Files: scipy/sparse/linalg/eigen/arpack/ARPACK/* +License: 3-clause BSD + For details, see scipy/sparse/linalg/eigen/arpack/ARPACK/COPYING + +Name: Qhull +Files: scipy/spatial/qhull/* +License: Qhull license (BSD-like) + For details, see scipy/spatial/qhull/COPYING.txt + +Name: Cephes +Files: scipy/special/cephes/* +License: 3-clause BSD + Distributed under 3-clause BSD license with permission from the author, + see https://lists.debian.org/debian-legal/2004/12/msg00295.html + + Cephes Math Library Release 2.8: June, 2000 + Copyright 1984, 1995, 2000 by Stephen L. Moshier + + This software is derived from the Cephes Math Library and is + incorporated herein by permission of the author. + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Name: Faddeeva +Files: scipy/special/Faddeeva.* +License: MIT + Copyright (c) 2012 Massachusetts Institute of Technology + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Name: qd +Files: scipy/special/cephes/dd_*.[ch] +License: modified BSD license ("BSD-LBNL-License.doc") + This work was supported by the Director, Office of Science, Division + of Mathematical, Information, and Computational Sciences of the + U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and + DE-AC02-05CH11231. + + Copyright (c) 2003-2009, The Regents of the University of California, + through Lawrence Berkeley National Laboratory (subject to receipt of + any required approvals from U.S. Dept. of Energy) All rights reserved. + + 1. Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + (1) Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) Neither the name of the University of California, Lawrence + Berkeley National Laboratory, U.S. Dept. of Energy nor the names + of its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + 2. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + 3. You are under no obligation whatsoever to provide any bug fixes, + patches, or upgrades to the features, functionality or performance of + the source code ("Enhancements") to anyone; however, if you choose to + make your Enhancements available either publicly, or directly to + Lawrence Berkeley National Laboratory, without imposing a separate + written license agreement for such Enhancements, then you hereby grant + the following license: a non-exclusive, royalty-free perpetual license + to install, use, modify, prepare derivative works, incorporate into + other computer software, distribute, and sublicense such enhancements + or derivative works thereof, in binary and source code form. + + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +Copyright (C) 2008 Stefan van der Walt , Pauli Virtanen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Copyright (C) 2003-2005 Peter J. Verveer + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org) + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + Qhull, Copyright (c) 1993-2015 + + C.B. Barber + Arlington, MA + + and + + The National Science and Technology Research Center for + Computation and Visualization of Geometric Structures + (The Geometry Center) + University of Minnesota + + email: qhull@qhull.org + +This software includes Qhull from C.B. Barber and The Geometry Center. +Qhull is copyrighted as noted above. Qhull is free software and may +be obtained via http from www.qhull.org. It may be freely copied, modified, +and redistributed under the following conditions: + +1. All copyright notices must remain intact in all files. + +2. A copy of this text file must be distributed along with any copies + of Qhull that you redistribute; this includes copies that you have + modified, or copies of programs or other software products that + include Qhull. + +3. If you modify Qhull, you must include a notice giving the + name of the person performing the modification, the date of + modification, and the reason for such modification. + +4. When distributing modified versions of Qhull, or other software + products that include Qhull, you must provide notice that the original + source code may be obtained as noted above. + +5. There is no warranty or other guarantee of fitness for Qhull, it is + provided solely "as is". Bug reports or fixes may be sent to + qhull_bug@qhull.org; the authors may or may not act on them as + they desire. + + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +dask/dask 54019e9c05134585c9c40e4195206aa78e2ea61a - BSD-3-Clause +Copyright 2002 Gary Strangman. +Copyright 2002-2016 The SciPy Developers +Copyright (c) 2005-2015, NumPy Developers. +copyright u'2014-2018, Anaconda, Inc. and contributors +Copyright (c) 2014-2018, Anaconda, Inc. and contributors + +BSD 3-Clause License + +Copyright (c) 2018, PyTorch team +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +pytorch/ignite 38a4f37de759e33bc08441bde99bcb50f3d81f55 - BSD-3-Clause +copyright 2018, Torch +Copyright (c) 2018, PyTorch team +Copyright (c) 2010-2017 Benjamin Peterson + +BSD 3-Clause License + +Copyright (c) 2018, PyTorch team +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +olivesgatech/facies_classification_benchmark 12102683a1ae78f8fbc953823c35a43b151194b3 - MIT +Copyright (c) 2017 Meet Pragnesh Shah + +Copyright (c) 2010-2018 Benjamin Peterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +opesci/devito f6129286d9c0b3a8bfe07e724ac5b00dc762efee - MIT +copyright u'2016-2019, Devito +Copyright (c) 2016, Imperial College, London + +Copyright (c) 2010-2018 Benjamin Peterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +six 1.12.0 - MIT +copyright u'2010-2018, Benjamin Peterson +Copyright (c) 2010-2018 Benjamin Peterson + +Copyright (c) 2010-2018 Benjamin Peterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +waldeland/cnn-for-asi 6f985cccecf9a811565d0b7cd919412569a22b7b - MIT +Copyright (c) 2017 + +Copyright (c) 2010-2018 Benjamin Peterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +------------------------------------------------------------------- From c4a4239afa746645e814dd40a092485c1f01122e Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Wed, 28 Aug 2019 12:47:55 +0000 Subject: [PATCH 027/207] quick minor fixes in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c89383d8..c0a8d6c5 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,14 @@ ### Setting up Env Run ```bash -conda env create -f DeepSeismic/interpretation/environments/anaconda/local/environment.yml +conda env create -f DeepSeismic/interpretation/environment/anaconda/local/environment.yml ``` This will create the appropriate environment to run experiments The compute vision repo will be downloaded into the src folder under interpretation/environments/anaconda/local/ ### Data -The scripts expect the data to be contained in /mnt/tgssalt +The scripts expect the data to be contained in /mnt/alaudah ## Contributing From c53f2fe570d5714779669e7d077bd9d29746f774 Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Wed, 28 Aug 2019 19:38:25 -0400 Subject: [PATCH 028/207] 3D SEG: first commit for PR. --- interpretation/voxel2pixel/README.md | 48 ++-- interpretation/voxel2pixel/__init__.py | 0 interpretation/voxel2pixel/batch.py | 194 ++++++++------ interpretation/voxel2pixel/data.py | 249 ++++++++++-------- interpretation/voxel2pixel/tb_logger.py | 113 +++++---- interpretation/voxel2pixel/test_parallel.py | 17 +- interpretation/voxel2pixel/texture_net.py | 70 +++--- interpretation/voxel2pixel/train.py | 144 +++++++---- interpretation/voxel2pixel/utils.py | 266 +++++++++++++------- scripts/get_F3_voxel.sh | 9 +- 10 files changed, 671 insertions(+), 439 deletions(-) create mode 100644 interpretation/voxel2pixel/__init__.py mode change 100644 => 100755 scripts/get_F3_voxel.sh diff --git a/interpretation/voxel2pixel/README.md b/interpretation/voxel2pixel/README.md index 3fd2d891..abd9e74d 100644 --- a/interpretation/voxel2pixel/README.md +++ b/interpretation/voxel2pixel/README.md @@ -1,46 +1,40 @@ -# CNN for ASI -Code for the paper:
+# Voxel to Pixel approach to Seismic Interpretation + +The code which is used in this approach is greatly described in the paper +
**Convolutional Neural Networks for Automated Seismic Interpretation**,
A. U. Waldeland, A. C. Jensen, L. Gelius and A. H. S. Solberg
[*The Leading Edge, July 2018*](https://library.seg.org/doi/abs/10.1190/tle37070529.1) -EAGE E-lecture: [*Seismic interpretation with deep learning*](https://www.youtube.com/watch?v=lm85Ap4OstM) (YouTube) - -This repository contains python/pytorch code for applying Convolutional Neural Networks (CNN) on seismic data. The input is a segy-file containing post-stack seismic amplitude data. The training labels are given as images of the training slices, colored to indicate the classes. +There is also an +EAGE E-lecture which you can watch: [*Seismic interpretation with deep learning*](https://www.youtube.com/watch?v=lm85Ap4OstM) (YouTube) ### Setup to get started -CNNs requires time-demanding computations, consider using a computer with a fast NVIDIA GPU with at least 6GB ram. -- Make sure you have all the required python packages installed:
--- pytorch
--- tensorflow (GPU enabled version is not needed - we only use this package for plotting)
--- tensorflow-tensorboard
--- numpy
--- scipy
--- segyio
--- matplotlib
-- Clone this repository
-- Download the demo data set [here](https://drive.google.com/drive/folders/0B7brcf-eGK8CbGhBdmZoUnhiTWs). This is the F3 Netherlands dataset - originally made available by OpenDTect - and available via the [MalenoV Project](https://github.com/bolgebrygg/MalenoV). -- Locate the '.segy'-file, rename it to 'data.segy' and put it in the 'F3'-folder. +- make sure you follow `SETUP.md` file in root of repo to install all the proper dependencies. +- download the data by running `scrips/get_F3_voxel.sh` from the root of this repo, i.e. from DeepSeismic folder. +This will automatically download the required data and will place it in the `F3` subfolder of this repo. -### How to use tensorboard (for visualization) -- Open a terminal
-- cd to the code-folder
-- run: tensorboard --logdir='log'
-- Open a web-browser and go to localhost:6006
+### Monitoring progress with TensorBoard +- from the `voxel2pixel` directory, run `tensorboard --logdir='log'` (all runtime logging information is +written to the `log` folder
+- open a web-browser and go to localhost:6006
More information can be found [here](https://www.tensorflow.org/get_started/summaries_and_tensorboard#launching_tensorboard). ### Usage -- train.py - train the CNN
-- test.py - Example of how the trained CNN can be applied to predict salt in a slice or the full cube. In addition it shows how learned attributes can be extracted.
+- `python train.py` will train the CNN and produce a model after a few hours on a decent gaming GPU +with at least 6GB of onboard memory
+- `python test_parallel.py` - Example of how the trained CNN can be applied to predict salt in a slice or +the full cube in distributed fashion on a single multi-GPU machine (single GPU mode is also supported). +In addition it shows how learned attributes can be extracted.
### Files In addition, it may be useful to have a look on these files
- texture_net.py - this is where the network is defined
-- batch.py - provide functionality to generate training batches with random augmentation
+- batch.py - provides functionality to generate training batches with random augmentation
- data.py - load/save data sets with segy-format and labeled slices as images
- tb_logger.py - connects to the tensorboard functionality
- utils.py - some help functions
-- test_parallel.py - An implemenation of test.py supporting multi-gpu prediction (thanks to Max Kaznady).
+- test_parallel.py - multi-GPU prediction script for scoring
### Using a different data set and custom training labels If you want to use a different data set, do the following: @@ -49,5 +43,3 @@ If you want to use a different data set, do the following: - Save images of the slices you want to train on as 'SLICETYPE_SLICENO.png' (or jpg), where SLICETYPE is either 'inline', 'crossline', or 'timeslice' and SLICENO is the slice number. - Draw the classes on top of the seismic data, using a simple image editing program with the class colors. Currently up to six classes are supported, indicated by the colors: red, blue, green, cyan, magenta and yellow. -# Contact -Email: anders.u.waldeland@gmail.com diff --git a/interpretation/voxel2pixel/__init__.py b/interpretation/voxel2pixel/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/interpretation/voxel2pixel/batch.py b/interpretation/voxel2pixel/batch.py index e75f7b08..e182cd7f 100644 --- a/interpretation/voxel2pixel/batch.py +++ b/interpretation/voxel2pixel/batch.py @@ -1,32 +1,39 @@ import numpy as np """ Returns a batch of augmented samples with center pixels randomly drawn from label_coordinates""" -def get_random_batch(data_cube, label_coordinates, im_size, batch_size, - random_flip = False, - random_stretch = None, - random_rot_xy=None, - random_rot_z=None): - #Make 3 im_size elements + +def get_random_batch( + data_cube, + label_coordinates, + im_size, + batch_size, + random_flip=False, + random_stretch=None, + random_rot_xy=None, + random_rot_z=None, +): + + # Make 3 im_size elements if type(im_size) == type(1): - im_size = [im_size,im_size,im_size] + im_size = [im_size, im_size, im_size] - #Output arrays + # Output arrays batch = np.zeros([batch_size, 1, im_size[0], im_size[1], im_size[2]]) labels = np.zeros([batch_size]) class_keys = list(label_coordinates) n_classes = len(class_keys) - #Loop through batch - n_for_class = 0; + # Loop through batch + n_for_class = 0 class_ind = 0 for i in range(batch_size): - #Start by getting a grid centered around (0,0,0) + # Start by getting a grid centered around (0,0,0) grid = getGrid(im_size) - #Apply random flip + # Apply random flip if random_flip: grid = augment_flip(grid) @@ -36,37 +43,40 @@ def get_random_batch(data_cube, label_coordinates, im_size, batch_size, if random_rot_z: grid = augment_rot_z(grid, random_rot_z) - #Apply random stretch + # Apply random stretch if random_stretch: grid = augment_stretch(grid, random_stretch) - #Pick random location from the label_coordinates for this class: - coords_for_class = label_coordinates[class_keys[class_ind]] + # Pick random location from the label_coordinates for this class: + coords_for_class = label_coordinates[class_keys[class_ind]] random_index = rand_int(0, coords_for_class.shape[1]) - coord = coords_for_class[:,random_index:random_index+1] + coord = coords_for_class[:, random_index : random_index + 1] - #Move grid to be centered around this location + # Move grid to be centered around this location grid += coord - #Interpolate samples at grid from the data: + # Interpolate samples at grid from the data: sample = trilinear_interpolation(data_cube, grid) - #Insert in output arrays + # Insert in output arrays labels[i] = class_ind - batch[i, 0, :, :, :] = np.reshape( sample, (im_size[0], im_size[1], im_size[2])) + batch[i, 0, :, :, :] = np.reshape( + sample, (im_size[0], im_size[1], im_size[2]) + ) # We seek to have a balanced batch with equally many samples from each class. n_for_class += 1 - if n_for_class+1 > int(.5+batch_size / float(n_classes) ): - if class_ind < n_classes-1: + if n_for_class + 1 > int(0.5 + batch_size / float(n_classes)): + if class_ind < n_classes - 1: class_ind += 1 n_for_class = 0 - - return batch, labels + """ Get x,y,z grid for sample """ + + def getGrid(im_size): """ getGrid returns z,x,y coordinates centered around (0,0,0) @@ -77,7 +87,7 @@ def getGrid(im_size): win1 = np.linspace(-im_size[1] // 2, im_size[1] // 2, im_size[1]) win2 = np.linspace(-im_size[2] // 2, im_size[2] // 2, im_size[2]) - x0,x1,x2 = np.meshgrid(win0, win1, win2, indexing='ij') + x0, x1, x2 = np.meshgrid(win0, win1, win2, indexing="ij") x0 = np.expand_dims(x0.ravel(), 0) x1 = np.expand_dims(x1.ravel(), 0) x2 = np.expand_dims(x2.ravel(), 0) @@ -85,43 +95,58 @@ def getGrid(im_size): return grid + """ Random flip of non-depth axes """ + + def augment_flip(grid): - #Flip x axis + # Flip x axis if rand_bool(): - grid[1,:] = -grid[1,:] + grid[1, :] = -grid[1, :] - #Flip y axis + # Flip y axis if rand_bool(): grid[2, :] = -grid[2, :] return grid + """ Random stretch/scale """ + + def augment_stretch(grid, stretch_factor): stretch = rand_float(-stretch_factor, stretch_factor) - grid *= (1+stretch) + grid *= 1 + stretch return grid + """ Random rotation """ + + def augment_rot_xy(grid, random_rot_xy): - theta = np.deg2rad(rand_float(- random_rot_xy, random_rot_xy)) - x = grid[2,:] * np.cos(theta) - grid[1,:] * np.sin(theta) - y = grid[2,:] * np.sin(theta) + grid[1,:] * np.cos(theta) + theta = np.deg2rad(rand_float(-random_rot_xy, random_rot_xy)) + x = grid[2, :] * np.cos(theta) - grid[1, :] * np.sin(theta) + y = grid[2, :] * np.sin(theta) + grid[1, :] * np.cos(theta) grid[1, :] = x grid[2, :] = y return grid + """ Random tilt """ + + def augment_rot_z(grid, random_rot_z): - theta = np.deg2rad( rand_float( - random_rot_z, random_rot_z) ) - z = grid[0,:] * np.cos(theta) - grid[1,:] * np.sin(theta) - x = grid[0,:] * np.sin(theta) + grid[1,:] * np.cos(theta) + theta = np.deg2rad(rand_float(-random_rot_z, random_rot_z)) + z = grid[0, :] * np.cos(theta) - grid[1, :] * np.sin(theta) + x = grid[0, :] * np.sin(theta) + grid[1, :] * np.cos(theta) grid[0, :] = z grid[1, :] = x return grid + """ Linear interpolation """ + + def trilinear_interpolation(input_array, indices): # http://stackoverflow.com/questions/6427276/3d-interpolation-of-numpy-arrays-without-scipy @@ -139,9 +164,21 @@ def trilinear_interpolation(input_array, indices): y1 = y0 + 1 z1 = z0 + 1 - #put all samples outside datacube to 0 - inds_out_of_range = (x0 < 0) | (x1 < 0) | (y0 < 0) | (y1 < 0) | (z0 < 0) | (z1 < 0) | \ - (x0 >= N0) | (x1 >= N0) | (y0 >= N1) | (y1 >= N1) | (z0 >= N2) | (z1 >= N2) + # put all samples outside datacube to 0 + inds_out_of_range = ( + (x0 < 0) + | (x1 < 0) + | (y0 < 0) + | (y1 < 0) + | (z0 < 0) + | (z1 < 0) + | (x0 >= N0) + | (x1 >= N0) + | (y0 >= N1) + | (y1 >= N1) + | (z0 >= N2) + | (z1 >= N2) + ) x0[inds_out_of_range] = 0 y0[inds_out_of_range] = 0 @@ -153,60 +190,77 @@ def trilinear_interpolation(input_array, indices): x = x_indices - x0 y = y_indices - y0 z = z_indices - z0 - output = (input_array[x0, y0, z0] * (1 - x) * (1 - y) * (1 - z) + - input_array[x1, y0, z0] * x * (1 - y) * (1 - z) + - input_array[x0, y1, z0] * (1 - x) * y * (1 - z) + - input_array[x0, y0, z1] * (1 - x) * (1 - y) * z + - input_array[x1, y0, z1] * x * (1 - y) * z + - input_array[x0, y1, z1] * (1 - x) * y * z + - input_array[x1, y1, z0] * x * y * (1 - z) + - input_array[x1, y1, z1] * x * y * z) + output = ( + input_array[x0, y0, z0] * (1 - x) * (1 - y) * (1 - z) + + input_array[x1, y0, z0] * x * (1 - y) * (1 - z) + + input_array[x0, y1, z0] * (1 - x) * y * (1 - z) + + input_array[x0, y0, z1] * (1 - x) * (1 - y) * z + + input_array[x1, y0, z1] * x * (1 - y) * z + + input_array[x0, y1, z1] * (1 - x) * y * z + + input_array[x1, y1, z0] * x * y * (1 - z) + + input_array[x1, y1, z1] * x * y * z + ) output[inds_out_of_range] = 0 return output + """ Functions to get random variables: """ + + def rand_float(low, high): return (high - low) * np.random.random_sample() + low + def rand_int(low, high): return np.random.randint(low, high) + def rand_bool(): return bool(np.random.randint(0, 2)) -#Test the batch-functions -if __name__ == '__main__': +# Test the batch-functions +if __name__ == "__main__": from data import readSEGY, readLabels, get_slice import tb_logger import numpy as np + import os - data, data_info = readSEGY(join('F3','data.segy')) + data, data_info = readSEGY(os.path.join("F3", "data.segy")) - train_coordinates = {'1':np.expand_dims( np.array([50,50,50]), 1)} + train_coordinates = {"1": np.expand_dims(np.array([50, 50, 50]), 1)} - - logger = tb_logger.TBLogger('log', 'batch test') + logger = tb_logger.TBLogger("log", "batch test") [batch, labels] = get_random_batch(data, train_coordinates, 65, 32) - logger.log_images('normal',batch) - - [batch, labels] = get_random_batch(data, train_coordinates, 65, 32,random_flip=True) - logger.log_images('flipping', batch) - - [batch, labels] = get_random_batch(data, train_coordinates, 65, 32, random_stretch=.50) - logger.log_images('stretching', batch) - - [batch, labels] = get_random_batch(data, train_coordinates, 65, 32, random_rot_xy=180) - logger.log_images('rot', batch) - - [batch, labels] = get_random_batch(data, train_coordinates, 65, 32, random_rot_z=15) - logger.log_images('dip', batch) - - train_cls_imgs, train_coordinates = readLabels(join('F3','train'), data_info) + logger.log_images("normal", batch) + + [batch, labels] = get_random_batch( + data, train_coordinates, 65, 32, random_flip=True + ) + logger.log_images("flipping", batch) + + [batch, labels] = get_random_batch( + data, train_coordinates, 65, 32, random_stretch=0.50 + ) + logger.log_images("stretching", batch) + + [batch, labels] = get_random_batch( + data, train_coordinates, 65, 32, random_rot_xy=180 + ) + logger.log_images("rot", batch) + + [batch, labels] = get_random_batch( + data, train_coordinates, 65, 32, random_rot_z=15 + ) + logger.log_images("dip", batch) + + train_cls_imgs, train_coordinates = readLabels( + os.path.join("F3", "train"), data_info + ) [batch, labels] = get_random_batch(data, train_coordinates, 65, 32) - logger.log_images('salt', batch[:16,:,:,:,:]) - logger.log_images('not salt', batch[16:, :, :, :, :]) + logger.log_images("salt", batch[:16, :, :, :, :]) + logger.log_images("not salt", batch[16:, :, :, :, :]) - logger.log_images('data', data[:,:,50]) + logger.log_images("data", data[:, :, 50]) diff --git a/interpretation/voxel2pixel/data.py b/interpretation/voxel2pixel/data.py index 4333607b..effa08ca 100644 --- a/interpretation/voxel2pixel/data.py +++ b/interpretation/voxel2pixel/data.py @@ -9,7 +9,7 @@ def readSEGY(filename): - print('Loading data cube from',filename,'with:') + print("Loading data cube from", filename, "with:") # Read full data cube data = segyio.tools.cube(filename) @@ -17,59 +17,61 @@ def readSEGY(filename): # Put temporal axis first data = np.moveaxis(data, -1, 0) - #Make data cube fast to acess - data = np.ascontiguousarray(data,'float32') + # Make data cube fast to acess + data = np.ascontiguousarray(data, "float32") - #Read meta data + # Read meta data segyfile = segyio.open(filename, "r") - print(' Crosslines: ', segyfile.xlines[0], ':', segyfile.xlines[-1]) - print(' Inlines: ', segyfile.ilines[0], ':', segyfile.ilines[-1]) - print(' Timeslices: ', '1', ':', data.shape[0]) + print(" Crosslines: ", segyfile.xlines[0], ":", segyfile.xlines[-1]) + print(" Inlines: ", segyfile.ilines[0], ":", segyfile.ilines[-1]) + print(" Timeslices: ", "1", ":", data.shape[0]) - #Make dict with cube-info + # Make dict with cube-info data_info = {} - data_info['crossline_start'] = segyfile.xlines[0] - data_info['inline_start'] = segyfile.ilines[0] - data_info['timeslice_start'] = 1 #Todo: read this from segy - data_info['shape'] = data.shape - #Read dt and other params needed to do create a new - + data_info["crossline_start"] = segyfile.xlines[0] + data_info["inline_start"] = segyfile.ilines[0] + data_info["timeslice_start"] = 1 # Todo: read this from segy + data_info["shape"] = data.shape + # Read dt and other params needed to do create a new return data, data_info + # Writes out_cube to a segy-file (out_filename) with same header/size as in_filename def writeSEGY(out_filename, in_filename, out_cube): - #Select last channel + # Select last channel if type(out_cube) is list: out_cube = out_cube[-1] - print('Writing interpretation to ' + out_filename) - #Copy segy file + print("Writing interpretation to " + out_filename) + # Copy segy file from shutil import copyfile + copyfile(in_filename, out_filename) # Moving temporal axis back again - out_cube = np.moveaxis(out_cube, 0,-1) + out_cube = np.moveaxis(out_cube, 0, -1) - #Open out-file + # Open out-file with segyio.open(out_filename, "r+") as src: iline_start = src.ilines[0] dtype = src.iline[iline_start].dtype # loop through inlines and insert output for i in src.ilines: - iline = out_cube[i-iline_start,:,:] + iline = out_cube[i - iline_start, :, :] src.iline[i] = np.ascontiguousarray(iline.astype(dtype)) # Moving temporal axis first again - just in case the user want to keep working on it out_cube = np.moveaxis(out_cube, -1, 0) - print('Writing interpretation - Finished') + print("Writing interpretation - Finished") return -#Alternative writings for slice-type -inline_alias = ['inline','in-line','iline','y'] -crossline_alias = ['crossline','cross-line','xline','x'] -timeslice_alias = ['timeslice','time-slice','t','z','depthslice','depth'] + +# Alternative writings for slice-type +inline_alias = ["inline", "in-line", "iline", "y"] +crossline_alias = ["crossline", "cross-line", "xline", "x"] +timeslice_alias = ["timeslice", "time-slice", "t", "z", "depthslice", "depth"] # Read labels from an image def readLabels(foldername, data_info): @@ -78,164 +80,193 @@ def readLabels(foldername, data_info): label_imgs = [] label_coordinates = {} - - #Find image files in folder + # Find image files in folder for file in files: - if file[-3:].lower() in ['jpg','png','peg', 'bmp','gif'] and file[0] !='.': + if ( + file[-3:].lower() in ["jpg", "png", "peg", "bmp", "gif"] + and file[0] != "." + ): if True: - tmp = file.split('_') + tmp = file.split("_") slice_type = tmp[0].lower() - tmp = tmp[1].split('.') + tmp = tmp[1].split(".") slice_no = int(tmp[0]) - if slice_type not in inline_alias + crossline_alias + timeslice_alias: - print('File:', file, 'could not be loaded.', 'Unknown slice type') + if ( + slice_type + not in inline_alias + crossline_alias + timeslice_alias + ): + print( + "File:", + file, + "could not be loaded.", + "Unknown slice type", + ) continue if slice_type in inline_alias: - slice_type = 'inline' + slice_type = "inline" if slice_type in crossline_alias: - slice_type = 'crossline' + slice_type = "crossline" if slice_type in timeslice_alias: - slice_type = 'timeslice' - - #Read file - print('Loading labels for', slice_type, slice_no, 'with') - img = scipy.misc.imread(join(foldername, file)) - img = interpolate_to_fit_data(img, slice_type, slice_no, data_info) + slice_type = "timeslice" + + # Read file + print("Loading labels for", slice_type, slice_no, "with") + img = scipy.misc.imread(join(foldername, file)) + img = interpolate_to_fit_data( + img, slice_type, slice_no, data_info + ) label_img = parseLabelsInImage(img) - #Get coordinates for slice - coords = get_coordinates_for_slice(slice_type, slice_no, data_info) + # Get coordinates for slice + coords = get_coordinates_for_slice( + slice_type, slice_no, data_info + ) - #Loop through labels in label_img and append to label_coordinates + # Loop through labels in label_img and append to label_coordinates for cls in np.unique(label_img): if cls > -1: if str(cls) not in label_coordinates.keys(): - label_coordinates[str(cls)] = np.array(np.zeros([3,0])) - inds_with_cls = label_img==cls + label_coordinates[str(cls)] = np.array( + np.zeros([3, 0]) + ) + inds_with_cls = label_img == cls cords_with_cls = coords[:, inds_with_cls.ravel()] - label_coordinates[str(cls)] = np.concatenate( (label_coordinates[str(cls)] , cords_with_cls), 1) - print(' ', str(np.sum(inds_with_cls)), 'labels for class',str(cls)) + label_coordinates[str(cls)] = np.concatenate( + (label_coordinates[str(cls)], cords_with_cls), 1 + ) + print( + " ", + str(np.sum(inds_with_cls)), + "labels for class", + str(cls), + ) if len(np.unique(label_img)) == 1: - print(' ', 0, 'labels', str(cls)) + print(" ", 0, "labels", str(cls)) - #Add label_img to output + # Add label_img to output label_imgs.append([label_img, slice_type, slice_no]) - #except: + # except: # print('File:', file, 'could not be loaded.') return label_imgs, label_coordinates + # Add colors to this table to make it possible to have more classes -class_color_coding =[ - [0,0,255], #blue - [0,255,0], #green - [0,255,255], #cyan - [255,0,0], #red - [255,0,255], #blue - [255,255,0] #yellow +class_color_coding = [ + [0, 0, 255], # blue + [0, 255, 0], # green + [0, 255, 255], # cyan + [255, 0, 0], # red + [255, 0, 255], # blue + [255, 255, 0], # yellow ] -#Convert RGB image to class img +# Convert RGB image to class img def parseLabelsInImage(img): - label_img = np.int16(img[:,:,0])*0 -1 # -1 = no class + label_img = np.int16(img[:, :, 0]) * 0 - 1 # -1 = no class - #decompose color channels (#Alpha is ignored) - r = img[:,:,0] - g = img[:,:,1] - b = img[:,:,2] + # decompose color channels (#Alpha is ignored) + r = img[:, :, 0] + g = img[:, :, 1] + b = img[:, :, 2] - #Alpha channel + # Alpha channel if img.shape[2] == 4: - a = 1-img.shape[2]//255 + a = 1 - img.shape[2] // 255 r = r * a g = g * a b = b * a - tolerance = 1 - #Go through classes and find pixels with this class + # Go through classes and find pixels with this class cls = 0 for color in class_color_coding: - #Find pixels with these labels - inds = (np.abs(r - color[0]) < tolerance) & \ - (np.abs(g - color[1]) < tolerance) & \ - (np.abs(b - color[2]) < tolerance) + # Find pixels with these labels + inds = ( + (np.abs(r - color[0]) < tolerance) + & (np.abs(g - color[1]) < tolerance) + & (np.abs(b - color[2]) < tolerance) + ) label_img[inds] = cls - cls +=1 + cls += 1 return label_img + # Function to resize image if needed def interpolate_to_fit_data(img, slice_type, slice_no, data_info): - #Get wanted output size - if slice_type == 'inline': - n0 = data_info['shape'][0] - n1 = data_info['shape'][2] - elif slice_type == 'crossline': - n0 = data_info['shape'][0] - n1 = data_info['shape'][1] - elif slice_type == 'timeslice': - n0 = data_info['shape'][1] - n1 = data_info['shape'][2] - return scipy.misc.imresize(img, (n0,n1), interp='nearest') + # Get wanted output size + if slice_type == "inline": + n0 = data_info["shape"][0] + n1 = data_info["shape"][2] + elif slice_type == "crossline": + n0 = data_info["shape"][0] + n1 = data_info["shape"][1] + elif slice_type == "timeslice": + n0 = data_info["shape"][1] + n1 = data_info["shape"][2] + return scipy.misc.imresize(img, (n0, n1), interp="nearest") + # Get coordinates for slice in the full cube -def get_coordinates_for_slice( slice_type, slice_no, data_info): - ds = data_info['shape'] - - #Coordinates for cube - x0,x1,x2 = np.meshgrid( np.linspace(0, ds[0] - 1, ds[0]), - np.linspace(0, ds[1] - 1, ds[1]), - np.linspace(0, ds[2] - 1, ds[2]), - indexing='ij') - if slice_type == 'inline': - start = data_info['inline_start'] +def get_coordinates_for_slice(slice_type, slice_no, data_info): + ds = data_info["shape"] + + # Coordinates for cube + x0, x1, x2 = np.meshgrid( + np.linspace(0, ds[0] - 1, ds[0]), + np.linspace(0, ds[1] - 1, ds[1]), + np.linspace(0, ds[2] - 1, ds[2]), + indexing="ij", + ) + if slice_type == "inline": + start = data_info["inline_start"] slice_no = slice_no - start x0 = x0[:, slice_no, :] x1 = x1[:, slice_no, :] x2 = x2[:, slice_no, :] - elif slice_type == 'crossline': - start = data_info['crossline_start'] + elif slice_type == "crossline": + start = data_info["crossline_start"] slice_no = slice_no - start x0 = x0[:, :, slice_no] x1 = x1[:, :, slice_no] x2 = x2[:, :, slice_no] - - elif slice_type == 'timeslice': - start = data_info['timeslice_start'] + elif slice_type == "timeslice": + start = data_info["timeslice_start"] slice_no = slice_no - start x0 = x0[slice_no, :, :] x1 = x1[slice_no, :, :] x2 = x2[slice_no, :, :] - #Collect indexes + # Collect indexes x0 = np.expand_dims(x0.ravel(), 0) x1 = np.expand_dims(x1.ravel(), 0) x2 = np.expand_dims(x2.ravel(), 0) - coords = np.concatenate((x0,x1,x2), axis=0) + coords = np.concatenate((x0, x1, x2), axis=0) return coords + # Return data-slice def get_slice(data, data_info, slice_type, slice_no, window=0): - if slice_type == 'inline': - start = data_info['inline_start'] + if slice_type == "inline": + start = data_info["inline_start"] slice_no = slice_no - start - slice = data[:, slice_no-window:slice_no+window+1, :] - elif slice_type == 'crossline': - start = data_info['crossline_start'] + slice = data[:, slice_no - window : slice_no + window + 1, :] + elif slice_type == "crossline": + start = data_info["crossline_start"] slice_no = slice_no - start - slice = data[:, slice_no-window:slice_no+window+1, :] + slice = data[:, slice_no - window : slice_no + window + 1, :] - elif slice_type == 'timeslice': - start = data_info['timeslice_start'] + elif slice_type == "timeslice": + start = data_info["timeslice_start"] slice_no = slice_no - start - slice = data[:, slice_no-window:slice_no+window+1, :] + slice = data[:, slice_no - window : slice_no + window + 1, :] return np.squeeze(slice) diff --git a/interpretation/voxel2pixel/tb_logger.py b/interpretation/voxel2pixel/tb_logger.py index 10cf3ee9..ce0cf0d4 100644 --- a/interpretation/voxel2pixel/tb_logger.py +++ b/interpretation/voxel2pixel/tb_logger.py @@ -5,7 +5,9 @@ try: import tensorflow as tf except: - print('Tensorflow could not be imported, therefore tensorboard cannot be used.') + print( + "Tensorflow could not be imported, therefore tensorboard cannot be used." + ) from io import BytesIO import matplotlib.pyplot as plt @@ -13,18 +15,24 @@ import torch import datetime -class TBLogger(object): - - def __init__(self, log_dir, folder_name = '' ): - self.log_dir = join(log_dir, folder_name + ' ' + datetime.datetime.now().strftime("%I%M%p, %B %d, %Y")) - self.log_dir = self.log_dir.replace('//','/') +class TBLogger(object): + def __init__(self, log_dir, folder_name=""): + + self.log_dir = join( + log_dir, + folder_name + + " " + + datetime.datetime.now().strftime("%I%M%p, %B %d, %Y"), + ) + self.log_dir = self.log_dir.replace("//", "/") self.writer = tf.summary.FileWriter(self.log_dir) - #Add scalar + # Add scalar def log_scalar(self, tag, value, step=0): - summary = tf.Summary(value=[tf.Summary.Value(tag=tag, - simple_value=value)]) + summary = tf.Summary( + value=[tf.Summary.Value(tag=tag, simple_value=value)] + ) self.writer.add_summary(summary, step) def make_list_of_2D_array(self, im): @@ -35,63 +43,71 @@ def make_list_of_2D_array(self, im): ims.append(im) elif len(im.shape) == 3: for i in range(im.shape[0]): - ims.append(np.squeeze(im[i,:,:])) + ims.append(np.squeeze(im[i, :, :])) elif len(im.shape) == 4: for i in range(im.shape[0]): ims.append(np.squeeze(im[i, 0, :, :])) return ims - def log_images(self, tag, images, step=0, dim = 2, max_imgs = 50,cm='jet'): + def log_images(self, tag, images, step=0, dim=2, max_imgs=50, cm="jet"): - #Make sure images are on numpy format in case the input is a Torch-variable + # Make sure images are on numpy format in case the input is a Torch-variable images = self.convert_to_numpy(images) try: - if len(images.shape)>2: + if len(images.shape) > 2: dim = 3 except: None - #Make list of images + # Make list of images if dim == 2: images = self.make_list_of_2D_array(images) - #If 3D we make one list for each slice-type + # If 3D we make one list for each slice-type if dim == 3: - new_images_ts, new_images_il, new_images_cl = self.get_slices_from_3D(images) - self.log_images(tag + '_timeslice', new_images_ts, step, 2, max_imgs) - self.log_images(tag + '_inline', new_images_il, step, 2, max_imgs) - self.log_images(tag + '_crossline', new_images_cl, step, 2, max_imgs) + new_images_ts, new_images_il, new_images_cl = self.get_slices_from_3D( + images + ) + self.log_images( + tag + "_timeslice", new_images_ts, step, 2, max_imgs + ) + self.log_images(tag + "_inline", new_images_il, step, 2, max_imgs) + self.log_images( + tag + "_crossline", new_images_cl, step, 2, max_imgs + ) return - im_summaries = [] for nr, img in enumerate(images): - #Grayscale - if cm == 'gray' or cm == 'grey': - img = img.astype('float') - img = np.repeat(np.expand_dims(img,2),3,2) + # Grayscale + if cm == "gray" or cm == "grey": + img = img.astype("float") + img = np.repeat(np.expand_dims(img, 2), 3, 2) img -= img.min() img /= img.max() img *= 255 - img = img.astype('uint8') + img = img.astype("uint8") # Write the image to a string s = BytesIO() - plt.imsave(s, img, format='png') + plt.imsave(s, img, format="png") # Create an Image object - img_sum = tf.Summary.Image(encoded_image_string=s.getvalue(), - height=img.shape[0], - width=img.shape[1]) + img_sum = tf.Summary.Image( + encoded_image_string=s.getvalue(), + height=img.shape[0], + width=img.shape[1], + ) # Create a Summary value - im_summaries.append(tf.Summary.Value(tag='%s/%d' % (tag, nr), - image=img_sum)) + im_summaries.append( + tf.Summary.Value(tag="%s/%d" % (tag, nr), image=img_sum) + ) - #if nr == max_imgs-1: + # if nr == max_imgs-1: # break # Create and write Summary @@ -112,22 +128,35 @@ def get_slices_from_3D(self, img): elif len(img.shape) == 4: for i in range(img.shape[0]): - new_images_ts.append(np.squeeze(img[i, img.shape[1] / 2, :, :])) - new_images_il.append(np.squeeze(img[i, :, img.shape[2] / 2, :])) - new_images_cl.append(np.squeeze(img[i, :, :, img.shape[3] / 2])) + new_images_ts.append( + np.squeeze(img[i, img.shape[1] / 2, :, :]) + ) + new_images_il.append( + np.squeeze(img[i, :, img.shape[2] / 2, :]) + ) + new_images_cl.append( + np.squeeze(img[i, :, :, img.shape[3] / 2]) + ) elif len(img.shape) == 5: for i in range(img.shape[0]): - new_images_ts.append(np.squeeze(img[i, 0, img.shape[2] / 2, :, :])) - new_images_il.append(np.squeeze(img[i, 0, :, img.shape[3] / 2, :])) - new_images_cl.append(np.squeeze(img[i, 0, :, :, img.shape[4] / 2])) + new_images_ts.append( + np.squeeze(img[i, 0, img.shape[2] / 2, :, :]) + ) + new_images_il.append( + np.squeeze(img[i, 0, :, img.shape[3] / 2, :]) + ) + new_images_cl.append( + np.squeeze(img[i, 0, :, :, img.shape[4] / 2]) + ) return new_images_ts, new_images_il, new_images_cl - #Convert torch to numpy - def convert_to_numpy(self,im): + + # Convert torch to numpy + def convert_to_numpy(self, im): if type(im) == torch.autograd.Variable: - #Put on CPU + # Put on CPU im = im.cpu() - #Get np-data + # Get np-data im = im.data.numpy() return im diff --git a/interpretation/voxel2pixel/test_parallel.py b/interpretation/voxel2pixel/test_parallel.py index 3ecfd26f..08945e04 100644 --- a/interpretation/voxel2pixel/test_parallel.py +++ b/interpretation/voxel2pixel/test_parallel.py @@ -44,9 +44,10 @@ # graphical progress bar from tqdm import tqdm + class ModelWrapper(nn.Module): """ - Wrap TextureNet for DataParallel to invoke classify method + Wrap TextureNet for (Distributed)DataParallel to invoke classify method """ def __init__(self, texture_model): @@ -176,7 +177,9 @@ def main_worker(gpu, ngpus_per_node, args): # we need to map the data manually to each rank - DistributedDataParallel doesn't do this at score time print("take a subset of coord_list by chunk") - coord_list = list(np.array_split(np.array(coord_list), args.world_size)[args.rank]) + coord_list = list( + np.array_split(np.array(coord_list), args.world_size)[args.rank] + ) coord_list = [tuple(x) for x in coord_list] # we only score first batch in debug mode @@ -244,13 +247,13 @@ def main_worker(gpu, ngpus_per_node, args): f, ) - # TODO: we cannot use pickle do dump from multiprocess - processes lock up + # TODO: we cannot use pickle to dump from multiprocess - processes lock up # with open("result_predictions_{}.pkl".format(args.rank), "wb") as f: # print ("dumping predictions pickle file") # pickle.dump(predictions, f) -parser = argparse.ArgumentParser(description="PyTorch ImageNet Training") +parser = argparse.ArgumentParser(description="Seismic Distributed Scoring") parser.add_argument( "-d", "--data", default="F3", type=str, help="default dataset folder name" ) @@ -292,13 +295,16 @@ def main_worker(gpu, ngpus_per_node, args): parser.add_argument( "--dist-backend", default="nccl", type=str, help="distributed backend" ) -parser.add_argument("--seed", default=0, type=int, help="default random number seed") +parser.add_argument( + "--seed", default=0, type=int, help="default random number seed" +) parser.add_argument( "--debug", action="store_true", help="debug flag - if on we will only process one batch", ) + def main(): # use distributed scoring+ @@ -451,5 +457,6 @@ def worker(classified_cube, coord): cm="binary", ) + if __name__ == "__main__": main() diff --git a/interpretation/voxel2pixel/texture_net.py b/interpretation/voxel2pixel/texture_net.py index bf55215c..3f3ab3c5 100644 --- a/interpretation/voxel2pixel/texture_net.py +++ b/interpretation/voxel2pixel/texture_net.py @@ -5,73 +5,69 @@ class TextureNet(nn.Module): - def __init__(self,n_classes=2): - super(TextureNet,self).__init__() + def __init__(self, n_classes=2): + super(TextureNet, self).__init__() # Network definition self.net = nn.Sequential( - nn.Conv3d(1,50,5,4,padding=2), #Parameters #in_channels, #out_channels, filter_size, stride (downsampling factor) + nn.Conv3d( + 1, 50, 5, 4, padding=2 + ), # Parameters #in_channels, #out_channels, filter_size, stride (downsampling factor) nn.BatchNorm3d(50), - #nn.Dropout3d() #Droput can be added like this ... + # nn.Dropout3d() #Droput can be added like this ... nn.ReLU(), - - nn.Conv3d(50,50,3,2,padding=1, bias=False), + nn.Conv3d(50, 50, 3, 2, padding=1, bias=False), nn.BatchNorm3d(50), nn.ReLU(), - - nn.Conv3d(50,50,3,2,padding=1, bias=False), + nn.Conv3d(50, 50, 3, 2, padding=1, bias=False), nn.BatchNorm3d(50), nn.ReLU(), - - nn.Conv3d(50,50,3,2,padding=1, bias=False), + nn.Conv3d(50, 50, 3, 2, padding=1, bias=False), nn.BatchNorm3d(50), nn.ReLU(), - - nn.Conv3d(50,50,3,3,padding=1, bias=False), + nn.Conv3d(50, 50, 3, 3, padding=1, bias=False), nn.BatchNorm3d(50), nn.ReLU(), - - nn.Conv3d(50,n_classes,1,1), #This is the equivalent of a fully connected layer since input has width/height/depth = 1 + nn.Conv3d( + 50, n_classes, 1, 1 + ), # This is the equivalent of a fully connected layer since input has width/height/depth = 1 nn.ReLU(), - ) - #The filter weights are by default initialized by random + # The filter weights are by default initialized by random - #Is called to compute network output - def forward(self,x): + # Is called to compute network output + def forward(self, x): return self.net(x) - - - def classify(self,x): + def classify(self, x): x = self.net(x) _, class_no = torch.max(x, 1, keepdim=True) return class_no - # Functions to get output from intermediate feature layers - def f1(self, x,): - return self.getFeatures( x, 0) - def f2(self, x,): - return self.getFeatures( x, 1) - def f3(self, x,): - return self.getFeatures( x, 2) - def f4(self, x,): - return self.getFeatures( x, 3) - def f5(self, x,): - return self.getFeatures( x, 4) + def f1(self, x): + return self.getFeatures(x, 0) + + def f2(self, x): + return self.getFeatures(x, 1) + + def f3(self, x): + return self.getFeatures(x, 2) + def f4(self, x): + return self.getFeatures(x, 3) + + def f5(self, x): + return self.getFeatures(x, 4) def getFeatures(self, x, layer_no): layer_indexes = [0, 3, 6, 9, 12] - #Make new network that has the layers up to the requested output + # Make new network that has the layers up to the requested output tmp_net = nn.Sequential() - layers = list(self.net.children())[0:layer_indexes[layer_no]+1] + layers = list(self.net.children())[0 : layer_indexes[layer_no] + 1] for i in range(len(layers)): - tmp_net.add_module(str(i),layers[i]) + tmp_net.add_module(str(i), layers[i]) if type(gpu_no_of_var(self)) == int: tmp_net.cuda(gpu_no_of_var(self)) return tmp_net(x) - - diff --git a/interpretation/voxel2pixel/train.py b/interpretation/voxel2pixel/train.py index efe631d2..7b8981e7 100644 --- a/interpretation/voxel2pixel/train.py +++ b/interpretation/voxel2pixel/train.py @@ -12,44 +12,56 @@ import tb_logger - - import numpy as np from utils import * -#This is the network definition proposed in the paper -#Parameters -dataset_name = 'F3' +# This is the network definition proposed in the paper + +# Parameters +dataset_name = "F3" im_size = 65 -batch_size = 32 #If you have a GPU with little memory, try reducing this to 16 (may degrade results) -use_gpu = True #Switch to toggle the use of GPU or not -log_tensorboard = True #Log progress on tensor board -if log_tensorboard: logger = tb_logger.TBLogger('log', 'Train') +batch_size = ( + 32 +) # If you have a GPU with little memory, try reducing this to 16 (may degrade results) +use_gpu = True # Switch to toggle the use of GPU or not +log_tensorboard = True # Log progress on tensor board +if log_tensorboard: + logger = tb_logger.TBLogger("log", "Train") -#See the texture_net.py file for the network configuration +# See the texture_net.py file for the network configuration from texture_net import TextureNet + network = TextureNet(n_classes=2) -#Loss function -cross_entropy = nn.CrossEntropyLoss() #Softmax function is included +# Loss function +cross_entropy = nn.CrossEntropyLoss() # Softmax function is included -#Optimizer to control step size in gradient descent +# Optimizer to control step size in gradient descent optimizer = torch.optim.Adam(network.parameters()) -#Transfer model to gpu +# Transfer model to gpu if use_gpu: network = network.cuda() -#Load the data cube and labels -data, data_info = readSEGY(join(dataset_name,'data.segy')) -train_class_imgs, train_coordinates = readLabels(join(dataset_name,'train'), data_info) -val_class_imgs, _ = readLabels(join(dataset_name,'val'), data_info) +# Load the data cube and labels +data, data_info = readSEGY(join(dataset_name, "data.segy")) +train_class_imgs, train_coordinates = readLabels( + join(dataset_name, "train"), data_info +) +val_class_imgs, _ = readLabels(join(dataset_name, "val"), data_info) -#Plot training/validation data with labels +# Plot training/validation data with labels if log_tensorboard: for class_img in train_class_imgs + val_class_imgs: - logger.log_images(class_img[1] + '_' + str(class_img[2] ), get_slice(data, data_info, class_img[1], class_img[2]), cm='gray') - logger.log_images(class_img[1] + '_' + str(class_img[2]) + '_true_class', class_img[0]) + logger.log_images( + class_img[1] + "_" + str(class_img[2]), + get_slice(data, data_info, class_img[1], class_img[2]), + cm="gray", + ) + logger.log_images( + class_img[1] + "_" + str(class_img[2]) + "_true_class", + class_img[0], + ) # Training loop @@ -57,30 +69,34 @@ # Get random training batch with augmentation # This is the bottle-neck for training and could be done more efficient on the GPU... - [batch, labels] = get_random_batch(data, train_coordinates, im_size, batch_size, - random_flip=True, - random_stretch=.2, - random_rot_xy=180, - random_rot_z=15) - - - #Format data to torch-variable - batch = Variable( torch.Tensor(batch).float() ) - labels = Variable( torch.Tensor(labels).long() ) + [batch, labels] = get_random_batch( + data, + train_coordinates, + im_size, + batch_size, + random_flip=True, + random_stretch=0.2, + random_rot_xy=180, + random_rot_z=15, + ) + + # Format data to torch-variable + batch = Variable(torch.Tensor(batch).float()) + labels = Variable(torch.Tensor(labels).long()) # Transfer data to gpu if use_gpu: batch = batch.cuda() labels = labels.cuda() - #Set network to training phase + # Set network to training phase network.train() - #Run the samples through the network + # Run the samples through the network output = network(batch) - #Compute loss - loss = cross_entropy( torch.squeeze(output) , labels) + # Compute loss + loss = cross_entropy(torch.squeeze(output), labels) # Do back-propagation to get gradients of weights w.r.t. loss loss.backward() @@ -92,16 +108,16 @@ if i % 10 == 0: network.eval() - #Log to training loss/acc - print('Iteration:', i, 'Training loss:', var_to_np(loss)) + # Log to training loss/acc + print("Iteration:", i, "Training loss:", var_to_np(loss)) if log_tensorboard: - logger.log_scalar('training_loss', var_to_np(loss),i) - for k,v in computeAccuracy(torch.argmax(output,1), labels).items(): + logger.log_scalar("training_loss", var_to_np(loss), i) + for k, v in computeAccuracy(torch.argmax(output, 1), labels).items(): if log_tensorboard: - logger.log_scalar('training_' + k, v, i) - print(' -',k,v,'%') + logger.log_scalar("training_" + k, v, i) + print(" -", k, v, "%") - #every 100th iteration + # every 100th iteration if i % 100 == 0 and log_tensorboard: network.eval() @@ -111,15 +127,35 @@ slice = class_img[1] slice_no = class_img[2] - class_img = interpret(network.classify, data, data_info, slice, slice_no, im_size, 16, return_full_size=True, use_gpu=use_gpu) - logger.log_images( slice + '_' + str(slice_no)+ '_pred_class', class_img, i) - - class_img = interpret(network, data, data_info, slice, slice_no, im_size, 16, return_full_size=True, use_gpu=use_gpu) - logger.log_images( slice + '_' + str(slice_no) + '_pred_prob', class_img, i) - - #Store trained network - torch.save(network.state_dict(), join(dataset_name, 'saved_model.pt')) - - - - + class_img = interpret( + network.classify, + data, + data_info, + slice, + slice_no, + im_size, + 16, + return_full_size=True, + use_gpu=use_gpu, + ) + logger.log_images( + slice + "_" + str(slice_no) + "_pred_class", class_img, i + ) + + class_img = interpret( + network, + data, + data_info, + slice, + slice_no, + im_size, + 16, + return_full_size=True, + use_gpu=use_gpu, + ) + logger.log_images( + slice + "_" + str(slice_no) + "_pred_prob", class_img, i + ) + + # Store trained network + torch.save(network.state_dict(), join(dataset_name, "saved_model.pt")) diff --git a/interpretation/voxel2pixel/utils.py b/interpretation/voxel2pixel/utils.py index 279dc9fb..68dccb5f 100644 --- a/interpretation/voxel2pixel/utils.py +++ b/interpretation/voxel2pixel/utils.py @@ -6,63 +6,82 @@ from torch.autograd import Variable from scipy.interpolate import interpn -def interpret( network, data, data_info, slice, slice_no, im_size, subsampl, return_full_size=True, use_gpu=True): + +def interpret( + network, + data, + data_info, + slice, + slice_no, + im_size, + subsampl, + return_full_size=True, + use_gpu=True, +): # Wrap np.linspace in compact function call - def ls(N): return np.linspace(0, N - 1, N, dtype='int') + def ls(N): + return np.linspace(0, N - 1, N, dtype="int") - #Size of cube + # Size of cube N0, N1, N2 = data.shape - #Coords for full cube + # Coords for full cube x0_range = ls(N0) x1_range = ls(N1) x2_range = ls(N2) - #Coords for subsampled cube - pred_points = (x0_range[::subsampl], x1_range[::subsampl], x2_range[::subsampl]) + # Coords for subsampled cube + pred_points = ( + x0_range[::subsampl], + x1_range[::subsampl], + x2_range[::subsampl], + ) - #Select slice - if slice == 'full': + # Select slice + if slice == "full": class_cube = data[::subsampl, ::subsampl, ::subsampl] * 0 - elif slice == 'inline': - slice_no = slice_no - data_info['inline_start'] + elif slice == "inline": + slice_no = slice_no - data_info["inline_start"] class_cube = data[::subsampl, 0:1, ::subsampl] * 0 x1_range = np.array([slice_no]) - pred_points = (pred_points[0],pred_points[2]) + pred_points = (pred_points[0], pred_points[2]) - elif slice == 'crossline': - slice_no = slice_no - data_info['crossline_start'] - class_cube = data[::subsampl, ::subsampl, 0:1,] * 0 + elif slice == "crossline": + slice_no = slice_no - data_info["crossline_start"] + class_cube = data[::subsampl, ::subsampl, 0:1] * 0 x2_range = np.array([slice_no]) pred_points = (pred_points[0], pred_points[1]) - elif slice == 'timeslice': - slice_no = slice_no - data_info['timeslice_start'] + elif slice == "timeslice": + slice_no = slice_no - data_info["timeslice_start"] class_cube = data[0:1, ::subsampl, ::subsampl] * 0 x0_range = np.array([slice_no]) pred_points = (pred_points[1], pred_points[2]) + # Grid for small class slice/cube + n0, n1, n2 = class_cube.shape + x0_grid, x1_grid, x2_grid = np.meshgrid( + ls(n0), ls(n1), ls(n2), indexing="ij" + ) - #Grid for small class slice/cube - n0,n1,n2 = class_cube.shape - x0_grid, x1_grid, x2_grid = np.meshgrid(ls(n0,), ls(n1), ls(n2), indexing='ij') - - #Grid for full slice/cube - X0_grid, X1_grid, X2_grid = np.meshgrid(x0_range, x1_range, x2_range, indexing='ij') + # Grid for full slice/cube + X0_grid, X1_grid, X2_grid = np.meshgrid( + x0_range, x1_range, x2_range, indexing="ij" + ) - #Indexes for large cube at small cube pixels + # Indexes for large cube at small cube pixels X0_grid_sub = X0_grid[::subsampl, ::subsampl, ::subsampl] X1_grid_sub = X1_grid[::subsampl, ::subsampl, ::subsampl] X2_grid_sub = X2_grid[::subsampl, ::subsampl, ::subsampl] - #Get half window size - w = im_size//2 + # Get half window size + w = im_size // 2 - #Loop through center pixels in output cube + # Loop through center pixels in output cube for i in range(X0_grid_sub.size): - #Get coordinates in small and large cube + # Get coordinates in small and large cube x0 = x0_grid.ravel()[i] x1 = x1_grid.ravel()[i] x2 = x2_grid.ravel()[i] @@ -71,84 +90,136 @@ def ls(N): return np.linspace(0, N - 1, N, dtype='int') X1 = X1_grid_sub.ravel()[i] X2 = X2_grid_sub.ravel()[i] - - #Only compute when a full 65x65x65 cube can be extracted around center pixel - if X0>w and X1>w and X2>w and X0 w + and X1 > w + and X2 > w + and X0 < N0 - w + 1 + and X1 < N1 - w + 1 + and X2 < N2 - w + 1 + ): + + # Get mini-cube around center pixel + mini_cube = data[ + X0 - w : X0 + w + 1, X1 - w : X1 + w + 1, X2 - w : X2 + w + 1 + ] + + # Get predicted "probabilities" + mini_cube = Variable( + torch.FloatTensor(mini_cube[np.newaxis, np.newaxis, :, :, :]) + ) + if use_gpu: + mini_cube = mini_cube.cuda() out = network(mini_cube) out = out.data.cpu().numpy() - out = out[:,:, out.shape[2]//2, out.shape[3]//2, out.shape[4]//2] + out = out[ + :, :, out.shape[2] // 2, out.shape[3] // 2, out.shape[4] // 2 + ] out = np.squeeze(out) # Make one output pr output channel if type(class_cube) != type(list()): - class_cube = np.split( np.repeat(class_cube[:,:,:,np.newaxis],out.size,3),out.size, axis=3) + class_cube = np.split( + np.repeat(class_cube[:, :, :, np.newaxis], out.size, 3), + out.size, + axis=3, + ) # Insert into output if out.size == 1: class_cube[0][x0, x1, x2] = out else: for i in range(out.size): - class_cube[i][x0,x1,x2] = out[i] + class_cube[i][x0, x1, x2] = out[i] - #Keep user informed about progress - if slice == 'full': printProgressBar(i,x0_grid.size) + # Keep user informed about progress + if slice == "full": + printProgressBar(i, x0_grid.size) - #Resize to input size + # Resize to input size if return_full_size: - if slice == 'full': print('Interpolating down sampled results to fit input cube') + if slice == "full": + print("Interpolating down sampled results to fit input cube") N = X0_grid.size - #Output grid - if slice == 'full': - grid_output_cube = np.concatenate( [X0_grid.reshape([N, 1]), X1_grid.reshape([N, 1]), X2_grid.reshape([N, 1])], 1) - elif slice == 'inline': - grid_output_cube = np.concatenate( [X0_grid.reshape([N, 1]), X2_grid.reshape([N, 1])], 1) - elif slice == 'crossline': - grid_output_cube = np.concatenate( [X0_grid.reshape([N, 1]), X1_grid.reshape([N, 1])], 1) - elif slice == 'timeslice': - grid_output_cube = np.concatenate( [X1_grid.reshape([N, 1]), X2_grid.reshape([N, 1])], 1) - - #Interpolation + # Output grid + if slice == "full": + grid_output_cube = np.concatenate( + [ + X0_grid.reshape([N, 1]), + X1_grid.reshape([N, 1]), + X2_grid.reshape([N, 1]), + ], + 1, + ) + elif slice == "inline": + grid_output_cube = np.concatenate( + [X0_grid.reshape([N, 1]), X2_grid.reshape([N, 1])], 1 + ) + elif slice == "crossline": + grid_output_cube = np.concatenate( + [X0_grid.reshape([N, 1]), X1_grid.reshape([N, 1])], 1 + ) + elif slice == "timeslice": + grid_output_cube = np.concatenate( + [X1_grid.reshape([N, 1]), X2_grid.reshape([N, 1])], 1 + ) + + # Interpolation for i in range(len(class_cube)): - is_int = np.sum(np.unique(class_cube[i]).astype('float') - np.unique(class_cube[i]).astype('int32').astype('float') ) == 0 - class_cube[i] = interpn(pred_points, class_cube[i].astype('float').squeeze(), grid_output_cube, method='linear', fill_value=0, bounds_error=False) - class_cube[i] = class_cube[i].reshape([x0_range.size, x1_range.size, x2_range.size]) - - #If ouput is class labels we convert the interpolated array to ints + is_int = ( + np.sum( + np.unique(class_cube[i]).astype("float") + - np.unique(class_cube[i]).astype("int32").astype("float") + ) + == 0 + ) + class_cube[i] = interpn( + pred_points, + class_cube[i].astype("float").squeeze(), + grid_output_cube, + method="linear", + fill_value=0, + bounds_error=False, + ) + class_cube[i] = class_cube[i].reshape( + [x0_range.size, x1_range.size, x2_range.size] + ) + + # If ouput is class labels we convert the interpolated array to ints if is_int: - class_cube[i] = class_cube[i].astype('int32') + class_cube[i] = class_cube[i].astype("int32") - if slice == 'full': print('Finished interpolating') + if slice == "full": + print("Finished interpolating") - #Squeeze outputs + # Squeeze outputs for i in range(len(class_cube)): - class_cube[i]= class_cube[i].squeeze() + class_cube[i] = class_cube[i].squeeze() return class_cube + # Print progress information import sys import time + st = 0 last_update = 0 -#Adapted from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console/14879561#14879561 -def printProgressBar(iteration, total, prefix = '', suffix = '', decimals = 1, length = 100, fill = '='): +# Adapted from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console/14879561#14879561 +def printProgressBar( + iteration, total, prefix="", suffix="", decimals=1, length=100, fill="=" +): global st, last_update - #Expect itteration to go from 0 to N-1 - iteration = iteration + 1 ; + # Expect itteration to go from 0 to N-1 + iteration = iteration + 1 - #Only update every 5 second + # Only update every 5 second if time.time() - last_update < 5: if iteration == total: time.sleep(1) @@ -156,29 +227,38 @@ def printProgressBar(iteration, total, prefix = '', suffix = '', decimals = 1, l return if iteration <= 1: - st=time.time() - exp_h = '' - exp_m = '' - exp_s = '' + st = time.time() + exp_h = "" + exp_m = "" + exp_s = "" elif iteration == total: - exp_time = (time.time() - st) - exp_h = int(exp_time / 3600 ) - exp_m = int(exp_time / 60 - exp_h*60.) - exp_s = int(exp_time - exp_m * 60. - exp_h*3600.) + exp_time = time.time() - st + exp_h = int(exp_time / 3600) + exp_m = int(exp_time / 60 - exp_h * 60.0) + exp_s = int(exp_time - exp_m * 60.0 - exp_h * 3600.0) else: - exp_time = (time.time()-st)/(iteration-1)*total - (time.time()-st) + exp_time = (time.time() - st) / (iteration - 1) * total - ( + time.time() - st + ) exp_h = int(exp_time / 3600) - exp_m = int(exp_time / 60 - exp_h * 60.) - exp_s = int(exp_time - exp_m * 60. - exp_h * 3600.) - + exp_m = int(exp_time / 60 - exp_h * 60.0) + exp_s = int(exp_time - exp_m * 60.0 - exp_h * 3600.0) - percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total))) + percent = ("{0:." + str(decimals) + "f}").format( + 100 * (iteration / float(total)) + ) filledLength = int(length * iteration // total) - bar = fill * filledLength + '-' * (length - filledLength) + bar = fill * filledLength + "-" * (length - filledLength) if iteration != total: - print('\r%s |%s| %s%% %s - %sh %smin %ss left' % (prefix, bar, percent, suffix, exp_h, exp_m, exp_s),) + print( + "\r%s |%s| %s%% %s - %sh %smin %ss left" + % (prefix, bar, percent, suffix, exp_h, exp_m, exp_s) + ) else: - print('\r%s |%s| %s%% %s - %sh %smin %ss ' % (prefix, bar, percent, suffix, exp_h, exp_m, exp_s),) + print( + "\r%s |%s| %s%% %s - %sh %smin %ss " + % (prefix, bar, percent, suffix, exp_h, exp_m, exp_s) + ) sys.stdout.write("\033[F") # Print New Line on Complete if iteration == total: @@ -186,7 +266,7 @@ def printProgressBar(iteration, total, prefix = '', suffix = '', decimals = 1, l last_update = time.time() -#Function that returns the GPU number of a variable/module (or False if on CPU) +# Function that returns the GPU number of a variable/module (or False if on CPU) def gpu_no_of_var(var): try: is_cuda = next(var.parameters()).is_cuda @@ -202,12 +282,12 @@ def gpu_no_of_var(var): return False -#Take a pytorch variable and make numpy +# Take a pytorch variable and make numpy def var_to_np(var): if type(var) in [np.array, np.ndarray]: return var - #If input is list we do this for all elements + # If input is list we do this for all elements if type(var) == type([]): out = [] for v in var: @@ -238,7 +318,11 @@ def computeAccuracy(predicted_class, labels): accuracies = {} for cls in np.unique(labels): - if cls>=0: - accuracies['accuracy_class_' + str(cls)] = int(np.mean(predicted_class[labels==cls]==cls)*100) - accuracies['average_class_accuracy'] = np.mean([acc for acc in accuracies.values()]) + if cls >= 0: + accuracies["accuracy_class_" + str(cls)] = int( + np.mean(predicted_class[labels == cls] == cls) * 100 + ) + accuracies["average_class_accuracy"] = np.mean( + [acc for acc in accuracies.values()] + ) return accuracies diff --git a/scripts/get_F3_voxel.sh b/scripts/get_F3_voxel.sh old mode 100644 new mode 100755 index 3aabe27c..273e5e7d --- a/scripts/get_F3_voxel.sh +++ b/scripts/get_F3_voxel.sh @@ -1,5 +1,8 @@ #!/bin/bash -TODO: fetch Dutch F3 from Malenov project. - - +echo "Downloading Dutch F3 from https://github.com/bolgebrygg/MalenoV" +# fetch Dutch F3 from Malenov project. +wget https://drive.google.com/open?id=0B7brcf-eGK8CUUZKLXJURFNYeXM interpretation/voxel2pixel/F3/data.segy +wget https://github.com/waldeland/CNN-for-ASI/raw/master/F3/train/inline_339.png interpretation/voxel2pixel/F3/train/inline_339.png +wget https://github.com/waldeland/CNN-for-ASI/raw/master/F3/val/inline_405.png interpretation/voxel2pixel/F3/val/inline_405.png +echo "Download complete" From 0e79cc065930041dd2f9941f12705809be1f1a06 Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Wed, 28 Aug 2019 19:49:51 -0400 Subject: [PATCH 029/207] 3D SEG: removed data files to avoid redistribution. --- .../voxel2pixel/F3/train/inline_339.png | Bin 12482 -> 0 bytes .../voxel2pixel/F3/val/inline_405.png | Bin 22661 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 interpretation/voxel2pixel/F3/train/inline_339.png delete mode 100644 interpretation/voxel2pixel/F3/val/inline_405.png diff --git a/interpretation/voxel2pixel/F3/train/inline_339.png b/interpretation/voxel2pixel/F3/train/inline_339.png deleted file mode 100644 index 8128c686ad3322249855f8315ffcc1889b9afe22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12482 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 Jc;EQt{{c|Q1`GfI From f7ccf9cd896c8d0b1bb1a12bef01103e52ce2ed9 Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Thu, 29 Aug 2019 09:27:29 +0000 Subject: [PATCH 030/207] Merged PR 126: updated notice file with previously excluded components updated notice file with previously excluded components --- NOTICE.txt | 119 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 46 deletions(-) diff --git a/NOTICE.txt b/NOTICE.txt index dbaf3a7b..6dc34351 100755 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1847,36 +1847,53 @@ Copyright (c) 2005-2015, NumPy Developers. copyright u'2014-2018, Anaconda, Inc. and contributors Copyright (c) 2014-2018, Anaconda, Inc. and contributors -BSD 3-Clause License +Copyright (c) . All rights reserved. -Copyright (c) 2018, PyTorch team -All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------- + +------------------------------------------------------------------- + +mpmath 1.1.0 - BSD-3-Clause + +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +numpy 1.17.0 - BSD-3-Clause + +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------- @@ -1887,35 +1904,45 @@ copyright 2018, Torch Copyright (c) 2018, PyTorch team Copyright (c) 2010-2017 Benjamin Peterson -BSD 3-Clause License +Copyright (c) . All rights reserved. -Copyright (c) 2018, PyTorch team -All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------- + +------------------------------------------------------------------- + +hrnet/hrnet-semantic-segmentation 06142dc1c7026e256a7561c3e875b06622b5670f - MIT +Copyright (c) 2017 +Copyright (c) Microsoft +Copyright (c) 2019 Microsoft + +Copyright (c) 2010-2018 Benjamin Peterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------- From 333ea4fdc48b7ba2cdeaa5e15a1c878b413b28be Mon Sep 17 00:00:00 2001 From: masalvar Date: Thu, 29 Aug 2019 16:11:02 +0000 Subject: [PATCH 031/207] Updates --- WORKERS | 51 +++++ .../environment/anaconda/local/src/cv-lib | 2 +- .../dutchf3/local/configs/hrnet.yaml | 46 +++-- .../local/configs/patch_deconvnet.yaml | 24 ++- .../dutchf3/local/configs/seresnet_unet.yaml | 9 +- .../segmentation/dutchf3/local/default.py | 12 +- .../segmentation/dutchf3/local/run.sh | 2 +- .../segmentation/dutchf3/local/test.py | 20 +- .../segmentation/dutchf3/local/test.sh | 2 +- .../segmentation/dutchf3/local/train.py | 179 +++++++----------- 10 files changed, 210 insertions(+), 137 deletions(-) create mode 100644 WORKERS diff --git a/WORKERS b/WORKERS new file mode 100644 index 00000000..10439679 --- /dev/null +++ b/WORKERS @@ -0,0 +1,51 @@ +AUTO_RESUME: False +CUDNN: + BENCHMARK: True + DETERMINISTIC: False + ENABLED: True +DATASET: + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + NUM_CLASSES: 6 + ROOT: +GPUS: (0,) +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_DIR: +MODEL: + IN_CHANNELS: 1 + NAME: patch_deconvnet +OUTPUT_DIR: output +PIN_MEMORY: True +PRINT_FREQ: 20 +SEED: 42 +TEST: + CROSSLINE: True + INLINE: True + MODEL_PATH: + SPLIT: Both + TEST_STRIDE: 10 +TRAIN: + AUGMENTATION: True + AUGMENTATIONS: + PAD: + HEIGHT: 256 + WIDTH: 256 + RESIZE: + HEIGHT: 200 + WIDTH: 200 + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + DEPTH: no + END_EPOCH: 484 + MAX_LR: 0.01 + MEAN: 0.0009997 + MIN_LR: 0.001 + MODEL_DIR: models + MOMENTUM: 0.9 + PATCH_SIZE: 99 + SNAPSHOTS: 5 + STD: 0.20977 + STRIDE: 50 + WEIGHT_DECAY: 0.0001 +VALIDATION: + BATCH_SIZE_PER_GPU: 32 +WORKERS: 4 diff --git a/interpretation/environment/anaconda/local/src/cv-lib b/interpretation/environment/anaconda/local/src/cv-lib index 2a3ebbba..7cde8158 160000 --- a/interpretation/environment/anaconda/local/src/cv-lib +++ b/interpretation/environment/anaconda/local/src/cv-lib @@ -1 +1 @@ -Subproject commit 2a3ebbba65cd05f6db7796d367735184abdfdc50 +Subproject commit 7cde8158edb8be836540b3a036b5e42988e38285 diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml index 81e8aca0..a2491511 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml @@ -3,19 +3,24 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' +OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + DATASET: - NUM_CLASSES: 1 - ROOT: /mnt/tgssalt + NUM_CLASSES: 6 + ROOT: /mnt/alaudah + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + MODEL: NAME: seg_hrnet - PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' + IN_CHANNELS: 3 + PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: @@ -59,17 +64,36 @@ MODEL: FUSE_METHOD: SUM TRAIN: - BATCH_SIZE_PER_GPU: 32 + BATCH_SIZE_PER_GPU: 16 BEGIN_EPOCH: 0 END_EPOCH: 300 MIN_LR: 0.001 - MAX_LR: 0.01 + MAX_LR: 0.02 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "patch" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 TEST: - BATCH_SIZE_PER_GPU: 64 + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml index 513aa149..179e985f 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml @@ -3,37 +3,53 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' +OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf SEED: 2019 + DATASET: NUM_CLASSES: 6 ROOT: /mnt/alaudah + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: NAME: patch_deconvnet IN_CHANNELS: 1 + TRAIN: BATCH_SIZE_PER_GPU: 64 BEGIN_EPOCH: 0 END_EPOCH: 300 MIN_LR: 0.001 - MAX_LR: 0.1 + MAX_LR: 0.02 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True + DEPTH: "No" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 99 + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" VALIDATION: - BATCH_SIZE_PER_GPU: 128 + BATCH_SIZE_PER_GPU: 512 TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/188e66a26115760b7cbde7e9f6a626b6519a3bb7/patch_deconvnet/Aug25_102649/patch_deconvnet_running_model_25.pth" + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/5cc37bbe5302e1989ef1388d629400a16f82d1a9/patch_deconvnet/Aug27_200339/models/patch_deconvnet_snapshot1model_50.pth" TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml index 9e4b08ee..a2e95ba9 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml @@ -3,23 +3,23 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' +OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf SEED: 2019 + DATASET: NUM_CLASSES: 6 ROOT: /mnt/alaudah - + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: NAME: resnet_unet IN_CHANNELS: 3 - TRAIN: BATCH_SIZE_PER_GPU: 16 BEGIN_EPOCH: 0 @@ -40,6 +40,9 @@ TRAIN: PAD: HEIGHT: 256 WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" VALIDATION: diff --git a/interpretation/experiments/segmentation/dutchf3/local/default.py b/interpretation/experiments/segmentation/dutchf3/local/default.py index c0e1d436..15608966 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/default.py +++ b/interpretation/experiments/segmentation/dutchf3/local/default.py @@ -14,8 +14,8 @@ _C = CN() -_C.OUTPUT_DIR = "output" -_C.LOG_DIR = "" +_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models +_C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.GPUS = (0,) _C.WORKERS = 4 _C.PRINT_FREQ = 20 @@ -40,6 +40,8 @@ _C.MODEL = CN() _C.MODEL.NAME = "patch_deconvnet" _C.MODEL.IN_CHANNELS = 1 +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) # training @@ -52,12 +54,12 @@ _C.TRAIN.BATCH_SIZE_PER_GPU = 32 _C.TRAIN.WEIGHT_DECAY = 0.0001 _C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.SAVE_LOCATION = "/tmp/models" +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR _C.TRAIN.AUGMENTATION = True _C.TRAIN.STRIDE = 50 _C.TRAIN.PATCH_SIZE = 99 _C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 -_C.TRAIN.STD = 0.20977 # 0.20976548783479299 +_C.TRAIN.STD = 0.20977 # 0.20976548783479299 # TODO: Should we apply std scaling? _C.TRAIN.DEPTH = 'no' # Options are None, Patch and Section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 @@ -70,8 +72,6 @@ _C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 _C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 - - # validation _C.VALIDATION = CN() _C.VALIDATION.BATCH_SIZE_PER_GPU = 32 diff --git a/interpretation/experiments/segmentation/dutchf3/local/run.sh b/interpretation/experiments/segmentation/dutchf3/local/run.sh index c1eb179b..e0a67712 100755 --- a/interpretation/experiments/segmentation/dutchf3/local/run.sh +++ b/interpretation/experiments/segmentation/dutchf3/local/run.sh @@ -1,3 +1,3 @@ #!/bin/bash export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python train.py --cfg "configs/patch_deconvnet.yaml" \ No newline at end of file +python train.py --cfg "configs/hrnet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.py b/interpretation/experiments/segmentation/dutchf3/local/test.py index 2fc7f913..ccca4ee6 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/test.py +++ b/interpretation/experiments/segmentation/dutchf3/local/test.py @@ -1,3 +1,13 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 +# url: https://github.com/olivesgatech/facies_classification_benchmark + +""" +Modified version of the Alaudah testing script +#TODO: Needs to be improved. Needs to be able to run across multiple GPUs and better factoring around the loader +""" + import argparse import logging import logging.config @@ -155,8 +165,9 @@ def _extract_patch(img_p, hdx, wdx, ps, patch_size, aug=None): ] if aug is not None: # TODO: Make depth optional from config - patch = add_patch_depth_channels(aug(image=patch.numpy())['image']) - return torch.from_numpy(patch).to(torch.float32) + # patch = add_patch_depth_channels(aug(image=patch.numpy())['image']) + patch = aug(image=patch.numpy())['image'] + return torch.from_numpy(patch).to(torch.float32).unsqueeze(0) else: return patch.unsqueeze(dim=0) @@ -171,6 +182,11 @@ def _generate_batches( test_aug = Compose( [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ), Resize( config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.sh b/interpretation/experiments/segmentation/dutchf3/local/test.sh index 066e9ad5..4c09072f 100755 --- a/interpretation/experiments/segmentation/dutchf3/local/test.sh +++ b/interpretation/experiments/segmentation/dutchf3/local/test.sh @@ -1,3 +1,3 @@ #!/bin/bash export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python test.py --cfg "configs/seresnet_unet.yaml" \ No newline at end of file +python test.py --cfg "configs/patch_deconvnet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/train.py b/interpretation/experiments/segmentation/dutchf3/local/train.py index d9639674..5fe763f2 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/train.py +++ b/interpretation/experiments/segmentation/dutchf3/local/train.py @@ -1,67 +1,48 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. # /* spell-checker: disable */ import logging import logging.config import os from datetime import datetime +from os import path +import cv2 import fire import numpy as np import torch import torch.nn.functional as F import torchvision.utils as vutils +from albumentations import (Compose, GaussNoise, HorizontalFlip, Normalize, + PadIfNeeded, Resize) from ignite.contrib.handlers import CosineAnnealingScheduler from ignite.engine import Events from ignite.metrics import Loss from ignite.utils import convert_tensor from sklearn.model_selection import train_test_split from tensorboardX import SummaryWriter -from toolz import compose +from toolz import compose, itertoolz from torch.utils import data from tqdm import tqdm -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) +from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, + tensorboard_handlers) from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) - -from cv_lib.segmentation.dutchf3.data import ( - get_train_loader, - decode_segmap, - split_train_val, - split_non_overlapping_train_val, -) -from cv_lib.segmentation.dutchf3.engine import ( - create_supervised_evaluator, - create_supervised_trainer, -) -from cv_lib.segmentation.dutchf3.metrics import MeanIoU, PixelwiseAccuracy -from cv_lib.segmentation import models -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, - np_to_tb, -) +from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, + create_summary_writer) +from cv_lib.segmentation.dutchf3.data import (decode_segmap, get_train_loader, + split_non_overlapping_train_val, + split_train_val) +from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, + create_supervised_trainer) +from cv_lib.segmentation.dutchf3.metrics import (FrequencyWeightedIoU, + MeanClassAccuracy, MeanIoU, + PixelwiseAccuracy) +from cv_lib.segmentation.dutchf3.utils import (current_datetime, generate_path, + git_branch, git_hash, np_to_tb) from default import _C as config from default import update_config -from albumentations import ( - Compose, - HorizontalFlip, - GaussNoise, - Normalize, - Resize, - PadIfNeeded, -) -import cv2 -from toolz import itertoolz def prepare_batch(batch, device=None, non_blocking=False): @@ -104,64 +85,38 @@ def run(*options, cfg=None): ) # Setup Augmentations + basic_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ), + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] + ) if config.TRAIN.AUGMENTATION: train_aug = Compose( - # TODO: Should we apply std scaling? [ - Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, - ), + basic_aug, HorizontalFlip(p=0.5), - # GaussNoise(var_limit=(0.02) ** 2), - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, - ), - PadIfNeeded( - min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, - min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=255, - ), - ] - ) - val_aug = Compose( - # TODO: Should we apply std scaling? - [ - Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, - ), - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, - ), - PadIfNeeded( - min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, - min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=255, - ), ] ) + val_aug = basic_aug else: - data_aug = Compose( - [ - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, - ), - ] - ) - + train_aug = val_aug = basic_aug + TrainPatchLoader = get_train_loader(config) train_set = TrainPatchLoader( @@ -172,13 +127,12 @@ def run(*options, cfg=None): augmentations=train_aug, ) - # Without Augmentation: val_set = TrainPatchLoader( split="val", is_transform=True, stride=config.TRAIN.STRIDE, patch_size=config.TRAIN.PATCH_SIZE, - augmentations=val_aug + augmentations=val_aug, ) n_classes = train_set.n_classes @@ -190,7 +144,7 @@ def run(*options, cfg=None): shuffle=True, ) val_loader = data.DataLoader( - train_set, + val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, ) @@ -202,6 +156,7 @@ def run(*options, cfg=None): device = "cuda" model = model.to(device) # Send to GPU + optimizer = torch.optim.SGD( model.parameters(), lr=config.TRAIN.MAX_LR, @@ -209,15 +164,17 @@ def run(*options, cfg=None): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - tboard_log_dir = generate_path( - config.LOG_DIR, + output_dir = generate_path( + config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(), ) - summary_writer = create_summary_writer(log_dir=tboard_log_dir) - snapshot_duration = scheduler_step * len(train_set) + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + snapshot_duration = scheduler_step * len(train_loader) scheduler = CosineAnnealingScheduler( optimizer, "lr", @@ -271,6 +228,12 @@ def _select_pred_and_mask(model_out_dict): n_classes, device, output_transform=_select_pred_and_mask ), "nll": Loss(criterion, output_transform=_select_pred_and_mask), + "mca": MeanClassAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + "fiou": FrequencyWeightedIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), "pixa": PixelwiseAccuracy( n_classes, device, output_transform=_select_pred_and_mask ), @@ -291,6 +254,8 @@ def _select_pred_and_mask(model_out_dict): "IoU": "IoU :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :", + "mca": "Mean Class Accuracy :", + "fiou": "Freq Weighted IoU :", }, ), ) @@ -300,7 +265,12 @@ def _select_pred_and_mask(model_out_dict): summary_writer, trainer, "epoch", - metrics_dict={"IoU": "Validation/IoU", "nll": "Validation/Loss"}, + metrics_dict={ + "IoU": "Validation/IoU", + "nll": "Validation/Loss", + "mca": "Validation/MCA", + "fiou": "Validation/FIoU", + }, ), ) @@ -342,15 +312,8 @@ def _tensor_to_numpy(pred_tensor): def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) checkpoint_handler = SnapshotHandler( - output_dir, + path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, snapshot_function, ) @@ -359,7 +322,7 @@ def snapshot_function(): ) logger.info("Starting training") - trainer.run(itertoolz.take(2, train_loader), max_epochs=config.TRAIN.END_EPOCH) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) if __name__ == "__main__": From 6476f3dc95e0a4bb0b0d79d2ae9f941377a7d970 Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Thu, 29 Aug 2019 18:10:02 -0400 Subject: [PATCH 032/207] 3D SEG: restyled batch file, moving onto others. --- interpretation/voxel2pixel/batch.py | 217 +++++++++++++++++++--------- 1 file changed, 149 insertions(+), 68 deletions(-) diff --git a/interpretation/voxel2pixel/batch.py b/interpretation/voxel2pixel/batch.py index e182cd7f..d3a9485b 100644 --- a/interpretation/voxel2pixel/batch.py +++ b/interpretation/voxel2pixel/batch.py @@ -1,26 +1,41 @@ import numpy as np -""" Returns a batch of augmented samples with center pixels randomly drawn from label_coordinates""" - - def get_random_batch( - data_cube, - label_coordinates, - im_size, - batch_size, - random_flip=False, - random_stretch=None, - random_rot_xy=None, - random_rot_z=None, + data_cube, + label_coordinates, + im_size, + num_batch_size, + random_flip=False, + random_stretch=None, + random_rot_xy=None, + random_rot_z=None, ): + """ + Returns a batch of augmented samples with center pixels randomly drawn from label_coordinates + + Args: + data_cube: 3D numpy array with floating point velocity values + label_coordinates: 3D coordinates of the labeled training slice + im_size: size of the 3D voxel which we're cutting out around each label_coordinate + num_batch_size: size of the batch + random_flip: bool to perform random voxel flip + random_stretch: bool to enable random stretch + random_rot_xy: bool to enable random rotation of the voxel around dim-0 and dim-1 + random_rot_z: bool to enable random rotation around dim-2 + + Returns: + a tuple of batch numpy array array of data with dimension + (batch, 1, data_cube.shape[0], data_cube.shape[1], data_cube.shape[2]) and the associated labels as an array + of size (batch). + """ # Make 3 im_size elements - if type(im_size) == type(1): + if isinstance(im_size, int): im_size = [im_size, im_size, im_size] # Output arrays - batch = np.zeros([batch_size, 1, im_size[0], im_size[1], im_size[2]]) - labels = np.zeros([batch_size]) + batch = np.zeros([num_batch_size, 1, im_size[0], im_size[1], im_size[2]]) + ret_labels = np.zeros([num_batch_size]) class_keys = list(label_coordinates) n_classes = len(class_keys) @@ -28,10 +43,10 @@ def get_random_batch( # Loop through batch n_for_class = 0 class_ind = 0 - for i in range(batch_size): + for i in range(num_batch_size): # Start by getting a grid centered around (0,0,0) - grid = getGrid(im_size) + grid = get_grid(im_size) # Apply random flip if random_flip: @@ -50,7 +65,7 @@ def get_random_batch( # Pick random location from the label_coordinates for this class: coords_for_class = label_coordinates[class_keys[class_ind]] random_index = rand_int(0, coords_for_class.shape[1]) - coord = coords_for_class[:, random_index : random_index + 1] + coord = coords_for_class[:, random_index: random_index + 1] # Move grid to be centered around this location grid += coord @@ -59,47 +74,57 @@ def get_random_batch( sample = trilinear_interpolation(data_cube, grid) # Insert in output arrays - labels[i] = class_ind + ret_labels[i] = class_ind batch[i, 0, :, :, :] = np.reshape( sample, (im_size[0], im_size[1], im_size[2]) ) # We seek to have a balanced batch with equally many samples from each class. n_for_class += 1 - if n_for_class + 1 > int(0.5 + batch_size / float(n_classes)): + if n_for_class + 1 > int(0.5 + num_batch_size / float(n_classes)): if class_ind < n_classes - 1: class_ind += 1 n_for_class = 0 - return batch, labels + return batch, ret_labels -""" Get x,y,z grid for sample """ +def get_grid(im_size): + """ + getGrid returns z,x,y coordinates centered around (0,0,0) + Args: + im_size: size of window -def getGrid(im_size): - """ - getGrid returns z,x,y coordinates centered around (0,0,0) - :param im_size: size of window - :return: numpy int array with size: 3 x im_size**3 + Returns + numpy int array with size: 3 x im_size**3 """ win0 = np.linspace(-im_size[0] // 2, im_size[0] // 2, im_size[0]) win1 = np.linspace(-im_size[1] // 2, im_size[1] // 2, im_size[1]) win2 = np.linspace(-im_size[2] // 2, im_size[2] // 2, im_size[2]) x0, x1, x2 = np.meshgrid(win0, win1, win2, indexing="ij") - x0 = np.expand_dims(x0.ravel(), 0) - x1 = np.expand_dims(x1.ravel(), 0) - x2 = np.expand_dims(x2.ravel(), 0) - grid = np.concatenate((x0, x1, x2), axis=0) - return grid + ex0 = np.expand_dims(x0.ravel(), 0) + ex1 = np.expand_dims(x1.ravel(), 0) + ex2 = np.expand_dims(x2.ravel(), 0) + grid = np.concatenate((ex0, ex1, ex2), axis=0) -""" Random flip of non-depth axes """ + return grid def augment_flip(grid): + """ + Random flip of non-depth axes. + + Args: + grid: 3D coordinates of the voxel + + Returns: + flipped grid coordinates + """ + # Flip x axis if rand_bool(): grid[1, :] = -grid[1, :] @@ -111,19 +136,35 @@ def augment_flip(grid): return grid -""" Random stretch/scale """ +def augment_stretch(grid, stretch_factor): + """ + Random stretch/scale + Args: + grid: 3D coordinate grid of the voxel + stretch_factor: this is actually a boolean which triggers stretching + TODO: change this to just call the function and not do -1,1 in rand_float -def augment_stretch(grid, stretch_factor): + Returns: + stretched grid coordinates + """ stretch = rand_float(-stretch_factor, stretch_factor) grid *= 1 + stretch return grid -""" Random rotation """ +def augment_rot_xy(grid, random_rot_xy): + """ + Random rotation + Args: + grid: coordinate grid list of 3D points + random_rot_xy: this is actually a boolean which triggers rotation + TODO: change this to just call the function and not do -1,1 in rand_float -def augment_rot_xy(grid, random_rot_xy): + Returns: + randomly rotated grid + """ theta = np.deg2rad(rand_float(-random_rot_xy, random_rot_xy)) x = grid[2, :] * np.cos(theta) - grid[1, :] * np.sin(theta) y = grid[2, :] * np.sin(theta) + grid[1, :] * np.cos(theta) @@ -132,10 +173,18 @@ def augment_rot_xy(grid, random_rot_xy): return grid -""" Random tilt """ +def augment_rot_z(grid, random_rot_z): + """ + Random tilt around z-axis (dim-2) + Args: + grid: coordinate grid list of 3D points + random_rot_z: this is actually a boolean which triggers rotation + TODO: change this to just call the function and not do -1,1 in rand_float -def augment_rot_z(grid, random_rot_z): + Returns: + randomly tilted coordinate grid + """ theta = np.deg2rad(rand_float(-random_rot_z, random_rot_z)) z = grid[0, :] * np.cos(theta) - grid[1, :] * np.sin(theta) x = grid[0, :] * np.sin(theta) + grid[1, :] * np.cos(theta) @@ -144,18 +193,23 @@ def augment_rot_z(grid, random_rot_z): return grid -""" Linear interpolation """ +def trilinear_interpolation(input_array, indices): + """ + Linear interpolation + code taken from + http://stackoverflow.com/questions/6427276/3d-interpolation-of-numpy-arrays-without-scipy + Args: + input_array: 3D data array + indices: 3D grid coordinates -def trilinear_interpolation(input_array, indices): + Returns: + interpolated input array + """ - # http://stackoverflow.com/questions/6427276/3d-interpolation-of-numpy-arrays-without-scipy - output = np.empty(indices[0].shape) - x_indices = indices[0] - y_indices = indices[1] - z_indices = indices[2] + x_indices, y_indices, z_indices = indices[0:3] - N0, N1, N2 = input_array.shape + n0, n1, n2 = input_array.shape x0 = x_indices.astype(np.integer) y0 = y_indices.astype(np.integer) @@ -166,18 +220,18 @@ def trilinear_interpolation(input_array, indices): # put all samples outside datacube to 0 inds_out_of_range = ( - (x0 < 0) - | (x1 < 0) - | (y0 < 0) - | (y1 < 0) - | (z0 < 0) - | (z1 < 0) - | (x0 >= N0) - | (x1 >= N0) - | (y0 >= N1) - | (y1 >= N1) - | (z0 >= N2) - | (z1 >= N2) + (x0 < 0) + | (x1 < 0) + | (y0 < 0) + | (y1 < 0) + | (z0 < 0) + | (z1 < 0) + | (x0 >= n0) + | (x1 >= n0) + | (y0 >= n1) + | (y1 >= n1) + | (z0 >= n2) + | (z1 >= n2) ) x0[inds_out_of_range] = 0 @@ -191,35 +245,61 @@ def trilinear_interpolation(input_array, indices): y = y_indices - y0 z = z_indices - z0 output = ( - input_array[x0, y0, z0] * (1 - x) * (1 - y) * (1 - z) - + input_array[x1, y0, z0] * x * (1 - y) * (1 - z) - + input_array[x0, y1, z0] * (1 - x) * y * (1 - z) - + input_array[x0, y0, z1] * (1 - x) * (1 - y) * z - + input_array[x1, y0, z1] * x * (1 - y) * z - + input_array[x0, y1, z1] * (1 - x) * y * z - + input_array[x1, y1, z0] * x * y * (1 - z) - + input_array[x1, y1, z1] * x * y * z + input_array[x0, y0, z0] * (1 - x) * (1 - y) * (1 - z) + + input_array[x1, y0, z0] * x * (1 - y) * (1 - z) + + input_array[x0, y1, z0] * (1 - x) * y * (1 - z) + + input_array[x0, y0, z1] * (1 - x) * (1 - y) * z + + input_array[x1, y0, z1] * x * (1 - y) * z + + input_array[x0, y1, z1] * (1 - x) * y * z + + input_array[x1, y1, z0] * x * y * (1 - z) + + input_array[x1, y1, z1] * x * y * z ) output[inds_out_of_range] = 0 return output -""" Functions to get random variables: """ +def rand_float(low, high): + """ + Generate random floating point number between two limits + Args: + low: low limit + high: high limit -def rand_float(low, high): + Returns: + single random floating point number + """ return (high - low) * np.random.random_sample() + low def rand_int(low, high): + """ + Generate random integer between two limits + + Args: + low: low limit + high: high limit + + Returns: + random integer between two limits + """ return np.random.randint(low, high) def rand_bool(): + """ + Generate random boolean. + + Returns: + Random boolean + """ return bool(np.random.randint(0, 2)) +""" +TODO: the following is not needed and should be added as tests later. + # Test the batch-functions if __name__ == "__main__": from data import readSEGY, readLabels, get_slice @@ -264,3 +344,4 @@ def rand_bool(): logger.log_images("not salt", batch[16:, :, :, :, :]) logger.log_images("data", data[:, :, 50]) +""" \ No newline at end of file From c0ea6caf6f8a2f618b277d6c480907b452e5534b Mon Sep 17 00:00:00 2001 From: masalvar Date: Fri, 30 Aug 2019 15:11:11 +0000 Subject: [PATCH 033/207] Working HRNet --- .../segmentation/dutchf3/distributed/train.py | 121 ++++------ .../dutchf3/local/configs/hrnet.yaml | 4 +- .../segmentation/dutchf3/local/test.py | 54 +++-- .../segmentation/dutchf3/local/test.sh | 2 +- .../segmentation/dutchf3/local/train.py | 11 +- .../segmentation/dutchf3/prepare_data.py | 207 ++++++++++++++++++ 6 files changed, 294 insertions(+), 105 deletions(-) create mode 100644 interpretation/experiments/segmentation/dutchf3/prepare_data.py diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/train.py b/interpretation/experiments/segmentation/dutchf3/distributed/train.py index caee149c..a0ae7412 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/train.py +++ b/interpretation/experiments/segmentation/dutchf3/distributed/train.py @@ -75,6 +75,7 @@ Resize, PadIfNeeded, ) +from os import path import cv2 @@ -104,7 +105,6 @@ def run(*options, cfg=None, local_rank=0): To see what options are available consult default.py cfg (str, optional): Location of config file to load. Defaults to None. """ - fraction_validation = 0.2 update_config(config, options=options, config_file=cfg) logging.config.fileConfig(config.LOG_CONFIG) logger = logging.getLogger(__name__) @@ -125,73 +125,39 @@ def run(*options, cfg=None, local_rank=0): scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - if local_rank == 0: # Only do this on local rank otherwise overwriting - # Generate the train and validation sets for the model: - split_non_overlapping_train_val( - config.TRAIN.STRIDE, - per_val=fraction_validation, - loader_type="patch", - ) # Setup Augmentations + basic_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ), + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] + ) if config.TRAIN.AUGMENTATION: train_aug = Compose( - # TODO: Should we apply std scaling? [ - Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, - ), + basic_aug, HorizontalFlip(p=0.5), - # GaussNoise(var_limit=(0.02) ** 2), - # Normalize( - # mean=(config.TRAIN.MEAN,), - # std=(config.TRAIN.STD,), - # max_pixel_value=1, - # ), - PadIfNeeded( - min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, - min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=255, - ), - ] - ) - val_aug = Compose( - # TODO: Should we apply std scaling? - [ - Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, - ), - # Normalize( - # mean=(config.TRAIN.MEAN,), - # std=(config.TRAIN.STD,), - # max_pixel_value=1, - # ), - PadIfNeeded( - min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, - min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=255, - ), ] ) + val_aug = basic_aug else: - train_aug = Compose( - [ - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, - ) - ] - ) - val_aug = train_aug + train_aug = val_aug = basic_aug TrainPatchLoader = get_train_loader(config) @@ -204,7 +170,6 @@ def run(*options, cfg=None, local_rank=0): ) logger.info(f"Training examples {len(train_set)}") - # Without Augmentation: val_set = TrainPatchLoader( split="val", is_transform=True, @@ -314,7 +279,7 @@ def _select_pred_and_mask(model_out_dict): model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze(), ) - # Need to fix so that the mask isn't used + evaluator = create_supervised_evaluator( model, prepare_batch, @@ -328,6 +293,12 @@ def _select_pred_and_mask(model_out_dict): config.VALIDATION.BATCH_SIZE_PER_GPU, output_transform=_select_pred_and_mask, ), + "mca": apex.MeanClassAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + "fiou": apex.FrequencyWeightedIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), "pixa": apex.PixelwiseAccuracy( n_classes, device, output_transform=_select_pred_and_mask ), @@ -341,15 +312,17 @@ def _select_pred_and_mask(model_out_dict): ) if local_rank == 0: # Run only on master process - tboard_log_dir = generate_path( - config.LOG_DIR, + output_dir = generate_path( + config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(), ) - logger.info(f"Logging Tensorboard to {tboard_log_dir}") - summary_writer = create_summary_writer(log_dir=tboard_log_dir) + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + logger.info(f"Logging Tensorboard to {path.join(output_dir, config.LOG_DIR)}") trainer.add_event_handler( Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), @@ -366,6 +339,8 @@ def _select_pred_and_mask(model_out_dict): "IoU": "IoU :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :", + "mca": "Mean Class Accuracy :", + "fiou": "Freq Weighted IoU :", }, ), ) @@ -378,6 +353,8 @@ def _select_pred_and_mask(model_out_dict): metrics_dict={ "IoU": "Validation/IoU", "nll": "Validation/Loss", + "mca": "Validation/MCA", + "fiou": "Validation/FIoU", }, ), ) @@ -387,7 +364,7 @@ def _select_max(pred_tensor): def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() - #TODO: Add removal of border on all images + transform_func = compose( np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy ) @@ -420,15 +397,9 @@ def _tensor_to_numpy(pred_tensor): def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) + checkpoint_handler = SnapshotHandler( - output_dir, + path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, snapshot_function, ) @@ -436,7 +407,7 @@ def snapshot_function(): Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} ) - logger = logging.getLogger(__name__) + logger.info("Starting training") trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml index a2491511..2759eca2 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml @@ -73,7 +73,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "patch" # Options are No, Patch and Section + DEPTH: "section" #"patch" # Options are No, Patch and Section STRIDE: 50 PATCH_SIZE: 100 AUGMENTATIONS: @@ -92,7 +92,7 @@ VALIDATION: BATCH_SIZE_PER_GPU: 32 TEST: - MODEL_PATH: "" + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/5cc37bbe5302e1989ef1388d629400a16f82d1a9/seg_hrnet/Aug29_154238/models/seg_hrnet_running_model_16.pth" TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.py b/interpretation/experiments/segmentation/dutchf3/local/test.py index ccca4ee6..6686f091 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/test.py +++ b/interpretation/experiments/segmentation/dutchf3/local/test.py @@ -57,10 +57,9 @@ from cv_lib.segmentation.dutchf3.data import ( decode_segmap, get_train_loader, - split_non_overlapping_train_val, - split_train_val, section_loader, - add_patch_depth_channels + add_patch_depth_channels, + get_seismic_labels ) from cv_lib.segmentation.dutchf3.engine import ( create_supervised_evaluator, @@ -165,9 +164,9 @@ def _extract_patch(img_p, hdx, wdx, ps, patch_size, aug=None): ] if aug is not None: # TODO: Make depth optional from config - # patch = add_patch_depth_channels(aug(image=patch.numpy())['image']) - patch = aug(image=patch.numpy())['image'] - return torch.from_numpy(patch).to(torch.float32).unsqueeze(0) + patch = add_patch_depth_channels(aug(image=patch.numpy())['image']) + # patch = aug(image=patch.numpy())['image'] + return torch.from_numpy(patch).to(torch.float32)#.unsqueeze(0) else: return patch.unsqueeze(dim=0) @@ -233,18 +232,15 @@ def patch_label_2d(model, img, patch_size, stride, config, device): batch_indexes, model_output.detach().cpu() ): output=output.unsqueeze(0) - if config.TRAIN.AUGMENTATIONS.PAD.HEIGHT > 0: - height_diff = (config.TRAIN.AUGMENTATIONS.PAD.HEIGHT - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT)//2 - output = output[ + # TODO: This is specific to hrnet need to make general + output=F.interpolate(output, size=(128, 128), mode="bilinear") + output = output[ :, :, - height_diff : output.shape[2]-height_diff, - height_diff : output.shape[3]-height_diff, - ] - - if config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT > 0: - output=F.interpolate(output, size=(patch_size, patch_size), mode="bilinear") - + 14 : output.shape[2]-14, + 14 : output.shape[3]-14, + ] + output_p[ :, :, @@ -257,6 +253,28 @@ def patch_label_2d(model, img, patch_size, stride, config, device): return output + + +from PIL import Image +@curry +def to_image(label_mask, n_classes=6): + label_colours = get_seismic_labels() + r = label_mask.copy() + g = label_mask.copy() + b = label_mask.copy() + for ll in range(0, n_classes): + r[label_mask == ll] = label_colours[ll, 0] + g[label_mask == ll] = label_colours[ll, 1] + b[label_mask == ll] = label_colours[ll, 2] + rgb = np.zeros( + (label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3) + ) + rgb[:, :, :, 0] = r + rgb[:, :, :, 1] = g + rgb[:, :, :, 2] = b + return rgb + + def test(*options, cfg=None): update_config(config, options=options, config_file=cfg) logging.config.fileConfig(config.LOG_CONFIG) @@ -340,9 +358,9 @@ def test(*options, cfg=None): gt = labels.numpy() running_metrics_split.update(gt, pred) running_metrics_overall.update(gt, pred) - decoded = decode_segmap(pred) - # get scores and save in writer() + + # get scores score, class_iou = running_metrics_split.get_scores() # Add split results to TB: diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.sh b/interpretation/experiments/segmentation/dutchf3/local/test.sh index 4c09072f..106c0093 100755 --- a/interpretation/experiments/segmentation/dutchf3/local/test.sh +++ b/interpretation/experiments/segmentation/dutchf3/local/test.sh @@ -1,3 +1,3 @@ #!/bin/bash export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python test.py --cfg "configs/patch_deconvnet.yaml" \ No newline at end of file +python test.py --cfg "configs/hrnet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/train.py b/interpretation/experiments/segmentation/dutchf3/local/train.py index 5fe763f2..d4ca3798 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/train.py +++ b/interpretation/experiments/segmentation/dutchf3/local/train.py @@ -31,9 +31,7 @@ from cv_lib.event_handlers.logging_handlers import Evaluator from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, create_summary_writer) -from cv_lib.segmentation.dutchf3.data import (decode_segmap, get_train_loader, - split_non_overlapping_train_val, - split_train_val) +from cv_lib.segmentation.dutchf3.data import (decode_segmap, get_train_loader) from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, create_supervised_trainer) from cv_lib.segmentation.dutchf3.metrics import (FrequencyWeightedIoU, @@ -66,7 +64,7 @@ def run(*options, cfg=None): To see what options are available consult default.py cfg (str, optional): Location of config file to load. Defaults to None. """ - fraction_validation = 0.2 + update_config(config, options=options, config_file=cfg) logging.config.fileConfig(config.LOG_CONFIG) logger = logging.getLogger(__name__) @@ -79,11 +77,6 @@ def run(*options, cfg=None): torch.cuda.manual_seed_all(config.SEED) np.random.seed(seed=config.SEED) - # Generate the train and validation sets for the model: - split_non_overlapping_train_val( - config.TRAIN.STRIDE, per_val=fraction_validation, loader_type="patch" - ) - # Setup Augmentations basic_aug = Compose( [ diff --git a/interpretation/experiments/segmentation/dutchf3/prepare_data.py b/interpretation/experiments/segmentation/dutchf3/prepare_data.py new file mode 100644 index 00000000..c30b8d20 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/prepare_data.py @@ -0,0 +1,207 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 +# url: https://github.com/olivesgatech/facies_classification_benchmark +"""Script to generate train and validation sets for Netherlands F3 dataset +""" +import collections +import itertools +import json +import logging +import logging.config +import math +import os +import warnings +from os import path + +import fire +import numpy as np +import torch +from sklearn.model_selection import train_test_split +from toolz import curry +from torch.utils import data + + +def split_non_overlapping_train_val( + data_dir, stride, per_val=0.2, loader_type="patch" +): + # create inline and crossline pacthes for training and validation: + logger = logging.getLogger(__name__) + SPLITS = path.join(data_dir, "splits") + labels_path = path.join(data_dir, "train", "train_labels.npy") + + logger.info('Reading data from {data_dir}') + logger.info('Loading {labels_path}') + labels = np.load(labels_path) + iline, xline, depth = labels.shape + logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") + # INLINE PATCHES: ------------------------------------------------ + test_iline = math.floor(iline*0.1) + test_iline_range = itertools.chain(range(0, test_iline),range(iline-test_iline, iline)) + train_line_range = range(test_iline, iline-test_iline) + horz_locations = range(0, xline - stride, stride) + vert_locations = range(0, depth - stride, stride) + logger.debug("Generating Inline patches") + logger.debug(horz_locations) + logger.debug(vert_locations) + + def _i_extract_patches(iline_range, horz_locations, vert_locations): + for i in iline_range: + locations = ([j, k] for j in horz_locations for k in vert_locations) + for j, k in locations: + yield "i_" + str(i) + "_" + str(j) + "_" + str(k) + + test_i_list = list(_i_extract_patches(test_iline_range, horz_locations, vert_locations)) + train_i_list = list(_i_extract_patches(train_line_range, horz_locations, vert_locations)) + + + # XLINE PATCHES: ------------------------------------------------ + test_xline = math.floor(xline*0.1) + test_xline_range = itertools.chain(range(0, test_xline),range(xline-test_xline, xline)) + train_xline_range = range(test_xline, xline-test_xline) + + horz_locations = range(0, iline - stride, stride) + vert_locations = range(0, depth - stride, stride) + + def _x_extract_patches(xline_range, horz_locations, vert_locations): + for j in xline_range: + locations = ([i, k] for i in horz_locations for k in vert_locations) + for i, k in locations: + yield "x_" + str(i) + "_" + str(j) + "_" + str(k) + + + test_x_list = list(_x_extract_patches(test_xline_range, horz_locations, vert_locations)) + train_x_list = list(_x_extract_patches(train_xline_range, horz_locations, vert_locations)) + + train_list = train_x_list + train_i_list + test_list = test_x_list + test_i_list + + # write to files to disk: + # NOTE: This isn't quite right we should calculate the patches again for the whole volume + file_object = open(path.join(SPLITS, loader_type + "_train_val.txt"), "w") + file_object.write("\n".join(train_list+test_list)) + file_object.close() + file_object = open(path.join(SPLITS, loader_type + "_train.txt"), "w") + file_object.write("\n".join(train_list)) + file_object.close() + file_object = open(path.join(SPLITS, loader_type + "_val.txt"), "w") + file_object.write("\n".join(test_list)) + file_object.close() + + + +def split_train_val( + data_dir, stride, per_val=0.2, loader_type="patch", labels_path=LABELS +): + warnings.warn("THIS CREATES OVERLAPPING TRAINING AND VALIDATION SETS") + # create inline and crossline pacthes for training and validation: + logger = logging.getLogger(__name__) + SPLITS = path.join(data_dir, "splits") + labels_path = path.join(data_dir, "train", "train_labels.npy") + + logger.info('Reading data from {data_dir}') + logger.info('Loading {labels_path}') + labels = np.load(labels_path) + iline, xline, depth = labels.shape + logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") + + # INLINE PATCHES: ------------------------------------------------ + i_list = [] + horz_locations = range(0, xline - stride, stride) + vert_locations = range(0, depth - stride, stride) + logger.debug("Generating Inline patches") + logger.debug(horz_locations) + logger.debug(vert_locations) + for i in range(iline): + # for every inline: + # images are references by top-left corner: + locations = [[j, k] for j in horz_locations for k in vert_locations] + patches_list = [ + "i_" + str(i) + "_" + str(j) + "_" + str(k) for j, k in locations + ] + i_list.append(patches_list) + + # flatten the list + i_list = list(itertools.chain(*i_list)) + + # XLINE PATCHES: ------------------------------------------------ + x_list = [] + horz_locations = range(0, iline - stride, stride) + vert_locations = range(0, depth - stride, stride) + for j in range(xline): + # for every xline: + # images are references by top-left corner: + locations = [[i, k] for i in horz_locations for k in vert_locations] + patches_list = [ + "x_" + str(i) + "_" + str(j) + "_" + str(k) for i, k in locations + ] + x_list.append(patches_list) + + # flatten the list + x_list = list(itertools.chain(*x_list)) + + list_train_val = i_list + x_list + + # create train and test splits: + list_train, list_val = train_test_split( + list_train_val, test_size=per_val, shuffle=True + ) + + # write to files to disk: + file_object = open(path.join(SPLITS, loader_type + "_train_val.txt"), "w") + file_object.write("\n".join(list_train_val)) + file_object.close() + file_object = open(path.join(SPLITS, loader_type + "_train.txt"), "w") + file_object.write("\n".join(list_train)) + file_object.close() + file_object = open(path.join(SPLITS, loader_type + "_val.txt"), "w") + file_object.write("\n".join(list_val)) + file_object.close() + + +def split_non_overlapping(stride, fraction_validation = 0.2, log_config=None): + """Generate train and validation files of non-overlapping segments + + Args: + stride (int): stride to use when sectioning of the volume + fraction_validation (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + log_config ([type], optional): path to log config. Defaults to None. + """ + + if log_config is not None: + logging.config.fileConfig(config.LOG_CONFIG) + + split_non_overlapping_train_val( + stride, per_val=fraction_validation, loader_type="patch" + ) + + +def split_overlapping(stride, fraction_validation = 0.2, log_config=None): + """Generate train and validation files of segments + This is the original spliting method from https://github.com/olivesgatech/facies_classification_benchmark + DON'T USE SEE NOTES BELOW + + Args: + stride (int): stride to use when sectioning of the volume + fraction_validation (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + log_config ([type], optional): path to log config. Defaults to None. + + Notes: + Only kept for reproducibility. It generates overlapping train and val which makes validation results unreliable + """ + if log_config is not None: + logging.config.fileConfig(config.LOG_CONFIG) + + split_train_val( + stride, per_val=fraction_validation, loader_type="patch" + ) + + +if __name__=="__main__": + """Example: + python prepare_data.py split_non_overlapping /mnt/dutchf3 50 --log-config=logging.conf + """ + fire.Fire({ + 'split_non_overlapping': split_non_overlapping, + 'split_overlapping': split_overlapping, + }) From 63cb4d5e7db1614d38555af61c9719f45175ebe1 Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Sun, 1 Sep 2019 22:55:53 -0400 Subject: [PATCH 034/207] 3D SEG: finished going through Waldeland code --- interpretation/voxel2pixel/README.md | 7 +- interpretation/voxel2pixel/__init__.py | 0 interpretation/voxel2pixel/batch.py | 72 ++++---- interpretation/voxel2pixel/data.py | 129 +++++++++++--- interpretation/voxel2pixel/tb_logger.py | 82 +++++++-- interpretation/voxel2pixel/test_parallel.py | 47 ++--- interpretation/voxel2pixel/texture_net.py | 90 +++++++++- interpretation/voxel2pixel/train.py | 93 +++++----- interpretation/voxel2pixel/utils.py | 181 ++++++++++++++------ scripts/get_F3_voxel.sh | 6 +- 10 files changed, 507 insertions(+), 200 deletions(-) delete mode 100644 interpretation/voxel2pixel/__init__.py diff --git a/interpretation/voxel2pixel/README.md b/interpretation/voxel2pixel/README.md index abd9e74d..a1b78b73 100644 --- a/interpretation/voxel2pixel/README.md +++ b/interpretation/voxel2pixel/README.md @@ -12,7 +12,12 @@ EAGE E-lecture which you can watch: [*Seismic interpretation with deep learning* ### Setup to get started - make sure you follow `SETUP.md` file in root of repo to install all the proper dependencies. - download the data by running `scrips/get_F3_voxel.sh` from the root of this repo, i.e. from DeepSeismic folder. -This will automatically download the required data and will place it in the `F3` subfolder of this repo. +This will download the training and validation labels/masks. +- to get the main input dataset which is the [Dutch F3 dataset](https://terranubis.com/datainfo/Netherlands-Offshore-F3-Block-Complete), +navigate to [MalenoV](https://github.com/bolgebrygg/MalenoV) project website and follow the links (which will lead to +[this](https://drive.google.com/drive/folders/0B7brcf-eGK8CbGhBdmZoUnhiTWs) download). Save this file as +`interpretation/voxel2pixel/F3/data.segy` + ### Monitoring progress with TensorBoard - from the `voxel2pixel` directory, run `tensorboard --logdir='log'` (all runtime logging information is diff --git a/interpretation/voxel2pixel/__init__.py b/interpretation/voxel2pixel/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/interpretation/voxel2pixel/batch.py b/interpretation/voxel2pixel/batch.py index d3a9485b..4947a08a 100644 --- a/interpretation/voxel2pixel/batch.py +++ b/interpretation/voxel2pixel/batch.py @@ -1,14 +1,20 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +# code modified from https://github.com/waldeland/CNN-for-ASI + import numpy as np + def get_random_batch( - data_cube, - label_coordinates, - im_size, - num_batch_size, - random_flip=False, - random_stretch=None, - random_rot_xy=None, - random_rot_z=None, + data_cube, + label_coordinates, + im_size, + num_batch_size, + random_flip=False, + random_stretch=None, + random_rot_xy=None, + random_rot_z=None, ): """ Returns a batch of augmented samples with center pixels randomly drawn from label_coordinates @@ -65,7 +71,7 @@ def get_random_batch( # Pick random location from the label_coordinates for this class: coords_for_class = label_coordinates[class_keys[class_ind]] random_index = rand_int(0, coords_for_class.shape[1]) - coord = coords_for_class[:, random_index: random_index + 1] + coord = coords_for_class[:, random_index : random_index + 1] # Move grid to be centered around this location grid += coord @@ -220,18 +226,18 @@ def trilinear_interpolation(input_array, indices): # put all samples outside datacube to 0 inds_out_of_range = ( - (x0 < 0) - | (x1 < 0) - | (y0 < 0) - | (y1 < 0) - | (z0 < 0) - | (z1 < 0) - | (x0 >= n0) - | (x1 >= n0) - | (y0 >= n1) - | (y1 >= n1) - | (z0 >= n2) - | (z1 >= n2) + (x0 < 0) + | (x1 < 0) + | (y0 < 0) + | (y1 < 0) + | (z0 < 0) + | (z1 < 0) + | (x0 >= n0) + | (x1 >= n0) + | (y0 >= n1) + | (y1 >= n1) + | (z0 >= n2) + | (z1 >= n2) ) x0[inds_out_of_range] = 0 @@ -245,14 +251,14 @@ def trilinear_interpolation(input_array, indices): y = y_indices - y0 z = z_indices - z0 output = ( - input_array[x0, y0, z0] * (1 - x) * (1 - y) * (1 - z) - + input_array[x1, y0, z0] * x * (1 - y) * (1 - z) - + input_array[x0, y1, z0] * (1 - x) * y * (1 - z) - + input_array[x0, y0, z1] * (1 - x) * (1 - y) * z - + input_array[x1, y0, z1] * x * (1 - y) * z - + input_array[x0, y1, z1] * (1 - x) * y * z - + input_array[x1, y1, z0] * x * y * (1 - z) - + input_array[x1, y1, z1] * x * y * z + input_array[x0, y0, z0] * (1 - x) * (1 - y) * (1 - z) + + input_array[x1, y0, z0] * x * (1 - y) * (1 - z) + + input_array[x0, y1, z0] * (1 - x) * y * (1 - z) + + input_array[x0, y0, z1] * (1 - x) * (1 - y) * z + + input_array[x1, y0, z1] * x * (1 - y) * z + + input_array[x0, y1, z1] * (1 - x) * y * z + + input_array[x1, y1, z0] * x * y * (1 - z) + + input_array[x1, y1, z1] * x * y * z ) output[inds_out_of_range] = 0 @@ -302,12 +308,12 @@ def rand_bool(): # Test the batch-functions if __name__ == "__main__": - from data import readSEGY, readLabels, get_slice + from data import read_segy, read_labels, get_slice import tb_logger import numpy as np import os - data, data_info = readSEGY(os.path.join("F3", "data.segy")) + data, data_info = read_segy(os.path.join("F3", "data.segy")) train_coordinates = {"1": np.expand_dims(np.array([50, 50, 50]), 1)} @@ -336,7 +342,7 @@ def rand_bool(): ) logger.log_images("dip", batch) - train_cls_imgs, train_coordinates = readLabels( + train_cls_imgs, train_coordinates = read_labels( os.path.join("F3", "train"), data_info ) [batch, labels] = get_random_batch(data, train_coordinates, 65, 32) @@ -344,4 +350,4 @@ def rand_bool(): logger.log_images("not salt", batch[16:, :, :, :, :]) logger.log_images("data", data[:, :, 50]) -""" \ No newline at end of file +""" diff --git a/interpretation/voxel2pixel/data.py b/interpretation/voxel2pixel/data.py index effa08ca..06505716 100644 --- a/interpretation/voxel2pixel/data.py +++ b/interpretation/voxel2pixel/data.py @@ -1,4 +1,8 @@ -# Compatability Imports +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +# code modified from https://github.com/waldeland/CNN-for-ASI + from __future__ import print_function from os.path import isfile, join @@ -8,7 +12,17 @@ import scipy.misc -def readSEGY(filename): +def read_segy(filename): + """ + Read in a SEGY-format file given a filename + + Args: + filename: input filename + + Returns: + numpy data array and its info as a dictionary (tuple) + + """ print("Loading data cube from", filename, "with:") # Read full data cube @@ -17,7 +31,7 @@ def readSEGY(filename): # Put temporal axis first data = np.moveaxis(data, -1, 0) - # Make data cube fast to acess + # Make data cube fast to access data = np.ascontiguousarray(data, "float32") # Read meta data @@ -27,18 +41,30 @@ def readSEGY(filename): print(" Timeslices: ", "1", ":", data.shape[0]) # Make dict with cube-info - data_info = {} - data_info["crossline_start"] = segyfile.xlines[0] - data_info["inline_start"] = segyfile.ilines[0] - data_info["timeslice_start"] = 1 # Todo: read this from segy - data_info["shape"] = data.shape + # TODO: read this from segy # Read dt and other params needed to do create a new + data_info = { + "crossline_start": segyfile.xlines[0], + "inline_start": segyfile.ilines[0], + "timeslice_start": 1, + "shape": data.shape, + } return data, data_info -# Writes out_cube to a segy-file (out_filename) with same header/size as in_filename -def writeSEGY(out_filename, in_filename, out_cube): +def write_segy(out_filename, in_filename, out_cube): + """ + Writes out_cube to a segy-file (out_filename) with same header/size as in_filename + + Args: + out_filename: + in_filename: + out_cube: + + Returns: + + """ # Select last channel if type(out_cube) is list: out_cube = out_cube[-1] @@ -61,6 +87,7 @@ def writeSEGY(out_filename, in_filename, out_cube): iline = out_cube[i - iline_start, :, :] src.iline[i] = np.ascontiguousarray(iline.astype(dtype)) + # TODO: rewrite this whole function - this is terrible # Moving temporal axis first again - just in case the user want to keep working on it out_cube = np.moveaxis(out_cube, -1, 0) @@ -73,8 +100,18 @@ def writeSEGY(out_filename, in_filename, out_cube): crossline_alias = ["crossline", "cross-line", "xline", "x"] timeslice_alias = ["timeslice", "time-slice", "t", "z", "depthslice", "depth"] -# Read labels from an image -def readLabels(foldername, data_info): + +def read_labels(foldername, data_info): + """ + Read labels from an image. + + Args: + foldername: folder which contains the image. + data_info: dictionary describing the data + + Returns: + list of labels and list of coordinates + """ files = [f for f in listdir(foldername) if isfile(join(foldername, f))] label_imgs = [] @@ -117,7 +154,7 @@ def readLabels(foldername, data_info): img = interpolate_to_fit_data( img, slice_type, slice_no, data_info ) - label_img = parseLabelsInImage(img) + label_img = parse_labels_in_image(img) # Get coordinates for slice coords = get_coordinates_for_slice( @@ -163,8 +200,17 @@ def readLabels(foldername, data_info): [255, 255, 0], # yellow ] -# Convert RGB image to class img -def parseLabelsInImage(img): + +def parse_labels_in_image(img): + """ + Convert RGB image to class img. + + Args: + img: 3-channel image array + + Returns: + monotonically increasing class labels + """ label_img = np.int16(img[:, :, 0]) * 0 - 1 # -1 = no class # decompose color channels (#Alpha is ignored) @@ -195,8 +241,21 @@ def parseLabelsInImage(img): return label_img -# Function to resize image if needed def interpolate_to_fit_data(img, slice_type, slice_no, data_info): + """ + Function to resize image if needed + + Args: + img: image array + slice_type: inline, crossline or timeslice slice type + slice_no: slice number + data_info: data info dictionary distracted from SEGY file + + Returns: + resized image array + + """ + # Get wanted output size if slice_type == "inline": n0 = data_info["shape"][0] @@ -210,8 +269,20 @@ def interpolate_to_fit_data(img, slice_type, slice_no, data_info): return scipy.misc.imresize(img, (n0, n1), interp="nearest") -# Get coordinates for slice in the full cube def get_coordinates_for_slice(slice_type, slice_no, data_info): + """ + + Get coordinates for slice in the full cube + + Args: + slice_type: type of slice, e.g. inline, crossline, etc + slice_no: slice number + data_info: data dictionary array + + Returns: + index coordinates of the voxel + + """ ds = data_info["shape"] # Coordinates for cube @@ -251,22 +322,32 @@ def get_coordinates_for_slice(slice_type, slice_no, data_info): return coords -# Return data-slice def get_slice(data, data_info, slice_type, slice_no, window=0): + """ + Return data-slice + + Args: + data: input 3D voxel numpy array + data_info: data info dictionary + slice_type: type of slice, like inline, crossline, etc + slice_no: slice number + window: window size around center pixel + + Returns: + 2D slice of the voxel as a numpy array + + """ if slice_type == "inline": start = data_info["inline_start"] - slice_no = slice_no - start - slice = data[:, slice_no - window : slice_no + window + 1, :] elif slice_type == "crossline": start = data_info["crossline_start"] - slice_no = slice_no - start - slice = data[:, slice_no - window : slice_no + window + 1, :] elif slice_type == "timeslice": start = data_info["timeslice_start"] - slice_no = slice_no - start - slice = data[:, slice_no - window : slice_no + window + 1, :] + + slice_no = slice_no - start + slice = data[:, slice_no - window : slice_no + window + 1, :] return np.squeeze(slice) diff --git a/interpretation/voxel2pixel/tb_logger.py b/interpretation/voxel2pixel/tb_logger.py index ce0cf0d4..64af80f0 100644 --- a/interpretation/voxel2pixel/tb_logger.py +++ b/interpretation/voxel2pixel/tb_logger.py @@ -1,7 +1,12 @@ -# Compatability Imports +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +# code modified from https://github.com/waldeland/CNN-for-ASI + from __future__ import print_function from os.path import join +# TODO: make this nicer and remove the non-bare except for PEP8 compliance try: import tensorflow as tf except: @@ -15,8 +20,12 @@ import torch import datetime - +# TODO: it looks like the majority of the methods of this class are static and as such they should be in utils class TBLogger(object): + """ + TensorBoard logger class + """ + def __init__(self, log_dir, folder_name=""): self.log_dir = join( @@ -28,15 +37,34 @@ def __init__(self, log_dir, folder_name=""): self.log_dir = self.log_dir.replace("//", "/") self.writer = tf.summary.FileWriter(self.log_dir) - # Add scalar def log_scalar(self, tag, value, step=0): + """ + Add scalar + + Args: + tag: tag + value: simple_value + step: step + + """ summary = tf.Summary( value=[tf.Summary.Value(tag=tag, simple_value=value)] ) self.writer.add_summary(summary, step) - def make_list_of_2D_array(self, im): - if type(im) == type([]): + # TODO: this should probably be a static method - take care of this when re-writing the whole thing + def make_list_of_2d_array(self, im): + """ + Flatten 2D array to a list + + Args: + im: image + + Returns: + Flattened image list + + """ + if isinstance(im, list): return im ims = [] if len(im.shape) == 2: @@ -51,23 +79,32 @@ def make_list_of_2D_array(self, im): return ims def log_images(self, tag, images, step=0, dim=2, max_imgs=50, cm="jet"): + """ + Log images to TensorBoard + + Args: + tag: image tag + images: list of images + step: training step + dim: image shape (3 for voxel) + max_imgs: max number of images + cm: colormap + + """ # Make sure images are on numpy format in case the input is a Torch-variable images = self.convert_to_numpy(images) - try: - if len(images.shape) > 2: - dim = 3 - except: - None + if len(images.shape) > 2: + dim = 3 # Make list of images if dim == 2: - images = self.make_list_of_2D_array(images) + images = self.make_list_of_2d_array(images) # If 3D we make one list for each slice-type if dim == 3: - new_images_ts, new_images_il, new_images_cl = self.get_slices_from_3D( + new_images_ts, new_images_il, new_images_cl = self.get_slices_from_3d( images ) self.log_images( @@ -114,8 +151,15 @@ def log_images(self, tag, images, step=0, dim=2, max_imgs=50, cm="jet"): summary = tf.Summary(value=im_summaries) self.writer.add_summary(summary, step) - # Cuts out middle slices from image - def get_slices_from_3D(self, img): + # TODO: probably another static method + def get_slices_from_3d(self, img): + """ + Cuts out middle slices from image + + Args: + img: image array + + """ new_images_ts = [] new_images_il = [] @@ -152,8 +196,16 @@ def get_slices_from_3D(self, img): return new_images_ts, new_images_il, new_images_cl - # Convert torch to numpy + # TODO: another static method most likely def convert_to_numpy(self, im): + """ + Convert torch to numpy + + Args: + im: image array + + """ + if type(im) == torch.autograd.Variable: # Put on CPU im = im.cpu() diff --git a/interpretation/voxel2pixel/test_parallel.py b/interpretation/voxel2pixel/test_parallel.py index 08945e04..d20ca6df 100644 --- a/interpretation/voxel2pixel/test_parallel.py +++ b/interpretation/voxel2pixel/test_parallel.py @@ -1,4 +1,7 @@ -# Compatability Imports +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +# code modified from https://github.com/waldeland/CNN-for-ASI from __future__ import print_function import os @@ -34,12 +37,12 @@ import multiprocessing from os.path import join -from data import readSEGY, get_slice +from data import read_segy, get_slice from texture_net import TextureNet import itertools import numpy as np import tb_logger -from data import writeSEGY +from data import write_segy # graphical progress bar from tqdm import tqdm @@ -54,8 +57,8 @@ def __init__(self, texture_model): super(ModelWrapper, self).__init__() self.texture_model = texture_model - def forward(self, input): - return self.texture_model.classify(input) + def forward(self, input_net): + return self.texture_model.classify(input_net) class MyDataset(Dataset): @@ -74,9 +77,9 @@ def __getitem__(self, index): x, y, z = pixel # TODO: current bottleneck - can we slice out voxels any faster small_cube = self.data[ - x - self.window : x + self.window + 1, - y - self.window : y + self.window + 1, - z - self.window : z + self.window + 1, + x - self.window: x + self.window + 1, + y - self.window: y + self.window + 1, + z - self.window: z + self.window + 1, ] return small_cube[np.newaxis, :, :, :], pixel @@ -122,12 +125,12 @@ def main_worker(gpu, ngpus_per_node, args): # set default GPU device for this worker torch.cuda.set_device(args.gpu) # set up device for the rest of the code - device = torch.device("cuda:" + str(args.gpu)) + local_device = torch.device("cuda:" + str(args.gpu)) # Load trained model (run train.py to create trained network = TextureNet(n_classes=N_CLASSES) model_state_dict = torch.load( - join(args.data, "saved_model.pt"), map_location=device + join(args.data, "saved_model.pt"), map_location=local_device ) network.load_state_dict(model_state_dict) network.eval() @@ -155,14 +158,14 @@ def main_worker(gpu, ngpus_per_node, args): # Read 3D cube # NOTE: we cannot pass this data manually as serialization of data into each python process is costly, # so each worker has to load the data on its own. - data, data_info = readSEGY(join(args.data, "data.segy")) + data, data_info = read_segy(join(args.data, "data.segy")) # Get half window size window = IM_SIZE // 2 # reduce data size for debugging if args.debug: - data = data[0 : 3 * window] + data = data[0: 3 * window] # generate full list of coordinates # memory footprint of this isn't large yet, so not need to wrap as a generator @@ -184,7 +187,7 @@ def main_worker(gpu, ngpus_per_node, args): # we only score first batch in debug mode if args.debug: - coord_list = coord_list[0 : args.batch_size] + coord_list = coord_list[0: args.batch_size] # prepare the data print("setup dataset") @@ -220,8 +223,8 @@ def main_worker(gpu, ngpus_per_node, args): with torch.no_grad(): print("no grad") for (chunk, pixel) in tqdm(my_loader): - input = chunk.cuda(args.gpu, non_blocking=True) - output = model(input) + data_input = chunk.cuda(args.gpu, non_blocking=True) + output = model(data_input) # save and deal with it later on CPU # we want to make sure order is preserved pixels_x += pixel[0].tolist() @@ -388,7 +391,7 @@ def main(): print("-- aggregating results --") # Read 3D cube - data, data_info = readSEGY(join(args.data, "data.segy")) + data, data_info = read_segy(join(args.data, "data.segy")) # Log to tensorboard - input slice logger = tb_logger.TBLogger("log", "Test") @@ -404,12 +407,12 @@ def main(): predictions = [] for i in range(args.world_size): with open("results_{}.json".format(i), "r") as f: - dict = json.load(f) + results_dict = json.load(f) - x_coords += dict["pixels_x"] - y_coords += dict["pixels_y"] - z_coords += dict["pixels_z"] - predictions += dict["preds"] + x_coords += results_dict["pixels_x"] + y_coords += results_dict["pixels_y"] + z_coords += results_dict["pixels_z"] + predictions += results_dict["preds"] """ So because of Python's GIL having multiple workers write to the same array is not efficient - basically @@ -446,7 +449,7 @@ def worker(classified_cube, coord): print("-- writing segy --") in_file = join(args.data, "data.segy".format(RESOLUTION)) out_file = join(args.data, "salt_{}.segy".format(RESOLUTION)) - writeSEGY(out_file, in_file, classified_cube) + write_segy(out_file, in_file, classified_cube) print("-- logging prediction --") # log prediction to tensorboard diff --git a/interpretation/voxel2pixel/texture_net.py b/interpretation/voxel2pixel/texture_net.py index 3f3ab3c5..95768383 100644 --- a/interpretation/voxel2pixel/texture_net.py +++ b/interpretation/voxel2pixel/texture_net.py @@ -1,3 +1,8 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +# code modified from https://github.com/waldeland/CNN-for-ASI + import torch from torch import nn @@ -9,10 +14,11 @@ def __init__(self, n_classes=2): super(TextureNet, self).__init__() # Network definition + # Parameters #in_channels, #out_channels, filter_size, stride (downsampling factor) self.net = nn.Sequential( nn.Conv3d( 1, 50, 5, 4, padding=2 - ), # Parameters #in_channels, #out_channels, filter_size, stride (downsampling factor) + ), nn.BatchNorm3d(50), # nn.Dropout3d() #Droput can be added like this ... nn.ReLU(), @@ -35,32 +41,112 @@ def __init__(self, n_classes=2): ) # The filter weights are by default initialized by random - # Is called to compute network output def forward(self, x): + """ + Is called to compute network output + + Args: + x: network input - torch tensor + + Returns: + output from the neural network + + """ return self.net(x) def classify(self, x): + """ + Classification wrapper + + Args: + x: input tensor for classification + + Returns: + classification result + + """ x = self.net(x) _, class_no = torch.max(x, 1, keepdim=True) return class_no # Functions to get output from intermediate feature layers def f1(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ return self.getFeatures(x, 0) def f2(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ return self.getFeatures(x, 1) def f3(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ return self.getFeatures(x, 2) def f4(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ return self.getFeatures(x, 3) def f5(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ return self.getFeatures(x, 4) def getFeatures(self, x, layer_no): + """ + Main call method to call the wrapped layers + + Args: + x: input tensor for classification + layer_no: number of hidden layer we want to extract + + Returns: + requested layer + + """ layer_indexes = [0, 3, 6, 9, 12] # Make new network that has the layers up to the requested output diff --git a/interpretation/voxel2pixel/train.py b/interpretation/voxel2pixel/train.py index 7b8981e7..e2e37679 100644 --- a/interpretation/voxel2pixel/train.py +++ b/interpretation/voxel2pixel/train.py @@ -1,57 +1,55 @@ -# Compatability Imports +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +# code modified from https://github.com/waldeland/CNN-for-ASI + from __future__ import print_function from os.path import join - import torch from torch import nn -import torch.nn.functional as F -from data import readSEGY, readLabels, get_slice +from data import read_segy, read_labels, get_slice from batch import get_random_batch from torch.autograd import Variable -from torch.utils.data import DataLoader, Dataset +from texture_net import TextureNet import tb_logger - - -import numpy as np -from utils import * - -# This is the network definition proposed in the paper +import utils # Parameters -dataset_name = "F3" -im_size = 65 -batch_size = ( - 32 -) # If you have a GPU with little memory, try reducing this to 16 (may degrade results) -use_gpu = True # Switch to toggle the use of GPU or not -log_tensorboard = True # Log progress on tensor board -if log_tensorboard: +DATASET_NAME = "F3" +IM_SIZE = 65 +# If you have a GPU with little memory, try reducing this to 16 (may degrade results) +BATCH_SIZE = 32 +# Switch to toggle the use of GPU or not +USE_GPU = True +# Log progress on tensor board +LOG_TENSORBOARD = True + +# the rest of the code +if LOG_TENSORBOARD: logger = tb_logger.TBLogger("log", "Train") -# See the texture_net.py file for the network configuration -from texture_net import TextureNet - +# This is the network definition proposed in the paper network = TextureNet(n_classes=2) -# Loss function -cross_entropy = nn.CrossEntropyLoss() # Softmax function is included +# Loss function - Softmax function is included +cross_entropy = nn.CrossEntropyLoss() # Optimizer to control step size in gradient descent optimizer = torch.optim.Adam(network.parameters()) # Transfer model to gpu -if use_gpu: +if USE_GPU and torch.cuda.is_available(): network = network.cuda() # Load the data cube and labels -data, data_info = readSEGY(join(dataset_name, "data.segy")) -train_class_imgs, train_coordinates = readLabels( - join(dataset_name, "train"), data_info +data, data_info = read_segy(join(DATASET_NAME, "data.segy")) +train_class_imgs, train_coordinates = read_labels( + join(DATASET_NAME, "train"), data_info ) -val_class_imgs, _ = readLabels(join(dataset_name, "val"), data_info) +val_class_imgs, _ = read_labels(join(DATASET_NAME, "val"), data_info) # Plot training/validation data with labels -if log_tensorboard: +if LOG_TENSORBOARD: for class_img in train_class_imgs + val_class_imgs: logger.log_images( class_img[1] + "_" + str(class_img[2]), @@ -63,7 +61,6 @@ class_img[0], ) - # Training loop for i in range(2000): @@ -72,8 +69,8 @@ [batch, labels] = get_random_batch( data, train_coordinates, - im_size, - batch_size, + IM_SIZE, + BATCH_SIZE, random_flip=True, random_stretch=0.2, random_rot_xy=180, @@ -85,7 +82,7 @@ labels = Variable(torch.Tensor(labels).long()) # Transfer data to gpu - if use_gpu: + if USE_GPU and torch.cuda.is_available(): batch = batch.cuda() labels = labels.cuda() @@ -109,16 +106,18 @@ network.eval() # Log to training loss/acc - print("Iteration:", i, "Training loss:", var_to_np(loss)) - if log_tensorboard: - logger.log_scalar("training_loss", var_to_np(loss), i) - for k, v in computeAccuracy(torch.argmax(output, 1), labels).items(): - if log_tensorboard: + print("Iteration:", i, "Training loss:", utils.var_to_np(loss)) + if LOG_TENSORBOARD: + logger.log_scalar("training_loss", utils.var_to_np(loss), i) + for k, v in utils.compute_accuracy( + torch.argmax(output, 1), labels + ).items(): + if LOG_TENSORBOARD: logger.log_scalar("training_" + k, v, i) print(" -", k, v, "%") # every 100th iteration - if i % 100 == 0 and log_tensorboard: + if i % 100 == 0 and LOG_TENSORBOARD: network.eval() # Output predicted train/validation class/probability images @@ -127,35 +126,35 @@ slice = class_img[1] slice_no = class_img[2] - class_img = interpret( + class_img = utils.interpret( network.classify, data, data_info, slice, slice_no, - im_size, + IM_SIZE, 16, return_full_size=True, - use_gpu=use_gpu, + use_gpu=USE_GPU, ) logger.log_images( slice + "_" + str(slice_no) + "_pred_class", class_img, i ) - class_img = interpret( + class_img = utils.interpret( network, data, data_info, slice, slice_no, - im_size, + IM_SIZE, 16, return_full_size=True, - use_gpu=use_gpu, + use_gpu=USE_GPU, ) logger.log_images( slice + "_" + str(slice_no) + "_pred_prob", class_img, i ) # Store trained network - torch.save(network.state_dict(), join(dataset_name, "saved_model.pt")) + torch.save(network.state_dict(), join(DATASET_NAME, "saved_model.pt")) diff --git a/interpretation/voxel2pixel/utils.py b/interpretation/voxel2pixel/utils.py index 68dccb5f..011b0041 100644 --- a/interpretation/voxel2pixel/utils.py +++ b/interpretation/voxel2pixel/utils.py @@ -1,10 +1,20 @@ -# Compatability Imports +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +# code modified from https://github.com/waldeland/CNN-for-ASI + from __future__ import print_function import torch import numpy as np from torch.autograd import Variable from scipy.interpolate import interpn +import sys +import time + +# global parameters +ST = 0 +LAST_UPDATE = 0 def interpret( @@ -18,17 +28,38 @@ def interpret( return_full_size=True, use_gpu=True, ): + """ + Down-samples a slice from the classified image and upsamples to full resolution if needed. Basically + given a full 3D-classified voxel at a particular resolution (say we classify every n-th pixel as given by the + subsampl variable below) we take a particular slice from the voxel and optoinally blow it up to full resolution + as if we classified every single pixel. + + Args: + network: pytorch model definition + data: input voxel + data_info: input voxel information + slice: slice type which we want to interpret + slice_no: slice number + im_size: size of the voxel + subsampl: at what resolution do we want to subsample, e.g. we move across every subsampl pixels + return_full_size: boolean flag, enable if you want to return full size without downsampling + use_gpu: boolean flag to use the GPU + + Returns: + upsampled slice + + """ + # Wrap np.linspace in compact function call - def ls(N): - return np.linspace(0, N - 1, N, dtype="int") + ls = lambda N: np.linspace(0, N - 1, N, dtype="int") # Size of cube - N0, N1, N2 = data.shape + n0, n1, n2 = data.shape # Coords for full cube - x0_range = ls(N0) - x1_range = ls(N1) - x2_range = ls(N2) + x0_range = ls(n0) + x1_range = ls(n1) + x2_range = ls(n2) # Coords for subsampled cube pred_points = ( @@ -59,6 +90,9 @@ def ls(N): x0_range = np.array([slice_no]) pred_points = (pred_points[1], pred_points[2]) + else: + class_cube = None + # Grid for small class slice/cube n0, n1, n2 = class_cube.shape x0_grid, x1_grid, x2_grid = np.meshgrid( @@ -66,43 +100,41 @@ def ls(N): ) # Grid for full slice/cube - X0_grid, X1_grid, X2_grid = np.meshgrid( + full_x0_grid, full_x1_grid, full_x2_grid = np.meshgrid( x0_range, x1_range, x2_range, indexing="ij" ) # Indexes for large cube at small cube pixels - X0_grid_sub = X0_grid[::subsampl, ::subsampl, ::subsampl] - X1_grid_sub = X1_grid[::subsampl, ::subsampl, ::subsampl] - X2_grid_sub = X2_grid[::subsampl, ::subsampl, ::subsampl] + full_x0_grid_sub = full_x0_grid[::subsampl, ::subsampl, ::subsampl] + full_x1_grid_sub = full_x1_grid[::subsampl, ::subsampl, ::subsampl] + full_x2_grid_sub = full_x2_grid[::subsampl, ::subsampl, ::subsampl] # Get half window size w = im_size // 2 # Loop through center pixels in output cube - for i in range(X0_grid_sub.size): + for i in range(full_x0_grid_sub.size): # Get coordinates in small and large cube x0 = x0_grid.ravel()[i] x1 = x1_grid.ravel()[i] x2 = x2_grid.ravel()[i] - X0 = X0_grid_sub.ravel()[i] - X1 = X1_grid_sub.ravel()[i] - X2 = X2_grid_sub.ravel()[i] + full_x0 = full_x0_grid_sub.ravel()[i] + full_x1 = full_x1_grid_sub.ravel()[i] + full_x2 = full_x2_grid_sub.ravel()[i] # Only compute when a full 65x65x65 cube can be extracted around center pixel if ( - X0 > w - and X1 > w - and X2 > w - and X0 < N0 - w + 1 - and X1 < N1 - w + 1 - and X2 < N2 - w + 1 + min(full_x0, full_x1, full_x2) > w + and full_x0 < n0 - w + 1 + and full_x1 < n1 - w + 1 + and full_x2 < n2 - w + 1 ): # Get mini-cube around center pixel mini_cube = data[ - X0 - w : X0 + w + 1, X1 - w : X1 + w + 1, X2 - w : X2 + w + 1 + full_x0 - w: full_x0 + w + 1, full_x1 - w: full_x1 + w + 1, full_x2 - w: full_x2 + w + 1 ] # Get predicted "probabilities" @@ -120,7 +152,7 @@ def ls(N): out = np.squeeze(out) # Make one output pr output channel - if type(class_cube) != type(list()): + if not isinstance(class_cube, list): class_cube = np.split( np.repeat(class_cube[:, :, :, np.newaxis], out.size, 3), out.size, @@ -136,37 +168,39 @@ def ls(N): # Keep user informed about progress if slice == "full": - printProgressBar(i, x0_grid.size) + print_progress_bar(i, x0_grid.size) # Resize to input size if return_full_size: if slice == "full": print("Interpolating down sampled results to fit input cube") - N = X0_grid.size + N = full_x0_grid.size # Output grid if slice == "full": grid_output_cube = np.concatenate( [ - X0_grid.reshape([N, 1]), - X1_grid.reshape([N, 1]), - X2_grid.reshape([N, 1]), + full_x0_grid.reshape([N, 1]), + full_x1_grid.reshape([N, 1]), + full_x2_grid.reshape([N, 1]), ], 1, ) elif slice == "inline": grid_output_cube = np.concatenate( - [X0_grid.reshape([N, 1]), X2_grid.reshape([N, 1])], 1 + [full_x0_grid.reshape([N, 1]), full_x2_grid.reshape([N, 1])], 1 ) elif slice == "crossline": grid_output_cube = np.concatenate( - [X0_grid.reshape([N, 1]), X1_grid.reshape([N, 1])], 1 + [full_x0_grid.reshape([N, 1]), full_x1_grid.reshape([N, 1])], 1 ) elif slice == "timeslice": grid_output_cube = np.concatenate( - [X1_grid.reshape([N, 1]), X2_grid.reshape([N, 1])], 1 + [full_x1_grid.reshape([N, 1]), full_x2_grid.reshape([N, 1])], 1 ) + else: + grid_output_cube = None # Interpolation for i in range(len(class_cube)): @@ -189,7 +223,7 @@ def ls(N): [x0_range.size, x1_range.size, x2_range.size] ) - # If ouput is class labels we convert the interpolated array to ints + # If output is class labels we convert the interpolated array to ints if is_int: class_cube[i] = class_cube[i].astype("int32") @@ -203,24 +237,32 @@ def ls(N): return class_cube -# Print progress information -import sys -import time - -st = 0 -last_update = 0 - -# Adapted from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console/14879561#14879561 -def printProgressBar( +# TODO: this should probably be replaced with TQDM +def print_progress_bar( iteration, total, prefix="", suffix="", decimals=1, length=100, fill="=" ): - global st, last_update + """ + Privides a progress bar implementation. + + Adapted from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console/14879561#14879561 + + Args: + iteration: iteration number + total: total number of iterations + prefix: comment prefix in display + suffix: comment suffix in display + decimals: how many decimals to display + length: character length of progress bar + fill: character to display as progress bar + + """ + global ST, LAST_UPDATE # Expect itteration to go from 0 to N-1 iteration = iteration + 1 # Only update every 5 second - if time.time() - last_update < 5: + if time.time() - LAST_UPDATE < 5: if iteration == total: time.sleep(1) else: @@ -232,13 +274,13 @@ def printProgressBar( exp_m = "" exp_s = "" elif iteration == total: - exp_time = time.time() - st + exp_time = time.time() - ST exp_h = int(exp_time / 3600) exp_m = int(exp_time / 60 - exp_h * 60.0) exp_s = int(exp_time - exp_m * 60.0 - exp_h * 3600.0) else: - exp_time = (time.time() - st) / (iteration - 1) * total - ( - time.time() - st + exp_time = (time.time() - ST) / (iteration - 1) * total - ( + time.time() - ST ) exp_h = int(exp_time / 3600) exp_m = int(exp_time / 60 - exp_h * 60.0) @@ -247,8 +289,8 @@ def printProgressBar( percent = ("{0:." + str(decimals) + "f}").format( 100 * (iteration / float(total)) ) - filledLength = int(length * iteration // total) - bar = fill * filledLength + "-" * (length - filledLength) + filled_length = int(length * iteration // total) + bar = fill * filled_length + "-" * (length - filled_length) if iteration != total: print( "\r%s |%s| %s%% %s - %sh %smin %ss left" @@ -262,12 +304,24 @@ def printProgressBar( sys.stdout.write("\033[F") # Print New Line on Complete if iteration == total: - print - last_update = time.time() + print("") + # last_update = time.time() -# Function that returns the GPU number of a variable/module (or False if on CPU) +# TODO: rewrite this whole function to get rid of excepts +# TODO: also not sure what this function is for - it's almost as if it's not needed - try to remove it. def gpu_no_of_var(var): + """ + Function that returns the GPU number or whether the tensor is on GPU or not + + Args: + var: torch tensor + + Returns: + The CUDA device that the torch tensor is on, or whether the tensor is on GPU + + """ + try: is_cuda = next(var.parameters()).is_cuda except: @@ -282,8 +336,18 @@ def gpu_no_of_var(var): return False -# Take a pytorch variable and make numpy +# TODO: remove all the try except statements def var_to_np(var): + """ + Take a pyTorch tensor and convert it to numpy array of the same shape, as the name suggests. + + Args: + var: input variable + + Returns: + numpy array of the tensor + + """ if type(var) in [np.array, np.ndarray]: return var @@ -312,7 +376,18 @@ def var_to_np(var): return var -def computeAccuracy(predicted_class, labels): +def compute_accuracy(predicted_class, labels): + """ + Accuracy performance metric which needs to be computed + + Args: + predicted_class: pyTorch tensor with predictions + labels: pyTorch tensor with ground truth labels + + Returns: + Accuracy calculation as a dictionary per class and average class accuracy across classes + + """ labels = var_to_np(labels) predicted_class = var_to_np(predicted_class) diff --git a/scripts/get_F3_voxel.sh b/scripts/get_F3_voxel.sh index 273e5e7d..4c432677 100755 --- a/scripts/get_F3_voxel.sh +++ b/scripts/get_F3_voxel.sh @@ -2,7 +2,7 @@ echo "Downloading Dutch F3 from https://github.com/bolgebrygg/MalenoV" # fetch Dutch F3 from Malenov project. -wget https://drive.google.com/open?id=0B7brcf-eGK8CUUZKLXJURFNYeXM interpretation/voxel2pixel/F3/data.segy -wget https://github.com/waldeland/CNN-for-ASI/raw/master/F3/train/inline_339.png interpretation/voxel2pixel/F3/train/inline_339.png -wget https://github.com/waldeland/CNN-for-ASI/raw/master/F3/val/inline_405.png interpretation/voxel2pixel/F3/val/inline_405.png +# wget https://drive.google.com/open?id=0B7brcf-eGK8CUUZKLXJURFNYeXM -O interpretation/voxel2pixel/F3/data.segy +wget https://github.com/waldeland/CNN-for-ASI/raw/master/F3/train/inline_339.png -O interpretation/voxel2pixel/F3/train/inline_339.png +wget https://github.com/waldeland/CNN-for-ASI/raw/master/F3/val/inline_405.png -O interpretation/voxel2pixel/F3/val/inline_405.png echo "Download complete" From 871ecf4bfdfe01a0ba45736cb5da8da6465f6f47 Mon Sep 17 00:00:00 2001 From: masalvar Date: Wed, 4 Sep 2019 10:42:58 +0000 Subject: [PATCH 035/207] Updates test scripts and makes it take processing arguments --- .../environment/anaconda/local/src/cv-lib | 2 +- .../dutchf3/local/configs/hrnet.yaml | 7 +- .../local/configs/patch_deconvnet.yaml | 4 +- .../dutchf3/local/configs/seresnet_unet.yaml | 9 +- .../segmentation/dutchf3/local/default.py | 3 + .../segmentation/dutchf3/local/run.sh | 3 +- .../segmentation/dutchf3/local/test.py | 511 +++++++++++------- .../segmentation/dutchf3/local/test.sh | 3 +- 8 files changed, 335 insertions(+), 207 deletions(-) diff --git a/interpretation/environment/anaconda/local/src/cv-lib b/interpretation/environment/anaconda/local/src/cv-lib index 7cde8158..2fa67f28 160000 --- a/interpretation/environment/anaconda/local/src/cv-lib +++ b/interpretation/environment/anaconda/local/src/cv-lib @@ -1 +1 @@ -Subproject commit 7cde8158edb8be836540b3a036b5e42988e38285 +Subproject commit 2fa67f280ebf0537f1a782097fb6808698911b5d diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml index 2759eca2..8543c27d 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml @@ -91,9 +91,12 @@ TRAIN: VALIDATION: BATCH_SIZE_PER_GPU: 32 -TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/5cc37bbe5302e1989ef1388d629400a16f82d1a9/seg_hrnet/Aug29_154238/models/seg_hrnet_running_model_16.pth" +TEST: + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/ccb7206b41dc7411609705e49d9f4c2d74c6eb88/seg_hrnet/Aug30_141919/models/seg_hrnet_running_model_18.pth" TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True CROSSLINE: True + POST_PROCESSING: # Expect output to be 64x64 and label to be 128x128 with 28 being padding + SIZE: 128 # + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml index 179e985f..9f59e687 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml @@ -54,4 +54,6 @@ TEST: SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True CROSSLINE: True - + POST_PROCESSING: # Expect output to be 64x64 and label to be 128x128 with 28 being padding + SIZE: 99 + CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml index a2e95ba9..d3f8be55 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml @@ -30,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "patch" # Options are No, Patch and Section + DEPTH: "section" # Options are No, Patch and Section STRIDE: 50 PATCH_SIZE: 100 AUGMENTATIONS: @@ -49,8 +49,11 @@ VALIDATION: BATCH_SIZE_PER_GPU: 32 TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/distributed/output/mat/exp/74d07f41ff0dc72920c279cc93f2eb1f42ed8a0d/resnet_unet/Aug26_165055/resnet_unet_running_model_31.pth" + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/dc2e2d20b7f6d508beb779ffff37c77d0139e588/resnet_unet/Sep01_125513/models/resnet_unet_snapshot1model_52.pth" TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True - CROSSLINE: True \ No newline at end of file + CROSSLINE: True + POST_PROCESSING: # Expect output to be 64x64 and label to be 128x128 with 28 being padding + SIZE: 128 + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/default.py b/interpretation/experiments/segmentation/dutchf3/local/default.py index 15608966..805eae0b 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/default.py +++ b/interpretation/experiments/segmentation/dutchf3/local/default.py @@ -83,6 +83,9 @@ _C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 _C.TEST.INLINE = True _C.TEST.CROSSLINE = True +_C.TEST.POST_PROCESSING = CN() # Model output postprocessing +_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels +_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right def update_config(cfg, options=None, config_file=None): diff --git a/interpretation/experiments/segmentation/dutchf3/local/run.sh b/interpretation/experiments/segmentation/dutchf3/local/run.sh index e0a67712..1568bc35 100755 --- a/interpretation/experiments/segmentation/dutchf3/local/run.sh +++ b/interpretation/experiments/segmentation/dutchf3/local/run.sh @@ -1,3 +1,4 @@ #!/bin/bash export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python train.py --cfg "configs/hrnet.yaml" \ No newline at end of file +export CUDA_VISIBLE_DEVICES=1 +python train.py --cfg "configs/seresnet_unet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.py b/interpretation/experiments/segmentation/dutchf3/local/test.py index 6686f091..42a6c128 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/test.py +++ b/interpretation/experiments/segmentation/dutchf3/local/test.py @@ -1,5 +1,5 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. # commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 # url: https://github.com/olivesgatech/facies_classification_benchmark @@ -9,6 +9,7 @@ """ import argparse +import itertools import logging import logging.config import os @@ -16,82 +17,51 @@ from os import path from os.path import join as pjoin +import cv2 import fire import numpy as np import torch import torch.nn.functional as F import torchvision.utils as vutils -from ignite.contrib.handlers import ( - ConcatScheduler, - CosineAnnealingScheduler, - CustomPeriodicEvent, - LinearCyclicalScheduler, -) +from albumentations import (Compose, GaussNoise, HorizontalFlip, Normalize, + PadIfNeeded, Resize) +from ignite.contrib.handlers import (ConcatScheduler, CosineAnnealingScheduler, + CustomPeriodicEvent, + LinearCyclicalScheduler) from ignite.engine import Events from ignite.metrics import Loss from ignite.utils import convert_tensor from sklearn.model_selection import train_test_split from tensorboardX import SummaryWriter -from toolz import compose, curry +from toolz import compose, curry, itertoolz, pipe +from toolz.itertoolz import take from torch.utils import data from tqdm import tqdm -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) +from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, + tensorboard_handlers) from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) -from albumentations import ( - Compose, - HorizontalFlip, - GaussNoise, - Normalize, - Resize, - PadIfNeeded, -) -from cv_lib.segmentation.dutchf3.data import ( - decode_segmap, - get_train_loader, - section_loader, - add_patch_depth_channels, - get_seismic_labels -) -from cv_lib.segmentation.dutchf3.engine import ( - create_supervised_evaluator, - create_supervised_trainer, -) -from cv_lib.segmentation.dutchf3.metrics import ( - MeanIoU, - PixelwiseAccuracy, - apex, -) -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, - np_to_tb, -) - get_data_ids, - get_distributed_data_loaders, - kfold_split, -) - create_supervised_evaluator, - create_supervised_trainer, -) +from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, + create_summary_writer) +from cv_lib.segmentation.dutchf3.data import (TestSectionLoaderWithDepth, + add_patch_depth_channels, + decode_segmap, + get_seismic_labels, + get_test_loader, + get_train_loader) +from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, + create_supervised_trainer) +from cv_lib.segmentation.dutchf3.metrics import (MeanIoU, PixelwiseAccuracy, + apex) +from cv_lib.segmentation.dutchf3.utils import (current_datetime, generate_path, + git_branch, git_hash, np_to_tb) + get_distributed_data_loaders, + kfold_split) + create_supervised_trainer) from default import _C as config from default import update_config -from toolz.itertoolz import take -import itertools -from toolz import itertoolz -import cv2 -class_names = [ +_CLASS_NAMES = [ "upper_ns", "middle_ns", "lower_ns", @@ -100,6 +70,8 @@ "zechstein", ] +DATA_ROOT = path.join("/mnt", "alaudah") + class runningScore(object): def __init__(self, n_classes): @@ -157,105 +129,178 @@ def reset(self): self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) +def _transform_CHW_to_HWC(numpy_array): + return np.moveaxis(numpy_array, 0, -1) + + +def _transform_HWC_to_CHW(numpy_array): + return np.moveaxis(numpy_array, -1, 0) + + +@curry +def _apply_augmentation3D(aug, numpy_array): + assert len(numpy_array.shape) == 3, "This method only accepts 3D arrays" + patch = _transform_CHW_to_HWC(numpy_array) + patch = aug(image=patch)["image"] + return _transform_HWC_to_CHW(patch) + + +@curry +def _apply_augmentation2D(aug, numpy_array): + assert len(numpy_array.shape) == 2, "This method only accepts 2D arrays" + return aug(image=numpy_array)["image"] + + +_AUGMENTATION = {3: _apply_augmentation3D, 2: _apply_augmentation2D} + + @curry -def _extract_patch(img_p, hdx, wdx, ps, patch_size, aug=None): - patch = img_p[ - hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size - ] +def _apply_augmentation(aug, image): + if isinstance(image, torch.Tensor): + image = image.numpy() + if aug is not None: - # TODO: Make depth optional from config - patch = add_patch_depth_channels(aug(image=patch.numpy())['image']) - # patch = aug(image=patch.numpy())['image'] - return torch.from_numpy(patch).to(torch.float32)#.unsqueeze(0) + return _AUGMENTATION[len(image.shape)](aug, image) else: - return patch.unsqueeze(dim=0) + return image -def _generate_batches( - img_p, h, w, ps, patch_size, stride, config, batch_size=64 -): +def _add_depth(image): + if isinstance(image, torch.Tensor): + image = image.numpy() + return add_patch_depth_channels(image) + + +def _to_torch(image): + if isinstance(image, torch.Tensor): + return image + else: + return torch.from_numpy(image).to(torch.float32) + + +def _expand_dims_if_necessary(torch_tensor): + if len(torch_tensor.shape) == 2: + return torch_tensor.unsqueeze(dim=0) + else: + return torch_tensor + + +@curry +def _extract_patch(hdx, wdx, ps, patch_size, img_p): + if len(img_p.shape) == 2: # 2D + return img_p[ + hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size + ] + else: # 3D + return img_p[ + :, + hdx + ps : hdx + ps + patch_size, + wdx + ps : wdx + ps + patch_size, + ] + + +def _compose_processing_pipeline(depth, aug=None): + steps = [] + if aug is not None: + steps.append(_apply_augmentation(aug)) + + if depth == "patch": + steps.append(_add_depth) + + steps.append(_to_torch) + steps.append(_expand_dims_if_necessary) + steps.reverse() + return compose(*steps) + + +def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): hdc_wdx_generator = itertools.product( range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride), ) - test_aug = Compose( - [ - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, - ), - Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, - ), - PadIfNeeded( - min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, - min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=255, - ), - ] - ) - for batch_indexes in itertoolz.partition_all( batch_size, hdc_wdx_generator ): - yield batch_indexes, torch.stack( - [ - _extract_patch(img_p, hdx, wdx, ps, patch_size, test_aug) - for hdx, wdx in batch_indexes - ], - dim=0, - ) + yield batch_indexes + +@curry +def _output_processing_pipeline(config, output): + output = output.unsqueeze(0) + _, _, h, w = output.shape + if config.TEST.POST_PROCESSING.SIZE != h or config.TEST.POST_PROCESSING.SIZE != w: + output = F.interpolate( + output, + size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE), + mode="bilinear", + ) -def patch_label_2d(model, img, patch_size, stride, config, device): + if config.TEST.POST_PROCESSING.CROP_PIXELS > 0: + _, _, h, w = output.shape + output = output[ + :, + :, + config.TEST.POST_PROCESSING.CROP_PIXELS : h - config.TEST.POST_PROCESSING.CROP_PIXELS, + config.TEST.POST_PROCESSING.CROP_PIXELS : w - config.TEST.POST_PROCESSING.CROP_PIXELS, + ] + return output.squeeze() + + +def _patch_label_2d( + model, + img, + pre_processing, + output_processing, + patch_size, + stride, + batch_size, + device, + num_classes, +): + """Processes a whole section + """ img = torch.squeeze(img) - h, w = img.shape # height and width + h, w = img.shape[-2], img.shape[-1] # height and width # Pad image with patch_size/2: ps = int(np.floor(patch_size / 2)) # pad size img_p = F.pad(img, pad=(ps, ps, ps, ps), mode="constant", value=0) - - num_classes = 6 output_p = torch.zeros([1, num_classes, h + 2 * ps, w + 2 * ps]) + # generate output: - for batch_indexes, batch in _generate_batches( - img_p, h, w, ps, patch_size, stride, config, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU + for batch_indexes in _generate_batches( + h, w, ps, patch_size, stride, batch_size=batch_size ): + batch = torch.stack( + [ + pipe( + img_p, + _extract_patch(hdx, wdx, ps, patch_size), + pre_processing, + ) + for hdx, wdx in batch_indexes + ], + dim=0, + ) + model_output = model(batch.to(device)) for (hdx, wdx), output in zip( batch_indexes, model_output.detach().cpu() ): - output=output.unsqueeze(0) - # TODO: This is specific to hrnet need to make general - output=F.interpolate(output, size=(128, 128), mode="bilinear") - output = output[ - :, - :, - 14 : output.shape[2]-14, - 14 : output.shape[3]-14, - ] - + output = output_processing(output) output_p[ :, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size, - ] += torch.squeeze(output) + ] += output - # crop the output_p in the middke + # crop the output_p in the middle output = output_p[:, :, ps:-ps, ps:-ps] return output - - -from PIL import Image @curry def to_image(label_mask, n_classes=6): label_colours = get_seismic_labels() @@ -269,19 +314,116 @@ def to_image(label_mask, n_classes=6): rgb = np.zeros( (label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3) ) - rgb[:, :, :, 0] = r + rgb[:, :, :, 0] = r rgb[:, :, :, 1] = g rgb[:, :, :, 2] = b return rgb +from PIL import Image +def save_image(numpy_array, n_classes, filename): + if len(numpy_array.shape)==4: + numpy_array=numpy_array.squeeze(0) + Image.fromarray(to_image(numpy_array, n_classes=n_classes).squeeze(0).astype('uint8')).save(filename) + +def _evaluate_split( + split, + section_aug, + model, + pre_processing, + output_processing, + device, + running_metrics_overall, + config, +): + logger = logging.getLogger(__name__) + + TestSectionLoader = get_test_loader(config) + test_set = TestSectionLoader( + split=split, is_transform=True, augmentations=section_aug + ) + + n_classes = test_set.n_classes + + test_loader = data.DataLoader( + test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False + ) + running_metrics_split = runningScore(n_classes) + + # testing mode: + with torch.no_grad(): # operations inside don't track history + model.eval() + total_iteration = 0 + for i, (images, labels) in enumerate(test_loader): + logger.info(f"split: {split}, section: {i}") + total_iteration = total_iteration + 1 + image_original, labels_original = images, labels + + outputs = _patch_label_2d( + model, + images, + pre_processing, + output_processing, + config.TRAIN.PATCH_SIZE, + config.TEST.TEST_STRIDE, + config.VALIDATION.BATCH_SIZE_PER_GPU, + device, + n_classes, + ) + + pred = outputs.detach().max(1)[1].numpy() + gt = labels.numpy() + running_metrics_split.update(gt, pred) + running_metrics_overall.update(gt, pred) + #image out here + # save_image(pred, 6, f"{config.MODEL.NAME}_{split}_{i}_pred.jpeg") + # save_image(gt, 6, f"{config.MODEL.NAME}_{split}_{i}_label.jpeg") + + + # get scores + score, class_iou = running_metrics_split.get_scores() + + # Log split results + logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') + for cdx, class_name in enumerate(_CLASS_NAMES): + logger.info( + f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' + ) + + logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') + logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') + logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') + running_metrics_split.reset() + + +def _write_section_file(labels, section_file): + # define indices of the array + irange, xrange, depth = labels.shape + + if config.TEST.INLINE: + i_list = list(range(irange)) + i_list = ["i_" + str(inline) for inline in i_list] + else: + i_list = [] + + if config.TEST.CROSSLINE: + x_list = list(range(xrange)) + x_list = ["x_" + str(crossline) for crossline in x_list] + else: + x_list = [] + + list_test = i_list + x_list + + file_object = open(section_file, "w") + file_object.write("\n".join(list_test)) + file_object.close() + def test(*options, cfg=None): + n_classes = 6 update_config(config, options=options, config_file=cfg) logging.config.fileConfig(config.LOG_CONFIG) logger = logging.getLogger(__name__) - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - log_dir, model_name = os.path.split(config.TEST.MODEL_PATH) # load model: @@ -289,7 +431,40 @@ def test(*options, cfg=None): model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False) model = model.to(device) # Send to GPU if available - running_metrics_overall = runningScore(6) + running_metrics_overall = runningScore(n_classes) + + # Augmentation + section_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ) + ] + ) + + patch_aug = Compose( + [ + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] + ) + + pre_processing = _compose_processing_pipeline( + config.TRAIN.DEPTH, aug=patch_aug + ) + output_processing = _output_processing_pipeline(config) splits = ( ["test1", "test2"] @@ -297,90 +472,30 @@ def test(*options, cfg=None): else [config.TEST.SPLIT] ) for sdx, split in enumerate(splits): - DATA_ROOT = path.join("/mnt", "alaudah") labels = np.load( path.join(DATA_ROOT, "test_once", split + "_labels.npy") ) section_file = path.join( DATA_ROOT, "splits", "section_" + split + ".txt" ) - # define indices of the array - irange, xrange, depth = labels.shape - - if config.TEST.INLINE: - i_list = list(range(irange)) - i_list = ["i_" + str(inline) for inline in i_list] - else: - i_list = [] - - if config.TEST.CROSSLINE: - x_list = list(range(xrange)) - x_list = ["x_" + str(crossline) for crossline in x_list] - else: - x_list = [] - - list_test = i_list + x_list - - file_object = open(section_file, "w") - file_object.write("\n".join(list_test)) - file_object.close() - - test_set = section_loader( - is_transform=True, split=split, augmentations=None + _write_section_file(labels, section_file) + _evaluate_split( + split, + section_aug, + model, + pre_processing, + output_processing, + device, + running_metrics_overall, + config, ) - n_classes = test_set.n_classes - - test_loader = data.DataLoader( - test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False - ) - - running_metrics_split = runningScore(n_classes) - - # testing mode: - with torch.no_grad(): # operations inside don't track history - model.eval() - total_iteration = 0 - for i, (images, labels) in enumerate(test_loader): - logger.info(f"split: {split}, section: {i}") - total_iteration = total_iteration + 1 - image_original, labels_original = images, labels - - outputs = patch_label_2d( - model=model, - img=images, - patch_size=config.TRAIN.PATCH_SIZE, - stride=config.TEST.TEST_STRIDE, - config=config, - device=device, - ) - - pred = outputs.detach().max(1)[1].numpy() - gt = labels.numpy() - running_metrics_split.update(gt, pred) - running_metrics_overall.update(gt, pred) - - - # get scores - score, class_iou = running_metrics_split.get_scores() - - # Add split results to TB: - logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') - for cdx, class_name in enumerate(class_names): - logger.info( - f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' - ) - - logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') - logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') - logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') - running_metrics_split.reset() # FINAL TEST RESULTS: score, class_iou = running_metrics_overall.get_scores() logger.info("--------------- FINAL RESULTS -----------------") logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') - for cdx, class_name in enumerate(class_names): + for cdx, class_name in enumerate(_CLASS_NAMES): logger.info( f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' ) diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.sh b/interpretation/experiments/segmentation/dutchf3/local/test.sh index 106c0093..57e33fc2 100755 --- a/interpretation/experiments/segmentation/dutchf3/local/test.sh +++ b/interpretation/experiments/segmentation/dutchf3/local/test.sh @@ -1,3 +1,4 @@ #!/bin/bash export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python test.py --cfg "configs/hrnet.yaml" \ No newline at end of file +export CUDA_VISIBLE_DEVICES=1 +python test.py --cfg "configs/seresnet_unet.yaml" \ No newline at end of file From 49a700321ecb3ceae9e732a52eda32cb3bb054a1 Mon Sep 17 00:00:00 2001 From: masalvar Date: Wed, 4 Sep 2019 18:59:59 +0000 Subject: [PATCH 036/207] minor update --- .../environment/anaconda/local/src/cv-lib | 2 +- .../dutchf3/distributed/configs/hrnet.yaml | 49 +++++++++++---- .../distributed/configs/patch_deconvnet.yaml | 36 +++++++++-- .../distributed/configs/seresnet_unet.yaml | 21 ++++--- .../dutchf3/distributed/configs/unet.yaml | 44 ++++++++++--- .../dutchf3/distributed/default.py | 9 ++- .../segmentation/dutchf3/distributed/run.sh | 2 +- .../segmentation/dutchf3/distributed/train.py | 7 ++- .../dutchf3/local/configs/hrnet.yaml | 2 +- .../local/configs/patch_deconvnet.yaml | 2 +- .../local/configs/patch_deconvnet_skip.yaml | 59 +++++++++++++++++ .../dutchf3/local/configs/seresnet_unet.yaml | 2 +- .../dutchf3/local/configs/unet.yaml | 63 +++++++++++++++++++ .../segmentation/dutchf3/local/run.sh | 4 -- .../segmentation/dutchf3/local/test.py | 10 +-- .../segmentation/dutchf3/local/test.sh | 2 - .../segmentation/dutchf3/local/train.sh | 2 + 17 files changed, 258 insertions(+), 58 deletions(-) create mode 100644 interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml create mode 100644 interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml delete mode 100755 interpretation/experiments/segmentation/dutchf3/local/run.sh create mode 100755 interpretation/experiments/segmentation/dutchf3/local/train.sh diff --git a/interpretation/environment/anaconda/local/src/cv-lib b/interpretation/environment/anaconda/local/src/cv-lib index 2fa67f28..3cadf99f 160000 --- a/interpretation/environment/anaconda/local/src/cv-lib +++ b/interpretation/environment/anaconda/local/src/cv-lib @@ -1 +1 @@ -Subproject commit 2fa67f280ebf0537f1a782097fb6808698911b5d +Subproject commit 3cadf99f6a24addbe1ec77e6777bed8653336f58 diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml index ef921135..c06a1083 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml @@ -1,23 +1,26 @@ -# HRNet configuration - CUDNN: BENCHMARK: true DETERMINISTIC: false ENABLED: true -OUTPUT_DIR: 'output' +GPUS: (0,) +OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + DATASET: NUM_CLASSES: 6 ROOT: /mnt/alaudah + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + MODEL: NAME: seg_hrnet IN_CHANNELS: 3 - PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' + PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: @@ -61,17 +64,39 @@ MODEL: FUSE_METHOD: SUM TRAIN: - BATCH_SIZE_PER_GPU: 32 + BATCH_SIZE_PER_GPU: 16 BEGIN_EPOCH: 0 END_EPOCH: 300 MIN_LR: 0.001 - MAX_LR: 0.01 + MAX_LR: 0.02 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "section" #"patch" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 -TEST: - BATCH_SIZE_PER_GPU: 64 +TEST: + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/ccb7206b41dc7411609705e49d9f4c2d74c6eb88/seg_hrnet/Aug30_141919/models/seg_hrnet_running_model_18.pth" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 # + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml index 69670909..314da3c7 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml @@ -3,20 +3,22 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' +OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf SEED: 2019 + DATASET: - NUM_CLASSES: 1 + NUM_CLASSES: 6 ROOT: /mnt/alaudah + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: NAME: patch_deconvnet - IN_CHANNELS: 3 + IN_CHANNELS: 1 TRAIN: @@ -24,10 +26,34 @@ TRAIN: BEGIN_EPOCH: 0 END_EPOCH: 300 MIN_LR: 0.001 - MAX_LR: 0.1 + MAX_LR: 0.02 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "No" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 99 + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + +VALIDATION: + BATCH_SIZE_PER_GPU: 512 TEST: - BATCH_SIZE_PER_GPU: 128 + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/5cc37bbe5302e1989ef1388d629400a16f82d1a9/patch_deconvnet/Aug27_200339/models/patch_deconvnet_snapshot1model_50.pth" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 99 + CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml index c4748327..ba243924 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml @@ -3,23 +3,23 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' +OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf SEED: 2019 + DATASET: NUM_CLASSES: 6 ROOT: /mnt/alaudah - + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: NAME: resnet_unet IN_CHANNELS: 3 - TRAIN: BATCH_SIZE_PER_GPU: 16 BEGIN_EPOCH: 0 @@ -30,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "patch" # Options are No, Patch and Section + DEPTH: "section" # Options are No, Patch and Section STRIDE: 50 PATCH_SIZE: 100 AUGMENTATIONS: @@ -40,15 +40,20 @@ TRAIN: PAD: HEIGHT: 256 WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + VALIDATION: BATCH_SIZE_PER_GPU: 32 - output/mat/exp/74d07f41ff0dc72920c279cc93f2eb1f42ed8a0d/resnet_unet/Aug26_165055/ - TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/188e66a26115760b7cbde7e9f6a626b6519a3bb7/patch_deconvnet/Aug25_102649/patch_deconvnet_running_model_25.pth" + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/dc2e2d20b7f6d508beb779ffff37c77d0139e588/resnet_unet/Sep01_125513/models/resnet_unet_snapshot1model_52.pth" TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True - CROSSLINE: True \ No newline at end of file + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml b/interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml index 3877d96e..09bba307 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml @@ -4,32 +4,60 @@ CUDNN: BENCHMARK: true DETERMINISTIC: false ENABLED: true -OUTPUT_DIR: 'output' +GPUS: (0,) +OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + DATASET: - NUM_CLASSES: 1 + NUM_CLASSES: 6 ROOT: /mnt/alaudah + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: NAME: resnet_unet IN_CHANNELS: 3 + TRAIN: BATCH_SIZE_PER_GPU: 16 BEGIN_EPOCH: 0 END_EPOCH: 300 MIN_LR: 0.001 - MAX_LR: 0.01 + MAX_LR: 0.02 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "section" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" -TEST: + +VALIDATION: BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right + diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/default.py b/interpretation/experiments/segmentation/dutchf3/distributed/default.py index 46fe9507..aa3ddf88 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/default.py +++ b/interpretation/experiments/segmentation/dutchf3/distributed/default.py @@ -41,6 +41,8 @@ _C.MODEL = CN() _C.MODEL.NAME = "patch_deconvnet" _C.MODEL.IN_CHANNELS = 1 +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) # training @@ -53,12 +55,12 @@ _C.TRAIN.BATCH_SIZE_PER_GPU = 32 _C.TRAIN.WEIGHT_DECAY = 0.0001 _C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.SAVE_LOCATION = "/tmp/models" +_C.TRAIN.MODEL_DIR = "models" _C.TRAIN.AUGMENTATION = True _C.TRAIN.STRIDE = 50 _C.TRAIN.PATCH_SIZE = 99 _C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 -_C.TRAIN.STD = 1 # 0.20976548783479299 +_C.TRAIN.STD = 0.21 # 0.20976548783479299 _C.TRAIN.DEPTH = 'None' # Options are None, Patch and Section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 @@ -83,6 +85,9 @@ _C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 _C.TEST.INLINE = True _C.TEST.CROSSLINE = True +_C.TEST.POST_PROCESSING = CN() # Model output postprocessing +_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels +_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right def update_config(cfg, options=None, config_file=None): diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/run.sh b/interpretation/experiments/segmentation/dutchf3/distributed/run.sh index d9be4bd7..e9394ecd 100755 --- a/interpretation/experiments/segmentation/dutchf3/distributed/run.sh +++ b/interpretation/experiments/segmentation/dutchf3/distributed/run.sh @@ -1,3 +1,3 @@ #!/bin/bash export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python -m torch.distributed.launch --nproc_per_node=8 train.py --cfg configs/seresnet_unet.yaml \ No newline at end of file +python -m torch.distributed.launch --nproc_per_node=8 train.py --cfg configs/hrnet.yaml \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/train.py b/interpretation/experiments/segmentation/dutchf3/distributed/train.py index a0ae7412..489cf21d 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/train.py +++ b/interpretation/experiments/segmentation/dutchf3/distributed/train.py @@ -24,7 +24,6 @@ from ignite.engine import Events from ignite.metrics import Loss from ignite.utils import convert_tensor -from sklearn.model_selection import train_test_split from tensorboardX import SummaryWriter from toolz import compose, curry from torch.utils import data @@ -44,8 +43,6 @@ from cv_lib.segmentation.dutchf3.data import ( get_train_loader, decode_segmap, - split_train_val, - split_non_overlapping_train_val, ) from cv_lib.segmentation.dutchf3.engine import ( create_supervised_evaluator, @@ -126,6 +123,10 @@ def run(*options, cfg=None, local_rank=0): scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + torch.manual_seed(config.SEED) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(config.SEED) + np.random.seed(seed=config.SEED) # Setup Augmentations basic_aug = Compose( [ diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml index 8543c27d..c06a1083 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml @@ -97,6 +97,6 @@ TEST: SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True CROSSLINE: True - POST_PROCESSING: # Expect output to be 64x64 and label to be 128x128 with 28 being padding + POST_PROCESSING: SIZE: 128 # CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml index 9f59e687..314da3c7 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml @@ -54,6 +54,6 @@ TEST: SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True CROSSLINE: True - POST_PROCESSING: # Expect output to be 64x64 and label to be 128x128 with 28 being padding + POST_PROCESSING: SIZE: 99 CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml new file mode 100644 index 00000000..2171b5d5 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml @@ -0,0 +1,59 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/alaudah + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: patch_deconvnet_skip + IN_CHANNELS: 1 + + +TRAIN: + BATCH_SIZE_PER_GPU: 64 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "no" #"patch" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 99 + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + +VALIDATION: + BATCH_SIZE_PER_GPU: 512 + +TEST: + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 99 # + CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right + diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml index d3f8be55..ba243924 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml @@ -54,6 +54,6 @@ TEST: SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True CROSSLINE: True - POST_PROCESSING: # Expect output to be 64x64 and label to be 128x128 with 28 being padding + POST_PROCESSING: SIZE: 128 CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml b/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml new file mode 100644 index 00000000..09bba307 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml @@ -0,0 +1,63 @@ +# UNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/alaudah + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: resnet_unet + IN_CHANNELS: 3 + + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "section" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right + diff --git a/interpretation/experiments/segmentation/dutchf3/local/run.sh b/interpretation/experiments/segmentation/dutchf3/local/run.sh deleted file mode 100755 index 1568bc35..00000000 --- a/interpretation/experiments/segmentation/dutchf3/local/run.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -export CUDA_VISIBLE_DEVICES=1 -python train.py --cfg "configs/seresnet_unet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.py b/interpretation/experiments/segmentation/dutchf3/local/test.py index 42a6c128..2dd4fc1f 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/test.py +++ b/interpretation/experiments/segmentation/dutchf3/local/test.py @@ -319,11 +319,6 @@ def to_image(label_mask, n_classes=6): rgb[:, :, :, 2] = b return rgb -from PIL import Image -def save_image(numpy_array, n_classes, filename): - if len(numpy_array.shape)==4: - numpy_array=numpy_array.squeeze(0) - Image.fromarray(to_image(numpy_array, n_classes=n_classes).squeeze(0).astype('uint8')).save(filename) def _evaluate_split( split, @@ -374,9 +369,6 @@ def _evaluate_split( gt = labels.numpy() running_metrics_split.update(gt, pred) running_metrics_overall.update(gt, pred) - #image out here - # save_image(pred, 6, f"{config.MODEL.NAME}_{split}_{i}_pred.jpeg") - # save_image(gt, 6, f"{config.MODEL.NAME}_{split}_{i}_label.jpeg") # get scores @@ -419,8 +411,8 @@ def _write_section_file(labels, section_file): def test(*options, cfg=None): - n_classes = 6 update_config(config, options=options, config_file=cfg) + n_classes = config.DATASET.NUM_CLASSES logging.config.fileConfig(config.LOG_CONFIG) logger = logging.getLogger(__name__) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.sh b/interpretation/experiments/segmentation/dutchf3/local/test.sh index 57e33fc2..ad68cf2e 100755 --- a/interpretation/experiments/segmentation/dutchf3/local/test.sh +++ b/interpretation/experiments/segmentation/dutchf3/local/test.sh @@ -1,4 +1,2 @@ #!/bin/bash -export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -export CUDA_VISIBLE_DEVICES=1 python test.py --cfg "configs/seresnet_unet.yaml" \ No newline at end of file diff --git a/interpretation/experiments/segmentation/dutchf3/local/train.sh b/interpretation/experiments/segmentation/dutchf3/local/train.sh new file mode 100755 index 00000000..b9bb9338 --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/local/train.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python train.py --cfg "configs/hrnet.yaml" \ No newline at end of file From 66c5a60e4645bb2ac036fadf54e5f7764590dfcd Mon Sep 17 00:00:00 2001 From: msalvaris Date: Wed, 4 Sep 2019 14:17:44 -0500 Subject: [PATCH 037/207] Fixing imports --- .../dutchf3/distributed/default.py | 3 -- .../segmentation/dutchf3/distributed/train.py | 51 +++++++------------ .../segmentation/dutchf3/local/default.py | 3 -- .../segmentation/dutchf3/local/test.py | 44 +++------------- .../segmentation/dutchf3/local/train.py | 22 +++----- 5 files changed, 31 insertions(+), 92 deletions(-) diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/default.py b/interpretation/experiments/segmentation/dutchf3/distributed/default.py index aa3ddf88..d705a856 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/default.py +++ b/interpretation/experiments/segmentation/dutchf3/distributed/default.py @@ -7,11 +7,8 @@ from __future__ import division from __future__ import print_function -import os - from yacs.config import CfgNode as CN - _C = CN() _C.OUTPUT_DIR = "output" diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/train.py b/interpretation/experiments/segmentation/dutchf3/distributed/train.py index 489cf21d..9818d4db 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/train.py +++ b/interpretation/experiments/segmentation/dutchf3/distributed/train.py @@ -8,27 +8,19 @@ import logging import logging.config import os -from datetime import datetime +from os import path +import cv2 import fire import numpy as np import torch -import torch.nn.functional as F -import torchvision.utils as vutils -from ignite.contrib.handlers import ( - ConcatScheduler, - CosineAnnealingScheduler, - CustomPeriodicEvent, - LinearCyclicalScheduler, +from albumentations import ( + Compose, + HorizontalFlip, + Normalize, + Resize, + PadIfNeeded, ) -from ignite.engine import Events -from ignite.metrics import Loss -from ignite.utils import convert_tensor -from tensorboardX import SummaryWriter -from toolz import compose, curry -from torch.utils import data -from tqdm import tqdm - from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -39,7 +31,7 @@ create_image_writer, create_summary_writer, ) - +from cv_lib.segmentation import models from cv_lib.segmentation.dutchf3.data import ( get_train_loader, decode_segmap, @@ -48,8 +40,7 @@ create_supervised_evaluator, create_supervised_trainer, ) -from cv_lib.segmentation.dutchf3.metrics import MeanIoU, PixelwiseAccuracy -from cv_lib.segmentation import models +from cv_lib.segmentation.dutchf3.metrics import apex from cv_lib.segmentation.dutchf3.utils import ( current_datetime, generate_path, @@ -57,24 +48,18 @@ git_hash, np_to_tb, ) -from cv_lib.segmentation.dutchf3.engine import ( - create_supervised_evaluator, - create_supervised_trainer, -) -from cv_lib.segmentation.dutchf3.metrics import apex from default import _C as config from default import update_config -from albumentations import ( - Compose, - HorizontalFlip, - GaussNoise, - Normalize, - Resize, - PadIfNeeded, +from ignite.contrib.handlers import ( + ConcatScheduler, + CosineAnnealingScheduler, + LinearCyclicalScheduler, ) -from os import path +from ignite.engine import Events +from ignite.utils import convert_tensor +from toolz import compose, curry +from torch.utils import data -import cv2 def prepare_batch(batch, device=None, non_blocking=False): x, y = batch diff --git a/interpretation/experiments/segmentation/dutchf3/local/default.py b/interpretation/experiments/segmentation/dutchf3/local/default.py index 805eae0b..e0fc893e 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/default.py +++ b/interpretation/experiments/segmentation/dutchf3/local/default.py @@ -7,11 +7,8 @@ from __future__ import division from __future__ import print_function -import os - from yacs.config import CfgNode as CN - _C = CN() _C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.py b/interpretation/experiments/segmentation/dutchf3/local/test.py index 2dd4fc1f..ec66be9f 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/test.py +++ b/interpretation/experiments/segmentation/dutchf3/local/test.py @@ -8,58 +8,27 @@ #TODO: Needs to be improved. Needs to be able to run across multiple GPUs and better factoring around the loader """ -import argparse import itertools import logging import logging.config import os -from datetime import datetime from os import path -from os.path import join as pjoin import cv2 import fire import numpy as np import torch import torch.nn.functional as F -import torchvision.utils as vutils -from albumentations import (Compose, GaussNoise, HorizontalFlip, Normalize, +from albumentations import (Compose, Normalize, PadIfNeeded, Resize) -from ignite.contrib.handlers import (ConcatScheduler, CosineAnnealingScheduler, - CustomPeriodicEvent, - LinearCyclicalScheduler) -from ignite.engine import Events -from ignite.metrics import Loss -from ignite.utils import convert_tensor -from sklearn.model_selection import train_test_split -from tensorboardX import SummaryWriter -from toolz import compose, curry, itertoolz, pipe -from toolz.itertoolz import take -from torch.utils import data -from tqdm import tqdm - -from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, - tensorboard_handlers) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, - create_summary_writer) -from cv_lib.segmentation.dutchf3.data import (TestSectionLoaderWithDepth, - add_patch_depth_channels, - decode_segmap, +from cv_lib.segmentation import models +from cv_lib.segmentation.dutchf3.data import (add_patch_depth_channels, get_seismic_labels, - get_test_loader, - get_train_loader) -from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, - create_supervised_trainer) -from cv_lib.segmentation.dutchf3.metrics import (MeanIoU, PixelwiseAccuracy, - apex) -from cv_lib.segmentation.dutchf3.utils import (current_datetime, generate_path, - git_branch, git_hash, np_to_tb) - get_distributed_data_loaders, - kfold_split) - create_supervised_trainer) + get_test_loader) from default import _C as config from default import update_config +from toolz import compose, curry, itertoolz, pipe +from torch.utils import data _CLASS_NAMES = [ "upper_ns", @@ -351,7 +320,6 @@ def _evaluate_split( for i, (images, labels) in enumerate(test_loader): logger.info(f"split: {split}, section: {i}") total_iteration = total_iteration + 1 - image_original, labels_original = images, labels outputs = _patch_label_2d( model, diff --git a/interpretation/experiments/segmentation/dutchf3/local/train.py b/interpretation/experiments/segmentation/dutchf3/local/train.py index d4ca3798..eab6600b 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/train.py +++ b/interpretation/experiments/segmentation/dutchf3/local/train.py @@ -4,28 +4,14 @@ import logging import logging.config -import os -from datetime import datetime from os import path import cv2 import fire import numpy as np import torch -import torch.nn.functional as F -import torchvision.utils as vutils -from albumentations import (Compose, GaussNoise, HorizontalFlip, Normalize, +from albumentations import (Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize) -from ignite.contrib.handlers import CosineAnnealingScheduler -from ignite.engine import Events -from ignite.metrics import Loss -from ignite.utils import convert_tensor -from sklearn.model_selection import train_test_split -from tensorboardX import SummaryWriter -from toolz import compose, itertoolz -from torch.utils import data -from tqdm import tqdm - from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, tensorboard_handlers) from cv_lib.event_handlers.logging_handlers import Evaluator @@ -41,6 +27,12 @@ git_branch, git_hash, np_to_tb) from default import _C as config from default import update_config +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from toolz import compose +from torch.utils import data def prepare_batch(batch, device=None, non_blocking=False): From ade65cba98a0bd9dafbf61fdbca1e18204ac2b78 Mon Sep 17 00:00:00 2001 From: msalvaris Date: Wed, 4 Sep 2019 17:46:50 -0500 Subject: [PATCH 038/207] Refactoring the experiments --- .../dutchf3/distributed/default.py | 20 ------------------- .../{ => experiments}/voxel2pixel/README.md | 0 .../{ => experiments}/voxel2pixel/batch.py | 0 .../{ => experiments}/voxel2pixel/data.py | 0 .../voxel2pixel/tb_logger.py | 0 .../voxel2pixel/test_parallel.py | 0 .../voxel2pixel/texture_net.py | 0 .../{ => experiments}/voxel2pixel/train.py | 0 .../{ => experiments}/voxel2pixel/utils.py | 0 9 files changed, 20 deletions(-) rename interpretation/{ => experiments}/voxel2pixel/README.md (100%) rename interpretation/{ => experiments}/voxel2pixel/batch.py (100%) rename interpretation/{ => experiments}/voxel2pixel/data.py (100%) rename interpretation/{ => experiments}/voxel2pixel/tb_logger.py (100%) rename interpretation/{ => experiments}/voxel2pixel/test_parallel.py (100%) rename interpretation/{ => experiments}/voxel2pixel/texture_net.py (100%) rename interpretation/{ => experiments}/voxel2pixel/train.py (100%) rename interpretation/{ => experiments}/voxel2pixel/utils.py (100%) diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/default.py b/interpretation/experiments/segmentation/dutchf3/distributed/default.py index d705a856..1b218be9 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/default.py +++ b/interpretation/experiments/segmentation/dutchf3/distributed/default.py @@ -106,23 +106,3 @@ def update_config(cfg, options=None, config_file=None): print(_C, file=f) - - -def update_config(cfg, options=None, config_file=None): - cfg.defrost() - - if config_file: - cfg.merge_from_file(config_file) - - if options: - cfg.merge_from_list(options) - - cfg.freeze() - - -if __name__ == "__main__": - import sys - - with open(sys.argv[1], "w") as f: - print(_C, file=f) - diff --git a/interpretation/voxel2pixel/README.md b/interpretation/experiments/voxel2pixel/README.md similarity index 100% rename from interpretation/voxel2pixel/README.md rename to interpretation/experiments/voxel2pixel/README.md diff --git a/interpretation/voxel2pixel/batch.py b/interpretation/experiments/voxel2pixel/batch.py similarity index 100% rename from interpretation/voxel2pixel/batch.py rename to interpretation/experiments/voxel2pixel/batch.py diff --git a/interpretation/voxel2pixel/data.py b/interpretation/experiments/voxel2pixel/data.py similarity index 100% rename from interpretation/voxel2pixel/data.py rename to interpretation/experiments/voxel2pixel/data.py diff --git a/interpretation/voxel2pixel/tb_logger.py b/interpretation/experiments/voxel2pixel/tb_logger.py similarity index 100% rename from interpretation/voxel2pixel/tb_logger.py rename to interpretation/experiments/voxel2pixel/tb_logger.py diff --git a/interpretation/voxel2pixel/test_parallel.py b/interpretation/experiments/voxel2pixel/test_parallel.py similarity index 100% rename from interpretation/voxel2pixel/test_parallel.py rename to interpretation/experiments/voxel2pixel/test_parallel.py diff --git a/interpretation/voxel2pixel/texture_net.py b/interpretation/experiments/voxel2pixel/texture_net.py similarity index 100% rename from interpretation/voxel2pixel/texture_net.py rename to interpretation/experiments/voxel2pixel/texture_net.py diff --git a/interpretation/voxel2pixel/train.py b/interpretation/experiments/voxel2pixel/train.py similarity index 100% rename from interpretation/voxel2pixel/train.py rename to interpretation/experiments/voxel2pixel/train.py diff --git a/interpretation/voxel2pixel/utils.py b/interpretation/experiments/voxel2pixel/utils.py similarity index 100% rename from interpretation/voxel2pixel/utils.py rename to interpretation/experiments/voxel2pixel/utils.py From fa1d1aa923ab8b3d0f84073d36254acebf815145 Mon Sep 17 00:00:00 2001 From: msalvaris Date: Wed, 4 Sep 2019 17:56:35 -0500 Subject: [PATCH 039/207] Removing .vscode --- .vscode/settings.json | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 5c682cd8..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "python.jediEnabled": false, - "[python]": { - "editor.defaultFormatter": "ms-python.python" - }, - "python.formatting.provider": "black", - "git.autofetch": true, - "python.linting.pylintEnabled": true, - "python.pythonPath": "/anaconda/envs/seismic-interpretation/bin/python", - "python.linting.pylintArgs": [ - "--enable=W0614" - ], - "python.envFile": "${workspaceFolder}/.env", - "window.zoomLevel": 0, - "autoDocstring.docstringFormat": "google", - "files.autoSave": "afterDelay", - "files.autoSaveDelay": 100, - "editor.suggestSelection": "first", - "vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue", - "remote.SSH.showLoginTerminal": true, - "docker.host": "tcp://msnarwal.eastus.cloudapp.azure.com:2375", - "terminal.integrated.env.linux":{ - "PYTHONPATH": "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local:/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/loader:/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/models:${env:PYTHONPATH}" - }, - "files.exclude": { - "**/__pycache__":true, - "**/bin":true, - "**/deepseismic":true, - }, - "files.watcherExclude":{ - "**/__pycache__":true, - "**/.git/objects/**": true, - "**/.git/subtree-cache/**": true, - "**/bin": true, - "**/deepseismic": true, - "**/log": true, - "**/output": true, - "**/cv_lib.egg-info": true, - } -} \ No newline at end of file From 253af1cdc431ae19d06eb1938d28e8cb6f792ea0 Mon Sep 17 00:00:00 2001 From: masalvar Date: Thu, 5 Sep 2019 15:44:01 +0000 Subject: [PATCH 040/207] Updates gitignore --- .gitignore | 7 ++----- .idea/vcs.xml | 6 ------ 2 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index 206d6b04..e8f1ab83 100644 --- a/.gitignore +++ b/.gitignore @@ -48,12 +48,8 @@ venv/ venv.bak/ wheels/ -*/.vscode/* -.vscode/settings.json -.vscode/launch.json .dev_env - .azureml # Logs @@ -66,4 +62,5 @@ wheels/ interpretation/environment/anaconda/local/src/* interpretation/environment/anaconda/local/src/cv-lib .code-workspace.code-workspace -.vscode +**/.vscode +**/.idea diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 358242afb4ee86723a4232a4578bb36015671ec5 Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Tue, 17 Sep 2019 10:04:18 +0000 Subject: [PATCH 041/207] Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) --- .../segmentation/dutchf3/README.md | 58 +++ .../segmentation/dutchf3/prepare_data.py | 347 +++++++++++------- 2 files changed, 275 insertions(+), 130 deletions(-) create mode 100644 interpretation/experiments/segmentation/dutchf3/README.md diff --git a/interpretation/experiments/segmentation/dutchf3/README.md b/interpretation/experiments/segmentation/dutchf3/README.md new file mode 100644 index 00000000..ec5808ce --- /dev/null +++ b/interpretation/experiments/segmentation/dutchf3/README.md @@ -0,0 +1,58 @@ +## F3 Netherlands Experiments Setup + +### Conda env setup + +Please set up a conda environment following the instructions in the top-level [README.md](../../../../README.md) file. + +### Data download + +To download the F3 Netherlands data set, please follow the data download instructions at: +https://github.com/olivesgatech/facies_classification_benchmark + + +Once you've downloaded the F3 data set, you'll find your data files in the following directory tree: + +``` +data +├── splits +├── test_once +│ ├── test1_labels.npy +│ ├── test1_seismic.npy +│ ├── test2_labels.npy +│ └── test2_seismic.npy +└── train + ├── train_labels.npy + └── train_seismic.npy +``` + +### Data preparation + +To split the dataset into training and validation, please run the [prepare_data.py](prepare_data.py) script. + +Example run: +``` +// To split data into sections +python prepare_data.py split_train_val --data-dir=/mnt/dutchf3 --loader-type="section" --log-config=../logging.conf + +// To split data into patches +python prepare_data.py split_train_val --data-dir=/mnt/dutchf3 --loader-type="patch" --stride=50 --log-config=../logging.conf +``` + +Please see `prepare_data.py` script for more arguments. + + +### Configuration files +We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manage configuration options for our experiments. + +We use the following three ways to pass options to the experiment scripts (e.g. train.py or test.py): + +- __default.py__ - A project config file `default.py` is a one-stop reference point for all configurable options, and provides sensible defaults for all options. + +- __yml config files__ - YAML configuration files under `configs/` are typically created one for each experiment. These are meant to be used for repeatable experiment runs and reproducible settings. Each configuration file only overrides the options that are changing in that experiment (e.g. options loaded from `defaults.py` during an experiment run will be overridden by options loaded from the yaml file) + +- __command line__ - Finally, options can be passed in through `options` argument, and those will override options loaded from the configuration file. We created CLIs for all our scripts (using Python Fire library), so you can pass these options via command-line arguments. + + +### Running experiments + +Now you're all set to run training and testing experiments on the F3 Netherlands dataset. Please start from the `train.sh` and `test.sh` scripts under the `local/` and `distributed/` directories, which invoke the corresponding python scripts. Take a look at the project configurations in (e.g in `default.py`) for experiment options and modify if necessary. diff --git a/interpretation/experiments/segmentation/dutchf3/prepare_data.py b/interpretation/experiments/segmentation/dutchf3/prepare_data.py index c30b8d20..5f4c2f6a 100644 --- a/interpretation/experiments/segmentation/dutchf3/prepare_data.py +++ b/interpretation/experiments/segmentation/dutchf3/prepare_data.py @@ -1,5 +1,5 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. # commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 # url: https://github.com/olivesgatech/facies_classification_benchmark """Script to generate train and validation sets for Netherlands F3 dataset @@ -22,23 +22,108 @@ from torch.utils import data -def split_non_overlapping_train_val( - data_dir, stride, per_val=0.2, loader_type="patch" -): - # create inline and crossline pacthes for training and validation: +def _get_splits_path(data_dir): + return path.join(data_dir, "splits") + +def _get_labels_path(data_dir): + return path.join(data_dir, "train", "train_labels.npy") + + +def _write_split_files(splits_path, train_list, test_list, loader_type): + file_object = open(path.join(splits_path, loader_type + "_train_val.txt"), "w") + file_object.write("\n".join(train_list + test_list)) + file_object.close() + file_object = open(path.join(splits_path, loader_type + "_train.txt"), "w") + file_object.write("\n".join(train_list)) + file_object.close() + file_object = open(path.join(splits_path, loader_type + "_val.txt"), "w") + file_object.write("\n".join(test_list)) + file_object.close() + + +def _get_aline_range(aline, per_val): + # Inline sections + test_aline = math.floor(aline * per_val/2) + test_aline_range = itertools.chain( + range(0, test_aline), range(aline - test_aline, aline) + ) + train_aline_range = range(test_aline, aline - test_aline) + + return train_aline_range, test_aline_range + + +def split_section_train_val(data_dir, per_val=0.2, log_config=None): + """Generate train and validation files for Netherlands F3 dataset. + + Args: + data_dir (str): data directory path + per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + """ + + if log_config is not None: + logging.config.fileConfig(log_config) + logger = logging.getLogger(__name__) - SPLITS = path.join(data_dir, "splits") - labels_path = path.join(data_dir, "train", "train_labels.npy") - logger.info('Reading data from {data_dir}') - logger.info('Loading {labels_path}') + logger.info('Splitting data into sections .... ') + logger.info(f"Reading data from {data_dir}") + + labels_path = _get_labels_path(data_dir) + logger.info(f"Loading {labels_path}") + labels = np.load(labels_path) + logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") + + iline, xline, _ = labels.shape + # Inline sections + train_iline_range, test_iline_range = _get_aline_range(iline, per_val) + train_i_list = ["i_" + str(i) for i in train_iline_range] + test_i_list = ["i_" + str(i) for i in test_iline_range] + + # Xline sections + train_xline_range, test_xline_range = _get_aline_range(xline, per_val) + train_x_list = ["x_" + str(x) for x in train_xline_range] + test_x_list = ["x_" + str(x) for x in test_xline_range] + + train_list = train_x_list + train_i_list + test_list = test_x_list + test_i_list + + # write to files to disk + splits_path = _get_splits_path(data_dir) + _write_split_files(splits_path, train_list, test_list, "section") + + + +def split_patch_train_val(data_dir, stride, per_val=0.2, log_config=None): + """Generate train and validation files for Netherlands F3 dataset. + + Args: + data_dir (str): data directory path + stride (int): stride to use when sectioning of the volume + per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + """ + + if log_config is not None: + logging.config.fileConfig(log_config) + + logger = logging.getLogger(__name__) + + logger.info('Splitting data into patches .... ') + logger.info(f"Reading data from {data_dir}") + + labels_path = _get_labels_path(data_dir) + logger.info(f"Loading {labels_path}") labels = np.load(labels_path) - iline, xline, depth = labels.shape logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") - # INLINE PATCHES: ------------------------------------------------ - test_iline = math.floor(iline*0.1) - test_iline_range = itertools.chain(range(0, test_iline),range(iline-test_iline, iline)) - train_line_range = range(test_iline, iline-test_iline) + + iline, xline, depth = labels.shape + # Inline sections + train_iline_range, test_iline_range = _get_aline_range(iline, per_val) + + # Xline sections + train_xline_range, test_xline_range = _get_aline_range(xline, per_val) + + # Generate patches from sections + # Process inlines horz_locations = range(0, xline - stride, stride) vert_locations = range(0, depth - stride, stride) logger.debug("Generating Inline patches") @@ -47,161 +132,163 @@ def split_non_overlapping_train_val( def _i_extract_patches(iline_range, horz_locations, vert_locations): for i in iline_range: - locations = ([j, k] for j in horz_locations for k in vert_locations) + locations = ( + [j, k] for j in horz_locations for k in vert_locations + ) for j, k in locations: yield "i_" + str(i) + "_" + str(j) + "_" + str(k) - test_i_list = list(_i_extract_patches(test_iline_range, horz_locations, vert_locations)) - train_i_list = list(_i_extract_patches(train_line_range, horz_locations, vert_locations)) - - - # XLINE PATCHES: ------------------------------------------------ - test_xline = math.floor(xline*0.1) - test_xline_range = itertools.chain(range(0, test_xline),range(xline-test_xline, xline)) - train_xline_range = range(test_xline, xline-test_xline) + test_i_list = list( + _i_extract_patches(test_iline_range, horz_locations, vert_locations) + ) + train_i_list = list( + _i_extract_patches(train_iline_range, horz_locations, vert_locations) + ) + # Process crosslines horz_locations = range(0, iline - stride, stride) vert_locations = range(0, depth - stride, stride) def _x_extract_patches(xline_range, horz_locations, vert_locations): for j in xline_range: - locations = ([i, k] for i in horz_locations for k in vert_locations) + locations = ( + [i, k] for i in horz_locations for k in vert_locations + ) for i, k in locations: yield "x_" + str(i) + "_" + str(j) + "_" + str(k) - - test_x_list = list(_x_extract_patches(test_xline_range, horz_locations, vert_locations)) - train_x_list = list(_x_extract_patches(train_xline_range, horz_locations, vert_locations)) + test_x_list = list( + _x_extract_patches(test_xline_range, horz_locations, vert_locations) + ) + train_x_list = list( + _x_extract_patches(train_xline_range, horz_locations, vert_locations) + ) train_list = train_x_list + train_i_list test_list = test_x_list + test_i_list # write to files to disk: # NOTE: This isn't quite right we should calculate the patches again for the whole volume - file_object = open(path.join(SPLITS, loader_type + "_train_val.txt"), "w") - file_object.write("\n".join(train_list+test_list)) - file_object.close() - file_object = open(path.join(SPLITS, loader_type + "_train.txt"), "w") - file_object.write("\n".join(train_list)) - file_object.close() - file_object = open(path.join(SPLITS, loader_type + "_val.txt"), "w") - file_object.write("\n".join(test_list)) - file_object.close() + splits_path = _get_splits_path(data_dir) + _write_split_files(splits_path, train_list, test_list, "patch") +_LOADER_TYPES = { + "section": split_section_train_val, + "patch": split_patch_train_val, +} -def split_train_val( - data_dir, stride, per_val=0.2, loader_type="patch", labels_path=LABELS -): - warnings.warn("THIS CREATES OVERLAPPING TRAINING AND VALIDATION SETS") - # create inline and crossline pacthes for training and validation: - logger = logging.getLogger(__name__) - SPLITS = path.join(data_dir, "splits") - labels_path = path.join(data_dir, "train", "train_labels.npy") - - logger.info('Reading data from {data_dir}') - logger.info('Loading {labels_path}') - labels = np.load(labels_path) - iline, xline, depth = labels.shape - logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") - - # INLINE PATCHES: ------------------------------------------------ - i_list = [] - horz_locations = range(0, xline - stride, stride) - vert_locations = range(0, depth - stride, stride) - logger.debug("Generating Inline patches") - logger.debug(horz_locations) - logger.debug(vert_locations) - for i in range(iline): - # for every inline: - # images are references by top-left corner: - locations = [[j, k] for j in horz_locations for k in vert_locations] - patches_list = [ - "i_" + str(i) + "_" + str(j) + "_" + str(k) for j, k in locations - ] - i_list.append(patches_list) - - # flatten the list - i_list = list(itertools.chain(*i_list)) - - # XLINE PATCHES: ------------------------------------------------ - x_list = [] - horz_locations = range(0, iline - stride, stride) - vert_locations = range(0, depth - stride, stride) - for j in range(xline): - # for every xline: - # images are references by top-left corner: - locations = [[i, k] for i in horz_locations for k in vert_locations] - patches_list = [ - "x_" + str(i) + "_" + str(j) + "_" + str(k) for i, k in locations - ] - x_list.append(patches_list) - - # flatten the list - x_list = list(itertools.chain(*x_list)) - list_train_val = i_list + x_list +def get_split_function(loader_type): + return _LOADER_TYPES.get(loader_type, split_patch_train_val) - # create train and test splits: - list_train, list_val = train_test_split( - list_train_val, test_size=per_val, shuffle=True - ) - # write to files to disk: - file_object = open(path.join(SPLITS, loader_type + "_train_val.txt"), "w") - file_object.write("\n".join(list_train_val)) - file_object.close() - file_object = open(path.join(SPLITS, loader_type + "_train.txt"), "w") - file_object.write("\n".join(list_train)) - file_object.close() - file_object = open(path.join(SPLITS, loader_type + "_val.txt"), "w") - file_object.write("\n".join(list_val)) - file_object.close() +def run_split_func(loader_type, *args, **kwargs): + split_func = get_split_function(loader_type) + split_func(*args, **kwargs) -def split_non_overlapping(stride, fraction_validation = 0.2, log_config=None): - """Generate train and validation files of non-overlapping segments +def split_alaudah_et_al_19( + data_dir, + stride, + fraction_validation=0.2, + loader_type="patch", + log_config=None, +): + """Generate train and validation files (with overlap) for Netherlands F3 dataset. + This is the original spliting method from https://github.com/olivesgatech/facies_classification_benchmark + DON'T USE, SEE NOTES BELOW Args: + data_dir (str): data directory path stride (int): stride to use when sectioning of the volume fraction_validation (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. - log_config ([type], optional): path to log config. Defaults to None. + loader_type (str, optional): type of data loader, can be "patch" or "section". Defaults to "patch". + log_config (str, optional): path to log config. Defaults to None. + + Notes: + Only kept for reproducibility. It generates overlapping train and val which makes validation results unreliable. """ if log_config is not None: - logging.config.fileConfig(config.LOG_CONFIG) + logging.config.fileConfig(log_config) - split_non_overlapping_train_val( - stride, per_val=fraction_validation, loader_type="patch" - ) + warnings.warn("THIS CREATES OVERLAPPING TRAINING AND VALIDATION SETS") + + assert loader_type in ["section", "patch"], f"Loader type {loader_type} is not valid. \ + Please specify either 'section' or 'patch' for loader_type" + + # create inline and crossline pacthes for training and validation: + logger = logging.getLogger(__name__) + + logger.info("Reading data from {data_dir}") + labels_path = _get_labels_path(data_dir) + logger.info("Loading {labels_path}") + labels = np.load(labels_path) + iline, xline, depth = labels.shape + logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") -def split_overlapping(stride, fraction_validation = 0.2, log_config=None): - """Generate train and validation files of segments - This is the original spliting method from https://github.com/olivesgatech/facies_classification_benchmark - DON'T USE SEE NOTES BELOW + if (loader_type == "section"): + i_list = ["i_" + str(i) for i in range(iline)] + x_list = ["x_" + str(x) for x in range(xline)] + elif (loader_type == "patch"): + i_list = [] + horz_locations = range(0, xline - stride, stride) + vert_locations = range(0, depth - stride, stride) + logger.debug("Generating Inline patches") + logger.debug(horz_locations) + logger.debug(vert_locations) + for i in range(iline): + # for every inline: + # images are references by top-left corner: + locations = [[j, k] for j in horz_locations for k in vert_locations] + patches_list = [ + "i_" + str(i) + "_" + str(j) + "_" + str(k) for j, k in locations + ] + i_list.append(patches_list) + + # flatten the list + i_list = list(itertools.chain(*i_list)) + + x_list = [] + horz_locations = range(0, iline - stride, stride) + vert_locations = range(0, depth - stride, stride) + for j in range(xline): + # for every xline: + # images are references by top-left corner: + locations = [[i, k] for i in horz_locations for k in vert_locations] + patches_list = [ + "x_" + str(i) + "_" + str(j) + "_" + str(k) for i, k in locations + ] + x_list.append(patches_list) + + # flatten the list + x_list = list(itertools.chain(*x_list)) - Args: - stride (int): stride to use when sectioning of the volume - fraction_validation (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. - log_config ([type], optional): path to log config. Defaults to None. + list_train_val = i_list + x_list - Notes: - Only kept for reproducibility. It generates overlapping train and val which makes validation results unreliable - """ - if log_config is not None: - logging.config.fileConfig(config.LOG_CONFIG) + # create train and test splits: + train_list, test_list = train_test_split(list_train_val, test_size=fraction_validation, shuffle=True) - split_train_val( - stride, per_val=fraction_validation, loader_type="patch" - ) + # write to files to disk: + splits_path = _get_splits_path(data_dir) + _write_split_files(splits_path, train_list, test_list, loader_type) -if __name__=="__main__": + +if __name__ == "__main__": """Example: - python prepare_data.py split_non_overlapping /mnt/dutchf3 50 --log-config=logging.conf + python prepare_data.py split_train_val --data-dir=/mnt/dutch --loader-type="section" + or + python prepare_data.py split_train_val --data-dir=/mnt/dutch --loader-type="patch" --stride=50 + """ - fire.Fire({ - 'split_non_overlapping': split_non_overlapping, - 'split_overlapping': split_overlapping, - }) + + + fire.Fire( + { + "split_train_val": run_split_func, + "split_alaudah_et_al_19": split_alaudah_et_al_19 + } + ) From fc5388ad60d81eae98fc761364ecb943ea7c83c9 Mon Sep 17 00:00:00 2001 From: Mathew Salvaris Date: Tue, 17 Sep 2019 11:48:49 +0000 Subject: [PATCH 042/207] Merged PR 204: Adds loaders to deepseismic from cv_lib --- README.md | 18 +- SETUP.md | 6 - deepseismic/__init__.py | 3 - .../anaconda/local/environment.yml | 3 +- .../docker/apex/Makefile | 0 .../docker/apex/dockerfile | 0 .../docker/horovod/Makefile | 0 .../docker/horovod/dockerfile | 0 .../segmentation/dutchf3/README.md | 0 .../dutchf3/distributed/configs/hrnet.yaml | 0 .../distributed/configs/patch_deconvnet.yaml | 0 .../configs/patch_deconvnet_skip.yaml | 0 .../distributed/configs/seresnet_unet.yaml | 0 .../dutchf3/distributed/configs/unet.yaml | 0 .../dutchf3/distributed/default.py | 0 .../segmentation/dutchf3/distributed/run.sh | 0 .../segmentation/dutchf3/distributed/train.py | 2 +- .../dutchf3/local/configs/hrnet.yaml | 0 .../local/configs/patch_deconvnet.yaml | 0 .../local/configs/patch_deconvnet_skip.yaml | 0 .../dutchf3/local/configs/seresnet_unet.yaml | 0 .../dutchf3/local/configs/unet.yaml | 0 .../segmentation/dutchf3/local/default.py | 0 .../segmentation/dutchf3/local/test.py | 0 .../segmentation/dutchf3/local/test.sh | 0 .../segmentation/dutchf3/local/train.py | 20 +- .../segmentation/dutchf3/local/train.sh | 0 .../segmentation/dutchf3/prepare_data.py | 39 +- .../tgs_salt/apex/configs/hrnet.yaml | 0 .../tgs_salt/apex/configs/unet.yaml | 0 .../segmentation/tgs_salt/apex/default.py | 0 .../segmentation/tgs_salt/apex/run.sh | 0 .../segmentation/tgs_salt/apex/train.py | 0 .../tgs_salt/distributed/configs/hrnet.yaml | 0 .../tgs_salt/distributed/configs/unet.yaml | 0 .../tgs_salt/distributed/default.py | 0 .../segmentation/tgs_salt/distributed/run.sh | 0 .../tgs_salt/distributed/train.py | 0 .../tgs_salt/horovod/configs/hrnet.yaml | 0 .../tgs_salt/horovod/configs/unet.yaml | 0 .../segmentation/tgs_salt/horovod/default.py | 0 .../segmentation/tgs_salt/horovod/run.sh | 0 .../segmentation/tgs_salt/horovod/train.py | 0 .../tgs_salt/local/configs/hrnet.yaml | 0 .../tgs_salt/local/configs/unet.yaml | 0 .../segmentation/tgs_salt/local/default.py | 0 .../segmentation/tgs_salt/local/run.sh | 0 .../segmentation/tgs_salt/local/train.py | 0 .../voxel2pixel/README.md | 0 .../voxel2pixel/batch.py | 0 .../voxel2pixel/data.py | 0 .../voxel2pixel/tb_logger.py | 0 .../voxel2pixel/test_parallel.py | 0 .../voxel2pixel/texture_net.py | 0 .../voxel2pixel/train.py | 0 .../voxel2pixel/utils.py | 0 {bin => imaging/bin}/ds | 2 +- imaging/deepseismic_imaging/__init__.py | 3 + .../deepseismic_imaging}/cli/__init__.py | 0 .../deepseismic_imaging}/cli/forward.py | 2 +- .../deepseismic_imaging}/cli/velocity.py | 2 +- .../deepseismic_imaging}/forward/__init__.py | 0 .../deepseismic_imaging}/forward/models.py | 0 .../deepseismic_imaging}/forward/sources.py | 0 .../forward/subdomains.py | 0 .../deepseismic_imaging}/forward/time.py | 0 .../deepseismic_imaging}/forward/types.py | 0 .../deepseismic_imaging}/velocity/__init__.py | 0 .../velocity/generator.py | 0 .../velocity/roeth_tarantola.py | 0 imaging/requirements.txt | 6 + {scripts => imaging}/setup.cfg | 0 {scripts => imaging}/setup.py | 20 +- .../deepseismic_interpretation/__init__.py | 0 .../dutchf3/__init__.py | 0 .../dutchf3/data.py | 602 ++++++++++++++++++ .../environment/anaconda/local/src/cv-lib | 1 - interpretation/requirements.txt | 1 + interpretation/setup.cfg | 2 + interpretation/setup.py | 41 ++ 80 files changed, 710 insertions(+), 63 deletions(-) delete mode 100644 SETUP.md delete mode 100644 deepseismic/__init__.py rename {interpretation/environment => environment}/anaconda/local/environment.yml (71%) rename {interpretation/environment => environment}/docker/apex/Makefile (100%) rename {interpretation/environment => environment}/docker/apex/dockerfile (100%) rename {interpretation/environment => environment}/docker/horovod/Makefile (100%) rename {interpretation/environment => environment}/docker/horovod/dockerfile (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/README.md (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/distributed/configs/hrnet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/distributed/configs/unet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/distributed/default.py (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/distributed/run.sh (100%) mode change 100755 => 100644 rename {interpretation/experiments => experiments}/segmentation/dutchf3/distributed/train.py (99%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/configs/hrnet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/configs/patch_deconvnet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/configs/seresnet_unet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/configs/unet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/default.py (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/test.py (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/test.sh (100%) mode change 100755 => 100644 rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/train.py (98%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/local/train.sh (100%) rename {interpretation/experiments => experiments}/segmentation/dutchf3/prepare_data.py (97%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/apex/configs/hrnet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/apex/configs/unet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/apex/default.py (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/apex/run.sh (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/apex/train.py (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/distributed/configs/hrnet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/distributed/configs/unet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/distributed/default.py (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/distributed/run.sh (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/distributed/train.py (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/horovod/configs/hrnet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/horovod/configs/unet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/horovod/default.py (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/horovod/run.sh (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/horovod/train.py (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/local/configs/hrnet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/local/configs/unet.yaml (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/local/default.py (100%) rename {interpretation/experiments => experiments}/segmentation/tgs_salt/local/run.sh (100%) mode change 100755 => 100644 rename {interpretation/experiments => experiments}/segmentation/tgs_salt/local/train.py (100%) rename {interpretation/experiments => experiments}/voxel2pixel/README.md (100%) rename {interpretation/experiments => experiments}/voxel2pixel/batch.py (100%) rename {interpretation/experiments => experiments}/voxel2pixel/data.py (100%) rename {interpretation/experiments => experiments}/voxel2pixel/tb_logger.py (100%) rename {interpretation/experiments => experiments}/voxel2pixel/test_parallel.py (100%) rename {interpretation/experiments => experiments}/voxel2pixel/texture_net.py (100%) rename {interpretation/experiments => experiments}/voxel2pixel/train.py (100%) rename {interpretation/experiments => experiments}/voxel2pixel/utils.py (100%) rename {bin => imaging/bin}/ds (64%) create mode 100644 imaging/deepseismic_imaging/__init__.py rename {deepseismic => imaging/deepseismic_imaging}/cli/__init__.py (100%) rename {deepseismic => imaging/deepseismic_imaging}/cli/forward.py (97%) rename {deepseismic => imaging/deepseismic_imaging}/cli/velocity.py (97%) rename {deepseismic => imaging/deepseismic_imaging}/forward/__init__.py (100%) rename {deepseismic => imaging/deepseismic_imaging}/forward/models.py (100%) rename {deepseismic => imaging/deepseismic_imaging}/forward/sources.py (100%) rename {deepseismic => imaging/deepseismic_imaging}/forward/subdomains.py (100%) rename {deepseismic => imaging/deepseismic_imaging}/forward/time.py (100%) rename {deepseismic => imaging/deepseismic_imaging}/forward/types.py (100%) rename {deepseismic => imaging/deepseismic_imaging}/velocity/__init__.py (100%) rename {deepseismic => imaging/deepseismic_imaging}/velocity/generator.py (100%) rename {deepseismic => imaging/deepseismic_imaging}/velocity/roeth_tarantola.py (100%) create mode 100644 imaging/requirements.txt rename {scripts => imaging}/setup.cfg (100%) rename {scripts => imaging}/setup.py (78%) rename scripts/setup.sh => interpretation/deepseismic_interpretation/__init__.py (100%) create mode 100644 interpretation/deepseismic_interpretation/dutchf3/__init__.py create mode 100644 interpretation/deepseismic_interpretation/dutchf3/data.py delete mode 160000 interpretation/environment/anaconda/local/src/cv-lib create mode 100644 interpretation/requirements.txt create mode 100644 interpretation/setup.cfg create mode 100644 interpretation/setup.py diff --git a/README.md b/README.md index c0a8d6c5..7d9315a3 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,26 @@ ## Interpretation -### Setting up Env +### Setting up Environment Run ```bash -conda env create -f DeepSeismic/interpretation/environment/anaconda/local/environment.yml +conda env create -f DeepSeismic/environment/anaconda/local/environment.yml ``` This will create the appropriate environment to run experiments -The compute vision repo will be downloaded into the src folder under interpretation/environments/anaconda/local/ +Then you will need to install the common packages for interpretation +```bash +conda activate seismic-interpretation +pip install -e DeepSeismic/deepseismic_interpretation +``` + +Then you will also need to pull computer vision contrib +```bash +git clone https://aicat-ongip@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork +pip install -e ComputerVision_fork/contrib +``` + +Both repos are installed in developer mode with the -e flag. This means that to update simply go to the folder and pull the appropriate commit or branch ### Data The scripts expect the data to be contained in /mnt/alaudah diff --git a/SETUP.md b/SETUP.md deleted file mode 100644 index 70d41949..00000000 --- a/SETUP.md +++ /dev/null @@ -1,6 +0,0 @@ -# Setup guide - -This document describes how to setup all the dependencies to run the code in this repository in following platforms: - -* Local (Linux, MacOS or Windows) or [DSVM](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/) (Linux or Windows) -* TODO: add more diff --git a/deepseismic/__init__.py b/deepseismic/__init__.py deleted file mode 100644 index 8dc07e06..00000000 --- a/deepseismic/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from . import cli, forward, velocity - -__all__ = ["cli", "forward", "velocity"] diff --git a/interpretation/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml similarity index 71% rename from interpretation/environment/anaconda/local/environment.yml rename to environment/anaconda/local/environment.yml index a1abf1fd..d1723cba 100644 --- a/interpretation/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -26,4 +26,5 @@ dependencies: - invoke - yacs - albumentations - - "--editable=git+https://dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork@c4939283ae7a187047f4e1a1b2f8ea5266d14731#egg=cv_lib&subdirectory=contrib" + - black + - pylint diff --git a/interpretation/environment/docker/apex/Makefile b/environment/docker/apex/Makefile similarity index 100% rename from interpretation/environment/docker/apex/Makefile rename to environment/docker/apex/Makefile diff --git a/interpretation/environment/docker/apex/dockerfile b/environment/docker/apex/dockerfile similarity index 100% rename from interpretation/environment/docker/apex/dockerfile rename to environment/docker/apex/dockerfile diff --git a/interpretation/environment/docker/horovod/Makefile b/environment/docker/horovod/Makefile similarity index 100% rename from interpretation/environment/docker/horovod/Makefile rename to environment/docker/horovod/Makefile diff --git a/interpretation/environment/docker/horovod/dockerfile b/environment/docker/horovod/dockerfile similarity index 100% rename from interpretation/environment/docker/horovod/dockerfile rename to environment/docker/horovod/dockerfile diff --git a/interpretation/experiments/segmentation/dutchf3/README.md b/experiments/segmentation/dutchf3/README.md similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/README.md rename to experiments/segmentation/dutchf3/README.md diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml b/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml rename to experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml b/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml rename to experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml b/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml rename to experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml b/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml rename to experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml b/experiments/segmentation/dutchf3/distributed/configs/unet.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/distributed/configs/unet.yaml rename to experiments/segmentation/dutchf3/distributed/configs/unet.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/default.py b/experiments/segmentation/dutchf3/distributed/default.py similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/distributed/default.py rename to experiments/segmentation/dutchf3/distributed/default.py diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/run.sh b/experiments/segmentation/dutchf3/distributed/run.sh old mode 100755 new mode 100644 similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/distributed/run.sh rename to experiments/segmentation/dutchf3/distributed/run.sh diff --git a/interpretation/experiments/segmentation/dutchf3/distributed/train.py b/experiments/segmentation/dutchf3/distributed/train.py similarity index 99% rename from interpretation/experiments/segmentation/dutchf3/distributed/train.py rename to experiments/segmentation/dutchf3/distributed/train.py index 9818d4db..47504a36 100644 --- a/interpretation/experiments/segmentation/dutchf3/distributed/train.py +++ b/experiments/segmentation/dutchf3/distributed/train.py @@ -32,7 +32,7 @@ create_summary_writer, ) from cv_lib.segmentation import models -from cv_lib.segmentation.dutchf3.data import ( +from deepseismic_interpretation.dutchf3.data import ( get_train_loader, decode_segmap, ) diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml b/experiments/segmentation/dutchf3/local/configs/hrnet.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/local/configs/hrnet.yaml rename to experiments/segmentation/dutchf3/local/configs/hrnet.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml b/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml rename to experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml b/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml rename to experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml b/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml rename to experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml b/experiments/segmentation/dutchf3/local/configs/unet.yaml similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/local/configs/unet.yaml rename to experiments/segmentation/dutchf3/local/configs/unet.yaml diff --git a/interpretation/experiments/segmentation/dutchf3/local/default.py b/experiments/segmentation/dutchf3/local/default.py similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/local/default.py rename to experiments/segmentation/dutchf3/local/default.py diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.py b/experiments/segmentation/dutchf3/local/test.py similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/local/test.py rename to experiments/segmentation/dutchf3/local/test.py diff --git a/interpretation/experiments/segmentation/dutchf3/local/test.sh b/experiments/segmentation/dutchf3/local/test.sh old mode 100755 new mode 100644 similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/local/test.sh rename to experiments/segmentation/dutchf3/local/test.sh diff --git a/interpretation/experiments/segmentation/dutchf3/local/train.py b/experiments/segmentation/dutchf3/local/train.py similarity index 98% rename from interpretation/experiments/segmentation/dutchf3/local/train.py rename to experiments/segmentation/dutchf3/local/train.py index eab6600b..66289dee 100644 --- a/interpretation/experiments/segmentation/dutchf3/local/train.py +++ b/experiments/segmentation/dutchf3/local/train.py @@ -10,14 +10,21 @@ import fire import numpy as np import torch -from albumentations import (Compose, HorizontalFlip, Normalize, - PadIfNeeded, Resize) +from albumentations import (Compose, HorizontalFlip, Normalize, PadIfNeeded, + Resize) +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from toolz import compose +from torch.utils import data + +from deepseismic_interpretation.dutchf3.data import get_train_loader, decode_segmap from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, tensorboard_handlers) from cv_lib.event_handlers.logging_handlers import Evaluator from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, create_summary_writer) -from cv_lib.segmentation.dutchf3.data import (decode_segmap, get_train_loader) from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, create_supervised_trainer) from cv_lib.segmentation.dutchf3.metrics import (FrequencyWeightedIoU, @@ -25,14 +32,9 @@ PixelwiseAccuracy) from cv_lib.segmentation.dutchf3.utils import (current_datetime, generate_path, git_branch, git_hash, np_to_tb) + from default import _C as config from default import update_config -from ignite.contrib.handlers import CosineAnnealingScheduler -from ignite.engine import Events -from ignite.metrics import Loss -from ignite.utils import convert_tensor -from toolz import compose -from torch.utils import data def prepare_batch(batch, device=None, non_blocking=False): diff --git a/interpretation/experiments/segmentation/dutchf3/local/train.sh b/experiments/segmentation/dutchf3/local/train.sh similarity index 100% rename from interpretation/experiments/segmentation/dutchf3/local/train.sh rename to experiments/segmentation/dutchf3/local/train.sh diff --git a/interpretation/experiments/segmentation/dutchf3/prepare_data.py b/experiments/segmentation/dutchf3/prepare_data.py similarity index 97% rename from interpretation/experiments/segmentation/dutchf3/prepare_data.py rename to experiments/segmentation/dutchf3/prepare_data.py index 5f4c2f6a..ba17b3ea 100644 --- a/interpretation/experiments/segmentation/dutchf3/prepare_data.py +++ b/experiments/segmentation/dutchf3/prepare_data.py @@ -4,27 +4,22 @@ # url: https://github.com/olivesgatech/facies_classification_benchmark """Script to generate train and validation sets for Netherlands F3 dataset """ -import collections import itertools -import json import logging import logging.config import math -import os import warnings from os import path import fire import numpy as np -import torch from sklearn.model_selection import train_test_split -from toolz import curry -from torch.utils import data def _get_splits_path(data_dir): return path.join(data_dir, "splits") + def _get_labels_path(data_dir): return path.join(data_dir, "train", "train_labels.npy") @@ -50,11 +45,11 @@ def _get_aline_range(aline, per_val): train_aline_range = range(test_aline, aline - test_aline) return train_aline_range, test_aline_range - + def split_section_train_val(data_dir, per_val=0.2, log_config=None): """Generate train and validation files for Netherlands F3 dataset. - + Args: data_dir (str): data directory path per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. @@ -72,13 +67,13 @@ def split_section_train_val(data_dir, per_val=0.2, log_config=None): logger.info(f"Loading {labels_path}") labels = np.load(labels_path) logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") - + iline, xline, _ = labels.shape # Inline sections train_iline_range, test_iline_range = _get_aline_range(iline, per_val) train_i_list = ["i_" + str(i) for i in train_iline_range] - test_i_list = ["i_" + str(i) for i in test_iline_range] - + test_i_list = ["i_" + str(i) for i in test_iline_range] + # Xline sections train_xline_range, test_xline_range = _get_aline_range(xline, per_val) train_x_list = ["x_" + str(x) for x in train_xline_range] @@ -92,10 +87,9 @@ def split_section_train_val(data_dir, per_val=0.2, log_config=None): _write_split_files(splits_path, train_list, test_list, "section") - def split_patch_train_val(data_dir, stride, per_val=0.2, log_config=None): """Generate train and validation files for Netherlands F3 dataset. - + Args: data_dir (str): data directory path stride (int): stride to use when sectioning of the volume @@ -106,19 +100,19 @@ def split_patch_train_val(data_dir, stride, per_val=0.2, log_config=None): logging.config.fileConfig(log_config) logger = logging.getLogger(__name__) - + logger.info('Splitting data into patches .... ') logger.info(f"Reading data from {data_dir}") - + labels_path = _get_labels_path(data_dir) logger.info(f"Loading {labels_path}") labels = np.load(labels_path) logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") - + iline, xline, depth = labels.shape # Inline sections train_iline_range, test_iline_range = _get_aline_range(iline, per_val) - + # Xline sections train_xline_range, test_xline_range = _get_aline_range(xline, per_val) @@ -198,7 +192,7 @@ def split_alaudah_et_al_19( """Generate train and validation files (with overlap) for Netherlands F3 dataset. This is the original spliting method from https://github.com/olivesgatech/facies_classification_benchmark DON'T USE, SEE NOTES BELOW - + Args: data_dir (str): data directory path stride (int): stride to use when sectioning of the volume @@ -217,10 +211,10 @@ def split_alaudah_et_al_19( assert loader_type in ["section", "patch"], f"Loader type {loader_type} is not valid. \ Please specify either 'section' or 'patch' for loader_type" - + # create inline and crossline pacthes for training and validation: logger = logging.getLogger(__name__) - + logger.info("Reading data from {data_dir}") labels_path = _get_labels_path(data_dir) @@ -265,7 +259,7 @@ def split_alaudah_et_al_19( # flatten the list x_list = list(itertools.chain(*x_list)) - + list_train_val = i_list + x_list # create train and test splits: @@ -276,7 +270,6 @@ def split_alaudah_et_al_19( _write_split_files(splits_path, train_list, test_list, loader_type) - if __name__ == "__main__": """Example: python prepare_data.py split_train_val --data-dir=/mnt/dutch --loader-type="section" @@ -284,8 +277,6 @@ def split_alaudah_et_al_19( python prepare_data.py split_train_val --data-dir=/mnt/dutch --loader-type="patch" --stride=50 """ - - fire.Fire( { "split_train_val": run_split_func, diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml b/experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml rename to experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/configs/unet.yaml b/experiments/segmentation/tgs_salt/apex/configs/unet.yaml similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/apex/configs/unet.yaml rename to experiments/segmentation/tgs_salt/apex/configs/unet.yaml diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/default.py b/experiments/segmentation/tgs_salt/apex/default.py similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/apex/default.py rename to experiments/segmentation/tgs_salt/apex/default.py diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/run.sh b/experiments/segmentation/tgs_salt/apex/run.sh similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/apex/run.sh rename to experiments/segmentation/tgs_salt/apex/run.sh diff --git a/interpretation/experiments/segmentation/tgs_salt/apex/train.py b/experiments/segmentation/tgs_salt/apex/train.py similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/apex/train.py rename to experiments/segmentation/tgs_salt/apex/train.py diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml b/experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml rename to experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/configs/unet.yaml b/experiments/segmentation/tgs_salt/distributed/configs/unet.yaml similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/distributed/configs/unet.yaml rename to experiments/segmentation/tgs_salt/distributed/configs/unet.yaml diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/default.py b/experiments/segmentation/tgs_salt/distributed/default.py similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/distributed/default.py rename to experiments/segmentation/tgs_salt/distributed/default.py diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/run.sh b/experiments/segmentation/tgs_salt/distributed/run.sh similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/distributed/run.sh rename to experiments/segmentation/tgs_salt/distributed/run.sh diff --git a/interpretation/experiments/segmentation/tgs_salt/distributed/train.py b/experiments/segmentation/tgs_salt/distributed/train.py similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/distributed/train.py rename to experiments/segmentation/tgs_salt/distributed/train.py diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml b/experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml rename to experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml b/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml rename to experiments/segmentation/tgs_salt/horovod/configs/unet.yaml diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/default.py b/experiments/segmentation/tgs_salt/horovod/default.py similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/horovod/default.py rename to experiments/segmentation/tgs_salt/horovod/default.py diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/run.sh b/experiments/segmentation/tgs_salt/horovod/run.sh similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/horovod/run.sh rename to experiments/segmentation/tgs_salt/horovod/run.sh diff --git a/interpretation/experiments/segmentation/tgs_salt/horovod/train.py b/experiments/segmentation/tgs_salt/horovod/train.py similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/horovod/train.py rename to experiments/segmentation/tgs_salt/horovod/train.py diff --git a/interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml b/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml rename to experiments/segmentation/tgs_salt/local/configs/hrnet.yaml diff --git a/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml b/experiments/segmentation/tgs_salt/local/configs/unet.yaml similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml rename to experiments/segmentation/tgs_salt/local/configs/unet.yaml diff --git a/interpretation/experiments/segmentation/tgs_salt/local/default.py b/experiments/segmentation/tgs_salt/local/default.py similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/local/default.py rename to experiments/segmentation/tgs_salt/local/default.py diff --git a/interpretation/experiments/segmentation/tgs_salt/local/run.sh b/experiments/segmentation/tgs_salt/local/run.sh old mode 100755 new mode 100644 similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/local/run.sh rename to experiments/segmentation/tgs_salt/local/run.sh diff --git a/interpretation/experiments/segmentation/tgs_salt/local/train.py b/experiments/segmentation/tgs_salt/local/train.py similarity index 100% rename from interpretation/experiments/segmentation/tgs_salt/local/train.py rename to experiments/segmentation/tgs_salt/local/train.py diff --git a/interpretation/experiments/voxel2pixel/README.md b/experiments/voxel2pixel/README.md similarity index 100% rename from interpretation/experiments/voxel2pixel/README.md rename to experiments/voxel2pixel/README.md diff --git a/interpretation/experiments/voxel2pixel/batch.py b/experiments/voxel2pixel/batch.py similarity index 100% rename from interpretation/experiments/voxel2pixel/batch.py rename to experiments/voxel2pixel/batch.py diff --git a/interpretation/experiments/voxel2pixel/data.py b/experiments/voxel2pixel/data.py similarity index 100% rename from interpretation/experiments/voxel2pixel/data.py rename to experiments/voxel2pixel/data.py diff --git a/interpretation/experiments/voxel2pixel/tb_logger.py b/experiments/voxel2pixel/tb_logger.py similarity index 100% rename from interpretation/experiments/voxel2pixel/tb_logger.py rename to experiments/voxel2pixel/tb_logger.py diff --git a/interpretation/experiments/voxel2pixel/test_parallel.py b/experiments/voxel2pixel/test_parallel.py similarity index 100% rename from interpretation/experiments/voxel2pixel/test_parallel.py rename to experiments/voxel2pixel/test_parallel.py diff --git a/interpretation/experiments/voxel2pixel/texture_net.py b/experiments/voxel2pixel/texture_net.py similarity index 100% rename from interpretation/experiments/voxel2pixel/texture_net.py rename to experiments/voxel2pixel/texture_net.py diff --git a/interpretation/experiments/voxel2pixel/train.py b/experiments/voxel2pixel/train.py similarity index 100% rename from interpretation/experiments/voxel2pixel/train.py rename to experiments/voxel2pixel/train.py diff --git a/interpretation/experiments/voxel2pixel/utils.py b/experiments/voxel2pixel/utils.py similarity index 100% rename from interpretation/experiments/voxel2pixel/utils.py rename to experiments/voxel2pixel/utils.py diff --git a/bin/ds b/imaging/bin/ds similarity index 64% rename from bin/ds rename to imaging/bin/ds index 3bd01081..5171b1a7 100644 --- a/bin/ds +++ b/imaging/bin/ds @@ -1,6 +1,6 @@ #!/usr/bin/env python -from deepseismic import cli +from deepseismic.imaging import cli if __name__ == "__main__": cli.main() diff --git a/imaging/deepseismic_imaging/__init__.py b/imaging/deepseismic_imaging/__init__.py new file mode 100644 index 00000000..34749819 --- /dev/null +++ b/imaging/deepseismic_imaging/__init__.py @@ -0,0 +1,3 @@ +from deepseismic_imaging import velocity, forward, cli + +__all__ = ["cli", "forward", "velocity"] diff --git a/deepseismic/cli/__init__.py b/imaging/deepseismic_imaging/cli/__init__.py similarity index 100% rename from deepseismic/cli/__init__.py rename to imaging/deepseismic_imaging/cli/__init__.py diff --git a/deepseismic/cli/forward.py b/imaging/deepseismic_imaging/cli/forward.py similarity index 97% rename from deepseismic/cli/forward.py rename to imaging/deepseismic_imaging/cli/forward.py index 0ef69d39..2f924735 100644 --- a/deepseismic/cli/forward.py +++ b/imaging/deepseismic_imaging/cli/forward.py @@ -4,7 +4,7 @@ import h5py import numpy as np -from ..forward import Receiver, RickerSource, TimeAxis, VelocityModel +from deepseismic_imaging.forward import Receiver, RickerSource, TimeAxis, VelocityModel click.option = partial(click.option, show_default=True) diff --git a/deepseismic/cli/velocity.py b/imaging/deepseismic_imaging/cli/velocity.py similarity index 97% rename from deepseismic/cli/velocity.py rename to imaging/deepseismic_imaging/cli/velocity.py index 1c87c340..b8e51da5 100644 --- a/deepseismic/cli/velocity.py +++ b/imaging/deepseismic_imaging/cli/velocity.py @@ -5,7 +5,7 @@ import click import h5py -from ..velocity import RoethTarantolaGenerator +from deepseismic_imaging.velocity import RoethTarantolaGenerator click.option = partial(click.option, show_default=True) diff --git a/deepseismic/forward/__init__.py b/imaging/deepseismic_imaging/forward/__init__.py similarity index 100% rename from deepseismic/forward/__init__.py rename to imaging/deepseismic_imaging/forward/__init__.py diff --git a/deepseismic/forward/models.py b/imaging/deepseismic_imaging/forward/models.py similarity index 100% rename from deepseismic/forward/models.py rename to imaging/deepseismic_imaging/forward/models.py diff --git a/deepseismic/forward/sources.py b/imaging/deepseismic_imaging/forward/sources.py similarity index 100% rename from deepseismic/forward/sources.py rename to imaging/deepseismic_imaging/forward/sources.py diff --git a/deepseismic/forward/subdomains.py b/imaging/deepseismic_imaging/forward/subdomains.py similarity index 100% rename from deepseismic/forward/subdomains.py rename to imaging/deepseismic_imaging/forward/subdomains.py diff --git a/deepseismic/forward/time.py b/imaging/deepseismic_imaging/forward/time.py similarity index 100% rename from deepseismic/forward/time.py rename to imaging/deepseismic_imaging/forward/time.py diff --git a/deepseismic/forward/types.py b/imaging/deepseismic_imaging/forward/types.py similarity index 100% rename from deepseismic/forward/types.py rename to imaging/deepseismic_imaging/forward/types.py diff --git a/deepseismic/velocity/__init__.py b/imaging/deepseismic_imaging/velocity/__init__.py similarity index 100% rename from deepseismic/velocity/__init__.py rename to imaging/deepseismic_imaging/velocity/__init__.py diff --git a/deepseismic/velocity/generator.py b/imaging/deepseismic_imaging/velocity/generator.py similarity index 100% rename from deepseismic/velocity/generator.py rename to imaging/deepseismic_imaging/velocity/generator.py diff --git a/deepseismic/velocity/roeth_tarantola.py b/imaging/deepseismic_imaging/velocity/roeth_tarantola.py similarity index 100% rename from deepseismic/velocity/roeth_tarantola.py rename to imaging/deepseismic_imaging/velocity/roeth_tarantola.py diff --git a/imaging/requirements.txt b/imaging/requirements.txt new file mode 100644 index 00000000..9983a4db --- /dev/null +++ b/imaging/requirements.txt @@ -0,0 +1,6 @@ +click==7.0 +devito==3.5 +h5py==2.9.0 +numpy==1.17.0 +scipy==1.3.0 +sympy==1.4 \ No newline at end of file diff --git a/scripts/setup.cfg b/imaging/setup.cfg similarity index 100% rename from scripts/setup.cfg rename to imaging/setup.cfg diff --git a/scripts/setup.py b/imaging/setup.py similarity index 78% rename from scripts/setup.py rename to imaging/setup.py index 3d2cc7b3..6b074193 100644 --- a/scripts/setup.py +++ b/imaging/setup.py @@ -1,8 +1,11 @@ import setuptools -with open("README.md", "r") as f: +with open("../README.md", "r") as f: long_description = f.read() +with open('requirements.txt') as f: + requirements = f.read().splitlines() + setuptools.setup( author="DeepSeismic Maintainers", author_email="deepseismic@microsoft.com", @@ -22,23 +25,16 @@ "https://github.com/opesci/devito/archive/v3.5.tar.gz#egg=devito-3.5" ], description="DeepSeismic", - install_requires=[ - "click==7.0", - "devito==3.5", - "h5py==2.9.0", - "numpy==1.17.0", - "scipy==1.3.0", - "sympy==1.4", - ], + install_requires=requirements, license="MIT", long_description=long_description, long_description_content_type="text/markdown", - name="deepseismic", + name="deepseismic_imaging", packages=setuptools.find_packages( - include=["deepseismic", "deepseismic.*"] + include=["deepseismic_imaging", "deepseismic_imaging.*"] ), platforms="any", - python_requires=">= 3.5", + python_requires=">= 3.6", scripts=["bin/ds"], setup_requires=["pytest-runner"], tests_require=["pytest"], diff --git a/scripts/setup.sh b/interpretation/deepseismic_interpretation/__init__.py similarity index 100% rename from scripts/setup.sh rename to interpretation/deepseismic_interpretation/__init__.py diff --git a/interpretation/deepseismic_interpretation/dutchf3/__init__.py b/interpretation/deepseismic_interpretation/dutchf3/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py new file mode 100644 index 00000000..4cd688df --- /dev/null +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -0,0 +1,602 @@ +import collections +import itertools +import json +import logging +import math +import os +import warnings +from os import path + +import matplotlib.pyplot as plt +import numpy as np +import torch +from sklearn.model_selection import train_test_split +from toolz import curry +from torch.utils import data + +DATA_ROOT = path.join("/mnt", "alaudah") +SPLITS = path.join(DATA_ROOT, "splits") +LABELS = path.join(DATA_ROOT, "train", "train_labels.npy") + +TRAIN_PATH = path.join(DATA_ROOT, "train") +TEST_PATH = path.join(DATA_ROOT, "test_once") +TRAIN_SEISMIC = path.join(TRAIN_PATH, "train_seismic.npy") +TRAIN_LABELS = path.join(TRAIN_PATH, "train_labels.npy") + +TEST1_SEISMIC = path.join(TEST_PATH, "test1_seismic.npy") +TEST2_SEISMIC = path.join(TEST_PATH, "test2_seismic.npy") + +TEST1_LABELS = path.join(TEST_PATH, "test1_labels.npy") +TEST2_LABELS = path.join(TEST_PATH, "test2_labels.npy") + + + +class SectionLoader(data.Dataset): + def __init__( + self, split="train", is_transform=True, augmentations=None + ): + self.split = split + self.root = DATA_ROOT + self.is_transform = is_transform + self.augmentations = augmentations + self.n_classes = 6 + self.sections = collections.defaultdict(list) + + def __len__(self): + return len(self.sections[self.split]) + + def __getitem__(self, index): + + section_name = self.sections[self.split][index] + direction, number = section_name.split(sep="_") + + if direction == "i": + im = self.seismic[int(number), :, :] + lbl = self.labels[int(number), :, :] + elif direction == "x": + im = self.seismic[:, int(number), :] + lbl = self.labels[:, int(number), :] + + if self.augmentations is not None: + augmented_dict = self.augmentations(image=im, mask=lbl) + im, lbl = augmented_dict["image"], augmented_dict["mask"] + + if self.is_transform: + im, lbl = self.transform(im, lbl) + + return im, lbl + + def transform(self, img, lbl): + # to be in the BxCxHxW that PyTorch uses: + lbl = np.expand_dims(lbl, 0) + if len(img.shape) == 2: + img = np.expand_dims(img, 0) + return torch.from_numpy(img).float(), torch.from_numpy(lbl).long() + + +class TrainSectionLoader(SectionLoader): + + def __init__( + self, split="train", is_transform=True, augmentations=None + ): + super(TrainSectionLoader, self).__init__( + split=split, is_transform=is_transform, augmentations=augmentations + ) + + self.seismic = np.load( + path.join(DATA_ROOT, "train", "train_seismic.npy") + ) + self.labels = np.load( + path.join(DATA_ROOT, "train", "train_labels.npy") + ) + for split in ["train", "val", "train_val"]: + # reading the file names for 'train', 'val', 'trainval'"" + txt_path = path.join( + DATA_ROOT, "splits", "section_" + split + ".txt" + ) + file_list = tuple(open(txt_path, "r")) + file_list = [id_.rstrip() for id_ in file_list] + self.sections[split] = file_list + + +class TrainSectionLoaderWithDepth(TrainSectionLoader): + + def __init__( + self, split="train", is_transform=True, augmentations=None + ): + super(TrainSectionLoader, self).__init__( + split=split, is_transform=is_transform, augmentations=augmentations + ) + self.seismic = add_section_depth_channels(self.seismic)#NCWH + + def __getitem__(self, index): + + section_name = self.sections[self.split][index] + direction, number = section_name.split(sep="_") + + if direction == "i": + im = self.seismic[int(number), :, :, :] + lbl = self.labels[int(number), :, :, :] + elif direction == "x": + im = self.seismic[:, :, int(number), :] + lbl = self.labels[:, :, int(number), :] + + im = np.swapaxes(im, 0, 1) # From WCH to CWH + + im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) + + if self.augmentations is not None: + im = _transform_CHW_to_HWC(im) + augmented_dict = self.augmentations(image=im, mask=lbl) + im, lbl = augmented_dict["image"], augmented_dict["mask"] + im = _transform_HWC_to_CHW(im) + + if self.is_transform: + im, lbl = self.transform(im, lbl) + + return im, lbl + +class TestSectionLoader(SectionLoader): + def __init__( + self, split="test1", is_transform=True, augmentations=None + ): + super(TestSectionLoader, self).__init__( + split=split, is_transform=is_transform, augmentations=augmentations + ) + + if "test1" in self.split: + self.seismic = np.load( + path.join(DATA_ROOT, "test_once", "test1_seismic.npy") + ) + self.labels = np.load( + path.join(DATA_ROOT, "test_once", "test1_labels.npy") + ) + elif "test2" in self.split: + self.seismic = np.load( + path.join(DATA_ROOT, "test_once", "test2_seismic.npy") + ) + self.labels = np.load( + path.join(DATA_ROOT, "test_once", "test2_labels.npy") + ) + + # We are in test mode. Only read the given split. The other one might not + # be available. + txt_path = path.join( + DATA_ROOT, "splits", "section_" + split + ".txt" + ) + file_list = tuple(open(txt_path, "r")) + file_list = [id_.rstrip() for id_ in file_list] + self.sections[split] = file_list + + +class TestSectionLoaderWithDepth(TestSectionLoader): + def __init__( + self, split="test1", is_transform=True, augmentations=None + ): + super(TestSectionLoaderWithDepth, self).__init__( + split=split, is_transform=is_transform, augmentations=augmentations + ) + + self.seismic = add_section_depth_channels(self.seismic)#NCWH + + def __getitem__(self, index): + + section_name = self.sections[self.split][index] + direction, number = section_name.split(sep="_") + + if direction == "i": + im = self.seismic[int(number), :, :, :] + lbl = self.labels[int(number), :, :] + elif direction == "x": + im = self.seismic[:, :, int(number), :] + lbl = self.labels[:, int(number), :] + + im = np.swapaxes(im, 0, 1) # From WCH to CWH + + im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) + + if self.augmentations is not None: + im = _transform_CHW_to_HWC(im) + augmented_dict = self.augmentations(image=im, mask=lbl) + im, lbl = augmented_dict["image"], augmented_dict["mask"] + im = _transform_HWC_to_CHW(im) + + if self.is_transform: + im, lbl = self.transform(im, lbl) + + return im, lbl + + +def _transform_WH_to_HW(numpy_array): + assert len(numpy_array.shape) >= 2, "This method needs at least 2D arrays" + return np.swapaxes(numpy_array, -2, -1) + + +class PatchLoader(data.Dataset): + """ + Data loader for the patch-based deconvnet + """ + + def __init__( + self, stride=30, patch_size=99, is_transform=True, augmentations=None + ): + self.root = DATA_ROOT + self.is_transform = is_transform + self.augmentations = augmentations + self.n_classes = 6 + self.patches = collections.defaultdict(list) + self.patch_size = patch_size + self.stride = stride + + def pad_volume(self, volume): + """ + Only used for train/val!! Not test. + """ + return np.pad( + volume, + pad_width=self.patch_size, + mode="constant", + constant_values=255, + ) + + def __len__(self): + return len(self.patches[self.split]) + + def __getitem__(self, index): + + patch_name = self.patches[self.split][index] + direction, idx, xdx, ddx = patch_name.split(sep="_") + + # Shift offsets the padding that is added in training + # shift = self.patch_size if "test" not in self.split else 0 + # TODO: Remember we are cancelling the shit since we no longer pad + shift = 0 + idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift + + if direction == "i": + im = self.seismic[ + idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size + ] + lbl = self.labels[ + idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size + ] + elif direction == "x": + im = self.seismic[ + idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size + ] + lbl = self.labels[ + idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size + ] + + im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) + + if self.augmentations is not None: + augmented_dict = self.augmentations(image=im, mask=lbl) + im, lbl = augmented_dict["image"], augmented_dict["mask"] + + if self.is_transform: + im, lbl = self.transform(im, lbl) + return im, lbl + + def transform(self, img, lbl): + # to be in the BxCxHxW that PyTorch uses: + lbl = np.expand_dims(lbl, 0) + if len(img.shape) == 2: + img = np.expand_dims(img, 0) + return torch.from_numpy(img).float(), torch.from_numpy(lbl).long() + + +class TestPatchLoader(PatchLoader): + def __init__( + self, + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=TEST1_SEISMIC, + labels_path=TEST1_LABELS, + ): + super(TestPatchLoader, self).__init__( + stride=stride, + patch_size=patch_size, + is_transform=is_transform, + augmentations=augmentations, + ) + self.seismic = np.load(seismic_path) + self.labels = np.load(labels_path) + + # We are in test mode. Only read the given split. The other one might not + # be available. + self.split = "test1" # TODO: Fix this can also be test2 + txt_path = path.join( + DATA_ROOT, "splits", "patch_" + self.split + ".txt" + ) + patch_list = tuple(open(txt_path, "r")) + self.patches[split] = patch_list + + +class TrainPatchLoader(PatchLoader): + def __init__( + self, + split="train", + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=TRAIN_SEISMIC, + labels_path=TRAIN_LABELS, + ): + super(TrainPatchLoader, self).__init__( + stride=stride, + patch_size=patch_size, + is_transform=is_transform, + augmentations=augmentations, + ) + # self.seismic = self.pad_volume(np.load(seismic_path)) + # self.labels = self.pad_volume(np.load(labels_path)) + warnings.warn("This no longer pads the volume") + self.seismic = np.load(seismic_path) + self.labels = np.load(labels_path) + # We are in train/val mode. Most likely the test splits are not saved yet, + # so don't attempt to load them. + self.split = split + for split in ["train", "val", "train_val"]: + # reading the file names for 'train', 'val', 'trainval'"" + txt_path = path.join( + DATA_ROOT, "splits", "patch_" + split + ".txt" + ) + patch_list = tuple(open(txt_path, "r")) + self.patches[split] = patch_list + + +class TrainPatchLoaderWithDepth(TrainPatchLoader): + def __init__( + self, + split="train", + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=TRAIN_SEISMIC, + labels_path=TRAIN_LABELS, + ): + super(TrainPatchLoaderWithDepth, self).__init__( + stride=stride, + patch_size=patch_size, + is_transform=is_transform, + augmentations=augmentations, + ) + self.seismic = np.load(seismic_path) + self.labels = np.load(labels_path) + # We are in train/val mode. Most likely the test splits are not saved yet, + # so don't attempt to load them. + self.split = split + for split in ["train", "val", "train_val"]: + # reading the file names for 'train', 'val', 'trainval'"" + txt_path = path.join( + DATA_ROOT, "splits", "patch_" + split + ".txt" + ) + patch_list = tuple(open(txt_path, "r")) + self.patches[split] = patch_list + + def __getitem__(self, index): + + patch_name = self.patches[self.split][index] + direction, idx, xdx, ddx = patch_name.split(sep="_") + + # Shift offsets the padding that is added in training + # shift = self.patch_size if "test" not in self.split else 0 + # TODO: Remember we are cancelling the shift since we no longer pad + shift = 0 + idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift + + if direction == "i": + im = self.seismic[ + idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size + ] + lbl = self.labels[ + idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size + ] + elif direction == "x": + im = self.seismic[ + idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size + ] + lbl = self.labels[ + idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size + ] + + im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) + + # TODO: Add check for rotation augmentations and raise warning if found + if self.augmentations is not None: + augmented_dict = self.augmentations(image=im, mask=lbl) + im, lbl = augmented_dict["image"], augmented_dict["mask"] + + im = add_patch_depth_channels(im) + + if self.is_transform: + im, lbl = self.transform(im, lbl) + return im, lbl + + +def _transform_CHW_to_HWC(numpy_array): + return np.moveaxis(numpy_array, 0, -1) + + +def _transform_HWC_to_CHW(numpy_array): + return np.moveaxis(numpy_array, -1, 0) + + +class TrainPatchLoaderWithSectionDepth(TrainPatchLoader): + def __init__( + self, + split="train", + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=TRAIN_SEISMIC, + labels_path=TRAIN_LABELS, + ): + super(TrainPatchLoaderWithSectionDepth, self).__init__( + split=split, + stride=stride, + patch_size=patch_size, + is_transform=is_transform, + augmentations=augmentations, + seismic_path=seismic_path, + labels_path=labels_path, + ) + self.seismic = add_section_depth_channels(self.seismic) + + def __getitem__(self, index): + + patch_name = self.patches[self.split][index] + direction, idx, xdx, ddx = patch_name.split(sep="_") + + # Shift offsets the padding that is added in training + # shift = self.patch_size if "test" not in self.split else 0 + # TODO: Remember we are cancelling the shit since we no longer pad + shift = 0 + idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift + if direction == "i": + im = self.seismic[ + idx, + :, + xdx : xdx + self.patch_size, + ddx : ddx + self.patch_size, + ] + lbl = self.labels[ + idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size + ] + elif direction == "x": + im = self.seismic[ + idx : idx + self.patch_size, + :, + xdx, + ddx : ddx + self.patch_size, + ] + lbl = self.labels[ + idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size + ] + im = np.swapaxes(im, 0, 1) # From WCH to CWH + + im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) + + if self.augmentations is not None: + im = _transform_CHW_to_HWC(im) + augmented_dict = self.augmentations(image=im, mask=lbl) + im, lbl = augmented_dict["image"], augmented_dict["mask"] + im = _transform_HWC_to_CHW(im) + + if self.is_transform: + im, lbl = self.transform(im, lbl) + return im, lbl + + +_TRAIN_LOADERS = { + "section": TrainPatchLoaderWithSectionDepth, + "patch": TrainPatchLoaderWithDepth, +} + + +def get_train_loader(cfg): + return _TRAIN_LOADERS.get(cfg.TRAIN.DEPTH, TrainPatchLoader) + + +_TEST_LOADERS = { + "section": TestSectionLoaderWithDepth, +} + + +def get_test_loader(cfg): + return _TEST_LOADERS.get(cfg.TRAIN.DEPTH, TestSectionLoader) + + +def add_patch_depth_channels(image_array): + """Add 2 extra channels to a 1 channel numpy array + One channel is a linear sequence from 0 to 1 starting from the top of the image to the bottom + The second channel is the product of the input channel and the 'depth' channel + + Args: + image_array (np.array): 1D Numpy array + + Returns: + [np.array]: 3D numpy array + """ + h, w = image_array.shape + image = np.zeros([3, h, w]) + image[0] = image_array + for row, const in enumerate(np.linspace(0, 1, h)): + image[1, row, :] = const + image[2] = image[0] * image[1] + return image + + +def add_section_depth_channels(sections_numpy): + """Add 2 extra channels to a 1 channel section + One channel is a linear sequence from 0 to 1 starting from the top of the section to the bottom + The second channel is the product of the input channel and the 'depth' channel + + Args: + sections_numpy (numpy array): 3D Matrix (NWH)Image tensor + + Returns: + [pytorch tensor]: 3D image tensor + """ + n, w, h = sections_numpy.shape + image = np.zeros([3, n, w, h]) + image[0] = sections_numpy + for row, const in enumerate(np.linspace(0, 1, h)): + image[1, :, :, row] = const + image[2] = image[0] * image[1] + return np.swapaxes(image, 0, 1) + + +def get_seismic_labels(): + return np.asarray( + [ + [69, 117, 180], + [145, 191, 219], + [224, 243, 248], + [254, 224, 144], + [252, 141, 89], + [215, 48, 39], + ] + ) + + +@curry +def decode_segmap(label_mask, n_classes=6): + """Decode segmentation class labels into a color image + Args: + label_mask (np.ndarray): an (N,H,W) array of integer values denoting + the class label at each spatial location. + Returns: + (np.ndarray): the resulting decoded color image (NCHW). + """ + label_colours = get_seismic_labels() + r = label_mask.copy() + g = label_mask.copy() + b = label_mask.copy() + for ll in range(0, n_classes): + r[label_mask == ll] = label_colours[ll, 0] + g[label_mask == ll] = label_colours[ll, 1] + b[label_mask == ll] = label_colours[ll, 2] + rgb = np.zeros( + (label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3) + ) + rgb[:, :, :, 0] = r / 255.0 + rgb[:, :, :, 1] = g / 255.0 + rgb[:, :, :, 2] = b / 255.0 + return np.transpose(rgb, (0, 3, 1, 2)) + + +def get_loader(arch): + if "patch" in arch: + return patch_loader + elif "section" in arch: + return section_loader + else: + NotImplementedError() + diff --git a/interpretation/environment/anaconda/local/src/cv-lib b/interpretation/environment/anaconda/local/src/cv-lib deleted file mode 160000 index 3cadf99f..00000000 --- a/interpretation/environment/anaconda/local/src/cv-lib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3cadf99f6a24addbe1ec77e6777bed8653336f58 diff --git a/interpretation/requirements.txt b/interpretation/requirements.txt new file mode 100644 index 00000000..61ee6929 --- /dev/null +++ b/interpretation/requirements.txt @@ -0,0 +1 @@ +numpy==1.17.0 \ No newline at end of file diff --git a/interpretation/setup.cfg b/interpretation/setup.cfg new file mode 100644 index 00000000..b7e47898 --- /dev/null +++ b/interpretation/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/interpretation/setup.py b/interpretation/setup.py new file mode 100644 index 00000000..ebc40ba1 --- /dev/null +++ b/interpretation/setup.py @@ -0,0 +1,41 @@ +import setuptools + +with open("../README.md", "r") as f: + long_description = f.read() + +with open('requirements.txt') as f: + requirements = f.read().splitlines() + + +setuptools.setup( + author="DeepSeismic Maintainers", + author_email="deepseismic@microsoft.com", + classifiers=[ + "Development Status :: 1 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Scientific/Engineering", + "Topic :: Software Development", + ], + + description="DeepSeismic", + install_requires=requirements, + license="MIT", + long_description=long_description, + long_description_content_type="text/markdown", + name="deepseismic_interpretation", + packages=setuptools.find_packages( + include=["deepseismic_interpretation", "deepseismic_interpretation.*"] + ), + platforms="any", + python_requires=">=3.6", + setup_requires=["pytest-runner"], + tests_require=["pytest"], + url="https://github.com/microsoft/deepseismic", + version="0.1.0", + zip_safe=False, +) From 1dec008af3c499643a618fb8d936e94bee2d6fbe Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Tue, 17 Sep 2019 15:47:25 +0000 Subject: [PATCH 043/207] Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width --- .../dutchf3/data.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index 4cd688df..fe483433 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -14,7 +14,7 @@ from toolz import curry from torch.utils import data -DATA_ROOT = path.join("/mnt", "alaudah") +DATA_ROOT = path.join("/mnt", "dutchf3") SPLITS = path.join(DATA_ROOT, "splits") LABELS = path.join(DATA_ROOT, "train", "train_labels.npy") @@ -57,6 +57,8 @@ def __getitem__(self, index): im = self.seismic[:, int(number), :] lbl = self.labels[:, int(number), :] + im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) + if self.augmentations is not None: augmented_dict = self.augmentations(image=im, mask=lbl) im, lbl = augmented_dict["image"], augmented_dict["mask"] @@ -104,7 +106,7 @@ class TrainSectionLoaderWithDepth(TrainSectionLoader): def __init__( self, split="train", is_transform=True, augmentations=None ): - super(TrainSectionLoader, self).__init__( + super(TrainSectionLoaderWithDepth, self).__init__( split=split, is_transform=is_transform, augmentations=augmentations ) self.seismic = add_section_depth_channels(self.seismic)#NCWH @@ -116,10 +118,10 @@ def __getitem__(self, index): if direction == "i": im = self.seismic[int(number), :, :, :] - lbl = self.labels[int(number), :, :, :] + lbl = self.labels[int(number), :, :] elif direction == "x": im = self.seismic[:, :, int(number), :] - lbl = self.labels[:, :, int(number), :] + lbl = self.labels[ :, int(number), :] im = np.swapaxes(im, 0, 1) # From WCH to CWH @@ -136,6 +138,7 @@ def __getitem__(self, index): return im, lbl + class TestSectionLoader(SectionLoader): def __init__( self, split="test1", is_transform=True, augmentations=None @@ -249,7 +252,7 @@ def __getitem__(self, index): # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 - # TODO: Remember we are cancelling the shit since we no longer pad + # TODO: Remember we are cancelling the shift since we no longer pad shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift @@ -456,7 +459,7 @@ def __getitem__(self, index): # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 - # TODO: Remember we are cancelling the shit since we no longer pad + # TODO: Remember we are cancelling the shift since we no longer pad shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift if direction == "i": @@ -494,14 +497,25 @@ def __getitem__(self, index): return im, lbl -_TRAIN_LOADERS = { +_TRAIN_PATCH_LOADERS = { "section": TrainPatchLoaderWithSectionDepth, "patch": TrainPatchLoaderWithDepth, } +_TRAIN_SECTION_LOADERS = { + "section": TrainSectionLoaderWithDepth +} + + +def get_patch_loader(cfg): + assert cfg.TRAIN.DEPTH in ["section", "patch", "none"], f"Depth {cfg.TRAIN.DEPTH} not supported for patch data. \ + Valid values: section, patch, none." + return _TRAIN_PATCH_LOADERS.get(cfg.TRAIN.DEPTH, TrainPatchLoader) -def get_train_loader(cfg): - return _TRAIN_LOADERS.get(cfg.TRAIN.DEPTH, TrainPatchLoader) +def get_section_loader(cfg): + assert cfg.TRAIN.DEPTH in ["section", "none"], f"Depth {cfg.TRAIN.DEPTH} not supported for section data. \ + Valid values: section, none." + return _TRAIN_SECTION_LOADERS.get(cfg.TRAIN.DEPTH, TrainSectionLoader) _TEST_LOADERS = { @@ -590,13 +604,3 @@ def decode_segmap(label_mask, n_classes=6): rgb[:, :, :, 1] = g / 255.0 rgb[:, :, :, 2] = b / 255.0 return np.transpose(rgb, (0, 3, 1, 2)) - - -def get_loader(arch): - if "patch" in arch: - return patch_loader - elif "section" in arch: - return section_loader - else: - NotImplementedError() - From 71a4888ff8f534775d44323cfa625be8b5093da5 Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Tue, 17 Sep 2019 17:26:29 +0000 Subject: [PATCH 044/207] Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d9315a3..61466a02 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,35 @@ pip install -e ComputerVision_fork/contrib Both repos are installed in developer mode with the -e flag. This means that to update simply go to the folder and pull the appropriate commit or branch +## Benchmarks + +### Dense Labels + +This section contains benchmarks of different algorithms for seismic interpretation on 3D seismic datasets with densely-annotated data. + +### Sparse Labels + +This section contains benchmarks of different algorithms for seismic interpretation on 3D seismic datasets with sparsely-annotated data and is organized by the levels of sparsity. + +| Model \ Dataset | Dutch F3 (Alaudah) | Penobscot | +| :---: | :---: | :---: | +Alaudah base slice | Pixel Acc, IoU
train time (s), score time (s)| | +Alaudah base patch | Pixel Acc, IoU
train time (s), score time (s)| | +HRNet slice | | | +DeepLab V3 slice | | | +| TODO: add all models | | | + + +#### Scribble-Level Labels + +We present results of algorithms which are based on scribble-level annotations, where the annotator labels a large collection of consecutive pixels with no gaps, e.g. brushstroke label. + +#### Pixel-Level Labels + +We present results of algorithms which are based on pixel-level annotations, where the annotator labels individual pixels and gaps are allowed between pixels; the annotator can also label a small neighborhood of pixels, e.g. large dot of ~100 pixels. + ### Data -The scripts expect the data to be contained in /mnt/alaudah +The scripts expect the data to be contained in /mnt/dutchf3 ## Contributing From c85ca5aa67adeea2ef9df17feaa85917daece820 Mon Sep 17 00:00:00 2001 From: Mathew Salvaris Date: Tue, 17 Sep 2019 19:45:37 +0000 Subject: [PATCH 045/207] Merged PR 211: Fixes issues left over from changes to data.py --- experiments/segmentation/dutchf3/distributed/train.py | 4 ++-- experiments/segmentation/dutchf3/local/train.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/experiments/segmentation/dutchf3/distributed/train.py b/experiments/segmentation/dutchf3/distributed/train.py index 47504a36..0c169347 100644 --- a/experiments/segmentation/dutchf3/distributed/train.py +++ b/experiments/segmentation/dutchf3/distributed/train.py @@ -33,7 +33,7 @@ ) from cv_lib.segmentation import models from deepseismic_interpretation.dutchf3.data import ( - get_train_loader, + get_patch_loader, decode_segmap, ) from cv_lib.segmentation.dutchf3.engine import ( @@ -145,7 +145,7 @@ def run(*options, cfg=None, local_rank=0): else: train_aug = val_aug = basic_aug - TrainPatchLoader = get_train_loader(config) + TrainPatchLoader = get_patch_loader(config) train_set = TrainPatchLoader( split="train", diff --git a/experiments/segmentation/dutchf3/local/train.py b/experiments/segmentation/dutchf3/local/train.py index 66289dee..761f6c75 100644 --- a/experiments/segmentation/dutchf3/local/train.py +++ b/experiments/segmentation/dutchf3/local/train.py @@ -19,7 +19,7 @@ from toolz import compose from torch.utils import data -from deepseismic_interpretation.dutchf3.data import get_train_loader, decode_segmap +from deepseismic_interpretation.dutchf3.data import get_patch_loader, decode_segmap from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, tensorboard_handlers) from cv_lib.event_handlers.logging_handlers import Evaluator @@ -104,7 +104,7 @@ def run(*options, cfg=None): else: train_aug = val_aug = basic_aug - TrainPatchLoader = get_train_loader(config) + TrainPatchLoader = get_patch_loader(config) train_set = TrainPatchLoader( split="train", From 87897637503a54bb9a60cebfc2603e231df3f1a5 Mon Sep 17 00:00:00 2001 From: Mathew Salvaris Date: Wed, 18 Sep 2019 11:51:47 +0000 Subject: [PATCH 046/207] Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py --- environment/docker/horovod/Makefile | 5 +- environment/docker/horovod/dockerfile | 6 +- .../dutchf3/distributed/configs/hrnet.yaml | 2 +- .../distributed/configs/patch_deconvnet.yaml | 2 +- .../configs/patch_deconvnet_skip.yaml | 2 +- .../distributed/configs/seresnet_unet.yaml | 2 +- .../dutchf3/distributed/configs/unet.yaml | 2 +- .../segmentation/dutchf3/distributed/run.sh | 3 - .../segmentation/dutchf3/distributed/train.py | 19 +- .../segmentation/dutchf3/distributed/train.sh | 3 + .../dutchf3/horovod/configs/hrnet.yaml | 102 +++++ .../horovod/configs/patch_deconvnet.yaml | 59 +++ .../horovod/configs/patch_deconvnet_skip.yaml | 34 ++ .../horovod/configs/seresnet_unet.yaml | 59 +++ .../dutchf3/horovod/configs/unet.yaml | 63 +++ .../segmentation/dutchf3/horovod/default.py | 111 +++++ .../segmentation/dutchf3/horovod/train.py | 411 ++++++++++++++++++ .../segmentation/dutchf3/horovod/train.sh | 3 + .../dutchf3/local/configs/hrnet.yaml | 6 +- .../local/configs/patch_deconvnet.yaml | 2 +- .../local/configs/patch_deconvnet_skip.yaml | 2 +- .../dutchf3/local/configs/seresnet_unet.yaml | 2 +- .../dutchf3/local/configs/unet.yaml | 2 +- .../segmentation/dutchf3/local/test.py | 39 +- .../segmentation/dutchf3/local/train.py | 2 + .../segmentation/dutchf3/prepare_data.py | 85 +++- .../dutchf3/data.py | 152 ++++--- 27 files changed, 1045 insertions(+), 135 deletions(-) create mode 100755 experiments/segmentation/dutchf3/distributed/train.sh create mode 100644 experiments/segmentation/dutchf3/horovod/configs/hrnet.yaml create mode 100644 experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet.yaml create mode 100644 experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet_skip.yaml create mode 100644 experiments/segmentation/dutchf3/horovod/configs/seresnet_unet.yaml create mode 100644 experiments/segmentation/dutchf3/horovod/configs/unet.yaml create mode 100644 experiments/segmentation/dutchf3/horovod/default.py create mode 100644 experiments/segmentation/dutchf3/horovod/train.py create mode 100755 experiments/segmentation/dutchf3/horovod/train.sh diff --git a/environment/docker/horovod/Makefile b/environment/docker/horovod/Makefile index 0ea5ba7d..a423b726 100644 --- a/environment/docker/horovod/Makefile +++ b/environment/docker/horovod/Makefile @@ -14,7 +14,8 @@ IMAGE_NAME:=horovod_image NAME:=horovod_container # Name of running container DATA:=/mnt -BASEDIR:=$(shell dirname $(shell dirname ${PWD})) +BASEDIR:=$(shell dirname $(shell dirname $(shell dirname ${PWD}))) +REPODIR:=$(shell dirname ${BASEDIR}) local_code_volume:=-v $(BASEDIR):/workspace volumes:=-v $(DATA):/data \ @@ -24,7 +25,7 @@ help: echo "$$PROJECT_HELP_MSG" | less build: - docker build -t $(IMAGE_NAME) -f dockerfile . + docker build -t $(IMAGE_NAME) -f dockerfile ${REPODIR} run: @echo ${BASEDIR} diff --git a/environment/docker/horovod/dockerfile b/environment/docker/horovod/dockerfile index 947c4089..0e12f455 100644 --- a/environment/docker/horovod/dockerfile +++ b/environment/docker/horovod/dockerfile @@ -60,7 +60,11 @@ RUN pip install future typing RUN pip install numpy RUN pip install https://download.pytorch.org/whl/cu100/torch-${PYTORCH_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl \ https://download.pytorch.org/whl/cu100/torchvision-${TORCHVISION_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl -RUN pip install --no-cache-dir torchvision h5py toolz pytorch-ignite pandas opencv-python fire tensorboardx scikit-learn tqdm yacs +RUN pip install --no-cache-dir torchvision h5py toolz pytorch-ignite pandas opencv-python fire tensorboardx scikit-learn tqdm yacs albumentations gitpython +COPY ComputerVision_fork/contrib /contrib +RUN pip install -e /contrib +COPY DeepSeismic /DeepSeismic +RUN pip install -e DeepSeismic/interpretation # Install Open MPI RUN mkdir /tmp/openmpi && \ diff --git a/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml b/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml index c06a1083..2fdf6d9d 100644 --- a/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml +++ b/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml @@ -13,7 +13,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] diff --git a/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml b/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml index 314da3c7..ca56f9b4 100644 --- a/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml +++ b/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml @@ -13,7 +13,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml b/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml index 93f5d5c7..d0450cc5 100644 --- a/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml +++ b/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml @@ -12,7 +12,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT: /mnt/dutchf3 DEPTH: 'no' MODEL: diff --git a/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml b/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml index ba243924..24b8ee36 100644 --- a/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml +++ b/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml @@ -13,7 +13,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/experiments/segmentation/dutchf3/distributed/configs/unet.yaml b/experiments/segmentation/dutchf3/distributed/configs/unet.yaml index 09bba307..74fdc13a 100644 --- a/experiments/segmentation/dutchf3/distributed/configs/unet.yaml +++ b/experiments/segmentation/dutchf3/distributed/configs/unet.yaml @@ -15,7 +15,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/experiments/segmentation/dutchf3/distributed/run.sh b/experiments/segmentation/dutchf3/distributed/run.sh index e9394ecd..e69de29b 100644 --- a/experiments/segmentation/dutchf3/distributed/run.sh +++ b/experiments/segmentation/dutchf3/distributed/run.sh @@ -1,3 +0,0 @@ -#!/bin/bash -export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python -m torch.distributed.launch --nproc_per_node=8 train.py --cfg configs/hrnet.yaml \ No newline at end of file diff --git a/experiments/segmentation/dutchf3/distributed/train.py b/experiments/segmentation/dutchf3/distributed/train.py index 0c169347..84c5cc16 100644 --- a/experiments/segmentation/dutchf3/distributed/train.py +++ b/experiments/segmentation/dutchf3/distributed/train.py @@ -148,6 +148,7 @@ def run(*options, cfg=None, local_rank=0): TrainPatchLoader = get_patch_loader(config) train_set = TrainPatchLoader( + config.DATASET.ROOT, split="train", is_transform=True, stride=config.TRAIN.STRIDE, @@ -157,6 +158,7 @@ def run(*options, cfg=None, local_rank=0): logger.info(f"Training examples {len(train_set)}") val_set = TrainPatchLoader( + config.DATASET.ROOT, split="val", is_transform=True, stride=config.TRAIN.STRIDE, @@ -247,14 +249,6 @@ def run(*options, cfg=None, local_rank=0): Events.EPOCH_STARTED, update_sampler_epoch(train_loader) ) - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) - ) - if silence_other_ranks & local_rank != 0: logging.getLogger("ignite.engine.engine.Engine").setLevel( logging.WARNING @@ -298,6 +292,15 @@ def _select_pred_and_mask(model_out_dict): ) if local_rank == 0: # Run only on master process + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) + output_dir = generate_path( config.OUTPUT_DIR, git_branch(), diff --git a/experiments/segmentation/dutchf3/distributed/train.sh b/experiments/segmentation/dutchf3/distributed/train.sh new file mode 100755 index 00000000..e9394ecd --- /dev/null +++ b/experiments/segmentation/dutchf3/distributed/train.sh @@ -0,0 +1,3 @@ +#!/bin/bash +export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH +python -m torch.distributed.launch --nproc_per_node=8 train.py --cfg configs/hrnet.yaml \ No newline at end of file diff --git a/experiments/segmentation/dutchf3/horovod/configs/hrnet.yaml b/experiments/segmentation/dutchf3/horovod/configs/hrnet.yaml new file mode 100644 index 00000000..2fdf6d9d --- /dev/null +++ b/experiments/segmentation/dutchf3/horovod/configs/hrnet.yaml @@ -0,0 +1,102 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/dutchf3 + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + + +MODEL: + NAME: seg_hrnet + IN_CHANNELS: 3 + PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "section" #"patch" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/ccb7206b41dc7411609705e49d9f4c2d74c6eb88/seg_hrnet/Aug30_141919/models/seg_hrnet_running_model_18.pth" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 # + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right diff --git a/experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet.yaml b/experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet.yaml new file mode 100644 index 00000000..ca56f9b4 --- /dev/null +++ b/experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet.yaml @@ -0,0 +1,59 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/dutchf3 + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: patch_deconvnet + IN_CHANNELS: 1 + + +TRAIN: + BATCH_SIZE_PER_GPU: 64 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "No" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 99 + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + +VALIDATION: + BATCH_SIZE_PER_GPU: 512 + +TEST: + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/5cc37bbe5302e1989ef1388d629400a16f82d1a9/patch_deconvnet/Aug27_200339/models/patch_deconvnet_snapshot1model_50.pth" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 99 + CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right diff --git a/experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet_skip.yaml b/experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet_skip.yaml new file mode 100644 index 00000000..d0450cc5 --- /dev/null +++ b/experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet_skip.yaml @@ -0,0 +1,34 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/dutchf3 + DEPTH: 'no' + +MODEL: + NAME: patch_deconvnet_skip + IN_CHANNELS: 1 + + +TRAIN: + BATCH_SIZE_PER_GPU: 64 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + +TEST: + BATCH_SIZE_PER_GPU: 128 diff --git a/experiments/segmentation/dutchf3/horovod/configs/seresnet_unet.yaml b/experiments/segmentation/dutchf3/horovod/configs/seresnet_unet.yaml new file mode 100644 index 00000000..24b8ee36 --- /dev/null +++ b/experiments/segmentation/dutchf3/horovod/configs/seresnet_unet.yaml @@ -0,0 +1,59 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/dutchf3 + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: resnet_unet + IN_CHANNELS: 3 + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "section" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/dc2e2d20b7f6d508beb779ffff37c77d0139e588/resnet_unet/Sep01_125513/models/resnet_unet_snapshot1model_52.pth" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right \ No newline at end of file diff --git a/experiments/segmentation/dutchf3/horovod/configs/unet.yaml b/experiments/segmentation/dutchf3/horovod/configs/unet.yaml new file mode 100644 index 00000000..74fdc13a --- /dev/null +++ b/experiments/segmentation/dutchf3/horovod/configs/unet.yaml @@ -0,0 +1,63 @@ +# UNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/dutchf3 + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: resnet_unet + IN_CHANNELS: 3 + + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "section" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right + diff --git a/experiments/segmentation/dutchf3/horovod/default.py b/experiments/segmentation/dutchf3/horovod/default.py new file mode 100644 index 00000000..60286bf7 --- /dev/null +++ b/experiments/segmentation/dutchf3/horovod/default.py @@ -0,0 +1,111 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from yacs.config import CfgNode as CN + +_C = CN() + +_C.OUTPUT_DIR = "output" +_C.LOG_DIR = "log" +_C.GPUS = (0,) +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +_C.LOG_CONFIG = "/data/home/mat/repos/DeepSeismic/logging.conf" +_C.SEED = 42 + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "" +_C.DATASET.NUM_CLASSES = 6 +_C.DATASET.CLASS_WEIGHTS = [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "patch_deconvnet" +_C.MODEL.IN_CHANNELS = 1 +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + + +# training +_C.TRAIN = CN() +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.MODEL_DIR = "models" +_C.TRAIN.AUGMENTATION = True +_C.TRAIN.STRIDE = 50 +_C.TRAIN.PATCH_SIZE = 99 +_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 +_C.TRAIN.STD = 0.21 # 0.20976548783479299 +_C.TRAIN.DEPTH = 'None' # Options are None, Patch and Section +# None adds no depth information and the num of channels remains at 1 +# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 +# Section adds depth per section so contains depth information for the whole section, channels=3 +_C.TRAIN.AUGMENTATIONS = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 +_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TRAIN.AUGMENTATIONS.PAD = CN() +_C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 +_C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 + + +# validation +_C.VALIDATION = CN() +_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 + +# TEST +_C.TEST = CN() +_C.TEST.MODEL_PATH = "" +_C.TEST.TEST_STRIDE = 10 +_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 +_C.TEST.INLINE = True +_C.TEST.CROSSLINE = True +_C.TEST.POST_PROCESSING = CN() # Model output postprocessing +_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels +_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right + +# Horovod related params +_C.HOROVOD = CN() +_C.HOROVOD.FP16 = False + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + + diff --git a/experiments/segmentation/dutchf3/horovod/train.py b/experiments/segmentation/dutchf3/horovod/train.py new file mode 100644 index 00000000..afdf8353 --- /dev/null +++ b/experiments/segmentation/dutchf3/horovod/train.py @@ -0,0 +1,411 @@ +"""Train models on Dutch F3 salt dataset + +Trains models using PyTorch DistributedDataParallel +Uses a warmup schedule that then goes into a cyclic learning rate +""" + +import logging +import logging.config +import os +from os import path + +import cv2 +import fire +import numpy as np +import torch +from albumentations import ( + Compose, + HorizontalFlip, + Normalize, + Resize, + PadIfNeeded, +) +import fire +import horovod.torch as hvd +import torch +import torch.nn.functional as F +from default import _C as config +from default import update_config +from ignite.contrib.handlers import ( + CustomPeriodicEvent, + CosineAnnealingScheduler, + LinearCyclicalScheduler, + ConcatScheduler, +) +from ignite.engine import Events +from toolz import curry + +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) +from cv_lib.segmentation import models +from deepseismic_interpretation.dutchf3.data import ( + get_patch_loader, + decode_segmap, +) +from cv_lib.segmentation.dutchf3.engine import ( + create_supervised_evaluator, + create_supervised_trainer, +) +from cv_lib.segmentation.dutchf3.metrics import horovod +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) +from default import _C as config +from default import update_config +from ignite.contrib.handlers import ( + ConcatScheduler, + CosineAnnealingScheduler, + LinearCyclicalScheduler, +) +from ignite.engine import Events +from ignite.utils import convert_tensor +from toolz import compose, curry +from torch.utils import data + + +def prepare_batch(batch, device=None, non_blocking=False): + x, y = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ) + +@curry +def update_sampler_epoch(data_loader, engine): + data_loader.sampler.epoch = engine.state.epoch + + +def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + hvd.init() + silence_other_ranks = True + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + torch.manual_seed(config.SEED) + torch.cuda.set_device(hvd.local_rank()) + torch.cuda.manual_seed(config.SEED) + rank, world_size = hvd.rank(), hvd.size() + + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + torch.manual_seed(config.SEED) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(config.SEED) + np.random.seed(seed=config.SEED) + # Setup Augmentations + basic_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ), + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] + ) + if config.TRAIN.AUGMENTATION: + train_aug = Compose( + [ + basic_aug, + HorizontalFlip(p=0.5), + ] + ) + val_aug = basic_aug + else: + train_aug = val_aug = basic_aug + + TrainPatchLoader = get_patch_loader(config) + + train_set = TrainPatchLoader( + config.DATASET.ROOT, + split="train", + is_transform=True, + stride=config.TRAIN.STRIDE, + patch_size=config.TRAIN.PATCH_SIZE, + augmentations=train_aug, + ) + logger.info(f"Training examples {len(train_set)}") + + val_set = TrainPatchLoader( + config.DATASET.ROOT, + split="val", + is_transform=True, + stride=config.TRAIN.STRIDE, + patch_size=config.TRAIN.PATCH_SIZE, + augmentations=val_aug + ) + logger.info(f"Validation examples {len(val_set)}") + n_classes = train_set.n_classes + + train_sampler = torch.utils.data.distributed.DistributedSampler( + train_set, num_replicas=world_size, rank=rank + ) + + train_loader = data.DataLoader( + train_set, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + sampler=train_sampler, + ) + + val_sampler = torch.utils.data.distributed.DistributedSampler( + val_set, num_replicas=world_size, rank=rank + ) + + val_loader = data.DataLoader( + val_set, + batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + sampler=val_sampler, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model = model.to(device) # Send to GPU + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + # weights are inversely proportional to the frequency of the classes in the training set + class_weights = torch.tensor( + config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False + ) + + criterion = torch.nn.CrossEntropyLoss( + weight=class_weights, ignore_index=255, reduction="mean" + ) + # Horovod: broadcast parameters & optimizer state. + hvd.broadcast_parameters(model.state_dict(), root_rank=0) + hvd.broadcast_optimizer_state(optimizer, root_rank=0) + + # Horovod: (optional) compression algorithm. + compression = hvd.Compression.fp16 if config.HOROVOD.FP16 else hvd.Compression.none + + # Horovod: wrap optimizer with DistributedOptimizer. + optimizer = hvd.DistributedOptimizer(optimizer, + named_parameters=model.named_parameters(), + compression=compression) + + # summary_writer = create_summary_writer(log_dir=config.LOG_DIR) + snapshot_duration = scheduler_step * len(train_loader) + warmup_duration = 5 * len(train_loader) + warmup_scheduler = LinearCyclicalScheduler( + optimizer, + "lr", + start_value=config.TRAIN.MAX_LR, + end_value=config.TRAIN.MAX_LR * world_size, + cycle_size=10 * len(train_loader), + ) + cosine_scheduler = CosineAnnealingScheduler( + optimizer, + "lr", + config.TRAIN.MAX_LR * world_size, + config.TRAIN.MIN_LR * world_size, + snapshot_duration, + ) + + scheduler = ConcatScheduler( + schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] + ) + + trainer = create_supervised_trainer( + model, optimizer, criterion, prepare_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles + trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) + + if silence_other_ranks & rank != 0: + logging.getLogger("ignite.engine.engine.Engine").setLevel( + logging.WARNING + ) + + def _select_pred_and_mask(model_out_dict): + return ( + model_out_dict["y_pred"].squeeze(), + model_out_dict["mask"].squeeze(), + ) + + + evaluator = create_supervised_evaluator( + model, + prepare_batch, + metrics={ + "IoU": horovod.MeanIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "nll": salt_metrics.LossMetric( + criterion, + world_size, + config.VALIDATION.BATCH_SIZE_PER_GPU, + output_transform=_select_pred_and_mask, + ), + "mca": horovod.MeanClassAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + "fiou": horovod.FrequencyWeightedIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "pixa": horovod.PixelwiseAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + }, + device=device, + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + + if rank == 0: # Run only on master process + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + logger.info(f"Logging Tensorboard to {path.join(output_dir, config.LOG_DIR)}") + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={ + "IoU": "IoU :", + "nll": "Avg loss :", + "pixa": "Pixelwise Accuracy :", + "mca": "Mean Class Accuracy :", + "fiou": "Freq Weighted IoU :", + }, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={ + "IoU": "Validation/IoU", + "nll": "Validation/Loss", + "mca": "Validation/MCA", + "fiou": "Validation/FIoU", + }, + ), + ) + def _select_max(pred_tensor): + return pred_tensor.max(1)[1] + + def _tensor_to_numpy(pred_tensor): + return pred_tensor.squeeze().cpu().numpy() + + transform_func = compose( + np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy + ) + + transform_pred = compose(transform_func, _select_max) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Mask", + "mask", + transform_func=transform_func, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Pred", + "y_pred", + transform_func=transform_pred, + ), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + path.join(output_dir, config.TRAIN.MODEL_DIR), + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + + logger.info("Starting training") + + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/experiments/segmentation/dutchf3/horovod/train.sh b/experiments/segmentation/dutchf3/horovod/train.sh new file mode 100755 index 00000000..1dd57685 --- /dev/null +++ b/experiments/segmentation/dutchf3/horovod/train.sh @@ -0,0 +1,3 @@ +#!/bin/bash +export PYTHONPATH=/workspace:$PYTHONPATH +mpirun -np 8 -bind-to none -map-by slot -x NCCL_DEBUG=INFO -x LD_LIBRARY_PATH -x PATH -mca pml ob1 -mca btl ^openib python train.py LOG_CONFIG /workspace/logging.conf DATASET.ROOT /data/dutchf3 MODEL.PRETRAINED /data/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth --cfg "/workspace/experiments/segmentation/dutchf3/horovod/configs/hrnet.yaml" \ No newline at end of file diff --git a/experiments/segmentation/dutchf3/local/configs/hrnet.yaml b/experiments/segmentation/dutchf3/local/configs/hrnet.yaml index c06a1083..01f37893 100644 --- a/experiments/segmentation/dutchf3/local/configs/hrnet.yaml +++ b/experiments/segmentation/dutchf3/local/configs/hrnet.yaml @@ -13,7 +13,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] @@ -89,10 +89,10 @@ TRAIN: VALIDATION: - BATCH_SIZE_PER_GPU: 32 + BATCH_SIZE_PER_GPU: 128 TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/ccb7206b41dc7411609705e49d9f4c2d74c6eb88/seg_hrnet/Aug30_141919/models/seg_hrnet_running_model_18.pth" + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/dutchf3/local/output/mat/exp/237c16780794800631c3f1895cacc475e15aca99/seg_hrnet/Sep17_115731/models/seg_hrnet_running_model_33.pth" TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True diff --git a/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml b/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml index 314da3c7..ca56f9b4 100644 --- a/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml +++ b/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml @@ -13,7 +13,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml b/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml index 2171b5d5..cf03ff37 100644 --- a/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml +++ b/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml @@ -12,7 +12,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml b/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml index ba243924..24b8ee36 100644 --- a/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml +++ b/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml @@ -13,7 +13,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/experiments/segmentation/dutchf3/local/configs/unet.yaml b/experiments/segmentation/dutchf3/local/configs/unet.yaml index 09bba307..fd614866 100644 --- a/experiments/segmentation/dutchf3/local/configs/unet.yaml +++ b/experiments/segmentation/dutchf3/local/configs/unet.yaml @@ -15,7 +15,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/alaudah + ROOT:/mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/experiments/segmentation/dutchf3/local/test.py b/experiments/segmentation/dutchf3/local/test.py index ec66be9f..a68c3618 100644 --- a/experiments/segmentation/dutchf3/local/test.py +++ b/experiments/segmentation/dutchf3/local/test.py @@ -19,12 +19,13 @@ import numpy as np import torch import torch.nn.functional as F -from albumentations import (Compose, Normalize, - PadIfNeeded, Resize) +from albumentations import Compose, Normalize, PadIfNeeded, Resize from cv_lib.segmentation import models -from cv_lib.segmentation.dutchf3.data import (add_patch_depth_channels, - get_seismic_labels, - get_test_loader) +from deepseismic_interpretation.dutchf3.data import ( + add_patch_depth_channels, + get_seismic_labels, + get_test_loader, +) from default import _C as config from default import update_config from toolz import compose, curry, itertoolz, pipe @@ -39,8 +40,6 @@ "zechstein", ] -DATA_ROOT = path.join("/mnt", "alaudah") - class runningScore(object): def __init__(self, n_classes): @@ -198,10 +197,16 @@ def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): def _output_processing_pipeline(config, output): output = output.unsqueeze(0) _, _, h, w = output.shape - if config.TEST.POST_PROCESSING.SIZE != h or config.TEST.POST_PROCESSING.SIZE != w: + if ( + config.TEST.POST_PROCESSING.SIZE != h + or config.TEST.POST_PROCESSING.SIZE != w + ): output = F.interpolate( output, - size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE), + size=( + config.TEST.POST_PROCESSING.SIZE, + config.TEST.POST_PROCESSING.SIZE, + ), mode="bilinear", ) @@ -210,8 +215,10 @@ def _output_processing_pipeline(config, output): output = output[ :, :, - config.TEST.POST_PROCESSING.CROP_PIXELS : h - config.TEST.POST_PROCESSING.CROP_PIXELS, - config.TEST.POST_PROCESSING.CROP_PIXELS : w - config.TEST.POST_PROCESSING.CROP_PIXELS, + config.TEST.POST_PROCESSING.CROP_PIXELS : h + - config.TEST.POST_PROCESSING.CROP_PIXELS, + config.TEST.POST_PROCESSING.CROP_PIXELS : w + - config.TEST.POST_PROCESSING.CROP_PIXELS, ] return output.squeeze() @@ -303,7 +310,10 @@ def _evaluate_split( TestSectionLoader = get_test_loader(config) test_set = TestSectionLoader( - split=split, is_transform=True, augmentations=section_aug + config.DATASET.ROOT, + split=split, + is_transform=True, + augmentations=section_aug, ) n_classes = test_set.n_classes @@ -337,7 +347,6 @@ def _evaluate_split( gt = labels.numpy() running_metrics_split.update(gt, pred) running_metrics_overall.update(gt, pred) - # get scores score, class_iou = running_metrics_split.get_scores() @@ -433,10 +442,10 @@ def test(*options, cfg=None): ) for sdx, split in enumerate(splits): labels = np.load( - path.join(DATA_ROOT, "test_once", split + "_labels.npy") + path.join(config.DATASEST.ROOT, "test_once", split + "_labels.npy") ) section_file = path.join( - DATA_ROOT, "splits", "section_" + split + ".txt" + config.DATASEST.ROOT, "splits", "section_" + split + ".txt" ) _write_section_file(labels, section_file) _evaluate_split( diff --git a/experiments/segmentation/dutchf3/local/train.py b/experiments/segmentation/dutchf3/local/train.py index 761f6c75..2e898063 100644 --- a/experiments/segmentation/dutchf3/local/train.py +++ b/experiments/segmentation/dutchf3/local/train.py @@ -107,6 +107,7 @@ def run(*options, cfg=None): TrainPatchLoader = get_patch_loader(config) train_set = TrainPatchLoader( + config.DATASET.ROOT, split="train", is_transform=True, stride=config.TRAIN.STRIDE, @@ -115,6 +116,7 @@ def run(*options, cfg=None): ) val_set = TrainPatchLoader( + config.DATASET.ROOT, split="val", is_transform=True, stride=config.TRAIN.STRIDE, diff --git a/experiments/segmentation/dutchf3/prepare_data.py b/experiments/segmentation/dutchf3/prepare_data.py index ba17b3ea..ca7d6319 100644 --- a/experiments/segmentation/dutchf3/prepare_data.py +++ b/experiments/segmentation/dutchf3/prepare_data.py @@ -25,7 +25,9 @@ def _get_labels_path(data_dir): def _write_split_files(splits_path, train_list, test_list, loader_type): - file_object = open(path.join(splits_path, loader_type + "_train_val.txt"), "w") + file_object = open( + path.join(splits_path, loader_type + "_train_val.txt"), "w" + ) file_object.write("\n".join(train_list + test_list)) file_object.close() file_object = open(path.join(splits_path, loader_type + "_train.txt"), "w") @@ -38,7 +40,7 @@ def _write_split_files(splits_path, train_list, test_list, loader_type): def _get_aline_range(aline, per_val): # Inline sections - test_aline = math.floor(aline * per_val/2) + test_aline = math.floor(aline * per_val / 2) test_aline_range = itertools.chain( range(0, test_aline), range(aline - test_aline, aline) ) @@ -60,7 +62,7 @@ def split_section_train_val(data_dir, per_val=0.2, log_config=None): logger = logging.getLogger(__name__) - logger.info('Splitting data into sections .... ') + logger.info("Splitting data into sections .... ") logger.info(f"Reading data from {data_dir}") labels_path = _get_labels_path(data_dir) @@ -87,12 +89,13 @@ def split_section_train_val(data_dir, per_val=0.2, log_config=None): _write_split_files(splits_path, train_list, test_list, "section") -def split_patch_train_val(data_dir, stride, per_val=0.2, log_config=None): +def split_patch_train_val(data_dir, stride, patch, per_val=0.2, log_config=None): """Generate train and validation files for Netherlands F3 dataset. Args: data_dir (str): data directory path stride (int): stride to use when sectioning of the volume + patch (int): size of patch to extract per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. """ @@ -101,7 +104,7 @@ def split_patch_train_val(data_dir, stride, per_val=0.2, log_config=None): logger = logging.getLogger(__name__) - logger.info('Splitting data into patches .... ') + logger.info("Splitting data into patches .... ") logger.info(f"Reading data from {data_dir}") labels_path = _get_labels_path(data_dir) @@ -118,8 +121,8 @@ def split_patch_train_val(data_dir, stride, per_val=0.2, log_config=None): # Generate patches from sections # Process inlines - horz_locations = range(0, xline - stride, stride) - vert_locations = range(0, depth - stride, stride) + horz_locations = range(0, xline - patch, stride) + vert_locations = range(0, depth - patch, stride) logger.debug("Generating Inline patches") logger.debug(horz_locations) logger.debug(vert_locations) @@ -140,8 +143,8 @@ def _i_extract_patches(iline_range, horz_locations, vert_locations): ) # Process crosslines - horz_locations = range(0, iline - stride, stride) - vert_locations = range(0, depth - stride, stride) + horz_locations = range(0, iline - patch, stride) + vert_locations = range(0, depth - patch, stride) def _x_extract_patches(xline_range, horz_locations, vert_locations): for j in xline_range: @@ -157,7 +160,7 @@ def _x_extract_patches(xline_range, horz_locations, vert_locations): train_x_list = list( _x_extract_patches(train_xline_range, horz_locations, vert_locations) ) - + train_list = train_x_list + train_i_list test_list = test_x_list + test_i_list @@ -209,7 +212,10 @@ def split_alaudah_et_al_19( warnings.warn("THIS CREATES OVERLAPPING TRAINING AND VALIDATION SETS") - assert loader_type in ["section", "patch"], f"Loader type {loader_type} is not valid. \ + assert loader_type in [ + "section", + "patch", + ], f"Loader type {loader_type} is not valid. \ Please specify either 'section' or 'patch' for loader_type" # create inline and crossline pacthes for training and validation: @@ -223,10 +229,10 @@ def split_alaudah_et_al_19( iline, xline, depth = labels.shape logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") - if (loader_type == "section"): - i_list = ["i_" + str(i) for i in range(iline)] + if loader_type == "section": + i_list = ["i_" + str(i) for i in range(iline)] x_list = ["x_" + str(x) for x in range(xline)] - elif (loader_type == "patch"): + elif loader_type == "patch": i_list = [] horz_locations = range(0, xline - stride, stride) vert_locations = range(0, depth - stride, stride) @@ -236,9 +242,12 @@ def split_alaudah_et_al_19( for i in range(iline): # for every inline: # images are references by top-left corner: - locations = [[j, k] for j in horz_locations for k in vert_locations] + locations = [ + [j, k] for j in horz_locations for k in vert_locations + ] patches_list = [ - "i_" + str(i) + "_" + str(j) + "_" + str(k) for j, k in locations + "i_" + str(i) + "_" + str(j) + "_" + str(k) + for j, k in locations ] i_list.append(patches_list) @@ -251,9 +260,12 @@ def split_alaudah_et_al_19( for j in range(xline): # for every xline: # images are references by top-left corner: - locations = [[i, k] for i in horz_locations for k in vert_locations] + locations = [ + [i, k] for i in horz_locations for k in vert_locations + ] patches_list = [ - "x_" + str(i) + "_" + str(j) + "_" + str(k) for i, k in locations + "x_" + str(i) + "_" + str(j) + "_" + str(k) + for i, k in locations ] x_list.append(patches_list) @@ -263,23 +275,50 @@ def split_alaudah_et_al_19( list_train_val = i_list + x_list # create train and test splits: - train_list, test_list = train_test_split(list_train_val, test_size=fraction_validation, shuffle=True) + train_list, test_list = train_test_split( + list_train_val, test_size=fraction_validation, shuffle=True + ) # write to files to disk: splits_path = _get_splits_path(data_dir) _write_split_files(splits_path, train_list, test_list, loader_type) +#TODO: Try https://github.com/Chilipp/docrep for doscstring reuse +class SplitTrainValCLI(object): + + def section(self, data_dir, per_val=0.2, log_config=None): + """Generate section based train and validation files for Netherlands F3 dataset. + + Args: + data_dir (str): data directory path + per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + log_config (str): path to log configurations + """ + return split_section_train_val(data_dir, per_val=per_val, log_config=log_config) + + def patch(self, data_dir, stride, patch, per_val=0.2, log_config=None): + """Generate patch based train and validation files for Netherlands F3 dataset. + + Args: + data_dir (str): data directory path + stride (int): stride to use when sectioning of the volume + patch (int): size of patch to extract + per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + log_config (str): path to log configurations + """ + return split_patch_train_val(data_dir, stride, patch, per_val=per_val, log_config=log_config) + if __name__ == "__main__": """Example: - python prepare_data.py split_train_val --data-dir=/mnt/dutch --loader-type="section" + python prepare_data.py split_train_val section --data-dir=/mnt/dutch or - python prepare_data.py split_train_val --data-dir=/mnt/dutch --loader-type="patch" --stride=50 + python prepare_data.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=100 """ fire.Fire( { - "split_train_val": run_split_func, - "split_alaudah_et_al_19": split_alaudah_et_al_19 + "split_train_val": SplitTrainValCLI, + "split_alaudah_et_al_19": split_alaudah_et_al_19, } ) diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index fe483433..fa783609 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -14,29 +14,38 @@ from toolz import curry from torch.utils import data -DATA_ROOT = path.join("/mnt", "dutchf3") -SPLITS = path.join(DATA_ROOT, "splits") -LABELS = path.join(DATA_ROOT, "train", "train_labels.npy") -TRAIN_PATH = path.join(DATA_ROOT, "train") -TEST_PATH = path.join(DATA_ROOT, "test_once") -TRAIN_SEISMIC = path.join(TRAIN_PATH, "train_seismic.npy") -TRAIN_LABELS = path.join(TRAIN_PATH, "train_labels.npy") -TEST1_SEISMIC = path.join(TEST_PATH, "test1_seismic.npy") -TEST2_SEISMIC = path.join(TEST_PATH, "test2_seismic.npy") +def _train_data_for(data_dir): + return path.join(data_dir, "train", "train_seismic.npy") -TEST1_LABELS = path.join(TEST_PATH, "test1_labels.npy") -TEST2_LABELS = path.join(TEST_PATH, "test2_labels.npy") +def _train_labels_for(data_dir): + return path.join(data_dir, "train", "train_labels.npy") + + +def _test1_data_for(data_dir): + return path.join(data_dir, "test_once", "test1_seismic.npy") + + +def _test1_labels_for(data_dir): + return path.join(data_dir, "test_once", "test1_labels.npy") + + +def _test2_data_for(data_dir): + return path.join(data_dir, "test_once", "test2_seismic.npy") + + +def _test2_labels_for(data_dir): + return path.join(data_dir, "test_once", "test2_labels.npy") class SectionLoader(data.Dataset): def __init__( - self, split="train", is_transform=True, augmentations=None + self, data_dir, split="train", is_transform=True, augmentations=None ): self.split = split - self.root = DATA_ROOT + self.data_dir = data_dir self.is_transform = is_transform self.augmentations = augmentations self.n_classes = 6 @@ -77,24 +86,22 @@ def transform(self, img, lbl): class TrainSectionLoader(SectionLoader): - def __init__( - self, split="train", is_transform=True, augmentations=None + self, data_dir, split="train", is_transform=True, augmentations=None ): super(TrainSectionLoader, self).__init__( - split=split, is_transform=is_transform, augmentations=augmentations + data_dir, + split=split, + is_transform=is_transform, + augmentations=augmentations, ) - self.seismic = np.load( - path.join(DATA_ROOT, "train", "train_seismic.npy") - ) - self.labels = np.load( - path.join(DATA_ROOT, "train", "train_labels.npy") - ) + self.seismic = np.load(_train_data_for(self.data_dir)) + self.labels = np.load(_train_labels_for(self.data_dir)) for split in ["train", "val", "train_val"]: # reading the file names for 'train', 'val', 'trainval'"" txt_path = path.join( - DATA_ROOT, "splits", "section_" + split + ".txt" + self.data_dir, "splits", "section_" + split + ".txt" ) file_list = tuple(open(txt_path, "r")) file_list = [id_.rstrip() for id_ in file_list] @@ -102,21 +109,23 @@ def __init__( class TrainSectionLoaderWithDepth(TrainSectionLoader): - def __init__( - self, split="train", is_transform=True, augmentations=None + self, data_dir, split="train", is_transform=True, augmentations=None ): - super(TrainSectionLoaderWithDepth, self).__init__( - split=split, is_transform=is_transform, augmentations=augmentations + super(TrainSectionLoader, self).__init__( + data_dir, + split=split, + is_transform=is_transform, + augmentations=augmentations, ) - self.seismic = add_section_depth_channels(self.seismic)#NCWH + self.seismic = add_section_depth_channels(self.seismic) # NCWH def __getitem__(self, index): section_name = self.sections[self.split][index] direction, number = section_name.split(sep="_") - if direction == "i": + if direction == "i": im = self.seismic[int(number), :, :, :] lbl = self.labels[int(number), :, :] elif direction == "x": @@ -141,31 +150,26 @@ def __getitem__(self, index): class TestSectionLoader(SectionLoader): def __init__( - self, split="test1", is_transform=True, augmentations=None + self, data_dir, split="test1", is_transform=True, augmentations=None ): super(TestSectionLoader, self).__init__( - split=split, is_transform=is_transform, augmentations=augmentations + data_dir, + split=split, + is_transform=is_transform, + augmentations=augmentations, ) if "test1" in self.split: - self.seismic = np.load( - path.join(DATA_ROOT, "test_once", "test1_seismic.npy") - ) - self.labels = np.load( - path.join(DATA_ROOT, "test_once", "test1_labels.npy") - ) + self.seismic = np.load(_test1_data_for(self.data_dir)) + self.labels = np.load(_test1_labels_for(self.data_dir)) elif "test2" in self.split: - self.seismic = np.load( - path.join(DATA_ROOT, "test_once", "test2_seismic.npy") - ) - self.labels = np.load( - path.join(DATA_ROOT, "test_once", "test2_labels.npy") - ) - + self.seismic = np.load(_test2_data_for(self.data_dir)) + self.labels = np.load(_test2_labels_for(self.data_dir)) + # We are in test mode. Only read the given split. The other one might not # be available. txt_path = path.join( - DATA_ROOT, "splits", "section_" + split + ".txt" + self.data_dir, "splits", "section_" + split + ".txt" ) file_list = tuple(open(txt_path, "r")) file_list = [id_.rstrip() for id_ in file_list] @@ -174,20 +178,23 @@ def __init__( class TestSectionLoaderWithDepth(TestSectionLoader): def __init__( - self, split="test1", is_transform=True, augmentations=None + self, data_dir, split="test1", is_transform=True, augmentations=None ): super(TestSectionLoaderWithDepth, self).__init__( - split=split, is_transform=is_transform, augmentations=augmentations + data_dir, + split=split, + is_transform=is_transform, + augmentations=augmentations, ) - self.seismic = add_section_depth_channels(self.seismic)#NCWH + self.seismic = add_section_depth_channels(self.seismic) # NCWH def __getitem__(self, index): section_name = self.sections[self.split][index] direction, number = section_name.split(sep="_") - if direction == "i": + if direction == "i": im = self.seismic[int(number), :, :, :] lbl = self.labels[int(number), :, :] elif direction == "x": @@ -207,7 +214,7 @@ def __getitem__(self, index): if self.is_transform: im, lbl = self.transform(im, lbl) - return im, lbl + return im, lbl def _transform_WH_to_HW(numpy_array): @@ -221,9 +228,14 @@ class PatchLoader(data.Dataset): """ def __init__( - self, stride=30, patch_size=99, is_transform=True, augmentations=None + self, + data_dir, + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, ): - self.root = DATA_ROOT + self.data_dir = data_dir self.is_transform = is_transform self.augmentations = augmentations self.n_classes = 6 @@ -292,27 +304,27 @@ def transform(self, img, lbl): class TestPatchLoader(PatchLoader): def __init__( self, + data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None, - seismic_path=TEST1_SEISMIC, - labels_path=TEST1_LABELS, ): super(TestPatchLoader, self).__init__( + data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, ) - self.seismic = np.load(seismic_path) - self.labels = np.load(labels_path) + self.seismic = np.load(_train_data_for(self.data_dir)) + self.labels = np.load(_train_labels_for(self.data_dir)) # We are in test mode. Only read the given split. The other one might not # be available. self.split = "test1" # TODO: Fix this can also be test2 txt_path = path.join( - DATA_ROOT, "splits", "patch_" + self.split + ".txt" + self.data_dir, "splits", "patch_" + self.split + ".txt" ) patch_list = tuple(open(txt_path, "r")) self.patches[split] = patch_list @@ -321,15 +333,15 @@ def __init__( class TrainPatchLoader(PatchLoader): def __init__( self, + data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, - seismic_path=TRAIN_SEISMIC, - labels_path=TRAIN_LABELS, ): super(TrainPatchLoader, self).__init__( + data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, @@ -338,15 +350,15 @@ def __init__( # self.seismic = self.pad_volume(np.load(seismic_path)) # self.labels = self.pad_volume(np.load(labels_path)) warnings.warn("This no longer pads the volume") - self.seismic = np.load(seismic_path) - self.labels = np.load(labels_path) + self.seismic = np.load(_train_data_for(self.data_dir)) + self.labels = np.load(_train_labels_for(self.data_dir)) # We are in train/val mode. Most likely the test splits are not saved yet, # so don't attempt to load them. self.split = split for split in ["train", "val", "train_val"]: # reading the file names for 'train', 'val', 'trainval'"" txt_path = path.join( - DATA_ROOT, "splits", "patch_" + split + ".txt" + self.data_dir, "splits", "patch_" + split + ".txt" ) patch_list = tuple(open(txt_path, "r")) self.patches[split] = patch_list @@ -355,29 +367,29 @@ def __init__( class TrainPatchLoaderWithDepth(TrainPatchLoader): def __init__( self, + data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, - seismic_path=TRAIN_SEISMIC, - labels_path=TRAIN_LABELS, ): super(TrainPatchLoaderWithDepth, self).__init__( + data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, ) - self.seismic = np.load(seismic_path) - self.labels = np.load(labels_path) + self.seismic = np.load(_train_data_for(self.data_dir)) + self.labels = np.load(_train_labels_for(self.data_dir)) # We are in train/val mode. Most likely the test splits are not saved yet, # so don't attempt to load them. self.split = split for split in ["train", "val", "train_val"]: # reading the file names for 'train', 'val', 'trainval'"" txt_path = path.join( - DATA_ROOT, "splits", "patch_" + split + ".txt" + self.data_dir, "splits", "patch_" + split + ".txt" ) patch_list = tuple(open(txt_path, "r")) self.patches[split] = patch_list @@ -433,22 +445,20 @@ def _transform_HWC_to_CHW(numpy_array): class TrainPatchLoaderWithSectionDepth(TrainPatchLoader): def __init__( self, + data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, - seismic_path=TRAIN_SEISMIC, - labels_path=TRAIN_LABELS, ): super(TrainPatchLoaderWithSectionDepth, self).__init__( + data_dir, split=split, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, - seismic_path=seismic_path, - labels_path=labels_path, ) self.seismic = add_section_depth_channels(self.seismic) From 7b6a2e88434acf1ab75c7c27c9391285bce96e2d Mon Sep 17 00:00:00 2001 From: Mathew Salvaris Date: Wed, 18 Sep 2019 14:19:52 +0000 Subject: [PATCH 047/207] Merged PR 222: Moves cv_lib into repo and updates setup instructions --- README.md | 11 +- cv_lib/AUTHORS.md | 1 + cv_lib/README.md | 11 + cv_lib/cv_lib/__init__.py | 0 cv_lib/cv_lib/__version__.py | 1 + cv_lib/cv_lib/event_handlers/__init__.py | 46 ++ .../cv_lib/event_handlers/azureml_handlers.py | 0 .../cv_lib/event_handlers/logging_handlers.py | 95 ++++ .../event_handlers/tensorboard_handlers.py | 67 +++ .../segmentation/dutchf3/augmentations.py | 183 +++++++ cv_lib/cv_lib/segmentation/dutchf3/engine.py | 131 +++++ .../segmentation/dutchf3/metrics/__init__.py | 151 ++++++ .../segmentation/dutchf3/metrics/apex.py | 66 +++ .../segmentation/dutchf3/metrics/horovod.py | 67 +++ cv_lib/cv_lib/segmentation/dutchf3/utils.py | 43 ++ cv_lib/cv_lib/segmentation/models/__init__.py | 8 + .../segmentation/models/patch_deconvnet.py | 323 ++++++++++++ .../models/patch_deconvnet_skip.py | 324 ++++++++++++ .../cv_lib/segmentation/models/resnet_unet.py | 350 +++++++++++++ .../segmentation/models/section_deconvnet.py | 322 ++++++++++++ .../models/section_deconvnet_skip.py | 321 ++++++++++++ .../cv_lib/segmentation/models/seg_hrnet.py | 477 ++++++++++++++++++ cv_lib/cv_lib/segmentation/models/unet.py | 122 +++++ cv_lib/cv_lib/segmentation/models/utils.py | 72 +++ .../cv_lib/segmentation/tgs_salt/__init__.py | 15 + cv_lib/cv_lib/segmentation/tgs_salt/data.py | 315 ++++++++++++ cv_lib/cv_lib/segmentation/tgs_salt/engine.py | 131 +++++ cv_lib/cv_lib/segmentation/tgs_salt/losses.py | 270 ++++++++++ .../segmentation/tgs_salt/metrics/__init__.py | 179 +++++++ .../segmentation/tgs_salt/metrics/apex.py | 102 ++++ .../segmentation/tgs_salt/metrics/horovod.py | 87 ++++ .../segmentation/tgs_salt/models/__init__.py | 1 + .../tgs_salt/models/resnet_unet.py | 349 +++++++++++++ .../segmentation/tgs_salt/models/seg_hrnet.py | 477 ++++++++++++++++++ .../segmentation/tgs_salt/models/unet.py | 132 +++++ .../cv_lib/segmentation/tgs_salt/transform.py | 187 +++++++ cv_lib/requirements.txt | 9 + cv_lib/setup.py | 50 ++ interpretation/requirements.txt | 2 +- 39 files changed, 5492 insertions(+), 6 deletions(-) create mode 100644 cv_lib/AUTHORS.md create mode 100644 cv_lib/README.md create mode 100644 cv_lib/cv_lib/__init__.py create mode 100644 cv_lib/cv_lib/__version__.py create mode 100644 cv_lib/cv_lib/event_handlers/__init__.py create mode 100644 cv_lib/cv_lib/event_handlers/azureml_handlers.py create mode 100644 cv_lib/cv_lib/event_handlers/logging_handlers.py create mode 100644 cv_lib/cv_lib/event_handlers/tensorboard_handlers.py create mode 100644 cv_lib/cv_lib/segmentation/dutchf3/augmentations.py create mode 100644 cv_lib/cv_lib/segmentation/dutchf3/engine.py create mode 100644 cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py create mode 100644 cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py create mode 100644 cv_lib/cv_lib/segmentation/dutchf3/metrics/horovod.py create mode 100644 cv_lib/cv_lib/segmentation/dutchf3/utils.py create mode 100644 cv_lib/cv_lib/segmentation/models/__init__.py create mode 100644 cv_lib/cv_lib/segmentation/models/patch_deconvnet.py create mode 100644 cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py create mode 100644 cv_lib/cv_lib/segmentation/models/resnet_unet.py create mode 100644 cv_lib/cv_lib/segmentation/models/section_deconvnet.py create mode 100644 cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py create mode 100644 cv_lib/cv_lib/segmentation/models/seg_hrnet.py create mode 100644 cv_lib/cv_lib/segmentation/models/unet.py create mode 100644 cv_lib/cv_lib/segmentation/models/utils.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/__init__.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/data.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/engine.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/losses.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/metrics/__init__.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/metrics/apex.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/metrics/horovod.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/models/__init__.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/models/resnet_unet.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/models/seg_hrnet.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/models/unet.py create mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/transform.py create mode 100644 cv_lib/requirements.txt create mode 100644 cv_lib/setup.py diff --git a/README.md b/README.md index 61466a02..9bf45e3b 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,23 @@ ## Interpretation ### Setting up Environment +Navigate to the folder where you pulled the DeepSeismic repo to + Run ```bash -conda env create -f DeepSeismic/environment/anaconda/local/environment.yml +conda env create -f environment/anaconda/local/environment.yml ``` This will create the appropriate environment to run experiments Then you will need to install the common packages for interpretation ```bash conda activate seismic-interpretation -pip install -e DeepSeismic/deepseismic_interpretation +pip install -e interpretation ``` -Then you will also need to pull computer vision contrib +Then you will also need to install cv_lib ```bash -git clone https://aicat-ongip@dev.azure.com/aicat-ongip/AI%20CAT%20OnG%20IP/_git/ComputerVision_fork -pip install -e ComputerVision_fork/contrib +pip install -e cv_lib ``` Both repos are installed in developer mode with the -e flag. This means that to update simply go to the folder and pull the appropriate commit or branch diff --git a/cv_lib/AUTHORS.md b/cv_lib/AUTHORS.md new file mode 100644 index 00000000..173bb039 --- /dev/null +++ b/cv_lib/AUTHORS.md @@ -0,0 +1 @@ +[Mathew Salvaris] [@msalvaris](http://github.com/msalvaris/) diff --git a/cv_lib/README.md b/cv_lib/README.md new file mode 100644 index 00000000..7ff14865 --- /dev/null +++ b/cv_lib/README.md @@ -0,0 +1,11 @@ +# CVLib + +A set of utility functions for computer vision + +## Install + +```bash +pip install -e . +``` + +This will install the package cv_lib \ No newline at end of file diff --git a/cv_lib/cv_lib/__init__.py b/cv_lib/cv_lib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cv_lib/cv_lib/__version__.py b/cv_lib/cv_lib/__version__.py new file mode 100644 index 00000000..99c4176c --- /dev/null +++ b/cv_lib/cv_lib/__version__.py @@ -0,0 +1 @@ +__version__ = '0.0.1' \ No newline at end of file diff --git a/cv_lib/cv_lib/event_handlers/__init__.py b/cv_lib/cv_lib/event_handlers/__init__.py new file mode 100644 index 00000000..589c131c --- /dev/null +++ b/cv_lib/cv_lib/event_handlers/__init__.py @@ -0,0 +1,46 @@ +from ignite.handlers import ModelCheckpoint +import glob +import os +from shutil import copyfile + + +class SnapshotHandler: + def __init__( + self, dir_name, filename_prefix, score_function, snapshot_function + ): + self._model_save_location = dir_name + self._running_model_prefix = filename_prefix+"_running" + self._snapshot_prefix = filename_prefix+"_snapshot" + self._snapshot_function = snapshot_function + self._snapshot_num = 1 + self._score_function = score_function + self._checkpoint_handler = self._create_checkpoint_handler() + + def _create_checkpoint_handler(self): + return ModelCheckpoint( + self._model_save_location, + self._running_model_prefix, + score_function=self._score_function, + n_saved=1, + create_dir=True, + save_as_state_dict=True, + require_empty=False + ) + + def __call__(self, engine, to_save): + self._checkpoint_handler(engine, to_save) + if self._snapshot_function(): + files = glob.glob( + os.path.join(self._model_save_location, self._running_model_prefix + "*") + ) + print(files) + name_postfix = os.path.basename(files[0]).lstrip(self._running_model_prefix) + copyfile( + files[0], + os.path.join( + self._model_save_location, + f"{self._snapshot_prefix}{self._snapshot_num}{name_postfix}", + ), + ) + self._checkpoint_handler = self._create_checkpoint_handler() # Reset the checkpoint handler + self._snapshot_num += 1 diff --git a/cv_lib/cv_lib/event_handlers/azureml_handlers.py b/cv_lib/cv_lib/event_handlers/azureml_handlers.py new file mode 100644 index 00000000..e69de29b diff --git a/cv_lib/cv_lib/event_handlers/logging_handlers.py b/cv_lib/cv_lib/event_handlers/logging_handlers.py new file mode 100644 index 00000000..a6d8f97c --- /dev/null +++ b/cv_lib/cv_lib/event_handlers/logging_handlers.py @@ -0,0 +1,95 @@ +import logging +import logging.config +from toolz import curry + + +@curry +def log_training_output(engine, log_interval=100): + logger = logging.getLogger(__name__) + + if engine.state.iteration % log_interval == 0: + logger.info(f"Epoch: {engine.state.epoch} Iter: {engine.state.iteration} loss {engine.state.output['loss']}") + + +@curry +def log_lr(optimizer, engine): + logger = logging.getLogger(__name__) + lr = [param_group["lr"] for param_group in optimizer.param_groups] + logger.info(f"lr - {lr}") + + +_DEFAULT_METRICS = {"accuracy": "Avg accuracy :", "nll": "Avg loss :"} + + +@curry +def log_metrics(log_msg, engine, metrics_dict=_DEFAULT_METRICS): + logger = logging.getLogger(__name__) + metrics = engine.state.metrics + metrics_msg = " ".join([f"{metrics_dict[k]} {metrics[k]:.2f}" for k in metrics_dict]) + logger.info( + f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}] " + + metrics_msg + ) + + +class Evaluator: + def __init__(self, evaluation_engine, data_loader): + logger = logging.getLogger(__name__) + self._evaluation_engine = evaluation_engine + self._data_loader = data_loader + + def __call__(self, engine): + self._evaluation_engine.run(self._data_loader) + + +class HorovodLRScheduler: + """ + Horovod: using `lr = base_lr * hvd.size()` from the very beginning leads to worse final + accuracy. Scale the learning rate `lr = base_lr` ---> `lr = base_lr * hvd.size()` during + the first five epochs. See https://arxiv.org/abs/1706.02677 for details. + After the warmup reduce learning rate by 10 on the 30th, 60th and 80th epochs. + """ + + def __init__( + self, + base_lr, + warmup_epochs, + cluster_size, + data_loader, + optimizer, + batches_per_allreduce, + ): + self._warmup_epochs = warmup_epochs + self._cluster_size = cluster_size + self._data_loader = data_loader + self._optimizer = optimizer + self._base_lr = base_lr + self._batches_per_allreduce = batches_per_allreduce + self._logger = logging.getLogger(__name__) + + def __call__(self, engine): + epoch = engine.state.epoch + if epoch < self._warmup_epochs: + epoch += float(engine.state.iteration + 1) / len(self._data_loader) + lr_adj = ( + 1.0 + / self._cluster_size + * (epoch * (self._cluster_size - 1) / self._warmup_epochs + 1) + ) + elif epoch < 30: + lr_adj = 1.0 + elif epoch < 60: + lr_adj = 1e-1 + elif epoch < 80: + lr_adj = 1e-2 + else: + lr_adj = 1e-3 + for param_group in self._optimizer.param_groups: + param_group["lr"] = ( + self._base_lr + * self._cluster_size + * self._batches_per_allreduce + * lr_adj + ) + self._logger.debug(f"Adjust learning rate {param_group['lr']}") + diff --git a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py new file mode 100644 index 00000000..30e12abd --- /dev/null +++ b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py @@ -0,0 +1,67 @@ +from toolz import curry +import torchvision +import logging +import logging.config + +try: + from tensorboardX import SummaryWriter +except ImportError: + raise RuntimeError( + "No tensorboardX package is found. Please install with the command: \npip install tensorboardX" + ) + + +def create_summary_writer(log_dir): + writer = SummaryWriter(logdir=log_dir) + return writer + + +def _log_model_output(log_label, summary_writer, engine): + summary_writer.add_scalar(log_label, engine.state.output["loss"], engine.state.iteration) + + +@curry +def log_training_output(summary_writer, engine): + _log_model_output("training/loss", summary_writer, engine) + + +@curry +def log_validation_output(summary_writer, engine): + _log_model_output("validation/loss", summary_writer, engine) + + +@curry +def log_lr(summary_writer, optimizer, log_interval, engine): + """[summary] + + Args: + optimizer ([type]): [description] + log_interval ([type]): iteration or epoch + summary_writer ([type]): [description] + engine ([type]): [description] + """ + lr = [param_group["lr"] for param_group in optimizer.param_groups] + summary_writer.add_scalar("lr", lr[0], getattr(engine.state, log_interval)) + +_DEFAULT_METRICS = {"accuracy": "Avg accuracy :", "nll": "Avg loss :"} + +@curry +def log_metrics(summary_writer, train_engine, log_interval, engine, metrics_dict=_DEFAULT_METRICS): + metrics = engine.state.metrics + for m in metrics_dict: + summary_writer.add_scalar(metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval)) + + +def create_image_writer(summary_writer, label, output_variable, normalize=False, transform_func=lambda x: x): + logger = logging.getLogger(__name__) + def write_to(engine): + try: + data_tensor = transform_func(engine.state.output[output_variable]) + image_grid = torchvision.utils.make_grid( + data_tensor, normalize=normalize, scale_each=True + ) + summary_writer.add_image(label, image_grid, engine.state.epoch) + except KeyError: + logger.warning("Predictions and or ground truth labels not available to report") + + return write_to \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py b/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py new file mode 100644 index 00000000..ba9c86ae --- /dev/null +++ b/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py @@ -0,0 +1,183 @@ +import math +import numbers +import random +import numpy as np + +from PIL import Image, ImageOps, ImageChops + +class Compose(object): + def __init__(self, augmentations): + self.augmentations = augmentations + + def __call__(self, img, mask): + + img, mask = Image.fromarray(img, mode=None), Image.fromarray(mask, mode='L') + assert img.size == mask.size + + for a in self.augmentations: + img, mask = a(img, mask) + return np.array(img), np.array(mask, dtype=np.uint8) + +class AddNoise(object): + def __call__(self, img, mask): + noise = np.random.normal(loc=0,scale=0.02,size=(img.size[1], img.size[0])) + return img + noise, mask + +class RandomCrop(object): + def __init__(self, size, padding=0): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + self.padding = padding + + def __call__(self, img, mask): + if self.padding > 0: + img = ImageOps.expand(img, border=self.padding, fill=0) + mask = ImageOps.expand(mask, border=self.padding, fill=0) + + assert img.size == mask.size + w, h = img.size + th, tw = self.size + if w == tw and h == th: + return img, mask + if w < tw or h < th: + return img.resize((tw, th), Image.BILINEAR), mask.resize((tw, th), Image.NEAREST) + + x1 = random.randint(0, w - tw) + y1 = random.randint(0, h - th) + return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) + + +class CenterCrop(object): + def __init__(self, size): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + + def __call__(self, img, mask): + assert img.size == mask.size + w, h = img.size + th, tw = self.size + x1 = int(round((w - tw) / 2.)) + y1 = int(round((h - th) / 2.)) + return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) + + +class RandomHorizontallyFlip(object): + def __call__(self, img, mask): + if random.random() < 0.5: + #Note: we use FLIP_TOP_BOTTOM here intentionaly. Due to the dimensions of the image, + # it ends up being a horizontal flip. + return img.transpose(Image.FLIP_TOP_BOTTOM), mask.transpose(Image.FLIP_TOP_BOTTOM) + return img, mask + +class RandomVerticallyFlip(object): + def __call__(self, img, mask): + if random.random() < 0.5: + return img.transpose(Image.FLIP_LEFT_RIGHT), mask.transpose(Image.FLIP_LEFT_RIGHT) + return img, mask + +class FreeScale(object): + def __init__(self, size): + self.size = tuple(reversed(size)) # size: (h, w) + + def __call__(self, img, mask): + assert img.size == mask.size + return img.resize(self.size, Image.BILINEAR), mask.resize(self.size, Image.NEAREST) + + +class Scale(object): + def __init__(self, size): + self.size = size + + def __call__(self, img, mask): + assert img.size == mask.size + w, h = img.size + if (w >= h and w == self.size) or (h >= w and h == self.size): + return img, mask + if w > h: + ow = self.size + oh = int(self.size * h / w) + return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) + else: + oh = self.size + ow = int(self.size * w / h) + return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) + + +class RandomSizedCrop(object): + def __init__(self, size): + self.size = size + + def __call__(self, img, mask): + assert img.size == mask.size + for attempt in range(10): + area = img.size[0] * img.size[1] + target_area = random.uniform(0.45, 1.0) * area + aspect_ratio = random.uniform(0.5, 2) + + w = int(round(math.sqrt(target_area * aspect_ratio))) + h = int(round(math.sqrt(target_area / aspect_ratio))) + + if random.random() < 0.5: + w, h = h, w + + if w <= img.size[0] and h <= img.size[1]: + x1 = random.randint(0, img.size[0] - w) + y1 = random.randint(0, img.size[1] - h) + + img = img.crop((x1, y1, x1 + w, y1 + h)) + mask = mask.crop((x1, y1, x1 + w, y1 + h)) + assert (img.size == (w, h)) + + return img.resize((self.size, self.size), Image.BILINEAR), mask.resize((self.size, self.size), + Image.NEAREST) + + # Fallback + scale = Scale(self.size) + crop = CenterCrop(self.size) + return crop(*scale(img, mask)) + + +class RandomRotate(object): + def __init__(self, degree): + self.degree = degree + + def __call__(self, img, mask): + ''' + PIL automatically adds zeros to the borders of images that rotated. To fix this + issue, the code in the botton sets anywhere in the labels (mask) that is zero to + 255 (the value used for ignore_index). + ''' + rotate_degree = random.random() * 2 * self.degree - self.degree + + img = img.rotate(rotate_degree, Image.BILINEAR) + mask = mask.rotate(rotate_degree, Image.NEAREST) + + binary_mask = Image.fromarray(np.ones([mask.size[1], mask.size[0]])) + binary_mask = binary_mask.rotate(rotate_degree, Image.NEAREST) + binary_mask = np.array(binary_mask) + + mask_arr = np.array(mask) + mask_arr[binary_mask==0] = 255 + mask = Image.fromarray(mask_arr) + + return img, mask + +class RandomSized(object): + def __init__(self, size): + self.size = size + self.scale = Scale(self.size) + self.crop = RandomCrop(self.size) + + def __call__(self, img, mask): + assert img.size == mask.size + + w = int(random.uniform(0.5, 2) * img.size[0]) + h = int(random.uniform(0.5, 2) * img.size[1]) + + img, mask = img.resize((w, h), Image.BILINEAR), mask.resize((w, h), Image.NEAREST) + + return self.crop(*self.scale(img, mask)) \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/dutchf3/engine.py b/cv_lib/cv_lib/segmentation/dutchf3/engine.py new file mode 100644 index 00000000..1bb8aa39 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/dutchf3/engine.py @@ -0,0 +1,131 @@ +import torch + +from ignite.engine.engine import Engine, State, Events +from ignite.utils import convert_tensor +import torch.nn.functional as F +from toolz import curry +from torch.nn import functional as F +import numpy as np + +def _upscale_model_output(y_pred, y): + ph, pw = y_pred.size(2), y_pred.size(3) + h, w = y.size(2), y.size(3) + if ph != h or pw != w: + y_pred = F.upsample(input=y_pred, size=(h, w), mode='bilinear') + return y_pred + +def create_supervised_trainer( + model, + optimizer, + loss_fn, + prepare_batch, + device=None, + non_blocking=False, + output_transform=lambda x, y, y_pred, loss: {"loss": loss.item()}, +): + if device: + model.to(device) + + def _update(engine, batch): + model.train() + optimizer.zero_grad() + x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) + y_pred = model(x) + y_pred = _upscale_model_output(y_pred, y) + loss = loss_fn(y_pred.squeeze(1), y.squeeze(1)) + loss.backward() + optimizer.step() + return output_transform(x, y, y_pred, loss) + + return Engine(_update) + + +@curry +def val_transform(x, y, y_pred): + return {"image":x, "y_pred": y_pred.detach(), "mask":y.detach()} + + +def create_supervised_evaluator( + model, + prepare_batch, + metrics=None, + device=None, + non_blocking=False, + output_transform=val_transform, +): + metrics = metrics or {} + + if device: + model.to(device) + + def _inference(engine, batch): + model.eval() + with torch.no_grad(): + x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) + y_pred = model(x) + y_pred = _upscale_model_output(y_pred, x) + return output_transform(x, y, y_pred) + + engine = Engine(_inference) + + for name, metric in metrics.items(): + metric.attach(engine, name) + + return engine + + +def create_supervised_trainer_apex( + model, + optimizer, + loss_fn, + prepare_batch, + device=None, + non_blocking=False, + output_transform=lambda x, y, y_pred, loss: {"loss": loss.item()}, +): + from apex import amp + + if device: + model.to(device) + + def _update(engine, batch): + model.train() + optimizer.zero_grad() + x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) + y_pred = model(x) + loss = loss_fn(y_pred.squeeze(1), y.squeeze(1)) + with amp.scale_loss(loss, optimizer) as scaled_loss: + scaled_loss.backward() + optimizer.step() + return output_transform(x, y, y_pred, loss) + + return Engine(_update) + + +# def create_supervised_evaluator_apex( +# model, +# prepare_batch, +# metrics=None, +# device=None, +# non_blocking=False, +# output_transform=lambda x, y, y_pred: (x, y, pred), +# ): +# metrics = metrics or {} + +# if device: +# model.to(device) + +# def _inference(engine, batch): +# model.eval() +# with torch.no_grad(): +# x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) +# y_pred = model(x) +# return output_transform(x, y, y_pred) + +# engine = Engine(_inference) + +# for name, metric in metrics.items(): +# metric.attach(engine, name) + +# return engine + diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py new file mode 100644 index 00000000..bd9ef9e9 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py @@ -0,0 +1,151 @@ +import numpy as np +import warnings + +import numpy as np +import torch +from ignite.metrics import Metric + + +# class runningScore(object): +# def __init__(self, n_classes): +# self.n_classes = n_classes +# self.confusion_matrix = np.zeros((n_classes, n_classes)) + +# def _fast_hist(self, label_true, label_pred, n_class): +# mask = (label_true >= 0) & (label_true < n_class) +# hist = np.bincount( +# n_class * label_true[mask].astype(int) + label_pred[mask], +# minlength=n_class ** 2, +# ).reshape(n_class, n_class) +# return hist + +# def update(self, label_trues, label_preds): +# for lt, lp in zip(label_trues, label_preds): +# self.confusion_matrix += self._fast_hist( +# lt.flatten(), lp.flatten(), self.n_classes +# ) + +# def get_scores(self): +# """Returns accuracy score evaluation result. +# - overall accuracy +# - mean accuracy +# - mean IU +# - fwavacc +# """ +# hist = self.confusion_matrix +# acc = np.diag(hist).sum() / hist.sum() +# acc_cls = np.diag(hist) / hist.sum(axis=1) +# mean_acc_cls = np.nanmean(acc_cls) +# iu = np.diag(hist) / ( +# hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) +# ) +# mean_iu = np.nanmean(iu) +# freq = ( +# hist.sum(axis=1) / hist.sum() +# ) # fraction of the pixels that come from each class +# fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() +# cls_iu = dict(zip(range(self.n_classes), iu)) + +# return ( +# { +# "Pixel Acc: ": acc, +# "Class Accuracy: ": acc_cls, +# "Mean Class Acc: ": mean_acc_cls, +# "Freq Weighted IoU: ": fwavacc, +# "Mean IoU: ": mean_iu, +# "confusion_matrix": self.confusion_matrix, +# }, +# cls_iu, +# ) + +# def reset(self): +# self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) + + +def _fast_hist(label_true, label_pred, n_class): + mask = (label_true >= 0) & (label_true < n_class) + hist = np.bincount( + n_class * label_true[mask].astype(int) + label_pred[mask], + minlength=n_class ** 2, + ).reshape(n_class, n_class) + return hist + + +def _torch_hist(label_true, label_pred, n_class): + """Calculates the confusion matrix for the labels + + Args: + label_true ([type]): [description] + label_pred ([type]): [description] + n_class ([type]): [description] + + Returns: + [type]: [description] + """ + # TODO Add exceptions + assert len(label_true.shape) == 1, "Labels need to be 1D" + assert len(label_pred.shape) == 1, "Predictions need to be 1D" + mask = (label_true >= 0) & (label_true < n_class) + hist = torch.bincount( + n_class * label_true[mask] + label_pred[mask], minlength=n_class ** 2 + ).reshape(n_class, n_class) + return hist + + +class ConfusionMatrix(Metric): + def __init__(self, num_classes, device, output_transform=lambda x: x): + self._num_classes = num_classes + self._device = device + super(ConfusionMatrix, self).__init__(output_transform=output_transform) + + def reset(self): + self._confusion_matrix = torch.zeros((self._num_classes, self._num_classes),dtype=torch.long).to(self._device) + + def update(self, output): + y_pred, y = output + #TODO: Make assertion exception + assert y.shape==y_pred.max(1)[1].squeeze().shape, "Shape not the same" + self._confusion_matrix += _torch_hist( + torch.flatten(y), + torch.flatten(y_pred.max(1)[1].squeeze()), # Get the maximum index + self._num_classes, + ) + + def compute(self): + return self._confusion_matrix.cpu().numpy() + + +class MeanIoU(ConfusionMatrix): + def compute(self): + hist = self._confusion_matrix.cpu().numpy() + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + mean_iu = np.nanmean(iu) + return mean_iu + + +class PixelwiseAccuracy(ConfusionMatrix): + def compute(self): + hist = self._confusion_matrix.cpu().numpy() + acc = np.diag(hist).sum() / hist.sum() + return acc + +class FrequencyWeightedIoU(ConfusionMatrix): + def compute(self): + hist = self._confusion_matrix.cpu().numpy() + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + freq = hist.sum(axis=1) / hist.sum() # fraction of the pixels that come from each class + fwiou = (freq[freq > 0] * iu[freq > 0]).sum() + return fwiou + + +class MeanClassAccuracy(ConfusionMatrix): + def compute(self): + hist = self._confusion_matrix.cpu().numpy() + acc = np.diag(hist).sum() / hist.sum() + acc_cls = np.diag(hist) / hist.sum(axis=1) + mean_acc_cls = np.nanmean(acc_cls) + return mean_acc_cls \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py new file mode 100644 index 00000000..44a7a3c5 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py @@ -0,0 +1,66 @@ +import torch.distributed as dist +from ignite.metrics.metric import Metric +from ignite.exceptions import NotComputableError +import torch +from cv_lib.segmentation.dutchf3 import metrics +import numpy as np + +@torch.no_grad() +def reduce_tensor(tensor, world_size): + rt = tensor.clone() + dist.all_reduce(rt, op=dist.reduce_op.SUM) + rt /= world_size + return rt + +@torch.no_grad() +def sum_reduce_tensor(tensor): + rt = tensor.clone() + dist.all_reduce(rt, op=dist.reduce_op.SUM) + return rt + +@torch.no_grad() +def gather_tensor(tensor, world_size): + gather_t = [torch.ones_like(tensor).cuda() for _ in range(dist.get_world_size())] + dist.all_gather(gather_t, tensor) + return gather_t + + +class ConfusionMatrix(metrics.ConfusionMatrix): + def compute(self): + reduced_metric = sum_reduce_tensor(self._confusion_matrix) + return reduced_metric.cpu().numpy() + +class PixelwiseAccuracy(ConfusionMatrix): + def compute(self): + hist = super(PixelwiseAccuracy,self).compute() + acc = np.diag(hist).sum() / hist.sum() + return acc + +class MeanIoU(ConfusionMatrix): + def compute(self): + hist = super(MeanIoU, self).compute() + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + mean_iu = np.nanmean(iu) + return mean_iu + + +class FrequencyWeightedIoU(ConfusionMatrix): + def compute(self): + hist = super(FrequencyWeightedIoU, self).compute() + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + freq = hist.sum(axis=1) / hist.sum() # fraction of the pixels that come from each class + fwiou = (freq[freq > 0] * iu[freq > 0]).sum() + return fwiou + + +class MeanClassAccuracy(ConfusionMatrix): + def compute(self): + hist = super(MeanClassAccuracy, self).compute() + acc = np.diag(hist).sum() / hist.sum() + acc_cls = np.diag(hist) / hist.sum(axis=1) + mean_acc_cls = np.nanmean(acc_cls) + return mean_acc_cls \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/horovod.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/horovod.py new file mode 100644 index 00000000..2fc898ff --- /dev/null +++ b/cv_lib/cv_lib/segmentation/dutchf3/metrics/horovod.py @@ -0,0 +1,67 @@ +import torch.distributed as dist +from ignite.metrics.metric import Metric +from ignite.exceptions import NotComputableError +import torch +from cv_lib.segmentation.dutchf3 import metrics +import numpy as np +from ignite.metrics.metric import Metric +from ignite.exceptions import NotComputableError +import torch +import horovod.torch as hvd + + +def reduce_tensor(tensor): + return hvd.allreduce(tensor, average=False) + + +def gather_tensor(tensor): + return hvd.allgather(tensor) + + +def sum_reduce_tensor(tensor): + return hvd.allreduce(tensor, average=False) + + +class ConfusionMatrix(metrics.ConfusionMatrix): + def compute(self): + reduced_metric = sum_reduce_tensor(self._confusion_matrix) + return reduced_metric.cpu().numpy() + + +class PixelwiseAccuracy(ConfusionMatrix): + def compute(self): + hist = super(PixelwiseAccuracy, self).compute() + acc = np.diag(hist).sum() / hist.sum() + return acc + + +class MeanIoU(ConfusionMatrix): + def compute(self): + hist = super(MeanIoU, self).compute() + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + mean_iu = np.nanmean(iu) + return mean_iu + + +class FrequencyWeightedIoU(ConfusionMatrix): + def compute(self): + hist = super(FrequencyWeightedIoU, self).compute() + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + freq = ( + hist.sum(axis=1) / hist.sum() + ) # fraction of the pixels that come from each class + fwiou = (freq[freq > 0] * iu[freq > 0]).sum() + return fwiou + + +class MeanClassAccuracy(ConfusionMatrix): + def compute(self): + hist = super(MeanClassAccuracy, self).compute() + acc = np.diag(hist).sum() / hist.sum() + acc_cls = np.diag(hist) / hist.sum(axis=1) + mean_acc_cls = np.nanmean(acc_cls) + return mean_acc_cls diff --git a/cv_lib/cv_lib/segmentation/dutchf3/utils.py b/cv_lib/cv_lib/segmentation/dutchf3/utils.py new file mode 100644 index 00000000..52bfa0bc --- /dev/null +++ b/cv_lib/cv_lib/segmentation/dutchf3/utils.py @@ -0,0 +1,43 @@ +import numpy as np +import torch +import torchvision.utils as vutils +from git import Repo +from datetime import datetime +import os + +def np_to_tb(array): + # if 2D : + if array.ndim == 2: + # HW => CHW + array = np.expand_dims(array,axis=0) + # CHW => NCHW + array = np.expand_dims(array,axis=0) + elif array.ndim == 3: + # HWC => CHW + array = array.transpose(2, 0, 1) + # CHW => NCHW + array = np.expand_dims(array,axis=0) + + array = torch.from_numpy(array) + # array = vutils.make_grid(array, normalize=True, scale_each=True) + return array + + +def current_datetime(): + return datetime.now().strftime('%b%d_%H%M%S') + + +def git_branch(): + repo = Repo(search_parent_directories=True) + return repo.active_branch.name + + +def git_hash(): + repo = Repo(search_parent_directories=True) + return repo.active_branch.commit.hexsha + +def generate_path(base_path, *directories): + path = os.path.join(base_path, *directories) + if not os.path.exists(path): + os.makedirs(path) + return path \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/models/__init__.py b/cv_lib/cv_lib/segmentation/models/__init__.py new file mode 100644 index 00000000..93967f4e --- /dev/null +++ b/cv_lib/cv_lib/segmentation/models/__init__.py @@ -0,0 +1,8 @@ + +import cv_lib.segmentation.models.seg_hrnet +import cv_lib.segmentation.models.resnet_unet +import cv_lib.segmentation.models.unet +import cv_lib.segmentation.models.section_deconvnet +import cv_lib.segmentation.models.patch_deconvnet +import cv_lib.segmentation.models.patch_deconvnet_skip +import cv_lib.segmentation.models.section_deconvnet_skip diff --git a/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py b/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py new file mode 100644 index 00000000..b45f6fc4 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py @@ -0,0 +1,323 @@ +import torch.nn as nn + +class patch_deconvnet(nn.Module): + + def __init__(self, n_classes=4, learned_billinear=False): + super(patch_deconvnet, self).__init__() + self.learned_billinear = learned_billinear + self.n_classes = n_classes + self.unpool = nn.MaxUnpool2d(2, stride=2) + self.conv_block1 = nn.Sequential( + + # conv1_1 + nn.Conv2d(1, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv1_2 + nn.Conv2d(64, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool1 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_1 + + # 48*48 + + self.conv_block2 = nn.Sequential( + + # conv2_1 + nn.Conv2d(64, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv2_2 + nn.Conv2d(128, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool2 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_2 + + # 24*24 + + self.conv_block3 = nn.Sequential( + + # conv3_1 + nn.Conv2d(128, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_2 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_3 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool3 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_3 + + # 12*12 + + self.conv_block4 = nn.Sequential( + + # conv4_1 + nn.Conv2d(256, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool4 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_4 + + # 6*6 + + self.conv_block5 = nn.Sequential( + + # conv5_1 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool5 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_5 + + # 3*3 + + self.conv_block6 = nn.Sequential( + + # fc6 + nn.Conv2d(512, 4096, 3), + # set the filter size and nor padding to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 1*1 + + self.conv_block7 = nn.Sequential( + + # fc7 + nn.Conv2d(4096, 4096, 1), + # set the filter size to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.deconv_block8 = nn.Sequential( + + # fc6-deconv + nn.ConvTranspose2d(4096, 512, 3, stride=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 3*3 + + self.unpool_block9 = nn.Sequential( + + # unpool5 + nn.MaxUnpool2d(2, stride=2), ) + # usage unpool(output, indices) + + # 6*6 + + self.deconv_block10 = nn.Sequential( + + # deconv5_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_3 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block11 = nn.Sequential( + + # unpool4 + nn.MaxUnpool2d(2, stride=2), ) + + # 12*12 + + self.deconv_block12 = nn.Sequential( + + # deconv4_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_3 + nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block13 = nn.Sequential( + + # unpool3 + nn.MaxUnpool2d(2, stride=2), ) + + # 24*24 + + self.deconv_block14 = nn.Sequential( + + # deconv3_1 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_2 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_3 + nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block15 = nn.Sequential( + + # unpool2 + nn.MaxUnpool2d(2, stride=2), ) + + # 48*48 + + self.deconv_block16 = nn.Sequential( + + # deconv2_1 + nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv2_2 + nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block17 = nn.Sequential( + + # unpool1 + nn.MaxUnpool2d(2, stride=2), ) + + # 96*96 + + self.deconv_block18 = nn.Sequential( + + # deconv1_1 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv1_2 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.seg_score19 = nn.Sequential( + + # seg-score + nn.Conv2d(64, self.n_classes, 1), ) + + if self.learned_billinear: + raise NotImplementedError + + def forward(self, x): + size0 = x.size() + conv1, indices1 = self.conv_block1(x) + size1 = conv1.size() + conv2, indices2 = self.conv_block2(conv1) + size2 = conv2.size() + conv3, indices3 = self.conv_block3(conv2) + size3 = conv3.size() + conv4, indices4 = self.conv_block4(conv3) + size4 = conv4.size() + conv5, indices5 = self.conv_block5(conv4) + + conv6 = self.conv_block6(conv5) + conv7 = self.conv_block7(conv6) + conv8 = self.deconv_block8(conv7) + conv9 = self.unpool(conv8, indices5, output_size=size4) + conv10 = self.deconv_block10(conv9) + conv11 = self.unpool(conv10, indices4, output_size=size3) + conv12 = self.deconv_block12(conv11) + conv13 = self.unpool(conv12, indices3, output_size=size2) + conv14 = self.deconv_block14(conv13) + conv15 = self.unpool(conv14, indices2, output_size=size1) + conv16 = self.deconv_block16(conv15) + conv17 = self.unpool(conv16, indices1, output_size=size0) + conv18 = self.deconv_block18(conv17) + out = self.seg_score19(conv18) + + return out + + def init_vgg16_params(self, vgg16, copy_fc8=True): + blocks = [self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5] + + ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] + features = list(vgg16.features.children()) + i_layer = 0; + # copy convolutional filters from vgg16 + for idx, conv_block in enumerate(blocks): + for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): + if i_layer == 0: + l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, + 2, :, + :]) / 3.0).view( + l2.weight.size()) + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + else: + assert l1.weight.size() == l2.weight.size() + assert l1.bias.size() == l2.bias.size() + l2.weight.data = l1.weight.data + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + + +def get_seg_model(cfg, **kwargs): + assert cfg.MODEL.IN_CHANNELS==1, f"Patch deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" + model = patch_deconvnet(n_classes=cfg.DATASET.NUM_CLASSES) + + return model diff --git a/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py b/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py new file mode 100644 index 00000000..e2f5d951 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py @@ -0,0 +1,324 @@ +import torch.nn as nn + +class patch_deconvnet_skip(nn.Module): + + def __init__(self, n_classes=4, learned_billinear=False): + super(patch_deconvnet_skip, self).__init__() + self.learned_billinear = learned_billinear + self.n_classes = n_classes + self.unpool = nn.MaxUnpool2d(2, stride=2) + self.conv_block1 = nn.Sequential( + + # conv1_1 + nn.Conv2d(1, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv1_2 + nn.Conv2d(64, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool1 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_1 + + # 48*48 + + self.conv_block2 = nn.Sequential( + + # conv2_1 + nn.Conv2d(64, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv2_2 + nn.Conv2d(128, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool2 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_2 + + # 24*24 + + self.conv_block3 = nn.Sequential( + + # conv3_1 + nn.Conv2d(128, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_2 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_3 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool3 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_3 + + # 12*12 + + self.conv_block4 = nn.Sequential( + + # conv4_1 + nn.Conv2d(256, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool4 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_4 + + # 6*6 + + self.conv_block5 = nn.Sequential( + + # conv5_1 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool5 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_5 + + # 3*3 + + self.conv_block6 = nn.Sequential( + + # fc6 + nn.Conv2d(512, 4096, 3), + # set the filter size and nor padding to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 1*1 + + self.conv_block7 = nn.Sequential( + + # fc7 + nn.Conv2d(4096, 4096, 1), + # set the filter size to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.deconv_block8 = nn.Sequential( + + # fc6-deconv + nn.ConvTranspose2d(4096, 512, 3, stride=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 3*3 + + self.unpool_block9 = nn.Sequential( + + # unpool5 + nn.MaxUnpool2d(2, stride=2), ) + # usage unpool(output, indices) + + # 6*6 + + self.deconv_block10 = nn.Sequential( + + # deconv5_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_3 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block11 = nn.Sequential( + + # unpool4 + nn.MaxUnpool2d(2, stride=2), ) + + # 12*12 + + self.deconv_block12 = nn.Sequential( + + # deconv4_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_3 + nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block13 = nn.Sequential( + + # unpool3 + nn.MaxUnpool2d(2, stride=2), ) + + # 24*24 + + self.deconv_block14 = nn.Sequential( + + # deconv3_1 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_2 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_3 + nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block15 = nn.Sequential( + + # unpool2 + nn.MaxUnpool2d(2, stride=2), ) + + # 48*48 + + self.deconv_block16 = nn.Sequential( + + # deconv2_1 + nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv2_2 + nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block17 = nn.Sequential( + + # unpool1 + nn.MaxUnpool2d(2, stride=2), ) + + # 96*96 + + self.deconv_block18 = nn.Sequential( + + # deconv1_1 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv1_2 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.seg_score19 = nn.Sequential( + + # seg-score + nn.Conv2d(64, self.n_classes, 1), ) + + if self.learned_billinear: + raise NotImplementedError + + def forward(self, x): + size0 = x.size() + conv1, indices1 = self.conv_block1(x) + size1 = conv1.size() + conv2, indices2 = self.conv_block2(conv1) + size2 = conv2.size() + conv3, indices3 = self.conv_block3(conv2) + size3 = conv3.size() + conv4, indices4 = self.conv_block4(conv3) + size4 = conv4.size() + conv5, indices5 = self.conv_block5(conv4) + + conv6 = self.conv_block6(conv5) + conv7 = self.conv_block7(conv6) + conv8 = self.deconv_block8(conv7) + conv5 + conv9 = self.unpool(conv8,indices5, output_size=size4) + conv10 = self.deconv_block10(conv9) + conv4 + conv11 = self.unpool(conv10,indices4, output_size=size3) + conv12 = self.deconv_block12(conv11) + conv3 + conv13 = self.unpool(conv12,indices3, output_size=size2) + conv14 = self.deconv_block14(conv13) + conv2 + conv15 = self.unpool(conv14,indices2, output_size=size1) + conv16 = self.deconv_block16(conv15) + conv1 + conv17 = self.unpool(conv16,indices1, output_size=size0) + conv18 = self.deconv_block18(conv17) + out = self.seg_score19(conv18) + + return out + + def init_vgg16_params(self, vgg16, copy_fc8=True): + blocks = [self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5] + + ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] + features = list(vgg16.features.children()) + i_layer = 0; + # copy convolutional filters from vgg16 + for idx, conv_block in enumerate(blocks): + for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): + if i_layer == 0: + l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, + 2, :, + :]) / 3.0).view( + l2.weight.size()) + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + else: + assert l1.weight.size() == l2.weight.size() + assert l1.bias.size() == l2.bias.size() + l2.weight.data = l1.weight.data + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + + +def get_seg_model(cfg, **kwargs): + assert cfg.MODEL.IN_CHANNELS==1, f"Patch deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" + model = patch_deconvnet_skip(n_classes=cfg.DATASET.NUM_CLASSES) + print("hello") + return model + diff --git a/cv_lib/cv_lib/segmentation/models/resnet_unet.py b/cv_lib/cv_lib/segmentation/models/resnet_unet.py new file mode 100644 index 00000000..f226125f --- /dev/null +++ b/cv_lib/cv_lib/segmentation/models/resnet_unet.py @@ -0,0 +1,350 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +import torchvision + + +class FPAv2(nn.Module): + def __init__(self, input_dim, output_dim): + super(FPAv2, self).__init__() + self.glob = nn.Sequential(nn.AdaptiveAvgPool2d(1), + nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False)) + + self.down2_1 = nn.Sequential(nn.Conv2d(input_dim, input_dim, kernel_size=5, stride=2, padding=2, bias=False), + nn.BatchNorm2d(input_dim), + nn.ELU(True)) + self.down2_2 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=5, padding=2, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True)) + + self.down3_1 = nn.Sequential(nn.Conv2d(input_dim, input_dim, kernel_size=3, stride=2, padding=1, bias=False), + nn.BatchNorm2d(input_dim), + nn.ELU(True)) + self.down3_2 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=3, padding=1, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True)) + + self.conv1 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True)) + + def forward(self, x): + # x shape: 512, 16, 16 + x_glob = self.glob(x) # 256, 1, 1 + x_glob = F.upsample(x_glob, scale_factor=16, mode='bilinear', align_corners=True) # 256, 16, 16 + + d2 = self.down2_1(x) # 512, 8, 8 + d3 = self.down3_1(d2) # 512, 4, 4 + + d2 = self.down2_2(d2) # 256, 8, 8 + d3 = self.down3_2(d3) # 256, 4, 4 + + d3 = F.upsample(d3, scale_factor=2, mode='bilinear', align_corners=True) # 256, 8, 8 + d2 = d2 + d3 + + d2 = F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True) # 256, 16, 16 + x = self.conv1(x) # 256, 16, 16 + x = x * d2 + + x = x + x_glob + + return x + + +def conv3x3(input_dim, output_dim, rate=1): + return nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=3, dilation=rate, padding=rate, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True)) + + +class SpatialAttention2d(nn.Module): + def __init__(self, channel): + super(SpatialAttention2d, self).__init__() + self.squeeze = nn.Conv2d(channel, 1, kernel_size=1, bias=False) + self.sigmoid = nn.Sigmoid() + + def forward(self, x): + z = self.squeeze(x) + z = self.sigmoid(z) + return x * z + + +class GAB(nn.Module): + def __init__(self, input_dim, reduction=4): + super(GAB, self).__init__() + self.global_avgpool = nn.AdaptiveAvgPool2d(1) + self.conv1 = nn.Conv2d(input_dim, input_dim // reduction, kernel_size=1, stride=1) + self.conv2 = nn.Conv2d(input_dim // reduction, input_dim, kernel_size=1, stride=1) + self.relu = nn.ReLU(inplace=True) + self.sigmoid = nn.Sigmoid() + + def forward(self, x): + z = self.global_avgpool(x) + z = self.relu(self.conv1(z)) + z = self.sigmoid(self.conv2(z)) + return x * z + + +class Decoder(nn.Module): + def __init__(self, in_channels, channels, out_channels): + super(Decoder, self).__init__() + self.conv1 = conv3x3(in_channels, channels) + self.conv2 = conv3x3(channels, out_channels) + self.s_att = SpatialAttention2d(out_channels) + self.c_att = GAB(out_channels, 16) + + def forward(self, x, e=None): + x = F.upsample(input=x, scale_factor=2, mode='bilinear', align_corners=True) + if e is not None: + x = torch.cat([x, e], 1) + x = self.conv1(x) + x = self.conv2(x) + s = self.s_att(x) + c = self.c_att(x) + output = s + c + return output + + +class Decoderv2(nn.Module): + def __init__(self, up_in, x_in, n_out): + super(Decoderv2, self).__init__() + up_out = x_out = n_out // 2 + self.x_conv = nn.Conv2d(x_in, x_out, 1, bias=False) + self.tr_conv = nn.ConvTranspose2d(up_in, up_out, 2, stride=2) + self.bn = nn.BatchNorm2d(n_out) + self.relu = nn.ReLU(True) + self.s_att = SpatialAttention2d(n_out) + self.c_att = GAB(n_out, 16) + + def forward(self, up_p, x_p): + up_p = self.tr_conv(up_p) + x_p = self.x_conv(x_p) + + cat_p = torch.cat([up_p, x_p], 1) + cat_p = self.relu(self.bn(cat_p)) + s = self.s_att(cat_p) + c = self.c_att(cat_p) + return s + c + + +class SCse(nn.Module): + def __init__(self, dim): + super(SCse, self).__init__() + self.satt = SpatialAttention2d(dim) + self.catt = GAB(dim) + + def forward(self, x): + return self.satt(x) + self.catt(x) + + +# stage1 model +class Res34Unetv4(nn.Module): + def __init__(self, n_classes=1): + super(Res34Unetv4, self).__init__() + self.resnet = torchvision.models.resnet34(True) + + self.conv1 = nn.Sequential( + self.resnet.conv1, + self.resnet.bn1, + self.resnet.relu) + + self.encode2 = nn.Sequential(self.resnet.layer1, + SCse(64)) + self.encode3 = nn.Sequential(self.resnet.layer2, + SCse(128)) + self.encode4 = nn.Sequential(self.resnet.layer3, + SCse(256)) + self.encode5 = nn.Sequential(self.resnet.layer4, + SCse(512)) + + self.center = nn.Sequential(FPAv2(512, 256), + nn.MaxPool2d(2, 2)) + + self.decode5 = Decoderv2(256, 512, 64) + self.decode4 = Decoderv2(64, 256, 64) + self.decode3 = Decoderv2(64, 128, 64) + self.decode2 = Decoderv2(64, 64, 64) + self.decode1 = Decoder(64, 32, 64) + + self.logit = nn.Sequential(nn.Conv2d(320, 64, kernel_size=3, padding=1), + nn.ELU(True), + nn.Conv2d(64, n_classes, kernel_size=1, bias=False)) + + def forward(self, x): + # x: (batch_size, 3, 256, 256) + + x = self.conv1(x) # 64, 128, 128 + e2 = self.encode2(x) # 64, 128, 128 + e3 = self.encode3(e2) # 128, 64, 64 + e4 = self.encode4(e3) # 256, 32, 32 + e5 = self.encode5(e4) # 512, 16, 16 + + f = self.center(e5) # 256, 8, 8 + + d5 = self.decode5(f, e5) # 64, 16, 16 + d4 = self.decode4(d5, e4) # 64, 32, 32 + d3 = self.decode3(d4, e3) # 64, 64, 64 + d2 = self.decode2(d3, e2) # 64, 128, 128 + d1 = self.decode1(d2) # 64, 256, 256 + + f = torch.cat((d1, + F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True), + F.upsample(d3, scale_factor=4, mode='bilinear', align_corners=True), + F.upsample(d4, scale_factor=8, mode='bilinear', align_corners=True), + F.upsample(d5, scale_factor=16, mode='bilinear', align_corners=True)), 1) # 320, 256, 256 + + logit = self.logit(f) # 1, 256, 256 + + return logit + + +# stage2 model +class Res34Unetv3(nn.Module): + def __init__(self): + super(Res34Unetv3, self).__init__() + self.resnet = torchvision.models.resnet34(True) + + self.conv1 = nn.Sequential( + self.resnet.conv1, + self.resnet.bn1, + self.resnet.relu) + + self.encode2 = nn.Sequential(self.resnet.layer1, + SCse(64)) + self.encode3 = nn.Sequential(self.resnet.layer2, + SCse(128)) + self.encode4 = nn.Sequential(self.resnet.layer3, + SCse(256)) + self.encode5 = nn.Sequential(self.resnet.layer4, + SCse(512)) + + self.center = nn.Sequential(FPAv2(512, 256), + nn.MaxPool2d(2, 2)) + + self.decode5 = Decoderv2(256, 512, 64) + self.decode4 = Decoderv2(64, 256, 64) + self.decode3 = Decoderv2(64, 128, 64) + self.decode2 = Decoderv2(64, 64, 64) + self.decode1 = Decoder(64, 32, 64) + + self.dropout2d = nn.Dropout2d(0.4) + self.dropout = nn.Dropout(0.4) + + self.fuse_pixel = conv3x3(320, 64) + self.logit_pixel = nn.Conv2d(64, 1, kernel_size=1, bias=False) + + self.fuse_image = nn.Sequential(nn.Linear(512, 64), + nn.ELU(True)) + self.logit_image = nn.Sequential(nn.Linear(64, 1), + nn.Sigmoid()) + self.logit = nn.Sequential(nn.Conv2d(128, 64, kernel_size=3, padding=1, bias=False), + nn.ELU(True), + nn.Conv2d(64, 1, kernel_size=1, bias=False)) + + def forward(self, x): + # x: (batch_size, 3, 256, 256) + batch_size, c, h, w = x.shape + + x = self.conv1(x) # 64, 128, 128 + e2 = self.encode2(x) # 64, 128, 128 + e3 = self.encode3(e2) # 128, 64, 64 + e4 = self.encode4(e3) # 256, 32, 32 + e5 = self.encode5(e4) # 512, 16, 16 + + e = F.adaptive_avg_pool2d(e5, output_size=1).view(batch_size, -1) # 512 + e = self.dropout(e) + + f = self.center(e5) # 256, 8, 8 + + d5 = self.decode5(f, e5) # 64, 16, 16 + d4 = self.decode4(d5, e4) # 64, 32, 32 + d3 = self.decode3(d4, e3) # 64, 64, 64 + d2 = self.decode2(d3, e2) # 64, 128, 128 + d1 = self.decode1(d2) # 64, 256, 256 + + f = torch.cat((d1, + F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True), + F.upsample(d3, scale_factor=4, mode='bilinear', align_corners=True), + F.upsample(d4, scale_factor=8, mode='bilinear', align_corners=True), + F.upsample(d5, scale_factor=16, mode='bilinear', align_corners=True)), 1) # 320, 256, 256 + f = self.dropout2d(f) + + # segmentation process + fuse_pixel = self.fuse_pixel(f) # 64, 256, 256 + logit_pixel = self.logit_pixel(fuse_pixel) # 1, 256, 256 + + # classification process + fuse_image = self.fuse_image(e) # 64 + logit_image = self.logit_image(fuse_image) # 1 + + # combine segmentation and classification + fuse = torch.cat([fuse_pixel, + F.upsample(fuse_image.view(batch_size, -1, 1, 1), scale_factor=256, mode='bilinear', + align_corners=True)], 1) # 128, 256, 256 + logit = self.logit(fuse) # 1, 256, 256 + + return logit, logit_pixel, logit_image.view(-1) + + +# stage3 model +class Res34Unetv5(nn.Module): + def __init__(self): + super(Res34Unetv5, self).__init__() + self.resnet = torchvision.models.resnet34(True) + + self.conv1 = nn.Sequential( + nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False), + self.resnet.bn1, + self.resnet.relu) + + self.encode2 = nn.Sequential(self.resnet.layer1, + SCse(64)) + self.encode3 = nn.Sequential(self.resnet.layer2, + SCse(128)) + self.encode4 = nn.Sequential(self.resnet.layer3, + SCse(256)) + self.encode5 = nn.Sequential(self.resnet.layer4, + SCse(512)) + + self.center = nn.Sequential(FPAv2(512, 256), + nn.MaxPool2d(2, 2)) + + self.decode5 = Decoderv2(256, 512, 64) + self.decode4 = Decoderv2(64, 256, 64) + self.decode3 = Decoderv2(64, 128, 64) + self.decode2 = Decoderv2(64, 64, 64) + + self.logit = nn.Sequential(nn.Conv2d(256, 32, kernel_size=3, padding=1), + nn.ELU(True), + nn.Conv2d(32, 1, kernel_size=1, bias=False)) + + def forward(self, x): + # x: batch_size, 3, 128, 128 + x = self.conv1(x) # 64, 128, 128 + e2 = self.encode2(x) # 64, 128, 128 + e3 = self.encode3(e2) # 128, 64, 64 + e4 = self.encode4(e3) # 256, 32, 32 + e5 = self.encode5(e4) # 512, 16, 16 + + f = self.center(e5) # 256, 8, 8 + + d5 = self.decode5(f, e5) # 64, 16, 16 + d4 = self.decode4(d5, e4) # 64, 32, 32 + d3 = self.decode3(d4, e3) # 64, 64, 64 + d2 = self.decode2(d3, e2) # 64, 128, 128 + + f = torch.cat((d2, + F.upsample(d3, scale_factor=2, mode='bilinear', align_corners=True), + F.upsample(d4, scale_factor=4, mode='bilinear', align_corners=True), + F.upsample(d5, scale_factor=8, mode='bilinear', align_corners=True)), 1) # 256, 128, 128 + + f = F.dropout2d(f, p=0.4) + logit = self.logit(f) # 1, 128, 128 + + return logit + + +def get_seg_model(cfg, **kwargs): + assert cfg.MODEL.IN_CHANNELS==3, f"SEResnet Unet deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 3 for cfg.MODEL.IN_CHANNELS" + model = Res34Unetv4(n_classes=cfg.DATASET.NUM_CLASSES) + return model \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/models/section_deconvnet.py b/cv_lib/cv_lib/segmentation/models/section_deconvnet.py new file mode 100644 index 00000000..d4edfd45 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/models/section_deconvnet.py @@ -0,0 +1,322 @@ +import torch.nn as nn + +class section_deconvnet(nn.Module): + + def __init__(self, n_classes=4, learned_billinear=False): + super(section_deconvnet, self).__init__() + self.learned_billinear = learned_billinear + self.n_classes = n_classes + self.unpool = nn.MaxUnpool2d(2, stride=2) + self.conv_block1 = nn.Sequential( + + # conv1_1 + nn.Conv2d(1, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv1_2 + nn.Conv2d(64, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool1 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_1 + + # 48*48 + + self.conv_block2 = nn.Sequential( + + # conv2_1 + nn.Conv2d(64, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv2_2 + nn.Conv2d(128, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool2 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_2 + + # 24*24 + + self.conv_block3 = nn.Sequential( + + # conv3_1 + nn.Conv2d(128, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_2 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_3 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool3 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_3 + + # 12*12 + + self.conv_block4 = nn.Sequential( + + # conv4_1 + nn.Conv2d(256, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool4 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_4 + + # 6*6 + + self.conv_block5 = nn.Sequential( + + # conv5_1 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool5 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_5 + + # 3*3 + + self.conv_block6 = nn.Sequential( + + # fc6 + nn.Conv2d(512, 4096, 3), + # set the filter size and nor padding to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 1*1 + + self.conv_block7 = nn.Sequential( + + # fc7 + nn.Conv2d(4096, 4096, 1), + # set the filter size to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.deconv_block8 = nn.Sequential( + + # fc6-deconv + nn.ConvTranspose2d(4096, 512, 3, stride=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 3*3 + + self.unpool_block9 = nn.Sequential( + + # unpool5 + nn.MaxUnpool2d(2, stride=2), ) + # usage unpool(output, indices) + + # 6*6 + + self.deconv_block10 = nn.Sequential( + + # deconv5_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_3 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block11 = nn.Sequential( + + # unpool4 + nn.MaxUnpool2d(2, stride=2), ) + + # 12*12 + + self.deconv_block12 = nn.Sequential( + + # deconv4_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_3 + nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block13 = nn.Sequential( + + # unpool3 + nn.MaxUnpool2d(2, stride=2), ) + + # 24*24 + + self.deconv_block14 = nn.Sequential( + + # deconv3_1 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_2 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_3 + nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block15 = nn.Sequential( + + # unpool2 + nn.MaxUnpool2d(2, stride=2), ) + + # 48*48 + + self.deconv_block16 = nn.Sequential( + + # deconv2_1 + nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv2_2 + nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block17 = nn.Sequential( + + # unpool1 + nn.MaxUnpool2d(2, stride=2), ) + + # 96*96 + + self.deconv_block18 = nn.Sequential( + + # deconv1_1 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv1_2 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.seg_score19 = nn.Sequential( + + # seg-score + nn.Conv2d(64, self.n_classes, 1), ) + + if self.learned_billinear: + raise NotImplementedError + + def forward(self, x): + size0 = x.size() + conv1, indices1 = self.conv_block1(x) + size1 = conv1.size() + conv2, indices2 = self.conv_block2(conv1) + size2 = conv2.size() + conv3, indices3 = self.conv_block3(conv2) + size3 = conv3.size() + conv4, indices4 = self.conv_block4(conv3) + size4 = conv4.size() + conv5, indices5 = self.conv_block5(conv4) + + conv6 = self.conv_block6(conv5) + conv7 = self.conv_block7(conv6) + conv8 = self.deconv_block8(conv7) + conv9 = self.unpool(conv8,indices5, output_size=size4) + conv10 = self.deconv_block10(conv9) + conv11 = self.unpool(conv10,indices4, output_size=size3) + conv12 = self.deconv_block12(conv11) + conv13 = self.unpool(conv12,indices3, output_size=size2) + conv14 = self.deconv_block14(conv13) + conv15 = self.unpool(conv14,indices2, output_size=size1) + conv16 = self.deconv_block16(conv15) + conv17 = self.unpool(conv16,indices1, output_size=size0) + conv18 = self.deconv_block18(conv17) + out = self.seg_score19(conv18) + + return out + + def init_vgg16_params(self, vgg16, copy_fc8=True): + blocks = [self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5] + + ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] + features = list(vgg16.features.children()) + i_layer = 0; + # copy convolutional filters from vgg16 + for idx, conv_block in enumerate(blocks): + for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): + if i_layer == 0: + l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, + 2, :, + :]) / 3.0).view( + l2.weight.size()) + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + else: + assert l1.weight.size() == l2.weight.size() + assert l1.bias.size() == l2.bias.size() + l2.weight.data = l1.weight.data + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + + +def get_seg_model(cfg, **kwargs): + assert cfg.MODEL.IN_CHANNELS==1, f"Section deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" + model = section_deconvnet(n_classes=cfg.DATASET.NUM_CLASSES) + return model \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py b/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py new file mode 100644 index 00000000..602d1f4a --- /dev/null +++ b/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py @@ -0,0 +1,321 @@ +import torch.nn as nn + +class section_deconvnet_skip(nn.Module): + + def __init__(self, n_classes=4, learned_billinear=False): + super(section_deconvnet_skip, self).__init__() + self.learned_billinear = learned_billinear + self.n_classes = n_classes + self.unpool = nn.MaxUnpool2d(2, stride=2) + self.conv_block1 = nn.Sequential( + + # conv1_1 + nn.Conv2d(1, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv1_2 + nn.Conv2d(64, 64, 3, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool1 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_1 + + # 48*48 + + self.conv_block2 = nn.Sequential( + + # conv2_1 + nn.Conv2d(64, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv2_2 + nn.Conv2d(128, 128, 3, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool2 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_2 + + # 24*24 + + self.conv_block3 = nn.Sequential( + + # conv3_1 + nn.Conv2d(128, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_2 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv3_3 + nn.Conv2d(256, 256, 3, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool3 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_3 + + # 12*12 + + self.conv_block4 = nn.Sequential( + + # conv4_1 + nn.Conv2d(256, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv4_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool4 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_4 + + # 6*6 + + self.conv_block5 = nn.Sequential( + + # conv5_1 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_2 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # conv5_3 + nn.Conv2d(512, 512, 3, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # pool5 + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + # it returns outputs and pool_indices_5 + + # 3*3 + + self.conv_block6 = nn.Sequential( + + # fc6 + nn.Conv2d(512, 4096, 3), + # set the filter size and nor padding to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 1*1 + + self.conv_block7 = nn.Sequential( + + # fc7 + nn.Conv2d(4096, 4096, 1), + # set the filter size to make output into 1*1 + nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.deconv_block8 = nn.Sequential( + + # fc6-deconv + nn.ConvTranspose2d(4096, 512, 3, stride=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + # 3*3 + + self.unpool_block9 = nn.Sequential( + + # unpool5 + nn.MaxUnpool2d(2, stride=2), ) + # usage unpool(output, indices) + + # 6*6 + + self.deconv_block10 = nn.Sequential( + + # deconv5_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv5_3 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block11 = nn.Sequential( + + # unpool4 + nn.MaxUnpool2d(2, stride=2), ) + + # 12*12 + + self.deconv_block12 = nn.Sequential( + + # deconv4_1 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_2 + nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), + nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv4_3 + nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block13 = nn.Sequential( + + # unpool3 + nn.MaxUnpool2d(2, stride=2), ) + + # 24*24 + + self.deconv_block14 = nn.Sequential( + + # deconv3_1 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_2 + nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), + nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv3_3 + nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block15 = nn.Sequential( + + # unpool2 + nn.MaxUnpool2d(2, stride=2), ) + + # 48*48 + + self.deconv_block16 = nn.Sequential( + + # deconv2_1 + nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), + nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv2_2 + nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.unpool_block17 = nn.Sequential( + + # unpool1 + nn.MaxUnpool2d(2, stride=2), ) + + # 96*96 + + self.deconv_block18 = nn.Sequential( + + # deconv1_1 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), + + # deconv1_2 + nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), + nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), + nn.ReLU(inplace=True), ) + + self.seg_score19 = nn.Sequential( + + # seg-score + nn.Conv2d(64, self.n_classes, 1), ) + + if self.learned_billinear: + raise NotImplementedError + + def forward(self, x): + size0 = x.size() + conv1, indices1 = self.conv_block1(x) + size1 = conv1.size() + conv2, indices2 = self.conv_block2(conv1) + size2 = conv2.size() + conv3, indices3 = self.conv_block3(conv2) + size3 = conv3.size() + conv4, indices4 = self.conv_block4(conv3) + size4 = conv4.size() + conv5, indices5 = self.conv_block5(conv4) + + conv6 = self.conv_block6(conv5) + conv7 = self.conv_block7(conv6) + conv8 = self.deconv_block8(conv7) + conv5 + conv9 = self.unpool(conv8,indices5, output_size=size4) + conv10 = self.deconv_block10(conv9) + conv4 + conv11 = self.unpool(conv10,indices4, output_size=size3) + conv12 = self.deconv_block12(conv11) + conv3 + conv13 = self.unpool(conv12,indices3, output_size=size2) + conv14 = self.deconv_block14(conv13) + conv2 + conv15 = self.unpool(conv14,indices2, output_size=size1) + conv16 = self.deconv_block16(conv15) + conv1 + conv17 = self.unpool(conv16,indices1, output_size=size0) + conv18 = self.deconv_block18(conv17) + out = self.seg_score19(conv18) + + return out + + def init_vgg16_params(self, vgg16, copy_fc8=True): + blocks = [self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5] + + ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] + features = list(vgg16.features.children()) + i_layer = 0; + # copy convolutional filters from vgg16 + for idx, conv_block in enumerate(blocks): + for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): + if i_layer == 0: + l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, + 2, :, + :]) / 3.0).view( + l2.weight.size()) + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + else: + assert l1.weight.size() == l2.weight.size() + assert l1.bias.size() == l2.bias.size() + l2.weight.data = l1.weight.data + l2.bias.data = l1.bias.data + i_layer = i_layer + 1 + +def get_seg_model(cfg, **kwargs): + assert cfg.MODEL.IN_CHANNELS==1, f"Section deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" + model = section_deconvnet_skip(n_classes=cfg.DATASET.NUM_CLASSES) + return model \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/models/seg_hrnet.py b/cv_lib/cv_lib/segmentation/models/seg_hrnet.py new file mode 100644 index 00000000..200e118d --- /dev/null +++ b/cv_lib/cv_lib/segmentation/models/seg_hrnet.py @@ -0,0 +1,477 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# Written by Ke Sun (sunk@mail.ustc.edu.cn) +# ------------------------------------------------------------------------------ +"""HRNET for segmentation taken from https://github.com/HRNet/HRNet-Semantic-Segmentation +pytorch-v1.1 branch +hash: 06142dc1c7026e256a7561c3e875b06622b5670f + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import logging +import os + +import numpy as np +import torch +import torch._utils +import torch.nn as nn +import torch.nn.functional as F + +BatchNorm2d = nn.BatchNorm2d +BN_MOMENTUM = 0.1 +logger = logging.getLogger(__name__) + +def conv3x3(in_planes, out_planes, stride=1): + """3x3 convolution with padding""" + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, + padding=1, bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + padding=1, bias=False) + self.bn2 = BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, + bias=False) + self.bn3 = BatchNorm2d(planes * self.expansion, + momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class HighResolutionModule(nn.Module): + def __init__(self, num_branches, blocks, num_blocks, num_inchannels, + num_channels, fuse_method, multi_scale_output=True): + super(HighResolutionModule, self).__init__() + self._check_branches( + num_branches, blocks, num_blocks, num_inchannels, num_channels) + + self.num_inchannels = num_inchannels + self.fuse_method = fuse_method + self.num_branches = num_branches + + self.multi_scale_output = multi_scale_output + + self.branches = self._make_branches( + num_branches, blocks, num_blocks, num_channels) + self.fuse_layers = self._make_fuse_layers() + self.relu = nn.ReLU(inplace=True) + + def _check_branches(self, num_branches, blocks, num_blocks, + num_inchannels, num_channels): + if num_branches != len(num_blocks): + error_msg = 'NUM_BRANCHES({}) <> NUM_BLOCKS({})'.format( + num_branches, len(num_blocks)) + logger.error(error_msg) + raise ValueError(error_msg) + + if num_branches != len(num_channels): + error_msg = 'NUM_BRANCHES({}) <> NUM_CHANNELS({})'.format( + num_branches, len(num_channels)) + logger.error(error_msg) + raise ValueError(error_msg) + + if num_branches != len(num_inchannels): + error_msg = 'NUM_BRANCHES({}) <> NUM_INCHANNELS({})'.format( + num_branches, len(num_inchannels)) + logger.error(error_msg) + raise ValueError(error_msg) + + def _make_one_branch(self, branch_index, block, num_blocks, num_channels, + stride=1): + downsample = None + if stride != 1 or \ + self.num_inchannels[branch_index] != num_channels[branch_index] * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.num_inchannels[branch_index], + num_channels[branch_index] * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm2d(num_channels[branch_index] * block.expansion, + momentum=BN_MOMENTUM), + ) + + layers = [] + layers.append(block(self.num_inchannels[branch_index], + num_channels[branch_index], stride, downsample)) + self.num_inchannels[branch_index] = \ + num_channels[branch_index] * block.expansion + for i in range(1, num_blocks[branch_index]): + layers.append(block(self.num_inchannels[branch_index], + num_channels[branch_index])) + + return nn.Sequential(*layers) + + def _make_branches(self, num_branches, block, num_blocks, num_channels): + branches = [] + + for i in range(num_branches): + branches.append( + self._make_one_branch(i, block, num_blocks, num_channels)) + + return nn.ModuleList(branches) + + def _make_fuse_layers(self): + if self.num_branches == 1: + return None + + num_branches = self.num_branches + num_inchannels = self.num_inchannels + fuse_layers = [] + for i in range(num_branches if self.multi_scale_output else 1): + fuse_layer = [] + for j in range(num_branches): + if j > i: + fuse_layer.append(nn.Sequential( + nn.Conv2d(num_inchannels[j], + num_inchannels[i], + 1, + 1, + 0, + bias=False), + BatchNorm2d(num_inchannels[i], momentum=BN_MOMENTUM))) + elif j == i: + fuse_layer.append(None) + else: + conv3x3s = [] + for k in range(i-j): + if k == i - j - 1: + num_outchannels_conv3x3 = num_inchannels[i] + conv3x3s.append(nn.Sequential( + nn.Conv2d(num_inchannels[j], + num_outchannels_conv3x3, + 3, 2, 1, bias=False), + BatchNorm2d(num_outchannels_conv3x3, + momentum=BN_MOMENTUM))) + else: + num_outchannels_conv3x3 = num_inchannels[j] + conv3x3s.append(nn.Sequential( + nn.Conv2d(num_inchannels[j], + num_outchannels_conv3x3, + 3, 2, 1, bias=False), + BatchNorm2d(num_outchannels_conv3x3, + momentum=BN_MOMENTUM), + nn.ReLU(inplace=True))) + fuse_layer.append(nn.Sequential(*conv3x3s)) + fuse_layers.append(nn.ModuleList(fuse_layer)) + + return nn.ModuleList(fuse_layers) + + def get_num_inchannels(self): + return self.num_inchannels + + def forward(self, x): + if self.num_branches == 1: + return [self.branches[0](x[0])] + + for i in range(self.num_branches): + x[i] = self.branches[i](x[i]) + + x_fuse = [] + for i in range(len(self.fuse_layers)): + y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]) + for j in range(1, self.num_branches): + if i == j: + y = y + x[j] + elif j > i: + width_output = x[i].shape[-1] + height_output = x[i].shape[-2] + y = y + F.interpolate( + self.fuse_layers[i][j](x[j]), + size=[height_output, width_output], + mode='bilinear') + else: + y = y + self.fuse_layers[i][j](x[j]) + x_fuse.append(self.relu(y)) + + return x_fuse + + +blocks_dict = { + 'BASIC': BasicBlock, + 'BOTTLENECK': Bottleneck +} + + +class HighResolutionNet(nn.Module): + + def __init__(self, config, **kwargs): + extra = config.MODEL.EXTRA + super(HighResolutionNet, self).__init__() + + # stem net + self.conv1 = nn.Conv2d(config.MODEL.IN_CHANNELS, 64, kernel_size=3, stride=2, padding=1, + bias=False) + self.bn1 = BatchNorm2d(64, momentum=BN_MOMENTUM) + self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, + bias=False) + self.bn2 = BatchNorm2d(64, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + + self.layer1 = self._make_layer(Bottleneck, 64, 64, 4) + + self.stage2_cfg = extra['STAGE2'] + num_channels = self.stage2_cfg['NUM_CHANNELS'] + block = blocks_dict[self.stage2_cfg['BLOCK']] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels))] + self.transition1 = self._make_transition_layer([256], num_channels) + self.stage2, pre_stage_channels = self._make_stage( + self.stage2_cfg, num_channels) + + self.stage3_cfg = extra['STAGE3'] + num_channels = self.stage3_cfg['NUM_CHANNELS'] + block = blocks_dict[self.stage3_cfg['BLOCK']] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels))] + self.transition2 = self._make_transition_layer( + pre_stage_channels, num_channels) + self.stage3, pre_stage_channels = self._make_stage( + self.stage3_cfg, num_channels) + + self.stage4_cfg = extra['STAGE4'] + num_channels = self.stage4_cfg['NUM_CHANNELS'] + block = blocks_dict[self.stage4_cfg['BLOCK']] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels))] + self.transition3 = self._make_transition_layer( + pre_stage_channels, num_channels) + self.stage4, pre_stage_channels = self._make_stage( + self.stage4_cfg, num_channels, multi_scale_output=True) + + last_inp_channels = np.int(np.sum(pre_stage_channels)) + + self.last_layer = nn.Sequential( + nn.Conv2d( + in_channels=last_inp_channels, + out_channels=last_inp_channels, + kernel_size=1, + stride=1, + padding=0), + BatchNorm2d(last_inp_channels, momentum=BN_MOMENTUM), + nn.ReLU(inplace=True), + nn.Conv2d( + in_channels=last_inp_channels, + out_channels=config.DATASET.NUM_CLASSES, + kernel_size=extra.FINAL_CONV_KERNEL, + stride=1, + padding=1 if extra.FINAL_CONV_KERNEL == 3 else 0) + ) + + def _make_transition_layer( + self, num_channels_pre_layer, num_channels_cur_layer): + num_branches_cur = len(num_channels_cur_layer) + num_branches_pre = len(num_channels_pre_layer) + + transition_layers = [] + for i in range(num_branches_cur): + if i < num_branches_pre: + if num_channels_cur_layer[i] != num_channels_pre_layer[i]: + transition_layers.append(nn.Sequential( + nn.Conv2d(num_channels_pre_layer[i], + num_channels_cur_layer[i], + 3, + 1, + 1, + bias=False), + BatchNorm2d( + num_channels_cur_layer[i], momentum=BN_MOMENTUM), + nn.ReLU(inplace=True))) + else: + transition_layers.append(None) + else: + conv3x3s = [] + for j in range(i+1-num_branches_pre): + inchannels = num_channels_pre_layer[-1] + outchannels = num_channels_cur_layer[i] \ + if j == i-num_branches_pre else inchannels + conv3x3s.append(nn.Sequential( + nn.Conv2d( + inchannels, outchannels, 3, 2, 1, bias=False), + BatchNorm2d(outchannels, momentum=BN_MOMENTUM), + nn.ReLU(inplace=True))) + transition_layers.append(nn.Sequential(*conv3x3s)) + + return nn.ModuleList(transition_layers) + + def _make_layer(self, block, inplanes, planes, blocks, stride=1): + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), + ) + + layers = [] + layers.append(block(inplanes, planes, stride, downsample)) + inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(inplanes, planes)) + + return nn.Sequential(*layers) + + def _make_stage(self, layer_config, num_inchannels, + multi_scale_output=True): + num_modules = layer_config['NUM_MODULES'] + num_branches = layer_config['NUM_BRANCHES'] + num_blocks = layer_config['NUM_BLOCKS'] + num_channels = layer_config['NUM_CHANNELS'] + block = blocks_dict[layer_config['BLOCK']] + fuse_method = layer_config['FUSE_METHOD'] + + modules = [] + for i in range(num_modules): + # multi_scale_output is only used last module + if not multi_scale_output and i == num_modules - 1: + reset_multi_scale_output = False + else: + reset_multi_scale_output = True + modules.append( + HighResolutionModule(num_branches, + block, + num_blocks, + num_inchannels, + num_channels, + fuse_method, + reset_multi_scale_output) + ) + num_inchannels = modules[-1].get_num_inchannels() + + return nn.Sequential(*modules), num_inchannels + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + x = self.layer1(x) + + x_list = [] + for i in range(self.stage2_cfg['NUM_BRANCHES']): + if self.transition1[i] is not None: + x_list.append(self.transition1[i](x)) + else: + x_list.append(x) + y_list = self.stage2(x_list) + + x_list = [] + for i in range(self.stage3_cfg['NUM_BRANCHES']): + if self.transition2[i] is not None: + x_list.append(self.transition2[i](y_list[-1])) + else: + x_list.append(y_list[i]) + y_list = self.stage3(x_list) + + x_list = [] + for i in range(self.stage4_cfg['NUM_BRANCHES']): + if self.transition3[i] is not None: + x_list.append(self.transition3[i](y_list[-1])) + else: + x_list.append(y_list[i]) + x = self.stage4(x_list) + + # Upsampling + x0_h, x0_w = x[0].size(2), x[0].size(3) + x1 = F.upsample(x[1], size=(x0_h, x0_w), mode='bilinear') + x2 = F.upsample(x[2], size=(x0_h, x0_w), mode='bilinear') + x3 = F.upsample(x[3], size=(x0_h, x0_w), mode='bilinear') + + x = torch.cat([x[0], x1, x2, x3], 1) + + x = self.last_layer(x) + + return x + + def init_weights(self, pretrained='',): + logger.info('=> init weights from normal distribution') + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.normal_(m.weight, std=0.001) + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + if os.path.isfile(pretrained): + pretrained_dict = torch.load(pretrained) + logger.info('=> loading pretrained model {}'.format(pretrained)) + model_dict = self.state_dict() + pretrained_dict = {k: v for k, v in pretrained_dict.items() + if k in model_dict.keys()} + #for k, _ in pretrained_dict.items(): + # logger.info( + # '=> loading {} pretrained model {}'.format(k, pretrained)) + model_dict.update(pretrained_dict) + self.load_state_dict(model_dict) + +def get_seg_model(cfg, **kwargs): + model = HighResolutionNet(cfg, **kwargs) + model.init_weights(cfg.MODEL.PRETRAINED) + + return model diff --git a/cv_lib/cv_lib/segmentation/models/unet.py b/cv_lib/cv_lib/segmentation/models/unet.py new file mode 100644 index 00000000..3c37d983 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/models/unet.py @@ -0,0 +1,122 @@ +""" Taken from https://github.com/milesial/Pytorch-UNet + +""" +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.autograd import Variable +from torch.nn import init + + + + +class double_conv(nn.Module): + '''(conv => BN => ReLU) * 2''' + def __init__(self, in_ch, out_ch): + super(double_conv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d(in_ch, out_ch, 3, padding=1), + nn.BatchNorm2d(out_ch), + nn.ReLU(inplace=True), + nn.Conv2d(out_ch, out_ch, 3, padding=1), + nn.BatchNorm2d(out_ch), + nn.ReLU(inplace=True) + ) + + def forward(self, x): + x = self.conv(x) + return x + + +class inconv(nn.Module): + def __init__(self, in_ch, out_ch): + super(inconv, self).__init__() + self.conv = double_conv(in_ch, out_ch) + + def forward(self, x): + x = self.conv(x) + return x + + +class down(nn.Module): + def __init__(self, in_ch, out_ch): + super(down, self).__init__() + self.mpconv = nn.Sequential( + nn.MaxPool2d(2), + double_conv(in_ch, out_ch) + ) + + def forward(self, x): + x = self.mpconv(x) + return x + + +class up(nn.Module): + def __init__(self, in_ch, out_ch, bilinear=True): + super(up, self).__init__() + + if bilinear: + self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) + else: + self.up = nn.ConvTranspose2d(in_ch//2, in_ch//2, 2, stride=2) + + self.conv = double_conv(in_ch, out_ch) + + def forward(self, x1, x2): + x1 = self.up(x1) + + # input is CHW + diffY = x2.size()[2] - x1.size()[2] + diffX = x2.size()[3] - x1.size()[3] + + x1 = F.pad(x1, (diffX // 2, diffX - diffX//2, + diffY // 2, diffY - diffY//2)) + + x = torch.cat([x2, x1], dim=1) + x = self.conv(x) + return x + + +class outconv(nn.Module): + def __init__(self, in_ch, out_ch): + super(outconv, self).__init__() + self.conv = nn.Conv2d(in_ch, out_ch, 1) + + def forward(self, x): + x = self.conv(x) + return x + + +class UNet(nn.Module): + def __init__(self, n_channels, n_classes): + super(UNet, self).__init__() + self.inc = inconv(n_channels, 64) + self.down1 = down(64, 128) + self.down2 = down(128, 256) + self.down3 = down(256, 512) + self.down4 = down(512, 512) + self.up1 = up(1024, 256) + self.up2 = up(512, 128) + self.up3 = up(256, 64) + self.up4 = up(128, 64) + self.outc = outconv(64, n_classes) + + def forward(self, x): + x1 = self.inc(x) + x2 = self.down1(x1) + x3 = self.down2(x2) + x4 = self.down3(x3) + x5 = self.down4(x4) + x = self.up1(x5, x4) + x = self.up2(x, x3) + x = self.up3(x, x2) + x = self.up4(x, x1) + x = self.outc(x) + return x + + +def get_seg_model(cfg, **kwargs): + model = UNet(cfg.MODEL.IN_CHANNELS, cfg.DATASET.NUM_CLASSES) + return model + diff --git a/cv_lib/cv_lib/segmentation/models/utils.py b/cv_lib/cv_lib/segmentation/models/utils.py new file mode 100644 index 00000000..359544b9 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/models/utils.py @@ -0,0 +1,72 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class conv2DBatchNorm(nn.Module): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): + super(conv2DBatchNorm, self).__init__() + + if dilation > 1: + conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias, dilation=dilation) + + else: + conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias, dilation=1) + + + self.cb_unit = nn.Sequential(conv_mod, + nn.BatchNorm2d(int(n_filters)),) + + def forward(self, inputs): + outputs = self.cb_unit(inputs) + return outputs + + +class deconv2DBatchNorm(nn.Module): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): + super(deconv2DBatchNorm, self).__init__() + + self.dcb_unit = nn.Sequential(nn.ConvTranspose2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias), + nn.BatchNorm2d(int(n_filters)),) + + def forward(self, inputs): + outputs = self.dcb_unit(inputs) + return outputs + + +class conv2DBatchNormRelu(nn.Module): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): + super(conv2DBatchNormRelu, self).__init__() + + if dilation > 1: + conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias, dilation=dilation) + + else: + conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias, dilation=1) + + self.cbr_unit = nn.Sequential(conv_mod, + nn.BatchNorm2d(int(n_filters)), + nn.ReLU(inplace=True),) + + def forward(self, inputs): + outputs = self.cbr_unit(inputs) + return outputs + + +class deconv2DBatchNormRelu(nn.Module): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): + super(deconv2DBatchNormRelu, self).__init__() + + self.dcbr_unit = nn.Sequential(nn.ConvTranspose2d(int(in_channels), int(n_filters), kernel_size=k_size, + padding=padding, stride=stride, bias=bias), + nn.BatchNorm2d(int(n_filters)), + nn.ReLU(inplace=True),) + + def forward(self, inputs): + outputs = self.dcbr_unit(inputs) + return outputs diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/__init__.py b/cv_lib/cv_lib/segmentation/tgs_salt/__init__.py new file mode 100644 index 00000000..e48edba2 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/__init__.py @@ -0,0 +1,15 @@ +from toolz import curry +import torch.nn.functional as F + +@curry +def extract_metric_from(metric, engine): + metrics = engine.state.metrics + return metrics[metric] + + +@curry +def padded_val_transform(pad_left, fine_size, x, y, y_pred): + y_pred = y_pred[ + :, :, pad_left : pad_left + fine_size, pad_left : pad_left + fine_size + ].contiguous() + return {"image":x, "y_pred": F.sigmoid(y_pred).detach(), "mask":y.detach()} diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/data.py b/cv_lib/cv_lib/segmentation/tgs_salt/data.py new file mode 100644 index 00000000..88a7d5a8 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/data.py @@ -0,0 +1,315 @@ +from copy import deepcopy +import os +import numpy as np +import pandas as pd +from tqdm import tqdm + +import cv2 +import torch +from torch.utils.data import Dataset + +from torch.utils.data import DataLoader +from torch.utils.data.sampler import RandomSampler +from ignite.utils import convert_tensor +from sklearn.model_selection import KFold +import torch.nn.functional as F + + +def get_data_ids(data_root, train_csv="train.csv", depths_csv="depths.csv"): + train_id = pd.read_csv(os.path.join(data_root, "train.csv"))["id"].values + depth_id = pd.read_csv(os.path.join(data_root, "depths.csv"))["id"].values + test_id = np.setdiff1d(depth_id, train_id) + return train_id,depth_id,test_id + + +def add_depth_channels(image_tensor): + _, h, w = image_tensor.size() + image = torch.zeros([3, h, w]) + image[0] = image_tensor + for row, const in enumerate(np.linspace(0, 1, h)): + image[1, row, :] = const + image[2] = image[0] * image[1] + return image + + +def train_aug(image, mask): + if np.random.rand() < 0.5: + image, mask = do_horizontal_flip2(image, mask) + + if np.random.rand() < 0.5: + c = np.random.choice(3) + if c == 0: + image, mask = do_random_shift_scale_crop_pad2(image, mask, 0.2) + + if c == 1: + image, mask = do_horizontal_shear2( + image, mask, dx=np.random.uniform(-0.07, 0.07) + ) + + if c == 2: + image, mask = do_shift_scale_rotate2( + image, mask, dx=0, dy=0, scale=1, angle=np.random.uniform(0, 15) + ) + + if np.random.rand() < 0.5: + c = np.random.choice(2) + if c == 0: + image = do_brightness_shift(image, np.random.uniform(-0.1, +0.1)) + if c == 1: + image = do_brightness_multiply(image, np.random.uniform(1 - 0.08, 1 + 0.08)) + + return image, mask + + +class SaltDataset(Dataset): + def __init__( + self, + image_list, + mode, + mask_list=None, + is_tta=False, + is_semi=False, + fine_size=202, + pad_left=0, + pad_right=0, + ): + self.imagelist = image_list + self.mode = mode + self.masklist = mask_list + self.is_tta = is_tta + self.is_semi = is_semi + self.fine_size = fine_size + self.pad_left = pad_left + self.pad_right = pad_right + + def __len__(self): + return len(self.imagelist) + + def __getitem__(self, idx): + image = deepcopy(self.imagelist[idx]) + + if self.mode == "train": + mask = deepcopy(self.masklist[idx]) + + image, mask = train_aug(image, mask) + label = np.where(mask.sum() == 0, 1.0, 0.0).astype(np.float32) + + if self.fine_size != image.shape[0]: + image, mask = do_resize2(image, mask, self.fine_size, self.fine_size) + + if self.pad_left != 0: + image, mask = do_center_pad2(image, mask, self.pad_left, self.pad_right) + + image = image.reshape( + 1, + self.fine_size + self.pad_left + self.pad_right, + self.fine_size + self.pad_left + self.pad_right, + ) + mask = mask.reshape( + 1, + self.fine_size + self.pad_left + self.pad_right, + self.fine_size + self.pad_left + self.pad_right, + ) + image, mask = torch.from_numpy(image), torch.from_numpy(mask) + image = add_depth_channels(image) + return image, mask, torch.from_numpy(label) + + elif self.mode == "val": + mask = deepcopy(self.masklist[idx]) + if self.fine_size != image.shape[0]: + image, mask = do_resize2(image, mask, self.fine_size, self.fine_size) + if self.pad_left != 0: + image = do_center_pad(image, self.pad_left, self.pad_right) + + image = image.reshape( + 1, + self.fine_size + self.pad_left + self.pad_right, + self.fine_size + self.pad_left + self.pad_right, + ) + mask = mask.reshape(1, self.fine_size, self.fine_size) + + image, mask = torch.from_numpy(image), torch.from_numpy(mask) + image = add_depth_channels(image) + + return image, mask + + elif self.mode == "test": + if self.is_tta: + image = cv2.flip(image, 1) + if self.fine_size != image.shape[0]: + image = cv2.resize(image, dsize=(self.fine_size, self.fine_size)) + if self.pad_left != 0: + image = do_center_pad(image, self.pad_left, self.pad_right) + + image = image.reshape( + 1, + self.fine_size + self.pad_left + self.pad_right, + self.fine_size + self.pad_left + self.pad_right, + ) + image = torch.from_numpy(image) + image = add_depth_channels(image) + return image + + +def train_image_fetch(images_id, train_root): + image_train = np.zeros((images_id.shape[0], 101, 101), dtype=np.float32) + mask_train = np.zeros((images_id.shape[0], 101, 101), dtype=np.float32) + + for idx, image_id in tqdm(enumerate(images_id), total=images_id.shape[0]): + image_path = os.path.join(train_root, "images", "{}.png".format(image_id)) + mask_path = os.path.join(train_root, "masks", "{}.png".format(image_id)) + + image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255 + mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255 + image_train[idx] = image + mask_train[idx] = mask + + return image_train, mask_train + + +def test_image_fetch(test_id, test_root): + image_test = np.zeros((len(test_id), 101, 101), dtype=np.float32) + + for n, image_id in tqdm(enumerate(test_id), total=len(test_id)): + image_path = os.path.join(test_root, "images", "{}.png".format(image_id)) + + img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255 + image_test[n] = img + + return image_test + + +def kfold_split(examples_index_list, n_splits=5, random_state=42, shuffle=True): + kf = KFold(n_splits=n_splits, random_state=random_state, shuffle=shuffle) + for train_index, test_index in kf.split(examples_index_list): + yield train_index, test_index + + +def get_data_loaders( + train_ids, + val_ids, + train_batch_size, + val_batch_size, + fine_size, + pad_left, + pad_right, + data_root, + num_workers=4, + pin_memory=True, +): + train_root = os.path.join(data_root, "train") + image_train, mask_train = train_image_fetch(train_ids, train_root) + train_data = SaltDataset( + image_train, + mode="train", + mask_list=mask_train, + fine_size=fine_size, + pad_left=pad_left, + pad_right=pad_right, + ) + + train_loader = DataLoader( + train_data, + sampler=RandomSampler(train_data), + batch_size=train_batch_size, + num_workers=num_workers, + pin_memory=pin_memory, + ) + + image_val, mask_val = train_image_fetch(val_ids, train_root) + val_data = SaltDataset( + image_val, + mode="val", + mask_list=mask_val, + fine_size=fine_size, + pad_left=pad_left, + pad_right=pad_right, + ) + + val_loader = DataLoader( + val_data, + shuffle=False, + batch_size=val_batch_size, + num_workers=num_workers, + pin_memory=pin_memory, + ) + + return train_loader, val_loader + + +def get_distributed_data_loaders( + train_ids, + val_ids, + train_batch_size, + val_batch_size, + fine_size, + pad_left, + pad_right, + rank, + size, + data_root, + num_workers=4, + pin_memory=True, + +): + train_root = os.path.join(data_root, "train") + image_train, mask_train = train_image_fetch(train_ids, train_root) + train_data = SaltDataset( + image_train, + mode="train", + mask_list=mask_train, + fine_size=fine_size, + pad_left=pad_left, + pad_right=pad_right, + ) + + train_sampler = torch.utils.data.distributed.DistributedSampler( + train_data, num_replicas=size, rank=rank + ) + train_loader = DataLoader( + train_data, + sampler=train_sampler, + batch_size=train_batch_size, + num_workers=num_workers, + pin_memory=pin_memory, + ) + + image_val, mask_val = train_image_fetch(val_ids, train_root) + val_data = SaltDataset( + image_val, + mode="val", + mask_list=mask_val, + fine_size=fine_size, + pad_left=pad_left, + pad_right=pad_right, + ) + + val_sampler = torch.utils.data.distributed.DistributedSampler( + val_data, num_replicas=size, rank=rank + ) + + val_loader = DataLoader( + val_data, + sampler=val_sampler, + batch_size=val_batch_size, + num_workers=num_workers, + pin_memory=pin_memory, + ) + + return train_loader, val_loader + + +def prepare_train_batch(batch, device=None, non_blocking=False): + x, y, _ = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ) + + +def prepare_val_batch(batch, device=None, non_blocking=False): + x, y = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ) diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/engine.py b/cv_lib/cv_lib/segmentation/tgs_salt/engine.py new file mode 100644 index 00000000..560c1130 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/engine.py @@ -0,0 +1,131 @@ +import torch + +from ignite.engine.engine import Engine, State, Events +from ignite.utils import convert_tensor +import torch.nn.functional as F +from toolz import curry +from torch.nn import functional as F + + +def _upscale_model_output(y_pred, y): + ph, pw = y_pred.size(2), y_pred.size(3) + h, w = y.size(2), y.size(3) + if ph != h or pw != w: + y_pred = F.upsample(input=y_pred, size=(h, w), mode='bilinear') + return y_pred + +def create_supervised_trainer( + model, + optimizer, + loss_fn, + prepare_batch, + device=None, + non_blocking=False, + output_transform=lambda x, y, y_pred, loss: {"loss": loss.item()}, +): + if device: + model.to(device) + + def _update(engine, batch): + model.train() + optimizer.zero_grad() + x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) + y_pred = model(x) + y_pred = _upscale_model_output(y_pred, y) + loss = loss_fn(y_pred.squeeze(1), y.squeeze(1)) + loss.backward() + optimizer.step() + return output_transform(x, y, y_pred, loss) + + return Engine(_update) + + +@curry +def val_transform(x, y, y_pred): + return {"image":x, "y_pred": F.sigmoid(y_pred).detach(), "mask":y.detach()} + + +def create_supervised_evaluator( + model, + prepare_batch, + metrics=None, + device=None, + non_blocking=False, + output_transform=val_transform, +): + metrics = metrics or {} + + if device: + model.to(device) + + def _inference(engine, batch): + model.eval() + with torch.no_grad(): + x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) + y_pred = model(x) + y_pred = _upscale_model_output(y_pred, x) + return output_transform(x, y, y_pred) + + engine = Engine(_inference) + + for name, metric in metrics.items(): + metric.attach(engine, name) + + return engine + + +def create_supervised_trainer_apex( + model, + optimizer, + loss_fn, + prepare_batch, + device=None, + non_blocking=False, + output_transform=lambda x, y, y_pred, loss: {"loss": loss.item()}, +): + from apex import amp + + if device: + model.to(device) + + def _update(engine, batch): + model.train() + optimizer.zero_grad() + x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) + y_pred = model(x) + loss = loss_fn(y_pred.squeeze(1), y.squeeze(1)) + with amp.scale_loss(loss, optimizer) as scaled_loss: + scaled_loss.backward() + optimizer.step() + return output_transform(x, y, y_pred, loss) + + return Engine(_update) + + +# def create_supervised_evaluator_apex( +# model, +# prepare_batch, +# metrics=None, +# device=None, +# non_blocking=False, +# output_transform=lambda x, y, y_pred: (x, y, pred), +# ): +# metrics = metrics or {} + +# if device: +# model.to(device) + +# def _inference(engine, batch): +# model.eval() +# with torch.no_grad(): +# x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) +# y_pred = model(x) +# return output_transform(x, y, y_pred) + +# engine = Engine(_inference) + +# for name, metric in metrics.items(): +# metric.attach(engine, name) + +# return engine + diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/losses.py b/cv_lib/cv_lib/segmentation/tgs_salt/losses.py new file mode 100644 index 00000000..9740083b --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/losses.py @@ -0,0 +1,270 @@ +""" +Lovasz-Softmax and Jaccard hinge loss in PyTorch +Maxim Berman 2018 ESAT-PSI KU Leuven (MIT License) +""" + +from __future__ import print_function, division + +import torch +from torch.autograd import Variable +import torch.nn.functional as F +import numpy as np +try: + from itertools import ifilterfalse +except ImportError: # py3k + from itertools import filterfalse + + +def lovasz_grad(gt_sorted): + """ + Computes gradient of the Lovasz extension w.r.t sorted errors + See Alg. 1 in paper + """ + p = len(gt_sorted) + gts = gt_sorted.sum() + intersection = gts - gt_sorted.float().cumsum(0) + union = gts + (1 - gt_sorted).float().cumsum(0) + jaccard = 1. - intersection / union + if p > 1: # cover 1-pixel case + jaccard[1:p] = jaccard[1:p] - jaccard[0:-1] + return jaccard + + +def iou_binary(preds, labels, EMPTY=1., ignore=None, per_image=True): + """ + IoU for foreground class + binary: 1 foreground, 0 background + """ + if not per_image: + preds, labels = (preds,), (labels,) + ious = [] + for pred, label in zip(preds, labels): + intersection = ((label == 1) & (pred == 1)).sum() + union = ((label == 1) | ((pred == 1) & (label != ignore))).sum() + if not union: + iou = EMPTY + else: + iou = float(intersection) / union + ious.append(iou) + iou = mean(ious) # mean accross images if per_image + return 100 * iou + + +def iou(preds, labels, C, EMPTY=1., ignore=None, per_image=False): + """ + Array of IoU for each (non ignored) class + """ + if not per_image: + preds, labels = (preds,), (labels,) + ious = [] + for pred, label in zip(preds, labels): + iou = [] + for i in range(C): + if i != ignore: # The ignored label is sometimes among predicted classes (ENet - CityScapes) + intersection = ((label == i) & (pred == i)).sum() + union = ((label == i) | ((pred == i) & (label != ignore))).sum() + if not union: + iou.append(EMPTY) + else: + iou.append(float(intersection) / union) + ious.append(iou) + ious = map(mean, zip(*ious)) # mean accross images if per_image + return 100 * np.array(ious) + + +# --------------------------- BINARY LOSSES --------------------------- + + +def lovasz_hinge(logits, labels, per_image=True, ignore=None): + """ + Binary Lovasz hinge loss + logits: [B, H, W] Variable, logits at each pixel (between -\infty and +\infty) + labels: [B, H, W] Tensor, binary ground truth masks (0 or 1) + per_image: compute the loss per image instead of per batch + ignore: void class id + """ + if per_image: + loss = mean(lovasz_hinge_flat(*flatten_binary_scores(log.unsqueeze(0), lab.unsqueeze(0), ignore)) + for log, lab in zip(logits, labels)) + else: + loss = lovasz_hinge_flat(*flatten_binary_scores(logits, labels, ignore)) + return loss + + +def lovasz_hinge_flat(logits, labels): + """ + Binary Lovasz hinge loss + logits: [P] Variable, logits at each prediction (between -\infty and +\infty) + labels: [P] Tensor, binary ground truth labels (0 or 1) + ignore: label to ignore + """ + if len(labels) == 0: + # only void pixels, the gradients should be 0 + return logits.sum() * 0. + signs = 2. * labels.float() - 1. + errors = (1. - logits * Variable(signs)) + errors_sorted, perm = torch.sort(errors, dim=0, descending=True) + perm = perm.data + gt_sorted = labels[perm] + grad = lovasz_grad(gt_sorted) + loss = torch.dot(F.relu(errors_sorted), Variable(grad)) + return loss + +def lovasz_hinge2(logits, labels, per_image=True, ignore=None): + """ + Binary Lovasz hinge loss + logits: [B, H, W] Variable, logits at each pixel (between -\infty and +\infty) + labels: [B, H, W] Tensor, binary ground truth masks (0 or 1) + per_image: compute the loss per image instead of per batch + ignore: void class id + """ + if per_image: + loss = mean(lovasz_hinge_flat2(*flatten_binary_scores(log.unsqueeze(0), lab.unsqueeze(0), ignore)) + for log, lab in zip(logits, labels)) + else: + loss = lovasz_hinge_flat2(*flatten_binary_scores(logits, labels, ignore)) + return loss + + +def lovasz_hinge_flat2(logits, labels): + """ + Binary Lovasz hinge loss + logits: [P] Variable, logits at each prediction (between -\infty and +\infty) + labels: [P] Tensor, binary ground truth labels (0 or 1) + ignore: label to ignore + """ + if len(labels) == 0: + # only void pixels, the gradients should be 0 + return logits.sum() * 0. + signs = 2. * labels.float() - 1. + errors = (1. - logits * Variable(signs)) + errors_sorted, perm = torch.sort(errors, dim=0, descending=True) + perm = perm.data + gt_sorted = labels[perm] + grad = lovasz_grad(gt_sorted) + weight = 1 + if labels.sum() == 0: + weight = 0 + loss = torch.dot(F.relu(errors_sorted), Variable(grad)) * weight + return loss + + +def flatten_binary_scores(scores, labels, ignore=None): + """ + Flattens predictions in the batch (binary case) + Remove labels equal to 'ignore' + """ + scores = scores.view(-1) + labels = labels.view(-1) + if ignore is None: + return scores, labels + valid = (labels != ignore) + vscores = scores[valid] + vlabels = labels[valid] + return vscores, vlabels + + +class StableBCELoss(torch.nn.modules.Module): + def __init__(self): + super(StableBCELoss, self).__init__() + def forward(self, input, target): + neg_abs = - input.abs() + loss = input.clamp(min=0) - input * target + (1 + neg_abs.exp()).log() + return loss.mean() + + +def binary_xloss(logits, labels, ignore=None): + """ + Binary Cross entropy loss + logits: [B, H, W] Variable, logits at each pixel (between -\infty and +\infty) + labels: [B, H, W] Tensor, binary ground truth masks (0 or 1) + ignore: void class id + """ + logits, labels = flatten_binary_scores(logits, labels, ignore) + loss = StableBCELoss()(logits, Variable(labels.float())) + return loss + + +# --------------------------- MULTICLASS LOSSES --------------------------- + + +def lovasz_softmax(probas, labels, only_present=False, per_image=False, ignore=None): + """ + Multi-class Lovasz-Softmax loss + probas: [B, C, H, W] Variable, class probabilities at each prediction (between 0 and 1) + labels: [B, H, W] Tensor, ground truth labels (between 0 and C - 1) + only_present: average only on classes present in ground truth + per_image: compute the loss per image instead of per batch + ignore: void class labels + """ + if per_image: + loss = mean(lovasz_softmax_flat(*flatten_probas(prob.unsqueeze(0), lab.unsqueeze(0), ignore), only_present=only_present) + for prob, lab in zip(probas, labels)) + else: + loss = lovasz_softmax_flat(*flatten_probas(probas, labels, ignore), only_present=only_present) + return loss + + +def lovasz_softmax_flat(probas, labels, only_present=False): + """ + Multi-class Lovasz-Softmax loss + probas: [P, C] Variable, class probabilities at each prediction (between 0 and 1) + labels: [P] Tensor, ground truth labels (between 0 and C - 1) + only_present: average only on classes present in ground truth + """ + C = probas.size(1) + losses = [] + for c in range(C): + fg = (labels == c).float() # foreground for class c + if only_present and fg.sum() == 0: + continue + errors = (Variable(fg) - probas[:, c]).abs() + errors_sorted, perm = torch.sort(errors, 0, descending=True) + perm = perm.data + fg_sorted = fg[perm] + losses.append(torch.dot(errors_sorted, Variable(lovasz_grad(fg_sorted)))) + return mean(losses) + + +def flatten_probas(probas, labels, ignore=None): + """ + Flattens predictions in the batch + """ + B, C, H, W = probas.size() + probas = probas.permute(0, 2, 3, 1).contiguous().view(-1, C) # B * H * W, C = P, C + labels = labels.view(-1) + if ignore is None: + return probas, labels + valid = (labels != ignore) + vprobas = probas[valid.nonzero().squeeze()] + vlabels = labels[valid] + return vprobas, vlabels + +def xloss(logits, labels, ignore=None): + """ + Cross entropy loss + """ + return F.cross_entropy(logits, Variable(labels), ignore_index=255) + + +# --------------------------- HELPER FUNCTIONS --------------------------- + +def mean(l, ignore_nan=False, empty=0): + """ + nanmean compatible with generators. + """ + l = iter(l) + if ignore_nan: + l = ifilterfalse(np.isnan, l) + try: + n = 1 + acc = next(l) + except StopIteration: + if empty == 'raise': + raise ValueError('Empty mean') + return empty + for n, v in enumerate(l, 2): + acc += v + if n == 1: + return acc + return acc / n \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/metrics/__init__.py b/cv_lib/cv_lib/segmentation/tgs_salt/metrics/__init__.py new file mode 100644 index 00000000..130104f8 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/metrics/__init__.py @@ -0,0 +1,179 @@ +import warnings + +import numpy as np +import torch +from ignite.metrics import Metric + + +def do_kaggle_metric(predict, truth, threshold=0.5): + N = len(predict) + predict = predict.reshape(N, -1) + truth = truth.reshape(N, -1) + + predict = predict > threshold + truth = truth > 0.5 + intersection = truth & predict + union = truth | predict + iou = intersection.sum(1) / (union.sum(1) + 1e-8) + + # ------------------------------------------- + result = [] + precision = [] + is_empty_truth = truth.sum(1) == 0 + is_empty_predict = predict.sum(1) == 0 + + threshold = np.array([0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95]) + for t in threshold: + p = iou >= t + + tp = (~is_empty_truth) & (~is_empty_predict) & (iou > t) + fp = (~is_empty_truth) & (~is_empty_predict) & (iou <= t) + fn = (~is_empty_truth) & (is_empty_predict) + fp_empty = (is_empty_truth) & (~is_empty_predict) + tn_empty = (is_empty_truth) & (is_empty_predict) + + p = (tp + tn_empty) / (tp + tn_empty + fp + fp_empty + fn) + + result.append(np.column_stack((tp, fp, fn, tn_empty, fp_empty))) + precision.append(p) + + result = np.array(result).transpose(1, 2, 0) + precision = np.column_stack(precision) + precision = precision.mean(1) + + return precision, result, threshold + + +class KaggleMetric(Metric): + def __init__(self, output_transform=lambda x: x): + super(KaggleMetric, self).__init__(output_transform=output_transform) + + def reset(self): + self._predictions = torch.tensor([], dtype=torch.float32) + self._targets = torch.tensor([], dtype=torch.long) + + def update(self, output): + y_pred, y = output + + y_pred = y_pred.type_as(self._predictions) + y = y.type_as(self._targets) + + self._predictions = torch.cat([self._predictions, y_pred], dim=0) + self._targets = torch.cat([self._targets, y], dim=0) + + # Check once the signature and execution of compute_fn + if self._predictions.shape == y_pred.shape: + try: + self.compute() + except Exception as e: + warnings.warn( + "Probably, there can be a problem with `compute_fn`:\n {}.".format( + e + ), + RuntimeWarning, + ) + + def compute(self): + precision, _, _ = do_kaggle_metric( + self._predictions.numpy(), self._targets.numpy(), 0.5 + ) + precision = precision.mean() + return precision + + +def var_to_np(var): + """Take a pytorch variable and make numpy + """ + if type(var) in [np.array, np.ndarray]: + return var + + # If input is list we do this for all elements + if type(var) == type([]): + out = [] + for v in var: + out.append(var_to_np(v)) + return out + + # TODO: Replace this is from the original implementation and is a really bad idea + try: + var = var.cpu() + except: + None + try: + var = var.data + except: + None + try: + var = var.numpy() + except: + None + + if type(var) == tuple: + var = var[0] + return var + + +def pixel_wise_accuracy(predicted_class, labels): + labels = var_to_np(labels) + predicted_class = var_to_np(predicted_class) + + accuracies = {} + for cls in np.unique(labels): + if cls >= 0: + accuracies["accuracy_class_" + str(cls)] = int( + np.mean(predicted_class[labels == cls] == cls) * 100 + ) + accuracies["average_class_accuracy"] = np.mean([acc for acc in accuracies.values()]) + return accuracies + +EPS = 1e-10 + + + +def overall_pixel_accuracy(hist): + """Computes the total pixel accuracy. + The overall pixel accuracy provides an intuitive + approximation for the qualitative perception of the + label when it is viewed in its overall shape but not + its details. + Args: + hist: confusion matrix. + Returns: + overall_acc: the overall pixel accuracy. + """ + correct = torch.diag(hist).sum() + total = hist.sum() + overall_acc = correct / (total + EPS) + return overall_acc + +def pixel_wise_accuracy2(predicted_class, labels, num_classes=1): + hist = torch.zeros(num_classes+1).cuda() + for i in range(num_classes+1): + hist[i]=torch.mean((predicted_class[labels == i] == i).float()) * 100 + return torch.mean(hist) + +class PixelwiseAccuracy(Metric): + def __init__(self, output_transform=lambda x: x): + super(PixelwiseAccuracy, self).__init__(output_transform=output_transform) + self._threshold=0.5 + + def reset(self): + self._accuracies = [] + self._weights = [] + + def update(self, output): + y_pred, y = output + y_pred[y_pred>self._threshold]=1 + y_pred[y_pred<=self._threshold]=0 + try: + acc_dict = pixel_wise_accuracy(y_pred, y) + print(acc_dict) + self._accuracies.append(acc_dict["average_class_accuracy"]) + self._weights.append(y_pred.shape[0]) # Weight by batch size + except Exception as e: + warnings.warn( + "Error computing accuracy:\n {}.".format(e), RuntimeWarning + ) + + def compute(self): + return np.average(self._accuracies, weights=self._weights) diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/metrics/apex.py b/cv_lib/cv_lib/segmentation/tgs_salt/metrics/apex.py new file mode 100644 index 00000000..f271133c --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/metrics/apex.py @@ -0,0 +1,102 @@ +import torch.distributed as dist +from ignite.metrics.metric import Metric +from ignite.exceptions import NotComputableError +import torch + +@torch.no_grad() +def reduce_tensor(tensor, world_size): + rt = tensor.clone() + dist.all_reduce(rt, op=dist.reduce_op.SUM) + rt /= world_size + return rt + +@torch.no_grad() +def gather_tensor(tensor, world_size): + gather_t = [torch.ones_like(tensor).cuda() for _ in range(dist.get_world_size())] + dist.all_gather(gather_t, tensor) + return gather_t + + +class AverageMetric(Metric): + def __init__(self, world_size, batch_size, output_transform=lambda x: x): + super(AverageMetric, self).__init__(output_transform=output_transform) + self._world_size = world_size + self._batch_size = batch_size + self._metric_name = "Metric" + + def reset(self): + self._sum = 0 + self._num_examples = 0 + + @torch.no_grad() + def update(self, output): + reduced_metric = reduce_tensor(output, self._world_size) + self._sum += reduced_metric * self._batch_size + self._num_examples += self._batch_size + + @torch.no_grad() + def compute(self): + if self._num_examples == 0: + raise NotComputableError( + f"{self._metric_name} must have at least one example before it can be computed." + ) + return self._sum / self._num_examples + + +class LossMetric(AverageMetric): + def __init__(self, loss_fn, world_size, batch_size, output_transform=lambda x: x): + super(LossMetric, self).__init__( + world_size, batch_size, output_transform=output_transform + ) + self._loss_fn = loss_fn + self._metric_name = "Loss" + + def update(self, output): + pred, y = output + loss = self._loss_fn(pred, y) + super().update(loss) + + +class KaggleMetric(Metric): + def __init__(self, output_transform=lambda x: x): + super(KaggleMetric, self).__init__(output_transform=output_transform) + + @torch.no_grad() + def reset(self): + self._predictions = torch.tensor([], dtype=torch.float32).cuda() + self._targets = torch.tensor([], dtype=torch.float32).cuda() + + @torch.no_grad() + def update(self, output): + y_pred, y = output + y_pred = y_pred.type_as(self._predictions) + y = y.type_as(self._targets) + self._predictions = torch.cat([self._predictions, y_pred], dim=0) + self._targets = torch.cat([self._targets, y], dim=0) + + @torch.no_grad() + def compute(self): + gather_predictions = gather_tensor(self._predictions, self._world_size) + gather_targets = gather_tensor(self._targets, self._world_size) + predictions = torch.cat(gather_predictions, dim=0) + targets = torch.cat(gather_targets, dim=0) + precision, _, _ = do_kaggle_metric(predictions.detach().cpu().numpy(), targets.detach().cpu().numpy(), 0.5) + precision = precision.mean() + return precision + + + +class PixelwiseAccuracyMetric(AverageMetric): + def __init__(self, world_size, batch_size, output_transform=lambda x: x, threshold=0.5): + super(PixelwiseAccuracyMetric, self).__init__( + world_size, batch_size, output_transform=output_transform + ) + self._acc_fn = pixel_wise_accuracy2 + self._metric_name = "PixelwiseAccuracy" + self._threshold = threshold + + def update(self, output): + y_pred, y = output + y_pred[y_pred>self._threshold] = 1 + y_pred[y_pred<=self._threshold] = 0 + super().update(self._acc_fn(y_pred, y)) diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/metrics/horovod.py b/cv_lib/cv_lib/segmentation/tgs_salt/metrics/horovod.py new file mode 100644 index 00000000..d6d017a2 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/metrics/horovod.py @@ -0,0 +1,87 @@ +from ignite.metrics.metric import Metric +from ignite.exceptions import NotComputableError +import torch +import horovod.torch as hvd + + +def reduce_tensor(tensor): + """Computes average of tensor + + Args: + tensor ([type]): [description] + + Returns: + [type]: [description] + """ + return hvd.allreduce(tensor) + + +def gather_tensor(tensor): + return hvd.allgather(tensor) + + +class AverageMetric(Metric): + def __init__(self, world_size, batch_size, output_transform=lambda x: x): + super(AverageMetric, self).__init__(output_transform=output_transform) + self._world_size = world_size + self._batch_size = batch_size + self._metric_name = "Metric" + + def reset(self): + self._sum = 0 + self._num_examples = 0 + + @torch.no_grad() + def update(self, output): + reduced_metric = reduce_tensor(output) + self._sum += reduced_metric * self._batch_size + self._num_examples += self._batch_size + + @torch.no_grad() + def compute(self): + if self._num_examples == 0: + raise NotComputableError( + f"{self._metric_name} must have at least one example before it can be computed." + ) + return self._sum / self._num_examples + + +class LossMetric(AverageMetric): + def __init__(self, loss_fn, world_size, batch_size, output_transform=lambda x: x): + super(LossMetric, self).__init__( + world_size, batch_size, output_transform=output_transform + ) + self._loss_fn = loss_fn + self._metric_name = "Loss" + + def update(self, output): + pred, y = output + loss = self._loss_fn(pred, y) + super().update(loss) + + +class KaggleMetric(Metric): + def __init__(self, output_transform=lambda x: x): + super(KaggleMetric, self).__init__(output_transform=output_transform) + + @torch.no_grad() + def reset(self): + self._predictions = torch.tensor([], dtype=torch.float32).cuda() + self._targets = torch.tensor([], dtype=torch.float32).cuda() + + @torch.no_grad() + def update(self, output): + y_pred, y = output + y_pred = y_pred.type_as(self._predictions) + y = y.type_as(self._targets) + self._predictions = torch.cat([self._predictions, y_pred], dim=0) + self._targets = torch.cat([self._targets, y], dim=0) + + @torch.no_grad() + def compute(self): + predictions = gather_tensor(self._predictions) + targets = gather_tensor(self._targets) + precision, _, _ = do_kaggle_metric(predictions.detach().cpu().numpy(), targets.detach().cpu().numpy(), 0.5) + precision = precision.mean() + return precision + diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/models/__init__.py b/cv_lib/cv_lib/segmentation/tgs_salt/models/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/models/__init__.py @@ -0,0 +1 @@ + diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/models/resnet_unet.py b/cv_lib/cv_lib/segmentation/tgs_salt/models/resnet_unet.py new file mode 100644 index 00000000..8b3906c9 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/models/resnet_unet.py @@ -0,0 +1,349 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +import torchvision + + +class FPAv2(nn.Module): + def __init__(self, input_dim, output_dim): + super(FPAv2, self).__init__() + self.glob = nn.Sequential(nn.AdaptiveAvgPool2d(1), + nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False)) + + self.down2_1 = nn.Sequential(nn.Conv2d(input_dim, input_dim, kernel_size=5, stride=2, padding=2, bias=False), + nn.BatchNorm2d(input_dim), + nn.ELU(True)) + self.down2_2 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=5, padding=2, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True)) + + self.down3_1 = nn.Sequential(nn.Conv2d(input_dim, input_dim, kernel_size=3, stride=2, padding=1, bias=False), + nn.BatchNorm2d(input_dim), + nn.ELU(True)) + self.down3_2 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=3, padding=1, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True)) + + self.conv1 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True)) + + def forward(self, x): + # x shape: 512, 16, 16 + x_glob = self.glob(x) # 256, 1, 1 + x_glob = F.upsample(x_glob, scale_factor=16, mode='bilinear', align_corners=True) # 256, 16, 16 + + d2 = self.down2_1(x) # 512, 8, 8 + d3 = self.down3_1(d2) # 512, 4, 4 + + d2 = self.down2_2(d2) # 256, 8, 8 + d3 = self.down3_2(d3) # 256, 4, 4 + + d3 = F.upsample(d3, scale_factor=2, mode='bilinear', align_corners=True) # 256, 8, 8 + d2 = d2 + d3 + + d2 = F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True) # 256, 16, 16 + x = self.conv1(x) # 256, 16, 16 + x = x * d2 + + x = x + x_glob + + return x + + +def conv3x3(input_dim, output_dim, rate=1): + return nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=3, dilation=rate, padding=rate, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True)) + + +class SpatialAttention2d(nn.Module): + def __init__(self, channel): + super(SpatialAttention2d, self).__init__() + self.squeeze = nn.Conv2d(channel, 1, kernel_size=1, bias=False) + self.sigmoid = nn.Sigmoid() + + def forward(self, x): + z = self.squeeze(x) + z = self.sigmoid(z) + return x * z + + +class GAB(nn.Module): + def __init__(self, input_dim, reduction=4): + super(GAB, self).__init__() + self.global_avgpool = nn.AdaptiveAvgPool2d(1) + self.conv1 = nn.Conv2d(input_dim, input_dim // reduction, kernel_size=1, stride=1) + self.conv2 = nn.Conv2d(input_dim // reduction, input_dim, kernel_size=1, stride=1) + self.relu = nn.ReLU(inplace=True) + self.sigmoid = nn.Sigmoid() + + def forward(self, x): + z = self.global_avgpool(x) + z = self.relu(self.conv1(z)) + z = self.sigmoid(self.conv2(z)) + return x * z + + +class Decoder(nn.Module): + def __init__(self, in_channels, channels, out_channels): + super(Decoder, self).__init__() + self.conv1 = conv3x3(in_channels, channels) + self.conv2 = conv3x3(channels, out_channels) + self.s_att = SpatialAttention2d(out_channels) + self.c_att = GAB(out_channels, 16) + + def forward(self, x, e=None): + x = F.upsample(input=x, scale_factor=2, mode='bilinear', align_corners=True) + if e is not None: + x = torch.cat([x, e], 1) + x = self.conv1(x) + x = self.conv2(x) + s = self.s_att(x) + c = self.c_att(x) + output = s + c + return output + + +class Decoderv2(nn.Module): + def __init__(self, up_in, x_in, n_out): + super(Decoderv2, self).__init__() + up_out = x_out = n_out // 2 + self.x_conv = nn.Conv2d(x_in, x_out, 1, bias=False) + self.tr_conv = nn.ConvTranspose2d(up_in, up_out, 2, stride=2) + self.bn = nn.BatchNorm2d(n_out) + self.relu = nn.ReLU(True) + self.s_att = SpatialAttention2d(n_out) + self.c_att = GAB(n_out, 16) + + def forward(self, up_p, x_p): + up_p = self.tr_conv(up_p) + x_p = self.x_conv(x_p) + + cat_p = torch.cat([up_p, x_p], 1) + cat_p = self.relu(self.bn(cat_p)) + s = self.s_att(cat_p) + c = self.c_att(cat_p) + return s + c + + +class SCse(nn.Module): + def __init__(self, dim): + super(SCse, self).__init__() + self.satt = SpatialAttention2d(dim) + self.catt = GAB(dim) + + def forward(self, x): + return self.satt(x) + self.catt(x) + + +# stage1 model +class Res34Unetv4(nn.Module): + def __init__(self): + super(Res34Unetv4, self).__init__() + self.resnet = torchvision.models.resnet34(True) + + self.conv1 = nn.Sequential( + self.resnet.conv1, + self.resnet.bn1, + self.resnet.relu) + + self.encode2 = nn.Sequential(self.resnet.layer1, + SCse(64)) + self.encode3 = nn.Sequential(self.resnet.layer2, + SCse(128)) + self.encode4 = nn.Sequential(self.resnet.layer3, + SCse(256)) + self.encode5 = nn.Sequential(self.resnet.layer4, + SCse(512)) + + self.center = nn.Sequential(FPAv2(512, 256), + nn.MaxPool2d(2, 2)) + + self.decode5 = Decoderv2(256, 512, 64) + self.decode4 = Decoderv2(64, 256, 64) + self.decode3 = Decoderv2(64, 128, 64) + self.decode2 = Decoderv2(64, 64, 64) + self.decode1 = Decoder(64, 32, 64) + + self.logit = nn.Sequential(nn.Conv2d(320, 64, kernel_size=3, padding=1), + nn.ELU(True), + nn.Conv2d(64, 1, kernel_size=1, bias=False)) + + def forward(self, x): + # x: (batch_size, 3, 256, 256) + + x = self.conv1(x) # 64, 128, 128 + e2 = self.encode2(x) # 64, 128, 128 + e3 = self.encode3(e2) # 128, 64, 64 + e4 = self.encode4(e3) # 256, 32, 32 + e5 = self.encode5(e4) # 512, 16, 16 + + f = self.center(e5) # 256, 8, 8 + + d5 = self.decode5(f, e5) # 64, 16, 16 + d4 = self.decode4(d5, e4) # 64, 32, 32 + d3 = self.decode3(d4, e3) # 64, 64, 64 + d2 = self.decode2(d3, e2) # 64, 128, 128 + d1 = self.decode1(d2) # 64, 256, 256 + + f = torch.cat((d1, + F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True), + F.upsample(d3, scale_factor=4, mode='bilinear', align_corners=True), + F.upsample(d4, scale_factor=8, mode='bilinear', align_corners=True), + F.upsample(d5, scale_factor=16, mode='bilinear', align_corners=True)), 1) # 320, 256, 256 + + logit = self.logit(f) # 1, 256, 256 + + return logit + + +# stage2 model +class Res34Unetv3(nn.Module): + def __init__(self): + super(Res34Unetv3, self).__init__() + self.resnet = torchvision.models.resnet34(True) + + self.conv1 = nn.Sequential( + self.resnet.conv1, + self.resnet.bn1, + self.resnet.relu) + + self.encode2 = nn.Sequential(self.resnet.layer1, + SCse(64)) + self.encode3 = nn.Sequential(self.resnet.layer2, + SCse(128)) + self.encode4 = nn.Sequential(self.resnet.layer3, + SCse(256)) + self.encode5 = nn.Sequential(self.resnet.layer4, + SCse(512)) + + self.center = nn.Sequential(FPAv2(512, 256), + nn.MaxPool2d(2, 2)) + + self.decode5 = Decoderv2(256, 512, 64) + self.decode4 = Decoderv2(64, 256, 64) + self.decode3 = Decoderv2(64, 128, 64) + self.decode2 = Decoderv2(64, 64, 64) + self.decode1 = Decoder(64, 32, 64) + + self.dropout2d = nn.Dropout2d(0.4) + self.dropout = nn.Dropout(0.4) + + self.fuse_pixel = conv3x3(320, 64) + self.logit_pixel = nn.Conv2d(64, 1, kernel_size=1, bias=False) + + self.fuse_image = nn.Sequential(nn.Linear(512, 64), + nn.ELU(True)) + self.logit_image = nn.Sequential(nn.Linear(64, 1), + nn.Sigmoid()) + self.logit = nn.Sequential(nn.Conv2d(128, 64, kernel_size=3, padding=1, bias=False), + nn.ELU(True), + nn.Conv2d(64, 1, kernel_size=1, bias=False)) + + def forward(self, x): + # x: (batch_size, 3, 256, 256) + batch_size, c, h, w = x.shape + + x = self.conv1(x) # 64, 128, 128 + e2 = self.encode2(x) # 64, 128, 128 + e3 = self.encode3(e2) # 128, 64, 64 + e4 = self.encode4(e3) # 256, 32, 32 + e5 = self.encode5(e4) # 512, 16, 16 + + e = F.adaptive_avg_pool2d(e5, output_size=1).view(batch_size, -1) # 512 + e = self.dropout(e) + + f = self.center(e5) # 256, 8, 8 + + d5 = self.decode5(f, e5) # 64, 16, 16 + d4 = self.decode4(d5, e4) # 64, 32, 32 + d3 = self.decode3(d4, e3) # 64, 64, 64 + d2 = self.decode2(d3, e2) # 64, 128, 128 + d1 = self.decode1(d2) # 64, 256, 256 + + f = torch.cat((d1, + F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True), + F.upsample(d3, scale_factor=4, mode='bilinear', align_corners=True), + F.upsample(d4, scale_factor=8, mode='bilinear', align_corners=True), + F.upsample(d5, scale_factor=16, mode='bilinear', align_corners=True)), 1) # 320, 256, 256 + f = self.dropout2d(f) + + # segmentation process + fuse_pixel = self.fuse_pixel(f) # 64, 256, 256 + logit_pixel = self.logit_pixel(fuse_pixel) # 1, 256, 256 + + # classification process + fuse_image = self.fuse_image(e) # 64 + logit_image = self.logit_image(fuse_image) # 1 + + # combine segmentation and classification + fuse = torch.cat([fuse_pixel, + F.upsample(fuse_image.view(batch_size, -1, 1, 1), scale_factor=256, mode='bilinear', + align_corners=True)], 1) # 128, 256, 256 + logit = self.logit(fuse) # 1, 256, 256 + + return logit, logit_pixel, logit_image.view(-1) + + +# stage3 model +class Res34Unetv5(nn.Module): + def __init__(self): + super(Res34Unetv5, self).__init__() + self.resnet = torchvision.models.resnet34(True) + + self.conv1 = nn.Sequential( + nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False), + self.resnet.bn1, + self.resnet.relu) + + self.encode2 = nn.Sequential(self.resnet.layer1, + SCse(64)) + self.encode3 = nn.Sequential(self.resnet.layer2, + SCse(128)) + self.encode4 = nn.Sequential(self.resnet.layer3, + SCse(256)) + self.encode5 = nn.Sequential(self.resnet.layer4, + SCse(512)) + + self.center = nn.Sequential(FPAv2(512, 256), + nn.MaxPool2d(2, 2)) + + self.decode5 = Decoderv2(256, 512, 64) + self.decode4 = Decoderv2(64, 256, 64) + self.decode3 = Decoderv2(64, 128, 64) + self.decode2 = Decoderv2(64, 64, 64) + + self.logit = nn.Sequential(nn.Conv2d(256, 32, kernel_size=3, padding=1), + nn.ELU(True), + nn.Conv2d(32, 1, kernel_size=1, bias=False)) + + def forward(self, x): + # x: batch_size, 3, 128, 128 + x = self.conv1(x) # 64, 128, 128 + e2 = self.encode2(x) # 64, 128, 128 + e3 = self.encode3(e2) # 128, 64, 64 + e4 = self.encode4(e3) # 256, 32, 32 + e5 = self.encode5(e4) # 512, 16, 16 + + f = self.center(e5) # 256, 8, 8 + + d5 = self.decode5(f, e5) # 64, 16, 16 + d4 = self.decode4(d5, e4) # 64, 32, 32 + d3 = self.decode3(d4, e3) # 64, 64, 64 + d2 = self.decode2(d3, e2) # 64, 128, 128 + + f = torch.cat((d2, + F.upsample(d3, scale_factor=2, mode='bilinear', align_corners=True), + F.upsample(d4, scale_factor=4, mode='bilinear', align_corners=True), + F.upsample(d5, scale_factor=8, mode='bilinear', align_corners=True)), 1) # 256, 128, 128 + + f = F.dropout2d(f, p=0.4) + logit = self.logit(f) # 1, 128, 128 + + return logit + + +def get_seg_model(cfg, **kwargs): + model = Res34Unetv4() + return model \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/models/seg_hrnet.py b/cv_lib/cv_lib/segmentation/tgs_salt/models/seg_hrnet.py new file mode 100644 index 00000000..947bf052 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/models/seg_hrnet.py @@ -0,0 +1,477 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# Written by Ke Sun (sunk@mail.ustc.edu.cn) +# ------------------------------------------------------------------------------ +"""HRNET for segmentation taken from https://github.com/HRNet/HRNet-Semantic-Segmentation +pytorch-v1.1 branch +hash: 06142dc1c7026e256a7561c3e875b06622b5670f + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import logging +import os + +import numpy as np +import torch +import torch._utils +import torch.nn as nn +import torch.nn.functional as F + +BatchNorm2d = nn.BatchNorm2d +BN_MOMENTUM = 0.1 +logger = logging.getLogger(__name__) + +def conv3x3(in_planes, out_planes, stride=1): + """3x3 convolution with padding""" + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, + padding=1, bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + padding=1, bias=False) + self.bn2 = BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, + bias=False) + self.bn3 = BatchNorm2d(planes * self.expansion, + momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class HighResolutionModule(nn.Module): + def __init__(self, num_branches, blocks, num_blocks, num_inchannels, + num_channels, fuse_method, multi_scale_output=True): + super(HighResolutionModule, self).__init__() + self._check_branches( + num_branches, blocks, num_blocks, num_inchannels, num_channels) + + self.num_inchannels = num_inchannels + self.fuse_method = fuse_method + self.num_branches = num_branches + + self.multi_scale_output = multi_scale_output + + self.branches = self._make_branches( + num_branches, blocks, num_blocks, num_channels) + self.fuse_layers = self._make_fuse_layers() + self.relu = nn.ReLU(inplace=True) + + def _check_branches(self, num_branches, blocks, num_blocks, + num_inchannels, num_channels): + if num_branches != len(num_blocks): + error_msg = 'NUM_BRANCHES({}) <> NUM_BLOCKS({})'.format( + num_branches, len(num_blocks)) + logger.error(error_msg) + raise ValueError(error_msg) + + if num_branches != len(num_channels): + error_msg = 'NUM_BRANCHES({}) <> NUM_CHANNELS({})'.format( + num_branches, len(num_channels)) + logger.error(error_msg) + raise ValueError(error_msg) + + if num_branches != len(num_inchannels): + error_msg = 'NUM_BRANCHES({}) <> NUM_INCHANNELS({})'.format( + num_branches, len(num_inchannels)) + logger.error(error_msg) + raise ValueError(error_msg) + + def _make_one_branch(self, branch_index, block, num_blocks, num_channels, + stride=1): + downsample = None + if stride != 1 or \ + self.num_inchannels[branch_index] != num_channels[branch_index] * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.num_inchannels[branch_index], + num_channels[branch_index] * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm2d(num_channels[branch_index] * block.expansion, + momentum=BN_MOMENTUM), + ) + + layers = [] + layers.append(block(self.num_inchannels[branch_index], + num_channels[branch_index], stride, downsample)) + self.num_inchannels[branch_index] = \ + num_channels[branch_index] * block.expansion + for i in range(1, num_blocks[branch_index]): + layers.append(block(self.num_inchannels[branch_index], + num_channels[branch_index])) + + return nn.Sequential(*layers) + + def _make_branches(self, num_branches, block, num_blocks, num_channels): + branches = [] + + for i in range(num_branches): + branches.append( + self._make_one_branch(i, block, num_blocks, num_channels)) + + return nn.ModuleList(branches) + + def _make_fuse_layers(self): + if self.num_branches == 1: + return None + + num_branches = self.num_branches + num_inchannels = self.num_inchannels + fuse_layers = [] + for i in range(num_branches if self.multi_scale_output else 1): + fuse_layer = [] + for j in range(num_branches): + if j > i: + fuse_layer.append(nn.Sequential( + nn.Conv2d(num_inchannels[j], + num_inchannels[i], + 1, + 1, + 0, + bias=False), + BatchNorm2d(num_inchannels[i], momentum=BN_MOMENTUM))) + elif j == i: + fuse_layer.append(None) + else: + conv3x3s = [] + for k in range(i-j): + if k == i - j - 1: + num_outchannels_conv3x3 = num_inchannels[i] + conv3x3s.append(nn.Sequential( + nn.Conv2d(num_inchannels[j], + num_outchannels_conv3x3, + 3, 2, 1, bias=False), + BatchNorm2d(num_outchannels_conv3x3, + momentum=BN_MOMENTUM))) + else: + num_outchannels_conv3x3 = num_inchannels[j] + conv3x3s.append(nn.Sequential( + nn.Conv2d(num_inchannels[j], + num_outchannels_conv3x3, + 3, 2, 1, bias=False), + BatchNorm2d(num_outchannels_conv3x3, + momentum=BN_MOMENTUM), + nn.ReLU(inplace=True))) + fuse_layer.append(nn.Sequential(*conv3x3s)) + fuse_layers.append(nn.ModuleList(fuse_layer)) + + return nn.ModuleList(fuse_layers) + + def get_num_inchannels(self): + return self.num_inchannels + + def forward(self, x): + if self.num_branches == 1: + return [self.branches[0](x[0])] + + for i in range(self.num_branches): + x[i] = self.branches[i](x[i]) + + x_fuse = [] + for i in range(len(self.fuse_layers)): + y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]) + for j in range(1, self.num_branches): + if i == j: + y = y + x[j] + elif j > i: + width_output = x[i].shape[-1] + height_output = x[i].shape[-2] + y = y + F.interpolate( + self.fuse_layers[i][j](x[j]), + size=[height_output, width_output], + mode='bilinear') + else: + y = y + self.fuse_layers[i][j](x[j]) + x_fuse.append(self.relu(y)) + + return x_fuse + + +blocks_dict = { + 'BASIC': BasicBlock, + 'BOTTLENECK': Bottleneck +} + + +class HighResolutionNet(nn.Module): + + def __init__(self, config, **kwargs): + extra = config.MODEL.EXTRA + super(HighResolutionNet, self).__init__() + + # stem net + self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, + bias=False) + self.bn1 = BatchNorm2d(64, momentum=BN_MOMENTUM) + self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, + bias=False) + self.bn2 = BatchNorm2d(64, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + + self.layer1 = self._make_layer(Bottleneck, 64, 64, 4) + + self.stage2_cfg = extra['STAGE2'] + num_channels = self.stage2_cfg['NUM_CHANNELS'] + block = blocks_dict[self.stage2_cfg['BLOCK']] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels))] + self.transition1 = self._make_transition_layer([256], num_channels) + self.stage2, pre_stage_channels = self._make_stage( + self.stage2_cfg, num_channels) + + self.stage3_cfg = extra['STAGE3'] + num_channels = self.stage3_cfg['NUM_CHANNELS'] + block = blocks_dict[self.stage3_cfg['BLOCK']] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels))] + self.transition2 = self._make_transition_layer( + pre_stage_channels, num_channels) + self.stage3, pre_stage_channels = self._make_stage( + self.stage3_cfg, num_channels) + + self.stage4_cfg = extra['STAGE4'] + num_channels = self.stage4_cfg['NUM_CHANNELS'] + block = blocks_dict[self.stage4_cfg['BLOCK']] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels))] + self.transition3 = self._make_transition_layer( + pre_stage_channels, num_channels) + self.stage4, pre_stage_channels = self._make_stage( + self.stage4_cfg, num_channels, multi_scale_output=True) + + last_inp_channels = np.int(np.sum(pre_stage_channels)) + + self.last_layer = nn.Sequential( + nn.Conv2d( + in_channels=last_inp_channels, + out_channels=last_inp_channels, + kernel_size=1, + stride=1, + padding=0), + BatchNorm2d(last_inp_channels, momentum=BN_MOMENTUM), + nn.ReLU(inplace=True), + nn.Conv2d( + in_channels=last_inp_channels, + out_channels=config.DATASET.NUM_CLASSES, + kernel_size=extra.FINAL_CONV_KERNEL, + stride=1, + padding=1 if extra.FINAL_CONV_KERNEL == 3 else 0) + ) + + def _make_transition_layer( + self, num_channels_pre_layer, num_channels_cur_layer): + num_branches_cur = len(num_channels_cur_layer) + num_branches_pre = len(num_channels_pre_layer) + + transition_layers = [] + for i in range(num_branches_cur): + if i < num_branches_pre: + if num_channels_cur_layer[i] != num_channels_pre_layer[i]: + transition_layers.append(nn.Sequential( + nn.Conv2d(num_channels_pre_layer[i], + num_channels_cur_layer[i], + 3, + 1, + 1, + bias=False), + BatchNorm2d( + num_channels_cur_layer[i], momentum=BN_MOMENTUM), + nn.ReLU(inplace=True))) + else: + transition_layers.append(None) + else: + conv3x3s = [] + for j in range(i+1-num_branches_pre): + inchannels = num_channels_pre_layer[-1] + outchannels = num_channels_cur_layer[i] \ + if j == i-num_branches_pre else inchannels + conv3x3s.append(nn.Sequential( + nn.Conv2d( + inchannels, outchannels, 3, 2, 1, bias=False), + BatchNorm2d(outchannels, momentum=BN_MOMENTUM), + nn.ReLU(inplace=True))) + transition_layers.append(nn.Sequential(*conv3x3s)) + + return nn.ModuleList(transition_layers) + + def _make_layer(self, block, inplanes, planes, blocks, stride=1): + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), + ) + + layers = [] + layers.append(block(inplanes, planes, stride, downsample)) + inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(inplanes, planes)) + + return nn.Sequential(*layers) + + def _make_stage(self, layer_config, num_inchannels, + multi_scale_output=True): + num_modules = layer_config['NUM_MODULES'] + num_branches = layer_config['NUM_BRANCHES'] + num_blocks = layer_config['NUM_BLOCKS'] + num_channels = layer_config['NUM_CHANNELS'] + block = blocks_dict[layer_config['BLOCK']] + fuse_method = layer_config['FUSE_METHOD'] + + modules = [] + for i in range(num_modules): + # multi_scale_output is only used last module + if not multi_scale_output and i == num_modules - 1: + reset_multi_scale_output = False + else: + reset_multi_scale_output = True + modules.append( + HighResolutionModule(num_branches, + block, + num_blocks, + num_inchannels, + num_channels, + fuse_method, + reset_multi_scale_output) + ) + num_inchannels = modules[-1].get_num_inchannels() + + return nn.Sequential(*modules), num_inchannels + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + x = self.layer1(x) + + x_list = [] + for i in range(self.stage2_cfg['NUM_BRANCHES']): + if self.transition1[i] is not None: + x_list.append(self.transition1[i](x)) + else: + x_list.append(x) + y_list = self.stage2(x_list) + + x_list = [] + for i in range(self.stage3_cfg['NUM_BRANCHES']): + if self.transition2[i] is not None: + x_list.append(self.transition2[i](y_list[-1])) + else: + x_list.append(y_list[i]) + y_list = self.stage3(x_list) + + x_list = [] + for i in range(self.stage4_cfg['NUM_BRANCHES']): + if self.transition3[i] is not None: + x_list.append(self.transition3[i](y_list[-1])) + else: + x_list.append(y_list[i]) + x = self.stage4(x_list) + + # Upsampling + x0_h, x0_w = x[0].size(2), x[0].size(3) + x1 = F.upsample(x[1], size=(x0_h, x0_w), mode='bilinear') + x2 = F.upsample(x[2], size=(x0_h, x0_w), mode='bilinear') + x3 = F.upsample(x[3], size=(x0_h, x0_w), mode='bilinear') + + x = torch.cat([x[0], x1, x2, x3], 1) + + x = self.last_layer(x) + + return x + + def init_weights(self, pretrained='',): + logger.info('=> init weights from normal distribution') + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.normal_(m.weight, std=0.001) + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + if os.path.isfile(pretrained): + pretrained_dict = torch.load(pretrained) + logger.info('=> loading pretrained model {}'.format(pretrained)) + model_dict = self.state_dict() + pretrained_dict = {k: v for k, v in pretrained_dict.items() + if k in model_dict.keys()} + #for k, _ in pretrained_dict.items(): + # logger.info( + # '=> loading {} pretrained model {}'.format(k, pretrained)) + model_dict.update(pretrained_dict) + self.load_state_dict(model_dict) + +def get_seg_model(cfg, **kwargs): + model = HighResolutionNet(cfg, **kwargs) + model.init_weights(cfg.MODEL.PRETRAINED) + + return model diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/models/unet.py b/cv_lib/cv_lib/segmentation/tgs_salt/models/unet.py new file mode 100644 index 00000000..4ed20136 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/models/unet.py @@ -0,0 +1,132 @@ +""" Taken from https://github.com/milesial/Pytorch-UNet + +""" +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.autograd import Variable +from torch.nn import init + + + + +class double_conv(nn.Module): + '''(conv => BN => ReLU) * 2''' + def __init__(self, in_ch, out_ch): + super(double_conv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d(in_ch, out_ch, 3, padding=1), + nn.BatchNorm2d(out_ch), + nn.ReLU(inplace=True), + nn.Conv2d(out_ch, out_ch, 3, padding=1), + nn.BatchNorm2d(out_ch), + nn.ReLU(inplace=True) + ) + + def forward(self, x): + x = self.conv(x) + return x + + +class inconv(nn.Module): + def __init__(self, in_ch, out_ch): + super(inconv, self).__init__() + self.conv = double_conv(in_ch, out_ch) + + def forward(self, x): + x = self.conv(x) + return x + + +class down(nn.Module): + def __init__(self, in_ch, out_ch): + super(down, self).__init__() + self.mpconv = nn.Sequential( + nn.MaxPool2d(2), + double_conv(in_ch, out_ch) + ) + + def forward(self, x): + x = self.mpconv(x) + return x + + +class up(nn.Module): + def __init__(self, in_ch, out_ch, bilinear=True): + super(up, self).__init__() + + if bilinear: + self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) + else: + self.up = nn.ConvTranspose2d(in_ch//2, in_ch//2, 2, stride=2) + + self.conv = double_conv(in_ch, out_ch) + + def forward(self, x1, x2): + x1 = self.up(x1) + + # input is CHW + diffY = x2.size()[2] - x1.size()[2] + diffX = x2.size()[3] - x1.size()[3] + + x1 = F.pad(x1, (diffX // 2, diffX - diffX//2, + diffY // 2, diffY - diffY//2)) + + x = torch.cat([x2, x1], dim=1) + x = self.conv(x) + return x + + +class outconv(nn.Module): + def __init__(self, in_ch, out_ch): + super(outconv, self).__init__() + self.conv = nn.Conv2d(in_ch, out_ch, 1) + + def forward(self, x): + x = self.conv(x) + return x + + +class UNet(nn.Module): + def __init__(self, n_channels, n_classes): + super(UNet, self).__init__() + self.inc = inconv(n_channels, 64) + self.down1 = down(64, 128) + self.down2 = down(128, 256) + self.down3 = down(256, 512) + self.down4 = down(512, 512) + self.up1 = up(1024, 256) + self.up2 = up(512, 128) + self.up3 = up(256, 64) + self.up4 = up(128, 64) + self.outc = outconv(64, n_classes) + + def forward(self, x): + x1 = self.inc(x) + x2 = self.down1(x1) + x3 = self.down2(x2) + x4 = self.down3(x3) + x5 = self.down4(x4) + x = self.up1(x5, x4) + x = self.up2(x, x3) + x = self.up3(x, x2) + x = self.up4(x, x1) + x = self.outc(x) + return x + + +def get_seg_model(cfg, **kwargs): + model = UNet(3, cfg.DATASET.NUM_CLASSES) + return model + + +# if __name__ == "__main__": +# """ +# testing +# """ +# model = UNet(1, in_channels=3, depth=5, merge_mode='concat') +# x = Variable(torch.FloatTensor(np.random.random((1, 3, 320, 320)))) +# out = model(x) +# loss = torch.sum(out) +# loss.backward() diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/transform.py b/cv_lib/cv_lib/segmentation/tgs_salt/transform.py new file mode 100644 index 00000000..85acd2bd --- /dev/null +++ b/cv_lib/cv_lib/segmentation/tgs_salt/transform.py @@ -0,0 +1,187 @@ +import cv2 +import numpy as np +import math +import random + + +def do_resize2(image, mask, H, W): + image = cv2.resize(image,dsize=(W,H)) + mask = cv2.resize(mask,dsize=(W,H)) + mask = (mask>0.5).astype(np.float32) + + return image,mask + +def do_horizontal_flip(image): + #flip left-right + image = cv2.flip(image,1) + return image + +def do_horizontal_flip2(image,mask): + image = do_horizontal_flip(image) + mask = do_horizontal_flip(mask ) + return image, mask + + +def do_center_pad(image, pad_left, pad_right): + return np.pad(image, (pad_left, pad_right), 'edge') + +def do_center_pad2(image, mask, pad_left, pad_right): + image = do_center_pad(image, pad_left, pad_right) + mask = do_center_pad(mask, pad_left, pad_right) + return image, mask + +def do_invert_intensity(image): + #flip left-right + image = np.clip(1-image,0,1) + return image + +def do_brightness_shift(image, alpha=0.125): + image = image + alpha + image = np.clip(image, 0, 1) + return image + + +def do_brightness_multiply(image, alpha=1): + image = alpha*image + image = np.clip(image, 0, 1) + return image + + +#https://www.pyimagesearch.com/2015/10/05/opencv-gamma-correction/ +def do_gamma(image, gamma=1.0): + + image = image ** (1.0 / gamma) + image = np.clip(image, 0, 1) + return image + +def do_shift_scale_crop( image, mask, x0=0, y0=0, x1=1, y1=1 ): + #cv2.BORDER_REFLECT_101 + #cv2.BORDER_CONSTANT + + height, width = image.shape[:2] + image = image[y0:y1,x0:x1] + mask = mask [y0:y1,x0:x1] + + image = cv2.resize(image,dsize=(width,height)) + mask = cv2.resize(mask,dsize=(width,height)) + mask = (mask>0.5).astype(np.float32) + return image, mask + + +def do_random_shift_scale_crop_pad2(image, mask, limit=0.10): + + H, W = image.shape[:2] + + dy = int(H*limit) + y0 = np.random.randint(0,dy) + y1 = H-np.random.randint(0,dy) + + dx = int(W*limit) + x0 = np.random.randint(0,dx) + x1 = W-np.random.randint(0,dx) + + #y0, y1, x0, x1 + image, mask = do_shift_scale_crop( image, mask, x0, y0, x1, y1 ) + return image, mask + +#=========================================================================== + +def do_shift_scale_rotate2( image, mask, dx=0, dy=0, scale=1, angle=0 ): + borderMode=cv2.BORDER_REFLECT_101 + #cv2.BORDER_REFLECT_101 cv2.BORDER_CONSTANT + + height, width = image.shape[:2] + sx = scale + sy = scale + cc = math.cos(angle/180*math.pi)*(sx) + ss = math.sin(angle/180*math.pi)*(sy) + rotate_matrix = np.array([ [cc,-ss], [ss,cc] ]) + + box0 = np.array([ [0,0], [width,0], [width,height], [0,height], ],np.float32) + box1 = box0 - np.array([width/2,height/2]) + box1 = np.dot(box1,rotate_matrix.T) + np.array([width/2+dx,height/2+dy]) + + box0 = box0.astype(np.float32) + box1 = box1.astype(np.float32) + mat = cv2.getPerspectiveTransform(box0,box1) + + image = cv2.warpPerspective(image, mat, (width,height),flags=cv2.INTER_LINEAR, + borderMode=borderMode,borderValue=(0,0,0,)) #cv2.BORDER_CONSTANT, borderValue = (0, 0, 0)) #cv2.BORDER_REFLECT_101 + mask = cv2.warpPerspective(mask, mat, (width,height),flags=cv2.INTER_NEAREST,#cv2.INTER_LINEAR + borderMode=borderMode,borderValue=(0,0,0,)) #cv2.BORDER_CONSTANT, borderValue = (0, 0, 0)) #cv2.BORDER_REFLECT_101 + mask = (mask>0.5).astype(np.float32) + return image, mask + +#https://www.kaggle.com/ori226/data-augmentation-with-elastic-deformations +#https://github.com/letmaik/lensfunpy/blob/master/lensfunpy/util.py +def do_elastic_transform2(image, mask, grid=32, distort=0.2): + borderMode=cv2.BORDER_REFLECT_101 + height, width = image.shape[:2] + + x_step = int(grid) + xx = np.zeros(width,np.float32) + prev = 0 + for x in range(0, width, x_step): + start = x + end = x + x_step + if end > width: + end = width + cur = width + else: + cur = prev + x_step*(1+random.uniform(-distort,distort)) + + xx[start:end] = np.linspace(prev,cur,end-start) + prev=cur + + + y_step = int(grid) + yy = np.zeros(height,np.float32) + prev = 0 + for y in range(0, height, y_step): + start = y + end = y + y_step + if end > height: + end = height + cur = height + else: + cur = prev + y_step*(1+random.uniform(-distort,distort)) + + yy[start:end] = np.linspace(prev,cur,end-start) + prev=cur + + #grid + map_x,map_y = np.meshgrid(xx, yy) + map_x = map_x.astype(np.float32) + map_y = map_y.astype(np.float32) + + #image = map_coordinates(image, coords, order=1, mode='reflect').reshape(shape) + image = cv2.remap(image, map_x, map_y, interpolation=cv2.INTER_LINEAR, borderMode=borderMode,borderValue=(0,0,0,)) + + + mask = cv2.remap(mask, map_x, map_y, interpolation=cv2.INTER_NEAREST, borderMode=borderMode,borderValue=(0,0,0,)) + mask = (mask>0.5).astype(np.float32) + return image, mask + + + + +def do_horizontal_shear2( image, mask, dx=0 ): + borderMode=cv2.BORDER_REFLECT_101 + #cv2.BORDER_REFLECT_101 cv2.BORDER_CONSTANT + + height, width = image.shape[:2] + dx = int(dx*width) + + box0 = np.array([ [0,0], [width,0], [width,height], [0,height], ],np.float32) + box1 = np.array([ [+dx,0], [width+dx,0], [width-dx,height], [-dx,height], ],np.float32) + + box0 = box0.astype(np.float32) + box1 = box1.astype(np.float32) + mat = cv2.getPerspectiveTransform(box0,box1) + + image = cv2.warpPerspective(image, mat, (width,height),flags=cv2.INTER_LINEAR, + borderMode=borderMode,borderValue=(0,0,0,)) #cv2.BORDER_CONSTANT, borderValue = (0, 0, 0)) #cv2.BORDER_REFLECT_101 + mask = cv2.warpPerspective(mask, mat, (width,height),flags=cv2.INTER_NEAREST,#cv2.INTER_LINEAR + borderMode=borderMode,borderValue=(0,0,0,)) #cv2.BORDER_CONSTANT, borderValue = (0, 0, 0)) #cv2.BORDER_REFLECT_101 + mask = (mask>0.5).astype(np.float32) + return image, mask \ No newline at end of file diff --git a/cv_lib/requirements.txt b/cv_lib/requirements.txt new file mode 100644 index 00000000..5c472c45 --- /dev/null +++ b/cv_lib/requirements.txt @@ -0,0 +1,9 @@ +numpy>=1.16.4 +toolz==0.9.0 +pandas==0.24.2 +ignite==1.1.0 +scikit_learn==0.21.3 +tensorboardX==1.8 +torch==1.2.0 +torchvision==0.4.0 +tqdm>=4.33.0 diff --git a/cv_lib/setup.py b/cv_lib/setup.py new file mode 100644 index 00000000..9f170040 --- /dev/null +++ b/cv_lib/setup.py @@ -0,0 +1,50 @@ +import os +try: + from setuptools import setup, find_packages +except ImportError: + from distutils.core import setup, find_packages + + +# Package meta-data. +NAME = 'cv_lib' +DESCRIPTION = 'A library for computer vision' +URL = '' +EMAIL = 'msalvaris@users.noreply.github.com' +AUTHOR = 'AUTHORS.md' +LICENSE = '' +LONG_DESCRIPTION = DESCRIPTION + + +with open('requirements.txt') as f: + requirements = f.read().splitlines() + + +here = os.path.abspath(os.path.dirname(__file__)) + +# Load the package's __version__.py module as a dictionary. +about = {} +with open(os.path.join(here, NAME, '__version__.py')) as f: + exec(f.read(), about) + + +setup( + name=NAME, + version=about['__version__'], + url=URL, + license=LICENSE, + author=AUTHOR, + author_email=EMAIL, + description=DESCRIPTION, + long_description=LONG_DESCRIPTION, + scripts=[], + packages=find_packages(), + include_package_data=True, + install_requires=requirements, + classifiers=[ + 'Development Status :: 1 - Alpha', + 'Intended Audience :: Data Scientists & Developers', + 'Operating System :: POSIX', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 3.6', + ] +) diff --git a/interpretation/requirements.txt b/interpretation/requirements.txt index 61ee6929..8f086ef6 100644 --- a/interpretation/requirements.txt +++ b/interpretation/requirements.txt @@ -1 +1 @@ -numpy==1.17.0 \ No newline at end of file +numpy>=1.17.0 \ No newline at end of file From cd69324c5b0f7875a8ee758c45c834000e639846 Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Thu, 19 Sep 2019 13:11:13 +0000 Subject: [PATCH 048/207] Merged PR 236: Cleaned up dutchf3 data loaders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` --- .../dutchf3/data.py | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index fa783609..c461ac17 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -49,14 +49,14 @@ def __init__( self.is_transform = is_transform self.augmentations = augmentations self.n_classes = 6 - self.sections = collections.defaultdict(list) + self.sections = list() def __len__(self): - return len(self.sections[self.split]) + return len(self.sections) def __getitem__(self, index): - section_name = self.sections[self.split][index] + section_name = self.sections[index] direction, number = section_name.split(sep="_") if direction == "i": @@ -98,21 +98,21 @@ def __init__( self.seismic = np.load(_train_data_for(self.data_dir)) self.labels = np.load(_train_labels_for(self.data_dir)) - for split in ["train", "val", "train_val"]: - # reading the file names for 'train', 'val', 'trainval'"" - txt_path = path.join( - self.data_dir, "splits", "section_" + split + ".txt" - ) - file_list = tuple(open(txt_path, "r")) - file_list = [id_.rstrip() for id_ in file_list] - self.sections[split] = file_list + + # reading the file names for split + txt_path = path.join( + self.data_dir, "splits", "section_" + split + ".txt" + ) + file_list = tuple(open(txt_path, "r")) + file_list = [id_.rstrip() for id_ in file_list] + self.sections = file_list class TrainSectionLoaderWithDepth(TrainSectionLoader): def __init__( self, data_dir, split="train", is_transform=True, augmentations=None ): - super(TrainSectionLoader, self).__init__( + super(TrainSectionLoaderWithDepth, self).__init__( data_dir, split=split, is_transform=is_transform, @@ -122,7 +122,7 @@ def __init__( def __getitem__(self, index): - section_name = self.sections[self.split][index] + section_name = self.sections[index] direction, number = section_name.split(sep="_") if direction == "i": @@ -173,7 +173,7 @@ def __init__( ) file_list = tuple(open(txt_path, "r")) file_list = [id_.rstrip() for id_ in file_list] - self.sections[split] = file_list + self.sections = file_list class TestSectionLoaderWithDepth(TestSectionLoader): @@ -186,12 +186,11 @@ def __init__( is_transform=is_transform, augmentations=augmentations, ) - self.seismic = add_section_depth_channels(self.seismic) # NCWH def __getitem__(self, index): - section_name = self.sections[self.split][index] + section_name = self.sections[index] direction, number = section_name.split(sep="_") if direction == "i": @@ -239,7 +238,7 @@ def __init__( self.is_transform = is_transform self.augmentations = augmentations self.n_classes = 6 - self.patches = collections.defaultdict(list) + self.patches = list() self.patch_size = patch_size self.stride = stride @@ -255,11 +254,11 @@ def pad_volume(self, volume): ) def __len__(self): - return len(self.patches[self.split]) + return len(self.patches) def __getitem__(self, index): - patch_name = self.patches[self.split][index] + patch_name = self.patches[index] direction, idx, xdx, ddx = patch_name.split(sep="_") # Shift offsets the padding that is added in training @@ -317,6 +316,8 @@ def __init__( is_transform=is_transform, augmentations=augmentations, ) + ## Warning: this is not used or tested + raise NotImplementedError('This class is not correctly implemented.') self.seismic = np.load(_train_data_for(self.data_dir)) self.labels = np.load(_train_labels_for(self.data_dir)) @@ -327,7 +328,8 @@ def __init__( self.data_dir, "splits", "patch_" + self.split + ".txt" ) patch_list = tuple(open(txt_path, "r")) - self.patches[split] = patch_list + patch_list = [id_.rstrip() for id_ in patch_list] + self.patches = patch_list class TrainPatchLoader(PatchLoader): @@ -355,13 +357,13 @@ def __init__( # We are in train/val mode. Most likely the test splits are not saved yet, # so don't attempt to load them. self.split = split - for split in ["train", "val", "train_val"]: - # reading the file names for 'train', 'val', 'trainval'"" - txt_path = path.join( + # reading the file names for split + txt_path = path.join( self.data_dir, "splits", "patch_" + split + ".txt" ) - patch_list = tuple(open(txt_path, "r")) - self.patches[split] = patch_list + patch_list = tuple(open(txt_path, "r")) + patch_list = [id_.rstrip() for id_ in patch_list] + self.patches = patch_list class TrainPatchLoaderWithDepth(TrainPatchLoader): @@ -381,22 +383,10 @@ def __init__( is_transform=is_transform, augmentations=augmentations, ) - self.seismic = np.load(_train_data_for(self.data_dir)) - self.labels = np.load(_train_labels_for(self.data_dir)) - # We are in train/val mode. Most likely the test splits are not saved yet, - # so don't attempt to load them. - self.split = split - for split in ["train", "val", "train_val"]: - # reading the file names for 'train', 'val', 'trainval'"" - txt_path = path.join( - self.data_dir, "splits", "patch_" + split + ".txt" - ) - patch_list = tuple(open(txt_path, "r")) - self.patches[split] = patch_list def __getitem__(self, index): - patch_name = self.patches[self.split][index] + patch_name = self.patches[index] direction, idx, xdx, ddx = patch_name.split(sep="_") # Shift offsets the padding that is added in training @@ -464,7 +454,7 @@ def __init__( def __getitem__(self, index): - patch_name = self.patches[self.split][index] + patch_name = self.patches[index] direction, idx, xdx, ddx = patch_name.split(sep="_") # Shift offsets the padding that is added in training From 70674e1997640d572297b182eac498a18591c1d6 Mon Sep 17 00:00:00 2001 From: masalvar Date: Thu, 19 Sep 2019 20:08:59 +0000 Subject: [PATCH 049/207] Updates the repo with preliminary results for 2D segmentation --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 9bf45e3b..a5a641b5 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,18 @@ Both repos are installed in developer mode with the -e flag. This means that to This section contains benchmarks of different algorithms for seismic interpretation on 3D seismic datasets with densely-annotated data. +Below are the results from the models contained in this repo. To run them check the instructions in folder. Alternatively take a look in for how to run them on your own dataset + +| Authorship | Experiment | PA | FW IoU | MCA | +|------------------|-----------------------------------|-------------|--------------|------------| +| Alaudah | Section-based | 0.905 | 0.817 | .832 | +| | Patch-based | 0.852 | 0.743 | .689 | +| Ours | Patch-based+fixed | .869 | .761 | .775 | +| | SEResNet UNet+section depth | .917 | .849 | .834 | +| | HRNet(patch)+patch_depth | .908 | .843 | .837 | +| | HRNet(patch)+section_depth | .928 | .871 | .871 | + + ### Sparse Labels This section contains benchmarks of different algorithms for seismic interpretation on 3D seismic datasets with sparsely-annotated data and is organized by the levels of sparsity. From 8518bc014df9986795a16b7a37b9aabcb63f09b1 Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Fri, 20 Sep 2019 14:46:48 +0000 Subject: [PATCH 050/207] Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. --- .../local/configs/section_deconvnet_skip.yaml | 45 +++ .../dutchf3_section/local/default.py | 91 +++++ .../dutchf3_section/local/test.py | 244 +++++++++++++ .../dutchf3_section/local/train.py | 342 ++++++++++++++++++ 4 files changed, 722 insertions(+) create mode 100644 experiments/segmentation/dutchf3_section/local/configs/section_deconvnet_skip.yaml create mode 100644 experiments/segmentation/dutchf3_section/local/default.py create mode 100644 experiments/segmentation/dutchf3_section/local/test.py create mode 100644 experiments/segmentation/dutchf3_section/local/train.py diff --git a/experiments/segmentation/dutchf3_section/local/configs/section_deconvnet_skip.yaml b/experiments/segmentation/dutchf3_section/local/configs/section_deconvnet_skip.yaml new file mode 100644 index 00000000..9018a5e9 --- /dev/null +++ b/experiments/segmentation/dutchf3_section/local/configs/section_deconvnet_skip.yaml @@ -0,0 +1,45 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /data/home/vapaunic/repos/DeepSeismic/logging.conf +SEED: 2019 + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/dutchf3 + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: section_deconvnet_skip + IN_CHANNELS: 1 + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "none" # Can be None, Patch and Section + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True \ No newline at end of file diff --git a/experiments/segmentation/dutchf3_section/local/default.py b/experiments/segmentation/dutchf3_section/local/default.py new file mode 100644 index 00000000..94a8cd7f --- /dev/null +++ b/experiments/segmentation/dutchf3_section/local/default.py @@ -0,0 +1,91 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from yacs.config import CfgNode as CN + +_C = CN() + +_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models +_C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR +_C.GPUS = (0,) +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +_C.LOG_CONFIG = "/data/home/vapaunic/repos/DeepSeismic/logging.conf" +_C.SEED = 42 + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "/mnt/dutchf3" +_C.DATASET.NUM_CLASSES = 6 +_C.DATASET.CLASS_WEIGHTS = [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "section_deconvnet_skip" +_C.MODEL.IN_CHANNELS = 1 +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + +# training +_C.TRAIN = CN() +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 100 +_C.TRAIN.BATCH_SIZE_PER_GPU = 16 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR +_C.TRAIN.AUGMENTATION = True +_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 +_C.TRAIN.STD = 0.20977 # 0.20976548783479299 +_C.TRAIN.DEPTH = 'none' # Options are 'none', 'patch' and 'section' +# None adds no depth information and the num of channels remains at 1 +# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 +# Section adds depth per section so contains depth information for the whole section, channels=3 + +# validation +_C.VALIDATION = CN() +_C.VALIDATION.BATCH_SIZE_PER_GPU = 16 + +# TEST +_C.TEST = CN() +_C.TEST.MODEL_PATH = "" +_C.TEST.TEST_STRIDE = 10 +_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 +_C.TEST.INLINE = True +_C.TEST.CROSSLINE = True + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/experiments/segmentation/dutchf3_section/local/test.py b/experiments/segmentation/dutchf3_section/local/test.py new file mode 100644 index 00000000..40374182 --- /dev/null +++ b/experiments/segmentation/dutchf3_section/local/test.py @@ -0,0 +1,244 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 +# url: https://github.com/olivesgatech/facies_classification_benchmark + +""" +Modified version of the Alaudah testing script +#TODO: Needs to be improved. Needs to be able to run across multiple GPUs and better factoring around the loader +""" + +import itertools +import logging +import logging.config +import os +from os import path + +import cv2 +import fire +import numpy as np +import torch +import torch.nn.functional as F +from albumentations import Compose, Normalize +from cv_lib.segmentation import models +from deepseismic_interpretation.dutchf3.data import ( + add_patch_depth_channels, + get_seismic_labels, + get_test_loader, +) +from default import _C as config +from default import update_config +from toolz import compose, curry, itertoolz, pipe +from torch.utils import data + +_CLASS_NAMES = [ + "upper_ns", + "middle_ns", + "lower_ns", + "rijnland_chalk", + "scruff", + "zechstein", +] + +DATA_ROOT = config.DATASET.ROOT + + +class runningScore(object): + def __init__(self, n_classes): + self.n_classes = n_classes + self.confusion_matrix = np.zeros((n_classes, n_classes)) + + def _fast_hist(self, label_true, label_pred, n_class): + mask = (label_true >= 0) & (label_true < n_class) + hist = np.bincount( + n_class * label_true[mask].astype(int) + label_pred[mask], + minlength=n_class ** 2, + ).reshape(n_class, n_class) + return hist + + def update(self, label_trues, label_preds): + for lt, lp in zip(label_trues, label_preds): + self.confusion_matrix += self._fast_hist( + lt.flatten(), lp.flatten(), self.n_classes + ) + + def get_scores(self): + """Returns accuracy score evaluation result. + - overall accuracy + - mean accuracy + - mean IU + - fwavacc + """ + hist = self.confusion_matrix + acc = np.diag(hist).sum() / hist.sum() + acc_cls = np.diag(hist) / hist.sum(axis=1) + mean_acc_cls = np.nanmean(acc_cls) + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + mean_iu = np.nanmean(iu) + freq = ( + hist.sum(axis=1) / hist.sum() + ) # fraction of the pixels that come from each class + fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() + cls_iu = dict(zip(range(self.n_classes), iu)) + + return ( + { + "Pixel Acc: ": acc, + "Class Accuracy: ": acc_cls, + "Mean Class Acc: ": mean_acc_cls, + "Freq Weighted IoU: ": fwavacc, + "Mean IoU: ": mean_iu, + "confusion_matrix": self.confusion_matrix, + }, + cls_iu, + ) + + def reset(self): + self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) + + +def _evaluate_split( + split, + section_aug, + model, + device, + running_metrics_overall, + config, +): + logger = logging.getLogger(__name__) + + TestSectionLoader = get_test_loader(config) + test_set = TestSectionLoader( + data_dir=DATA_ROOT, + split=split, + is_transform=True, + augmentations=section_aug, + ) + + n_classes = test_set.n_classes + + test_loader = data.DataLoader( + test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False + ) + running_metrics_split = runningScore(n_classes) + + # testing mode: + with torch.no_grad(): # operations inside don't track history + model.eval() + total_iteration = 0 + for i, (images, labels) in enumerate(test_loader): + logger.info(f"split: {split}, section: {i}") + total_iteration = total_iteration + 1 + + outputs = model(images.to(device)) + + pred = outputs.detach().max(1)[1].cpu().numpy() + gt = labels.numpy() + running_metrics_split.update(gt, pred) + running_metrics_overall.update(gt, pred) + + # get scores + score, class_iou = running_metrics_split.get_scores() + + # Log split results + logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') + for cdx, class_name in enumerate(_CLASS_NAMES): + logger.info( + f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' + ) + + logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') + logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') + logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') + running_metrics_split.reset() + + +def _write_section_file(labels, section_file): + # define indices of the array + irange, xrange, depth = labels.shape + + if config.TEST.INLINE: + i_list = list(range(irange)) + i_list = ["i_" + str(inline) for inline in i_list] + else: + i_list = [] + + if config.TEST.CROSSLINE: + x_list = list(range(xrange)) + x_list = ["x_" + str(crossline) for crossline in x_list] + else: + x_list = [] + + list_test = i_list + x_list + + file_object = open(section_file, "w") + file_object.write("\n".join(list_test)) + file_object.close() + + +def test(*options, cfg=None): + update_config(config, options=options, config_file=cfg) + n_classes = config.DATASET.NUM_CLASSES + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + log_dir, _ = os.path.split(config.TEST.MODEL_PATH) + + # load model: + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False) + model = model.to(device) # Send to GPU if available + + running_metrics_overall = runningScore(n_classes) + + # Augmentation + section_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ) + ] + ) + + splits = ( + ["test1", "test2"] + if "Both" in config.TEST.SPLIT + else [config.TEST.SPLIT] + ) + + for sdx, split in enumerate(splits): + labels = np.load( + path.join(DATA_ROOT, "test_once", split + "_labels.npy") + ) + section_file = path.join( + DATA_ROOT, "splits", "section_" + split + ".txt" + ) + _write_section_file(labels, section_file) + _evaluate_split( + split, section_aug, model, device, running_metrics_overall, config + ) + + # FINAL TEST RESULTS: + score, class_iou = running_metrics_overall.get_scores() + + logger.info("--------------- FINAL RESULTS -----------------") + logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') + for cdx, class_name in enumerate(_CLASS_NAMES): + logger.info( + f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' + ) + logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') + logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') + logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') + + # Save confusion matrix: + confusion = score["confusion_matrix"] + np.savetxt(path.join(log_dir, "confusion.csv"), confusion, delimiter=" ") + + +if __name__ == "__main__": + fire.Fire(test) diff --git a/experiments/segmentation/dutchf3_section/local/train.py b/experiments/segmentation/dutchf3_section/local/train.py new file mode 100644 index 00000000..5c11e650 --- /dev/null +++ b/experiments/segmentation/dutchf3_section/local/train.py @@ -0,0 +1,342 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# # Licensed under the MIT License. +# # /* spell-checker: disable */ + +import logging +import logging.config +from os import path + +# import cv2 +import fire +import numpy as np +import torch +from albumentations import Compose, HorizontalFlip, Normalize +from deepseismic_interpretation.dutchf3.data import ( + decode_segmap, + get_section_loader, +) +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) +from cv_lib.segmentation.dutchf3.engine import ( + create_supervised_evaluator, + create_supervised_trainer, +) +from cv_lib.segmentation.dutchf3.metrics import ( + FrequencyWeightedIoU, + MeanClassAccuracy, + MeanIoU, + PixelwiseAccuracy, +) +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) +from default import _C as config +from default import update_config +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from toolz import compose +from torch.utils import data + + +def prepare_batch(batch, device=None, non_blocking=False): + x, y = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ) + + +def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + logger.debug(config.WORKERS) + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + + torch.manual_seed(config.SEED) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(config.SEED) + np.random.seed(seed=config.SEED) + + # Setup Augmentations + basic_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ) + ] + ) + if config.TRAIN.AUGMENTATION: + train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)]) + val_aug = basic_aug + else: + train_aug = val_aug = basic_aug + + TrainLoader = get_section_loader(config) + + train_set = TrainLoader( + data_dir=config.DATASET.ROOT, + split="train", + is_transform=True, + augmentations=train_aug, + ) + + val_set = TrainLoader( + data_dir=config.DATASET.ROOT, + split="val", + is_transform=True, + augmentations=val_aug, + ) + + class CustomSampler(torch.utils.data.Sampler): + def __init__(self, data_source): + self.data_source = data_source + + def __iter__(self): + char = ["i" if np.random.randint(2) == 1 else "x"] + self.indices = [ + idx + for (idx, name) in enumerate(self.data_source) + if char[0] in name + ] + return (self.indices[i] for i in torch.randperm(len(self.indices))) + + def __len__(self): + return len(self.data_source) + + n_classes = train_set.n_classes + + val_list = val_set.sections + train_list = val_set.sections + + train_loader = data.DataLoader( + train_set, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + sampler=CustomSampler(train_list), + num_workers=config.WORKERS, + shuffle=False, + ) + + val_loader = data.DataLoader( + val_set, + batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, + sampler=CustomSampler(val_list), + num_workers=config.WORKERS, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model = model.to(device) # Send to GPU + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) + + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + + snapshot_duration = scheduler_step * len(train_loader) + scheduler = CosineAnnealingScheduler( + optimizer, + "lr", + config.TRAIN.MAX_LR, + config.TRAIN.MIN_LR, + snapshot_duration, + ) + + # weights are inversely proportional to the frequency of the classes in the training set + class_weights = torch.tensor( + config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False + ) + + criterion = torch.nn.CrossEntropyLoss( + weight=class_weights, ignore_index=255, reduction="mean" + ) + + trainer = create_supervised_trainer( + model, optimizer, criterion, prepare_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) + + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + def _select_pred_and_mask(model_out_dict): + return ( + model_out_dict["y_pred"].squeeze(), + model_out_dict["mask"].squeeze(), + ) + + evaluator = create_supervised_evaluator( + model, + prepare_batch, + metrics={ + "IoU": MeanIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "nll": Loss(criterion, output_transform=_select_pred_and_mask), + "mca": MeanClassAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + "fiou": FrequencyWeightedIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "pixa": PixelwiseAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + }, + device=device, + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler( + Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={ + "IoU": "IoU :", + "nll": "Avg loss :", + "pixa": "Pixelwise Accuracy :", + "mca": "Mean Class Accuracy :", + "fiou": "Freq Weighted IoU :", + }, + ), + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={ + "IoU": "Validation/IoU", + "nll": "Validation/Loss", + "mca": "Validation/MCA", + "fiou": "Validation/FIoU", + }, + ), + ) + + def _select_max(pred_tensor): + return pred_tensor.max(1)[1] + + def _tensor_to_numpy(pred_tensor): + return pred_tensor.squeeze().cpu().numpy() + + transform_func = compose( + np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy + ) + + transform_pred = compose(transform_func, _select_max) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Mask", + "mask", + transform_func=transform_func, + ), + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Pred", + "y_pred", + transform_func=transform_pred, + ), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + path.join(output_dir, config.TRAIN.MODEL_DIR), + config.MODEL.NAME, + snapshot_function, + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger.info("Starting training") + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) From 1ff19cbb8359b46eddb548ed83e0c617b677addb Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Fri, 20 Sep 2019 20:08:22 +0000 Subject: [PATCH 051/207] Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 --- README.md | 8 +- environment/anaconda/local/environment.yml | 1 + .../dutchf3_patch}/README.md | 0 .../distributed/configs/hrnet.yaml | 0 .../distributed/configs/patch_deconvnet.yaml | 0 .../configs/patch_deconvnet_skip.yaml | 0 .../distributed/configs/seresnet_unet.yaml | 0 .../distributed/configs/unet.yaml | 0 .../dutchf3_patch}/distributed/default.py | 0 .../dutchf3_patch}/distributed/run.sh | 0 .../dutchf3_patch}/distributed/train.py | 0 .../dutchf3_patch}/distributed/train.sh | 0 .../dutchf3_patch}/horovod/configs/hrnet.yaml | 0 .../horovod/configs/patch_deconvnet.yaml | 0 .../horovod/configs/patch_deconvnet_skip.yaml | 0 .../horovod/configs/seresnet_unet.yaml | 0 .../dutchf3_patch}/horovod/configs/unet.yaml | 0 .../dutchf3_patch}/horovod/default.py | 0 .../dutchf3_patch}/horovod/train.py | 0 .../dutchf3_patch}/horovod/train.sh | 0 .../dutchf3_patch}/local/configs/hrnet.yaml | 2 +- .../local/configs/patch_deconvnet.yaml | 0 .../local/configs/patch_deconvnet_skip.yaml | 0 .../local/configs/seresnet_unet.yaml | 0 .../local/configs/texture_net.yaml | 64 +++ .../dutchf3_patch}/local/configs/unet.yaml | 4 +- .../dutchf3_patch}/local/default.py | 3 +- .../dutchf3_patch}/local/test.py | 0 .../dutchf3_patch}/local/test.sh | 0 .../dutchf3_patch}/local/train.py | 0 .../dutchf3_patch}/local/train.sh | 0 .../dutchf3_patch}/prepare_data.py | 0 .../local/configs/texture_net.yaml | 64 +++ .../dutchf3_voxel/local/default.py | 109 ++++ .../dutchf3_voxel/local/test.py | 481 ++++++++++++++++++ .../dutchf3_voxel/local/train.py | 302 +++++++++++ .../tgs_salt/apex/configs/hrnet.yaml | 0 .../tgs_salt/apex/configs/unet.yaml | 0 .../tgs_salt/apex/default.py | 0 .../tgs_salt/apex/run.sh | 0 .../tgs_salt/apex/train.py | 0 .../tgs_salt/distributed/configs/hrnet.yaml | 0 .../tgs_salt/distributed/configs/unet.yaml | 0 .../tgs_salt/distributed/default.py | 0 .../tgs_salt/distributed/run.sh | 0 .../tgs_salt/distributed/train.py | 0 .../tgs_salt/horovod/configs/hrnet.yaml | 0 .../tgs_salt/horovod/configs/unet.yaml | 0 .../tgs_salt/horovod/default.py | 0 .../tgs_salt/horovod/run.sh | 0 .../tgs_salt/horovod/train.py | 0 .../tgs_salt/local/configs/hrnet.yaml | 0 .../tgs_salt/local/configs/unet.yaml | 0 .../tgs_salt/local/default.py | 0 .../tgs_salt/local/run.sh | 0 .../tgs_salt/local/train.py | 0 .../dutchf3/data.py | 116 ++++- interpretation/models/__init__.py | 0 interpretation/models/texture_net.py | 161 ++++++ 59 files changed, 1308 insertions(+), 7 deletions(-) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/README.md (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/distributed/configs/hrnet.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/distributed/configs/patch_deconvnet.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/distributed/configs/patch_deconvnet_skip.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/distributed/configs/seresnet_unet.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/distributed/configs/unet.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/distributed/default.py (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/distributed/run.sh (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/distributed/train.py (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/distributed/train.sh (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/horovod/configs/hrnet.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/horovod/configs/patch_deconvnet.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/horovod/configs/patch_deconvnet_skip.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/horovod/configs/seresnet_unet.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/horovod/configs/unet.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/horovod/default.py (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/horovod/train.py (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/horovod/train.sh (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/configs/hrnet.yaml (97%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/configs/patch_deconvnet.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/configs/patch_deconvnet_skip.yaml (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/configs/seresnet_unet.yaml (100%) create mode 100644 experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/configs/unet.yaml (92%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/default.py (95%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/test.py (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/test.sh (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/train.py (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/local/train.sh (100%) rename experiments/{segmentation/dutchf3 => interpretation/dutchf3_patch}/prepare_data.py (100%) create mode 100644 experiments/interpretation/dutchf3_voxel/local/configs/texture_net.yaml create mode 100644 experiments/interpretation/dutchf3_voxel/local/default.py create mode 100644 experiments/interpretation/dutchf3_voxel/local/test.py create mode 100644 experiments/interpretation/dutchf3_voxel/local/train.py rename experiments/{segmentation => interpretation}/tgs_salt/apex/configs/hrnet.yaml (100%) rename experiments/{segmentation => interpretation}/tgs_salt/apex/configs/unet.yaml (100%) rename experiments/{segmentation => interpretation}/tgs_salt/apex/default.py (100%) rename experiments/{segmentation => interpretation}/tgs_salt/apex/run.sh (100%) rename experiments/{segmentation => interpretation}/tgs_salt/apex/train.py (100%) rename experiments/{segmentation => interpretation}/tgs_salt/distributed/configs/hrnet.yaml (100%) rename experiments/{segmentation => interpretation}/tgs_salt/distributed/configs/unet.yaml (100%) rename experiments/{segmentation => interpretation}/tgs_salt/distributed/default.py (100%) rename experiments/{segmentation => interpretation}/tgs_salt/distributed/run.sh (100%) rename experiments/{segmentation => interpretation}/tgs_salt/distributed/train.py (100%) rename experiments/{segmentation => interpretation}/tgs_salt/horovod/configs/hrnet.yaml (100%) rename experiments/{segmentation => interpretation}/tgs_salt/horovod/configs/unet.yaml (100%) rename experiments/{segmentation => interpretation}/tgs_salt/horovod/default.py (100%) rename experiments/{segmentation => interpretation}/tgs_salt/horovod/run.sh (100%) rename experiments/{segmentation => interpretation}/tgs_salt/horovod/train.py (100%) rename experiments/{segmentation => interpretation}/tgs_salt/local/configs/hrnet.yaml (100%) rename experiments/{segmentation => interpretation}/tgs_salt/local/configs/unet.yaml (100%) rename experiments/{segmentation => interpretation}/tgs_salt/local/default.py (100%) rename experiments/{segmentation => interpretation}/tgs_salt/local/run.sh (100%) rename experiments/{segmentation => interpretation}/tgs_salt/local/train.py (100%) create mode 100644 interpretation/models/__init__.py create mode 100644 interpretation/models/texture_net.py diff --git a/README.md b/README.md index a5a641b5..7e75d5c1 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,13 @@ Then you will also need to install cv_lib pip install -e cv_lib ``` -Both repos are installed in developer mode with the -e flag. This means that to update simply go to the folder and pull the appropriate commit or branch +Both repos are installed in developer mode with the -e flag. This means that to update simply go to the folder and pull the appropriate commit or branch. + +During development, in case you need to update the environment due to a conda env file change, you can run +``` +conda env update --file environment/anaconda/local/environment.yml +``` +from the root of DeepSeismic repo. ## Benchmarks diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index d1723cba..f108de52 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -28,3 +28,4 @@ dependencies: - albumentations - black - pylint + diff --git a/experiments/segmentation/dutchf3/README.md b/experiments/interpretation/dutchf3_patch/README.md similarity index 100% rename from experiments/segmentation/dutchf3/README.md rename to experiments/interpretation/dutchf3_patch/README.md diff --git a/experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/distributed/configs/hrnet.yaml rename to experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml diff --git a/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet.yaml rename to experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml diff --git a/experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml similarity index 100% rename from experiments/segmentation/dutchf3/distributed/configs/patch_deconvnet_skip.yaml rename to experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml diff --git a/experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/distributed/configs/seresnet_unet.yaml rename to experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml diff --git a/experiments/segmentation/dutchf3/distributed/configs/unet.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/distributed/configs/unet.yaml rename to experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml diff --git a/experiments/segmentation/dutchf3/distributed/default.py b/experiments/interpretation/dutchf3_patch/distributed/default.py similarity index 100% rename from experiments/segmentation/dutchf3/distributed/default.py rename to experiments/interpretation/dutchf3_patch/distributed/default.py diff --git a/experiments/segmentation/dutchf3/distributed/run.sh b/experiments/interpretation/dutchf3_patch/distributed/run.sh similarity index 100% rename from experiments/segmentation/dutchf3/distributed/run.sh rename to experiments/interpretation/dutchf3_patch/distributed/run.sh diff --git a/experiments/segmentation/dutchf3/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py similarity index 100% rename from experiments/segmentation/dutchf3/distributed/train.py rename to experiments/interpretation/dutchf3_patch/distributed/train.py diff --git a/experiments/segmentation/dutchf3/distributed/train.sh b/experiments/interpretation/dutchf3_patch/distributed/train.sh similarity index 100% rename from experiments/segmentation/dutchf3/distributed/train.sh rename to experiments/interpretation/dutchf3_patch/distributed/train.sh diff --git a/experiments/segmentation/dutchf3/horovod/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/hrnet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/horovod/configs/hrnet.yaml rename to experiments/interpretation/dutchf3_patch/horovod/configs/hrnet.yaml diff --git a/experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet.yaml rename to experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet.yaml diff --git a/experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet_skip.yaml similarity index 100% rename from experiments/segmentation/dutchf3/horovod/configs/patch_deconvnet_skip.yaml rename to experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet_skip.yaml diff --git a/experiments/segmentation/dutchf3/horovod/configs/seresnet_unet.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/seresnet_unet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/horovod/configs/seresnet_unet.yaml rename to experiments/interpretation/dutchf3_patch/horovod/configs/seresnet_unet.yaml diff --git a/experiments/segmentation/dutchf3/horovod/configs/unet.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/unet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/horovod/configs/unet.yaml rename to experiments/interpretation/dutchf3_patch/horovod/configs/unet.yaml diff --git a/experiments/segmentation/dutchf3/horovod/default.py b/experiments/interpretation/dutchf3_patch/horovod/default.py similarity index 100% rename from experiments/segmentation/dutchf3/horovod/default.py rename to experiments/interpretation/dutchf3_patch/horovod/default.py diff --git a/experiments/segmentation/dutchf3/horovod/train.py b/experiments/interpretation/dutchf3_patch/horovod/train.py similarity index 100% rename from experiments/segmentation/dutchf3/horovod/train.py rename to experiments/interpretation/dutchf3_patch/horovod/train.py diff --git a/experiments/segmentation/dutchf3/horovod/train.sh b/experiments/interpretation/dutchf3_patch/horovod/train.sh similarity index 100% rename from experiments/segmentation/dutchf3/horovod/train.sh rename to experiments/interpretation/dutchf3_patch/horovod/train.sh diff --git a/experiments/segmentation/dutchf3/local/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml similarity index 97% rename from experiments/segmentation/dutchf3/local/configs/hrnet.yaml rename to experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml index 01f37893..b34eae58 100644 --- a/experiments/segmentation/dutchf3/local/configs/hrnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf SEED: 2019 diff --git a/experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/local/configs/patch_deconvnet.yaml rename to experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml diff --git a/experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml similarity index 100% rename from experiments/segmentation/dutchf3/local/configs/patch_deconvnet_skip.yaml rename to experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml diff --git a/experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml similarity index 100% rename from experiments/segmentation/dutchf3/local/configs/seresnet_unet.yaml rename to experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml diff --git a/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml b/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml new file mode 100644 index 00000000..bf370c9e --- /dev/null +++ b/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml @@ -0,0 +1,64 @@ +# UNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf +SEED: 2019 +WINDOW_SIZE: 65 + + +DATASET: + NUM_CLASSES: 2 + ROOT: /mnt/dutchf3 + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: texture_net + IN_CHANNELS: 1 + + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "voxel" # Options are No, Patch, Section and Voxel + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right + diff --git a/experiments/segmentation/dutchf3/local/configs/unet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml similarity index 92% rename from experiments/segmentation/dutchf3/local/configs/unet.yaml rename to experiments/interpretation/dutchf3_patch/local/configs/unet.yaml index fd614866..e3c2e312 100644 --- a/experiments/segmentation/dutchf3/local/configs/unet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml @@ -9,13 +9,13 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT:/mnt/dutchf3 + ROOT: '/mnt/dutchf3' CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/experiments/segmentation/dutchf3/local/default.py b/experiments/interpretation/dutchf3_patch/local/default.py similarity index 95% rename from experiments/segmentation/dutchf3/local/default.py rename to experiments/interpretation/dutchf3_patch/local/default.py index e0fc893e..cf5c87f8 100644 --- a/experiments/segmentation/dutchf3/local/default.py +++ b/experiments/interpretation/dutchf3_patch/local/default.py @@ -18,7 +18,8 @@ _C.PRINT_FREQ = 20 _C.AUTO_RESUME = False _C.PIN_MEMORY = True -_C.LOG_CONFIG = "/data/home/mat/repos/DeepSeismic/logging.conf" +# TODO: this should be loaded by automatically figuring out the file path location +_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" _C.SEED = 42 # Cudnn related params diff --git a/experiments/segmentation/dutchf3/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py similarity index 100% rename from experiments/segmentation/dutchf3/local/test.py rename to experiments/interpretation/dutchf3_patch/local/test.py diff --git a/experiments/segmentation/dutchf3/local/test.sh b/experiments/interpretation/dutchf3_patch/local/test.sh similarity index 100% rename from experiments/segmentation/dutchf3/local/test.sh rename to experiments/interpretation/dutchf3_patch/local/test.sh diff --git a/experiments/segmentation/dutchf3/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py similarity index 100% rename from experiments/segmentation/dutchf3/local/train.py rename to experiments/interpretation/dutchf3_patch/local/train.py diff --git a/experiments/segmentation/dutchf3/local/train.sh b/experiments/interpretation/dutchf3_patch/local/train.sh similarity index 100% rename from experiments/segmentation/dutchf3/local/train.sh rename to experiments/interpretation/dutchf3_patch/local/train.sh diff --git a/experiments/segmentation/dutchf3/prepare_data.py b/experiments/interpretation/dutchf3_patch/prepare_data.py similarity index 100% rename from experiments/segmentation/dutchf3/prepare_data.py rename to experiments/interpretation/dutchf3_patch/prepare_data.py diff --git a/experiments/interpretation/dutchf3_voxel/local/configs/texture_net.yaml b/experiments/interpretation/dutchf3_voxel/local/configs/texture_net.yaml new file mode 100644 index 00000000..f95b18ce --- /dev/null +++ b/experiments/interpretation/dutchf3_voxel/local/configs/texture_net.yaml @@ -0,0 +1,64 @@ +# TextureNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf +SEED: 2019 +WINDOW_SIZE: 65 + + +DATASET: + NUM_CLASSES: 2 + ROOT: /mnt/dutchf3 + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: texture_net + IN_CHANNELS: 1 + + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "voxel" # Options are No, Patch, Section and Voxel + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right + diff --git a/experiments/interpretation/dutchf3_voxel/local/default.py b/experiments/interpretation/dutchf3_voxel/local/default.py new file mode 100644 index 00000000..e9bdf617 --- /dev/null +++ b/experiments/interpretation/dutchf3_voxel/local/default.py @@ -0,0 +1,109 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from yacs.config import CfgNode as CN + +_C = CN() + +_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models +_C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR +_C.GPUS = (0,) +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +# TODO: this should be loaded by automatically figuring out the file path location +_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" +_C.SEED = 42 + +# size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only +_C.WINDOW_SIZE = 65 + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "" +_C.DATASET.NUM_CLASSES = 6 +_C.DATASET.CLASS_WEIGHTS = [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "patch_deconvnet" +_C.MODEL.IN_CHANNELS = 1 +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + + +# training +_C.TRAIN = CN() +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR +_C.TRAIN.AUGMENTATION = True +_C.TRAIN.STRIDE = 50 +_C.TRAIN.PATCH_SIZE = 99 +_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 +_C.TRAIN.STD = 0.20977 # 0.20976548783479299 # TODO: Should we apply std scaling? +_C.TRAIN.DEPTH = 'no' # Options are None, Patch and Section +# None adds no depth information and the num of channels remains at 1 +# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 +# Section adds depth per section so contains depth information for the whole section, channels=3 +_C.TRAIN.AUGMENTATIONS = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 +_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TRAIN.AUGMENTATIONS.PAD = CN() +_C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 +_C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 + +# validation +_C.VALIDATION = CN() +_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 + +# TEST +_C.TEST = CN() +_C.TEST.MODEL_PATH = "" +_C.TEST.TEST_STRIDE = 10 +_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 +_C.TEST.INLINE = True +_C.TEST.CROSSLINE = True +_C.TEST.POST_PROCESSING = CN() # Model output postprocessing +_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels +_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/experiments/interpretation/dutchf3_voxel/local/test.py b/experiments/interpretation/dutchf3_voxel/local/test.py new file mode 100644 index 00000000..a68c3618 --- /dev/null +++ b/experiments/interpretation/dutchf3_voxel/local/test.py @@ -0,0 +1,481 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 +# url: https://github.com/olivesgatech/facies_classification_benchmark + +""" +Modified version of the Alaudah testing script +#TODO: Needs to be improved. Needs to be able to run across multiple GPUs and better factoring around the loader +""" + +import itertools +import logging +import logging.config +import os +from os import path + +import cv2 +import fire +import numpy as np +import torch +import torch.nn.functional as F +from albumentations import Compose, Normalize, PadIfNeeded, Resize +from cv_lib.segmentation import models +from deepseismic_interpretation.dutchf3.data import ( + add_patch_depth_channels, + get_seismic_labels, + get_test_loader, +) +from default import _C as config +from default import update_config +from toolz import compose, curry, itertoolz, pipe +from torch.utils import data + +_CLASS_NAMES = [ + "upper_ns", + "middle_ns", + "lower_ns", + "rijnland_chalk", + "scruff", + "zechstein", +] + + +class runningScore(object): + def __init__(self, n_classes): + self.n_classes = n_classes + self.confusion_matrix = np.zeros((n_classes, n_classes)) + + def _fast_hist(self, label_true, label_pred, n_class): + mask = (label_true >= 0) & (label_true < n_class) + hist = np.bincount( + n_class * label_true[mask].astype(int) + label_pred[mask], + minlength=n_class ** 2, + ).reshape(n_class, n_class) + return hist + + def update(self, label_trues, label_preds): + for lt, lp in zip(label_trues, label_preds): + self.confusion_matrix += self._fast_hist( + lt.flatten(), lp.flatten(), self.n_classes + ) + + def get_scores(self): + """Returns accuracy score evaluation result. + - overall accuracy + - mean accuracy + - mean IU + - fwavacc + """ + hist = self.confusion_matrix + acc = np.diag(hist).sum() / hist.sum() + acc_cls = np.diag(hist) / hist.sum(axis=1) + mean_acc_cls = np.nanmean(acc_cls) + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + mean_iu = np.nanmean(iu) + freq = ( + hist.sum(axis=1) / hist.sum() + ) # fraction of the pixels that come from each class + fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() + cls_iu = dict(zip(range(self.n_classes), iu)) + + return ( + { + "Pixel Acc: ": acc, + "Class Accuracy: ": acc_cls, + "Mean Class Acc: ": mean_acc_cls, + "Freq Weighted IoU: ": fwavacc, + "Mean IoU: ": mean_iu, + "confusion_matrix": self.confusion_matrix, + }, + cls_iu, + ) + + def reset(self): + self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) + + +def _transform_CHW_to_HWC(numpy_array): + return np.moveaxis(numpy_array, 0, -1) + + +def _transform_HWC_to_CHW(numpy_array): + return np.moveaxis(numpy_array, -1, 0) + + +@curry +def _apply_augmentation3D(aug, numpy_array): + assert len(numpy_array.shape) == 3, "This method only accepts 3D arrays" + patch = _transform_CHW_to_HWC(numpy_array) + patch = aug(image=patch)["image"] + return _transform_HWC_to_CHW(patch) + + +@curry +def _apply_augmentation2D(aug, numpy_array): + assert len(numpy_array.shape) == 2, "This method only accepts 2D arrays" + return aug(image=numpy_array)["image"] + + +_AUGMENTATION = {3: _apply_augmentation3D, 2: _apply_augmentation2D} + + +@curry +def _apply_augmentation(aug, image): + if isinstance(image, torch.Tensor): + image = image.numpy() + + if aug is not None: + return _AUGMENTATION[len(image.shape)](aug, image) + else: + return image + + +def _add_depth(image): + if isinstance(image, torch.Tensor): + image = image.numpy() + return add_patch_depth_channels(image) + + +def _to_torch(image): + if isinstance(image, torch.Tensor): + return image + else: + return torch.from_numpy(image).to(torch.float32) + + +def _expand_dims_if_necessary(torch_tensor): + if len(torch_tensor.shape) == 2: + return torch_tensor.unsqueeze(dim=0) + else: + return torch_tensor + + +@curry +def _extract_patch(hdx, wdx, ps, patch_size, img_p): + if len(img_p.shape) == 2: # 2D + return img_p[ + hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size + ] + else: # 3D + return img_p[ + :, + hdx + ps : hdx + ps + patch_size, + wdx + ps : wdx + ps + patch_size, + ] + + +def _compose_processing_pipeline(depth, aug=None): + steps = [] + if aug is not None: + steps.append(_apply_augmentation(aug)) + + if depth == "patch": + steps.append(_add_depth) + + steps.append(_to_torch) + steps.append(_expand_dims_if_necessary) + steps.reverse() + return compose(*steps) + + +def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): + hdc_wdx_generator = itertools.product( + range(0, h - patch_size + ps, stride), + range(0, w - patch_size + ps, stride), + ) + + for batch_indexes in itertoolz.partition_all( + batch_size, hdc_wdx_generator + ): + yield batch_indexes + + +@curry +def _output_processing_pipeline(config, output): + output = output.unsqueeze(0) + _, _, h, w = output.shape + if ( + config.TEST.POST_PROCESSING.SIZE != h + or config.TEST.POST_PROCESSING.SIZE != w + ): + output = F.interpolate( + output, + size=( + config.TEST.POST_PROCESSING.SIZE, + config.TEST.POST_PROCESSING.SIZE, + ), + mode="bilinear", + ) + + if config.TEST.POST_PROCESSING.CROP_PIXELS > 0: + _, _, h, w = output.shape + output = output[ + :, + :, + config.TEST.POST_PROCESSING.CROP_PIXELS : h + - config.TEST.POST_PROCESSING.CROP_PIXELS, + config.TEST.POST_PROCESSING.CROP_PIXELS : w + - config.TEST.POST_PROCESSING.CROP_PIXELS, + ] + return output.squeeze() + + +def _patch_label_2d( + model, + img, + pre_processing, + output_processing, + patch_size, + stride, + batch_size, + device, + num_classes, +): + """Processes a whole section + """ + img = torch.squeeze(img) + h, w = img.shape[-2], img.shape[-1] # height and width + + # Pad image with patch_size/2: + ps = int(np.floor(patch_size / 2)) # pad size + img_p = F.pad(img, pad=(ps, ps, ps, ps), mode="constant", value=0) + output_p = torch.zeros([1, num_classes, h + 2 * ps, w + 2 * ps]) + + # generate output: + for batch_indexes in _generate_batches( + h, w, ps, patch_size, stride, batch_size=batch_size + ): + batch = torch.stack( + [ + pipe( + img_p, + _extract_patch(hdx, wdx, ps, patch_size), + pre_processing, + ) + for hdx, wdx in batch_indexes + ], + dim=0, + ) + + model_output = model(batch.to(device)) + for (hdx, wdx), output in zip( + batch_indexes, model_output.detach().cpu() + ): + output = output_processing(output) + output_p[ + :, + :, + hdx + ps : hdx + ps + patch_size, + wdx + ps : wdx + ps + patch_size, + ] += output + + # crop the output_p in the middle + output = output_p[:, :, ps:-ps, ps:-ps] + return output + + +@curry +def to_image(label_mask, n_classes=6): + label_colours = get_seismic_labels() + r = label_mask.copy() + g = label_mask.copy() + b = label_mask.copy() + for ll in range(0, n_classes): + r[label_mask == ll] = label_colours[ll, 0] + g[label_mask == ll] = label_colours[ll, 1] + b[label_mask == ll] = label_colours[ll, 2] + rgb = np.zeros( + (label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3) + ) + rgb[:, :, :, 0] = r + rgb[:, :, :, 1] = g + rgb[:, :, :, 2] = b + return rgb + + +def _evaluate_split( + split, + section_aug, + model, + pre_processing, + output_processing, + device, + running_metrics_overall, + config, +): + logger = logging.getLogger(__name__) + + TestSectionLoader = get_test_loader(config) + test_set = TestSectionLoader( + config.DATASET.ROOT, + split=split, + is_transform=True, + augmentations=section_aug, + ) + + n_classes = test_set.n_classes + + test_loader = data.DataLoader( + test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False + ) + running_metrics_split = runningScore(n_classes) + + # testing mode: + with torch.no_grad(): # operations inside don't track history + model.eval() + total_iteration = 0 + for i, (images, labels) in enumerate(test_loader): + logger.info(f"split: {split}, section: {i}") + total_iteration = total_iteration + 1 + + outputs = _patch_label_2d( + model, + images, + pre_processing, + output_processing, + config.TRAIN.PATCH_SIZE, + config.TEST.TEST_STRIDE, + config.VALIDATION.BATCH_SIZE_PER_GPU, + device, + n_classes, + ) + + pred = outputs.detach().max(1)[1].numpy() + gt = labels.numpy() + running_metrics_split.update(gt, pred) + running_metrics_overall.update(gt, pred) + + # get scores + score, class_iou = running_metrics_split.get_scores() + + # Log split results + logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') + for cdx, class_name in enumerate(_CLASS_NAMES): + logger.info( + f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' + ) + + logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') + logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') + logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') + running_metrics_split.reset() + + +def _write_section_file(labels, section_file): + # define indices of the array + irange, xrange, depth = labels.shape + + if config.TEST.INLINE: + i_list = list(range(irange)) + i_list = ["i_" + str(inline) for inline in i_list] + else: + i_list = [] + + if config.TEST.CROSSLINE: + x_list = list(range(xrange)) + x_list = ["x_" + str(crossline) for crossline in x_list] + else: + x_list = [] + + list_test = i_list + x_list + + file_object = open(section_file, "w") + file_object.write("\n".join(list_test)) + file_object.close() + + +def test(*options, cfg=None): + update_config(config, options=options, config_file=cfg) + n_classes = config.DATASET.NUM_CLASSES + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + log_dir, model_name = os.path.split(config.TEST.MODEL_PATH) + + # load model: + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False) + model = model.to(device) # Send to GPU if available + + running_metrics_overall = runningScore(n_classes) + + # Augmentation + section_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ) + ] + ) + + patch_aug = Compose( + [ + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] + ) + + pre_processing = _compose_processing_pipeline( + config.TRAIN.DEPTH, aug=patch_aug + ) + output_processing = _output_processing_pipeline(config) + + splits = ( + ["test1", "test2"] + if "Both" in config.TEST.SPLIT + else [config.TEST.SPLIT] + ) + for sdx, split in enumerate(splits): + labels = np.load( + path.join(config.DATASEST.ROOT, "test_once", split + "_labels.npy") + ) + section_file = path.join( + config.DATASEST.ROOT, "splits", "section_" + split + ".txt" + ) + _write_section_file(labels, section_file) + _evaluate_split( + split, + section_aug, + model, + pre_processing, + output_processing, + device, + running_metrics_overall, + config, + ) + + # FINAL TEST RESULTS: + score, class_iou = running_metrics_overall.get_scores() + + logger.info("--------------- FINAL RESULTS -----------------") + logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') + for cdx, class_name in enumerate(_CLASS_NAMES): + logger.info( + f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' + ) + logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') + logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') + logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') + + # Save confusion matrix: + confusion = score["confusion_matrix"] + np.savetxt(path.join(log_dir, "confusion.csv"), confusion, delimiter=" ") + + +if __name__ == "__main__": + fire.Fire(test) diff --git a/experiments/interpretation/dutchf3_voxel/local/train.py b/experiments/interpretation/dutchf3_voxel/local/train.py new file mode 100644 index 00000000..0c1776cf --- /dev/null +++ b/experiments/interpretation/dutchf3_voxel/local/train.py @@ -0,0 +1,302 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# /* spell-checker: disable */ + +import logging +import logging.config +from os import path + +import cv2 +import fire +import numpy as np +import torch +from albumentations import (Compose, HorizontalFlip, Normalize, PadIfNeeded, + Resize) +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from toolz import compose +from torch.utils import data + +from deepseismic_interpretation.dutchf3.data import get_voxel_loader, decode_segmap +from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, + tensorboard_handlers) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, + create_summary_writer) +from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, + create_supervised_trainer) +from cv_lib.segmentation.dutchf3.metrics import (FrequencyWeightedIoU, + MeanClassAccuracy, MeanIoU, + PixelwiseAccuracy) +from cv_lib.segmentation.dutchf3.utils import (current_datetime, generate_path, + git_branch, git_hash, np_to_tb) + +from default import _C as config +from default import update_config + + +def prepare_batch(batch, device=None, non_blocking=False): + x, y = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ) + + +def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + logger.debug(config.WORKERS) + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + + torch.manual_seed(config.SEED) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(config.SEED) + np.random.seed(seed=config.SEED) + + # Setup Augmentations + basic_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=1, + ), + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), + ] + ) + if config.TRAIN.AUGMENTATION: + train_aug = Compose( + [ + basic_aug, + HorizontalFlip(p=0.5), + ] + ) + val_aug = basic_aug + else: + train_aug = val_aug = basic_aug + + TrainVoxelLoader = get_voxel_loader(config) + train_set = TrainVoxelLoader(split = "train", window_size = config.WINDOW_SIZE) + val_set = TrainVoxelLoader(split = "val", window_size = config.WINDOW_SIZE) + + n_classes = train_set.n_classes + + train_loader = data.DataLoader( + train_set, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + shuffle=True, + ) + val_loader = data.DataLoader( + val_set, + batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model = model.to(device) # Send to GPU + + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + snapshot_duration = scheduler_step * len(train_loader) + scheduler = CosineAnnealingScheduler( + optimizer, + "lr", + config.TRAIN.MAX_LR, + config.TRAIN.MIN_LR, + snapshot_duration, + ) + + # weights are inversely proportional to the frequency of the classes in the training set + class_weights = torch.tensor( + config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False + ) + + criterion = torch.nn.CrossEntropyLoss( + weight=class_weights, ignore_index=255, reduction="mean" + ) + + trainer = create_supervised_trainer( + model, optimizer, criterion, prepare_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + def _select_pred_and_mask(model_out_dict): + return ( + model_out_dict["y_pred"].squeeze(), + model_out_dict["mask"].squeeze(), + ) + + evaluator = create_supervised_evaluator( + model, + prepare_batch, + metrics={ + "IoU": MeanIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "nll": Loss(criterion, output_transform=_select_pred_and_mask), + "mca": MeanClassAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + "fiou": FrequencyWeightedIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "pixa": PixelwiseAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + }, + device=device, + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler( + Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={ + "IoU": "IoU :", + "nll": "Avg loss :", + "pixa": "Pixelwise Accuracy :", + "mca": "Mean Class Accuracy :", + "fiou": "Freq Weighted IoU :", + }, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={ + "IoU": "Validation/IoU", + "nll": "Validation/Loss", + "mca": "Validation/MCA", + "fiou": "Validation/FIoU", + }, + ), + ) + + def _select_max(pred_tensor): + return pred_tensor.max(1)[1] + + def _tensor_to_numpy(pred_tensor): + return pred_tensor.squeeze().cpu().numpy() + + transform_func = compose( + np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy + ) + + transform_pred = compose(transform_func, _select_max) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Mask", + "mask", + transform_func=transform_func, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Pred", + "y_pred", + transform_func=transform_pred, + ), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + path.join(output_dir, config.TRAIN.MODEL_DIR), + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger.info("Starting training") + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml b/experiments/interpretation/tgs_salt/apex/configs/hrnet.yaml similarity index 100% rename from experiments/segmentation/tgs_salt/apex/configs/hrnet.yaml rename to experiments/interpretation/tgs_salt/apex/configs/hrnet.yaml diff --git a/experiments/segmentation/tgs_salt/apex/configs/unet.yaml b/experiments/interpretation/tgs_salt/apex/configs/unet.yaml similarity index 100% rename from experiments/segmentation/tgs_salt/apex/configs/unet.yaml rename to experiments/interpretation/tgs_salt/apex/configs/unet.yaml diff --git a/experiments/segmentation/tgs_salt/apex/default.py b/experiments/interpretation/tgs_salt/apex/default.py similarity index 100% rename from experiments/segmentation/tgs_salt/apex/default.py rename to experiments/interpretation/tgs_salt/apex/default.py diff --git a/experiments/segmentation/tgs_salt/apex/run.sh b/experiments/interpretation/tgs_salt/apex/run.sh similarity index 100% rename from experiments/segmentation/tgs_salt/apex/run.sh rename to experiments/interpretation/tgs_salt/apex/run.sh diff --git a/experiments/segmentation/tgs_salt/apex/train.py b/experiments/interpretation/tgs_salt/apex/train.py similarity index 100% rename from experiments/segmentation/tgs_salt/apex/train.py rename to experiments/interpretation/tgs_salt/apex/train.py diff --git a/experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml b/experiments/interpretation/tgs_salt/distributed/configs/hrnet.yaml similarity index 100% rename from experiments/segmentation/tgs_salt/distributed/configs/hrnet.yaml rename to experiments/interpretation/tgs_salt/distributed/configs/hrnet.yaml diff --git a/experiments/segmentation/tgs_salt/distributed/configs/unet.yaml b/experiments/interpretation/tgs_salt/distributed/configs/unet.yaml similarity index 100% rename from experiments/segmentation/tgs_salt/distributed/configs/unet.yaml rename to experiments/interpretation/tgs_salt/distributed/configs/unet.yaml diff --git a/experiments/segmentation/tgs_salt/distributed/default.py b/experiments/interpretation/tgs_salt/distributed/default.py similarity index 100% rename from experiments/segmentation/tgs_salt/distributed/default.py rename to experiments/interpretation/tgs_salt/distributed/default.py diff --git a/experiments/segmentation/tgs_salt/distributed/run.sh b/experiments/interpretation/tgs_salt/distributed/run.sh similarity index 100% rename from experiments/segmentation/tgs_salt/distributed/run.sh rename to experiments/interpretation/tgs_salt/distributed/run.sh diff --git a/experiments/segmentation/tgs_salt/distributed/train.py b/experiments/interpretation/tgs_salt/distributed/train.py similarity index 100% rename from experiments/segmentation/tgs_salt/distributed/train.py rename to experiments/interpretation/tgs_salt/distributed/train.py diff --git a/experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml b/experiments/interpretation/tgs_salt/horovod/configs/hrnet.yaml similarity index 100% rename from experiments/segmentation/tgs_salt/horovod/configs/hrnet.yaml rename to experiments/interpretation/tgs_salt/horovod/configs/hrnet.yaml diff --git a/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml b/experiments/interpretation/tgs_salt/horovod/configs/unet.yaml similarity index 100% rename from experiments/segmentation/tgs_salt/horovod/configs/unet.yaml rename to experiments/interpretation/tgs_salt/horovod/configs/unet.yaml diff --git a/experiments/segmentation/tgs_salt/horovod/default.py b/experiments/interpretation/tgs_salt/horovod/default.py similarity index 100% rename from experiments/segmentation/tgs_salt/horovod/default.py rename to experiments/interpretation/tgs_salt/horovod/default.py diff --git a/experiments/segmentation/tgs_salt/horovod/run.sh b/experiments/interpretation/tgs_salt/horovod/run.sh similarity index 100% rename from experiments/segmentation/tgs_salt/horovod/run.sh rename to experiments/interpretation/tgs_salt/horovod/run.sh diff --git a/experiments/segmentation/tgs_salt/horovod/train.py b/experiments/interpretation/tgs_salt/horovod/train.py similarity index 100% rename from experiments/segmentation/tgs_salt/horovod/train.py rename to experiments/interpretation/tgs_salt/horovod/train.py diff --git a/experiments/segmentation/tgs_salt/local/configs/hrnet.yaml b/experiments/interpretation/tgs_salt/local/configs/hrnet.yaml similarity index 100% rename from experiments/segmentation/tgs_salt/local/configs/hrnet.yaml rename to experiments/interpretation/tgs_salt/local/configs/hrnet.yaml diff --git a/experiments/segmentation/tgs_salt/local/configs/unet.yaml b/experiments/interpretation/tgs_salt/local/configs/unet.yaml similarity index 100% rename from experiments/segmentation/tgs_salt/local/configs/unet.yaml rename to experiments/interpretation/tgs_salt/local/configs/unet.yaml diff --git a/experiments/segmentation/tgs_salt/local/default.py b/experiments/interpretation/tgs_salt/local/default.py similarity index 100% rename from experiments/segmentation/tgs_salt/local/default.py rename to experiments/interpretation/tgs_salt/local/default.py diff --git a/experiments/segmentation/tgs_salt/local/run.sh b/experiments/interpretation/tgs_salt/local/run.sh similarity index 100% rename from experiments/segmentation/tgs_salt/local/run.sh rename to experiments/interpretation/tgs_salt/local/run.sh diff --git a/experiments/segmentation/tgs_salt/local/train.py b/experiments/interpretation/tgs_salt/local/train.py similarity index 100% rename from experiments/segmentation/tgs_salt/local/train.py rename to experiments/interpretation/tgs_salt/local/train.py diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index c461ac17..15c1f7ed 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -15,7 +15,6 @@ from torch.utils import data - def _train_data_for(data_dir): return path.join(data_dir, "train", "train_seismic.npy") @@ -40,6 +39,47 @@ def _test2_labels_for(data_dir): return path.join(data_dir, "test_once", "test2_labels.npy") +def readSEGY(filename): + """[summary] + Read the segy file and return the data as a numpy array and a dictionary describing what has been read in. + + Arguments: + filename {str} -- .segy file location. + + Returns: + [type] -- 3D segy data as numy array and a dictionary with metadata information + """ + + # TODO: we really need to add logging to this repo + print('Loading data cube from', filename,'with:') + + # Read full data cube + data = segyio.tools.cube(filename) + + # Put temporal axis first + data = np.moveaxis(data, -1, 0) + + #Make data cube fast to acess + data = np.ascontiguousarray(data,'float32') + + #Read meta data + segyfile = segyio.open(filename, "r") + print(' Crosslines: ', segyfile.xlines[0], ':', segyfile.xlines[-1]) + print(' Inlines: ', segyfile.ilines[0], ':', segyfile.ilines[-1]) + print(' Timeslices: ', '1', ':', data.shape[0]) + + #Make dict with cube-info + data_info = {} + data_info['crossline_start'] = segyfile.xlines[0] + data_info['inline_start'] = segyfile.ilines[0] + data_info['timeslice_start'] = 1 #Todo: read this from segy + data_info['shape'] = data.shape + #Read dt and other params needed to do create a new + + + return data, data_info + + class SectionLoader(data.Dataset): def __init__( self, data_dir, split="train", is_transform=True, augmentations=None @@ -85,6 +125,49 @@ def transform(self, img, lbl): return torch.from_numpy(img).float(), torch.from_numpy(lbl).long() +class VoxelLoader(data.Dataset): + def __init__( + self, data_dir, window, coord_list, split="train", n_classes = 2 + ): + self.data_dir = data_dir + # TODO: write loader to poppulate data from directory + self.split = split + self.n_classes = n_classes + self.window = window + self.len = len(coord_list) + + # Read 3D cube + # NOTE: we cannot pass this data manually as serialization of data into each python process is costly, + # so each worker has to load the data on its own. + self.data, self.data_info = readSEGY(os.path.join(self.data_dir, "data.segy")) + + def __len__(self): + return self.len + + def __getitem__(self, index): + + # TODO: can we specify a pixel mathematically by index? + pixel = self.coord_list[index] + x, y, z = pixel + # TODO: current bottleneck - can we slice out voxels any faster + small_cube = self.data[ + x - self.window : x + self.window + 1, + y - self.window : y + self.window + 1, + z - self.window : z + self.window + 1, + ] + + return small_cube[np.newaxis, :, :, :], pixel + + # TODO: do we need a transformer for voxels? + """ + def transform(self, img, lbl): + # to be in the BxCxHxW that PyTorch uses: + lbl = np.expand_dims(lbl, 0) + if len(img.shape) == 2: + img = np.expand_dims(img, 0) + return torch.from_numpy(img).float(), torch.from_numpy(lbl).long() + """ + class TrainSectionLoader(SectionLoader): def __init__( self, data_dir, split="train", is_transform=True, augmentations=None @@ -148,6 +231,18 @@ def __getitem__(self, index): return im, lbl +class TrainVoxelLoader(VoxelLoader): + def __init__( + self, data_dir, split="train" + ): + super(TrainVoxelLoader, self).__init__( + data_dir, + split=split + ) + +# TODO: write TrainVoxelLoaderWithDepth +TrainVoxelLoaderWithDepth = TrainVoxelLoader + class TestSectionLoader(SectionLoader): def __init__( self, data_dir, split="test1", is_transform=True, augmentations=None @@ -215,6 +310,17 @@ def __getitem__(self, index): return im, lbl +class TestVoxelLoader(VoxelLoader): + def __init__( + self, data_dir, split="test" + ): + super(TestVoxelLoader, self).__init__( + data_dir, + split=split + ) + +# TODO: write TestVoxelLoaderWithDepth +TestVoxelLoaderWithDepth = TestVoxelLoader def _transform_WH_to_HW(numpy_array): assert len(numpy_array.shape) >= 2, "This method needs at least 2D arrays" @@ -506,6 +612,9 @@ def __getitem__(self, index): "section": TrainSectionLoaderWithDepth } +_TRAIN_VOXEL_LOADERS = { + "voxel": TrainVoxelLoaderWithDepth, +} def get_patch_loader(cfg): assert cfg.TRAIN.DEPTH in ["section", "patch", "none"], f"Depth {cfg.TRAIN.DEPTH} not supported for patch data. \ @@ -517,12 +626,15 @@ def get_section_loader(cfg): Valid values: section, none." return _TRAIN_SECTION_LOADERS.get(cfg.TRAIN.DEPTH, TrainSectionLoader) +def get_voxel_loader(cfg): + assert cfg.TRAIN.DEPTH in ["voxel", "none"], f"Depth {cfg.TRAIN.DEPTH} not supported for section data. \ + Valid values: voxel, none." + return _TRAIN_SECTION_LOADERS.get(cfg.TRAIN.DEPTH, TrainVoxelLoader) _TEST_LOADERS = { "section": TestSectionLoaderWithDepth, } - def get_test_loader(cfg): return _TEST_LOADERS.get(cfg.TRAIN.DEPTH, TestSectionLoader) diff --git a/interpretation/models/__init__.py b/interpretation/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/interpretation/models/texture_net.py b/interpretation/models/texture_net.py new file mode 100644 index 00000000..418b7ae4 --- /dev/null +++ b/interpretation/models/texture_net.py @@ -0,0 +1,161 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +# code modified from https://github.com/waldeland/CNN-for-ASI + +import torch +from torch import nn + +# TODO; set chanels from yaml config file +class TextureNet(nn.Module): + def __init__(self, n_classes=2): + super(TextureNet, self).__init__() + + # Network definition + # Parameters #in_channels, #out_channels, filter_size, stride (downsampling factor) + self.net = nn.Sequential( + nn.Conv3d( + 1, 50, 5, 4, padding=2 + ), + nn.BatchNorm3d(50), + # nn.Dropout3d() #Droput can be added like this ... + nn.ReLU(), + nn.Conv3d(50, 50, 3, 2, padding=1, bias=False), + nn.BatchNorm3d(50), + nn.ReLU(), + nn.Conv3d(50, 50, 3, 2, padding=1, bias=False), + nn.BatchNorm3d(50), + nn.ReLU(), + nn.Conv3d(50, 50, 3, 2, padding=1, bias=False), + nn.BatchNorm3d(50), + nn.ReLU(), + nn.Conv3d(50, 50, 3, 3, padding=1, bias=False), + nn.BatchNorm3d(50), + nn.ReLU(), + nn.Conv3d( + 50, n_classes, 1, 1 + ), # This is the equivalent of a fully connected layer since input has width/height/depth = 1 + nn.ReLU(), + ) + # The filter weights are by default initialized by random + + def forward(self, x): + """ + Is called to compute network output + + Args: + x: network input - torch tensor + + Returns: + output from the neural network + + """ + return self.net(x) + + def classify(self, x): + """ + Classification wrapper + + Args: + x: input tensor for classification + + Returns: + classification result + + """ + x = self.net(x) + _, class_no = torch.max(x, 1, keepdim=True) + return class_no + + # Functions to get output from intermediate feature layers + def f1(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ + return self.getFeatures(x, 0) + + def f2(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ + return self.getFeatures(x, 1) + + def f3(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ + return self.getFeatures(x, 2) + + def f4(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ + return self.getFeatures(x, 3) + + def f5(self, x): + """ + Wrapper to obtain a particular network layer + + Args: + x: input tensor for classification + + Returns: + requested layer + + """ + return self.getFeatures(x, 4) + + def getFeatures(self, x, layer_no): + """ + Main call method to call the wrapped layers + + Args: + x: input tensor for classification + layer_no: number of hidden layer we want to extract + + Returns: + requested layer + + """ + layer_indexes = [0, 3, 6, 9, 12] + + # Make new network that has the layers up to the requested output + tmp_net = nn.Sequential() + layers = list(self.net.children())[0 : layer_indexes[layer_no] + 1] + for i in range(len(layers)): + tmp_net.add_module(str(i), layers[i]) + return tmp_net(x) + + +def get_seg_model(cfg, **kwargs): + model = TextureNet(n_classes = cfg.DATASET.NUM_CLASSES) + return model + From a7daa6882e471b5e86b534cee969f88d1c677ee0 Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Thu, 26 Sep 2019 13:13:46 +0000 Subject: [PATCH 052/207] Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 --- .../models/patch_deconvnet_skip.py | 3 +- examples/interpretation/README.md | 1 + .../01_F3_training_and_evaluation_local.ipynb | 989 ++++++++++++++++++ .../notebooks/configs/hrnet.yaml | 98 ++ .../configs/patch_deconvnet_skip.yaml | 59 ++ .../notebooks/configs/unet.yaml | 59 ++ .../interpretation/notebooks/utilities.py | 309 ++++++ 7 files changed, 1516 insertions(+), 2 deletions(-) create mode 100644 examples/interpretation/README.md create mode 100644 examples/interpretation/notebooks/01_F3_training_and_evaluation_local.ipynb create mode 100644 examples/interpretation/notebooks/configs/hrnet.yaml create mode 100644 examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml create mode 100644 examples/interpretation/notebooks/configs/unet.yaml create mode 100644 examples/interpretation/notebooks/utilities.py diff --git a/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py b/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py index e2f5d951..52b8eda2 100644 --- a/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py +++ b/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py @@ -296,7 +296,7 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] features = list(vgg16.features.children()) - i_layer = 0; + i_layer = 0 # copy convolutional filters from vgg16 for idx, conv_block in enumerate(blocks): for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): @@ -319,6 +319,5 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): def get_seg_model(cfg, **kwargs): assert cfg.MODEL.IN_CHANNELS==1, f"Patch deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" model = patch_deconvnet_skip(n_classes=cfg.DATASET.NUM_CLASSES) - print("hello") return model diff --git a/examples/interpretation/README.md b/examples/interpretation/README.md new file mode 100644 index 00000000..7f151c60 --- /dev/null +++ b/examples/interpretation/README.md @@ -0,0 +1 @@ +Description of examples diff --git a/examples/interpretation/notebooks/01_F3_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/01_F3_training_and_evaluation_local.ipynb new file mode 100644 index 00000000..1447fa51 --- /dev/null +++ b/examples/interpretation/notebooks/01_F3_training_and_evaluation_local.ipynb @@ -0,0 +1,989 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Model training and evaluation on F3 Netherlands dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this notebook, we will train a deep neural network for facies prediction using F3 Netherlands dataset. The processed F3 data is made available by [Alaudah et al. 2019](https://github.com/olivesgatech/facies_classification_benchmark). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Environment setup\n", + "\n", + "To set up the conda environment, please follow the instructions in the top-level [README.md](../../../README.md) file.\n", + "\n", + "__Note__: To register the conda environment in Jupyter, run:\n", + "`python -m ipykernel install --user --name envname`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Library imports" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's load required libraries." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "import logging.config\n", + "from os import path\n", + "import random\n", + "import matplotlib.pyplot as plt\n", + "plt.rcParams.update({\"font.size\": 16})\n", + "\n", + "import yacs.config\n", + "\n", + "import cv2\n", + "import numpy as np\n", + "import torch\n", + "from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize\n", + "from ignite.contrib.handlers import CosineAnnealingScheduler\n", + "from ignite.handlers import ModelCheckpoint\n", + "from ignite.engine import Events\n", + "from ignite.metrics import Loss\n", + "from ignite.utils import convert_tensor\n", + "from toolz import compose\n", + "from torch.utils import data\n", + "\n", + "from cv_lib.event_handlers import SnapshotHandler, logging_handlers\n", + "from cv_lib.segmentation.dutchf3.engine import create_supervised_trainer\n", + "\n", + "from cv_lib.segmentation.dutchf3.utils import (\n", + " current_datetime,\n", + " generate_path,\n", + " git_branch,\n", + " git_hash,\n", + " np_to_tb,\n", + ")\n", + "\n", + "from deepseismic_interpretation.dutchf3.data import (\n", + " get_patch_loader,\n", + " decode_segmap,\n", + " get_test_loader,\n", + ")\n", + "\n", + "from utilities import (\n", + " plot_aline,\n", + " prepare_batch,\n", + " _patch_label_2d,\n", + " _compose_processing_pipeline,\n", + " _output_processing_pipeline,\n", + " _write_section_file,\n", + " runningScore,\n", + ")\n", + "\n", + "# set device\n", + "device = \"cpu\"\n", + "if torch.cuda.is_available():\n", + " device = \"cuda\"\n", + "\n", + "# number of images to score\n", + "N_EVALUATE = 20\n", + "# experiment configuration file\n", + "CONFIG_FILE = \"./configs/patch_deconvnet_skip.yaml\"\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data download and preparation\n", + "\n", + "To download and prepare the F3 data set, please follow the instructions [here](../../../interpretation/dutchf3_patch/README.md). Once you've downloaded and prepared the data set, you'll find your files in the following directory tree:\n", + "\n", + "```\n", + "data\n", + "├── splits\n", + "├── test_once\n", + "│ ├── test1_labels.npy\n", + "│ ├── test1_seismic.npy\n", + "│ ├── test2_labels.npy\n", + "│ └── test2_seismic.npy\n", + "└── train\n", + " ├── train_labels.npy\n", + " └── train_seismic.npy\n", + "```\n", + "\n", + "We recommend saving the data under `/mnt/dutchf3` since this notebook will use that location as the data root. Otherwise, modify the DATASET.ROOT field in the configuration file, described next. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Experiment configuration file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use configuration files to specify experiment configuration, such as hyperparameters used in training and evaluation, as well as other experiment settings. We provide several configuration files for this notebook, under `./configs`, mainly differing in the DNN architecture used for defining the model.\n", + "\n", + "Modify the `CONFIG_FILE` variable above if you would like to run the experiment using a different configuration file." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "with open(CONFIG_FILE, \"rt\") as f_read:\n", + " config = yacs.config.load_cfg(f_read)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## F3 data set " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize a few sections of the F3 data set. The processed F3 data set is stored as a 3D numpy array. Let's view slices of the data along inline and crossline directions. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of inline slices: 401\n", + "Number of crossline slices: 701\n", + "Depth dimension : 255\n" + ] + } + ], + "source": [ + "# Load training data and labels\n", + "train_seismic = np.load(path.join(config.DATASET.ROOT, \"train/train_seismic.npy\"))\n", + "train_labels = np.load(path.join(config.DATASET.ROOT, \"train/train_labels.npy\"))\n", + "\n", + "print(f\"Number of inline slices: {train_seismic.shape[0]}\")\n", + "print(f\"Number of crossline slices: {train_seismic.shape[1]}\")\n", + "print(f\"Depth dimension : {train_seismic.shape[2]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's plot an __inline__ slice." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "idx = 100\n", + "x_in = train_seismic[idx, :, :].swapaxes(0, 1)\n", + "x_inl = train_labels[idx, :, :].swapaxes(0, 1)\n", + "\n", + "plot_aline(x_in, x_inl, xlabel=\"inline\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's plot a __crossline__ slice." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABCsAAAFfCAYAAABnQEYhAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nOy9a6h1a5Ye9IzxzrX2/r5zqrqq6CimUzEaRJT4RzAdf6gBFYOx0T9eIiGiSFAJImpQFDUGL/mlKP6QgBfoqEETwVabSMQE8UYiiEYFpZE01V6gL1Xlqe98e68133f4Y1ze8c619qk6Zfqcb6fHA+fsvdea853vbc5vjmeM8QwSERQKhUKhUCgUCoVCoVAofCjgL7sDhUKhUCgUCoVCoVAoFAoZRVYUCoVCoVAoFAqFQqFQ+KBQZEWhUCgUCoVCoVAoFAqFDwpFVhQKhUKhUCgUCoVCoVD4oFBkRaFQKBQKhUKhUCgUCoUPCkVWFAqFQqFQKBQKhUKhUPigUGRFoVAoFAqFQqFQeNUgoj9ORH/vF31uoVD45UORFYVC4QZE9KeJ6D0RfUJE3yGi/4aI/j4i+r7PDCL6dUQkRLR9EX0tFAqFQqHwZxfsPeSv+7L7USgUvlwUWVEoFF7CT4jIVwD8+QB+H4B/DMC/8eV2qVAoFAqFQqFQKPxKQJEVhULhMyEi3xWRnwLwtwP4u4joNxDRbyWi/4GI/l8i+hYR/Z50yn9pP79DRN8jor+SiH49Ef0XRPSLRPQLRPTvENHXvvDBFAqFQqFQeJUgoq8T0X9CRD9PRN+233/N4bBfT0R/wt5P/iMi+kY6/zdZpOh3iOh/JKLf/MWOoFAofF4UWVEoFH4giMifAPBzAP4qAO8A/A4AXwPwWwH8/UT0t9ihf7X9/JqIfCwi/y0AAvAvAvjVAP4SAN8E8Hu+uN4XCoVCoVB45WAA/xY04vPXAngP4F87HPM7APw9AP48ADuAfxUAiOjHAPynAP45AN8A8I8C+MNE9Ku+kJ4XCoUfCkVWFAqFz4P/C8A3ROSPi8ifEpEhIv8TgH8PwF/z0kki8jMi8kdF5FlEfh7Av/RZxxcKhUKhUChkiMgvisgfFpFPReQTAP88bt8lflJE/mcReQfgnwLwtxFRA/DbAfy0iPy0vbv8UQD/PYC/8QsdRKFQ+FwoAbxCofB58GMAfomIfhyqY/EbAJwBPAD4D146iYj+XAD/CjQq4ytQovTbv+y9LRQKhUKh8GcFiOgtgH8ZwG8B8HX7+CtE1ESk29/fSqf8LIATgB+FRmP8rUT0E+n7E4A/9svb60Kh8P8HFVlRKBR+IBDRXwElK/4rAP8ugJ8C8E0R+REA/zo01QMA5M7p/4J9/peJyFehHg66c1yhUCgUCoXCPfwjAP5iAD9u7xKedprfJ76Zfv+1AK4AfgFKYvykiHwt/feRiPy+L6LjhULhh0ORFYVC4TNBRF8lor8JwB8E8AdE5E9BoyN+SUSeiOg3Avg70yk/D2AA+AvTZ18B8D0A37W80d/9xfS+UCgUCoXCK8WJiB79P2g0xXuogPc3APwzd8757UT0l1oUxu8F8Ics6uIPAPgJIvobiKhZm7/5jkBnoVD4gFBkRaFQeAn/MRF9AvVG/JNQnYm/2777BwD8Xvv+nwbw7/tJIvIpNI/0vzbF7d8E4J8F8JcD+C5U4Oo//MJGUSgUCoVC4TXip6HkhP/3NQBvoJES/x2AP3LnnJ8E8G8D+H8APAL4BwFARL4F4G8G8E9AnSrfgjpOyhYqFD5gkMi9iO1CoVAoFAqFQqFQKBQKhS8HxSYWCoVCoVAoFAqFQqFQ+KBQZEWhUCgUCoVCoVAoFAqFDwofHFlBRL+FiP43IvoZIvrHv+z+FAqFQqFQ+JWFehcpFAqFQuHLxwelWUFEDcD/DuCvB/BzAP4kgN8mIv/rl9qxQqFQKBQKvyJQ7yKFQqFQKHwY2L7sDhzwGwH8jIj8HwBARH8Qqtx79wWhffyRbN/4xhfYvUKhUCgUXgcu3/q5XxCRX/Vl9+MV4nO9iwDAmR7kER99Qd0rFAqFQuH14BN8+4d+H/nQyIofg5YScvwcgB/PBxDR7wTwOwGgff3r+NX/8D/0+a5Adz5LwSWUfhey48l+Px7knwkOB6Tj7HO6F8DyWUEtcujrvT7m89Ox0e/PuNZNf45/35sn3BlmnoMfAjdjodvrLMfcrAVux/5DdIJGbuT4/aH9wybxZc4/P6s/33fuPwsvrGO+/vL9eKGdO/1a5vx43nFBfJ0YsSZ393g+9TMOIMGL+/l4+WXQqa/R5+P96Oub2z9ci1767oXjY/yHz2J/0AvtvLRX7z1nrO8v4vts9nunLmtxOH/ZS3kuP+NZQgJgzGec8J3Nn56XN88menl/3KyZXfs4rhefB3Ln+O/znLtpy7v+OZ9vMc6Xzrv3bH/ps7yv7rXzGevzs7/rd//sD9jlworv+y4CrO8jj3iLH6e/9ovpXaFQKBQKrwj/ufyhH/p95EMjK74vROT3A/j9APDwzW8K8ot1HJR+z0YVANkORMMAqBOo28f5BXkTjE30nE2ANs8lFtCmF5adIZck/9HMWHGDoBMk99Ff7q/zLZMGQax96vNz4fSu733c1bijbmNlvYyPkRgYm8TnNNJ7rl976Lnk7R7nkNK1zWAZTdvWfgmoE2STu4Y6ddI+pTnLhoMf63NP3a7Z5riFRfuwa595t8/9mCb2t+h1jgaCXzsTESTr57ZOsnPMAV15zjcD4zzmfvHr+O8DwHPTvvo10vWkCcCyGNCReSW23zot8+zzu0Dmmt8jVsjn92DwtgvCaIt1SHvFPxO2e8Pa4T01MnTuIaTj3CjWYJwH5KRjFO/XmNeZHbF5YNG+JmNZrgxceOmnzlvuo/3NAmqic2jrhPfaGb8v+ALwhZa9xFdCu8z7IeYFwDgB7dkuMwDqMud1zMv7nu0n3TdjS3txA/oZGGfBOH0G8WXDBOlaDXseid+vTdaLtdTAYoTTes/K4Zp5PbwJm7+XjFvx5w4L8Ow3+rypZdN73p9bJDrn23v7uwP7W+2nANjeE4R1fn2OZFvvVR23dWATULNn6iDI+2bPB9sn9sxBJ10XHwdLWoe0b3YCX+z5Ypfg6xxTPEsyJ7LN+QnSRua/D74O8RzDC+TJpn1a+mknxDUPJFm+J2nof+Nk//6cxnI/EOs9wNtA29aHtwhhdLpd58KfceT3ka/SNz4npVUoFAqFQuH74UMjK/5PAN9Mf/8a++w+CJCTrPaAe/qydzN7QrfjmyUWI1mNFf19bNo+mpEV9mZJBFAbSlgAkNNAzzZFE/3ODeFO03CAkhsQQMxyCXLCX0aXF2NJdkgiGvJLsOCzpVLdLiSAwjLRl1kxO5x3WkkfSeSH/U4tGQVM4CswOkF8FyXDKjyBYYBpn+VgVAkLiAFi98Km8+1vJzxujK9BOp5BSmi4AWDjkLOsx7uR7h75rsY3yAwiwTTM/XsCGBzkwSLx4uu2EwiTcMiGjRr2MgmMO2EXixf7iJiuZNwfjBykQ5ywyJeg4z3Ah/k1okLOAnHDtOXFJ5sTMQIKej9A976SEADCEHyhb52AYXOe1kH3eDIyCdOA9XOToS1G8CAb1wSMs7XFhLEdxrsJwLqeToz5vI8NiawE0OjlqARW0i5+Oj9zUsNynDBJvqOhivVvb2/53J5fSmLxbT8GwgjW58AkDvK+6A/5geTMi/0v74V0AfK1EdLnXj4XAJro8/YBQZgoYemTQBgPk9iTxgCLrgV8r2CSfN4PWq8lQ/cJ+X7xuWna3zDqnQTkNH/D51DvYycLJO2FuIl9aPmesfmUTBLx7b0Xp3K6ZpriuK8YCzFHkp6HYl1x8iT/2+T7m9Lf/pyw5zaEMOx+YxZQ/PukNzwdCc/C58HnexcpFAqFQqHwy4IPrRrInwTwFxHRX0BEZwB/B4Cf+pL7VCgUCoVC4VcO6l2kUCgUCoUPAB9UZIWI7ET0uwD8ZwAagH9TRP6XF09g80Z5iDmgnrGB6Yk1j/uar57cmxadgew591SQ5IHEhWcEhDnKhnuDD25NcU9eyu1fPIi5/fy3e+Szx69rKDU2Ce9+5zW8mK5044kmmVETJOo9hofPA+bZ1D9GJ/BmEQXmvWMP9fbriH4me5q6rmHV/XGGVGevp5gXVTwqpc+F8HHldAf1JtvpF/Mu0wynHhuWlBbqAK5k0TBz/j1NYmzT8+qRJXnpuR8+P0Ys2PLRJW+E6YX3MWtqBuDh4kuECs2t4foOI6XGRKRKvgQB4mkYeQ/l9I8j3KEtQN7rsoUT/D5Y5rU7QGQRKg8jvgeLLh0LsFvYQKQyMehq4/Y9c7yGIKKBchrQTOfxvsqMNhgpFeWwJrxjSZXqb/TeGA+aYtDPdj+n+erQwI+Yq52WdJpIqUp7IdIUYPvU5+CYcuFt2P6lXb3iOcIlR0541AUNROoFgZZ0G+qaqsIXb3ue28+4ieKhke89YH8kTb8gLOkFvN/Za/7sY0SU1P5GdJkfxpynK0HOAzgPa0QgAxhf0b1CJJCrLWYn9Lf9zrMXFgnFMwXPI5l8T/U51rxPxhm3zxQhRDoVAH5isKeVDUBO+hzwVJTxYP3IzwxJUWXDP6KZdsayRo35s9v3cpvX93Q/uhJatwgXSZE6A6DoM+La0uZeyM/19l6PbZe5meKfmjSv4zwjToT0H0/50FwRrwif+12kUCgUCoXCLws+KLICAETkpwH89A9+AixcNoUSe3i2/5d0KGgQJAvu+cuovwt6iLq9SMJCrfmJw4B3zJfpmR7hfTrmIi/5yP6Sy9qPMPKSTkDwH2aESCInXKsh4mIOL8/6ok4RFhyhyMkwIYaGgBMgZ8F4Ywac60M86bXbczIYk3SDNmI/nRzy4/J38ZJubefUBzdMPYxb5hzw1fPE0/FYOCENt7d1UiNsXR/PV5cNEZq/pFCkvo4cmn2an4tdm4aTImtf3BiMsPw7kdd0+Mmpn743pOm6hgETfZykTtY1OV7HiR0/J3ixo5aH7c1JvM35zekr/dH7p3tb51DiPDeit08J7dmN7MNAsyEvAHVZ5sjvmf5oY2wU6QG8z2t4ChEZ08ZXbbCbNsT1KwRhQn/UORxnTcmI+4mntsmRVIz2/ANPXTJiIuab0zxezeBEImh208V4JtA17Yc8F77HE/EQ87PDjGxZuJD2LDF/3kW+AGJpUz4nNGb3XaPD01WyEZt1NafBa4a2k28D2M66h8aJp27HDoxTw3hI88fA/nbE+e09h7bKOAtympvPCe2aQkbdxhxkxZwjJ05GQyK1yJ61qkfiUxpzCqC9p4WQ2UlATDFPk3TS74/3LHn6SMeSJpa/DyQti/jc19hTCR8o7rtlDvLzQmzve7O2XrwD7QlGXB1u+NSekyqRktTmOhZ+eHzud5FCoVAoFAp/xvHBkRWfCwLQRV+mxQyXG00KezlejGSm9eWSEfnv+XwVc4MZw9MADJvMc99FBTSz9kD2th+No/B2uk6DG/tuBDdrA2bkDe1f2P3uHfT+H7xsc35oki9CQBf1nHsfLX9dmgAnNczcUJcTAzuFkR86Gem67rX3eTsKxAWRZJ5PutJKFIgZfT4Gmi/5lDQ0VBQP4VWeuhlmSJNgPEy7K172zcPq2gI/iKK/IO0B2zbSCNzFvLFY2AfqRjTQXP+s9RGNEkIAMhsaYWixEyNJcNTOVcMteZCTobuQN0SrngGAzckmzM9IpoHOV5hHX0JkcgHp/KkWA4NEjUU244l3if5k8ibGDSdibo0n1wYRE7sksT7YnLSrNjA2UtFL0jGCLLpg6PenT1Q35fQOU6+C57jHNvc8CKGhEB7sPfeNZv/zWOxvP5byGto8LuuG2T8gfU5zno6EhRqtJkjJwP6IIEVcQPcmCoXW851U4+skMOLrZh54msdRTxot6Zl4+t6874PUsbXWtijmR5p2Sjg/96YBflOtxefsQMxIoxAtdaIiz50TkjH/koiYvE4y23BNiSCVfI/eIdT0IpjRUqKEJw7r7fe6Coyu+zqe3Xa+fDrn1Meg0UHpH5PDXsiRE0GkpT5nstbbbde5No2A0Qjb8w/wwCsUCoVCoVD4gPG6yQpgWkkRTo14gZuWdDKSM2EQL4UEci9ufDdf/kASKQj5mJ68YTeK8G6wDu0fDYSDTiOoZRHSXGyWnCaQDNSjketkRER1ZALG+++kgIUfxwvvmO0KmxGTFOflNIATsJ9XI/kmfJygaRKMQx9pjRKIv5cpnF7VZHgA6hn3g8bZfu3q8R/ng5fUJ5BTv6DpLWu6DSyyBgtCDNGuF2keZnjISdDbwUjwAQy/ZjLY/evkQReaRmFUHfF+A1O8cRNwn59H9I97kg8GZDZwcj/8/O1dMtR9PpPh4/fAOGnagO7XVB3DzuMOiEiIc/YHbeD6FROXzJFFWNfTo0aW9AqaqUYgjYKgTlMU0fecrwMoSCHZLCXI+3aZhrLPO8ns0zgjiCbxaAO/R6x9vtItiSC48ZzzNa0zTfKgW4qC3ks+LJpr7UZ8EGDrHI2Tzu14kKg+I6cx0w8YoG1oqsVQ8URqMm9HG0sXwvDqRNdswRuciPN7M1f3SM+j7R1FhMa8L2cUzdgQ9xanZ2d/czDs/blgyOQCCR0qquhzNlcYyjlMTgBoFAqF2GiObFNhVVGiIj3rZgdiKJNsOTy8Z5SRaITXnp7votEdvE8x0uW5ZuPN0ShHQkmvQfF5JjoBRGRKlAVm3V/5e5/DGdU195qPgXu+cKFQKBQKhcLrw6snK6LcYoQ6S7yIBrJWgx8H6Ett5AmbYeQaCmb0Enm5xhe8VOGdnSX21LOXSIqRiA9vm6CGQnghMfPjU1UGUCIKInLDy/Gl8PZBU7Zi3ItgwDToMQ06zw9vF8JoKcR7U+X/qPpwL6bYrYFG6/h8mE5QeNTKvdNz6cCDgRvh+yeZaRCnMQ0ui5jRyBmf1znXN3oYDZBjL8KimR+5VsKAXdOrUBBu9wHbOAWrgYVkgEgiSUiWvXbjHQcgA8jEzVL9IK+tT2r2Th/barfH+lx6H7Nuh+9H9+jG+H2OSFNLvFpEfzNmtRwnh8IVf2SmROfLKulcL25h0ky7OjI+GV6i0aOQdp0Yuqrh52kf6u2mILXGeUxjzqOorC86v+v4Qlegz7GEoWwVYlzrw+chSq2eBvihzz3Y9SJLqdVgGARk+6mdO7gNPJ46mAfGYOw7o7V537Wmn3t7zAK2zcAkYB4QIfTBGtRk1/Zzeif9XWYpZRGaxTGEtByzAJfOoKbtRZWJJrhcmpaa3cac/6e5WUPrxAjK4972cUfamFVO0kFYRQsCpBNGVP/JGxzrPvO2ljbs0EHAlbSf+Xk4ZoST+LmJ9BQGxMiBcZaV4KQxSYZcdvVOOWGQ7c1DdA6ASbT6/et7E7rHsJOVP7WH98kf4mncQFScorb+GzOeNvBjEhgqFAqFQqFQeIV49WRFaE5kbGMxPG7QUlnRq728h7c8GUtkxq0JeFJEPNiLopXdBAnGlTH2yYTQefivGDdie3qNceVZftH7bdeNk4EZaeHHdoL0pAEh7g1MxpoPI16y04s5AIFpdzCF2B93gDxt4JlmxEUyNJeXfouaCI9sNqQPZAgIa0lN827nKIWsB0FQg4UY6E1mukgqWUlXjhz4GC/Lja0b6Tj3jP3o0BwbeYqEkU3iJS/J7K/wtk+D3z2oS2RF3kqxHsmjauNbUzn899RJ/w605PXn76Lv/j/fKo9jknliRIUQkHP4fV9kgyencrjhGQNDePzduAUAEUaUDY7OYBqY0DUQ4XWNWMkOjxbwMoxe5taNViIjcgZBxjRAxYRU5VF/0kknSNL9FCSWAGQ5NwtJYdEM1JS9WYR2fXrTYjrJ4PcTp5/cBoaTFDxzb+SkcyVCU6DU+ihCGJ3xtDcdY2fIU2Ka/B4yHYXQU8iG8GbM0zY06sIjRzxC5coqFHwQEF50OXxJzh1MgtN5D9KDWSCJTOldN/x4a6QF6dgBYHQGkWhpTU9lOUSm3fCGgzCcwPJnaX625+ew3VDi+83XZuiz0TVygsj0pbbnZERPEEL/IyMiNBgrGWJrEUSF74NgfNLcNrE9OfcRSP/daKexnAaB/lsC/fdCOsW9EFPm28/WNe4LFmzbDIthFlyb4Ee//gn+9M3ICoVCoVAoFF4PXjdZ4V71/MINrEbSsYLCIH1jDbE9O7Dlc1I7bsSRhGHhxhTxNKx642mgQD2dlIwuZvV8Zux70xf09EIPAGO3l3AzBojHYiD6C7kKEqY5CO8iRT+kQSMTTGhxIXayoTMAvvA0xK8ughdvyKtRndq45wiPFIpjJHoyhrNYZDb8AQB92ge0my7JIdVgiSiwcOhYUpunnN8OokiliL5kQsedwknTwNMvIj3AyBHva/xn3l4PS/c+ZQIj2SvzZ1+GMb9LUTphVPpBSd8kzpN0TGpwPNiaRzpCigACrGwApnFunm06TeNHdr4lHly88l1TLRInje6MM8Lis3FKKXXE0l+ceNzP6ybz/opY/8WFGSna8rSPNdVCv4+QfWvrnp5Df8Ox3rnNOCYLbAKQfe0jhs5JJ1EjMukcAE5UWVMDQZpNPQhbjqse7PoXx4CmI1m16GvYWo4TbjRkAIQI6CT2ELovQBKzJMF43DAEuD4O0CVFTrAAJ4uq8NSyTLJGNITNWdprdNUcJY9U8PSrSOcx4c1InbLqQnMNdJ941BVkRkFlEU9/boXQ5kHPwtN7XhKg9PSc0NDYZApgejrRyZbA+5efq04ObQI5C7CvBKk0QT+l/SM6j+Mml5Ai4ox2iipC/nwhI2UEwKXNalH+78R3Tm/vD7BQKBQKhULhleBOUnOhUCgUCoVCoVAoFAqFwpeHVx5ZAfATmafNXZQaZg73wu7Z1a5esXGiteqHUGhEZFG88P6b13mIq95L5FfnnO7wug7SEGlo6K7m6GNGVpCgNUFrA8IEGoRu4d+AebL7DBWXS7tJQ6FNgKYRGLJZ2HpEDBy8m6eZF74EQXj0h4cf7xTRIbgw6HLwwh8847LJ1Ax5CR6yDZg2weFgWyd0gJKIKaUQfL7A9C9cJ8CaO6kgH5qF2Hda6DcyEb5jlEKutBHpMd4XSeKewGzP2hGeYdhLeomox3h4FAugGhl8mJ+0TjRo0dvw60TffRySImLcJU238wis0Srazoz68HOXe+LQzjhZBI5704d5rz2yxO8rr9BwXfsKzCiXG2FDP991WpaogNnGMYLG05BiW5tIZlSV2NXz3FijB6bYKcX3kbI0ML3583bC8GpCkS6DmQKQ++RbeRNrzz37mClPQET0LGlAKeXnKP7owp0uGkliFU/yPXcnjSKLLXoEi4/Xj8nVaSJSIcZDIXLbuneMgHfa1th4KV9MYuKmWRy4zQXL1XJuIjvG/LlEPKTPc0WdLJIZYI+S8UgK+5krmshs61g2Wvubfsq6HjbM2Cd8hVZFSutFA5CnFOWCuX+FZsUPEKE/YCnr6xFiI/3LG+342O25QRZR4Xs9NI2y7pDf25jzOTbBOAHPl4qsKBQKhUKh8Lrx+smKnSD5BbkBYrnKvK/HAvpiyheaL7Ge133MW2YoebDhxsj20FyYCJ9Ary8ptJdSSoWr2/dkBF/PQ9NKLPReLm0R0AyRP0A1KjgZwQTV3WgDfBqQzaoDhAFqY3LCxA2ugdABwDCiY1hudBPwaWiINwA8kOZODydCaNXXIAGdBjin36R5dhKnbSOIEiVxpq6HdLY+2BhzOHW+1Kdths03gZgeCE42B5uAW0e/NtWA8D7sDBFEzv6NAGQaS8w7MNNtslirp+YAi/gpCKoRwFqlgbcBTvonBCQyS62ikVN+jqKLdo2R53o4IYJIDVp0IfISZK0JAPTMRtqlceYUnEGL0aWfAfzedArMAD9qcTjU8JKZ/uLdc/FY62MWH+Udh8oFFIZ66H/kOWmWomClKLVyjcQY+YmsQgRAdFt1RhqWEpPjhAUkmOkHNI1c1S2wNvZJtAgBzHRLRsT9h5k+5P1wI1rm96C5V8fZ5nmzlBgA/c2hHHB+zjnpcUhx0DQIT6egheiSZlUl2Eg+21OZzPO1cYKiXeY6Cuk8teeVBCCvHDNmn5ywGhtuyIKYL0aUGI0hmA7HcEIqkUrcMYkNnzerBMJ0u0Hv7Vlf1yxorKTunENpoiVLgSihnEvPAkbg5fLCee3JU32A7dP1fvGKNDGHqY/cfUxQAtqIj5jrJPYag3LSasy54aEbi/qdiS8UCoVCoVB4RXjdZEU2gpJXKsQi7+gcDE+/d8NB3Gt2MLodnUyQMB0iahBg0PIC7dUHADPAur+cUvQNMC7igYDWNPffqzOEUeEESiI/UqSFfydMarSRR3nYtZmmSJ8buE64hIeP4kUc5AanTAPxpGQKnzu8esCg9Hbtc2c540FoJAgmaUFsBrgb/DnColmkCAu2s1qqm5VpJAIuH7XFKtwsR777+GAVD+hAyJCAQCFcKmQRK0udwNThgUkK5O88ksFIktAusaiR1saMtsgcw6BFs4RsnZyw2baBfmIwj1nBwC7l42IW7HtTscJhFRKEZgUF7+Ihysfn4Pr+pPPuYqxpznUSDxZdE93zzxzrdBRyFNdx8TlqqufSTiOCRMRy6fOc+N/XnbRKhn+wKxHmFTtyBRYAM3rHCD7eBhpP0Yn9qszA/ty0neuB0Gl6jRBUtL1OaS+3Tznu8TBsD88Or5CSy3Gu0SFWMtPvo7y3rLIIdQRZtNxvDyq+yQ8zZKFtHc2fBTLXlGhde/2eQv9mv7QpzukLAlun0wBYYu+KC1Lamg0jw7qRVTRSFBopMcQXCpIKANqT9Ss9b4XV0B65ylG2nWO+0t5yAU0n/+y5G8+KnXUdvbKLrSWarESv700ng3NZz7Rft/Oc6yBUBWhNcL3qM2f3ErCuj8KIewRJv2ZdDNdFIS3rmm49X3+v/pG1TCa57QSFsSAsGvEkaZ5j342IQvNSwMfonUKhUCgUCoXXitdNVkDD1l10z/92yBkhGDcjL2QtY+gv8y+82FE2qv1Xj7ogwYBW+nDiQ7/Xl0tiJQ5yiC+8OzsBu4v/macvCQ7GgV16yFEAACAASURBVD4WSn2J1AZtQ1gwNl5fyJ2YEPcOznBvH1d4DGUSKj7GsTHGg6C/6fBIDzW2Mlnh/82yqdPD6AZb0yyaJmtqg60FrOSjr4tEmUU1ylobOJtR4Ya+G/v7pWFc2xR89OaPZEMCdVrFWPOiWHnTm1QVf/PflFRYjEWxaJFkIeSykNEfwmIgAsAYNrc5JNzCD7wKANEsScksUb7SU4pEKMgSLUNJQYoAwLZ1jMEYY0aGgORFckT7xejnFnPoaUxiBmJjwWYCnE60EAk2HuhC8zaxPgyZxEUjrRCxRJcMCkLGS2m2VM3C56KxkhR+XLe9wm/12Mu+QQS4XttCGp1OPVUU0bG3JHYrAJ6fTxZFRLrVB0W0DOzv/bkthM66RwA6DY0kIok5zusoMtPDfFzez7ePzxiD8XDacdkbRAjX3hZR3kaCfTBOrWs/0xzmHd0fGNfHxIxGH+X2byKMFFVG1tfxVnNpmAQPj9dYz+vesF8bmAUnK216eZ4bmFmLAzMPW1OAm9+/1qUcaUCIShZeinWz8XkJVscYjOu1WbSUEX6nvlTCGIOi3OvjaUcfhL232Cu+FkyC89ZviB+/f3R+gWtv2Pe2VKQ5nfa4167Xo+qvtWN76fLcbsfuz9Pjvzs5zcei/ryqCzdZSKVVlVf7uychVOw8o+QKhUKhUCgUXilePVkRHrrwHurf3T12ZD9TeoGQ6MuclbK8ke0XTIPVPL1LWDmbyrt5yCi/fPo13DOcNQiOOdqdIhrDvb36RxocEIZ/LrXokRZ0hb4Jd1ktFsJCJmi/Z1CBsGhOf5ufUcfMX78S+Ero1+nNi/mxLsU40mXd4xeh4GYfhQmb5gjDujy0/yKE/egR9uoDlq4iT21W47gSmivtu6cy7wW/lHthyb2/a19yDvyNEQqdXyE1UAWAdC+fkPaKL03eK77X7Pd+YHRG09vvmo3IqDrhRi5mOo95no97ljczjgEjNWbqzfm8o7XJUrkxNpLxBswolTj/wdpu0+BxPZP8We9aBScM5xTdMUsszggYMoM0t8NWVWfbelx/47EYkt7fPvRaI6UUPZx3MICTGcVbG9haKuVoXXvYdB669TUbsG/PVwwhdCcnzFje7RpjMHaP9mHB6GpIRxqPRbuInQsjbYiSIc6C1hB9H4kUe/f+AWMQ3r0/QwZjdMJ4ThFFTa8rnTQ64h7flkop+zoIsBAu4uWSfcvnvWTPSdoGTo8e4dTxeNLfGw98/Pi8zF0fDLx5jvVuRqw1Hrj2hj4IJy9n6pEwnbEPtjngIPV614oYe3yOmDv/uW3dyLWVANmM1BmuU2KEhKepcdpLvv5P1w1sc+T7S0R/b21AJF0zzbOSZnZ/bTnXEDEPbATe3nnZZz4Pee2PJXo9egrQe8TJxEEcx/toZBhZTmNNT5OOlir6FAqFQqFQKLxGvGqyggRgy60OvYiLeaoJEH8j9ygKYI2RjbKcmOSEYKZ9+N+W8++RDdJE8+bNsBSSKVhn50xjWzuaw33RNNrBQ8FDJM6IgRzOO0OFZYr9eVg+SXi7c7j3MWzfZwLAEt0gNJZcbbpw6HyQkQB8pdl+bpGh3vZm3mePKkjpAdJkPe0QBo6OGeUyeC0nmEOe2xSFvBF0JImolMTL+FcRWSLNMjwIoJ6JK1tvtw86sJQh9LkR0oskPYTQZbBr+LDyHAVpJdDc/EPTUxBwivgt1/dBkaUYsKzRPtBoIvH9MYCeyLn+MavRzCOMPk8ZiN8tGmRGXqTLi2mXXHmSMnk/DFIh1vTdCKJurqFrPQgBu5cp9ZKXlsrEm0WQtJkyc6+PiwYJgMtZ60iqF1q9+mxef2CSLB4hcL22VT8FwPnBiIxOYQCPTCjI4do+3stMlxi+Nml+dk+h8NQZL+d5TMtyUrDr+XQlnBJ5GGKfgiiJC6z7XZrMPefHZFKxE9qVEAKjpASlTL4ltEcuX9Eb6tIE77eH2Bfbedf0JdObGYOxOZmRSKyjAZ4/650xrqypQDutxLA/e61/S8rRJkHYefSDa++QX9uJ2dgPt6yO90OCaML8t8CJ53zNk6Ye5fa5vUAEHEgGjWyihRwcY/Zx7bPvV5pjEyWhZE9pU8DUrvHnfxbybQI+d+yXV/3Pe6FQKBQKhcLrJiswgO29v+DZZ6KecycHIv/cvo53V9Lzs7J6fC/20m5kgYv+ceQnE/qDLDntSwUJcaNDpmEhWIx12jHV6AehPVmUBIwgMJIi8r3bNGbFBA3HWcmGqGbi7R81BdJ8eYWEeMGNaA6CnEZ4ltEpqmm4UN8NIeKiin6pbJjk/jjSsRg0q4B0CoLGvx8Peixdk1iqd9Xz+E2MUDabEzfSMtE0CBhydw2OkQ9xfPo+2um6PjEn/nWMHWFkScvfJ+P1sAlD1+QoCJnD8l1UkQhj11aP9pc82X5JRIZH4XSvJuDzD8SazgYQZMMxRaZDiZj2rMZRkCupf9RTcwyrcIOlYsQc29zL42x99T1u8zWcrDmIR05NCVp1Cj5tUakFogSBpLQj2nXN3r05AwKwid9y0hu4PMwHhUcE4TBNJKrDEMQSybJv49mRIooWrQ8ba1SV6Wker2msPOfOydhJVGISa94p6ycN7/ici7xXlqifINxS2/bMhBD6czo2XaqfH/DcZBKBg+bcpTVx0mXRzDCShHbC5ucngpJMVyRS+lwI0+/35vtH73nVC5paJNrIYS1sDfJ8kc1j29O9fOf5H/8ONFlEWUmAPT9f073tc+xb/+lxzD3ryHPiHyWtHG+D+hSJPhKUGPZY8DS+JGQ6ToLxhtG+VwKbhUKhUCgUXjfqbaZQKBQKhUKhUCgUCoXCB4XXHVmB6VniXHLAvZQWPZGLPwAHTzalzzA9cF5Sb5gHjbL3bAda9pgDS5nD+XmKYkgQD7/e7EsBtncUpetyqHdU20hRHMJQT9/g8EoT04wSOOgmxLwcvXu3EdKAh62fhoa8WxUJGSmCw9FS9IR5MWc6BE33YrrknMOUOuBebJ7RKuNNBxgYVoa2W3lLOcnU7mDR/rLm8x+FMRfhSB/7nbDw+L5bGdw8d+4BBs2qAjkIhJIn3jymS5SN7cH409c2WqDpgc8CqQ4+XOcgVxDO8BRCT+aVBQD6XptpRAQcS5XOijMeCWTnhcdZxxx7845mn0YAHSIJKI8zfeZj8o6na8xypwevfk6Fsq9HmiRPDSLxiARaioFgaBTFeKd7zstz5ueCuBAu9F7zEpMjlSMGLBojPO5znqc4iyyRDx4hEZUe/Hybr7glT4j7PiIzWMDnPNHA4bGz3N/cJTR2loidtJ/GSaJsqkZGHG4N0ciu9n7ugxwhs1nkhc6ZtrMdI4Fw+8yN70eKOsnjB9L6adss0Pvx+ABlCmHlSGW5F1nhOjYpbcZT1SKiziLgctoZCICX1yVtdFbf0GMlpWqRpMiHrP8zgHHmdZw5JTDuwXWIMg9d0g+P2kozhQzL/TZOwLg0nL97mLdCoVAoFAqFV4bXTVYQpkBlMprZVdMJiGqb/p5nVQ34oIuWX3aTVmAYl2OD5tYDkUce9smguCYA9LNMDQvPD09GORtxQBdEPn82GqRhNehotgWoMUG7Gmn9TJECEMKRMah1ru6GHueX5lxK0XO0yRoiN4TtACvF6OQFiRIbfDkYb8BiHOe+5ZKGI3QMZkg5nQZkM+uM1QLZtqlFEE1lzQWZZT2pDbc1MK6sOewp3NpLIwYpFLoP2YKy7z3NROhW0yIZjCBAsgq/p7vYHhQ3jmAGMhv70DCJgCx6mK1uSsZx5kNC5yOdnw11AFbEVY3B3L0TzdQZopu2yKbLyTixtVhKdrJYCd3Up34gtvI8+f2Y9jUN0so2bnwdyTWWMOQBANskrQYboXIFyMaQ7ydswIAE0aDt2ZgwDfLDtrrds0AIuEbYfTx/9AaXvIeFEumDua/okLqU52STpY/BE0g6/wW+jYzYA8SMZQoCxOdwPFi50GNlEGuXrPRre6YQpM17gTq0dKkZ2GOTSKdZNGewbuH8mZhhrWQQFp2b6K+RdCSzTU8TiUZ976V9saTg5HnN1/dnK2OmiKV8wZyi42lOkV5l7ebnPYBJUqT+K/Fjz/PQDU73v/96XE+fW4JVmlpFg3VIsqSuHEuo8jWte6FQKBQKhcIrxasmK4TsZffwItfd6Kb12DjgSFbQ4XtSQmI1yKaXlU6YL9YNoC5L/vs4iRl3sogwLsa7iHqrOwAizc92AcRkRGWjLqIW7AWeLK9cNSwsKgC35Ij3/6Zkp3uT7aU/xAEBiPBqrBEgJJOsEDUuOQni5RxroamtQCliJNajAeM8r31cL3puKipHaggTm7gekkDdriJ/6LRqbLiHcQtLHfTMi0c1+mPzqzoCq8G9jN0N0CwEEHPhHx8M9ExixGfpGiSzXSdC8nwBEBpTdJCggqq8XkO8dK93Kxm81I2UcWFHIHRY/PdchSb0GFwnwIapBq79bDIjcHz+2pjik6LCgN5PSuONeVnEKrWPcprf+X7KWMg0pD5adE23KKCsK+Ln0aApxku34+ZrinDxeUt/x/xzItWWtbTxepSPkJWvtO8PUUQgqHhjkBX6kzfB8GcJAW2bA4nys0ciJ2E4MebkSCaFmlb64DZLpnrFkJirTpDOuF547sm8pzuBLhyRQNIE/Gwio/skZ5b7LJ0f2iIWRSWnVAXp+FOwCHA6GbMQUU48HDR6lmddJlB8Tnz98/w4PErL9+CYosfa+Cpye8P5pGgcMhJ0EkbzOR8nH0m9ps/aIwlKmazyNfb9eqEbUmb/+AVWq1AoFAqFQuGV4FWTFfEul8pvumhmpE24F2ub39Mwb6wbIG2mH/i7I18oRMw81Da/iIfxdhJgmOc+GYvzBRwpFSG9hAtBorKFHAgTNRL4msZoxg2g1R4inB3zvBz2vkyQkxV3wvLDO7lZxY0wDOZ3s/QrIUwb0uMF05MNEozNDWIJY5QmBxJ9G+dkqGeSwgw1tpdvYTNoSL24A5hVU/a0YEI3nl3ZWEPpdzNUScKbHtdLXljt02EdgGnYuPHm43LDcE9zkAYTHtqD6OWRMKFB0xB3A8enhlYCQ49Np98RUowwf9i+H7a+JhoYoeN+Piebadd+xDylOYAbhZtE9QXZ2cgGjpShgJMJRgIRY03LySkUJthK21AiYyeNqgFuU0+O1iFrFA42TOMfiP3nVVBGV/LLia9mYxsySQBqWh50CEXJWAcZKQNClK48glgt6WNp2JHbEmglDJJZ0tUWbHRolQof+kEA1veeDFrGCtIx0TbJITl0kZzsEvvO7v/4HlBCo3Xg3LXcJ8lSOlSE0K0SxbA8ln5xdtDuB5/jVAkn5kFoqdDBjPibrVxoTt8ag2flGquSMXLkUbqmjxEkQciI2B71qU9zmEm/INR4Rl9F5BWA7m142ddD1E8ujU0nJYRAoqVtZZJW3r/4LBGReQxLFROZa66/YM4rz1K6C1mxDfDpeOMUCoVCoVAovC68arIikwjZi0+Lk8mM3pxLnV72NJyZ1pddN+iARcE+8qk3qDHFati4BkVc8UozCiJ7aLNX1bvGmMZ2hOCrfgU/z+gEaVM/wwmW7gZk9v7mYTcs0RpHJ96Nczb3wQ1xC2G+Mc3Yvhv24m2fiZcQJCSLY14o5vB0+13+fsm5f85aBoScFy/NDGs32nvy7loodLvYOULghhX+/u8aIeYt9i9nuDgAWr322r9ZwYB2LHoR2burEQu06HLoF+4ppsWrf0M4JUJCEvsT+e55zy+ETTLeCZNwSbZ0pHYMmqRE7mInwMY2BgMXQJkP8zj7/ZIiPHI6ROzxhiD/kH6Ka5+QQJAM36WD8xpxrs+z3YfqrdcSqJTmXiw1qJ27lkRlQUulTdkM5N4JzIJBSiSM3pYbhqAGdUbwj4OVS9hbRCcQy33NFKEoeRpz4BcAlpD+cU0LlZk1kmUfgATSlaxZ9BRIZqlON9xzxEX24KdICt4GOrR9r87CNnfMgjE8i4LCKA6igPXv06nfEg/xO5nBThEpNUZbSAIvRRtEQi5F6iR0TxE6y8IYQeSkk09Jfg5b5FZeniCEPF2MoMTD1l9eD879QuwvIsHgsRAuk0gSW4d0XtprOUXNO0wHstnLswIAn/r8ngTn836TLlcoFAqFQqHw2vCqyQr1tpuxcwhxD12JRAAAyVje5kuqDAAe5uvexixCaO+u5HoMF39PpUk4ACsTYCdO8bMZOjySF137TCZiaO1b/v1arhNg06Rw4zy3c1v+0saWvem5e5KIGagRTv1gDPr5hFQOVmKecTzW5w5zzEB6yU62Fhjgp0nqqDd/5maHnoCTAF0jTZbUHot4cDFUPXHmcvta9uxtp9tl8rY8dSfrn0wCgUJ3YMn0yCSVeUrn9RM5IX7sXPgQ5vM1NoOdsnFlcyWcBAWXfts+TtEUuX8uRBlEBx/a9h6leclRRBhsJSL1M494iXz5pMER95hg0VBRMmSWE450CLfXW9IduLo1mwc526Ucjh/rOq+je0E0BShFwIwmqovi2IYaq7C+bPYM4fRTKKUX+F7m6I+ktYOnIzmxaQbvzV7z/XRd1yG0Do73aU5dyQRVk0PaUjou9VeycW6GOCWyYklJSM+9wXxDJEZwSJ4TfzjCDGcyskeUwPHL+nz5nLm+DDppRA7SsydFX2FbyaF4PpNFTnRCCN+mCRBrh/x+9E5kotCf8UEK2JyQTDKPRdMKfZ5Cd0Sfa9I1JiYiaUj3T28yoz7G7Fe0Y88KZQB1DpetkqI6ctRF/G19ka5rIJbiA+hz/XrZ0K8HdqNQKBQKhULhleFVkxVogv1H1FqMF90O8DNP77sdSnsy6mQad04mLNVAxA1hP1mNAidA2sUMZ1FCwQ2JReDSX4DjZxKHM4MxvPl3HGAkqV8CcE8GfLfvNrKqBPrCmm1GGvrCPZCM6/xSbn8fU0eWYwVBpGi/jaBJE3sUWkTyCktKtTnqb8hukSN9tg1QRCPIZmkiFtVALt6Zpzgbudvyqm/XT8ach2pnA1NoNYrp8NNzws0AVSdoug6r0bmSDJMsibWx4/hKU6TT53rIpC8IljqSjFxyI1xi3e9V5Fj67muHaU+G4X48l9LecbKlp2keab91Ag2J6h1++tgmYSFJwDT34bj3SBDqkTJozmoOi7p3Xzj5hzkmvz+W6g2Nb8YapJFF5NB++x0Sf6FCtzauRBbENdM853kLQrTRzVzklBtf8xjrSONzgzwZxz5xZBof2LDO1bKX3Ti+mULtThb2PEDXl0L34uaZkO99nilvC8knkwuaF0UQcZMYoyCgjlFfMmQK7CKNz/e4a6+44Z/bSGRzJteWqKbbR4a2QXNSfIyxLzwSxZc27q90/tXSjXbC8Zly1F1ZtIJe2Pc3Wi1+7NCOuN6OsJFDogFvNO4sbqFQKBQKhcIrQrleCoVCoVAoFAqFQqFQKHxQeOWRFQC9UfdorkTQT7JUEhCWJd2ATGcCmB63Y8nGLDyIFMoPqHeuNVo8szcREpQ8jYzQM/Br8A4M8/IRWTqLe+82RMg6m1ZGRHBYX2kH2tA2pNEi2mg+RrSBJXff+2LTtFQNoSxaZwdk/Qjg1vMJBoaJX7pHO3szc2SJa0aEF7YlgUwAUYrPB7mJebcFeKPCeyGsdxTIO4jsIfK4h+bYu+AfqRCd53KPY1i6z59vJffYDvOS+zVStIbsrNcjC+NO+RREs4+jE4a7mu+kOCB5bLMew3LNe6Urfe3Sfl/SCFJ0SPYwH+FaHB4ZsEgsbOaRHjau1IGIQABAIrPCgtzpT4pEkMPn5D/tmLWaxNpXeeHznEaV03MiOsfa5q73DXsEhN+rNMfi+1vocJE0oKic4h8veifreP3+Etd6ce98jkpIHvq4Pg7t5ciZ8K5jpmIcvOlyUNl0YeBox9fL22ea51jkwqxogkgX8/GOE9b0DevTss9SF5YKLzkKJ8YsaxRLpzVaIreTxUfz5WhGz3xm+c6XAg+WyA89MO4HpjV15g5U+JgidUzyOtPa70jJyWuZnwVLNE+6/yXNpVVCimZ31ZLpj5/dz0KhUCgUCoUPHa+brBiAPLeZXw7Ei96sNCGa332a33sFA8De//s0v7wSQ4SpEyAgq0phFRA2wi5ajWOp3HAwIoPwaKKim5HnrySEGwxeFjXIipNgPEh8TxdNIQhb/InAzwhtAU9ZyYQLdSNITLgx2vaX9+1gFGQjHICXC5iaG5jGSBojdZp5+IL5Ip9zxWn2LYgCEis7K9NQ7cmAvGp4eH9jBnAT0Fm/zGJ1UbFhpwPL4Eao5fbvdu1cTlKSoRAkyViU+2WnNfz8aPxsY5aBzOKiqZ8YpG1tSp4cxfYizSjls2dxvBBJ9L7nfHbROSDTCQAl49K+X8Q0j2HzQ+cryDzbj6EdYhoQchbcLcuayCk13s1ozik2ab59PwGYujJZ+2QeusK/Y9ymMIimO0XVka7aGrHn2ywhTEOr6YCBHmleblCm58ZirK6dCqmBHJ5PlmGU2sygMfVYooQuJyO2pXkDIMf7Mc1DTsOZF7D2jMyjNOdxiD/TFjLrzkWM4BJO5BP0WSIkEE7t3yEDFnIq/R0Cr/51888yC4FEVGDd6349ObRzHAL5M3HO4UoqzXXEkXjI99dhHMv3x8HmfWDaNzhJlMQNjaTtsK6ZfEp74HaPr/c8Cc3v7qS30cAUMS4UCoVCoVB4pXjVZAVdCadf3KZXEm6c5r8xxej0LM2TNpJBmr1Q2ks3m6AdX+1oUe+hMNDNupSTANvQlPvsBbvMN3fPIwZhCtKlShvhAfXPGqYxyABO6uambWBcWKMK7Jz+ntGeCe2JQFeY51vC0Mke6ngXzkYebExtGlBZswAwI22b2gljw1J1ZXmZT577abzIzN0/vpxbe+NhqJdZdC35mbB5/voOtGf1Uva9aeTJeRwMh1Tl42IWZIpskNbietStckWGkzA+AQSM81xDaQJ+TiSA569no9oiQEjIjNdJnAWRZV51cg2L6H7qL63fZUNcfL4BE3FM4/B5d8MOSxeiv7H2zQ1imyMjL8TWTkwwMUqXNtcOkVUXJBMXuwkqLt2aRtk0vmSWzARAba51JrWED3NxIL5ujHgfn+9xOpAVrGSB3xdThHL+nBFUiTTYaSUAnQTziIO2znNUNblj5C4khF+b0jyyr8PBSI0G7KfNTxCFhwpB8NvghbAButIa3bB8aWO3UrrCa8TWXbgYqcOJiTYFYY+2PnCY7yRiu5IEhwgNzH0sPO/z2WD6iNbzl2eQ7zXf93eQiYxjtMrdPZQ1LVK55+7ld/24o7iy3GmjY90XQaAf7nuy623QgTgJ00QjZM4vhFEVCoVCoVAovBK8brKiAw/fpoMxTOhnzJd/Ez8McUr37JrhPbYkfAgVzxRWQ9mPH5v+xxbG3x8F41E00mKzUomMKPEnAiuXZy/fJKBc894MVIkKArQqxg8A16SkTwCdp8d/AJAzYZyUsPAmg3igSbB4tYyomuKXeBjxQq0fOIHj1kbqzzaNuBvvnqcZ3PP0ugFMhxd0YBIy5hWUk4oieukQviq5wDuBPzGS5kxTZDJdQ5oeL+RCkdpGEDbDyYp0ns9ZhP9TeNfdUBgb5vWi73OzOZHlH7sH1aMUonqJ6KKMhpvIgGO60jSc7cM8r2z9ORA2ixCjG3v+p6c1WBrLQmKkIYU3GKLE2TF9aBC8TG1c1+HVHGy/C8tqWOVpS1FQuawqhhre4vPAiHWAaETJElGQ18QiCcbZ9vQ20M+0zKHfR1ksceleVK6Z+1x2vl8W075Hup+INdVII12SSidufl0/yGxh55dP8AoVBM3/Erpt08dIqYFE7ogABIJgkpC5FK9+C8Crvhz34qHL8TenMdg1xYVSjyRB6qZXTwmyIkcxpWEv5+eoiExcWYqSgNb73p+rKWImkw03+zR1UJzRyHDCNq5r82TPzYhkE5ljywSMp7Ldve6BfPFx9Dtz6X3hSYrP54poNZdCoVAoFAqFV45XTVZEyHc/fLxjpkUQ1BhOlTSyR7S5wWsv3DTU2I/ICvPoZs98eyL0B8E4s6ZvnCWIiujDIKDPF83xOFYju0kQFNRNEd9e1HmfVSPkxJCTeyDnNWQb6G8JQhwh7iO9WAsD46xe8fBecmqjaem/iELorHOQ86bNy45kXGeI/y+/F6fKIQBATdMkiGQ1/NwAdYPoJBAe2B+s/N7O4CclkjzthZ819caJJ89JH2eZRjkwCZdO4I6I8DhGh/jvHmhAAHCdA2VLrwmSwC+QhqGRABrNoxzY6o2/SQsYYRKqUehlQcUMvESgAOm6TqTcMeaWKjRYjwkyxAy7IGgiRcLG79FHL1Vw8BKTcmjXr5f39rEN/0wA8B3PvhmWkrQc1rQknfQgQXwNfLuTqGHJ6bwcbu+aJiygTRYSBLDt4hshG5Pp/vC2iTDTeFimZ54SUZH0cW7SDHw4mZgEAOElFWf5CZ23IBv9njzqaYyD8W3zvkTzHFIDMlkRuiVGVpCvSz7+aDRTnk7yoYCsKk5ONYu/0zyQUFSFuVe9Zbn2jtlXwkpYeLu+JkZgiXVajjzQMSoj74n87POPsraR0JJ5CCDKMzu5Q0iRdcBcW+h63GiLsAA819/vkXztJQpMEnF7wkpWWDgLPRdhUSgUCoVC4XXjVZMV0oDrx4gyog5/19SXZvsvvRCHUYT5culkxvCXQz9vJCFMJxOegPbeyoYyRXv9nIx6UcKCzFgeDykChOx6ySjJKQqaK25/d8LYARBhuKHh/SWolkD24nkbmwDbUFIie7OTh8/FJRfjw1942ciUJmp82cvzTU49Qa/jbYQRZt6+niJEsoexk5X4s+lq2l9+O2vO9reM/tQwWsPGakANpLU6qe5Ff1DCKOYgkQV8VQM0CJsxDQG+YnpffWkOhnQ37RDabd6TMR7LZ5EIGmkht+uQI0uylLi3DgAAIABJREFUhkAnjMtMZbEJXuaR3Mj3/XxI5wHmPvfffe8C00MehMKRbBBZNE10YpPRa6kQ1KHaGG7H34mkCa2LTFz4TzfAdusTEpFj9xnICIlOxiLO76NfLoi6jDkNuNMkJ9I8xxzu98kDoTluLycZqVHA1HCIdVwNaYH2jT06IKKa1mdCzEW6TszBPbLCx+GlWRkQcNyfOfULWXTS18TTEgAla05jnZtEIo7OQaAOS3+KdDZrL2v0zHs+TYTfG5soeZF1IzJJlomntHflQA5px+xeaLdkUhBsvpdSBFh0q8mqzRFRKjK1HvJYGIA/j4X0WebkAZOmAx0I8ptAoj777aS6fk7aRiIgSPy+nc+AJVIq5m9OPI2ZGiWmi5GJMSewC4VCoVAoFF4zXjdZsQmevzFmeD/MYLggEQZYwsfdoBxbehdu6p0HAK/c4VU4wsjzF3WYlsLlYOwKMM7Ts+hebCdKxr6q2kuzvrRpfIed/wCMLVUgifD/SQS4ERHe1jZTTSRX9nBS4srLCyxfaBpxcMMqGa2bwEP623tWA9ONSiBeoLUaiYqcei7/XKBpXB6jMkIE0YQ+pQHjgTFsjPy4oz10DBb0TTDeaqSFRsmYx/MskG0AJ0F7nOVSpoCmGWAkoCZobUCEVP8DwPW5TePWDb9khNBOQYJQt75uMg0iNiMmRb0QC9jWgXmAWb3xzPPaLp7ZO2NcmkawH9MNjpU/7hiEd78/CESO9y32bhg7DI3kAWZ0yaBpoB+1SWgamsOMougOHY47Guf5GIs04p0meeWHCIArpoc6Vdo5ilySOEFhH7U5J+TrmbVFzAut5903thfNFidM0jws97STmalbM2Tf5wFGZMrNtSINjA+kyiHyIIuRxr0EYJhOxEIc+PlLuo8avZIz0AZN8tJ0VEIY9mTpJQLgERD/3W1oEq1ok4zwGBNwIBJkrkOGa4JkAjVFqnjVnmhfMAnPK09CJmm9ZEKET133kLXBJOA20HdnMwCy+xKwMabnpQer7FcN6whh2yCSTER2TzfhkbXiW+IpIvuumJoiHvFx7x5H2mv+b1mKzlgI+HhmHZ4hh/YKhUKhUCgUXhsqTrRQKBQKhUKhUCgUCoXCB4VXHVkBFshXr+hWjcBBzxxRA+qVnWHAng5wk8ftApgEoBO6e7uHebQI0QZfrXzolUwI8tYr6qkjQohZdo8/D4Ddc9xMsPM0+9TfaLQAnbt6/SwyIsbngoYeos1iqRgpbHpvWvL0mTUH3QQrvVxkaDkQNNJjmwKQAKKUqjRNeeHr6s2DTWscf9BN8GOjIkjSYfBzaWg0SgSMnAj9vUUdvGnob7rOw0MHvdkxPrIoCfOKchOw5fm0prnqYxBkzNQTal2PAdC2od5h91Bvs1yJmGBh1tUYV1Zdgp1VX2MTbOcONk+0txWlS0lLjLbmkRXzWv59S/uOSDB4LDoIMT2ynuf9I6hnOI4bHNdwbRDiuRn3s45ZOoPamCH/z7pB6coa8TBoKa8b7W8ahSSnYREktriH/HkPkw+PekaIqNq83qnQEKVxrYlFBHHROsGMqEgpB6Ev0FJUiJ+WSju+qCci6VjrG2F+HtEU7u0ehz4mZ39ooCRveHyW9UnyYyv20Iwa8WoaMW4LIKCrHrikqXiQBcnsu0c15dSmpySuQILBmEKnEQUi4IduUQ7zVJCgsQ6AMPelR0IMU/kV65fv4RwpQTzPHaLqLZImMZbCoy7SmtC56z5OQpLRrkdGcLoHgIhs8r/jHiF7Vlg/fZh+XGsDQwjNNoxHzQEarTE8IiM6ab+64Oaxak9oA9FtRR8/JGuddJkRGr5vshjvsGgNj2ZLz2axtK7bKxQKhUKhUCi8LrxqsoIIOD3u64sjgLE1Ddv19I8cP7JpxQDyPHcCWpuhx2TG8LZ1iBD2nXF9f5ov/gD6IPS3qrnAV0SeetadWPQl3MAx8IWCrOiPgsvXO/CQdR8AOg2cHnaIWOlOTzkAgN7CCKGhP+MlGFrRhC8qTNme9SWWTZxuFYoz4+5YYQNKPoyTvhBTX428GIeVnOQ9hbSnNBHqEvnYrsGx6HZYeguR/mwXYPvUjOkTYX9L6G8E/eMBvNnB50k8AGpk9M4YV8bVSZ2eBBxFCaBu/bqSjc/bOAn4rEYZu+Flv+teMILJlf6bGf7OCfUWhMWw8PqxMyIraRDkwkvofw5Z977wZiRCDm1PRmiuUEFNFkPHw9jJzhuDQGnDb+fdxtEXQmcYOdefmxJhV9MrQBJyBDTVpglwHpou4H1biCfTJ1h0AbD+3sTKoAL98dbIi5Qa1zFIbYinO/j+CnFQO/BhgLYRBqLcKc0pG63Co0ILYblc023gLD6ZU8lM12EBrWlb3uY0QG0PXWcVD+ppmrxvDE0fIcxqMn5N74c3n/ayiqQCuNJ6Kx/IJ75ypK75c8lTFPpZ4nnVP9L0oXGsCsNpD3uqhRO9gpmy4adJGq+nDxmjE+kXeb18/Y8aEzY3C8mUdB5uSpBmkiffT0ifH/ebj8/FiAUYpwFqK2kjUMJC2IiWA6EThJMg0pr83xcnRGXQTZ+WKjI2N0F6DCixmKZEWCAnib0ZxJiVG36x0kmhUCgUCoXCK8GrJitcxZ9JVHQO0PxkMxrF8tdpm2/svAnYvIM3ecvQ9s7nHW/OV5xax9N1w3eHaguEEUTQcp480E+Ynu1LMiRSNRIX0gsH4oNo3j4B4+0Af/UKZkG/2Fv4uw1yZVyuZujuvAhD0lVfkMmMS7pq1Qt+tu870K4wAmW+60ubER821Kkz4cdaCcC2q2E0BFG6FcAixMgdWnnFiYr0cq4GhRMP8zrhDadDW6Tkh+uPtAuwvSf0R8L1PaG/YfSPRhAPgL6c8zPj/GyEkWh/MjHkOhxOuGSRv/4o6A8bxlk9zG7YdDfQcrlWAkRYL+3rvavBS0aQDHJSSL9nE5N0YgikWiRZa0HMsx1GaColO8cwjbX9bISB76VOQNd2RlMdg2yjdBMsJbsviPW+cQKknQeGCUFKs/3WZBpS1l9ijc7Q/Uh3PcOzw7N/Ohabc9NV2U7ap5HayPakWESB91H61PkII27MPvC5qyd8o/B65/4N12ZJFR4wKCJkQhvBogTcO54xsmF7HB+gGhI+b06YDJqVQXwrXXTzUceiOcGmrUPdDV3T3vH5yVox6X6WjebHA0EETA2O1IbYPZ7IR77O+3BsSSz4fbPPJBGVut/HlvrBJkJrF6CdECVW7e9F98PmTp85UyPFx0B9kiiuvSH5meKkkLV3HKcTSZT+Fm88wecgjk+Cl3mM46SEwA1pE8+GW9IKhEm+pfsnvndSElj0OhZxXddkIdH5aYDkiCrrA531XpLOU5uE9Z6QrKtRKBQKhUKh8ArxqskKDML13VmNhOS5IiCE2kJQzm2GAfUCC6Hbi2x/boCFxaMJ9ucNz+cTzg87xiD0pw3w1BIgRN6WKgM5zSH/dCMpQZoZxCQaXt8J+1MDvdPlOH/XPWQWqm/imtlgGdt8od6e1LA5GjfSgP6IeOkfDwiRRSEz8KOPTlZMQ1tYiYZxnkZ2LhFKls7iRExOEfGqGB6SHH06zI0bG+2iVU+crODdhEy/p2ko40ToDxzt+M/m4xaoSn9+52dEuH2O+IjokU3bzcaXJMHT/iCztKcJGkYkDZyoWY2/tbKIkReLgZnIBHbyKO0P0j0VaUKk/3Ojbdgc+DrGHrRqFWEc2vWuH7MGJNj6Oek0rEQszhbWv421qounTZnBLYM1TcTK7Ia4n10qUglwuBfsoEi/OhH6scwvIapAkM2Lk5A6ZwB5OP5gi3aZoofjqsQksSxkXMAIDmojIlOEJ1kZ/RYjJQCNsGqpDxst6TfMsqTqsD9vhNA7YfSmKQYR5aN9GI9mYe+8RAHJEylh6GlaVwE4pW3ZT4/eCMM/pc6wi/DStId17/oA/Z5Yjfww7AUqMEvA9j1bt1S5QiOwaL2fm95HACYh2ew/FqvG4wudJ3u24Yg0szRleS+JRZ1EGdx0/kwZSmOyOcnPoLiWH5fHZn3wqDJA111J3rlXyJ8TRqosZVH9d3vmOMGSx5TvhwgUy2kbMu/v/Ly6i4jGEpD/a25E/L3CN4VCoVAoFAqvCa+erNh+4aR59ZZ20c+yGl3AEnJLV8beBFEGEcDpE0Z78rddQHjD2ICnByUENtNsWA0wCsNQX6qTx+9Q3SNs0zCiZUYeXBl433D6lLC90yO394j+xuWSYTLOlibxkekJsGCcViPYrx+kQZs6FI4o+erGzZ2wdPf8H0Pc/R25Dx3YMdVFdQTSAA6l/paxDZrpJmGcqcHAF6A9Ae1ZcP7k5TZGIyuVCIxjtQyaBlQmDvgKbHtaJ4FVN9G/mxMZRnpopMQtMRGVJnxOzunyrPshkzSLgTbWv0HT+w1Mg8gJseHVBJhmH2INyXQSphHUnvSzMCgtmsNz8PePWPeF3zdNZoUU6P1CVl7Vo0Q4a1u4cZr2fC4N7GNSpgQAM/q7Bo848j7FfCUdgqXIQU6PEYsmirQra+8kSsoc4Jozw6u2CIAmN9IaORLCCaqIsmEBb9oOs2DI1BEBgNERWgvD0rK8ugWAIE/JNE88msujSzxyq19YIyyedc7bk43wmaZhLHfmeMy9MLa554QlCKqYa2d05bD/wsgnva4TeHY/MWESKnau7IkkdRKwwYx9imPnOs57Icp23uyVw+++JZIGjvfbCVA5kFROVkS625E887448ZIIGJCRHASQVZZiOm6WQz+DjJwExiRS6OZ42RIBwrM/Pt6oPmMpKes94oSJqAYJWRcSobKfBujTe8xdoVAoFAqFwuvB6yYrBHj4tr7lRZrCdscjnnLPtyclLnK50+0dsD2ZUWFpBGp0EvY30zDrD/bzTXrpFw3hBtEkTLZ5be8nCIuuhr9E8ztGe0/Y3icDlQ8v4hZ9HykW/nJrL7X721TCFPbSOlSzIsgJy7GennNEuLmfI1v2DKYX6Sz451PPqn9AMktiikwCKDz/olZnhIf7+ZZ6gE3z2OXEwD61PMau8827Rla0C6G9x2JgRNRHQ4g3ZtKI9hTenUK7fZ63T2kxvLKR4GsUYoqwtJecyuKGiZd8NTLH94l7ZIOsMG/r0ZvLnRZvN2+Yxk8yeu7B96pjMFQDJBEyi+FmhppYypCWj9V7ZmwNsomWqfU52N04tWiK5NXWA+w/C70nACKyGl+2V5sZ4FujxQj2qIylzO3hGiN0EuwzmfvRxxXlftMeB+b445kAJTaWKB/4fKV0rYOHe5y0D3tLezvYCiQr0jREGjA8Be2UIjug5EXbBjYXp3SNnGtTEuONCqL29/pg4yfWNC9fx8NewgaNAiAX7LW5YFmiXlQbR0KPZRF3HYiSnO2d32S0ps50J+xmX5xgjHsjkQc9rYffA8KzXPFxTYMwsTFmEjRSNjyFIxGgC0F8OE/YN4EN42R9nKcvaSMA1mi2O8TxIhy8fHlox3lzmedClNwiLzPL67kRAROpIBrdcaThCLT0I9oRfZ5u36s0kEKhUCgUCq8br5usMKMmRyJQ16iDGYqcDBpYasFFwN08kI3AVwny4vSpgHf9T4jQH5QI6ScKwbL9rUZwuBHqRm2E2L8ZVqXD+hkvrskqGmpMt2c1BkcD+sdmuJx0HHwlM+qxvNQuxlQTjI866GGE0B2ZMFv/9lm94d36vmNBfrk9Cg7q/MkU1ANCFwRAGDAS4dRmvHiKhBlyqudgbW6JHPH52dT7LJt69Ifn9F8QBAx9TOCd0N4rMeKGydg0nUFOk6jJ8LSFGJMbbfbZOE2S4m5YfFb7d+P6PA0xny/XAokom9P8XDx9JhM+3r7pS7hOCKW5zFEHOXoiqtz4Urnxar/LhqkRAWi0jhnqEXGRrB7dZ1PvRBpFCpCuE+L8IAQy6WUEzdIPpHMx5wuQKS6bjpfYK5bKAKzCkGawAUZMWaSQj5G6/s0XimogamzOfuToFJCSYTm9ILztZsB7Ko7vARqkmiA+F0EAUfQBQBjjEenE2slhQqXCwNXERtGUwAAwRRxFiYztzRWPby7YP9LJ269NhX53FfYNDRufQ9vbsgnwpoNPQ9NcnDiBPhfaNrBtHUSC3hlbG7EdVFBY01euH21wgpVTlZDeCddLspw7gd97ZRmKKKsgnNzoTnOzVJLJRIKk++1AbvqeoK7iwcf0uqzrslwD1pckdCrnfJNjZQGszXiOua5IJjOEYp/ekBVxjP2eIkH0b4p5cQLuhpD07qVnWhCF6ftc3ekYaSMX+3exUCgUCoVC4RWjXC+FQqFQKBQKhUKhUCgUPii87siKbeDpzxk4/xLj9Kl+1J4FzQQXwxt8WiMaeNfv+pk0jeAr8/vT90gjK65T1G5sdqzPFquntH80gNOAl8wMz9ZpTG9dKm0Xnn2CHs+AXFR7Yn8jwI9oeAefO/rOkPcNXkaPmmB71O9JNNd9/8RKqlpZybZZ5QcSDCZczxph4akMS241qTdyRoaQqfN7H2l6KBlreUFAPcMsev1UUm/1QE4P4TiLeRCTBxgALgw5DdBpqOJ9iCryLHcJYAxgd3X7pCMAlhnK7h7ZXLrUvcsWybKEzlPS8Ujh27P0qSxRC57ykT3ykXqwyRoN4KdZJRjXTaArLREtIbbne0J0bsJTbhUmPPJlirzOa+RKM1560T3N+1vbf2ye77GOka+anoGhFWRkzAgRnyPPr49opVx+1TzcIZ7qmRBAUKG69qI6GcPSTJCa8IgCAGQirzm1RSN2fHy6ZuOUIjaGzhl1CsFTzpETTY8JYUS/eKyjebhbigIAIM8czwDaPfpFOxvlIpMHPbJCiEBDozCi6saFlqo5XpXGr+f3h3jUxZsdIoQ3DyoI0d4I3j+ecLls2K8N47lF1RQAoIcO3gaYB87njmZioHvnEAIVoSizK6Kfd8IaeWHleU+nbmV5OdpqrFobrrPhlVeenjUHq3eea+qRVk3QkoIn2/2qfYiAGevfFC7te9OqTXaeY/SGftE8j6gWk9uxXCRuEueLELo9o7Qs9fz8CJ+LYcI3rj8SN4Tdoz1XhqLb++Emmi5FVizpKdYGdUydGMHN8zoHgBDS/ZkirJaIqfxMKBQKhUKhUHileNVkxbYNPPy6T/DpRx/h+h21XB6+Q9g+1VQPABCmKJ0JKPFwBaE/Av2NkgTypocBTE9NCY2Lhhu39zQrLXj6gBtrVooRgJZ1zNVCdhPgTHn4YYCeBrANCBH2t4A8DJy/9owf/ZHvAQDOraMPxrc/fRMv1KfW8aMfvwMAMAQDhP/78at4/+6M8e6E8b7hsqXl3ATYBsYjICcz4F1cELBSeM5ceEg5LXngNAjSoUbZlaMKip7CwEMHbUONtF3PD60KK/8oNNRofVBrThLpQc+M9iljnBjjow44aeH9T0aKVvUYM5wbAK5sBjhpqVahKBcKwIQl3djHbQoFVF8ijHC3P7IYqB2/EBopVYHMMPVxaYUUiuOEMausmNEtaZnGJgDTDAW3a3n6UuTfixq0oemQCJdIXbBwd9lE5w+AfDSC0BlOKgmSgCahP+teHRfMi0X/Zh/iWpm4MoM8R/K77RZd7Lp+ctJUiPGIQ5j+DOHHmH2LrweBn5xMw9QfiVSZFPbvmhE7LekHi6XXDsRSImaCgCNBfxhhlJKVa2UvsZBJrzSE0JNhURHQpOfg4JRa4KllTpqNk5ExnfCuM65vlax4OO84ta5lj1lwpWncA8DpvKO1aYTvnbHvDX1vUeEEgyDPjIuLqOb5AGKfgAWnhx3OW4qVxiASM/Z7XLfxwMdvVQWUlYuCCOHamx6PFYJJmlyvLa6hX1L8zTxAVrHFSQclMjr2E8ffgJIk0YSdv21jmY+rVVbSFBg9zgkJSvs9V4jx6i5X0xH5/9h7l17bkuQ87IvMXI+993nce6uri91mk5QgEvLIHhjmxAM/AE8NTTTVwID+gvUT/BesmScC7AlhjwQZBjy04akHFGQIMshW1+veuuex916PzAwPIiIz16liVzdZj77kCqD6nHv2Xq9cuVZnfPHF9wEoQI3Z6ZJOdgNLzAHGrHXZLrq466BBtdC8U3Tw9D4VVxQDK3L9rt271LMIfxJvgAm7nOxejv4ee+yxxx577LHHhxUfNFhBYPzi9Xt80a94f3MCAFxueoRHh3ClkhimviYzqQfymMHHBNcnHI4LTuNSqofMhJQdpjVgWQKmc18SKJpUfO7q5N+za6wJNaGHJMvhqj3F1tvsq6ZFGh3i6wh0DO4j+uOKn9w/436QRf/oVwSX0fmEKQZkJnhinIIkLsElBJfhwPgl7jF9OqpuR12cpoER30QRsDRRvUZQUxREXUmamAXQ4KYiSKsCLqtoa0j1XBftA5AOVIGeAFls1+FQB4VcLCDbCidfA8KzQ/cgFec4kwBHR2WH9ApuLDrW2p/vr65Wq2dxLWjF9lzjKCLJX036i5ZDU4203vTcoYJSRXcDtWJp21E9lokEUpP4mZ0roOdB8nOjxeDa7anaIGrivxHLIxTdiNxTBU2a5LgEKaBGQOrlg3ibBbxoEyD7T8cl9wx0EJFS1mT/BbNB5oReS2wqx1yvc/N9QsNEEr2H3BPyoOKKXW620Yt3CrIYY6BJABM5mYtamaYkQAsAAT9yBQMR8gYQskNYMk4+l2SyDTYQIcs5o+Oq1zLIdSUDH1+Wrds5ZcloAFi1cMhAJBK9jM1YQvfHXMRMXfTgi8Osjg7zmNEdFxBhk8D7UFGQZQlIyYnVcpLneaPbksUi2OZpAeKaucD6cz12RTTUm54Ji4DrOuTK3vGMcCMX2Q9rSeBj9OKakqkm+tltmQo23mUMFVBth9YzXKfvBM8qX2IbKdNjwx7R+xS9AnjtAVC+w9mVQ7sXlrSAgB12Ld5nZH3vOQgwkT1tgRbbd3ZCrIhQ5gvX65KrkME3QE7BVLPTBirIWd8TXEFvvY8AQD2QW8bPi2e21SDaY4899thjjz32+BDjgwYrYvK4rD1+dvuEXlsgHsYDrqcB6+RBiwP3mhQZ1XnI6A4rxmGFdxnHfsWhq9Ygh7AiM+EaO0wx4GkYsK4BKRHiIMOVXAc3aRJ/dWWRaCKdflJ2R8QGrLDEMh4I8Y7gTxFdH3EcZ2QmPMwjAODqO5y6BY4qhXpJHl9eBZAJLmMMK4Yg1dZ4BbpHKsenDKSBcBk88iGrkB/rIl+TL2tdsUSmcT0AoMAANW4cTXUd2hpz9Yg3moCGpuoPiA2mJiqcCSk6+WypbgPhTAgTlLYvLJZVWRH54MpYGmOCYmMjq+MtzgRcmQgvckinC//kAQQg92UIqk2qikxKO0fdgVm7tm0jmwomADehOMgUi8eWecH8tSqqtXAQAMQKAFnrAFD3JYwSzWWXCpC0J8EKoJA5JSSU8VjPHhzqtZg4pSX2uWv2ac9InSYbIMhaaYQpUo9f3CAKW0CPZefJAEjaqHJQcCZUmrqJZRYHBLt+qttb64pYpyqYpsdNgyRzBoSU+dgAb+wYUGHM7LZJnDnauNnVdhEn4q0l2tYXBT42YWOWCQwWwMJztdHtFKBR0MzaSIr9qrW+2OtqBlxy6J5kB2lkxFsP7jLcIcq4OwavMsgpOrC+8/zVyRy0lp/mMgpWmetcacVaLfKzL64bQe1TbR7kzpf7lj2wvJL34vU0wNqYKGnbkjLMALnWUObnN+AI7fvD/uSB7IXZwYGF1WbtYibOmWkrXpx0fLU1B67O96Tzwhgt7BjppegngKWTviYKedOqQsSFYVLOsQE+7H3H0SlDB6Xlr1yjzW3Sh4Ug/0+c6lfgoXNOPucWILPny4A9QJh8jSAwrYR8/Ia+tD2+t8ivTrj8l3+K45/9Xz/2qfwgcflHfwoAf2eud4899thjjx8nPmiwgheHv/z0Nf7kF58VVkLnMt6HhHnukKJH6CJyJnitMnmfMXQrOp+RmbAkj5dr5qzsipRdqeSFwDA7jciEFKRdhC6u9iE3CR57Xau2TgNtOMbhOOPQr+h8wuN1xDx15eOuj+hDKrTjeQnIqfZdO8e4P10xLZ24moRyeiAW15P+wSHOhDw4cM/FuQPQpDOpqj03VXA7vRXongSkMHAgN9amYRIwxk9OdD/UkaO229SMmpLoIuSuakA4dcJIA4o+CJ1rCwcHLwXJqKwHu+dNsp68bE9M1Y3D1WsxjYLsGXlQer1WhQFZ0NNKwoBh23eThFy0kh/rPRWGTP1OuBAo1Eo13EvpCqrn7V4kaQ0QIhaj1cK0ddIorQW2WZvIK8vCEUAKuLgVcCuXcQZJW1S5xoZxkgaoJoUwN0obxYs8pzifBC5VZjsXF1H1GwqwULd1GUACfJQcrLh7NCCOAWFMcr6bthyuY2f7d7HOS2NR5U6SwtSJHWsLsjDJdVYr2+baSJLW0p7RjpHlf3Z8Sx6tjcqq4fZcaYK8OWdgyxhwAHs5qWS2rcoacask2S4RqNHNcYu4gOTeIa0OHOo8BiAtVbNs72bagGf1S1xsO4WFUwEouwZSkMMvqMBQC1Ylhp9t4OQYxrxIQwUxWhZSGZtig4uNnW8FUOr9NQJCO4as7VIF+HVNS17zEndR5oNf670r1tYtM0mf9+Iug/qZPaeij9LYvxbwqQHB27YO+xldAenkhdCcYAt2tSBLaVuSa6IgrTDWZsLNMZgJoY9gRcPS6qQV0Q518fB3C/bYY4899thjjz0+5PigwQoXgfDLAV+8OeFulBV05xOOCgCsySM4EYwz5oXRhdfkEJNHTA7OMYJSgZcYSkvImgSwYCY4l4swW383IZ0cmIHl0oMXB1qqZsWmeg/gJRqSO4Y7RdyMMzwxYna4XnukJ+0Njw6rZ1zHVFop0uxlAWzBYmfITEivE9h7uMXGRS1RZ6CfZaUr7Qr1XFySBNUSy5I8NGPrNGHJnSS1eajf8bNsFy4APUPaGZrTM1tQY5e4lZFvi1ieAAAgAElEQVR6KvT8eATWW0bUym+4EtyCkgi5c93XeiP7iwcW3QNbk6sQZaniJqugN4mAJRSd0Mm7kOGaXvaUlJquFVHyXBKIefICVqyiX5IG2XcVSmVEtW0sVpKN2N4mWbTkqOPN30xHw89UkqRSfYWwBorWiAlZthoNChCYxgqxtsdoAhmuNfkr2h0R1S41VZvfvAq9XT6w40ubiNj86rVvEn1We1qqgEWmjUCtJOJyHpYEwxJjbM+vVNcb4IuygSlbUM0STGuzMRaNm8vtKdu3mhvl3jTMDpu/3BzbvWxTAORZMrBCxU/LeBG0HcXG5sUxolXz8TVmRmE6NEwIDjVBZZI5IvogThgqzTW4mSrYQFwBzOZeMpR14rARhK2Csnqvcn0OTbS0jKfeW7HOlPMyJtHGKlPHwu6JXUMBwdSKkysepOMg86a0RTV2y3J9jByoAFwcaAOiAvrOyfr+aphtFtvf9Z3dAJwFaGMBrERHRP9kzJ/OI3sA7oWVr32Hq75K2zbEjuXaba6rrTRvwCx9z5iujOeNiCiX/4FqZjBchy3QOnmMY2UM7vHDhTEO2vg29sHLbX4stsI3nftvus3OsNhjjz322OP7iA8arKAE9A+E9+9PWG9lBXo7zgWg8LqwHEPEsZNMfs0ecwyY1yDsieSQEhCU8p0ZCCQaC60avinhA8BxWDCGiEAZj7cDpqXDde4QVxnOuDg47XkGQypexGWxSSHjdCNMkOsasMSwUZynmUAgZF3Yp157nNsqHANxCvBjxPiTK6axr64cDLizR/fk0J0r6FBcQSDJau4riMFrTe4ArUYOkiTGEyMeGXnMhWpMC8FPhB6iz7FJPPXeSGVWK9FaMbekIJ4Y8T6WqmTuHfykgqZQMESLlrlTGvyrBDpEdKNkMF2XCigFVKE962mPUbKhjRsC13EchghXBEOBGGU/2cTwDtrCkhxSdHC9XGQr+BdvApy5JDRiqoCweKSnnsBJwC3X5Y2IH6uo4LqITQWZ0KsCKqFL1e0E0vfviAvgIvsAcvJYZ1/EXZ3qq3RPVNxIDCSw6jbw4p5rcm0ij4AASjwmoGOQz3BdRghpoxHA2ZVrZ72mNHvQ7MpcMfaRn2tCvGE+uHq/yzyy6a7nbAl4DihahIDMb7AmxlrNd6kpdvvm+tp4wZrInc5Pa4dpnjdrmbDNOGmi2wJTJnRorIIWeLBWANUsYWocVOwYmsAyKWPAKfBp16ktI25BYUPUm6fgRqh5cAFzGsDDAMvCamjBt+aa00gFJNsAbITiqmPn4y/2zFIBKUACZiQbG6ACog2Ik9v2GlZGScNOaR1XTDy3gCcGRLzQeEkKHlBfgYt2vm/cePTnxvWnaY+x9rACkhkjyN5lJI5S2/YlaubcFvxjxxsQjiJ9TcC3OAwpgJEdK3vMbqR8d508ChhrrB/oOyTjGzU19thjjz322GOPPT6k+KDBCkCTn09HPF3lUvJPHO6PV0lCXUZMHt7lkpRmVWiPClQA+JqFnWlFjCFW2z0AycCKbsUpLOh9xKmbsRwCzmuP8yKZxRo9hi4iZYcleqzRw/t6DgAQfMKTtn4k628uYn62OpfFLEcnC1cT07PFrhOLwTe3Zzx1sSTbzjHm2w7zsUd+50s7x7ZKTCU5sySpaC1AKurrDSMPjHxKcGNEF7KcKwSAWa++gAwuymLamXtC6Zmn0iaSO6nSA0C+SfDHWBwqEgHpiCpSGjUpssSgZ6CTZHkc1nKfgjeQiQqIYONgFok5EfLixekloegIxJsI36cCbqToCsAEAKFPcF6o2BwSvGesqy8uApwB5+Xv5jyQkiv78z4jZ1JXBkkeyFWgwcAMZiDr7+1n7dxMDHjPcCw99Pa59dADK+LgxdYyu6L5MR90LIz5oMmgiVM6S8zU6pCDOORYv7u/XRG6WIA2s7Zsnx12qZAtiOScYuexFq0BV47PoTIISpQEHsUWtWg5QOamgVimt5FCrSSXNgZrH4jb7dlahOxPJlhon5MmkZ2AcxbUCMYa0FdbFWhj4dqyB2SD7TUaMEHaEkN6jaW9xtX9WOLathxZSxMU5Ckip83xsyWtOmYbDRFY8m3jSJUtU1pVDMRgEea15Ngcehgb96OkH6eTglKzK2PLTi1qKwYroIeDsHHsOaOa6Bdx0pZp09wntwhjzJxznNoA5443bRyltcNp+0wDGhUxWK5zYgOSMbQdpxnHF8DZhrWh+zKGhp2bMTG+pgeiYIdZ7ALNfuyrjkqbUTnHXOcKSFlxtlunAIeCStzJPbg+D9jjdyNaxsJLBsI3sRl+E4bDd8Vk+OuwKf6q/ezsij322GOPPb7reNnVvMcee+yxxx577LHHHnvssccee+zxo8YHz6xwETh8RlgvUso6d+Ko0fmELiSkTJhiwKKtAk/TAGbCdO2RopOqeSacrb3BMaaQ4L04hQSXpbU4V1znunaYY6jHcQmHsKJvqAneZVxjhzP1YKBoYgCii7FGj+vDCCwOtJJUxaxqOSRQl6WXedZy5wshNtenYpuassNpWDA7Oac+JNweJkynDg/HI+JXPcJV2y1C3YdV0o3a3dLS8zGDDhG+yxj6iBASCCL0CSjVv8vAHbAq+4M8VyvXtcHBhgTfZ3R93DAC1tUj6hTMjuFDrqyEkIXFsHjgrNz22SHNPR4n6yWhTe8/aRWa7dimpRAJYSIRYmzYI/HZSVUWWk21CqwxL44Z3HPpIV8B0MWXHn9mgHvG6uXeWdW5nI/OrdIKpKea9XvRWlDUIhGQdg4AWFVwgC+huBesnrcifIBYO4YMcsLyiKsXtoWN86lWwqVlQyvaq7VouOp6wsqAOSb4Wykth5CQk0NcfBXwa11kANlQ2xbIMShsxwHQajqUws8tzQFi8akV/dxzqca3LSpSzabCAhHRQ913a9GorQ65bdcvWhTN9xrGQank23fIWB7YaoNYRb5hhrxs49joFRCqvkfz7zIHX3ZyGKvCoVgIt+drpwCH4hzRtjdY24T1gbSCp3ayfq7XItX7ppWFTCiXCuNK3hE6VyLJ/QlctVOMiQFIu5AxKYyF5bnOFV8HndvBs/YFOw9unx0qzhspqvWxtpqUVhgTO7UxVfFRcqzf56L3U7Ypg4mGotD83tiOyvNR2TuUhelj45p73jJqVEPHNEDkuO3nyvbJ2ubTtM4BVbPDWBilPahxPEpDe18h7VEqmivtUgx81fQQ7fE7E98lk6GN34TV8F0d+9v2vzMs9thjjz32+K7iRwEriOjfAXiCLLkjM/8nRPQGwP8E4I8A/DsA/5iZv/rWnWWgf6q07vWhw5kJrk8YD6JTcZ370r6wPusCbiW4xRWLw+hNQQ1YgqwQn9XmlFm0AnyjE5CiR04EHzIOxxljF3Hq5XiDb7QQVDNhTb5Q56dLD14d/PugbhSyeI1Kp+YxAT1K4kST21CFOTDyKgnjtDrE1ePV3WXTzvJ6vKI/PuPLYcFn4Q7rJEmvG2RF7C2h1AV5StJqYuKixyEiuFxaK0z/w64rMYH7iLGTVXjKVDQ+AGBNvojCdT5h6CLeHC4FtHlaBrw7HxFCLhoRh2EpQqg3/YKHacTjecQ8i4Bp9+Dhp0qHdjPBLC9BQOqthaBODwMWzG5zo9sRJUMtzhKW6OpTkQYv1p5ekjgRFKXSLkMJSL1+3rM4a7QODQZOBAaZmCDX5JqbBImNHm+Wlqr30D/Qtjf/hWhi9tq+EBjrMUtC2UlCCQA0KKhlIIWJO5roQ9CkkLiAL3BA1udlfu7gnj26qzpVMIo7ioWNEWuvfepZ9tNoIbx0bLAEzD6Tf+t5NuNQxqcFCiwpb8LcWqC7oW/ijCmI0AIP7bkIWCeACpmeQ3OcKowpbQ4vHVM2gCJkrDcODuaSoX9L/QurWgNMTKchAd5A1GTXTzUxR51jUMedAvI0x30xBPV6Y8Vn7LuW8LJDEdI03QUR5aUinps7AQnSqJt3mrSHZtwIFazI9ofmM0ZBKVpAqWj48PaZQuACgBSQ6mtiJAAiyXknEsCo1ZQoIrYV8Cv3jrABVcgx0sYWVfYZVwGZW0HYclmJS8tPNpHZ5nVr1q1F90IxHzJLZm2ZgraKMG/3z07b4rjurz2Gn4G1l3fHHt8e3+l6ZI899thjjz32+E7jx2RW/BfM/GXz738G4H9n5v+eiP6Z/vu/+7V7IBGBTCpyBgB+IlAKyJ3H5Var/ZMXtw4A3ZNTATVxn7B+9qIGn2QhaMlPPHTap05YVW+BWPunWb77dOjxPCZc70UO3+xIxf5UFozLEpBUS4GfA9zsEM4ifChWk5IIA0C8IcTFwXr3uydJFDf2lcGJnkVgrDcBz11C0ETfJYfMhOASXo1XvOuPojMQHbpBVs+HYUUXEmJywvRYPYZhxbHXirrLiFn2w0zovOzbABkTtWRA7VVVL8Ob+GQuwEXMDiE7rI0gQO8SbscZjrhogXQuF6tWB0ZMTvQnsjAk/CxinjYQfkKT8KCIeJYx8lYtln8nbeFuHQbMmrUkEKjJW7gC9Kz7clQFQ236ZSB4iDuBaoC0TgOWRKSR5NgKDFmy6TTxfGnT6WZCmPQczpL8lCq3VldJs2BWMb80ENZbqSLHA5DHXO4PilMHieWlJc3lRG28dH5PDu5BLqR/79A/An5mUOKSTLfjLG4MJI4aAYgjIY3Aeqd6IocMdBlMDkgQcUq31ZUg1OQYWV1j7ByNbRCETWHJ2kazQgEM0mp2m7DbvS0Co6kRKwREgNQYCdRoIDDVy1Sgh6nu+6U4KbGAkyAGGwJjO3gJZEDBp2YAWnZEq40h54jykz1VQKP5ezuvN24nLzQpuD0Ob8/BrtWtYk+8udcMOAIwoYAI7An5opc4kOqSMHJwxRK0Had2vLZCInYO20HdgLRfsxmtoFbR5UgEJi+Aoo6jiY7a7u15M2YI+wp2MwHcZQEs7HlxKEylAiz2Eaz/n1Jch3SMCgMnyy8b1xidn/V6m/FtGDBVz4Kai6ubGQMJAJBoI5rrIoG7DDd/8MTJHzL+5uuRHzm+b9bEbxN/1bn8TRgXfx2XlT322GOPPT78+F1azfw3AP5z/f1/BPB/4FsWB9kD158y0sgl8aEkFF0/EWLuwJ4Rrq7YeoZzY71nNvQkFXJAFrdpqKJs3aMyHwK2to6si2BPSDMhB4dnXfBehwTnk1iiKoMhRS/2owD8VZ0vJkmy/AyEKxewIlwJ8dmXxai/qoODHV6p8G4VGz9/JVxuR3S34jfITHg/HfC0DPDEWJdQRCaNdEAEdApuLItHXAKcY6RQ+yTM3tURwzsRjrzt5RhZnTW+ON9gWgNi9HAuFyq3cxlJwYa4eCx9wGXuyueHfsUQIg5hlZaQ7DHFgOsiyMLz3ON8HWTMnDiRrDdyDyyxMKq6MSm8JviWzJiAaDqIlWM24dKmWp1WWdznwNVK0QquV0L3BPilVjGtsmzHMSV/P+vxzfEBgFdgI64qDEjWHmCAgyT5rcgpgAJeAZZ0EjgLQOESb9ghtpF8n8BBJqexeJJ9xWjscOCGlsCOa6uDnnv34DC8k+/3DyzirHouOcj3DHey58QlBkeAVwEh0lyBlTUQuAdoTMLiyBCrWGvXUeFPdlQS5dw4UGyq0CT3sXWwkO/o2DZJfMnxTFi0ods73gJbhXUig1RBMLT7aMf9RXRo5ojusN2/Y2VHUFPZb3J1x9LWxPJsVjaBglI23iZgq6yNwvKxCrsl0+XE63UJG0Su26r1L7ECA3VorVX/l6BHmav2nBTLZDuHxt61BU2alhgDF7/GTjH2iw1hcx+z2cv6ZjzsvtgwatIudsn1+cwNI8nmkc1PbsE7J+AjBwW5vEcOXJ55qDgoD7lp8WjYLkzgPhdL4tLe14KokeReAyIorK4fxXXF59KuVWyZM4GN7cEm7lrnWFJAFwB4xpbVtMdfJ37r9cgee+yxxx577PHdx48FVjCAf0XSqP8/MPM/B/AJM/9KP/8UwCfftCER/VMA/xQA/JtXyH94Rd8lTF+pVsU7uSSKhPAMgAhuqeruRvm3xT3lWqEGdOGqC23KshAPZ0Y8ECyPF/ozEHVBSyvgI4EfVX8heKTAgGPEU4QPCWlxgFbi3Exws/aPo1ZNwyTn4hdUJX2lAr9kf7hVkkhKjHAl+PcBawOMLHMAMyF0CXH20rMdCVkXyVN01T1jCeDJYwaKdoJz4jIR1clkTaKFMAYp3/YuwbuMJXpxNEkOXR83fediC+uQV4/kGessxwGAy5gwHFac+744pzxfByxaDSRNqFzI8GOEDxn8BliunbijAKIfEYVxARZgCUBplUiDAFl5bJLbJsmSnch/NFp/AYo2A109cudF60Kr1rkH4oHL74V6faVSiXax7pyYi2WnbNQcu3GAKACEAgHGFFpP2FRq3dokhajzw+YRLUAAVSAOrtggstNEydGGAm+tCsQEWgjhTOif6iDFIxAP6h7TafXZ5qK2hJhFpFX4/QL0jyUTx8oAn5LoWVi7hrEbkjj05FHBitkVZoiemCSxOnf5kDZMBQZksigQkqFOEba5B5C5ujroOL907jAAps6VmjgzAUiAa9pU2FWtAtb92P4KENBUxdlRYclYW0i13JRWoZdaCuU+W6KfuTpcNAelpACGggPmHGKuEmUoGzCnvOvaFgk9sDiFtH/TdiaugB5l1RDR+e6MMZPq7/YOs2vILdNCwYiXoBBs2Lj5DgBn98faoWy+NVOlMAxYADT7vrO5Q7ofqt97ORfMjcPOjT1VlxiTbRmogotpy3ozhgkgDIjcueL0JBvXd5a0n8nvhQE2cpnzLqIyaexecwO0WGvSBhh78XOPb4vvZD3SH179EOf6Qcdv4xjymzBFfp3Lynd1jL/J/vfYY4899vibx48FVvxnzPxLIvopgP+NiP68/ZCZmYj4mzbUhcQ/B4DTn/yM/94nb/E0D5jeCVhBDQNBWkKaqiM0qWoWqYUqrovLNGpFvhO2htkg5h4gTTjZqpdNBZ/QLNoXp3RiRmJNwiYPN8nq0StQ4da68GffVM4S4CJL5REoyU/RKVBGB3sulnndMyEfDCzR2rljpFFFRENWDrdGIsxzB0csnytVORoQQAJYiBUmYVUQwWxBvc+4GWdp2yBG6BKGPm7sWaNj2Q8xuj5i5h7ZqNOzw3QJmLpBxEKZBMgoonkMd4zox4g3t2ccuxX3/RVfzUe8v8q9fv9wQl4dsjJW0uCqxSOA3GfgIOKeRIx4DSIsaYmDZ/gxwbmM8bAgq52taWikwWP2HeKzK2043HGxX+Uhw1gL8USIV6esCKu4orQYWPW5FWfMBnZFAc1EB0BaKNJBvrPc54bGz6JxsjRJtEOxeXWrtK74qbrcUiLkoepKgEWPwlgmJrAKBhCpJMLrUa4hHoF4ZKRjFrBM50lpwUgk56QCrW5RZtMsLSw237uzx/zaIZ2ybKtClDIQqFoarC0srZgoADCDswOtDjR50FKZERxYgANjiTggv0zUWg0F+1X/Vp7BhvEgiTB/LYm1F4aL/AJsQAWVrO1kw5xABYxsN80zzcQiFEm8TTJLxV/vUyZwR9V61hgG9vX2+prWJvsOM4vwrF5n9vV4GzYHCbDQtjUZ0NBajRp4BgBstqirzumXYESGXqOeD29/bsIOYcAC9D3bCIs61fH4JvaHtH9RZQE1fy9AteGTrXWoAYexAQJXwFPdHpA5bde0YYeQvctRmFgcXtwXAzS5AfharZyRynVvWpi+CegkFK2YVrOFqblxe3xbfCfrkZvXv9jpLHvssccee+zxHcePAlYw8y/15+dE9GcA/lMAnxHRz5j5V0T0MwCff9t+Opcw+Ih/f72DV1aDvxK4Q6lwF/aCCZ3ZwtQqal6qnS5Wii1llL74fJSEk0NdTLYCi9mSV1cX8X7SfWSAFicMhydfqt2lZQAold5NtXqFMD2spYAlcXK64s0BiAcAh1pVdQvgrrKCZU1A8pDBidAdVoSQNwKhxobIRBjGFakTBxRbk62rR0qEnAk5e/A5gBJhutQpM9116PuIvk84Dou2i6hOARNSdmAV4CRiOMd41u3ds4dbCEwOHFTvIjYJaMeF4eCJ4SnjtpObeVmlBGluAZbY5lveZD4UGGFcMQxR3Euik20s0fYiIOp8lnaV1SPOoSTJ5Bj+dkXyAcnOTSvKcpFUjsdjRuwZtFQGgOkvUBKwougPWHLnassHZUkgUw+sN4ys+hp5sLIygD6DiUX80m6lZyA60WC5CorWPQNukfPqkwBtqaeS9AggIp/HE5e/u0XEQ+MRWF7pPLhPQJ+r04o5gbT3qVfdkiygiR8cwrWymbpnRv8gmizrySON2LZxlCq0PndjBk1u26IQBKiR9imHcKlMiHjSNp9etSZC1bOo2+vzaQCBnqtcBAEGImVl0VhxWzdnZUoZ4wWOtmwKDTJtENvGW0W/OTZ9w3ZM3wxUtCiEiloUPQ8PYXDZebh6HMrCknkp+Jr1fWeaPMJ+4HoMCMhijhUbcUsnzjkF1CFx/yltUSTj7x2BDIh7kcjnrl7TS52Nr4+jArNlrqG29ygo+TVtzcJokX/mnkvrUhnGBPhF2UcKemwAFP0v96iAQjudGoDEPnv5TBuzxRubh7++/3YuulSBsTDVdpf2u645RnGOofr/FZUZhHJte3x7fFfrkT1+s/hN2BV/Hf2N39aJ5Lc9xu50sscee+zx48QPThQlohMR3drvAP5rAP8PgP8VwD/Rr/0TAP/LD31ue+yxxx577LHH343Y1yN77LHHHnvs8bsdPwaz4hMAf0YibhAA/Atm/pdE9H8D+J+J6L8F8P8B+MfftqPEDp893+Ly/oBBxSn9DGBFET10yqww5gQgAmqUuTorNFReN0PZDOruMGbEG/mb0yopTVJJCxdxPnCjqrM3vfhGzwUL4yGc1clCj+FWofqnvtUgMKZEUxKj+u9wlZ9paKjCTeWv7W83EU5OhBAyTuOCU78U5sRjGPDVVzdgBrpTQhfEXnReZUqsqxd71lW0NtykrRDGOkiEtA6Y7h0OpxljiHh/HeGL5kUtqTpCEc4ktU7lSTQ0KKNaG1JTyU0EPAdMq8OnUVpZHu5GdC7jSdtAePIb7QJ4bWnJbZlU2lC8T1gHjxTyxlYWEI2PNRHyFKSir0J37Bjd7YLcZZkrmUCLg5usbOsqi2XMUnnuUYXygFLdTCq4B9dW9I0CrkwcQAX+aoWdVmttkZYfGl426XOxH03eYVVNinyVY4Spzmmj5WdfaeyUSNxvlFLPHoh3GfmkzjLHKH9PTlgVs7A4yrh7rowFJ9eefEbu25tJGB4Y/QOjewbWk4pp2m3SCrzpJCz3XltbyuaIRzmGuOMA/fs6RvFMSL08E7mTnn+zdiy3oRnTEoW1UO+BVfuLDkHRMREdh9xpi8SLsjU189DaCxhULDy51RjhWhUvbh6M4mZTnEASbYRGrW3NmDDsGi0WQm0VIYC5vt8qPYRkbr6s7Jc2jcqwcAt9nZ1iLSytZofqKQAAVMsnKSXFaRtUbaVhdY6px21dLFpzEGMaZXWAAfR3Z0wG1XNo23B0fOs+UFxjWFu3xLaXkCYnukAZ1YUGst/SltFv91VvMMoYFi0kUy5WVkoOxlbavpvlOrnoYljrRqLGAaR5xE0DpDFS2uiSFG0NVMZfBtDPVEVF9/h18Z2tR/bYY4899thjj+8+fvDlDDP/WwD/0Tf8/S2A/+q32dcaPd6+uwFmh6Q9+P0jwU2yyHStgKYtCgdJaFykKl7Z0JX9IloVboX2+lNZeFqC4pxoSfhJtyPVGRjtYkgWxx5AksRQevmbk3dAHIH1jotdZRVFFKcPsNL39ZzHt5ZMNItfTTg2fdk9g64OtALIHvMgQEHmrWwFzx5gYJo7OAUhplm+G+cAnj1odXAX1bGIKLR2ykA4O0TucFGw4fLVoZyDHxKIWLQoGMjPnajit0lu2wrwgs7tLg7h2YHJIT8I3/vT+wHd3VxEPIuugSUUTsUiX9DaGQAR4zjKAAdth5mjxzx34ETScmKU7WI/QMjZFQFKrFQ0I2wM2JHMPUa9tiYJLsljLzeMAiMbOJMJCBkuZGQFZDiKJgNMY2V2xWEhJ6rbloHSY3sGPCO+Skg3IuAKiLNHmCr93YAto5SHC5BXAVLSKIlhHnJplcmLBxZXdClM+LUKfNLWUlJ1I/IhYzqogOvRY70h9I/SEuIX3lD1i+ittkOROiGY4CxlATjioWpv5A6bNpNAklzmjuS7x2ZqZUn6pQWhJrytO4XR88XZRFpBiOocAgGk7RdF58PusX6N7H5nuwZupjWJZWpgcXgIvHGeqWKStX2GsrZ72ZyyFgdNyEWXZgvWyXea8wNAjQqo2XVu+hrsu6W9iZHNsaYBG4qlJyrQlylvwDcAIG0fETFfLsdi1RMp58DqYmFAgI2fHKDcE9OIgQd40J6NlSqQ17wDWqCQFpJtHMu7Byj6ODk4aadigBZXNY0Wm49qb0sVYALq+Nv5mshu7ptn38aZqdgit+CDnWxxNgksoHU5Byp6RrC2kkZfqbTdBGlLMpcal+p9CI9AMgeTPf7K+C7XI3v85vF92qx+3xau33c7yN9UNHSPPfbY429bfNC1F04EvgRJ0j6S7Cs+d+hU3C17gHupYBmQsNznkuhQInHSmKozR2t7B2if/R0jHbhU1NcgivxwAlj0j0C+ENIon8eDJmykFdtVFrXJdAh0ERlvGfE+wZ1WrIsHZlkFx5PH4TOpLK63wPxRRj5kpIOVGKFVZkveGGlAqYSyZwVhCARC5B7Xc4erO5Rkww0JdBFb0OW9nNjUZ0DFKmkl+EXH5kpIB9mnykggB9Fh6J4c0tzjcvUIT9VuNR29nIcmAsMzgb2v4pdeElrudRXfaaZqeg9ZtD8AIKuAJHFA7DNuX10AAMMgNylnhxid6mu4wpzITOg6TU6yuMuJUWQAACAASURBVJ94x+h9LXOm5JBInE9y58AZ8DpGaZUshUpyJYm56RZYzzzfRLg+IV9NIa8mejanTAiVmworALguox8iciYRAV2DHNeApyhWiSiAlt/26ZtooxdwjI8R7q424U+nDm5yknwru0PcSZR5cW2AB70eWl29D4lEaLYBx9LAX9MaKEKFZo1JDH+/6lyIuLzyWL4KOHzRVNqNPaJghZ9ZxWWFcWRVcz8LIAEirAFY7hnxSEX3pXsUkNFPsg+3iCbBhlnhtdKstppw1dXFKvfsjSRgzhxUQB2KgE8CcqYe4E5z61a4094dkYpN6YYEk5TVlRTkaZJg6L4ssTW7TWpYQuz08UoAPLZMp0zKzJBx4heggH0H2RL6ZhKZGqtnBSyoAK1MqIK0gUH2vBoA0bsirGmOLQwUu028OJR8jWvCT7kAH9W+pRmTSBXUdAw3JAFng7KaotuAFQZKgABuqTStIwqxsJGc0Jm4YWNxIPDsNu5BX7N3bYA206aog6zH9iyAi70LDGgwdkgieXZJQAc+5MqyiQIOko6/a8RkgQqeMKm+iz3XrSvLM5V5vccee+yxxx577PGhxoe9nLH14ZARRkmM5o882EmCzEESkjwy8q2s5O4+OmPoItboMa8B14cRdPXwxh7IgL+Q2oKiLGbz/Yrca6atLhDL1WP8zKN/BPonBqv7wfxKq88rlcXrepex3srnQlEncJdBY8LPPn4AAES1MHg4HzDxDSgSlp8kfPSHX+HV4Ypf/uQeALAuAXEKoKcgjhRdloT6QVstVhFKtGobJVdYJJYA5iHAr8LYoLMph2IjRCpghVbhNckzocXcM7InhIngr0C+BEmoyqJakgg/S4JkgFAxgOiBdXVIR3XZYKigaF2Vp0EE+6xNIQ0MChmvj0JRueln9C5iSh2elwFTDFijLy0oOTuM/YrL3GNaOjCLi8mkzApP4lbSdQnUx5IPBAUz1uRFhLQTIGTVDCFr5hH6BHKM17cXeJfxxfsbsYFVwIdZWA+0OnFgSFTZGzp/MwJmBSrIQZgTriaFWZkY5rph4q21UqvggCa/qXdwhxVdl3ScI1J0iIsASaQCnetVznH4or4CWEURw4U2bQ1F0C/Is0R3S3FM4aTARhEPlYSVVictRJBqtn8VEY8R52EorA8DrgqraZbEjJK0faw3CqhcqlAukwiQ8pCKI0Z3cuieCeGCaumrY2/nLwdCsSiWirWBi0AehOnDAciOwD1AqbYg+Ku6ukxadVeGxwa0URJB6/CwaR/g+l+plr9wqRAwCLWdpX1DW8KdqbQzlF2THctAEH3HNCyQwlqJug9jDtgxfMMQ4gqUFGZRJnAkUJB2K/LCWODe5gKBF2FrwQmrCms9x5K4ZycgpX1UEBu7N1zPzRHQ2g4D4jDUZeRM8ozl5ly1labsw8l/ZPasmQSYyFSFYhtAhgnILiMX4EBAYwMzWiaMMYpalk5pHSrsiy3LhbsM9FlciVg38AzSd4lsomy0RdCzPFIBmuwgZX6l5u/NNeag7WR77LHH9xK/jQ1ru8139f2ddbHHHnv8XYkPG6wAZNGYCTkpI+CTCdPYS0VuzOhuFtwcFrw5STX+Pzg94BQk83lYD/js/haP04DrLOXyuHpMTz1odQhP0hqQjhk3r66YBvnOq7sLhhAxx4Avu1fgTwPoS0K4yOKwO8si2xIODkC6jxjv5bhdFxGjx7IEcCYc1JbzGLQP5DXwf8a/h2UKON1P+OPXX+BVf8Uf330BAFizx2fTLX71dIc+RIwh4svnE54eBMzwl+oWkb20tVirgtcWBr8oJZ1IAAVNxK2C7hcUoCJrUsZdTb4oUanEuyRV53jCllpfqpWSfIZrrVQbsyUtAlakUbUTmkp1OjByx+BTKn/zjguoE7NDIIfMBEfCmOh8qi6ExHDEOANYpiCL/yZx8V0GuYyuS3AO8C4jM5VK79hFJE9wJJ+tR4ecHbog53M7yP18PVyQmbAmj/PUYwmVf80A0hTkfkRfASxAW4tEEyRrGwc8g8aEw0n2nbMyRpJDWh3iQ6dtSrUaXe5JBvLVYXU9kraddEOE18q5D7m05ixBHv1ldVUzogPCtWFRQBPnURkwHaM/Lbi/uRb2ypoc5rVDzoSUHNY5IM8eNDlAHXpS75EPEaFPiK9XxEUTNavYO8B1SWxzE8E9BXDPG32U8OzgrzJnEWSMcJTPl94j3nixTJ2k3UTaShRY66iwHmScBPzqng1gAdJKwk4ylkWnz61R7s25JaqdMQCO27FvOh42f6t/QNVrUFei4uDg5PlKg7C45Pi5QffQJPd6rNhkwRDwgvR71vYDQtMaweJQZBoptvlqCCbX57YFVqy9YHKAc+qwweIS0+Uyv5iAzOJAJO0wDOL6LnJLbb3LBiqgPg/shEXECnaIHgtACi5yJuRV/FOp6KQwyAAzQH43dkOXQb0AuZwacE0dbShW5kexY7XfTbemM6DgBWCSqLCZ8ou2m3KPNiIcuqFn+D4jewavTueBtH/V54HhuoyUdLI4BmcGma20tpa4SMCCzTtTzk3fwz+4fPYee+yxxx577LHHdxsfNljBBH924KtDVvvLn/6Dt7gcO+Ts8Op4xZvDBTdhxsEL8yK4BE+MQAknv+Dv377FfAqIWn5dksdfPr0CM+Htl7fg2cEdI+4OEw697OP3bp5w101wlPHnxPiiv0caOxw+k9WiW1ULYDB2B0B9xh+8+QoAMISIzITPnm/x8HTAl88nrAePV4MwBv7g9A7/8S/+Ep9fbuFdxtvphHfzEf/w/jMAwMf9Ez7un/D7x/dYs0dmwrvzsSxaw6T91kWsEsWq0nQxwlXZH22FMFXOtonDmT4AZQjzwhKPFWX7EoRNJdisB/Mg1c943vbjexWfAwvzIo11h0mFKilK8kLaJhKvAZ++FVDG+QTvRaeCteWj80lsNAEc+hXzGjBde+RLAM2uiKACQAySgKeuVb+rvw7jihAShpAwhohXo80hGYRAGefY46v5iDV59D7BHWYsatfqNQGajx7rGjD5XmxaLeFRkcLCYvAMPySMhwW3B+lxKBR5CDDw0B2RZo/UtOsYw4ASwc1iz8pOPp/HUJLb2OeS6JktLN9ESeS00p4UbTIaPJ8i+qOMg/cZt6OIqVpkJiR9LtbsMC0dluhxeTjAv5PJQFcHfvJYb5OADF2WhNHaPMYo+z9mOMc4dyN48UWMdTwuAiI+9nAXAXz4HEoLEQ1ZGCg3hDUS4tELO8R0FTpJGk0Xg7JqyDRWwi7KHGS1ykwHbd3S5yYduAguegXYig0yUEUv0QASLwrbBkhkX+95LmKucgw+yBg5z0iLK0ACJVeZIr22L7QJsjEtGjAMJupqMSQgVgVKUltdmnQuKABgbTIiNkqlHUvabqqtZjow0siIN6rH0mUBGoyd0WpRND+5eQapYV4gyLhJS4srAAEXOpgI3AIQIMeYHbkRp2xsgzNcbYdp9Bzk/GR8jAVTGDckc58b4KAAGYAMTsgCLhh7o73GpAPomotmqjcrEnJs6DaMcm6FweLEMrowlvRdXgGVOredtne1ACNYQMav2bruscce32n8On2JH0o749tiZ2DsscceH3p80GAFJWD8UhavVqEMf5Lx+/cPCC7j4+G5gBTvV1Gq+2y6xaosjDkFvBnPGH3Em16YFw6M4KpjxHXpQMToXC45wBwDLtSh9wlvDhf0P094d3fE83gDABg/d+ieIa4fHcH1jDR5PK/CzMggnMKCQ7fi3PV4eH/E03nEp0H6RN7dH/Efvv4UgTLezUf8m3//U3AivL2cAACfKFgy+IindcDDcsDj46FU290MkKdagU1S1Uwjl+Qo2eI2N9VkoIAF7Ah5UNDAA22CDRgdHkXpnr1oKmRdvBsIYa0llkTkRvTNBOWE8VHuqny2QBLMBCxTJ0mjMx0LFQwNLIx1FalbxgQ3JBHOA/AcMvIUgEiq26DChpr7uNWDF0b2ThK3lZCHDKcJ0fUQgI5x7hPmG49XB8Kb8VKArffzAZ8/3mC69EAm3L8+4zgsOPS1zcS7jLuRkbLD4zBgWrrSQpGTg/MZKXr4kND3Ecd+xalfMEV1ZckOnU/ofcLtkDF2Edelw6TuKssSBLjIBKzW6y6gBSCJaGkZIQWmAgq1nm5XG06ELiEOHj5keGOPHGccurUAQMFlnJceSdktRIzOJ3Quy3+jMEKcYzyvMl/9k5f2leQR7yDskVjLvolFg8B3Gf2wwncZ6bkryZu7Yfzem0ecbzq8f38CfTYgnKU9BwDSKQMhi1NKz4hDRrr6TbUcUMq8uki4lRCejWUk883PAGZlUUQRzbW5kgZxlMgdI2oi7toWB0uAycYZm9hoFnigUBsMFFJXF0uE00xwT74woWithfp4opq8WkbqBdhzM1Wx0ATR6liUYaItA7RWHRI/E7pnmx+y3zhWgNGtwlQBVFPEWpBYxIrjgbDcyznEW1YtCGVIEFfdDGh3SXhxT+I3ZNSZVBzYVZcNOcHqeNER0iEDBwG/ij6FQwEi3Eyg5+3/xWW9j2y6FerCVMCORIKRsLwTxIWnAX6JQYHBPm3RgNxeJBpGhV6jYSXKoqDSoqLfDw0tJxN4cRXwcdvPBQsiYZR5cbaqjlJy7PU+FyHgPfbYY4899thjjw81dqLoHnvssccee+yxxx577LHHHnvs8TsVHzSzwq1i58mEYh339umEN4cLXvUXBJdwTR0e1hGfX4S18HAdsUav7hEOXx0PGLqINwdhVtx0M2L2GMOKm0GqylMMmJMv1eSn6RbenbSqnPHT0zN+fvOAf9N9DAB439/h+BcB3RnwK5AXwD96/OqzV3LeXcbxOIuum8/ga0B+JixaGfuLd8ICuelnvJ8OwJcD/Ex4qwKaX57uEA4Rw7himQPWKcA9dLVFIwJObVIpSUHOL4Q4Elir6DlwocSXVoQXbOf1pKKWaqdo1V3bb//IRdAuB2sNqW0k1g9OUSqi1lYCCGOjtIywsCzcWl1Y3CrWlS4C/XtgvXPIHptKaxrk2OnAYE9IK4FnX2jrlAhhRhHAYy9tFWlUG8PJyflq0dItQIzSQgAA/GTX1uHpvsP8poN/k7EoM+ft4wnz24O4oAA4D7E4jgDAlAl9SLjpF5yGCcduQcoOxtGJ2WHw0lLhiDH6FcFlxOzw+ZPMpaiCoadxwRgijt2K3ifcDEJFWZLHEn0RA42rR1w8soqmdg+i5WDVdrEalTEDpErvB2F1nMYFpNofxiyytppl7ZCZcFk6nK8DsraRkMvoe9UKIcbQiYbKT2+f0Wu7yPv3J6wPPdzVqdiosACKC8kawEEYEXH2IIK0d5Ec44wjbsYZf3j/FfqQ8NlXP0E4O6j0DNzikHuHdMzgIcONEVAdEkDo9aajwItDSoTEhDTKF/xEIqo7V7aQiwCvwlKyByJ3QD5msaHNKAwem8MyIPqzy9tWkNi0Apg+SSu+mQg0O9CTLw5F3XNt2yoWzESIJxVQ7OrzyKSOKos+IzrncwDM3jR1KHoOpM9jmIDhK22nYWGSiPWrPhcZCOfKlmICwizPJZ+FpdQ/yf6nN05dj4RNkTsRZC1D9NLylZrxQn0XuQhQdPCziL0WS2dCEXvNPSHOhMgkLSHGOlB7WTjAPzocvqDNe2c9qbvTqAyLnsHg2j6yyHHcqs4mvj4zdu+kTUYHI8vgm3go2nYMbafZtOJ4+7vo6TDJ3HRdAtl8zUCefXVn8QzXp2LZnPtcNDiStZ2osG2J24h4eUHv2WOPPb63+L7bPv668fK8/ja0hfy2Y/234Zr32OPvcnzQYEVJiI36C2D+/IhPj7eIR3XWmEdc5h7PZ/EuTdoDbj37T9HhCcA7Ly0c/bCiCwmHfsV16TB0EcyEaemKsOP1MiBr8uHUKu8fvPoSf/yRCGD+v8R4H18Bn9bFop8I+KIv/z6HEXnM8HeL0pYrrTy8d/gLfIz+9SSJlmOARGQQAHBxYNfh2o/i3pBo0xbNDvATgx2BWECJ/lESnniQL833VBbSlKhavdopkzippFGsIInFbYEagUw/ASChtVMGOFIBIOzeuAR0j1wSjSL4P4igYfk7hIYfrrKyDxOLNWsQfQ1LQtp95E51BXo5D+m/F6tVG/NwlXNZTyg2r2Yj6iKKY4rsUMRRw0XPcZGe9hyAZQ6YacQXXcK6yiAtT70AHlkSsZwcLtNQ3EiWuYMPCcvB4250OIQVQ4gITYP56CMGH7FmD0cZSw54WA44P8p8ZU2Ip6FH10ecDjP6kDD4RuRzEOCjV478ee3x9klaMJblVFt4uF5v6clnQggJ96crbvoFDozIDrO2oby/jpjXDssiIEKKHjlSdUdwDjl5zMQglzF3AThO+OhwwSdvngAAb48XfH53g4eHo9DbE4G9OIaU+wACDwBmLxolqbYJubcBX97c4I/u3+EXt+/x+es7rHFAOFvLkGovRIc8EFIi0JiKTSybloNnaZVRen9W0Eq0KRzcIomtaTUQ17nhLpK0xgykW026NwKK+m9te6BOxUxNG4QgGgzRVZ2CJsF0k0P/KM9Y98RwUea/S1y315vYP1ewor23IgDKpaUqDrR1E9F3hFnvOhVqDLOCEarB4RcgneX9kUMjtzBSeebcItv5BfBfsd4H+SyO1k4irWTWGpYVLDGRXdPGKJEBEMEtpO8CeXeEqWlN0+tOA6F7JKxXj3hw1Y0jcNmvnwndE6N/5jJO8UCYr4T1hrDeMNIpg0+pgLjck7ShRAK12hr2uCSZt0w2Zvp9+9xxea+WFhiPqmERxNGInDgRMcuczFMAqSiu7zLokOBDkmeBpN3K++pyBIgYNAPV4aQBzEIfBVTbY4899thjjz32+IDjgwYr2APTR1SU+gGge+fw5ekOT6cRKRHW5x6ITsQVIYlIPuRiF4dM4NUhaxVqoh7XwHjstGf7GNGPUWzyNEFLiwMm+X66eHy+3mOJHn9w/x4A8Pdfv8W/Th5nd1tsGskqYJBkoP+MkDuH6adOeqADI/Wy2vSTx/irgPh4EvG6Y0IcM0jtJv1MxWIVTrUbDlyqeuudAzEp+CA9+pQZx88zurOdjxM9Ckfws4gHoqfaeh20MmtOAiT2jSWBjFzsG63ySLkmInIMFS5ctQ++J1C0axQQxEVlSHBlUgBA9oTlViqhLgmQ4ueqrwGguIykmyTAyOw21UWz93Mr4AbdJlGzA62gmtMCA91TrWa7VYVHjVkyO5zfH6rzwOKQDxn5RpJUx8B86YrbSJ49oguI0WNaOoz9ij4kHDsV6qSM8zqg8wlzCojZ4bp2ePd4BCszwiwL+eoxdwHL1CH0EaOKfZ4GYVwEl3HbT7jvJmQQ7gYR6PzXU8B6CiJQSgyePNzFI6vDiusShj7ipl+QmXCNHR6nAcuibiGXHqzPDoIkVgiVEsAAcgQ4OxB5xCWACBhDxMejiCH80e1bfHx4xi+P93h7PmKeO7GEVSAmXcVWprtZEOcAnh3ikQvo5CKQHnp8er7Dz08P+OlPHvGFv8XyXrLu7tEXjQa3ECh6pFiFR6HvB7EaNXCBRZcBklTmQ0IejU0g27q5sj/CRW18F4cYqeh/FMcbV9kEItLohDxgdp32GK2a2OqcMk2KAlKo8Cd7IB5RhR9tHyzPgV/E5afqcfAGOwEE6DBrW0A0JwAgxSqSGQ+ENChotCrDSQGP1DNyICx3ygQ6iuYDKbDqr4TuzOgfK8DIi4AM7IAuKAsi6Bgpq8At9VzXG6qABaOeRwQoctW1gZyX2bNSYvhVwMXc1WPkXgCNNCpT6qDAxyRztn+ScevOhOVKWCePGQArcEUqQsuZ5HSMPdGyaIDqzuLkxhSgOGTVlwAoCAOCPIOM+ZFEi6KwchhwZ2GDJbXGjncR3WnFMKxFzHdNvuzDQIsUHXJyAmiSWskC5f3z+v6MPfbYY482vm8GyHfNYvguzvfXCaHusccev/vxQYMVqQMuP89CVVaxvHAl5M97zEMHtxAOj7LYpJpfYb0lxJsMVnotHIOeFQi4mpWjLn6HgOmjuK2iqkidtTjQ1eHh/Wv8+c8lefqHn3yOP3z9FX7pMp7OI9I1SCXVEpuLw/FXBHoS2735I0Y6ZuAkCejyBjj8MuD4K8JyHzD9bAUdEjDKyj3OXqqyqwhGos8iLDlpgnkbsfzEibhln8DRYX7TIXVBqowaLgEpSDV0uaNK4QYq44FJ6NxB2zicJR7V6pGVzt09b5MlOGE9XD8mrLeyb0tAu2dgfMcYHjLiWJkd8WiUcsL19zLyIUnV+Z0rDJpqn6o/DWHh+jcAJSHjxl6SfWWwWGLZJkndI5cqbQ6EeJLEZ72VJA1PoQpWeoa/XTGMC5gJ1/cjEF1lLaiwXuKAy+xx9QOcZ4wHQUOcy4jRI2fCugRwIhHfu/rqskJ6fRGg6MGrwxoC4kHu9bUbMIwrOp+wHjyWHPDRcMYnB2E1vPvoiCV6vD5eMfiILy8nfPVwQtCExzlG8AnXtcN57nG+9ljPfXGhcFcHl1WYMCtdPjXilaxihVktJ5lwdYy37lhaST4azxj9io8PAl5c+w7zGso5nKce87XDzWlCOjjMS8C6BMRnKXebxeQvv3yFzIRPjs8YfMKXo7BHzuEoyf9VxEQNaDCgMI2sVH0uTCC3UtPi4cSe1bOILgZh37CrFrFJGUguAt1jTYxLK4c+F9mq6TqvnIIjTNAWB6ogWiOKaM/WegfMmmyvN1UMtjCVIhDOVCxmba4KW0RAxlYE09omAHEJkmeEkTvCegOsd80xDISYCP2DnEM8AstrreiPjUtGFuDGT4ThrewgXBRQiJXp4RduaAl6rQmlrWVe3EZ0l6m+atNBztGYUH7WZ9jJeLnE6C7KqMpqb6qW0euJ5B3yCSOeCJ22anXPDL8yurMAQ92zgMPLnbIVbsWml4YMp+wYIlYbURQGA2cSV51M0p5hQEJgOJfhfEYIuTCSrG0qPQbQKnbR4g7D6B4cuifAUKXl3GH5WG2Se3HKmafa8+MV/MhMyKs6xih4BghY4caM3u/Mij322GOPPfbY48OODxqsQMegNzPyNSBrJZjOwPilJhqL6CpseoYJ6J4Jy73HshLSa6A7rFiPWmlegiQkz7J4Z0dgCsWaENCKqlHpWSp33RNhnqSV5N/2ER+dLvjkVhLGJz6UhBkAsgtYbh36B8L4Tqqfa3RYtbLm7xcszx7HT6EOGwHrKwfcCu2AhgT0WqVTK0oA0qsP4GcfP+Cmn7Fmj8FHOGK8/fkRn370GuFLyQzCWZIms1aN91EcFcydIBMwObjFCSDhM5ZXBLqpLRbxJoN7TUYSgV1Qe0MdapZ9Lx8l4G5FP66y6AYwf9XDLw7DgyU0wHLjcPlE9n/9xYo3P38AALx/OAHvD8hBXUiUfUFXuaExip4FsbJOCvtDfibrvtHkrarm636ybavnPMg5rDfAcs9INxkcGOHRg5YKyKQTw2lCsC4B7inIMfpmwhHEqUB725NjnJWVA8fA4kCT35w3GIj3lnzpvsxmcVGQKsk4RmLErgNCxlN/gPMJH796xscHqar+3ukJGYTfP77Hyc/4i/41PlUdFgCY14CcHb54uMF67YR5MbnN2Jj9IyWhx1uiBQjgUxJYgropdHhaPC7aevXZ4RYHdTmxVirnMu5GmSyHbsWn6x3W5HF3mPDmdMHjNODRHeW7PmOdAtK7AX8ZX+P4+wteDVeMQcC9v2BgXT3WoRMW1bStgmdl3gglXwDKOIXCtvLPhHAWJkQeGdywiYx9kTtG7sSdwtpOrD2rDWd2oQostEwjY0mYgw5QwYZ4EpbQeicsDzokjMcFTu+/aOwIqLXMHu7Jw0Uq88MSXzt3SgRa9FzMKEOdQvyVkAZGvM+gu6VU5BlAXAQQmx88ci/ADR3tgZLn3JJzPgGRCfEkc8mfZXz8LKCOvwrL4mvPo7WCKChRW1xkjHIQ0GV+zYgnRj5WjRkb+3AhxBtxdAkTI6hGj1+5WpF+JO+e9V7cgACgf5T2ke4sbTb9EyNcgEFcpbHcecSTx3qTdS6oy4wxIxScIM9wnsHMcI6rO2sisUxlKgBFjA5Zn/nxcw8/ybsmd8IsCVdl2ylDpX8E5nPAeuuxHBjTIYslsb6b1z4XW99yX4ACnHEmeM/46nzAHnvssccPGb+OCfGbsBp+SMvX74JlcflHf7qzNfbY43uODxusIEZePOgqonQAEM6yAE299mNPL+jRJH8TWrfDFDusHwHQRX68Tci9g5sJ3UUqeN1TrewBUumTSikhHRlukQWnncPj4R7Prw549fqM5/OI9NRhE8SYfpqRe4fxC6liyrZyO9IhIr9ekbsO3Zlx+iWwPnjMH2l1blCdiMBqVanX91M5wZ/8/+y9Wa8k23Xn99tTTJl55qrivZe8pCSqBXcDhhsNGIYf/JX9Bfxg+Ml+6QfDkNQtNi/JO9Vw6gw5xLgHP6wdEafYrSYhqdUsKBdA8NzKk5kx7IgT67/+Q33iTb3nMFVEFDs78PXmkf3lR353uAbg/rDBT4apt7x6vV9MFedp+BAMj4eG8X2z6LPTzqOyBGCaDK4ZKUsBQ/rBMVBRfDTzLopZ33Vk8+WB66ZjWwwMQfbx8armOVwTncF0MsnuXsPwlbAO3nz5xE82B+67DY85ynCeGK/T6CQNUavw+bl8prLPP8uEdv35pSmh7QQAWAz8EEbHeJXXwlYaR4oIvRGpUYRUzOsgMR0LpqhQvaY4yLqImdqfXGI2tVyGy0aRZnlCksbfdKu5Y7QZQNrkxjZ7dSwN7swEyd+hoyINAOL1EIPih33J4400+v/6zVusjuynipAvgquyozPZrDU0HNuK8bFCjUqabcXi57AYIKqE6vN1sV/BiqSF3bMYPkbQoyGddN54aG3JqYw8bCeMDdi8hoocj1qaQEqK48cGbuH17kjlPGNmoGzKTG+SDwAAIABJREFUkbZ0tPcFvCv5XXPNm8sDu0LW+6vdicNQcNKJ4A2h1MvkGaDZDIvuv3J+AU3mZq59v8E9mSyXUkSXIyvtC+DCJPzOCxgyamHN6P8cCJ2jYyGDFXP0aFilajEfmuhWo9NQJuKlp9wOFIXIeupi+iQuOSYYCgdb6KqCMDOKAO0i1kSMyV4ZSS0mwovcJrNfpl7MG83GU1br4jcmkmqFUonhwmFVYuzWe1fyGkYBddAifVIuwoXsmG9y3KZXqMy6ENPc9RgIAyDLY2YGyEtlllnBsekqkuqAbQTlCI3Gjxq8xjfCojGD3Bts+4LdMZHlcfkz60DYymd0l5rxYHB7LX8r9lAcE1UGK4pDwleKaaOzN4bJEc4rgDX7+cR8/oNhAQpEhpSvjwihjhL/mk1/qw/gWnmfr4W9FR2MOxbAxbWJcp8IhWJqFL6xqCD3sfk4+40hbCJ6M6HKLFsJ631FqUT7YcO5znWuc/2p1J+aCekfC6z8oe3+Y/brDGic61z/8Pq8wQqvKX502JOi2Ms/uWOi3EeGCy3GiLtPJ58o0TkXR9E860nRBYe/yJNsF4k7z4CFB3GkV0EmX+6FhGJuIPsbtUgm3EFe33yr8Q8VT3ciRakOeqE3g9DSpxtPvw3EQuQe5VNampxDVaJuR/o7Rf1ODOKKQ8IdZ/q57Fso5QFXe5FQ7BtpLL7dX3KYSqZg6L1lVw68qQ/85fY9f7ERE9DH24aTL/nbp9f829vv+aG7YIwWm+kjWkW2xcivjqU0ZlHMRJuNNIhz41dZj9GRZ10RrzSTF3MIFRTJJfT1wEXdL74MLiMJv7h64Lu/itzf7WDvSDpR3Pb85EIYAZdlz7t2y/2jvA4CNvgaVPafMIPCtgnbrsdOT2mVUCDeFyoII0NFmYQvhp79yqoIhVDe+7tIvJbmy5QBA4TeYlq9TudnnwCvsM9OKOmTWujvC/OiUiszQSeSg5gSOq7+KaZX+f0say0mFvPIZFagQ4VsEqrTCylMZmTkxs30kPaO4SSg0febSxo38djWmCz5aNy0JNuM3jJ1Dt2KzCYZSUhQ9UoJSJNGDUYMKLMp40tZVfIK5ciICqgRVNSfyHVCqQgnzdREpiqgbeQpH6iqmAheYz86jnFDSorSTQtwZnTkoho41JHi0dDdN/wQNHeXx2Wt+CiNuXcCupXOLzR494IOvysGKjNxV5542Amg89fmDYe0w5zMAnZFBbFIkuoBoBOuntA64b14bLxkS6HA2EAMhhhEIjBNYiYKgNeYo/7E4yIWafFK0LVnsxkprMdHzTBZhskuxA3vZVqfosK6gM6JInFe9zqSkvye1glrI2Xp8d4wzmaoGSSKWUaQInSncjFx1S5gbMS6QFONjFl2pAaznEcxnRVGx8LmKPOBcFFYGg5SEfEbMnViXij5/zOIRxBQgxe+HsmkxWskmSTARDu79rJK6aogXiMXoFxkHPM11RqR8mVQ0z4bYqWJGazQtSeWgX5j0Z1mvFSUT5oym4TaLsn/+rRc70lDKOZtkv2YtnoFDl/sm/bCFJuZeMOlHLsZqDJjBjANAopZGC8EFEtG9qF6UGx+DBSHQLlXhEL+fkxZIucb8REZvSJUAWM9UfOJH093KCnffd5/3s91rnOd61znOte59B/+lXOd61znOte5znWuc53rXOc617nOda5/vvqsRy96hN1vxeVe+1mrPU/QRQ883KZlGgYy7XIHRfkg0XvlM6ikGHPSxnipCJeBdDPS6wK3F5M9M65GdSqC7fMETeUJmWaBfur7hD+CbY3EiA6fyhdCpfCNpv7qyLBxtLFm16+a5eZHzbGy9K8CYIiPiuI5vTDHzLGCJstccrzm8F724XG65qGKKBdJXnFfRN7vtvTB8mUtFJTajAza0riJH7oLvnm8FdPDTM2/3bTsioFyO+AnSxg1SrG40y/xnMFAMBKdWU50l3IQYtBoF6iqiUNf8uibJcoT4MubZ/7N7VuKV9/x7emamBQXRc9+FJ+D758vOX7YYPYGExXTRfYOKFfnfTMo3LOiOCCshiisCl/NxoorA0BFMdSzJ5ZEEeXlnIVSok2ni0i6G7m6bJc1NgXDaV+Ig3/B4kcAotEvHl+yJ1gSTuBFRKPOkqE6kVAvtilLUF58pkqyXcV+NWZcaPJJpCli/sj6Orww+VO4PehskPl2d41tPH5fCBuo8RT1tO7fYEmdQWmIRU4yyGkGAGmUybo5ih+BigrfzN+1bsCShmFEIaCmle1iRnmfypGQPkEsFX2e6E+FmIuqBPbJclIN6aZdDDinYChMwFxOTJNG95rxseJ99gRo3khqQlNKosll2VMZv3haHKeSIVgGL4kr+1CxswOvS/GUGW8M/yFoukNF3Fv0qIi/zy6JijAZoknLBHs2WQRQOuFcwOaUlsIGYoKUj0+ImuOxInot8a1ZRqEz28HYSAia01SKd0hQn5jyvoylnMqs+1JpMUKNg1kTK1RirALaCvMi5XsbL8w/UwIGOZZzIkmyFl8nJp0YL0dCb1GdMBXmc6yiWhgWwi6TyT9kvw8lrIrZHwSTUPk8rsdK7h8xKeJoSLMJad4/eiNRzqPOsprZ4yUzlCwi0UkJikhRTahFImHEgHgw2GeDOypSqwjZK8fvDKkOEm1bBcZLxXRlGK5mTwstJqatGIW6Tkw8i+PMSpHrt3qSDRZmVlr8SGZGxZySpLzch2Zj4uF6ZWiECqZNTjMqIn2Wfk0XhlAaqo9aDEtDTjuZ73tjojgoVFL01jHVmfkypxRFJd4cB851rnOd61z/gPqnlqz8Y708znWuf8n1eYMVE1QPkWjVQtONleiMQym0/uFV9hyYH/qDYtpp/EZTvVe4NuGOL2UEmh7gywnuBnwssSeJ+5vj8ebkCttnnXEnMZ6zq71QfRXaJ4nmY32InbfbHTTeG+6ujnz4heIUara/lYf58jExbS3DFxPdV4nxUlN+1IsMZKYS60noySINgOJZXnd7w7Qz+K3sd7KJ48ny/3YFv2ruAGiKiWGyTMHw69Md6bnAHDVDNhQ8vap4fbunqUZC4emtIwbN6HPjQI7Z84YYNDEotEnYMtOtdcKYSIyKdt+QRo0+Gub277eDZeNG/pebb3hVHDmGkrf9Bd89XQHQfrel+mhQHvo3AS4nlE4UlV+i+7zXdPuS9F2BO2RzxwL6u9V/RA8ae1Ako7AZg1i07JX8PO0Svkmki4m6GXEZUJm8yQkX2cNBA15h29kfRUCsaCRRJamcdLBEn+Y18UKJ9DKKEdaeU83U8LxtL41KX36GHsVPIc6+GW41WAxKXpNEB3m9/L7AN44igyK+14zOfeK1YEZJfJG0FSV+GHNMbq+WmMroxMfBb1bASMX0iSQkFtLMS4JDysdBGludAT/TaYJJzBqH4LV4Qmwi5iQGmcPgSEX2KoiaXkfKamS4g3BwqEkJAAO8b7aUzi+RsI0dsbkZBpZI2NNQcOhFHvXU13y5FQPXynh+fvPIh3LLg9kQM7CToloBgKCIvREZR5DGUL/wW4hFoi0cuvEokxiyN8csQSmdJ217UlL0nWx3DEpMcoFpNEw5pla3Bj2nmsyAV479DEUijeqTNSW/kAEElU1Pg5IkY81iJLp4bGSPBdNmmdsc56oVIRtUjqmQfzdZDoOAWSrlWNde5bWuFq+FNMhijs7IWi7EfDW5FeyY78PB5XvyC6Duk31SAtq99LyQz8hSKDN/l2ZIIgWBFRDBRmKpSaccEfuU7529YdopQh2hjJgqQBkYmwxWvxIzXHsQDxN7FInI7FeEyoksfcqIDyIJmuU6laz9aAQETSYnCuW/DcNtlGs2Ksi+KLNBsbvIX7KDw1VBd++wvcb0OeUl3zvMyAK8NpMmab2kwIB8XPmYmM6WFec617nOda5zneszr88arFCArxXDhcZLEAe+kgdcm5s1FeVBcn6YTWjiNtDXETVZUOoTZobEEmpOm4LNmxOn2pE6I83sPM02YLby2c2PifI5oQNMOWJvmYKpbKJX5fjEGazI5nLTfc3BBn7y6pl3Gvps+Fc+JcoHmHYG80UHl9BeOcyznC49ianc3GSGEkhQPcjnF0+JZBWhnB/yFeakSV3F0Qhz4ahkShrLiH024kb/pJYJYD9UvE+w3fXURY5UnSRSEiBGTYp5ojtpVFD4MuK20qnPDI2+c3Cy0pBHlkY/jCV/zRcMwXJbndiPFd/c3zJ+J0/Y1b3GTAI46duRVzfCCKmsX9gdMSn2TcXH041sd1KMV5H6pzJSvK5G2qHg9FAz9QbT6qVhBmFpxDLBVoxDnZNtPvXSTA6DI45GGtciinFdWpsnM7AkF4QqAySsqR5JScMSs59DLEBl8795Accy5YSN3IhZ+fdP/R7yYs/NcXqplWdlX0QXZYqr9MLMmM1bZbqbML1CHdXqnWDyuo4KJmmidOCFienaGCebtzE3rQAp5qZeZ8BPQ7SRZNe1ZFu1eIMkr6CHWGnS7AfhpfFMm0DI2xRay/CCVZCyZ0pZjQwqiWnt7PHy1NAWgbBrxVgyakrrGXLiiY+adnR4b+hPBWnSnEzFc77eXl0ced0cuKlbRm/Ye41+cuiHF6BOWoGgOY1ChRV8ShqSMsTCCVigoKsTp9motA64ymNdQOko3hajQfUrKDSDBuILkr1MZjZWOZ+r7JWR5JiGTVzOY0QYB1QRRmFNxOIlkiTgR/K/B5DNJqJp3g9QkyaVEXsxojKLpnQB7zV+tPjOoAaNHvTixyCglEL5DGBFhWJt5BfANkEyJpvwfnpfReUEEqUy+JsWJpTsQ74+AigEiIijW5Iykk1iJZGTUaadAA0mG1zqAVwU889QakKjoYzL3we7GTEmMV0ZfFCMnWW4NZ8AJsqDO64KytlMGMT7JtmUrxMgyv7MgE/aebSLEjmazWwZNKbTTFruO7u7E5fbnv2uYugtaTBLchCAPRrcsxhA1+8lijW+OEbRKHRI/7lf07n+m9a0gx/+NznmX/5f6Q/89rnOda5zSf0+6+LMtDjXuT6tzxqsCA4OX2vGq7Q+tJcB1Rvq7w22hepHg2/0wnoQV/VEagKhSUxBGBPzBNOMCfUM4a1luJToiFniEe0qRQhbSDbmMZ9Mu2Ywwtcs0gyh+2Zne7M+wKhBYw+a1jZsqpGfv/nINz/7Ir9qsCeoPmi6G0u1HXDXniEbaIaoiJPIMkwRsrGfZpzEMNC2WSaQp6wqNze2W7dRT5pkwG+zT1x24Z/ZB9VHRWcqDkkRd4qUHeb9JM1VmrLzf1BLpGXSankdlUhRIvsU8gAfNgkV5+YM1A8lvzp+wa93E7G3uI+W6nke2Yvx3HgXeHN94LLs6b2j93Yxh9QqCdW+jIyXmuQC5V3Hz66f5DjoyKkoKKyXifboGE7FMhDV2UywqiamyRK8pmvLZZquBi1RoUqaQeVlX/WQGx+/povoSb0ADfJpnJu/F9GC2qtlHcxTYqUSoRSQ4JP3588IpZhzfgJWvHhdzUaAVpo0iT3Vy3nUE0zbtExfTf8inUQpkssNcvoUaJkrWpFSJfL7T+YT5geKhXWUJpFQoF80aGptupUHjUzmQ2bxEITBoGpPqrK8YDCLeSRRjBijTfSXStZ7GUmZMZA6gx80zzTE0fBsosgq5vOohcVgbISjWwCH4SS3vx8mI6acxrOtBqadYXhy2KNe42S1xNjGItPyfW7O833DnsCdUo40lf31lSKU8h3T1jLcOromijRikhjOuYmeQbz5+1R8wcxhXkesIMB8/vI9SW/F/NMVnm098Pi8wcdCAKQX952oMoALBJXP6wxoKGQxZfCiuez46dXzwg7RKnGaCg5DSTsUdKeC0FtijgXVLyJjF7ZNAD3/nNepfDlZRqGWfZyTcFIZZf2ZJIyEBdxLKC/XpJ4EtNExpzTl4yggrchdkkmEXSBWilDn43wUwFKMbTNjqDDCtAD8hSK6KDIVA2bjiUWU5I95H4BxBhDm4/aSHZKPo9JJ7pNev4gXFYYOXi/x18or3EGjBzkQB7UhXHUUmVmUCjn+3mU2mwaSsHzMlARsn1bAhAKGa/0pyHOuc53rXOc617nO9RnWZ/04Ewto/2zCbCbKPBVXKjGNlvi+wXaJ+qNEwPl6ndJOO8VwLQ12KIXG7yRYYIkYrO7h+aZC2TzpG9dIQklliKgyMryRqFN3WGUGKcdNak+m8Cp8o2Cm7teBVETS4DAHw4cfrqi+vqf8Qj5gOG3RXhJCph8KuhuDavxCcdY6oYrIZtNjTWCYHFNaH/rHHTkeEChyg6xzQ61fAC6V6KWDEe17NIb6Q36o76D+oOhTyXEwKBdRJq7695nCraXZSTZPx/MxmtpCmtBpbc4pIz7/HFuDaRXVW0v8aDB9llTkfehfRdL1xMVVS2ECT33Nvq1kMr5o81lGxGnnMWVgUw8cRokLCVFkK9ZEduUAGzg0JcMky36cbJaqaEJQTCdhr8xpBNrP0hJhocxNrsmT5KSyy38C08l5D8Xa6Oswy4TUAmzIVHlm4OTISieNsBmEYYFikXm8rCXW0aZP9OlkjbzyrE1ajsT0tVo+L1qJ2U0W4qwM8C/2I18fsVyn3bPEaF4TphPwo3xKyz5EJ9fRwrxwSqIdy1k+kL9ryhKqvBaX6E0lxzllFsscf6mnWYufJ+NK4ceSaeelAc+NOpOAZuHg0DkFxRvxNJF9EImLb8LCYNCe5TwHVfLW7rjetZQm8PriyG+vSnx0ayqLgtgEzE4O1BQkmSN1spaKj4b0QbxlRCYQF3mEnAeNbTWhFuB0BjUWBotn8beZPXbmdQECsKosP1GzNOlFWsnV5YmbpsPpwNYNFCbwY7xEvSvl88gT/zpKXz031y6uvhkuoHXiatsxesPr7ZGfbZ7QGXkYo6UyE7WdGCrLO73lREWcI5fn85HlHmqSa2ZhJeR1Cnnf0xopvGxfkcGSKR90BeTtUzbJvcbIhaCCmvG7BYRNCuJFXtRBGD+pzOyzXHrIkcV+9SMymeHiB9kwn695kbF86l9iiwBFQGc5mjFREmIAPxlhTYBIfIKSvx1zAlCW9ui8f6HK10lafWrMWNAfLHETln0gX+cgoGIsEqGG7ib7WsRVQhYqxemrtERun+ufv2aGxVxnpsW5znWuP7ZmpsWZYXGuc0l91mAFNlJd9YvZI4g8QZtIyLINd0oUh0go8wOkAtfKRNM34JsEbm1sVFKomDB9on6rGa+F0q09y0ReXOQ0MSJ6558Epp1dPCNUbuRNNs3Uo8IMemkQpp1hvAnSPHpF+aPlO3fD9krAirGWSXvxBNvfwfhs8Y1dpAbJypT84KVhUkcxBSyOL8fhc4Mn3grJCmtiNp8cbwJp46l3A8ZEhsEyuYJxkM6yfAC3l1HoOMp3R8dixrfGsEZSmVCVdF3zMVKdyQ3n3JQpwtWL36sCU2nRHwSo0OGFxwjgXnVcbju0StwfNnT7Cr23cp7mIaeT45B2HteMWBs5dSWPj1kTFBVp0uja0zYFdTFRWo8P6wR4HM0aN9mbhTUB0szELJNQeRIMrI1PISCXGRLuKM1l+0YtE82UQYxinzBjkqa+WBu2UMkYdroMpG3Aj5ri40xhWE+lnhRMGUQweTo+H+d58h/m90hzNL/fN2kxJlyWRpHWafaL9yU7G8ayTIKTyQ1WEJmQbYVWX+7lmguF7NM8FY9GpCDTVjHl0zBeRsyosEeVgb7snTAzJ0wCr1ZvhZiZBfNamw0dFRSTYkyW2MSliaWIa7yuzwamUeQvgJjR2kTw5hNZy3yIdacYDiUHG6l2RzZu5OdffeThuubw2OSTqdhet/zs6okiAwJXruMU5Hr5m4c3vP9wgf5QYE8a2xrMIEaNcg4Rf5xWwNHZyyAuayF7TWiV/W+EjaVvxcfAmsjYOhgMupcoy7T1uEbAk7um5bY6EfNe3dQtp13B/r5c2BvRKOJuotqMeX1msCafB+cC1kSuqo6QNJXxPI41fTZc6LzDKvHh2NiRq7qna8uVdWAAm7CVR5vI1DnCpIltju/MGAIKUpFIKqHH9VqMVRTwpAgiIxpeAKMAOlKUnlR5JkqRoUx57c2ARxG5ebNnmBztvhL2TVrZJfM1BIhcxc/gQT4GJwGl52s+GQGKQr/+/ZiqsJiHkpSAuBmgIBvAzmBickn8PZboUjHd1F58J2Kp6G/lGimessnns/i6jFdZwvjCnHleK8klfA3HX6SFuTSvpVgH3KuO4V3Nuc51rnOd61znOtfnXJ83WAFMoyRVLBWlGVHbyHBtUFHj2vQJTVcFKJ+TNIuZPh+qF69HhZ4SxfMLWjGsEgpA9QozGPxWwW6Cy4mhzJPgUbj6ZhBQpP4YqT+Ca7PXglPsf2bo76RptCcFvys4zMwNteAhVI+J4iCNrs/bmLRMZ31TyfS+lQdvPc1T2NwoBnkIn6f19pSWp/reJpRJaJ2IUVGWnhgM00WeFvYa2yOT4kFo1C9NQufjGa3BbzTTTVimvPMxmGnuppeJcasMITve692E2k2MUaE7JU34NrC9OwGwrQZGb7h/3MGTo7o32E4kNr7JjYeTSWx90VOXo8g8nmoBSljlGTwbOltyqgP1bbc2aV7jByueC0om9X6T1sam08QqkqooE9J5QjwvtwTp3mBPCj3Ke8brtHgA6FHhrKSVANkX5UWShwYa4MJzfXNEqcRDcYHe23WteQUpyXfk74+LF0nehgyoRJWZH/N0nryO8uckXngsmBWMmCtmz4DlTQjgZVqZPhfP63uGi9y8WQhONPIqgPEpT/5nPjyEP+vxkyZ+dJQf9cKumI9nykaDuhdzw5k5oLNRqYoCgESX8nqSSXXMprqpzikmMRGazJwY1DJpFkmFgm5esyyGlSBsBSZN1xb0tUWrxL+7/R32VeTbV9cAtL6gshOXrqM2E1+XD/x5+Z5KSRf6dN3w65++5q+PX/C7wzXPXUXXFUzPgmwVH2WdmF72L5Qic5oByNhEKIPIA1zE1RN/effAZSkb7aPmx9MFw2Q5HGu0iWybHpOBAmcCrS84TCW9txiVqIqJ55sRvpNt0FFAq9cXR0wGeGdJB0AIGg88dA3OBJ77iv2pYuxnLQrY0rNtBi6qAWcCrvCEmUmQNKYMNM3Azablqa0ZJkuXfXJiZz4B0rCJWPoFjFBFEECq9BTO0/WOGAwhgxbGRi42PbWbuHcbuueKtLcCsmVWh7kY+WInF9zv9BWHjxth3szX7ny9mATlev7nxBPiml6TrPy3msD69ZpLnZXPUevlMr+aIN/35dro7/K2zQkyCmKdoBdZnntIRKNofzkx2HzfiuIhpCe51+luXvN5E10iVolYRMq7br2HZ3bhphSD2ffnYf6fTP0+0+K/VGf2xbnOda6X9S+JYXH27TjXf630H/6Vc53rXOc617nOda5znetc5zrXuc51rn+++ryZFUGR3pXYaR1tLVPmJtC/UkyXCnvUnxgCSlpINpczMu3y21likSf/RuGOieJZ/C6m7YupR8rUdC/0B58cqZYIPBDmA0gyST9abKdo7uMyxa7f9piuYD9axkvRoBdPimhlgikpFUKlt0PCjGCHiO1nmUl64YsgSSZJrZPkQWlCKWwFnX0MVFz3DcDuDbHVtAdHMgmzmyTdYyfa7Glw2FaOQfmUiKd13yFbRQT53GkLrTdMOy2ac2SSPWuzi8M8ldf4Ro7NcKuJlx4aT7oUendVTEts6NOxoX+oKN5b3FFRf5DzOl6Bv5LfMbsJV3judie0SvSjQ02rmd2cmmF6hW4hdjLlNXVYdiLlqa622QC1BpMZIsFrbOGpqwmtElMwaB2XNJIQNcfLiv7k6O+E9m9+cWQ+zMFrupMjVA49CmXdjCtlPTrwu0S1Hbise940B751nnflJSFr6KNX6MwUad4K4yc6tVC+yR4R0cy+Bgp7YrkeJB1kXboq5HOXJ8XixyEsEG2E+aEH8YcAYWnYE5jM2hmuFaGWCE2YmRnit+GOInvRPqHCGgd8cyWGMPd6RzxUmFaRNmlhigSrIEtQdFplJ7PpJEomzNGt/hXmhVxnvFQiK3BRpuwuSjLHzHDxv2ccquU+sXiTBDGHjDge3IZj4ZmuDF+5J95cSgrNIVR8GHf80F1gdWSMlj467pxM8f9N+R1/Wbzjf938HR/vtjyFhnu/41ftawD++vENHx53tCcnTAKbKLcDm1KYGa+3RzZOJB+V8dyVR35RfeT7QaJ8977G6khlJu53WwodKIxnPwprYQiWH04Nx7bEDxZlEhe7jtubI/et3BjUqLA2YnSkthOF9kzBrCyLriIEzceHLcokwslhng1umOU0EKqCx03JcTey3fRcb1uesiZpGi11PXKzaXnTHLguWw5Txdvs7XCyFWmfU1y8AqNwu3HxfohREYOhLkdeb4+EneYwlBy6zAzRidpNfLl95qLs+dZesZ92spbzNW9M5DQVvKqPfHmx5zejZWwLUr6eEtkod9JiLJpZFrMyLKmUmSLihbPG7s73XvG80J6FBTSbn4Jcg9Gt70tFlH/L7BM1KmId0a0mOs32d1B/SHRfGbiStdDZhD0Y2a4bT+wM6aAXOU+yIu2iFKaJ0RGnI9tC1k+hA79+vKEsV5+Nc53rXOc617nOda7PsT5rsEIFRfGkcyMu/zZHP46lIl55kolMg1mM91SWRtijElBgNiUrZsPARFICbrhWpBPKZxnEDIjotDSDphM5yLRVi6GgPEyKnGD8ycQxOUJpGC/lPVe/0lT3E9sfAtOzpn2tSRrKpyzRuIHxIorGvNC44/rwC7m58uICL3RkiSkNmRYfSrXISLACmojJYlrkBcVjNr8LMDUwvNLErcc0Gay40pjOyrHqoxgSrioSkVh4sJ2AKCpo+lu9gD68MAH0lcId5KE8S/xxJ033xuG3keJNS1VMTMHw/CweAeZtyeajmIzqMeGSUCy0AAAgAElEQVS6RHejGW89xbUgT2U5YVRiihofjMSp1gGVvQy0SaQI08mhW0lfUJ0hZjBCacS0VKXcqCeUXiNMbSO/V9hAYT0KqN3Exoo+IaKwN4E+ODrv+Hhq+PPrB/ogiyolhU+ax9c1KSnavqB9rD5JBsBFKhuYgqzPN82BEPUSqwkwtI5xY2jeOolN7FfgbG6UYgF+FG28GdbX/bQ2/ClLoMyYFpPPUGazwYGsP5L3z9eTHtffP32paL+IpK3HZO8RrSMhaKbBMD1aikeNyUakPts9aJWorOfq6sR+W9D8qJfvn3cibGb9RwYqTgqXAbJkwG9YFp4ZRJaipTfDDDoDjjnhZhPlnpCv6VDk63VJJBFw0/h5HxUWiKNhCjVjFfi/i1/wm+0tt6VsxBAsH/sN33y4IWWzROsChZMP+Z+/+B3/avOWn7kHXtk9r+yer9wjvyzfAfDvdr/hmzeveJpqnIpEFFezIy9w7U40WffiVCAmRRtLftveACJDKXTg0vV81TwDcPAlU0at7o8b9o8NqrXo3NQ+R8XXP3nAfC2Zxu3oMCrx4bihtIFtOTBFzbYYl/O07yr6g6w992SW2Nn5POhRkVrF1Bket45Xv3hLdSnHYPCW0nq0SrS+4FV5pLETOt+4PpjAExvSYFCjGFlWtXhfADy2NX2nCVFTmMCr8plDWbKvBJB5f9zy2NY0buSuOlHdTvx/o2V4nDV84EfD797dsL8s+WJ34PbixN4FhtLl68GQjhZ71KRJEwsx0Fw8XUwi6eyHohGJYYRkMtiQDZjFwFgt5jGrueUcVSqAmvIKtZ0gX84pKap6Yrow9NcGdyzZfhfZfmM4/FW+L209voiozmArj2pGprIgHPN9RSFgz6R5eN6gdaQoAv0c1Rs0x28vSJsXRhfn+pOvP0Yq8sfWfwtJyX9t+84SlnOd679d/b5E4r9Uvy+b+KeWkPwx2/CHqvnf/58/+nP+Kb7v5fee6/OuzxqsgGw2qT/V6KuITNibgHERVQR8IbsavRLTt0KiN2MZZWqWNfzBzYwLxdSLu78ZxbdhddUXw75oBUTQA7ioF7AiVBkEsQl72zF9HZluHO5O9Ofvbhp235Rc/92EO04k5eju9Iv4VPBvJsbGMF1o3FH8Hxa/CICosD3Msam+enEMcmqEiqKN900iNJHUhMXPwZ0MxSGJn0QHKml6LGTWgd5MDK8UKEMyGjMKMBLd2vwqnzCjwbWJ8lmO2+KrUICvBZwIr4UNUN+nxYegfBQ2yDgY+qKi1yWqNZT3sn3VPRRHmdADDBeK9stEedPNQ3+O+5rUzsYEYt5pK0+Rp9XWRAobGHeG06nCH5yY5w3zlFX2A5MISUmSiU6EnEKhTcL3FqUTKsdhltXEppSdsDry55f3fL15xBB52jU8TxVtL126VomrsuMXrx+4ci3HUPLru7uleTtOhURADo6Pk8VHzWXZU1nPMU+btY7QgNkNdK8vcAcBcGw3u33KeY6jwvTCkkhmTVWZAa3ZB0JlLwgVZ98PFgNZHeR3tX+xlrRcC/21ov1ZoHjVUpcTZW7SjY70k2UKhqOr6QuH6QVA9Bns+fBxR1F5mmogXHvCxwLbszAvYmZGRcsSgys+JytDRg/i3aGCTLVtmxa2lO1km0Ml4Mu4M8SC1Q+iyNHGOWWDJKDOfD3NBovJgG0Nvtbc9zd8MNerIWwU3wP7aFFRWB8qwJzU+398ueP/vPpLvrh95uvdI1eu48q1fFFIjO6Vafm3zW+p9ESjBiIaTeStFwTzIWyZ8kFvY8Hb4ZLftjf8zfs3sg9R45znvtpwUfZ03rHvS/pRTnT7XKOf7WIgqkdIDwWH64L/4fa9fEZS/HC65Lff33KIiv12lLjT7FNTWc/oPMfsJ6Iy2PjS1wQE7LEniKPjx9sdv7h+BGDnBg5TyfePl6Sk+LDdcFu3i++G0ZHSeVnzXUFMiqaYFkbAcSgYdOSwr/lWJfyFRqu0MJmObYm/r/mPzzUfbg/81c0HfnH3wG+4YTgK+2KOQH58KOi/cvzk8oBpOrq8Xn2QiNvUCXhJUqSYVg+XJGswRS1eKWG9381rabm/zm7CL0l3RlgPsZS/DeW9YaBYgANbeXaNXOMA3x1fUz5qdt9GQgZU+leGVEbsUeNVCRcTtvFLkpI6WuxBo46G9FSTFHRFos2+HSoodr8R8Phc5zrXuc51rnOd63OuzxqsSAaGu/Cp80ZkcZhPg8Z7hS7DMgFTFhKR0OSHUptgWs0TU52Im4BXhiEKa0FP0tDMpvR40IjxZlQScacCnyRJKAugCBeGspmwFz0XeYKoLo/8cHdFcShp3nuadxO+LpbpnOkUtgyoeiJGzdg4maTNpcUMkkni+3SrZT/m4eAoE3J7VEtEYMqGmvNDuW8StlUkJQ1usZeYy7GSB2ZVe/TNQF9Zpo1bjAGjXZvcOZGifqcpH5IkHpzy50dFdIrkEtO1x1/zSWKKO8mU0p1AfyuMAdtCcch07kGm4aGQWMzj1+B/OtDYyOm9jNmrHyz1+yTskI2i/dISXo/kgTuTSqR6pCmmPKkWKYg5fJoykGxaTBijWyU2KSiqgzR/c2RoWydOWV6QXGL/E5ngGh3ZuoG//fCG05OMUbUL7C9LuITSeGoz8Yvtw3Iah2j49njN81NDGg39seB5W+Ocp+/W7FLrvIACf9EzPDumD5bycaWlm17iMuf4S98oxl1eC0Omro/Cxok2T4FfNFjRqQw+SRLObEIL0vz7BrovIpsvD2zKkfhCVzIFg8+skKoZGU3C5+tP5bWmf6gY6sh0Z3CbkeHWUr/TS7Rn9KCixjdpkRbN517Ws6SthHKVI8xrEGTNmCmhn2YjWsW4Vcvv+42iT8K+mk0R9fSCKTWzLDwwCHsgnkwGNOxynBeD2ZRTfua4V6B4tviN5cermm8vX4GLlBcDX90IC+JVfeTnzQM39sTW9FRqYkqG70ZhTvzt4Q0+syQ67/hw2nBsS6ZDdoFM0Cc42A0/loHY2U/uCeYoMqSkwe8iILKBp6cNTztZjzeZJUJnsEfDOGpwkft8IV80PTGJSWVoLeMcOTxHkub4TD1Ko68nOPyw430hB6Kyno+nhuFbiYF5ZxseXnd8fSdgRmk8P9s94Tea1hc8D5WwOQZhRoQ53vOh4HnveN5usKWnrHJc7FNF/dagouH5xvE3f6n5n958T7pVfBPlOE6HErdXlA+G1u/48OeRi2rAGlnQpfWEC8Vh1KSDXSUVuVRQWeahMTlNJrq0xO+GKjMxbGZjREkPmdksehTQOWYWSvM24Y5mAQ6mG03YtdzVwjo5/rzg8HjD9d/AxTeZyfWkGa80xR6SNgw3hv7LCZ1Zb9GklXkkZCRhKs3XtBIwOL6Q/Z3rX1b9U7I0/qHfd2ZbnOtc/3z19zER/ikZCv/Y+u+1LX/f9/59bJR/rjozPv74+qzBCnTCXo2kpJbIzBQU0RlJb/AaekXqzeIWTyH03lkjD6zRlIimOFWJWAdGq/G1SEJmxgBk5oYH5XJzUMsUbZ7UKq/QSRrg8FTQbwzlduB+lIf4n94+8a9++o7/9K+/ZrxwXP7a406JqZHvsD20ncU1E9YG2ED0a/SptgltAilqUlKEwq7IAcBeJqwqkR94FXqwhMqs2xglcjJUwgzRE7ijQsbPMF1oeNWzvW3pmoKxtzCu20AVMEVE68hxU+FrQ/VxBUz0JF4HyiumK8Xm9Ylh4zh1suTMk5V0hE5RPIM7pCWpAiRi1TeSauKbBH/WUrnA6b5h8418xsVvIs07YTlMO8nO7HyxSBgIcKpqDpmmziST1OJBr+cxgprTL3KDPDfDehIARU9AXiPTRq+u/AW0p0v+424rzelmZHzb4NrsWbIxPCVFPzrebbZU1nNdtoy5AW7sSDc50mhQnUZFQz8Y+iKu6QVRERtFDJov757oLh2PlxvGx9w9RbCtxrTCtIkWpl1imn09TgJomV7YOcmBryRNAED3iuRilgjJmkkmYfI+xDISm8DmruVue+I0FnSjI2SRv5/M8rPWiboZUJuEArqcImG6CtsZBl2iboRd4Q8FhfTx2FbYEcorYjEDLpCy9MN2M8NJQBhJ0njBNMpAS/UcUSFRPkfMqBewwx9FphQqkYQkI4kNM3AXywQ5SWWOiTRBwB+TARWVpPmLTkDSUK3vB/HAMaMAjfGjJVrwjeOba2HZ/LoK/PvtTylLj1KJJjf4o5fr7XCs11SN0QiA6hXqRfKMziBk0paik9dn+cKaFCPNtLcB3WnSY8F/MMLOuNi1jN4KSyCC7jQMmk4JWOC9xtqItQHVAA0oHdGzB0y+zwZv8EeHOWns3vD+vbBDlI3EzlI9aWGdBc0QG36bQcHdtuPV5sRF0XNTtmiV+PbpimGYs34VKcl5KD8YkjX4uuC0zSyjzBpxR3BHzYErvtueuCw6LrcCBD94g68t1YOi+qA5NDuGa0dRyD1Aq4RRic1NR1cV677NLJ/REKxGP5k1LtStIC0qr7dR0kLUpAV0PawyEXuS1KLkBCSsP6TF86ILhueLhue651V15Jc39/z7X9bs+4aL38h+NvcirdMTuC7SvFc8eUf303xPsC9kWmGVd0nakNzDxku1yLDOda5znetc5zrXuT7X+rzBCpVQKhHDC1RfgSoCyWsIMh3VnV4eNsMWidg0SQCNkNkHeUppOiWSgCqQmkC6HBl7i2rNYnBm8vRVhewX4LJh3wx45MkwSeH2mnTSjAe7PLB+0zlu7w6Uv9zzfNkQnaO6Xyd8KoC9d0yXGsqA0kn2J29jAEJysg9K9gPFOgGFheZePqSF1h8dizFjdGtTSJnBigNLAzkeNS0V0xcBYyKq8vjgFu+PqGTCpzWY64E+lNjWfDpt3ktjGAtL25Q024EvbuUL7i83dPsK89GhosT4TZWwOyBHlG4S03VAbydeXx15+/01zTeO3beZtv7oBWAojcS8PkjDOPsh2DaRtGK4KghlPgZlonyej1HKHg2zXCY3LGY9j7GQRlRNORJUrWaToVBEowmdsD/G1mJP65g2uQRe0z3U9G2B0omHbcOUG1RngzT0Ss59QuI7U/ZUmE92ai3TpOEGfnbxyKvNkfurzfI97VAw9I5hNGgX0Sbwk0uZoh+6ihgV42iJk0bpRLMdeL3LppfHjRgu5ubZqITRkYeTdDrWBGrnsTrSTY79qWIaLHHMBymze5JORJMIheei6VEqLWDFtElyzXQa31ts7RluLDYDIuVJXldBMdwIODVdSjQjgDkZij2YLsFOEarEeMkimxov5GCNjwZ3SrhTWl4DcF2Ce5GJ+EaMaJNeJU2hSYTZaNSzMKUE3MgfolZAK2kxlV1PkjTQM9ghLBawvcK2WXKkDckWTFpeeyyk4ZxNQFVUWD+DJWoxCg7NKjVQITMbyIDomIE2WIxDibmRriNxFzBPlvSDgBGPVSmgrc2ymJjlZXkbp7FisuK1o4uAUmB0WmRXznkBLFygB4K2Iivbrx4tOktHooPkE+6g8EGYHQ/bgsOuZtMMNOUo0qHHBpXvKcklVO1Jm4i615hWQKUpgzjTZaR/E4hOUz4oNt9pfnXzhjc/eVqYE0Xp6e8CKImKLe4N01gzZuNggsI0npvLE3UxodQKxAB0oyNGRWdq/E6LiXMZUZnVkLL/kWnVYlQr7KZ8muZ7eIKpTvSvRDIzA2vuqJg+lnyrrrmtTuzswF+8uec/ccdjKddc/U6AB18rktY0957dbxXJynHuv5gYryKhVst2EGUNAcRSAMmweakbPNe5znWuc53rXHP992ad/EuKpv3H1ucNVkTFdHJrsw7SULiUJ5BiDKgG0DPlv9fiMZkboVnzPxMT5gbAY0jFhCs90QW8c/giN+qdxrbyXdKMywP+bF45TzlVTsMwrVom3wD+UPLxYHnzy3uuft7xQ3mN/7tyabBUBLdXqGAIlUg8dFQLLXkGSkIlTQFaHOhTfMGu0NLAJCNNnnuKkiKi5TP6K0MoZF+TEtmFHWQqDdCfDHrStOOW2ESSStijoXiaqfiaUBtClQjbIP6FJct5WPb9JHT5vqs4/dzSfC1MiG09oHXiBPQ4YiESjNlnwG8DlJHmquN60/HU1tS/Kdh9Gxcvg/a1ZbjSjDuZMBZP4mOgs8+FayO2jdQfNVMjBqS+VsvrM7AiBpVKkjNeUMKjy4CFUdgpZf8G8JlePW1yo1vl468T/iJKygDgtiN+sDAZORdBcQRiP9P9M1PF5ffYRJrZK4tUXqGi+Gk8dxWNG7kqu0XHr0kUlx6buyGtEsep5E0tKRaPY4PNho69d4zRcFV0fFWLl8L39RU+6eX9pfFM0XBbnZbP248VH04bnp42xJNDTWsah4p52m9FYtLtK0rnlyYQINxOhEGLqWJQlNVEe6XxD9JEl09Z6nGCaSfrgLuBIhudTltL91wJoJfXdHw9LIkpZJ+A8cZgWo07aornlw2iABG2A5LcE8LqyUhIGRRQKXtmCPCQDC9YNEnYKZOkkYQqCSNjNp/Umb0S1vfMZowgvjICRGT5yCJxWj1eVGBhJglzg0/9IhJyXUcIpazH+V4jv5vTYFqNN8DGEwuRDIDIC0KZGF8FUhXlvjmaRb5m9uva8xtLVDBUVu6ngDIRpbMZbQLdeFIRSRnsUBkUnS7Tui0HvbAOUqfwJ8Ozq3iagaiDwR7l9VAnPHI9DDdpAYVnEDeVEXcxMl4Y/Kag/lFT/bbgXbimuBDxV4oKtfGMNpE+WkwvgHHI3h4qKsJgOBaeTTUuh7bMHhJKJbRKy2s+aErnKTIY8v55S/ywxZ7UJ6bDM4shGQHSYpGIu8DxQiSGMzBnWoV70oSx5m+qN/zFzUeuq5b/8aff86vmDoCntzvs3hDKiOkV/jeW6iGx+U6+w9cWf+VJt4GpteIv4lcZSDJyTaTyDFac61znOte5znWuz7vODlznOte5znWuc53rXOc617nOda5znetPqj57ZoU6Zb+GeRIdFDHELMOQSVe0q75djNAyuyKtlO+Xpbzo5KfCMGqHnqNIczJANInJakw/R99FmT5m/XPIRnREhe40RgudeYkN3YMZDB9ud/z89QM//eKBb8MtKkfT2ZPCtnlSO2VtfJIEAmBlaEwqJyAsDHAgTze38p5WK0IlrALbJ5E+5N+RiMjMQuhSNjLMdGcNZtQUz4rxygjLIED5lFkJ4+zXIWZw0zbJtDqfB61E0mHGRPUY2bxLmMFyP97KL1yNaJtQOhF2gb7ORpZlpnPvRpwLbKuBBLTfbbn9XlIwhkvB2J5/Cf7LgXIz0veW6ZsKt1eLcaOvNJUC2yWKUyIOCjsourvst9DIPoRCqNPR5rSJdeCKCjL1jkF8NKYLkTUAhG0UingpsiNlI9pGyhdpJPsuy3UyjT+2FvMs57l4Et37cBvE5LOIspaiWtJpUkQm4CbRtiU/ckHfuMXkMibFl9tn7sojt+7EEC3fqSu64JZ9uCo6aj2iVeLdsMNHw4+9+Ay0vmCMhs47QtQYHZmCYePkIBgd+dhueHrekB5K7JC9Hfy61pJZjSvV3vJsGjm3mV3hmhHqzIIyIjOpNiP9K9lG2xlZvCqvKwvNZqDKCQ5jadj/3GQTAPnespkIhawVV3i0TsRLxTQZurZgfLAL68DtFe64pouoIHHEeiZm9CqnPGS/EptQ2TthXs/JJaJXixdLtEokHZk5odLqgTInEkm85bqOljhfxzIFn1+fryf5j/yzWqUus8wjGvHXQCeiUgslbGZ4mT7HzR403hgo42JcWeyFpeNbTZxjLVX6RAJnRsQH5STMjlCKxGmuWEa8yyaslcQEpzmW86UcD0hKE+r0Yh8VRoE+KJLSC0tm2cdJPFbCNpG+6BkHIxKVmaSTo4avrk509USrNzQ/aoq3lmmW25QRXMRuJiYg7o2YgY4vPIcGRfdYM24sWkdiMBgrG6lUwtrIn99+pLEjGmFazNdb7y3PcSsSnpnlYxPjV3khRCgvBioncccpJ548d0LlOb7dUn9vcSdFly74u6j4i7uP/LR54idfHQB4eNXw7eGKwgT2fcnDxRUXf2vZvJU7/OY7zdEa2HrSZiIFLVLIeRe9Qp/Mp/HA5zrXuc51rnOd60+u/pAc5SwT+dzBCsWq33jx8K9HnR/u5aE6VGmhrYubvTz5zyAAsJptusyEzgZ0aXKEQoAIlRtpykByEe/MQs1GJ0kdQR6o06wfLgyx1sRWL+7sthUKMT9UfKuuubpo2d2e6GrpCqbGkR5tllKoNeIx6+c95HQK0bwn92m8YCojqgqMlWhBxhtx77etXgwDoxNqum2lmRD/BYW6kgYyWvFm2LyLuFbja5FQzIiI7SWBIR7B9prj1zkdJW+jbVVODBGgyJ0i2+/CQgnvXlX4bUIZCJso/g6Ayr4bzgW0jpyGguPHhu3vDLaPTBtN+yZT33/R8mevH7Aq8tA13PeW8dJgu1XH3z8byseEbddjNl7INo7X2VhyXk5TBjpyY6O9+FSMFzBeKaZNIlx43EVu5JOS5ZMUodWkoAhBMeU1OamEao1Q4jsx7DMHS/NjpuXvZ+BHwCDfGGl+6gjbOcc2kbRGmUjwmuOhom1fdI9AOzr224qLomcMloe+4d3TbjmOV03Hxo1clh0fui3HoeTUy4nSOjGOBj9aUlBizZCg3OR9NJFpMsTOorO0CtRy3KRJXj1gdK8x70qRKcw09DLgKo9y0sn3g6MsJ7Y/FanKwezwjcV02RdEf+oi35Qj9ifPPExXFI/iTxKCwticnmADSiXKSprDtpo4uYoZg5xOjuKDoTisEo+X6J4ZQD3o7Osi94y5cZ+vKz9ouSY6lT0xFGZcga3oZi8bMsiQZRqzd4YEdBCcYtqtwMQCUGTjxmQEkEjZV+Nloy6yNjH7VJPKCTD53qDS4n+xgA57Q6gjKZ8rX8/7q1DJZGNhtdxC53SVOfFEhRf3KuTfZjPVpEUOF8u4yEQkLkjlLp4lbclv88v5mDDIeSCthrDz5+tJEQdN/WoklJqxsMRh9UcZn0pSgk09/v/svUmvLdmV3/dbu4nmnHObd1+XmWQlq4VlSxOPPLSnNgxo5rFH+grWR/DUsAEDGhiyZgYMfQMDhkaGBUj2RHappCqymExm5mtud5poduPB2jviPEqqYlWRrEr5LIB8N+9pImLHjrix/vvfED8/Mp62+Gc582sQYmuINqsMq7GkvWeZDFmBPxkMMTREk5HBrgbNPjN2kY+bDVNj6WzASOJx0sE7TZ75WmNws0tIm3Bt4PffaMrPafbcdided3vG6HiYeu7aI9NOj+EPJXO6v6V/J+x+YjjEa95tBm6bI29bBSt+5+Ydf+fqa2I2/POH38J+mXkf7nAnHfvNO51Mx7ljvo3IJuDbgClxx9PJw/4X8mYvdalLXepSl7rUpb6H9T0HK7Jqr9Oq102i/ycJMroCmZtEWvIO9QFfH5ZlMcyrT+zRrqt9kkBGQw6qiV8sIZqkMaBt0oQMUCO28sBsrTZvYjLRZHIjRG+J26LN3hvMrFrqEDe8u2vwu2l52HTbmVBW1O2pGBiKrmqCskGW1VvDsnK/iHp8wnUzZpNJScjXBuMic7CchnLKZ0FGg3/UfTGhPMfP+pBrZvW66O7z0syEfo2PjK2UBAR9MblcVmLrfxfvz06Yt8LwwrD9LnL1lXY+7aNh2mlCw/Ezq82tZTFLPZpWmTEHx+7HjvY+M2+FeSdMt7qNfjNyf+w5nFrmUzGqvJkJShrANpHhC+H00NB+sMisYEUozIj8ZsT5yHxsYKpGkQpYgbIaktd4XHkx0XSBm25d8f/2ww1xr1Gom49Gmz2B2DXLmDRHTbhwe51r7gib74q/RNDxk2QXo9bQC9MLy1Abdld8GsrJzVFIsyzGigD3Tw2P283KJooG97Xuw3GbObRbZTRsZ3ISNdosLJ7sdWVdJqnEILKoWShAbpQ5gsmk67A03aZZ6Ui+zNswW5I42q8cTRJSW+Z7a5lv7DI/p9Jg/87rDwA8thPfXV2TP7S0HxVgO50aUrmeugauu5Hh7YFT2mEGITy3hAIO5qygi/oNgDOJ7dWwnKdwY7g318TOYafV8+XcELf7mBd/kmwLGDecgRUdjHeymNXaKSMPLKvZMelrdlKTVzfUwSxzzaixpxQj1+p/UgECpMz/6lNh8hJlfF5q4ilq3rk/89TwavaaGvVQsSdN+bGDWUxCU1u8JkrsaD22OpVimwm7ArKMlcX1KfOsjlu2ykjh2S7Mi9TklY1S78ddJlXgrYAvaTYaFZ1RIKWATnJwCo6MhmHwq31p2UF7NJhRiCfL0yvLbjfw9GbGzM0nnkMkIThHaiJNPzOfgV9pVoBZBquMEqP+K2Y8G8dZ+ObDDcZGbGE4jUc9UfnkcK9GdtuBu+2RF+2RN90eVwbp47RlP+uAbN3EPrQcg188Lz67euZf3l3hjo7Nt5ndT4XvvrjGm8RxU+4bWXjpD3gz8zh1bJuJ0w+feTroje3uXwjbbyJ2NOy/tMy/FRZ2EUCKhvnFjG3PTtylLvU3pH7dsaqXyNRLXepS/z7VLzIv/qpMi7+IsejfFFbH9xusMBnpavJHNZrT1T0ZTFmVVvPL2uiTIc9mlY4k0YbwzL0jN4mYCqMh5cU135SYiJR0xbBS9ckoWFEM/0JWdoWYpVdBmrSYekZniLPBTI7mQbCDJ+ws8Sou75UuIjYTW6v7KCoT0DfoPqXZQDCruVppAglCnMuKvmSsi/TtTLs7LZTwEA1ztBxvW6VvJ1EApn7HbHCPlvGFwZ1KXOTNKrfJUgCNwWDnAlZUNgsQr0oDe5OZXpmyim25Lk2cPybsnAtgISQvxaiujPGTwczQPAqbbxPzVpiuRQGTYsK5f9hgv2nwe6EZYbrOzC8ScqPL3b4JvNgd2V+1PF9tkHuvjX9NTYmGORhlPTlebBcAACAASURBVFSquJSoVMD4YrL5YuKz1480NjJFy8NBV1nzdy39e0P3MdM8JWKjDWttMsPGaFNqtHlNVs095119WCur9DEjE5h9JgymmJ7ql6QmE/tMdjqXJQtmUNM+/QaVZKQnWwA6bSSrZEgSmHsHCbJzzNcZcRlbUktSpyaRGlnKIlWwi+khZNHP+H6maQLeRlwBKOZgmaOuTDdt4JiEbB3bn69sn9gKw8npcRhIrWEKhg9bTTT5wdUjGz/z8+6a065DDpa495xqCkSrX/T66sDXkyP+vMc8W3JZaR43BvGJGA3WJryLdD6wbXQeGMnEN4b9pmOaDZwsZjRrQ34SeFQDTjMnvbwKIFFLoiH2yrJJXht4O61pIdmorCo5QZ4ykhQYOKfimwjuMWFHWeQVNZEkeSF0OudAt2/O0kIospQqOzGjMnMWIEGUMTFdC+OrqIBshOZDYVkA462+T4KCJ9mWa+FMfhKdMrNSX+aCrEylBYAorA+TwB9EzYZRYFKiRjmrZAWdWOU+aZtETspIoFu/U+p9dFADYIkQ71ud70kWOY8ZBXfSa2wyDScXsX1kehEXmYdEvReawRCfGlIfELPet5ByX3ZFr+MSQTKmWcFsMqSHhpSUxWZGwZXvt6PQfPbMH7x8x6v2wOvmmY2Z+Mmg8ranueOrxxv+cHzDzfaELUa4tqApRjLNi4Eh9kiytB8z9quOr+IdD7cqFfn54ZqbdqCzMw9Hvddc9wPz7+p18HS8YvszU1iEoscHhJIyZF3i9ef3bPzMn3CpS/311K8blPh1bPfXDXT8Mvt2AVsudalL/Vn1m0wx+bdt668DwPh+gxVlxU1sIpd8PTEZspDKCvACStQGNUO2cX0tnAEXpaR4U5BEwYbZkKNddc+iEpJKr14kKNO6+p1hkaEgKAW+PFSKT9qY7xISDWYC/2hWKUprYBuwNiL9v/sPV7YZAsgiWyiAzWxJoyHWxtxmps7T9jOuaLOdSbR+xl4nrEnLqnQoNO+YhcNVx/GFw5yKHtydyWb6qADHbHDPRoevxC4C5E3UBqpVOQeSOT5uF+aGHZVZMG90ldfM+vna+Jigq6TNXv1HpishbLR5r7R0/+OW6z/O+EPExMxwYzm9sZze6EP/aeeJ0dC1M+12Ytw7zLjKamTf4A+yRE5mqfGWlTavbAJrMw+HnpyF02OH/agd5eZboX+XaZ8VJXDj2coyME/qFVKb3tBp4378sqyAevBPBjtqo6xRq9UbpExBJ4StqDTAszBqqmeEZG2mzMxZlK4CHHVuuqNGK2okZvE4WXwCFHiLXVquEZnNIoWxJ6NRvm0i+oTvRxoXmcs8mYLldGgxNtN2E8YnptuE/eN1G3EEkhB7bdDnKzCj5b3VlWKRzIvuxOe3Twy7I1//6UtkNosHwjxZHmbL9s3M7fWRd48t9tliyz7GKOQmM0WD8ZHUCtYkDlORukjmbnukb2ZiMkv8aijgYjpYzKTMGHfU/Q6bcj1VXKsVYgPDZ0Gv4+JH84lFcXlvc2+YH6yCAuUOK0Hne/8x032MCog4OQN0DNOusC+KT0oWHTNYpRmgc1SCft6UX7qTSq38QYh9uQ+J+rX0H+pcKACVq/KdwmSqxzCUbQ9W/VhsVrDs3HejzNtc5qOd1A+kvp6czv/KtCIBRcYRq0QksQK9wSyyHzPLIp1xj2ZhaFT2SE1fkiS4vWFuW5WgXAViufdKSdgxo2CfLflo1vs0aveRXfE08gnjI5wxENJoFQAeVTpnJ/DP6x+HLBCTIWXhu2HHPjQ8TT1f71Vb9rjvmR5buq8973db0usJsVnZdujfJ2My7m7g6Bti62k/gMSG5zIfn+2Or13C+IRvAikJIpmX15rQ8/Pf94Rdi9sL020iR8Pp0CoIj7Kerl6OGPl3/+241KUudalLXepSl/o+1PccrGAxNqyrcwpWqPSCdLYiuC5mq0TDZoxPpKiN+mLQKejq29nHkkvFwKw8EEdWCYldVyZzaRzMaLSZlLIyahTAWOjOhdoft2k1Jgyy+DaAsj+CWNW/WzX3y8vxyGLgKWE1D63/1ihAqJ4WkKxj2LoVtCmME7GZppuXh+lcASDJOB/hNhKvDGmy5JNdGv1zJkuuRoOGBdBJhb0SserVkYGrzOHzdQztqAaTtdGWtBp+NntlHJBhuDXEtjTidqWob3+aaZ8ibh/JVtjMGX+ytB9Lk3vlmK537O8SqUu4o9Ho1TIEfl8MQyshpVMZRl1Jnne6qp9GS/zQI1G4uVdQAcDMWRkTThivNLYytiutPhtBkq4OaxykGqJOb7SL724HTs8tTEYjHE96bJIVzIHSuB31XGuTWIa/yG3qtqQYVCLazNXXa4SsBJBU5AHzSns3szatsxedv6JMijomagKqJq3hZLgfrZoqhnIeDxoXml3muHNIm8i3gdPLBr+vc02bZlMkDBodmnH3evt5l285vmr4wc0jnQv4q4n5oV2MH5mFPDTc73q27YS/GZlTi+yrZEl0QVwMaTJM0RBmh7HVNBG4OtK5QMoKAIwuMjkFnYLLnJxlmNXUFgrIkNbbRi6eENu3B3bdyBQsMRn2ewXGxGT6fsLbyPO+Z3hqkNGsxr6z+luEraV7b2ifI+6YMJO+ITnBH9ZrQ02BM7FIacycia0wXRmmK71ujp/nBaBsnqB7D81zZvO1YbpmMT8NRaZRDUZN9Yvw8okURQL4vWCHzOkzS9hoBGkFXBafDynMCZOZr84MOkdIBVBMXVajx1EW4Cv5lSVR71tmZpGR1BhQcrk3LL4eZa5biD5jJh1P++RIfSJ3cb0H28KkS0bjoo+fBl7FXo2AczFEztEoQ6809uIyuAhBx5hRlghogPk6kwfPv/74itPoVXLx1CyArgTwQejeweZr4bjviF0mbOtEgOwTZhPYvT5w7Fvs/9PTfhTM5JZ9rBNv+nyk6WeGyS/7+Pb1I8ON5+m5xzeB8X2PPZn1751x/CvzhlSvn0td6lK/VP0i8+FXwXL4izI9zt9/YVlc6lKX+ptWv2pZyi9T32+wIol6RpjM4opfVu/EJ/Jo1we4uspUG8msv3NeV/2lPueZTCyNWArKdshZoEmkyowYTaFlV8CirErXbU3alJFLo1Qe8nN5+s657K9PpFtd3UtppfOm2cJkyCcHJhN9gvnsYbRWoeyf694BXEk4kayrwynoQqYd7dIA2Kk8hJtM2DRMjqUJgfWh3/RBMRrRpIwKUrjjOnW0GRZ96K+mhWeACVkb8NRkprsCiriMOSmVuerildZctw/Bq6nnvNXGITuVQ1Qwxg2J8coSWjU/9KeMf450H4rR3I0jGzjdGaZrR7bQnnlwmBmaQ1qMDCWqLKNS96crITxqE3Lzx7EYhcbFKDVsDNPWEDYw3iqYMF+ndTV9Xg0Kl1VnWOQ6jQ/YW2W2HK469XIoZoDNfWGgDLqf1UMlWwVtYlfGyUGeWYCzZTW79kaB1dyxEQVBBm1qy6khNYIZLbHXcZTEAtg0j/rZ5ITUCOG+UUCkLEZXJky2UpJhErya2P9Owj2vjJ/2XhvumiSS7coOcR8dB9lw307MwdJ2M7P3mhgBZZUb9h83jFtH30/EYCiXKWYw679G2SLRZaIpA27gfTRc704LGGdtUjAOBeZkO9G2MzkLriSiwGooGWaLLQyNu+7IEB2NjfzR/BqArpn50e09Oz9yvPV8HLY8jw2nsVk+H6Lh6a3j8GTxT47+24w/lOtlLv4lUe9FkvVaap50kCRlJFuGF8o4iA3MvzUhBQAdnj3TjaP/Frr7hCRDamC8gfFFZclUYBT8MSvodtaIS9D9aJ4Tkq36w1yfmalWvDaLyuNEiNvEVG4q7iCETS7MjeKFcg6mBlkACXcoCS3DOpfnnUq8lHWystKq1AajUrOa2OOOQoqGvDcKjlDuEcsFXrYb19t/KqbFOckqeRNWg02boSssOKcyqNSwjsHdBLPh4d0OOTjsSdg8ntPy1DB0uobNN9B/m4mdML4of1M8IIZwskw+cn194vGLhv5n9oyhovc7CRC2HrOdMCYzznqjv9uc+FsvvuPwsmE/t/zh11/i9mfA2CTk+36ZW5e61KUudalLXepS39cyf/5bLnWpS13qUpe61KUudalLXepSl7rUpX5z9f1mVmSQWcjG8EmUQUbNKItMAlbtuC7tAcFoiIhLRUec1u8VpRGn2a6mlS4vEX3ZRvW6CLKs/mHyIkVJjVLdl20Xan5d7c4lOlGahG0ivgm6uluJGZNlDm3ZdpGzmPzpMRbaf/ZAkXDE8v1TUkkCqazGl8jFmoIAqnGXCCYKoZOiM2elXpcYx7ixKiVxutJbVyjt6Xw1cWW2LDTvyCIZqWyJ6QbCi2o4oYwRmQ3ZKUtFVwaLD0Gn+v3Y6XmuKQeVJg4wXas8IVldiXQH6N8L7SNlXzJ2TNghEx4MwwtlFiwRsJ1KB841P80+Q6Hm20kw3+o2+29HED2vydYYBZh3GkUZO5heRczttPiCxGAZB5XyMBmadxY7CP1XOhmf4zVyM9H2M9ZFnM+UoWJe5q1gT0KuHhPnsg/KlKr7n0VNCVn9IuxpPbexLTGdZ94gdlKqv98rgyV2OgeqKaPkDBFMUnlA87CyPEB9GEJf2TZKu99vLf7zI/FNMcicDfO7hube4M4SNs7nmpwMD08bYjCr4WJTpUmQk8G994ST5XhnsC6RN3qQSSxEwT2rqawZzSK/qmOWJsNjlMU/oMqeykGy7Ue+vHlg4yaciQxRV7FrssP745YQDUPQaFjdr3lZkU9ZmJJlShp3+dn2iVe94bujRsjOxdi2tRFbmBs/e3dLftDJ6J4N7mAWiYadKH4jeR0rgeFVlShldrdHPr/SuMvTa8/Htxse//SK6z8yuFMmiDC8SXBbTuZDA5Kxg6G5F9qHvBiFQrkfBAi9wR1122aSxRA2doUxYcp9NyuLItyU+d7rdWzmwiI7k8DoXM0kv/rOyKPgj5m2RPgejNFrzKxMBjKkbpVQUGKW7aD3FTsK/hmmm8KGusnkaWVzhI1GO1dDWmUaySJJWhJh4joXqzGuZJXq0UZsmYttOzMcGsxeWRU1Env5+5JgvonMn0XCtqH/Tu/V6/1SGWpmFCbpeXph8a9PnHJP961d9iWLfqd/MowvPDfXR5pyX2lt4E37zN32wD99+BGpS8RRFumYJKG9V3+US13qUn/5+usyCf1lt3+RiVzqUpf6665f2vDzH/+vf+ltfL/BClTjn/KZ0V3R3GdKPF5J0qgPzp+kfsyGXKQauVD7U1KecooCQVbTTFYDQttHUkhqaFb8CEiyaKtp1Ysih7L9XGjGtQkT/blSuGM0mDPAxNrMXGUlBQQRm6plhjZv1STSZKw7a7yA0MXVzG4ukZwY7MQSeZmrER6aUqA+HKwifYCTkI4qbwgbNdur41djCqsuHoq2vDwwk8q4ytpofcLjKSZ7OWfiJi8u/rFbNe1ILo2zEBttOswkStUGhpcKUoRe902CMN0Y/LNuyO8zzcEgUU9AcoZwu9LOa9RoPW4zVXmC7oM/ZuygdPT52pO8EIvkBCBZTSdJ1XdjFuLeLZGdSMb2mgkbJ0PzrMkJTWnO2gfL6U3PtG1JfUmuEaBJGskLBGPUpyMU+nuRdVTfjixrIkSVidSfgSWdpB5rNWocXlaAgcU7wA25eEqsPgLhVqn7CnAI9lgazVSbaFk8S/wh445weuswdwPXW0VEnI08X3Uc3m/w713xzBDCrsz/TcIeDfN9i8zq92IMpE05sDIu7UchHi3T3GmMap2rTSpGjTrXJBTsrI5LVjAk5obkE6mLRHfmUyCZOVpCNhjJWMnc+IF0Fg+7dy3PoeXjww5jI8ZkNt20vD7Njp8+3GIks2mnkuagySnn9arf88PNA1+2H3n/ZscfH18B8NPnW55OHTEaUhKG2eF84Hnwyz6m0eo9ZLDINuhtpNwzXvV7Pts+8eN24kN6ydWPNRaYFxNvXyt693zV4W1kmDzH2475o8Mehea57FzxsQg9dB+h+5DoP+TVw2Wrfh/VgDZlvcfEWwWNcp+RvdUkmlyu07yClYjeN7JPzDeCmSwmyGIA6vc6/1KjpqlLcomrxqB6D0hdIj9bbJFH9e/TAhokX7wuylyImww38+LfIB/9Arqqf8t67LXMJJpcI5D6jO/CEis9TY48G0wxs52vEukF5AJmmMFgrma+/Owj313vODTXuOMKiiSv92wzC907y3zscH/whPtiz8nsdDq/V9AtOU1bmR8axn7iswJMbdzEmBzPsePDsMVezQSXiIeaIFSuS/fX22hd6lKXutSlLnWpS/1V6/sPVsyrdwQUrbOHGjeaLYvOGUrjXFkKJbIzs/pJLFUYGktDFGVlNkjGen3ozlFWBkbxlBCfME3UVJAsagJ65qsBKABhMikKKVpylrV5KkBGLmklYhPWpeWBGdRHI0WD8xFr1SS06vGbtjQPGebJMR+9OvqLWaIQQ4+CB8X3YkmxWPwcZEnk0CZY1LugrHjON1FNQZu8gjAmkxajOf2yql0HSG1awB9tJvIC3NTzssSK1jGPQm5k9bgIsoIFTS4JHpncR6RJnF7BqexD951jPMoSszreZW3Cq7+JLfsUBCyYQQgbs6xQ+mfBTrJ4LKRGVn+QUvW/TYDdT7RDqmBIbGC+SZhR6B6F/tuMLaacAN19pv9gmLbCfK0r0fMO5ptM2Gl3k7pE9kIaNZlAomDSypwwxQegpOouq9gV0KnJD2GbsWMx6RTY/37xQhh1Nd/tDXYoc8vA8LZ0VxY1Sgy6yuv6ekGcXU+o94A/6Pm0AwwfO+K1vnh3c+Dzmyf2/cC3zS3u5w3+SQi78lU+YU8W/+yQYiiq/h+lSb5JGsP5lImjYCbDNPllLqYrTehIm6RzyiyOt8uY2Ak4GJIXNdl0aY3UbCPHQ8tP5ZaHrqf3M1s3kc6Qu5SFOVrSQ6MeMklwbxJto+M4To79xw1EYb8JjNeOTTPjCpiQsjBFy4dhizOJnR35ve47XhRjgd/dvOcp9MzZMCXHnCw/6B8WwGTOlse5548eXvN46ti2E8+nlh9/uAOg9YG77ZEvdk8Mv+t4lhuygbaf6Zzu4+b6md5pd/503fH4puP5sWco/jOSBHM74ZvA80+2kA39+7QwH9yQCZ0yLWIHMYCxkJo6+fRa93spyTTlflhuW7Ey3bIQryLjm0hyhorCuoP6dsRWPVKyAzusYElsRa/hbdRzX2JMs4HuQfdxvNNY2Bqtmg2Ezpyx4rKaqBbvjnOQopaZUMDaZ8zBMlu/UJpkNNiSKJQaTQtqdhNNmQfDqSHOhpgMX9w+8ZPfcgz3LbYYfdZ44BrL3N4Lh/ueu7dPpDcK7k3zFjuo4XIWwT0Zjm7LfqevNybyx/tXPAw9H/YbmnYmN4G512s2zIb5LQwuwX//bx7fpS51qX8/6t/GvLiwLS51qUv9+1bfe7Cipkgs/500trSu1GfJ2nBVnCEoy6Amc1TDyOWhNX968881cjSY5bU0WYxPyoRwChLkZKiOf9nqA7nYYhZHwtizPyCSMedSk4SuDNfXm7QknOSo3x+jWZM6TFITvtJ062uQ4rqi78rxVYAj+0zMK7CQXQZ/FndQne/qWJb4Pv8sxfAtL4acALILkME3cWF2GJMYO6W1pygYm7Em6f5PVh/SP67dfl2Vz2115cyLyajYRDZG3+P1/JjR6nieJWFUJgFZP9O0gbzRgxpMp3R1r8yN3EY1Xp0rPUT0/EVBmkhsDfF6ZdOMR2WjmEmWSNBzTEs4a3iycP11onmOy5iGzjDtDP6UkaTxptkKodU3dMfE5puJzglhqwcyvLCMR+HwA91QvIpL0wJG00Kmdc77ZzUETY3KWmrUY20Uw1ZNN8NVIltlaWQLb7/8uBxHTIbHfcf40Kl5rGTaz7UxEsnEaIjRMOw8Mpoyd1aavkwGuzc0T4V+bzTpI5W0jm9PntPdgRebE1e3R56fHP7J4g7leuqsAkODsnzcQa+bea+v751ZxtTM4AR4lAUUmpwldzUVIitgOZ/JsCJIMrgTpAlSsMTNytBJNpMHy9O05dC1OBfxPuJtZNNocz8Vw02CaNzwLKQk7Dp9PUSzsJjSaDkODTEZdp0iX94kxuB4PHU8DS3vTju+vbrGmxIlXJbevSQCGVNO8KviuuglQguH0LBrRnIWnk8tp/segBOw33b88PU9P7x55PE/HIlJm+ZTMWa8akdCMmzcxA93D/yduxN/evuCp1EHck6G37n+iJHM/+2+4JlrsjULE8iOmWavTJ4wC7ZRcK1KHARZGnES2MI+qLdUF8F9zEwnYYiW8HpmepOgGKFufia4U9ZY47kwC44sqTIaayoMry3zdcKOFolwem3o3xVW2qjvS07lLe4oxL1nfFk1eJVFpmBIrHG99e9DkaNJUomcfxbS4BdwsMpcUgPzFoiygMUAvgnE+y0//ckrNi+PbDcjj6Oj3prr/TV1IMnSvRf8d56Hdku/VQpK2kYk2MI0ywqYvrd81Skw9XCrDJzjQw8m024nrE1st4o2Ni7ydvfM71+9u2AVl7rUpS51qUtd6ntd32+wIhfa9xlYYUalmEvUBz0zC9GyPI3KLMgk5FaUQl/lG279zirrIAMuIy6TpSRyAIyO2EWN4qt0cpfJhRpPUB+FokkByjNqfSC2EMuDfWUPmMGsEX0Y3bf6FZNRBkd94C3MECnJJeeAR625ABFiy35ntJmrdGWfMDWyTzLeR5xNy0quSGYcPfONJx9tkWWwxKsayVivbI9z/b/zZcW+0cSFKm05pO6TdBYpMhGAlI025BhNtqDIcirTw+iYSlkpXcAKo7IUGQ0yWFISxsFhet0Hs50RA7ZovVMBfFLpGPJkF9+RnJQ54rqZVNgDaSekR782vbYAK+bTRp2kgEroheYJ/JMeRHOfKYx5Dj/smHcawZr8OgYtYKZE8xiInaXZJ8ysyRoAgy2afZtJfSINQh7WWEc36DiELOQtn8p40Gsged3vCMSNAlXjXBrEdtKV9x08ZiGMbpFHAXgfaX2J/OxmUtLm7LofyrkTxtmxP3QcnxuNI82lUbwv4N2HhsMLz/Obns3VCLczcwE3dFzNJz4Y2YEZC1MDbTinm8R8pewPbVohl0hM/2QIUUh9VFaVS5DMGq/rlHFlT4IbhTRDFrOCFUY9L2QyxFmI4hkBswkcy4q5tQlnE3kXMM8OkjCNnqmAGcZkaKNOV59ISRawAGDjZ7bNxGFqOE2ebybPu/2WrlwvjY20JVp1jpYpWv5f3rLx+v2fbZ+48Sem6Ohs4GHscTat95TJEJLnG3/Nl3f3/Kdv/xVztvzT9z/i/qiAhoie98ZF7vojr9o9v739yKEAjIfQ4EzES+L37j7wR0l46nc07xWoaT8Wn4usjAczawrOUoWtUCVEqSbgVLDioFHBZlYAcN858u3MfKsn/hQVHHWnNTlHUsYVxk/3GHGDspf2fysy3YpKuzZZfYvQeWFH3Rc7QP8uKXBXzkPY5iWpKXbqg0NmiYA1Y17uOyaqD0TzuHpgVDBQGVWCmR2n3J+Bvuo/0t5bxo/XHN9OKtupoLgUAL1JhJ0Q9pb2XhhMx+FNRV71++1JmVCSChvkp3qe9o9e408HQ+oSk/GIzWy2Coy1LnDXHtiYVaZ0qUtd6v8f9Wf5XHzxT/Iv5cNxYWdc6lKX+ptU32uw4szX8Sy2TVdnVR6gq2hmZtXv5rJSXqQN6rsgpHPEI6Pa+Qi5yYs/hDnpA68dhDipBCL6pEwIcyaHCKKr91nKTlKiR8vXO31Apmi6a9SklEY++wyzWUkeQZZ4Rj3YgoHYXCQOv8AOEchzYZY0ZpXANBHbauNuJJOKlKQyA5JbwYWuCfhNhM3AadcwDp5U2BEAaXDkNpIHx5jPtl8aeddGYijgQBLS0SkwUAEZ0f+TVACkM9q4jsG68q3a9RJfavPqDWKVDSJZQSo56v6FXWmSd3q8ucTCprLqvXhnRF19zzZDWckM+eySMFllFtULop7jM4+QGnsarOHpdw3TtacvzZ0/JvxzZL6yPP6eVemNrPGuyRnmjeCGjD8lpatnpdv37+rJNEzXspgYns9zWBvDJS61GnEuDBrUa2Vej1Wi8PxHtwA8Fi8QqlQqgh0N82ELwLiJmE3AmEzXT3RNUMlS2bvOBVqrgMa+azmMVzT3BnuSM2+OTPhGOH7sOPzI4W9G5tcBU1b8mwdhumYBo6Q0ie7EcszZZca7TPthBWqq34A7Cv5JmK+E2ELcFnZLnZY2EzaUVX9ZPluv62ztQizCiBovzkIKwmiKZ0QX2d0e6W8GTvMGEQP3DQ/lO5puxvczOZlllT0Ew6FEl4pkNn5m12pDeZo8x0PHYGt8KjRNIJV7wDxbYrC8m3Qufb25pu9mGqegxtPQKtPKF+At6U1heG55127hTq/xw9QwTsXLIAvDqSEDT8eOx7HjRXdi43QyHUPD1/sbnEls/MQfvHrP++2JD290Ljx/uyF8bXEnBZMQPS+LZ41kRFSqsXg0NHkBHtoPhvZRr/XuPhN7w6GxUIxSR2eYbtVoVMo90e5kMYN1g6F5imy/gcOPLPE6ErcJs505ouyQ3Z8K9rQCFt29zoXpppzHCh47FmA6G6AyJ8otOTtIJmOK/KreuyUrk8nMCoa4o2BHv5rFOpXCNE8Z/yyMz+3i1QF6vaZW2T/ZZsIu478VuvfCiO5juCqAclCMRQ2B88JEstMqdQFDQsGzQ1znzmFs+D/HHwH/mEtd6lKXutSlLnWp72t9r8EKYG3Qf0G+oSZjhc47CYmqWUZX40sTWlfKFvdK0IfkqUgiJlkYHNXLwEyFCuyF1IhSyt0ZcgLKckjoSm9lUNQeNwqSZTFlq/tVqcbL++tiXHWtPztGYQU39Hiz+kfAKtWo5p7FG8L4VYWvEhSjS6hN1AAAIABJREFUJqBRSMCcZAFcjj7RbGZ2m4HrzcDoA4dTQxh1yuSjI0e3aLgpipLFE+NKGQcStfEzScd9wTW8Nu0ZBStMWSWvD/0pgxhZnPERiK2Oscyr6Wk9RkkULTmYwjCZkxA7Qzz3ChnsIjuRcuxqMKn7iayrm9U4sv6cBBjM4omQbWZJZXGZ6Ucj4xvH8UG/w+0ddnBMt5np80llPYNFyrGGzhA2op4Ko4JTbsj4PbSPFRXSfRtQkMUdSqM06evDy3KOLYtR5nmqi5T0A38wiwmhRNh9tSJb2aq/RvK6klvnNmgyxHzlyA72140ycxI81LnpFajr+ol5tpjB0L3X5JBlRfyjTmx/cIDj+LsZfzUyvtUvaR8c021aWSvFT8YWGYl/NEgS5hcBMzkFphLL9/sn9QEJG022GV45wjYtDWL2mbyJTJ3BTApCyrwCLnZUX5LUZHJWhpMEwU12abpjZzg2LdvNyHQ9EZ8a+q8coZi5ji8t/moizipZyUHlNNVv9tS0bLYD192Is5HGCbOzC4MlJyEES4yCiJrsQiTtSwN77HnyLTQJ49MCwC0XVJ2Ls3A4tfyz+9/iODd8vN8u8rEQLPHkYDKcaDh96PlmG9jthnI9wfHQkTNstiN/+803/O7Ne377WiVDf3J1x9fdHe6jX0wjkzsDxtrCQOsScrJ632kTrit+Dr5BsqN5hOYx0zxkxluLvCij1KlsLQRLfFafCAmygMQmGHoL7pTZ/Mxy+A8Cm7uBxkUeZp0r+WcNbsiYSQ1BU1Ma/LHINJ61yQ+b4vdSk5kqsyLoNZPa4n+EHuNiYluYI8mt9yX/zJIwlEXlSfOV0L3LtI8KbsyK9zDdaHpQyAqOKphTAJmP9R5oPjEnjd0KtoGC5X4vpHLN50mZdakAY7NtiKMCIJe61KUuVeuXTTf5q6agXJgZl7rUpX6VZf78t1zqUpe61KUudalLXepSl7rUpS51qUv95up7zayoRmrCyiZQjW9JiCgxlGZe6d+xGPBVI7VFn1xX9qVEec4ro8HM6/tqmWk1YiMb1TDXJIusK7cAycYzOUj1KiiodZSFjZF9+jSRpFCUMZksmdQblmXY8tlln31WU7Yq8TC50MJZPSKKeWVdZY3Fa4EoywqmPa5SFYB563i4tfTbicYF2jYsK7nzrIaiNdayshvqerUU2YvMggnqC5LsOkbZ60pwthmDWT0pytBUDXn9XY08lCBrTGE0C5lF2QEaq1nPtXtGZR+Cbquc98WItJRkHUszyWLQV89B9XzQDYKdzCK1qHMiG0hdRq4Trj8x7/SyClHI0dDuRposxGCJQVbzS59JbVnpL8Z9qlMX+vd6IprDpwwIW6QR85V+x/hSY3LramstO6yUcYnQvc/YUcfHTpnNt0r9N0MEI8TWEjtD6M2aoFDGdS7eErFzazTqWRRjFhhftpB1NXfzXcJOqxmpZCBl+ncz2XpS4xm+zEjxFgm9w56EsE1Ik2j7Geci021Jqfi4wx4FXgXG14Ir5pyx+mqIroh3HxOhFyQbTiKV2U9sE36jXiQ5qSyJMy8SM4kmTpx01T32uqptB1nYVPIgHKXj8EZou4kjDe29rtQD2JNnfF0kVQaaB6OypXpCDOy3DfvdrH4xxbh0kaJEwwyLh4rpg17D9eOjkGeLuXeaylMiPFd6SGFZ+EQMlh+/v2N8bpFhlW5Fl1cmVNa5y4Pj6bpG2mRkUtnYfrJ8s73mVb/nRaOT7vdu3jMFx8d2yzBa/e64srGqga1rInP5fo1WLp4xVzMnn0lfe7JRyU62We8rqFSmb2asSXy0W2IozJM7/f5n3xF6w+6rxPbrzOFHFvsi0fqA7+dyXTSYWfCHxOiFpy8dfp+X+4rfK8vMTpo4UseiyqkA/AHykU+iPz9RCVqYr9Z7sX/+NCUou8x4qyyo7jFhprQkmphJE43MWNJEGr2G3CkvzLrkhPEF5Opb4SA3mbDVbZoo+Ec93bEk/CCQxroNlVC195fVzUtd6lKXutSlLvX9ru81WIHJxCulpdcH8moAickqQ0gCgz4cgjayyReq+bmsomqWhYUanMkrKBHXmMpsWBpgU7whzExJbCibj7XpteqzcP4wW80+gzZHktVLoNKpJcvyHgQ172wCZ19PjqKUcwCX8LtpeV0kk6LBukQMa8eYoiUtJqFWmwwDSFroy65E7JkJzGyIU8th5xiuZpom4Lw2Hnkj6gHhLWlTJCslgQVYtfTerPhHjSqFVZ5iioEknwIli9milLFp8qfynXIuddwKZbvV8+QO6+vaMSn4UeURZkFEyj7kSr/Omj5yljCSWoh9iTctx1YbD5XmqL49zjCblnEXMWWMmn4ulP7E4butxoQmSP2afpKuw2IEKYPKQrI3VNJT85ixE3QfVPsfO41iPb3V74h9AgN2sDoOZb+b5zp+Kl/q7jNmzgVcyoReD9KKkBqj5qFGxzZ0qy+ERGieFeiwc0ai0tqlmMkmq+8dv9Pm3MwaPykxL9fDeGNJTujfzfTvA7HxSGyWhIawyVz/CeyDJzUw3FpMH2i6Kh9R0GX/1mKuZoLVQQ/bdY6Mt6Jj5JXybybBlmOIOyEVLwnfBoxJHKsECpCkxq/tI0jMTNdFDhPWudTdJ8xoOE4d85d6zzEhL3Gvfg92tMROG/DtVxq1uVigJPRa8ZbUlJSHs8vFlThNMxXgrXeQWBpUKRG13QeN6wydqMFkjW/tis9BE8kJxqcW997r7aMAKrnM/2y0SdZ5A3JfJBRFNicJZmf5+uM1xyvPh0YH+qoZ2bUj6RZCtOz3HenkFsBVfMKUdCTTRNJoyYNlTIoEiMn0L06cZiH2lnATkE5TV0DvW6Z8124zcCqyhputgiWHzchze4Ukx+bbjHu07Dc97cu43JeGN5FsVOs3vYDxdWTzp3bxP6mxwSagPilRf65zqV4zfq/SonlXdWvleqj3+pKqo7eO1ZdDyuezgfEWmidDk9Mi2+oeEyYY5p0wvtD7U/IKINriQ5ON7ltyGUsBMUejMrhyTbqj/mxHnWexW/fRBAXxx7uLDORSl7rUb77+qjKSWn8ZOclfZNsXucqlLvX9qF8bWCEi/xPwXwLf5Zz/TvndHfC/AL8N/Bj4r3LO9yIiwH8H/BfAEfivc87/7M/diAGu5pWFAFineu4ctBnJWYhW0GV9fZCrjARJddV3XcmvTVpsM2L0AT8VK4NzsGJZaaufrcu4lCa29N9mUj+L5Fc/B0QbGgmCiDafn9xeg+rWJZXIUJ+gWT0xxGTEKhuDJBrzmQwxrt+SkyzJHpyzKUZb9lH02bZGu7oM3czc6JQwJzVJtCfBTI4wGk5bhynsDesizoLbaGyet5E5WuaSMpELkaRq8KWkm1RyyDw6TTHJgEuktjBQ6t+OCqrU8mlp6lNd3a+u/GdxrFkywRTPiMP68WQLUHIO+JjKmshLU5na9TxJhNTr+Ger7JNoV3PL6nWRrf7bvreEoyHudPtjV1gds6F5Z7GTAiahzJ24SUgbycUvJRezy8no6qrut9A8lwSFAOMLPYaaiCKzKWwJnUcKGKxeGyRt3rPRtJLYFI+Kdm3CYlcYRbOCbMmvx6jgg/5sx4SZyyp1ASJM0dY3T5HYGcZry+mlWRo2QH1dOkA87WOkfx8xs+G0L94iW7j+yUzzZAm9MF17Qu+Zbkvj+qiA1Pwzz/jGQJOIV2nxfAk3uro/fHCYGdoHBV0WdsmzJZUI29gHbI0jrufcZ1KrppB2UKZG7AuwNes+uCFz9VXETob7azVNnK5XE1E7KLAUZgVI/THhTiBlwksqSRVTIjZqrKp+D7oP7aMauNg5Exsher0pHL4oEbaNfr6912jP0AMI8005jV4KO0vIJ4vdW9oH9ULYflOALQ+nV4bhtR6zArtmYcmYWGJIMyRvmH3LQxYe2Og+djN9M3PVThxnz9zNjFlZIedlbMK6zJT1PkwxB84u0/oAb46EO8Pba3XOrIkpIRpOUzFddRHnIs4kdo1OwB/sHvmJi7y3N8TGY6dM+tBw3Pklkaj97MiwbRnvHNzM3N7teRxf0DyYch6ksKxgeIl64ARhKokkEoXklA1WvS90DpdxLteJfxZCnwvguZ7HXEHyDOEqM7zUZJ+mxK/6Y6L/EAtgYYidssZiKwug0T5mhoNhusmkRhOt3H5l8dRre/GmqQB7+fsUegUAl3vApX4zzyOXutSlLnWpS13qV16/TmbFPwT+B+Afnf3u7wP/W875vxWRv1/++78B/nPgD8r//hPgfyz//rllbF4SLGpFShpHlVm4tMRRmr3BVIlHYUxUGQOgjWCh1WajK6BLnTW6lYlRJQ41lQIoyR76rztjXMSurnDmEm26bofAJ6vZkszC1kheI+oWI7vCtqhpIjla0mRWeckZ7VsZJOVDZ4kjS8KGQcfJgNtO0FTKtmHeO+yzxZ4E/2RIJ0MsK/LzLmKaiPe6Mtq4iBFw5VxMweFtJGbBmYQ1GWfjIkN5ti3DqSFNFrGZtp8xJq0ylWiIQbeVoihdPmrDmUujThCNYzVATUTxmWT15MyizesnqSwFAAJl4eQyRhmU6RCFJZo0ycrAKckrmMRs1ubLjGYxZHX7IicorIg8aONqT7oaTplvleWTGlFzvErJR89P6iNTBROwZCe4Q8ZEXWnPBpXslHloB517yo7IEITQlU9PEI0QNkLsFRjQ5qgyXzK500ZZJqPRv5Oma9T5bQelyc8bt5rA1hJlGHQfNXVhuBOGN0XyUw9BsgIC1tC/E/oPSSNay3dNV4KZE9d/MhJ7R7I658fbAjAWI8erHwvu6Di9ScokKJe9u55ou5lwYxgfOrJ1uIMsTXh7L2RrCb3KbmKXP2X5GJh3meGVwR3Xazs2QJHbHMWw/Sax+S5x+sYxfB4YXucFVHKnYs5YVuqHO4M7ZXxl+QTw+4h/msne4DeW5FdJkH8qUogEsTcFRDKEEv8a+jKHsgIgZhb8fr2nSCfkLpMPjuajymT8XqUx/XfacWcjzNtG5ReNzv1IWuQwdpQlQjYflDU1+5USFmZL3gneRsbZYW3C+kioYEUWkETbznir94ajtKSh/JmxGe8ib6+eMYVFcQqeh5NO1pAMoVzzMZnlvjKGErO7mfiDF++464/86+1r8nctdhROh5ara6VOfHb7BLcwRcvOT5qc8nbD0Bej0o1+f7iJmN28pKiYwgSLk4K+sfWMLwx+X469X6e8G6B5XMGw5M/iWluWa1nZDZnhFTSPJcL2G2HzLuCPCjpNt3rfH14JvrCh2kc12Z23ReJRznc13c2Cmsn29ZpfjXUB4kbPrb+/WFKd1T/kN/A8cqlLXepXV78qhsZf9PsvjItLXepvVv3awIqc8z8Rkd/+hV//XeA/Kz//z8D/jj4c/F3gH+WcM/B/iMitiHyec/75n7mRJMSjI7ozs4NZkKBN19LA+VVikXzGniVzVNrzogipWuusNNzk+DfkApJA7DnwoB9cVihLAogJgpTVagVIyiaM0sOT1wY4W6AwLM73yQ6ysAfSZD55Pfni41BiUO3RfGKXmgt7I1tWX4ciuwDWJAt0dR7JhMnhi3582w/MreXkO/Kjwz+rfl/KKmiMQm4sh8FydJ2yO7IssY05Ca6JiMkEm5R9kdbXoUhuCpgikrX5Ka9HJ0xFzgKGeFQDgOwz0hUpShCkpLDk6NbzV0Gdq0Acz0Cc8oYlEtWWE13lN7astldZSKWBTIX6YjPSrCv6tknkpL4YKRiStUvMZt2X2viea9wXYCwJcjILiILNChy16zFOWeUD7niWWGFY5pot+xaKR4uu8makgB1pKNeAg3mbiVcRXNJxA2wTMTYhktVTYzakJ09Nx5EAZrNSUuocrtsn6z7MGyF2wvEHmfBak0+WuVCa2VPjiL0heUPznHGndSV5/0XDtlztdoj4/YwvSRuHH3RkA81erzEJhvlq9SsZJ8Oh9bjdrACWo4Ao+npt8s0sJCvEPi9+CfoGSNvIaRuRyeAfDCYI001aJDsyC2Fj2X6d6D7A+EoIr2ay0XkXD2aRhGhUpq7Kdx+LrGqEptV0mizKbElWFubFcNss96HsdNxDfyYjEQVPFNiRs2Si8u8gZGfxD4btzyjSKH1/+lI7aX9ICpBY9bqQ4tOSf7GnzQq+ZGuYGquAIGBcImc4jg1zsAtbaokjPmN2eZvovA7IWCQa3ke2zcRte8JJ4t1px/PYMhQ2Rc6i7LAshNmSGmWHjaKD8L7Z8bZ/5g+u37H1I3/YveH0zU6Tia51u72buS0eG0YyIRturg9Mm+Ij88riXOQHN4+6b9GRs3CYlDoxzI7OB56vW2IWTh87ZbjVueISzTee/lvB7/MC7lTW3bxR4Dd0CiyOryLmamaqqS4bh2SLP2b8XoGM5DOnN5noK4tG2S0mQvSaBBJ7IRXgRJLKVsJGGWM1XrmCb2YUNj8Tbv/4Qq2o9Rt5HrnUpS51qUtd6lK/8vpNe1a8PfuD/w3wtvz8A+CnZ+/7qvzu33g4EJG/B/w9AHf7AvtoVaNR+88SXymx+BkUSn3catNRNd61ibSjMgw+8VKwK602+1U+kCuDI8kSE5qqv4VR80aAmHQlX2I1blyBByhgxAQghfBQ9qmCEVEW48oKcshZI2AKCyN5bbwyRUddZSplfyUDtaksdOHsV7+Eaq6pqI6QD46pbMfahHOR/npgMC0h+cWsE4r8IQjmyfyCtGL9OWySbs9AjaSUylqo0hCbSMEwDp5gLabQuVM0qwFhFsxgVOaxDbhOH8JzMqsnh09qTni2j347ERtLjoVpk+XTFXUofieFGl7HoxJ1CkBxzszImeUYcl5lR9JGYm1oqgdFLDGJW43OrNuq0aVkUWZGmROpKecomMUslU0kdIlQqPT2qAaYNWE1i8pJ6nwESGHdRmxlmZ+pzxo9Knkxb4zBEEarxyT6hdllwq5qVbShXgC/zGLECNoYmSAMLyG1iXg3q9nhmR9MqmN4OzFaT/KW5tHQfVjlNs+/DcOrFnvKNHsFMpZ41jshi+jv5szmO4gPLPKU6TtLtpbxpac7FtPddCaFKY0fAyV1Vs9nZTVkC+kq47f6hZPvNNr0duKL14/LVPj5ixuS62meMvZoSC8m4nX5DgOISjviRr1IxCXmW7+Mkx0FM9rFUwBZmU7DW50fucnFmNaQfcTW2M5ZSC4Tb9Xk1n10NI8rm8sdBTMb/F6b6HmXmW+E02cw3xYPlfcWOxQvlxLb6Y5CrBGvThkmdlRWRvMEYJlelm1cK0IyTJ4YDPNQ8jur98dsiLPheXBMu5Hr7UDfzHReb7a3/YnezeznlpSFp6nlcd8TC5hlTCLFlU2VkmcybpF4/CzfMEbHl7t7XrZH/vbbb/i/5h8Q3vUMJwUb7lulQMRkCNnQ2sBVO7G7UtqCk8TL9sDb9omfjzc8Th1TcoQCwlqTuO1OfH71xMv2wM9fXzMnu4AZrzYH/kXzOdm0bH6u3i2uzFkAf1AZyfBSCBtwtxNv7p4Yilns4/WWB9Oz+6kyK9xBZVnzm5nB63umW1kiVrNA2iTGO1nihN1RwZAlorr+LTvzT3n9zw/4bx651J9Zv9LnEfvixa9vTy91qUv9xurPYnRcWBeXutRvvv7aDDZzzlnOl9l/+c/9A+AfAHQ//K1shzVtAj4FBXQVESCTC/U+bpPKIzIq+RgMEtfdkJKiUQGPRdIhrKMlkF1t+kXfZzgzxSyr/UDqi9fBKEtKhT1JkXigppF1878wGtmcNf/nC/Oi/22m0pjVhe8zKYumiLACLa40m3XV/6ypz4WdIEH17gCn1GGaSLeZcG1gflEb7PKh2SCTYEezgimZT9gdkgyx+XTVb7HR8Jl8FTBNhABpsCTs+vnwC38sjLJIXBuxlTlhImEsLneCHltagSfnEk0TidEwT46cWfxMPhnMlDXZZDSrbAbAQs5pYduQgcGuxwCkRuUI2YAUL4RcGvmMgRZlYiyslqy/R1fDq2fK+dHKJCRXvEV8wnWR5AWxmZTbwh4oY3ATFnPVMCnDI1tDLsyf1BXmDGV+BoFklrQasLiTWf075MwPBcAVb5M2LiCN8Wkxao2DJZYUCikpF2Hwal55BlggGdNFpIvMd3lJH9HzmJl+e2R8azFHqyye08qcmK8yZtRG0J6E9jHRPoE76j727zNmSszXDkmZ4dYyb2W5rrPR67MaopoIMZ+xRALIwTLPnR5nF6FTIMoXgPK6Hdh+MfEvD1+Qf+IwIRMO6+0zdYk0WQURm4S4xPZqIG6mdQhKAsjppBEqObMAW3dvn+h84EV34jA3i4/DY5FIHI8tzkXeXh8Q4LurHeNXG9xB3+dOxbfEwOmNygdSm5lvI//xf/QnAPz06QUf/9UdzYOyQJJTsLbeC6bbTHw9Y54ddjTYE/Tv+P/Ye5dfW5Iszeu3zMzd997nce+NV0bko94F1WqpqhoGMOEhMYIBs0ZihBBDQAhmjFrMGPWAPwIQSEi0BOOeoZIKQYNK1KsrO7MyIyMyIu49957H3tvdzRaDtczc982syuiszKyOKl/SjRvn+N7uZuZmfn199q3vI3hpxrHfIbtMiEp+SvSfJHum1R39ETS6Xsu7ibsPA1eHMzc7AzmGOHOcO754PDDlSCnCdE7t+rkqVlZGjq+j4nNxOiUeHnac30vcDid2caLvM7PA9MbAhM/1hof9YOeeIl2Xef/mkVtnecwa2MeJhzxwPw18frxmKqGNt6pwzonnw5F3ukfeefbIsfR8fjYFzm/tXzF/M/Dt4V0ewxXdo9C/XrRLwmzPj9ybq8xuN9HHzHVn8+B2d+bP5QX3cc/1d+2+IUL+Zia8b6hDngJj17dyMfrC/E5hfu7/ftxHZBLSkxAnmnho54DJ7q7QffKa/GKlGrrFXxo/i/eR4Ze+tWUxW2yxxRZbbPEzjl90UeunIvIRgP/9Q//994FvrT73Tf/dFltsscUWW2yxxc86tveRLbbYYosttvgXPH7RzIp/BPxHwH/rf/+vq9//ZyLyP2JCVq+/bH1oFcmsIpalW+rpi+tC2K7qajs8YLvwQc3ycr0fUowJEZ+CO30sZSK6Oi9VILNYPTteDlKvodF/jor2hbyjlSzkPjRxNvCNRGd6gO2AS8F29pMxFkpayhc0XLar/m4RacTp+7RSmHb+2tds/aRdpzSnDgB5iugxclRBgtLtJ2IqTcy0shXG1CGTsVOaQwosdd5VwFOto9V1xXTtTGRPu0Ip8cIBpJbx4Hokep2RVOj6mdnHUUuw71QhzM5r8Z31Mk+R1GViLGg3U4own9NS5rG+59nZLnFdw+9MDYXqVBKOoSn/SwYdV/eg8/Z6PySvyovO4WJ+2D1zC8mqm+KMkTAJ5cntJHdCiYpEG6s8ZERjYxINt2e6LttO8ilBZY2sdTkqTd/LZMJZmqiiqFHRwSx9NYjpa7SapDpvXORUlLJmpiQr5ZFavvPYEU7Ba/lX7J2g5EOAztg95SZzdtcV3RWurs/kfWC6iZyvOmQMTSsBFcJjhBBsfauVVuXe2rF7lYlTIX4xmh5MMMpSvY8a1noego7uCOTHw9nYA2GEMiTO72V0KExPA9+5fx+A7mbk3ecPpNuR87uB9CT0nycT4cSYFTI7wec+kovwFJS9Myuud2duBmMY/PD+mmk2ZkEVkZ1zJPYTh2SfT1I45dRsPe9iZpwT45zo08wwzDzc1Bovs7ZEYXyhzC9m5MmEceUceNYbq+DZez/gH392g3w+0D2YtWbuFxHhfJO5fe+Rx8PA6bynfyPsPld2n/tUyB3TbWK+LnQPges/N3eL+uwN2cvuohDHwGO/437Frnk49xzPPeenzoRlfa1U5k/wtdQckPyZvWZChYfAD3jGZ901fT9zOvZoXxBnhM0qPByT3YgxMAGvYmFw0d2HqeeTx1vToxg7Hp4GUKHr7biqcPZ7cpo7XgxPnHNidE2cN/Oeb13dsYsz/x9f4/jUcbpPzW0kPZq7yvmFkl/MiCgP54Frv/dX3chvfPgZfybv8TRf0d1b+YYW4eaZuaOcxo5jEeSLau8hhP1McvHjaZfQx0Q6meBt1WTp3M5UChx/4z2++Ls9/D5b/MXxM38f2WKLLf5mx08S/dzKRLbY4mcfP0/r0v8BE696T0S+B/wD7KXgfxKR/wT4DvAf+Mf/d8wm7E8xq7D/+J/nWpZk2f+PzxfLNsnSLB6bB/0YLIEHS9h3lgQ3Bfc5oJ2QC83iFGe0V8AjqFLEXqwlm/ChaGhCcxVEKEnNtUAxmnxyp41U0MGovDIvGhUtuaouH1424JISizim0KwsW8mKlxMAhLw4lGiwWvSShRJKK0GQWawMIVtJQfb+NCeMWov+pqN0BR0KqpnO69uHfqbvZ8Z+Js+xuXjUyLOYLkKRBayICmPNEN32EkhdZvZ+a7OQFUhKHKxzwzAxe4I3n90eNQevvRcI0spZakxPHZN0xJ3V+YuoaVesHFFqqChl52Nff+82hHX+LE4qFRXypL+CQUXMlaR+LqpbGS7lRbpqn3aKYqCZVAAm2BhE16jIKnZbu0KpuhKytGWeI+OpM52LY7RSnq4sYMWqREhm10KZZOm6LhoPtY1h1iaKqRUkm+MFmNYigBYf11lI96aLcCFq6mBPPAvTtaLDqn0ABR5f75ojA50533QHW8jzFClZmPe2EE/vibfZrnH+LJCOic4FO+edmOjtChyyEgVg1uYGVPsjGeQe0pOLl06R0ke6h8X+drrp+OFHe8pQCMGeDd3jIqw47yPpiQYK5ofI9Ljj8Zl94HTb8brbE0Lh+DBYAi4YiAXcvxp4M1zx8eGZaaAE1xWpz6UpIveJ4yhNd4ehtOdb3rk97bOZD7/5klf3B8ZPDwyfRX7ve78MwDdevCYNmbxTc5O4UvKwuOuEq4kgyjvPHnn5S/B036Gxo3tjl9s5rSzDAAAgAElEQVR/qvRvhPHGhGTTyQQm43lB/0on5CsDgNLrwJwH7lxckmLgiWSIs1TzENKTlzeccAcmmK/rc2B5LhKV9BiYPhuYojL29pyUfW7inuEUDECMWJI/w5svrjiP9swoOTAduwW8LdauyfUixJ+b53PH5/0V17trwmq+3533PB+OHNLIr7z3ksep582LgdP7hlo9PnZWUrbLHG5OqAoPx6E5puhe+OBwzy9/8JJ/OkXy9wa6B3MFGt31JMZCt5/IsbPn9FOkKFTTlWE/MQZlPAdUDMCLZzi7fsrpeeD4YSD+3dfwD9mCX+z7yBZbbPG3N/6qDiYb2LHFFj8aP083kP/wLzj07/yYzyrwn/601yrd8kIr757NflJdf6EJSdrxeAyW0GUTmMsA+4oGQBVh1KFQQmhggAZtmhMU29l9m+lQd7DjuTIDII+RMihlt+gMSJ8dRBATKnxrZx0HJjR5UleT/nWf3VEknKVZr9bkzcATB0DEPmfuD+EtpwohZBxUiGjn4ArWFlEXdIyCPkVyl5gHSzzGw0zXzy7E6e4YobQX+6zCPEcTyfRL9n3mfLLvi4vplRIIkkldRlNuAntggnsxuZNIzJaYHzv0FNt9qAk2FayJtLGSkwFCuWpRdLqwMOCyCCoaMyPUJNFDx9DYFSqgB13cD7wNFU1qOhBVU2OXV3PEhC2bqClQRTtlyOgZt5BVE1n0uRRGoUgwrY1ayy+KVIvXNz3haO4Vki0hLwS0Wb2s/rAAbqVqXgTfWVdzr6n9XDNgqpNGjTVWUTpzzGkgyIytPWERAPT1Es5CisbK0U6b3oK8cYeMvbvbRPtTdQjsQiaOm7N9rlznNs7js+QWq9LWQDyu3EAmSI+1j7ae40lXfah9NHbA4VP7TP9Q2L10HYEh8PjDxPlFIg8mcjjc6QpgrICPjV/poH8tTHfuQnHlCWeE3VFM5yQuIqFV76Gk/oKhVPugYtfrHtWdRODNr8eVpo0xkWQKPJwGxBkKwys4/YFZZfzp1/ekq4npWSEPgXIo6M4siMFYBa9fXXH17MiH77zhfJv4YnfN+Jndh5t/FohnZTfB+Ex4/LqxWHoHM0qEMphLhWTT0IgvI+pqsKL2bKpuNtVJY3hl9yKOdmy8EcYxUHrT3ahztbg17+EHgZJgPtjcm4dirCpAnmy92hqxCRzeJM6VSTQU5GF5xmhSwhiwB8cy1uPZdCOOw0DqZkIT1RW+SAc+uHngWX/kqjtzO5xIL+z6VW/k7M+xN087pimaywlQVBjizFUa+foHd3ysz5nvO8jC04O5tuwOIyEoc2/uN/EoyJRMGwYIzwqpy0zvjJx3yfQrHgMYCYjcF97/rc/5tz76U/6QLeAX+z6yxRZbbLHFFlv87OKvTWDzZxGKJQUlLS4fu91ElzJFhVPfk8fYhNrAPhsme2kOk4AGytS1xFXUd5Nd0NFsMW33vFRSwGrnuCRzSiiylJ+oCKH4TvZsjiFljK2NZS/2cp0ULX6NutNXOyZAX8xOs4j1oeZXnpjU0pTiwETNIoszPtSJBhqsvemoTXix0uILljDEETgLeXI1/sGyLinu+OCAT3HqfR4ip0Nn5TTJXD5Ct5SJ1J3hyrgI0Y71g+2Wx1h4eJPQKVBiIPYZCcv3cVvEUgQRsxacx4Q+LVO2lZp09kdiQVfgiGVH0ko3VC2pkWZZqYRkAIsEpesyIsq42oXNfj/qbndYsTdCKJYU+ummpx4J2koi+sFZJ347JRTrQwVbvExEs4MpFWRLSnbQaO0C00COqO2acg5+f/x+Ri/H8fmpzr6xcQJ1QKfuyGtUcwgJSjnHds2yAnI0gJxWDJ4638DLoJbPls5Lr2RJ5KUowe0YJZtjD6M0AUFjYkA5CnlQAyN6Jbl1aRnsXNorc1R0Vwj7VQL5oZX4VNtLzWIikcd6fnNSCKOzAaA5jVgD3cVjsPnWPSrppMRTIZxtUcfjTDwV5s8j47UJx3ZPpTmKhKyUJMSzmrBvgPKo9K/rc8KTc4UwFQcnVyCr3z87ris3oKWd8ZgJs11Ho0DozOYSKL1ZXnZvIuPrZ+QeusnYIjffse+np47H31Q4ZPJQGghafM2HN4n0KDzddaRfLQzdzEdfu+PV1QGAN+ma3Q8D6QnGG2X+lRP7q5E3jwZmSMDtTJX5h3v6u0D3uLiy5H5FSpqx55HYeFPviUCJAVG7J+MzoVzX+2Rz4+oPlXknjM+EeQ95nxqAGCYb+4p5NqZUXTvB5g/FHIbCkyX66/lcOiVPBjLnSch9IKQFQTrNPaUI+3cn+jBz3Z150VsJR9kF9nHkh+cbvnf/nFIMaJwne6YcVfiEG14cjlx1Ix997Y6n5z13n96gvv7G5MyxfaZoJEziDlLWh+lobLHUZeTFbOe/ja1MZOgy/8r73+OdSgvaYostttjiKxFflpmxMTC2+NsUX2mwgugWiwFzXMB2vvqUKQrhoDwykH2HDUAPmTzbLrjt+JpLx3qzu3RK2TmFP7LaHa9v2quHSVgzIjxxGLBEeHQb1VlIExTfJS1jJO8X+9Oqy9DAiOqEMQdUdSkxaGDFcnlzA9EfcdyoO7JSbEcyZIhPsgAqUW1nclgSyHh0xghQRtdmKLZDauAO4AlgiYLe2WeqnoYBR9raxapsYe4KU981qn9M2QCYKaDAPAtEvUgKwECCSbAX+YJpGQx+r1OBTgl9ZthNpqNx7BawYmflKxxjY19IKs3VQgJ0XSalTBczMSi5CDn6jr9nMCJKUSFGY3nUJLkySYq6Y4YKqct0Xu6TYuY0dsYeCfbdEzBVa1MHRZSlhIjic2pY5jNg4JZT1CsYYt9NFA0GWnXGypBpmSvSJjYGqimmUVJLLoKNX0yFuZYOTaHZ4FZApUzBwL0MqL5tXGOf6dVArsAl/QKz85VipUdhNGZBBUxEnZHhDCGKINNSHjAbpsh8yLAzILFMoUmPdPsJJSApt3szDZnp6KyGc2C+CqRHIT9aaUD3wLKOAsx7WRgipT4HItO1axGclPSU6d9MpKfA8b0OjcI8+L30spIwq623WUkzyLEm0YrMSqjOQ0XRFJgP1amFBiTG0dazzIqUOkZQukDeO+hTlOvvze07420wsGRUwqRMBysPCBOks58jw/i8Y3qWkSLEx9CeEwDDSzH7SxFex2foVebFe/dc701vQb6lPO2uSHeRfFX45Q9f8hu3nzP4SVLI3E87vvv4gj99+BB9MPCoPnMQA3zDCtSdr+Cpr44mVqIzHyw5718bzDfvV/PoqpDOYqBTDoy3gqZFnyRMdh3JXhrTOejsVsASFDnMlDEiD4HhCwNUqg6NOSgJ8yjknTLd2hosFTQMwCScnnreXO84dCOHNDKXFSNMlCSFp7EjZwM5q3vOPEYe5h25BN65euJrh3vClfL/PA2mOQOtpKU7jMwxMesCptvkFrJE6Av73ZkUCuG6cOuuK33IBJQ/ePiILbbYYosttthii69yfKXBCklKeGdECwR/mZvGxAO05DCEgnaLXaX4S6vOgXkIxIdl5wpsZ05mo8GXpKiPkL3EeqKxzsPUE0JZQIt8peRZiGer3YbKsvCX0BOEKdhO7q6gcmnKYpapArNSLVF/RANgVcetyXbMG2iSlFI/78lvGQWZQ6P3gyWAusuWEAezII21fvwoXl7jdfywaDNgYEg4+fnFWRcdECqYAVXrQztPtAal7H2Mohr4UEt0solGlrTe0mdhm1Qx0coEwICHmIwSvesnphyZp9i+kpwpkfeR+ZTsXlUABbufZTWuuQhzCS3hFWd32EFpv6/sj6LCnIOXugjFgYHox+dsZTC26++710GJ3v58svsqs7R5JllgBvVxCjvblq4Cm7Xdrc1RKX30EhhnGki4sCuV2onkYohh+SzKhWCmeimIVPDP50u+hjJVkG91iwI+Rx0wrMwToQkk0hW311VyF9FHA4/K3i0pb63cSNyStfSKZGnCjxp8HgS75zq7Von3carrxa8TvJwnXhvylneBeRfJ+0jem2ZCHpYkWtR2/UsVy4xmh1wBOLCykv4+MrwuhNlKMaYrGF0nQIOxk85jNKDi6An17HPJgQcV0CSUFCidMO/8+87u0ABhH72kRBtzQ1SZDkLphTBZUn/98Uw8+vPMc+UwKcPLMyUFNAUev963RL5/LFx/J3B+YZOte7RymDoOxhSxtV26yHSI3D0+R5676GefCVcTc1SYhC8eD+zTM373+ffs2qLcTzvujnvkHByAWdpmHYHcAb0xWU5fK5RnPsefEulNAPXxO1rpy+iWnfkAHDL33xjYf15IRysDAVmuodan+Uo4fg2zqYYGVpen1LQzuvvA4VPrfwVE8s6YHvEkVm7TBXLRFVvLJowW4fVxx1QC55w4ZZsoRYUgtzxNPQ+PO+YpElcAqeaAnoWjwLg7UzTQx4l3nj3yOlkjJme6DIMJqZ4cWJzPK0aWAyilmKXxVT9x5faofZi5m/b8wWcfssUWW2yxxd+8+KtqY/x1xcYI2eKnia80WBFCYX84M46J4upj831HDqntGK8TU7C8OsSMdJnSB+aklGNsFFuC0drDiAsm+gu3sAAXkUsRTGcFNAHMfQaFeQhGNT6LOUVUQGQ2scEwwySCTg52rCjhlhB6IlMdKsL6xbuiBs74WB9PlUagK70FYT4szIkGPnSKxEzpAlMXG3ASx+VBmAdzU8iwgCZeOiJ5aW8V/KynrsOeByjJNUJWZQ1l0AUEclCFt0GZ6tbi/dOkhM6p+Z4EqJqCvqpckl6CklLm9urEfWe142vRQs3CnO13U4yt7CTGS1ZDTVTybKBE/f00RcoULHn2z5zGyNR7GUkR21HVyrjJhJiJVWj14CBadUGpU3AUcvIfdgtQIUEbgNLKQLpMdkYEYju42nl5UR3GtLopcSktAkwYcwpWAhIUOQVjXoTVPygq0Be0x+bq2uVkvb7UjyE0UVGMHaNdQZIifbFdagFxtw+JhZzSyjXG/kw3fuo1OOKiqhRpa1YmA6I0RiT7vOqsVKS2UZKi17PdZxU0hgWsmK2cyNhAkPe0tV0Bk3gUTmehu4/Esz0L5h2c3ve5Miji+jHdQyA9wPCaBlCWVP82AVBLhFkYMN5Hczvx3zuAUTsxH4w9E2ZjnqCJ4U1p5w9ZyUNAcm9MjrICe/wa1z/I7F+G5mJiQr0LeyP3VuJz+LQwD0L3EBlfWBI9HxQ9GMMnvYkc75/zh1e3/Pk3ntv3RTk+DZQf7th9Hqj6JzXCZP3Le5ivjE0WP3zi733jYzt/iXz71Tu8eXnFNAYgcfhE6e7t+/MuMN/A/d87c/7OwO23DWhIj0quoE+E7slAm+naqByaFJ78ufYkdI/m/BOPzsAYjM0BBlrEswEe3QOUXhifQfZB1KjGOlLh4X7HOCVSyiQHKKccmedgQp73PRRhHnJjjOmMld6MkePY8Xm4oouZfTcxDl6CV4ZWTnPoJ66GkVwC90+maTEeK+3J9F9yDIw58jjZTZ1CZCyJuy+u2WKLLbbYYosttvgqR/jJH9liiy222GKLLbbYYosttthiiy22+MXFV5pZUUpgHBPTOTURzfgQTVwyGD1ee21K8QB5wsT2ukJKmXBdmLpE8e+XORCeQrMtFbwUorIpYLFzrLuiU7iwvAxdJkQld4HSBbQz2ntzi3D2RtXMaLalflj9M9Yg151IilLr42lblopR5tcuGKrqehF+QneZKHsribHzSmNiSFRCN6N9ZopOZ34MTcujDEqubfROS6msCmnsiupIAf6z/16j7ahWx4TayTDWHW1ZSgjWdGtAVveulRfULpSAzl7GMIWm6dCERmcTx+vTTNfNqMI8pVb6oV4+UM7Rd999l9qvua5CaXXrRci1xGKMi3VqbfMJsqxEQGcbZ41qrJIDdL3t+O+uRqYxkWM0hgPGmNBJCG5pWWKCTs22tJahrAQ2q/in2TAG69NqjIALm1CJanMzL3OjsYrErxuU4jaKVCHXzh0XqoZLGxiWdTAbs8KEIZcGaHTRyS5A1CaM2mxqS1xKfOq9WdvQ1tKlyjby/lbqfzw7I2O2sis9mY5BdvvXqg0ju4z2BVVhvsGtgKrQrpdFpWUda7c8O+YhMIsyvhuQyZxHwiiUK2eH7DM8c5bPVUc6RPIgTZ/h7MKPpXMXi87menMDcaZVc/eQ2h5vS1DyYGM5R2MUlD4wvrx02ig9xHe7JoZqrhy1PMj0K7r7ifkQOb4byR2tNKzahIqafkQclf0XSvfgrJAephv7Tjq5poQGzt81ZkUJsJ+guzfdjLzD2Q3ex6pdcbBSH02FblXe9DvPv8eL4Yn/Y/wVUiqcbjvysOfwA/ta/0bIN4l//Xf/mD96933u83sMd9bWWsYxH4xFlk5K/9rFVXvX4gD6N8rwurJR4OGbgelGyf3ybC+D0N8J+8+U3ecmAlqjJKFoQYnIqeO060wIuTInzrExhMLJ/h3Ju0C5dZXRoKZRVOB47JnmaCVLoSyPPlFzJfLSskM3MZXA0Nu9ns7VZnV5xB/HjuyquLkI+35Cjuv6my222GKLLbb4642fVL6ylYls8ePiKw1W6CyMr3bI6G4VuIhkscQgTEKZ3Da0JuaTgERyp5T9TBpmt81ckuK5ghdqyXhLYGry1BTmcUV9TyRXPBUJhRAFlWyJpnq5B1AGq7cP0yLCeUGbrzoULMl9GOUyQXT9CE0Ljb0mzRp1AT+8ndqpU5hrDYQDA7NQiITeABa59Tr/0FGOVhqgfXHNgFUip06tV+zlvLiGQF7aW/U/8BIaXWle1OTK9DlMM6CVEtRYuXaI2Mt7Axewtsts14knMW2Ot0ClaYjc5UCMhaJCmcVKCfB2r2xtdbJMbfbjsgIIgMXGdFUKI+uSCOxeLKCUJ9/BywBUyTE2zYvdfmy3s3jfNflXPNEOx4ie9aI0I8cVqAMLQFPHLhX7U/u4Fi1VLyGpGi7ZtVR8rmi0MoPkWisE126ZogkZrkC5ej5xwKbdT4zy33Q4io2JxgUM0KAwLm2o164aJm2e4p8V72dNeN1eGCATViVINh+YcEtKo/lrcsAnGghSS7UAA5/q+luvMViAElFkl4k3EwpM9x3dq4T4OOqkpMOEBCXfwtwX8iEucwFrw+K+AozBbHWxkoR08vIZxfUrVmO9+ls7K0U4X82Uodr40hxGpND0ZnafBYZXdZoIJVkZRB6E6VqaJkeNUIVOoelXVEAlnZTu0fVogN3rQjwWrj9Zzl8j74R5suu08jj/TzwJ3b3Zj57nA79//GUAXn3jwFWyiw3dxDeeveY76QXlC6sH6u5hfhkoCP/21/+Uf/RbB8ZPdvR3BjgA5BcT51Nk+CwyvLTvaDKQAnC9DwNI50F4/JWZ7vmJ4GtnHiPn28B8lci7wPDK+tzEk3ubLxqhuxfyTih9NPAJA86EZQzj2UrvJnf4kNvRyvqyMD12zJ0DdUWa+LCIQspAcBDCytXqc6k+00M0rZ6iQi6Bk8+147Hnnv3lM2KLLbbYYostttjiKxhfabBCstC9isi8JEZhlmV3ElpC7q+QZh/oyVkehWkfCbuZqnGZuhn2VquM2K51cEG1luRW14JVgteuBZQxNgtNEU96UbQ2rDMAIM8BfQowijMvFtYCTbfC/j+ukmqz91NCtmQjjIKGter+knSV6KyQScwtwl+qa5tlDHCGMgZKV4i7qrZXfMfeE7z6+Zq7xUJ07YjiO3qaFz2HPAfyaLvQFKsRvxAJrfeiiIkPaiQPa7TH9CnUd9xVsF37LMRaf141M2Zp9oiaVvoAnVImoZwDU+eJ38o1pf1cQZQKPrGcq/0k2oTtWOf+dWwczNEYkDqZxEEFWV37FJhWzIsQ3Tq1s53VIEruCsU/E54i8VhBAnHB0hVotErkm5Wri03aB6SJW2qR9oeaRM/GhNDkyXRSOAWS24qGUZAOdASCkHubT03rMxsAGJxB43gBmmi71eKgTsjOUKkuOHVo13O7rADC9oHVPVutu+rwoKmQp9AcfiQvopG1jWBgmgbTsYm7xQZDe/E1a8KL5rbhwEEFI5Lbp3aFrsvolTCfYrNXzZMwZTFL1aik/Yzu5yZeKqKkuvvuuifZ2w4wdwHtvA+sGBarcV4nnxoUkjI9z23cCD6WGfRmJnSFp26gdO628WT9ClNAsjIf/Kvdcq26piZZ7oloTfRNNLhajObOxjs9OLtEbe1N19FcUtTu/XS13MfqxDK8BI2ClEBJOwC+8/1vMn04IgJ5jnSx8MvvvuKfvWvaC4ePheGV8Ht/9Gv8G3/nj/mdb32P7z57wedf3JCcrfTe7RMiyif7F0juTUT0uMyH6Vo4vWvuIfMe+hcnvvb8ntdHa8OTDsT9xLzLnD9Szt/fc/1dd4/BQKSUhBKhv3emRUdzZanPbJkX0c40GtMHYL4RQjLLW33skYdkc10XMVA5zKgKIWamMfFUBnQ2C9RlUqtp9ogyjuaAlGdDkfKbnnQXkWFDK7bYYosttvjqxI9jXmxsiy2+0mAFxXbpUGzXFJpQngbI+1Ui2Sw7l7KF+BTQSchVIBNnPURFgu1cAZQc0cLKUlRMpK+KdhZZjoGxDGoSXHdT1zvfNXmNXpqRxEtXlhdeKTS2CMV2qmuCWDoISBMBbXZ9l66fAITgLgPRRENbIt8bE0QqeBOgpGBjcTHGnqBn3/mv41wFIAUTifQ+NVvQPlPEkndmgSkYy8JDfCdbMkRPglJazo8uu/h1l1hmaRasYLTy9ZDavVkILposkY7uYFETwJoIl953+WNZJcPLTnBj09RjAWfRXN5HGSwBDlHRkikr0EUc8KIAc7D77MyQqQihzzbXggsOpkKIhak2ZzYwqyX0yUAHajLf+RwvLCUgtR9wwQxpApi6MCAMFDIBWO1NlLKkSJ4sg5WjJVKNwo+Bga3cp9DYALnHWUE2t9aAg8wLyKO9swvqfE5qcySpgWc+NxoIuOpHK7kqC3AWD7MBf+fI3Nk5JPOjoFTGS6JgXS4kKxZSGxc8sa/rYTJmVM5CORggUfaZcOfjlEFPifkmMO8z0cVUQ78syhDMfabkWq6jSGWQdIXcWzmTFFkcgCpj7OxAVbA+CKCZBZQSKz8r5whTIHTmhpTfPXPsrI3xMTbxzniyNZGeuIjS2XjUe6yR9lwKLkYZ5tX0miP9rjJkzCVlvBE0CmHU1jawcxWF/qRc/TAjM8RTbs+tm+8n7n5t4PS+lcl8eteTf+mO8V2bbP3rRDrC9R/2/N7Vr/A73/g+37y5I5cFJI2h8P7+kfGDyKunF+Q3geGlkE2bkvEZjC9KY6qVMfLFw4HjvYEVeorMfaE7jHzj3dd8vh+ZXj6nf2PfT1MFIO152reFusw1K62B8Zkw3jhA88baN71IlP1MSAqTsP/EGCbzlTbQKAvkVAwwLwIPifSwCMLmnQGG0xS4myL65MCms7H6e+HmO/D4jU2Saosttthiiy22+GrHVxuscFbD2h6vAhUEp3oL+JY1AKpmBxgmL1M4i/GaPRkq52CU3qi2Iylq2gR11xVaPXilPVfdhhqhWp9GTwRX7iFLu1lKM3bGYlh256Ql9ua0IRclFHnwMo9W32/HWuK+3vn3nExmZ4n4y3XxRKRqRwBEgexaBSXZuIb58jz1GkZpj5ZUzQ5oRLXEE6z0wJNjqYyIUS7ABRXAqea1XTUDrW4otc6/gUyFNjb1vpsmhi7nrQ5/XlpSy0SqNkhZlSeUXluiQzKnA6luIF4WIFIBGu/TqpZFghK6YrluME2HblhQmRDUrE2LMI/RgIq6QzoFitLsZwFyMCZEtUmdh2y2s64PohEDLDwJDvu5lbhoFsoUl/IWb6MlxrIwgnTFBgqgKHSF0Ge6fqakvIAlXVrKO2q3CxfzvXQ2l/OhmCZFb+hFs4ANmLVuTSijmqWw/yyhkOdoJRSPnSXHDua1m1rn3drlxIGEdGUOK3NUSjZtjEvgBgdBvFRJIJMWhEtoDiOtJMh3u2sCGUYhPglhjMyjkPfFnxEL6yCcQTHAL4+B3MfLuVRZOdUq1t1tALPh3Wdbd1VDYg4NkClvr28Vv48LKKW69LmcIiUG4pCJbg2a97ldO4+BcDJmQ2MKBdOdEAdHQ53q9ZwJpgONIVWfK9VJQ4o5pOSdfS8dHYx0vYi2loMBIGnMBlZkG4Or+xGVA+kYmfdCmANfTO/AwSbbdG3PiuGVMv7xFf9X/hapy5yPXVtTr4rw8M7A0M3I85FxSJSU2jNRI5TbGX1IVqLx8Y5JdwxPSx9KB+M7kU+i8v7tAx+/e2vjhPUpntSmjhjLJJ3KxXo43wb7N2Y0YFPDwszoP4uM70PZZQS4+tjm9tPXhMnNO0QjeS8NBE4Pgd1ni+5G3hkAnXcB6Gx9FrOitbmqXH2amfdf7X/et9hiiy222OIv0rn4WTAu3j73xuL4FzO+2m8zUndzl19d7MZq/bNKXLBEpLp/hlGI4yXwoGcHQJy6W3dX3wYB6u+stGQ5FiZ7SQ9FLEkL/tK6TtQjVqKwK+hOXRuiNtt3ylNw5gMX7c/74oyDsDAKan/faqf1yY6JrhKNWVqSsLZKbUBAsuSk7aLKcg4bZ0+owpK8apCWdJcULiwT5a3130RLfTCbhkXdKHb2S6WONwvHVQKZdw7cJKVUynMtXQHbQc/SABeq9EbTEaiZWHCdD4G3WBY6rxg0ApIWO9xaXgGWiGuplP+l9ty0NjBQIylFcmNW2G6/JclN9LOIsVPqNZKxb2QSUC/j6UsTHg3BbkoQRQmU2bPaCoiI9aPpVlQRUv9ZuuKWqDRdkL7PpOeWGZ2HjuxCoqjpW8hKSFUD1r6d2TPGWAzwKIFcSyCCAReLJayVDlWR0K7LaG8nPBahpGAgV/fWRAYDdlbioGAlR6kzwCJ1yhyTWbiuWBKa1TVUnMV0kgvgTRyoqFajjdWz9zYnS57To12IHWIAACAASURBVCBzYJ6Fsi/MV9aJ9AhR7VkSshhY0IUGRoS3AM1FQLMCYYHcLQDfui21jes+19KWqvuhQdFzQFzXQ1yEMe8i4hauEtXEdwGuZvJOOMdk7DSwZ46X+MSTUGaz8Wwga1h29REHNMaFtSC+NnMrPxC6R+gel8U/PjOtjPsu0j0E+odIOtrx/vVEf58JWRmvg5ekBB6/tYxDSTZE138O4/2BEuFq/XwAHt9NPH50NsbSYWYOijzaB9KTEF4nugchHoXuAdKTEr0Er4KZ45vI9PkN3//NRHk+c/J/KtNjIB0XC2h1Rtr6mXt+Hhhvl/bmXhsz4/CJMF8ZSKn7gsbI4bMZlURd9JK9PKbTpkESJmsnOPDhGHuccH0mJZ0dcE/C+Vloz+Ittthiiy222GKLr2p8pV9nVBYqf3tZXIMIx9g0ChZdCfGEbdmVJ6+ScHXdixmr62/JubZa+0pzr/X1b2sxFBQ6b4ssYErTjtQV42ESK6lYn0IUet+1dbo4cbW16olm3ntSus694UJ3AUC8jGC9Ix4ma39YJXToiuEwCcGZF0tdxTLOjWkRfUhd4LDV2nv5CeKgRDD2w5IgVhBEKe4WErxco96/BqKsrqMR5mvraB4skScqpGIlF3mVqM8Ljb90ikRpDgngIJRoK8HJxcGJtXRGFS0VZzMEaYyBGmUOzc2DIuR6vIIZc2iJt8SylLo4mNLumYKWaCUvK3BBdtlcXPx8acgN3ChF0Ckhlbmjl/feSpKctROXSR4cHEh9RkTJczSXjNHcCQ47U9jc9RNTjuQcKEXMTWVeaPcEpetnhmGmFCHFQgzFvlP1HmpZgy+y7MeKLn3oukwIamK3sZBzWEqKWOXLoVCy1+afvJzm2FFUCKGQUqGUbM4t55Ugod/LkEEmuNB1SZdryOZggKDkqvFylZkUwhhNgPQIWQP5ytdiCY2J1FgWcdHeaAyp9bMIbwd1bkcDTlx0VlgYQ03nIzirarLPhAY0uMio1OQVAx+OkdnHQYfStC10yMSdgUTT0cUfx7CIuPZWstQ9rhLz+jxr4p/L83Hpj7e3gzKosYIeV8cVxufKfFDiKHSvI8OdfWd/CHSPmfRU2mevPgXRRShVst2v3avC9Q9MJDXMyvlZbNft3wQeyo75NluZTIB4tHHqX0sTvQyT0j0p6aTLcy1YP7tHe1a8Hvc8/dpEfscAn3wjjO4YJQqnGeL5EqyYbpT5/RGmQLpLzLeZ4c7GeP9DJT0JMwE+OPP6NyNhinRPhflhEZwNs4mTVq2U8ZZWjtM9qoEX3u4qhJp7Oz4dhDe/DmVVgrTFFltsscUWf5PiJzmL/DzOuTEv/npiK2rdYosttthiiy222GKLLbbYYost/oWKL82sEJEIfG39HVX97s+jUV86VqwFfgwYFo/LTn1ptd00W08NdZdTGiVdq4Dh6rRNtNOtEklqlGov88iB5qoANAvBxspw/clG5a6OFL4T3ixLWw29tOsQtDl06LoMReVCvM9+t3y9uOOJiFL6KhC6En48B9OqGF0pv1LPV32obbf/4YLGLquSEMLCtGhCjNnKOjQIZTB2Re5ZtEHS6kQ7pczmClKZAaIrdoULadbSlHxVHUsU6fPlvVcas6LaiCqgHc6iWHQ4gMUS1tkhGmPbLG673H7vipqTRm5aClbioCe3qBQbx7bLWvtQhLwzPYvSl4VNkZwVUkszlOVPna7OGgmihFgoORBiIZ+sFkYnE2X8sSKusJy3MnS8r00DQm0u5SmY280snIu0ko3r3Zk+udCsCnMOxiKot2/V3Kx2LBdjYeTqnqNWqjG7W4Fmc9ap2gxZMBvhZCwPAsR6070v4n0JQRExFkZdD+W+I+eOMqzrLN6aE6zWtmu8rIVUL2xCpypaK5TO59JVhheZcR6I1b1nVaZRdiaUq6cAqotGyoWDg98WnxNVmLQ1U1zgcv27sLpRUlldCmLOIVVvRrIxOjTYs8pEeQWmZd3OObQpUgrkLIRdJhxsQZRkdVmSCqW3UpyyC4uNrlssV6HP0ul6qrZ2VCZbdkcQVpam8wGm2wLPJ2aF6VmiDF6y0gvDndA/ljYekpXDD+3n8UqY99Ke+2EsxLEgs18LmAn098ruM2E8R8oQKUnpX9vxeIThtS7jHYXpcMkQkaLE0UpDbr8D021i+sAoZ93tSOrMpUNdZDmvtU6yMXLeefFAEPhcnnHz/gOPT88AY3Z099LYKde//QVfpHe5+fZSrmbMCRMdNncV5fxO4fS+HU9Pdt/TcbmuKM3dZT4o+3/prq23LbbYYosttthii69qfCmwQkT+c+AfAJ+yyLEp8Ns/p3Z9+fC37wv2u/+uCTIWMYU67CVXgiWw1VFBVyUWBmRoS8ABq5N3q0BgcVgQRRIoGZJcJibVQtJDy3K9i3O4Haas7FWrloGqJaCqnuBVsMFfjsuw0k9YlyZUJUkxUCVIdgFPbXmDDpk8BoqDFmYtSqtzruUPJVmSJFkIcpkLa6WUO+gjq0S9ARs1XxVMULS2cw3mVMvMtzMfpTmtVLBHky4OCJ7k60r7wUQU7SThbLT/UgVOA3Zs1aZUXS1miLNcJLElLZ2tJQs6xgXAEYVo16l2m1WvBJyKX8EXT/DLsJoTvaCHmdgvWg5a+yDLHAtuU5hS5lzESjZcq4A5IKOYGmItMQi6Eqdk0QStIEkwFwRgsUSsLhwqMAWqSUQpQkqZLmZiUFIs9CmTXZ9jzoHT2BkYMdV6J5ColFVpDNXZYgWoNOcNhRwMSAhBm4VrHfM63EWFMiXXAskG5ICBHtnmwZSFUOdHddpwQcqyL1YOI96m5jzjF5FlHsfH4OKQ3s8pcnh25PQ+TI+dWYy6fgf43OyUIuXCxrI50ARa6Zi58HjpQS3n6XRx36j3TFgEa2sku7dKMTXMBnS6VWxdj8n7ssKvqNN7EsIc0Rgo54hWu2JfTyEpGjKaA3KYTbMEE+WMj6GBvaVT6JfnXNWdQU0ItgQl7wvTrSzXjzZOohC7QrmZOPtcLlGYD8L5FM1utEA6WbkGrMR+YxWx7Ag+jtO+3gcb4+7eAKPS2zqOXspSBiuTqM93E+/logwojELn+hDDXeHqe4FHDBycPrSyp/3hjKrQxcxcQiuLOp8T02PP3d0Vt7dH4tXE8/0J/VU7/vjmOcMXVsJ3euj47V/7Ad/9V098++ojrr9t49y/VroHdeFgA6ymdwr9+7Yqq/7L+WTrrtpkd3sHVFLhZnfm/jSwxRZbbLHFFlv8bOKnKT35q5aOfPxvyt/68pMvy6z4L4B/WVW/+Hk25p871rubKwZA3Y3UZAlgmHTRlRBtm30yyyIeWaNqHNRkL2mzEWy6BCwbcW1H9u3zrMEDd8NYJw0ENR2DzhPGdUVOAZmsZhxRiiRL2Ju4nrdx1XddJ6gAU4CoZAdDWtbiO+ZdP6NDoByEPEbyOZhTQm1i0ZbomrYGl1ahVf/CE+G8V9fA8OMrRkRLBNdjtN4EryCFrMatWqQKnoh6clkTTe+jVkDD3TJkzXDxXWyt7imi0EGuOXVcdrHD2XY2pdB27MPsO8g9zrwQ5LxqdljpW4j3qTIj/D614wVCBc4qJpVN2V9EzUnEOzxPsYEJJqRXTLAyC/M5oVNATtXyRFbuNNh8SqtxTGqLoDq2gAF3znpo4zfXG+3tfmPJ2fEckd6cQmIsiFiiVJOzaUzkMdra8PG3JLtcrEmymKWos4pKXxbYE2AOri3pANTqGu1jijskQE6LpoUl/wa4aBaKChILcVgYSSWaa0sZzaFDc1hu/jm2uSV9JiRljh3hKTQxVjlGpkPk9vbIYyrM50Q5B2TFVkIsEdckzHJpn6oOItTfNT1Fp0OVQRdxWAf2Gli3HgRd/b+w6OEkbXbE7ZnkY92YW+5mIw+huSHFk5B3djzvC6JC1e7EgZ/g41iCktUBLbc71rQI0moVKfUx0wB6PS/6K2B9PCZ46JiHTNhl9NpQiClFpltbh/FobUtPS/ulmG5F8bEbnwnIisnk4xPPlvD3DwUNwrzzzwLn58r4zD5nuhDVTcrug2QDbqcn718IDHelad88nQdOzzvKzWzPcAE9L8h2fAjs3gTCBG++NlCuZ74YDnzz+WsA/uRXd/R3O9IT9J8m/vDVB/xrH3yH9HcKfyLfAOD2jyK7L4o7kxjwM94Hyjs21967vefDK1PsvB93vBkHztPyT/nx3PPxd98lPG7Mii222GKLLbbY4qsdXxas+HPg9c+zIT9NiGJ2mHChvlGFG2uiWMR2MtffU+zlt37v7RdesJfg0oOI75afl13MtRWephVgAVbOMYeWuIbRKNtru8jS+3miiSnWpLweV8oCWBxjS0hrhwxA8N3xWJMkf+H2hJFsgpFt59hdScDAmn7nloY72xXPtc1giaILkZYcYAyWxFQgoIInRv4w20qlCT/KLITzcryVkeQl8ahAh9akXtT8U+sY1jav74snvWBgwroNlAVgsIvQdoGba0c0NkT9/zmKfS/JBSuiXS+sLFBHWTmJWFKowRIogiWba5ZO6VeOJyuQp0ZQt7hMgdhhJTsq6BgID6m1QYMl2zkoMgXivALpvO9NiDQJpVOKJ6C6zza/gixzcn0fx8VRppVHRW3lBZwjpYvkIZpwqGKMhXqeacXIKLYrXRKIhqVsat3OeQGdtFvdW1jsPRVUF9ZATYwBZ9BYEqkrQBFnKYQsRjwagM5dMIKQhowEcz4x1kZp9ynPoc0zicpuP3ISJYcOnmyyhJMwvRmQw5l+mK10Jih6Ws2VldVqEW0MKDtB7eOqw2vwrquKmva70DsbqjrHzLJiGYV2zlzdSnp1MUldhEkraNGQVYEiJj6K3Yt4FlIVwCyG3pU5WV+ykJWFEeb3Xos2K08Ci9NMA1asLErFQeHad7U2hJPZppazA0su9sqVQlDD0s4ROQXSY2iATJ3jy3PW1pjM0kDSMBqIONzhJV9WIjLvfRhvizFJ5kA4hmYfTS2py2ZvmxsDKhDPcPjMjqenwHQljM97m+cF0pG2ntLRBDu7x8LxZeDpw57Tm8TnvT1r3333gfsXO/afmePK5//vB/zfv5v59dvPefNrxoR4efcB3b2QTkqYlXiE/WfCMVidxw9y4DwnrvqR05y4Pw6cTz35yZ4Z8U3k5uNAerxcX1tsscUWW2yxxS82fhZCoOtz/G1kWfylYIWI/Ff+v38G/GMR+d+Atresqv/w59i2nxzFdsSr44Q1Ct/Fl0a9LklbiUV1g2gJdP17RWW28xibIsztHX556V+DFYKp/selDSJyQWYIowEfzUpRADXqsKay0NXXlptigAXTojTfrEDN3PSCfk1eEjrJcrkzmz0xLNLaME/R6fSY3WQ3k/NCZy5FbNfQwYrch0ULA9vhVgcKUGkv+037Y7LBl0kQr5uPay2QSpFfjSlyWc5TlfBbUipWq93q9OfKVNCFAZIXq9E2H2oyDa0cpB3uCxQhJ9fNWCeYtS9xAbaKW1rWNld3B40QT0bbrmBITtpcT8ypwe7FulQmPgVKsV1mXPskPEX6O3cGmFhKklbjdVFuo8vUMWq7kN3GcBJF90rsi7VdMVZBuxHa1olWRxFZ5qoBNEoJnlkXgWk1BsX7LwrJSBva+3kq0FR8l7pbzk/QZd77eZreSE30nelTwbe2+10EZhrwVsuDZKaBWSpQQl2QSuzUrGXVEmRdr+fWBqGMkamLxFgou8V1Jd5HwkPkzcPerFabM88qka/aI6LI4BarDYCsDwOWz6/BCjFwoZ63ATdLzmwMr9EZGwHKPoPbhErS5YEj1hdVmh1tvaYq0BWyVKZWaA5AZlMsxAyafC1NsdkCG9C3gHEGEMHFoq3rp0DIQn6KZF8Q1VY1nsyNI47CJJFy5d/tTJtFImiYKSkyJV3GWGgMs8reKLti97ta6YoxI/JA0xuZDtJcVYw9U0yHqN0DXWxyO+tnFmOajc8Kx1MguevK7i6zewXzy9B0d+J5AcbVy7HiqOw/L4QxMN0EXnUvANh//YHzBzOHTyLxBM/+GL4nH/HqN/f86ouXAHzy9eec7gb615h9a4HhlZK8Nut8t+fN1Y6Xe22lbfEEvbcxjmZzGtbA6xZbbLHFFltsscVXMH4Ss+LG//6u/+n9D/zoq/4vPipo0IryF9ABDKTQ6LTh+rJadRVWVGr721/IXQVPStW8EKvLBi4Sa092gm/qNiFDsN1N/3zViCirkgNwwOQklBTRXC6TgGpVmhywWIMceMIR6v+wAmj850ijfLfv+Ut5bWM5RcbZyihiXxh248XQqtoObN2lNWFDRaMnkNF2fmXVKV0lLQoLmDGLAQvrGePMjQowrIXiapTkgoFOhUbU6ev+2ZoYiZ1rXXJix/12uPil6WUsYqoCpgtStTd6tZ/X96klX2L3sNLs2wdobUON8dLsG3u1eyGKqtlCSqSV8YSpJm5C6cUtJ832siYmYVV2Uq+1Fm+tWiMXO84RSrW0JDKrUOJkGg+ijgt4GUiny9zw3XSSLlUHnfW/aoo0AcW19kizWQXtnRWQF7aCelKsqbT5Q1RCV2uGoJwXYKGur8oeaboQVUsmellXXRcOQC02oQKToCznnOtzYS1k2sZ1tY7GwFh6S16FxpbQTgknIX8xcL6dDEyYw4U2CDjgUMRrflZgQ71OBVXrnK8Dnd021MG/pkly8Xzy8ahLu1uNIa5DsrbC9c+tn4kCaFesvC2qVS41kU7xNizlcRdMorhih9W5PgdbM95GqeVA2YDe8DoaiMSqHb5WrNwDKsqrQ7DyobVOxwoclM4YQvZcCSvAR5tOSEHJOzg/l1Z604BsIDwFcki08hj1NeXlPJLKAqSp2Sqf3lOCHz98At1ToXvM1hY/99Tb8fFWyL0Qb83etjuaUKf+mX3wXq/hZqZ0CSnK8Fp55w+E+8cX/NPfcUDkeuT4tY7cB7oHsyiNZ9i9snHZvVSkOGNkZ2U8Mlu/wYCap4+4sGDeYosttthiiy2++vEXMTX+JjMu/lKwQlX/GwAR+fuq+j+vj4nI3/95NuzLhO0iL17z9ktWZQcCqkit44emP7GuJYd1TqFeW67tXBesjfrhmvCU5Xo18dC3VCJL8mTqovGWhIURmIK5AKwAlNKXJQkTXcQI/bhWTYC6G71qX2MPSKWWr46XJZnRycYon5XjFJC4qj+fg7Ejmpgoy84tlpRJLC7gqUsJSU0soqJD9uQ0NLHLZQxtfFu+4WyXCyeNGU9CtEJRoLQdTSm6JG7+Z82qsORIW2mIuHtLTbhs99oERjWy7PzXNlThvVpi0WljJ6zvxdtU/0vmhS7JvW+Ws9oFTkdjB8VTTRhXyaifYz0mP5KAyGX/6zmiX8NcEAIzHXmXLeHL4UfBugryRBNlJc3L8eKgVZGmS9Culay0ooFNAVJnZRI1hy05oql4OUA2IdE5tPKAEAzEqGUCP8JskbeArujz4S2wrgJOjPa7uvuOeIJb14v6XKiJd1/aOSQLjBFNwX5fQcxO0UlIj4EpLmUSa4ASVues7Wrj7M+cRompfa3r0X/puibVXaaWyrRnUFIK4qU0K+ciF0YVL/uS2UuWVmuqki7mG39+YABPriBPdbRRf+6oWulbnSNqTJsKNLaONLxkVSYlCiLE4wIutpKmwdagOiCcjg40THIBTL69nrSWjom1u4EfsQ62PednWZ4R9d+Hur7SEcIczd2pAnAizZlGe58nUZ11BfMz5clRphID/b3QPdT7YmVPTRPjhT3vwwzdG6G/Nw2N4aV/PkQefsnKUlTs+b97XQh/IrwczDFk/mhEbzPnAGUQ9M6uUefa7i7TPWQ0CtNV5PTCRE+Pt3Z8ulHyu9OlVsgWW2yxxRZbbPE3Nn5SucnPEsz4qUpb/pef/npfdu/lv/6Sv9tiiy222GKLLbbYYosttthiiy22+CvFT9Ks+HeBfw/4hoj8d6tDt1jV+F9vuLjhWhRRoroThaE+IQs6Y4J3+Gbmik1Rd/Ea3uS7chcaFXV3srIK6u9Xu9ONiu+fbzuAvgt6UToQMKX9FQNE5tWGfDZafN5po0TLJLytk7Eu61hT20XFarjrjqPUXeBlJ1jysouL0+arTWntW3Aqemv7akdVk6K9GIMi2W792rpVgiK9UbpLULQPlPESiavOHRqVEOz/F8r4MobrnV2z89P2mSaw6W2WOha4vkSoY7yMfXNHCMDk7BZZatwbQ8bLiJbyCm3WltaAOlZurRr10kJXacKTbT6ExT62BDUxzcmEAet3SoJ8ZZ3Iw+L20Sjrq1KXxvxguYZMEH03O47Q3QtSAvNB0F7bWLUILKVFvqMefYxDXCxgdPV3JTVEd5cpLrgYQnFrxUXvoWqBxmjHAE7HWk1m55NUXO9Am32rNj0IZ/RUaPXHOd/U8pGuoCFcsB5E3aVkxaxoZQyA9rS5rVGR2QVxfYcdjIlQBpCnQBgDZefMphVTqZ2wsibWUY9naSyQ1ub2JS7nOyzru5YqpUK1qa1uOPY572Mtc9MfPUe1/mQWhLC4HFUL57Vwqrcvp8WFCAU5uwitl4oIumjnXDDDfO2UhRUis10r+61vtsjr52yuIrdvjSegMTYxYUJl13DJZgmmE1P2xk6pDIzah+owUjol97J6Xji7oxNr+8FZSMUe7vNzu8YxRcZnQvewcraJMD2z4/ONs3SyMUTyXugezEoVjGGRexMNLb0xMkQD/UPh9s+sDfelZ3w3Uw6FMQmSI3m3PJ+kREqy+TvvhNybq9D4wtfi3thOa/eqLbbYYosttthii69i/CTNio+B3wf+feD/XP3+Hvgvf16N+tLhL8Xrl3MpRuuV4KCFu1LUZLW6M9SXVMmrxJ+aCKrRhyt1vGZbLQuu11ol57MsOXVwGnVzosAU/Os1Et42+7wJ2y3diqNYPpJBk1HLwyRvgSNWOnLBj1+DKbNYbbpnxjLLklitoglxvn1c1JL/LEbXr5+poobBBCk1qlHm36Lwa1JT+Q+K7HIDN1p7S02sLKkos433WjhSPNFp9oxiSYZWwT/XHqmiqWGShSqOa0ZQKfF2v6QsYngVNKgCqGu6OhhoULoVFd/HoDlxrBPLdTlKHcIqcrgqPZGarGK/Kx2mSlmkiXjmgzLdLGiCvJ2AyjImpV+VA/kxmcxxAaC/s6QmPbo2RtWoaPepjrEaNb63mqpm3YsJsbY1U4fI50oRS5irfkkhkLM2UMLG2X5Oya1EqwuHg2EiWEmRAzklcwFIqJfv2A82fhKtDMnasPQn9sWAhbmqkmIlIJMYcNSAxFWZgdr9q/ob6vfTGldPDNorWXXRJelLEwGlCmLOstLw0LZeTKlWlpKJzsttah+DNHFKFWyOr+9rbWdkcS4ZQztfAx1E7VxJmQ/WjpaoP6yS1wISZBHJhCaAWUvPQKAvF0mvT1H/wltz3tcnAjoU6/Ig7TkZzrII5qosehTrPkIrZWngxeqZoCKUUJrWimQvvVkDG1UsM6dW7pMPi0qx+L0KeXmeV2vSMNqzaI6gZCuLCgrubJOjkg+B+dpLbhxwqq4sdU4IprlTbtT+Tnb+3RdK/xrG5zQnoeP7wrwLdK5Ts//EtIzmd2Z0r4zPzbWl6nJoEMZzRMqi5zPvlnGMD4H4WaS//9Hn/RZbbLHFFlts8bcv/rLSjS9TIvKzcDX5aeMnaVb8E+CfiMh/j70G/hb2SvRHqjr+Zd/9hYQnDWsBtbaDOVexQ09CfVdf07Izj0IoJiqn/jKpBTT7ZvhsL4Jhpu1qL9eWRWOhXqMljPWl3IGLub5we4LnzI1W361L4lzDEmcDS0rnifiK4SCzfaaqz1e7VvDERUDzYkvZdv/rddx2s6x3JSvTAhYNh3VCV/uLvdy363QLo6CBQp0u7idpESusgp1a7GdVFtHF1fVl9rr9sAJT6s7/KoGk0yW51MAa9cnXtssZzqHpV1xE7VPt17z6PeZeIsWwBA02Vy4sZH3cKw61vgftPtUDPhcv9FV8Lqos/URgvlL0xm03BWMorDVLlMa4SbuZ1FUQwD6f58A82mQ9dz3hJHSPQhyxpGfBzbx23/4OyUQaNWljRWRRu9a6b1lMXLIerwyEYLaWWqLZ3a6aK1g/Sg6UEsjHiJxi+0DpCzIUpCsEnxex6maokOdgAETVD9ElRw5dbu4ZIZRFd8OjzME0McawCFy+DfLV6RHVxDF19fNypsWWOCmhKwtBpbatVNBPCam0NdsEORsrpzSwpd7oxaWlJt6rBVvPvVpDjCvwQVk53RiQEvf5ghkzhaF9VrQm+SvQ1cEBHOStTjdr+2AdlCIrfRqlPSO00Oxqxd2BQlca+yOfoiOOPuYV1KlDUp8H0//P3tuF2rZt6UFfa32MOdfa55z7Y2KVFZNgiEbRICIo+CDGB9/8/wFFFKKYPER8UHzQFyOx3rR8EYIVlCIPUfIiCkkQBCEvShkiiOYpMQErhNStVNW99+y91pxj9NZ8aD+99bn3vbWLOufe2lRvcM5aa84x+uh/Y+zRvva1r3ECN3QUHYwA9E6yEsfOVKFOxo4BDGwhGs8t/3dAvT+dATAnUGzsGYB0gIl8B/iFTGvlYsyx2Ad87dBdrDRwvScriYFK2wD6dQCkfNr4ayWkfjVmhP6KH3MotneE828D0BTyxQndGuDsJdlMnDTAZas0BFy+a99vL8DlVxWf/eKPn/y4bNmyZcuWLVv2G7Ffi1kR9k8B+K8B/BXYq9jvIaI/rKp/7mvr2cdapCbkC6I5fwyjk0tTtHNE59ABLqVF+QaAyCLccPoye8Rf5nfyEY6mkSbijm5NRQHsb8GIDNbv2an8U0lSGn6J7gpWP+9AvpSGSChULb1FLRqO8LUK4kFx7ej2IyAWjk+Uhdx0KvuZgpDbcKJJCOSgTzA9WN0R8/5HuTwNsT4BdGOL4HsFgpzKoMLH+jWMEoRNLfIb8yBICpjERgAAIABJREFUxy8ixelIueileFWYBDOu5qgJwaj9zn4pWIOt0wZQYW/UNB/28qjBuJmYEYTBoomP6xpIAZP8vOp/aofTwQ1wi+/kIqMZr8ICpgQjVJEOK5GiNfEytIreGe0qCWDcmuJ8t0Hbhst3KauHZCA6SqkKAXf35Ylw3rwDHP0bwBXdC7sE/v1zz/2nglEhp9jpwB3fGVsVFVXgfGL0LyRFQINlAQDcBL3zAAI6AaeBEADAT6fPh0KELRWFdAxys3XKUpU6HOOxkL4xovRoCEAWgVnNny5OeXL2Mapo2ISYECk1gCPFQi0VTA/fLA+pNaCxpihrWy0qrLDfQ/0iw2H2Ki3p+O+CtnVcr8cg/wjb2rxuUNHB6jjndAE6bH25A72xMbTiu+sAYHxRZ+Bnl2ndmQsgc7Ux2BzoYK0F66jJ0B11cFI3gkQFogPOkIGlWTiYyjcapDCFlajdbb5ZCXQjdH++67XjJAW/MqiTV+xRqO93Ou2eYGdQ6Q2QCycIo2T9ZJ+TeJ4EMKte1UU3QK6C5gwncTDl/g24yKhaSWcxcEQuwM30NdHi3lMApGjPgg6g+zXkNKZd/rtCtjmjgtD24lvistJAli1btmzZsmU/3H6crImPsY8FK34GwD+pqn8ZAIjo9wL4MwB+vGCFlkh1nWc26m2UWezQjM7ppgMcUFe3Rzk/AIhI0XCHkgjvR7cxR9nzK374Xuf3eTo9oh0RyWBghK+zubPe7UXcOoIEVEhhGhyzLzNy8CNtw+cnWSEPe5HEymQqwQrS6vxd9olgL/SbDoeU2SJ6pQQjOTgR00FJrzbQSDcqzA5kaVm4FoSt57welGs8opOZI8+AYsybXjsmNCKQh6aQJ7WIsdPcrR0DXSiqSxAm1gGdXlr0iHmNsfv3BItQC4aD+TiHDmhUXzYdXDFtEbnaPGiUP+0EfYlE/3nNcmj+y/HacOx7VmeRk61CizuAbeugz4CuwHlsaK+EBx/Y031sr/Pdp+w1ACHTzZDCsuE7JrCiPwEncc493ykjyDGG2Id82p7jc067aU+E4zRdDXkW6KY4SjlJuTWj14fWykHZH2HNqiT95vn8pFltJECeOEaVwE0hqcVg8643NueZ1FMq6oy7T0hIJ1tvPOtoSLCN1DRg0LIiA3m6AG1q+0UdbHnY72U7Q3SAJcQ6MRwAAyQyNeCgZCNEn/rZcLAmYLDtJ0SMwRFlhWlTqH9PrFaq9rWB3pGDuYS+lU3ICrp4GeOoDpNVOTSfkcESOalNY8tqMvHMONiqtwCQk6Z7E1AbYw67ON/OrtCLALc29qM/Q8QrIWlT8J2TxaOfncCuEIjdZ7sCu6Bvvtc6gV8ptTNsrxJ6sBpgAApdnAHk8xDAGWJNN4FuQFdP4fDunc+K/pmlsQTo9/QdRruN5/u5eQpbmVu+9gHIHAy5sWv8EOiirkcynk/9txFefvI398vHsmXLli1btmzZr2UfC1Z8P4AKt/8Xplvx47cPsQYAAyx2//6CIrCo6cNWsc3URQhH1L9nwDQmSjS6XjKAgXjRtLbc+eFRXk8jgg2kFkNoKqQDFOfvir7Zi7N6eoA2DCE8IXeUZ7yC6i/p1Y7PatnLiATT4WyD7k5aOYbCOQ9nvkTX9SqWh91DwC7aHdFyVbtOgEbpnPp859wQ5ZzlHEeJUP8sc+ErJd0dRBw+iU71zrf6V0r2hm6azlRlaKg6u9odm0lz4j7aCaq1bHMfppQQFxScABsXe405AAroJAPB0BaMHhgLJOahgDt1TeteirXV5hkMm+Y1+pue6yYXzXYSOFGL5JKnOhmTBCn4qQ1or4BcKPUJqOzlmEeSAVa0V0K7jXtO29y2nV92roMY1K3UZX8m9GvZ7y6SGNT31BfxMRxbg+6u2XCYI65Fk4KqwC3BRCFR5pPg+idRMpSMecAK9UaoySQgi266IGO/jn1BykBXaFcT+4QDUVWjQcj0Lh7BzpImlHoMcb4f0/lxc3jf74wQ89XO6KcDAIElFC0NAqCvDUqSIAhfOghe+jMi9nWeSolaVTKGRB/ilSo29pGuBui5jbFtzrqoD61uaVoAgFdnMFVpjc969k+bAY7szyxpBHxxQm48QF2fQ2LyFA6bj+blUc+dR0loL/+aY4ul8X8v6DQWHvXBdqCzQXeGXGWsQ6cUIw6Qu38G0GcnFCc6thQ3lQboRUDPHXgiyEmQ712wvS3ddxZGe8uQJzLSzC5oT57ude2Qqwsad8p9e37mfeyWAte+KA/cZcuWLVu2bNmyT9A+Fqz4C0T0ZwH8adj73L8C4P8gon8RAFT1N1A99Tdm4RS/ZwzopbzkPpojDnqlyREYjAj7XA57uTWRzvmFuL5Yy+REI1+CFQppw0kDkI7rBHCUpnVynkskMh2bUNp/ePF/cGYnRxqPjr4dGKkimQ7TxjkpMqoWwdNLSbFoAQBYOgpJRNLj5b/0KQT+bnNFEx/KcOiLAxwR4+hLAhWVRUMwx+R0Snd87CwafmUHrdRSTNKZDNSABmBFBQxJ5wqQq8/FCWeDlOsDQ/iSgO5irwOgMicpWAs5H+EDkjngpEPjJJkJweaoOikx3gpWhBBpAbykUZ53vvHqBn6nh6xHAAGpQeKO6elttpt/fqhpo9y9jzyDXiA7th0jetzutp/aYdeQbcyVzTdZ1YX4XM3Jazc7t98IckFWPzAgZ8wfu2BqLmNjyJVGKlM4oyleWYECHek0Ho2nTYFIE5oEUMcwVQsLAsh9mE5yjCsqmMicKjPtX/g6looleZg/V+y+oAlkyb7w+2se96hcHVA4CMkWiaD/5UEnAzDAxOdJWLFdOvjSIc9k1TKOMR8gWMpIgC2eKpFsKIaBi36vkZL1I4GAGBjGPb0p4HstAIHYozYXnM/xnKN4JnlX9CrAa/mnjIxJJzSAu83vJ3619uI+p4Md7CwXUN9zzVhPfBtryZ6Kwq+DMcInpu9j3/QLgy4CPcX2T1gnSyHaBPwkuH9LjP0R9/QBsJggbr8p+p0gTwJ5tocI74L9ydAPq8xDkINzCHISaBc8Pf/4ZaWWLVu2bNmyZct+I/axYMUTgL8J4J/wv78D4BnAPwN7zfuxgBWZYtFLekHhuCdIsVcvGMPpYIVePPIap7mz4vIHXh2BIMSWMw0MR4N1NFUdk+KQyjaqV6C80IaA3QfHVdK+08E+C1hSz/sAWPGokfGhFJBxMTstUsvTl3KNiRApDQAAlYESehMRqa+OtetJ1Nz/87XQtd3xhhhN+r0Ic4k059hiUWr3O4FvDO6w6L4Wx+EEQJ577mkotWQndRffU0tvMTbNcPRDz0JJgdAm3Mr1dRa6010hbbBpLPcfYHipT52250g3OYCsJUPu/AQrQd05dYDLykaWNlzPg7y/uo3oLmAOYL+E8++OIg+ALyqsgDyi60BIRKK3FxPmDJDERBcHAAHYfuYbBpMhtDhSk8XuqRDztFKLsNK8vrbbW8L2am01TxMJMM9AAMzOYZkHbQS9m/CggXwOGgTgFcCa69kgCAKxbqojfSEYPMHMyFSRcgNtYqkEpKBzyz4NBoL/rIChEFgKEOJr+YNuyzw/q4kUYAwF+Cn3ozYMMC8q7dzHflaB3QOh9WL5ZMbIgIE3R2fTjrhY5/S1zbecs0EiHScBhzRNhpJCJwAyc62ajDna1crAwu7fPN73PN8YKWMa92WkFB2E82Wz53uIC3cyhpITMgSwNDDfS3y6vgUTQIr2yg/P2wFU6C7AVdB3zudfe2Hw3So21bUIi33ZXgiyb9DPT0sJ8b0YzytE+ds3J/SbB+73C7a3fowDdyTGUuqvBLkQ+rNNWn9jwAVfTJNHFQZCxRh6g75sePtaH8jLli1btmzZsmWfnn0UWKGqf/Dr7siyZcuWLVu2bNmyZcuWLVu2bBnwkWAFEf0+AH8cwE+q6u8non8QwD+rqv/Z19q7jzASAs5ROUBbMBZG1BYnjdBZidzZT2dV1MgpYVLm19MU8Wt0TLsOKr0SeqtR1PG77moROlbAheqkMfi1RPaBZDAAgHYKRnWyPAgwxf/Sd8VDVC+uXyL4UzVPHdoS5O3UnPsaQVUOpsAYU843AJzBrhj9nsQ8nDWR5TxJLcLIZZ79en1jT+coqSARtQyqfkTpQSOKqqNqgUVSnR5eK7OoU7NdzLK2NXjTBHaGQmpPxFdeljWjx1U7glxnIkP1sCh+zbm/OgPngtSuiNK5pAA2yjSIKIsa/Yh1kL2k8Zd1fTQ+7NhgWwDIXHlLC6IhVJpaGRZJll0nEc3zjX3f31rVjhYiiLsxIjJaT8D2jib2TqTWcLAVYh83S5WRi0WHo5ykzVODxLVC7yKmvJSZzLkq67y9GiMmtDtkN5ZI7F3d7G+NtKuH1Iuci11HOc1gx7hQpKpO59EuoIuix7mvbM1sRR9DMVKOuov8llSS6Z7ze6fq6WAbeyn2Th33lBqyqaf7zEwsPi1Ny8bAxiKSYFZgMJwA8N31PnYCPXXwJqZVcowHTqauKCy1qpU5m56hfk3wzI6K40KzhBXqzIrOzkbqcd+xr3fso7EAwYZobxn9c825sEohyDlgZw6Js3j4tTBUyJlP8SyDs3lyXvz5de0mPOp9jIeAtWMsntxbhdW2vRBOapamUqaGDzIdmJeGQwD+xoHz2ye02YZtL2QCp+IpVSeg7wj6pffhiXG+aXYP1YpIMTe3ZuWKv/eBh8SyZcuWLVu2bNknZB+bBvInAPyHsPKlUNX/i4j+FIAfL1gRDm3VkoDaS3S85Ebp0OL0aogNNoWew/HNFqI6RdNRPrApEIJ/J9nMhZCmKKK0ZLSl8f5eqPfRYVVLSbB+ASY+OZzwzEl3ajd59kPVeyAB0DAJN6a5Y5g57eHwhDAhHHSI65E5dFNaShW1I7VGHIAAfD6peAjZx9KIC4maAx+Tg/lnU0+1MYp6ppu418bxO4aTEWtVc/c1UsgLLpVihvowNszfEyK1wBxekdFXhms1RDsPoE6mIkUJwqiKEVNw0aJH4ZoK4VB2b0CNnt5erY0KMOnuDv6GSch06FMM4KM/kVeRGceNSibeX9c54dyLQPe0Dr0MlEtd5O+4MM47Je1dG9CfivOlgG48+qUD6JqcUwdJdPd7axeQgxVEis6A7Ay+MbYXr0byQLNPX9XXIz8/kWkwYEAOQI6R/iA70IXQaw7QVKN2ABXkpTf1tGM+BDxoJwMUr0PEUJ543P+5/4YTrSejHx8ATePQqL4TYFiAfOGEdjs3yormXsz18mNJTX9DxnH5fFS1tA8XtLXSn0PPBFGaWAx4eejipJGj8TzcNCueQGFVQKTMby2D4/eJnmWS2hijPnVLU+lkqRsN1p/puWSpdext8kGQO7+Hk6S2igT4YX2UnWYwsPkjzceVqVLkc35nA2Oijxex0qc3T4cRSzGJ0qaA9Zlvdk+0F7ZMogeAjE7CdgdADcdFwE8nevThwpDdKoS0F9NnaXeAvCRpe7G0qfNNg1wU/QLIdeSyhGBulDJdtmzZsmXLli37VO1jwYo3qvrzRPWtEecPOvhHZsUZzZ55JQA6TaCMBCZYdoy+62ZlC62ywyzsGDnvuit0E6i4sN5D2cCqWF8F4wBAm2Q+d1px9LMPUYEgfMRBgXAHQyf2QkbTH17eEaeW36toZDgJBlZExNgaCjCkNwd56kv/nZ0V4pVNUN66ndFg1xsnhXZHrRghjRCCoTmGUONvo+IBpjmAR27jC7z3wh8ghAYjoEapY558bqvGwsRGyXd8MmdOMa+tH5MR5VLAIdfFo9PslRHSN/Pcd2yw/PR0tosD6uskJ6NfCNs7msapOxJcC6f8YQqyjxLOf2FWSMfEJppYJ94AiY9LbFAkA1DRXaA7IE9kIJwDDanlIIRz7x+OuBdHfwKpOgEnQ0slFGwCfQb6rtCNjYniQEu702BaEBy4KQBMFa7VWfMDcKFOhaNbZeKiDC8BQmKldcPZPp3tMynf+nw5A0FYsV3tMZhlQcVhICUwSzK0dCfoEyX4KcIJaALm5BOQGjDEVnqVKqACQA6GeB+paPGwX0dOtkokbGyzmclEOTdK9ozrz0M/Qi8C3A3owKtXm0iaR7kHYh0BK+FaaT7HmLdJ2DSs+5wShrBxYbkhGDqsUMTGHNfUppCnAbqp9yuBqWZitrpp7hOggKgtwEN7ZujmIqHxLLuqgy3WLr2y7YtkVqnpbGx97OWmYy3I92+zueEDk1CqepWq/uR6MC+E8+1mWjfXnsecF0G/MfiZsb0jyCuS3RSaRO3VQA86gd5HZZrQnKkCzsuWLVu2bNmyZZ+ifSxY8UtE9Hvhr41E9C8D+BtfW69+HTYi2/Z3vLzSYdFZ6h6VKqwEicoTlwIARHsAtAdLmaER7uLyoh7OV+ZnfKBTOzLKaV5GDfnbyzTInDOOEpnFSWW4yJszI5R1ri5QKzNoHOcvxFH5wr+jTsAxBZLnNBH+0Bc2f3ryeLl/SJWJ+ayRyvw6XvgFoAajZiuVqPMAOXTTHHtGgVkhMb5wamSep6zcEf17jFZ7RRcAk9M0YR4pjvn+l8lkkHA6MDvewYAgRVLKS5UK3SnTSKZoec3pILgApFHNj42ndJhMt9h1pAFhpEYk00VHCkiWfcW4P4bD+f6WDSe0dUawPCJVRXZNMEhDyVPHda0BjOoPKPupglDwPkTJz8Ikysh0VGS5KE4GOCqYbDDg0Z3QrCTjfeC7rYVc7BLc7bNMgcn0iXK/1k1AwCmMXsYUTJtRUWScxy6O2yk1c7FdzmxWOzkQwyCnWxALtk3QmoBIoUo4z/Hw6WezcxwcIFIQyyAvxVx5noMqoW09v++dIMIg8bSli4n/TuCUwOY/QK9OUOYUuDTBYTEn+07GICssn9xLsQcOB+cSRJ1TXXRTyHNFBsu5CvvXR2nA3g3jmRptPFZycrHKrrYnoqRzHCdM0MPABfV7rzLvsjzx5swesUoaKM8dY2NQpnRQJ0h8v3la36ajnK1SVpYJsEU3ATag72QVR2pq2SZW5YSNBdFuhPOlZToMGKBLhzYDk+SJbf8H0CxlTzvYHWWB4VO4vQNef+Jh7pYtW7Zs2bJlyz4x+1iw4o8A+FkAfx8R/XUAfxXAv/619erXYXybab1ZmvC0yFM4LmGRWy8AmkeQp6i7wqLkmcpB6fw/liKsUfha8lKZBv05KNHArJcRqRFN0RtDthEZgyJLRYIsEmeaFu74dC/L55F70vIS7n3ToJB7NJzVo8FRztXH3C+YWQk5Uc5KuYfT4IyBiOBGdYYKlihKZYAxZrmWqgDFcY0XbrhjUIGjcDLmcqXuzMY8V0emFRQhfORakrZGb3OefI3Uxme5FMWxSaclwAiaHH2NNXAQQp29MnQ9jI1CDGiUgKyR7rIfBrtAoFFpIfotPr5tnCs8wIfoq0WjaRrjKOurA7jSgUlVLQR4hJkPypKcpmVhYElWppHSv0cHFLA0A8aosBFsAbU0A0t3oQQ3ZFP0g10vIMBBtbKxsL0aFVFST4QA9RI2fCkVL5o5zNuXQ2eDD3sGbA9gVprvte2l9jfAkWi3HH4G0NIgvtbHpaWjG/d8r8DUJui7MS1aEzAbYBEgBJFaW1kxhADhsQ0c4FD/PYCMcT7ladgMJTSmSN1vUWbUquekTkxUA7lYukNo+HCPe6PcZ0q+RP7clbFk0mg8wwiQeNAWMENbAQ9otAnAwLjYT1OZ2LEn0BwkACDMI00tQKUd0D0oN2rgwgVWQrXMD22C3VkxfW8jbU8coAUnQy+0hex7OCNKAaYsgUs3n8P6HN4c3BOd55AV2rpp9bCdxzeG1HlyDRbaO3QT00bJifZ9cnolpFeaNY/IgIvzzQ/a8MuWLVu2bNmyZZ+G/VCwgoj+/fLnnwXwv8JePd8C+JcA/MzX17WPMLEIEslwJmjz1IbTGRW9RKHh2Rnk78+nO5FP4z1Q3QHQjnzBld3ajxSHjJQD+XJdSysaL0Lc4RkOUEae01sk0GbOqbIMFoCayF2USI1oZzARButCp0jzexY6GQyoGqlaXIsg+iUXBRyIsYj++4BMlMIkIGnlxOplP72P5H+nToAmG0OvMij1k8glOS184AY1l3xiMcj4e+hf6HCU03GiCXiJeVcHESZnIhgI1WmqToGDEWhA30IL4n1HXb1PdjplNL0ycfigHF/qp2iJhrJrTjx7fnw6uWrh+6YjAk1IQb9M1yBz3Cgp7sWxycmAR68xKAGRciQRsfU95ikYfEZZUFv7SeARnpIRa4f5PtKt3C++VsEaqe20AFBcy0AZGRkHLL1FG026M7IhNWTk4se6jkI/Cf3SrCQuTAC0vWKAMh+wmhokG6WYp5b0sZoFpUzYoOguwAnmmaEUE5Jsp2Zjgu0lXAS0jTQRIrgWhpUOtecQLC0FzqhQ08WRpuBmQpmZJhKgBRvoRaygq0K7gWXAAK76c0O/Gbtl0kE5KNMjZAf4dIC2jCXYRbF+WoBOez4g75uRwhZgg/2uwFT6dJrfHAvNG8vPJ/IxOvPA0Bsd88jGfNCTsww1XzqUC4BBADcFO3BUU22kM3rT1LHQjZw9UfroaUIqvufOorVTGBpalZCrxXPsIuiKwZgIhl3za/plbZ8ImqNtMdbeGf1lg27N1zIGQTi+wKSjsWzZsmXLli1b9inar8Ws+MJ//r0A/hEA/yPsde3fAPDzX2O/PspIgf1LePTPPtPQOdBCjS3vbPFZhztkD46+CU16hYl4V1aPogVYUdIWgg3AGFoFJIB4KYJkfZRqCdQpX2T18DSRGoUlr5oRjoIFQ/OlX0AjraHQ0wf13/spmqwEdfr0VNVDrF/JCtHywowBHlC5RlaZONzBhbFXMoc83s8312vgMlekoDbAm1ivUNXXUv0AkdbBAPrs7Gf/gHQeSG2+qMwDFBZxFgDCBmqVa2S0M5wqhTkbyQ7BiCw3B4ZkAFC+GDlekIlVThooFQwB0lGPc6kbnd0ADcYpZNoTMQ+RehJisTW1JfrOmo6JQk1TIkGecmw3Z5fKXkXZYulkNoAjvyDy43v5uziZtI92cowdKXpqH1jVFN0cV9rsOqFJwR65jnYNBCPTUQGAzcEgFw8NnQANqsQGYFfw3m2vXoG+C+RmHehXdsCipNcUTGp6VgRwBtM/iHENYdpyz9+HExsaMJE2kQK5RWdG2VIwZLOKDvIsllIApNgoFOaoAqYFkXuJgHKP9c1TRPw+YmdEqAtxKmAMjmvHFk4uKdgH/Xrbcd4b5GVLVkBcH832k2w0KxOR6WAESIl9pB4BPt7YewEkbAUcJM0xoN5HNNbEBkiDHaQP58Pmh5qCuJtGxzgZIAVvahokvc3pHXAww9M6jqP5PBeGCyta0pXsO0hhqChM16M7804xpQtN+h7y8DzKsY5/RxJw8XbgQ2b158Bhwq1agCtqYqCkGJChVzJgsDx3Xr4A+M2PX1Zq2bJly5YtW7bsN2I/FKxQ1f8UAIjozwP4h1X1+/73HwXwZ7723i1btmzZsmXLli1btmzZsmXLfsvZx2pW/CSAovyAu3/2YzUSoypbRNw/1Iholqhd1WTwyDB79JPEpAAiAtphVGM+kJUiKPLIvQ3ZacgdBBXdz7NrUpYkDUuxzOiTC04iopqhzQCUcqF++GOOt+tdUGhiEGal/qRl86SlUXUdFJrsilTnb+PQ0AeICHmeGW2fEY2mjKRHyUDA0k3o9BKJ5+j/KAU59Cg02mWd2A4xbhIv2ejMiqws6lVKUv/CGRoT1b9Q1iNSml+HtkI9QUcJ2oy+KyztwvNVJv2CIvpp49HRfuhNACM95SxRWPFIuKgJ7Xm5zv7MgylUSj9GOlOtvqKXUnoyFmlK5fHzY24i5aLqZWwW8c7Uh7Kvk/1xUN4vjxbnxfFQY9tktQsiLxFpfZWrR7FDD+JEBtSr/kVqxEQfGZa6FcOLX0KvQCzazZuiXTsk9VUY9yc2YU+/X1IY1tcwKyu4ToXy6Cdg1RVMbBEjvSH2Ffx58TAGY3mN/UGKFGBtd8LRCfJEYygxdq9EMvQr4lqUbCM9g2lkfewhgFueA8cT4fLmSNbA3jouW8fGgut+4nZseNuu6JsvxEEjBan5Pn5Iewo9C/hep12yqgsAsLM4mE2DgwizHoQ6+4QVfPC0jOR7KdgdI/WiPDOclYHmDA7f28lGEXLmgafHdTKGTT4DBRqMG2qQi7MVknnhDJTsv9/z+WCjTGcjULK79BJ91LzPKYScFaCzaFr4kMQZQqnTESlyMe6Yi2DfZUphCK3CmCus9jOel1fB8xev07osW7Zs2bJly5Z9ivaxYMWfBPDzRPQ/+N//PICf+1p69OsxBc6nQiEHwIeCOyBQaPPypFVA84y0BXdKHpwvdmckHBMijHJ/meLgL9hc8tsLOBEOcDj7FPTsie4cjnj5O96HN01nkRSuyl/AjJIbH+kXk/hn2AlEuVltLjQXDlr3tJbiWD1qD9AJTzOIi83zSKfR2qFDImF8T+DN2g0BU6tYMpwzSxMZTv57ud1AalHYSe7wFe2QrJYQTlqdhsBpgopND5fIfTOfG3sifcGgrrc5BYN0UL2zZGldJ9cLMUc8EaSiueHfbQzdfE4FBswEg1spfbLoYwWNBOPzHCcPB1P30B1Agjp57ViHHSBHgOo+B1AEQ9UEF4EJeAsNEPW5HGUpxz2phKGVQUgnOyqwKOuUSpCgUzhfrwYQmmZCgG0PoocnQT1fqW8C2jRBJ/LqDXKR4Rjm5Nn69hsPsCJSpy5jT8rumi4FlGn32ZGve+hRbsE7nc8DvgE7CN2d2FPIHE7XDaFu+yB0O2yv0g/UeUhgLfRAxNJf7p3QnyMd5sDRG572E0SK637ivDIOH9S5DVSSXAdiVDaBO+7+TPSyrOSpIYDvgRi/C4aKEAQtv8/JIZqAOOugrzlparJM4FukygksxSP2u4wUPRwwfYq+/v/TAAAgAElEQVTnksd351xHZbZUt7juKwOEBLbAAHbTE4ljNAAk7yM87c32rZc1vVYhFzKQ6mBP5bP1tiGMFCw+XDQ1QNttrDUYwEUMQPKUGX4tNyYAsA69lvpc2kzAlT6ELC5btmzZsmXLln1C9lFghar+NBH9OQD/uH/0B1X1//z6uvVxpgzcv4Us5QaYI8t3uBNo1S4SXIBFNtmBiH4dYEENrke+eUTF2l0tIhrRboJX/PD2o1zdBFZQilkqAVycjBBjBFl/pekkbMkMK3vZAJCaH7QRNMsgRkMYznJ5Lw1HMaPoAJQIIqXsYzARyB1tcacznVCkOKnswwkZkzTGk053BTMEoDtcZFHHvNT186izbJRoh1S0pDImqkhg8fWzdGdEzB/7GP3aYt3q936NB4cznWS/FnmkVNm0Fx6j3eEkhi5BOh2PjAfGXKHEtTCkKfRigod8dyeo9CGAtbo+sd+nSgU8fw4Acno5yTIn2hQS7BAu6x77unra7KCLVxtBm++ndM5l/Iy2EnBh5H4PfQhNldtx/ySY4cKwzfVTyMttaqmkYvfsADsSIFFjUkTlCMCcQLpYqUpzsL2NmLfueimCFGXMsfkxPTQBCMn0AWNUTXHAUjcU0OgBsGD7njpGtSIfY7sRxLVwOERIT0Au415P/QOf20cGUV0LY0YRbseO8zNb7JfnDWiKd5eObe+4XE4rdxq6F5ukmCSx6VsYq8q/JwU3K79q80cuMKr5tySw6MKgOsp+ajBGAGMgXbzjcT+7HgkfZM+BKqBrHRh6QR0DoFAaIspqC9erDs9Z9DHeu78Howjwe+NC0NhDjImtMvaoDr2fTUBtnkPdaPp3gqLUh477Y3pm6gArQgjYxEJtrIoCZkQ/ql6RlPtJCG+/94TL84Fly5YtW7Zs2bJP2T6WWQFV/YsA/uLX2Jdft2kD7t+WIfoHe9HjuzsBHCyF8a4ZjAE+CLJr+mURNQ6gA1txQMKBizYEIC+R2m40p3egVAbxF1NyJzIdTYoovAMCoExTsIER9ADoosOhVwxV/+pwhxNTwA4iAz8y0hp6izpKPFrKRhnTOeYLMKFIvrsTEEBMcYgQtH6fG6pOnvcrHbY2/o55BgDcrW+ye5USGs52Ig+KUTo2UhXk4RrAVGZy6ocMoCVTPibgZQwqwKl0fKT47cXxqHupXj8AHE2goFyIXdizpJBk9JgV+tQNoHoyp5icIs8nLFK7DYccVMCtCqgUQCALMNwLGOPOs2xIR7465trckVdkrk0CcBsgwdIpFWhm5okxmlIw1S8hu05ik7HX8gAufa6gYR/HG3OpsJXKXkkAKfp0wCj6mZLkZUE3yT1QwTervGDO5+QA1vXzqjCRHqGtDKj0V0rGQS3rmaKbm33eXgnthiyr3F5tHPkc83tX75jN15m90hHJWEdlGoK1Yt/RSTjf2qY7P2dIA+Sy4X4VHM+n3XNFuDKZDCT2/IvOZ6cMlDhPhvSGfn9AIMtzgJoJ0uY8BuAVa3WpNxicIcMQX4RgjQXYFmBOFd2tz0w7xwGr1wa9iLdBM4hXHP0QU63pWqyAHuQpVjqEfoH3UtUAgPZRUYT8GCKgi51I147u/9RG5aSJiaOj7VhikA5Rz80eYjKVkIWLENPMPAGgB4NeG27nw9osW7Zs2bJly5Z9YvbRYMVvRtMNkDf9/Rz9TmgvPKj5pJNfQRdAzvGiqgRjFsD+4Ih+Nc9aIE/3CEe/lN8M3YyqI8DFqZfdXsKnKKh//eADDEelw0t60nDORGcdDmQQcTjMMS/kL+TlutTJSp6mp41sG03BQTGPNq4KYZieAA1na3ivDkJEjrs8ggCzoxbjq2k33EeKDakzTEqkMfopABCOHD/MZUlrSTp1cUQSRID7xETzXog5knD+aKr8APgacgx6tDelVFTnI1KSYigET1sInRB3zk53vj1NIXPPZYyhXzBdc6x9TK71WeHgk/pc+J1NAXYk6GDXeC+dJZqLMrbRx8DpCHO1lmhPMelAZEBcFN37MFgcMSHzz5zHH5RjH+BMIGY+3xNoFUBR+MMVHBQCdbZUkgYoPZT7Def1QbukplZlyVtynYZGkKtkSczYa7Ih2SXsqVIxXyoYaR3key2m5nDdi277LcdYtHhqJUyTUhj91dAF2X2KxJz57d1g1rQ7oV8MGOwXK1+KTWcn2LUiIqWDmgw9iMYQT0s6bw042EC1mm0Qz+JWUtkKq0E3DL2LTVLjIk8/GMrN9qz4/JUKRcZGUJDye3sTsH7z4VVX4mFB5dnpoJiUNDuJVKmyBfgkSLCNSK3CDgyAmXLefLxRlUQ/9BxkBbwyh0Y1m/qcC02Zeo+rjztAnabgSDWJ24ANENV7m9fANS7opSCjy5YtW7Zs2bJln6B90mDF8MRHdA4Ez0+30ojU58haRIX1olPUOZ25NtgM6u+AwZRIsMIjl3wC7VZext2CVkwdLtg2/KzsupizHtHuSkUOB5thYIEyPA3hcfyYBCvjfKlRcy19EYJmNFpNz6I6aTLGIiTAjhH9VwddgnXQKWnSVZjx0amukX+jmA8nQaP8X1ybaHZAfW6Y1PQ3xNYimQ/wMdGY68drRw68TbLOYIc7dXE+HzQAjod5rsKaUwpEOEvBhh+s+Pd0HeLzlKw4yXLsTzZtFXdKwpmbTMo4Inc/1iWAjgIghIOJg9Ixzn6RZgpFXaMUWy3H1hQj0xnRqa26p1O/Ij6P/pSUqmhIi4M80nDK+IBxH1YWBsb6vXc/xBRE36PdA2C11A0DXGgATMAUOa/TwkXMk8jO0Z3G+BilZKytZ+gwKCuk0cTgqOVMI3UlmBVR1jSYRnFcAqNa9hMB/WnubJRsll0T5OAb4fJ9Ah8+IS+EALdCH0XbSFuSXZGaMgrTU7iUfSMGJqoCuDVP28CcZpRT6uKapEM40pkbijH3TGNduQmEFN0ZGdrtv+nxSr5WXmJXH8AKANALge4uggzbT/m99z9AI41z495WWCoKdOjlRAlm2M9IJVIBRNgFUX3eYi8Fe0IIcjSQp87wJskcyQ6Iz3tJkcFZSmXDU1KiBG1oiUi5z/1agD3z9E0HfbnAimXLli1btmzZp22fNlgBgFw8bYqCRlTzQckewBAe5OF4cYnCqufk04HhkDQDALIKhEd19f6QlhDOGTkQEdoXj84TIvKJdG4zdaQe59HN7FthDMR1QrCNSxqJNpoBkAALqn/KFunPigURjY2KEzc2x24fTsA0n+V6yQjB4zXCcfKUANYU67OAopoehtB7czSxHzw/fYAuY34iwhipPFJYNFOqS9C0HXTJdn2Nk0nAGACND6pfbRwRjZ3WIebctQq0lYmuzneAFUqp5B8AEnUCi6dZyOzI17k3HRF6D/hKQUBgvh4AbaZJoKcjZiFiWCOxOR4abJw6bfLQ5sAT8iBlB9O4zvmYQ+1DjNWo9KWNKao8WA9S9l6d0uh/brUPMJfqfUkKYyqROa+P2iWTDuEjqJgN+o+DIB3J0Bjis1rABB0OdAVz6u8X4GwKutoV+hOS0WPPG9tLnLoMozPKOnRDYuChn+Cf0WkaKNoY+9twYpH3DJ8AvTwIEIfjT8gJ0gndK3uvphbV1KaYsAq2ab0+GQhKao68lPZ9n217z/lSYXRnNejJCZKm5klU8Sj7X8XEVifR4WSlBBAWc2XzNVX4UX8mBrNDAY3KJYfPc6Rg3Bl05ynNRNtgTJES8NJcuwfAVcYkBVMtfsaUpd5GMEQAEEGifM4mBpDEvYlx7Wh3fz5wPKboLFu2bNmyZcuWfWK23maWLVu2bNmyZcuWLVu2bNmyZb+p7NNmVgiZGr+zIwBjRCTrfxsMisqciAivXOxAPuaIvmyK1ueUBIv+a7ZhJTmHwGQqvLs9sgGCuTAOKMfHd5VZQciIc1Loy/HRRlQFoJIaEakZoTMRaSVV5FA3QDr5eG0uKlW/vRBkJ4iKpxlgjsZjsBGmlIDKPN587mmwLoI1oGzfWxTf8upJjD5uc1wYHVV7o5SIrSkYWlJfKmtcd+9esAeqkGk9zgUwp0oX8LXe1Cnz/l0VwvO9kTnzrHOUX3XMW7O/gzFAXvVhzvlHslFiPJnO4gwiKiNUVih4RO1DJHGrtIKHsZK+10bMJXkKQ3xvNPsyJo3Idv3bDyxMiml/f8BmZsaH2Boz+yH1SIJd05EUeJZyfvz3QPMxzRcMwdXKnnhgmWTKWBlD3BsqAHdO5kNG7INFIeQinD4fD2yLKD9qOghI5pI8jzXX0C5hzUoaYyK8j5fu5UW9f0X8EgD6naFCuGPLqD7fHp5pJ9A6RrWO0GgAkn2kJ80pFk0njZMce04mUg+FXPcj2Fp0mB6QdNOTUAa6IlNlhBncFMwCbgJmgLYTl6u1d7/tVupVAWwEamKsqtKGioKaZVYo84dZRI/7s2oeBYPGb6MoZytOvUiGWpSZPWhKgyG1NVBViKfU0EHAGeVbaaR5ZYri3Meo/BPiR/GckGC7bDSXTg0mRqSvMayayoMeyLJly5YtW7Zs2admnzZY4Y4JycygjUoffQPkWUzAsCi491cX37wK+q7QV84XTmUFNfNm+D7KglZxSW0OdJBR1XUbueLZL5gzwF6mkPoQbrTrALQPB2ui3gdAgeGEhQgkgNRZAFlZ1XDe0uJF3PvNpwES4YwDJhhKp82HNKCrp7SEc3YHeleQMORi6SDvvfhH3/3XrDriJo4chPBijNvmUCeHVDefp4cdGRR+MWW9URrTvxN3GLPiSPWjyNcpUn46ob1WRGnMo/IAJmQb7etuTmgq85d0iCxXSuPatQRtCm0e5OklDw7srlAd+hFTWdOwWhq1u/ZKSZsBCNJdZ6WAHLpxzrOtTQGBKvBUHbcqPFmFHRUlN14Hhd4PUDXHFCWtJcCFalqvhbEn4GunMb2x9ys1XyidYA0nbrphkGVtA1iY9B6IgIasnpH7BeNnrVoT5z2uNQnsudAUuo/FkublJl1HgGh28q06iaaDmfoN8VzaZaRjACBPfYCnJBFrlhWFEnhTq/rDs0Oqwjmx1BTyWcedbCLbK7C9o3xecF1j72XqV5SSwLKNcVbtB8Dv7QpQklppZde/yYpCQAIX2ghnV3S134doq6JvCmkMamLARRNcLtbAtncHUmyxybUhCKPyjHbTd6BdLI3EU6um+6reZ5EW9fDsNl2Ysae5VGUhMs2ITEl6bz6KEG0FSQHgIEszCzDN7+33dI82HfpJMvdBe3lWx38VPGqK/u7T/qd92bJly5YtW7YM+BrBCiL6bwH80wB+UVV/v3/2RwH8OwC+44f9x6r6Z/27/wjAvw1zk/49Vf2ff82LMCBPilqJQq72UicXhXze0d6c2C8nuLzUv+4XyL2BL/YGKXtLNXd7aVTITmjhQAfLojo3Ovqgrnw/aQ2wvZxLH85BdZ4yH9/FP7kI1dE5i0hGNJkrIOH9UCJwRLVnpGBqI66bDvSp6RSyO3tStBos4j8qWIj/PbUFDCcxc8Ln66nAop8cIclihRGhzRX5Q4CzD4eHnIUB1Ulo0MAKjPz48KNSf0JTPG+Uaf3BYIVc3MkIlkRHOpemA+AHFwdEm89plFUta0SeX08HWZv0wFqoznd0K8UlyxyCprK1dS+SWsQcTINdIEjUqWqmAMD5RZ+d5LpYjEQKavUE7+zY90UrwELlZEwCJhNCPR/WGTC2QUS1H5ZCONbe1pyz+kMJcwMJGARrCGVOHrVjUo8Efg/71lJy4cUHcC8YM1OJ1cJ6yX3n88tCBqB511qHVRspYIsUsCP0JULANNlTweIIEUXA5jPmOz7bSiWfg9EPnp3umM+4TyPizpoioJ0JJAy+E1rRbYlHI4mDFIoU4KzVdAAf48PyShGrzPsjnln3UjZUhrYOHVGhBYWV5ONkhVKDbAYy924LwuTCm95HVQBi85ZgYjdQijextqqGTEwTl7mUVp4NBirFHlVnEE3aNBXs+4C+zCNIp03nf2WjrQpyMOaKUUL+bAlgkIwx0geA1K8x5zruqXyGAPTaPqh981vVfiTvI8uWLVu2bNmyr9y+zvDLzwH4rwD8yYfP/0tV/c/rB0T09wP4VwH8AwB+B4D/hYh+n6o+uuezNQF9826RzKDmK+E8GXzpePN8x3U/scWbOYCzM/SZcNQXuacOFXfSDjbn5QropdlL9TG/sJJX8YhoMCLSV5xkOxBFtb6GvgC9KKrqJd0Y7eaniVO2I+rmzkMtnRoRaCtzSMPhwjjGWAIORLig4XActDBGLIL3WAWDNiBKhlZGQ4xNgaRVW7pHvNnHOD3KXj+rfkPMacxTVQFl2GsiZzB5lMgMUb1KgcZoK1JiwrGwdsd6JWW8+cfOqAgWRXZV6D0h0RoBnVJDAIt01+8VScsf4zWHHqUfE9sg2BcB4kQ7MfbmP9PZLNR+HzNhfDbtXUIKAL4XSY5qKVlmtEyE+IEu0Jl9BubKBxP4Mztt79nkXAWjQ639w53RBKV8Xr3vJoBYhDgvZU58nxh13sbAAQ4U5g+h3C8FUFDAwS1nRQWYIAUQ+UBKVLCYFANMonI/GFgSgChSKDXTcQ5n9JCOCjdTaoBmJJ0OmkCU6HtUzxnCsmT7KZ6N+xCLJQfXogIJMMCJ2G8GoKLsEWSKWQXLpFRPClAnGWF1DH4en/DUO4JcvTwvom1KZoI2Az76zTZlv8jcF8D2SxUyLSwK8lSS98yfMxrgTKcEYrU+AABjvFSA7SIGdhxsgp/Rl/iex9+D5VTAkKks8AAcqI17upq2qDxSns/x3D7In/0zIEMnrBrKezSt39L2c/i630eWLVu2bNmyZV+5fW1ghar+eSL6uz7y8H8OwH+vqjcAf5WI/jKAfxTA//bDTmpN8PzZfcpZZhaoEpgUTEaVPs6GU+zF8jgaiGBl8jrjvNnfUVpOvSxdROX0ZOAke+EPYOEkjwwiXzyz4gUwqg/04kwHu+C9QYzIqXzgxT7ZD8d4+Y2KGFFaUdyRqmCCVTgh9Mt4WSYhtFf//m6/s7MV2l1HtQaYo5Dtuv6GTFUyrJJHluoDZi0DdkfZncSI1D/OwQTwhMMKd3wwRy5VMYETAQCFzkKmMrQCeEQE251j3Yo74s5/RjCLo2FfU/4ezngFfGpFhDwjrlcsNE7SiaoAgTse5FFaIXYwQbPt1D8gn+Nt9Mty6sc8mb6G7U8AY58GOCAAdU72SESQST2pQsq1xnKMOXQmRYIWBWyyKgY+/6GDERZOpPrv0zagbENZ0a/OfoiIO+B0fN/j/D6LKdfDwS3sCnVPtTXTRJlpP2MvJuODy37YLFofLA/qAxAQ1zAJlkeuqWCkcKnfg8FwaepsBetHsj6iP6eBouCytuGU+pEVBHhMHbNrDJ81dA9EydLgYsy7VvzNgANnwvAdqeuR90HZ76RIoC07L0BLLZ+4l0dfogwrMJhb7Q5s7+y/HvuhjqFZ2lqUUQ3rT2RAUqz9I0gATCCblY22fwNE3kctiGHpPASnwWBMYGiMxL3oH2+XE8yKc9vQ7/5vwjHADntmUz7/B/untC/GJCLy6iq+74nm8QYYa9VHMMDq03QyUr+HKEHXnIsCliz70byPLFu2bNmyZcu+evtxJLb+u0T0bwL4CwD+A1X9FQB/J4D/vRzzC/7Ze0ZEfwjAHwKA/W//xnvfizCYBV0Yb18uJh4nNNGE6dqxXTpUCfRug5JCeSReW3R1OF9oAF0GkqBC0LvpXlCJPGaU019YjX5OKQBJGQrGeCn2F+8UW4TRqnvNdSbXmOh+rH9u9H+7/pQnDXiuuafDXJz1IYrN88/bq/Wr3b2fMmtfqPiLOLnjoKMvQIAPwRqxXilhMFx8Pgy4IRMAfABUxqLBAaH544k1Ecc9sDsGYKA2l1FSMCxLPxrDQypo5OBGpjVEewGgqE40a7mYA/DIQBkdxtTfzF935z9SAao4JVFpUwl0hwmTBjAUUdNgMGw+qbHX2Gnx7pCSpwtk1PekBDTC+BxTREKpGQGClUSM68YgYn7jpAKqUXdwJYAMANjEosEV0KgpRHdKp93GgHTYwIBeJUvd2gnw1AiMv+u0B6Ml96YCGyAOQMqFjLUkAy+xMUUDGOBFAFguKhvd5rs7lgFSTEACZnAxfvZxjSg7a3OOAQglcGXpGKqoQfKStkW5h5K1oqUPSkNElGAij2RgZDA6Yky6mTYEGOidwKd1gp2xQZ6SFsBB7ncHYOI+lGbXT72fUto5NHL680hZkN36x3eCNML+Nq7lp2mMj0BXTXAjv+8M2RXyhCw7DcBTemLveVfFn08+0XK0sdbBzCI/LvQpgAFa0DhuUn/Vsg9jfaTcX5G65kC2RFtVBydAIIlHjj0HtCJYivHvBRvIlI86GoBFGg2tHRN9xrKPs6/sfaR9+9tfc1eXLVu2bNmy33r2owYr/jiAPwZ7ZfxjAP4LAP/Wr6cBVf1ZAD8LANff8zv17XefAABTUMppuu2Xd7RIJShVKEQIPRwsBfjOQHkBtRd1f1kOB6q8vFJT6CYmDtci8lqdL0W8paZeRXXWTirvvsWhCweyVBqwSLI7GqUNPjEiwoiX/DGGfPHfkNF50sGOULYIuOwjusoHwEeJTIpaOopHteV0yr1fD0qj4sqm7pyXl/biuMWcal2oEiGlg+3vGE/8Xp3mdPLLOKVMAAUrxlkynYeT7+iJMpJFYw2UuVdYBDNp2n5uqZbQA8QBRqSfRlta+pfaClSAHq5sEbWoqMsPKCP1HvIYFyu0axBmTwnD6XW6OFh9KA5e7IB6ZQjAgALouE7dtxP4kyF6/zvWNT6LzyPFggu7RRyQqQ5cXE+MrfBYBUV2Z/JE5ZkYFzCApLh2BaOyASAEWME09Ar8/O5ChblPIoUo5qCMPwC2+tyQCwZYFqBDXYqcj/ERBSMLMNFe32ckrrVSph4Ngz3iIIGiPtc05y/TgSo4JjoEH8mAR2VzhCf2RYCJMc+7oPtay0lANxFMcYAgwQmfH47qGC4qHIKjOX9+H6mnv53PCg19BdeRkE6QawMpv5+G4kAQQKOCis93O+HpbWVKFODOg4Wjas9/aVOFnKqjEuCaJgCI2RKUo5Ga4R8dBw/g7oi0HhppOQWosL9j3sv8NwMm6rOiXiNTqkKLyEHYqOrSL+T3MQ9dEcztkA6QaNkPtK/2feR3/6414cuWLVu2bNlXbD8sq/wrN1X9m6raVVUA/AkYtRIA/jqA31UO/Z3+2bJly5YtW7Zs2Vdq631k2bJly5Yt+81vP1JmBRH9lKr+Df/zXwDwf/vv/xOAP0VEPwMTtPp7APz8r9ngSWh/K2r72Y/IK+eDcP0ViyrKBTif/furR9WcNq7PHaCWOf58c8o4nAbNZFHEk4boYZRC3QTYYIJrnUfKQXMqvhDoxqYbUFI/pmoOH2JG1PgMj2hcfiEmapfU8w/Ec9Qj3nQj6EGpwp/fM9CfLPopkbJSKpLw4eJ7XYGTwF1d/X70iztcR8LmUxssfeRxPdTH6VoiYeJrBSDTIEY03NuYKNiY0gcyyu5pAAqGnjoYGxkxp4xWUhNwRGEFRpev7Iw6/5kC4n1oVlmhU8yR96WmSjysX2gZ8EHJspi1P+w8Nc6+7ZVS6iJFHcn3QW+W9lBSiqgTCuFj0Grge7X2p5OtQ1Drxe8F9vmg8XnOCekQ9nyo6pIshA7rEw1GymBTjDlKEccafVbbZ9wZsmmWBR4lMWmk6oTmwyO7wlkV5Poq2pFlP+Meogg5A5OQYq2Ykvdm7PVSsSWKpSoZo0DoYR4AS/2IPR+6BeWAqB5DmT41hpi6F64boU2TyRQPCj51CIHW+8evTbEfJU4ZI1WlZAKJ+vOMZr0TNEXfAdop00Ty9vMUK3IGTJxHR0kdImeUkLMrds0UJbBalQ4CZBfc7xfs35/TqkKUsx1Igc3UzIjUiovp9ASThA+kJk6/6tDo8Wd8lPuNPsiO1IgBEbTWXI69GvvhsVxzsB68SpCW9CUAxpqIZ63fh6RjnYzRoiPNUMn+3ajbeRIaVtAmaPvohAhBT8Z5acbSuxP4HEwhbZ62dFmB/h9mX/n7yLJly5YtW7bsK7evs3TpfwfgDwD47UT0CwD+EwB/gIj+Idir3F8D8IcBQFX/HyL60wD+EkzC7Y98jPI2CdBuNPuzQ3oiX/T7VdE/89SA5w7aBq388vkd56VBbpHTvIHUK4CcrrXgL8UpIHcS5GoJ5nTp5vzuJyRowUFbV0C4AdSg3cqAAoA8gBKRRoFHsGJyZqrHWZz6KPEp9TwDJ7LsqsK0NYqjrJulRFhuuVHI+QDk6k7RHWg3m98Q3KNaixEBMCAdM1WnnfvapGilzwUICQpFm6PCCQEP7dfSlLEGj5RrGxxBT4DDQY89UKneTunXkyxfP+ZOAaqq/tFXwB1vd7obGY2dFOr7ShgOROlUSrOuX1QQ4WNgHiFoGABVpNBAHFRyhzvnwB2zKRe9OtcC6EsrlSDGFMqVx/lkGiU2Zj/fndsQlHwEzGw6ip7KNvtSI7++OObuSVOZh6nfBEsfCoHUyPO/+Z51/ZIqpKgN5nzVfU4PP/33cGoDDDARwwfx00j3wIyHJQhCAKkWYVg/Nmj8vrZ52dMayqn3lI6Y59CIiQMktVLKvIjtDas8ogDToPITJiAp1mCa4yp2kYAJJSpi5W+9Cshh6woyXRsAWYlmlPxVYKPxGNrEQBwH2OBOuBY9H7A9H/Mkv3cBACdD1Co38SY4v9lBvdkzBmMvRxUWPsY4ctwCbG8p9Rn4ILT7GHq70qh4co51G+llBAodjgDXHu6rBERlrsgUpgQDwlxrJcVqAWRFo9jzAQZG+5uCmoEPbesgAs6j2fIVkc5p3Ay0rYMdjBAh8JPivu+QgyE3tuokVbw4hHqXAfjRvI8sW7Zs2bJly756+zqrgfxrH/j4v/khx/80gJ/+dV2jAcc3BFnmEjAGwknQi4ZV1AYAACAASURBVOD4bZrR7v2NhZIv1xPMgvt9w3ls2LaObes4L+bh3gHIGwK/NPAdRS+gRPiEAGHoRa383VMHlWoPIKR+AF+7lcerAoGpl+EvtSEMGee7unxGnuPcZCFQRviyAocO5zCrBUSk2V/yawQ55ivFHz2yLru/EG9IfQm+exT50UGTIbIHWPAynTUBQB6F9QjiY/CQwgkNxwo09O36cDQoru1OfOa4d0yVMfiwOZDqPLD1Y0T8ixMc7JNHR6Q6w/63bgq5eG58+BS7TeoQ6YtGykCdVRNChXRSKa3qjmE2WEQOHxzpqEYxOadlHSpAMWmZvB2OkkaUmjH1gcRF/gDINfbmaI8VmbsfgFAygh5FUGPvPbQR0glEtj6PYrB8ILUs+LA+SmFePOJYVUOm3neyAQy1zRjnAyYgW9kYtW9x7yk+XLEHZd4DRNFwTP1718TQAPa8Qgu7I84nspqGbhhsmarX4HPGDnxM4pUOIrADARNgVOagavOE4z3pJ+jDc0BNiwZwtlaU8Y01eqRt1XkXmp6PtlAx4TAw8AQo0FkZ86c7ARfB+RkPwNKfV3oS+D6etxVwoA5sb2EaKeT75hjHiIPMudcB6D6eCUoYZaG97QQrY3xxPyt9YKIBImfONQV2Ae8yVaMiVhfRJKgSpBf2VozD9XVak6xgpQ/XEX/uqzD62SAspQ9lzpsa8FfAPWl9vi9/i9uP4n1k2bJly5YtW/bV24+jGshXZu164pu/+7sAgKMPSsX9tuH5+Y6f+OJLMBRfHhdcmr25XtuJjQXfuz3hl9++gWq8MNqLXvuWvRDeXnacr5s5m0qjFB3MqeLDI6Bns2AvYwg7nuzsCneCNve2OPptfZHTPGja7AU33i17Y+i9jYocESGNd9VgFDS4+KcfF+/cXdNJpwBGaHZiJ4e0/vTvZQPwZB80Ho5EWIIgFaCpUW/AABEZYMPEWoA5vlnFxB0RSseERhUSINkjtRoHdXNE8u9gCxyzYxBjm9IKgIya5twAk4pLrd6hzsIQhafkwMCqXTFq1j7MJytAjL4rZLc9w4fOIpoB1MTc6ZjbbNKP4V6OCwDNUzeyPCkKAAQXYnUxxqwWUO96QqY7kEfNqY4h5h7DF01n1BvIiicFBHovGu3nCTsgUtrQsjfCYZ8Al5hLLXMW8+ftUoeBPpuaeGUFJwp4V8edfQtBW0/FyTF8AIAaJXLndhQKYmdMwPapCGXKEXsJWT5pAgQn0GAzJ5a7AVqRipX9dAd9GkJcn8q6O2hDANDHfkMBRNIXFwxAxQVhdTMRRxGdWQO9onhw0BZjjuL+JiQjieT96jnaHJS8CvSp49y9Pa9cwyfAO9De0QTEqV9qezuuZ8+EUq44rrGN+6gLBnDVfU59LmwNxrmZCRYsuvbw3CRAiZxJpi7CSWMhGdg2qzR1nmzYThuPbnFg5NSG82jgzcAKjpLZsH+PojFV4DjJWHvlxj0UU6Uj26tjrfjSRwWUZcuWLVu2bNmyT9Q+abDi8/2Of+x3/DW89B2HODNCGn7l9gbfvr7D3/3Zd9DB+MXbF3nORoLndsfL8wX/3/4t/OKXn0OVkpjxjTevaCy4P214d9txv+0W4eqUL5r62kC34gB4+kcqx+tQskdTAwxKFQpuRv8FWVTeXlQ1X3jbPuQWoAZ+6I3fz38PZ4oUSjIIAd3ylwMMAWBUbcJIoUjHYDh0yuN7+Ltxf9L3ophARH/jgsVhxvhMgEkro6boKMEItkogd5L4jtkJvA7HLwEJLaCJAnRz7Yyi3zFpARRHuEaxcxp1OHnqDJ2M9JL/jzXBKjpL+xsKS6b0O6zZuoQmhXSytJFS6pEKCGPAiYJ4aEoAxUcJpszD+JKBEg5YmeeMLrvTpc3ZMzx/bnPnEXWh4ffUcRUntwJc6SBnSo/v/VLqNE7J1CBFlmokuAMfoBy8T5GDH4AKDz2Caa7J9iii1CzDUjVy45lj3eu55fzsUx2g+Bhq+cpHK/0NBz3vRwbAgu5pVd2fE3wMVonumnshU5rgegwdABnDABj7JPVO3FEPAIoqaFCmRskAEPsw9ljsBQUe2CZ8wlLWulftaMURD9Axx+v9rmwU349ENGuWAIOpoQAdnlYXrDIYQKCbOf/dU5LorIMB9CC0VySIoQ3ATvM9IDBGh/rzqgBVpEB7ieMMRJxKMueegIM+BprWsqzagN4BEoYcBN05z5NdkiHRj7Egyaw4eGLZdQL6JqBNwa5LIVu3lDoxsEM72Xmlog65Xo5e/J64DHYHAOzX0ypeLVu2bNmyZcuWfcL2SYMVG3X85OV7uMkYxpf9is+2O57bgW9uLzi04dwZN3/L37jj29s7YH+Lb2wvYPo78L3bE97d7fvPLnfs3PH0/BZvny/48n7B2Ru6ULI3brcN5+sOvfPkkI03ViBFHRVOMx8RYenNtTDshVPEHPaI8BEp2iYgj7j1s+Gkza4XFu1muUgMEKNZhFmjT2TpKMSAhDN4eJ5zpJyYzmiCF+mU7kBnF+I8kPnlcJq2bMNZDp0MwD5jIKnnU1QeBTdhxxO6ierl8AipFxCCjO/pKbgzEv0IJ3iK5OpwPOKcaQ6LfYDxPVJsUEpDxokHWyrIfPDcwGYCeYCtq3bONQCbTgSEgG5R6C4GJEW0m2DgwkgHGQ5njiHTgzCYFgkQkTtdCt18vXad0h1002QPWfqBjlKnSd1HMiAU4/oZdS7z9qHMgYjAkwIawI97V+kgun5KAinRpu9xjf0eX1bH2QUPSclAiocUhtw7AS6VMeTnMa+9fJmMG1/nIzasOY2oAGI8hij+riiWzXvvDxPFDnQGwBPnKtCvhPZqxwfDol/GY8WELr2pE5O+SWjUVJvFWgPoGP2z+8n2V6TkGNA6AxqhvSGh/fAgdpoMlpjPnO9yrZOAGw9tjDD2PbApTqYEVaNd3RSHDIAh7vvUrLg54OIPGPGUmxagzwlsr2raKGL3BX/g2WIHOzBxGcLCAZDwhSCvBrLpPs6XjXFGGeYoC1zBHL/PK/tLN2Mn9c0a6Xu5AZICM/59CJZZpOzQU8f2dEBk/PuwbYJtq1SiZcuWLVu2bNmyT88+abDiJjt+4fXbYBJc/W3+JpvlrAP4xfsXBlZoS+bFoYyGJ1z5xE9cvo/b5zt++fIG33n9HADwxf6Kz/cbvrW/4KVf8P3zClGCKOH0Nl77hi/vV3z/9Yr7saGfjN7ZHE/AKoMU6nFYOIByMqpnTGwRYfKXWm7m2DJrvnQSC87NlovInUkav2svQAZ7W2yOYaXbZ5S1KRRiuhRqTpcCwKs3cR+OZL4U8xD2lJ3SKcgX+QNo9f04hlgrH1TNC4IBKjwAiQQiMBwA8shoHSKApMpn5DYj+xiN+CVDJ2ASWfRjghESTlhtL/tE7nXtyMipKiDKD+0VJ0TU88nZ1sTF9Wh779CsTAIYoDQEXzGiz4qhxTAJrvrP7o6dDF0P3n2NdktZ0ahiE+cI+V6wiaKXBkURcAWNKPykRBkdH/NF3l5S92sXU6PBWCN00Ax4sJpjGddCOd8dTyKafck+9mgCFmrOoELnCQ4QIIQhqynN96s7lFZ9ZQBLlU1iKTM6pUGlxfrUNhNYdDYD2XWSXdIoQZAAZfRZIZ+NPgEAdh33kK8dAGN3eUpHCLrSg6+qrJCtrCE/AE9+DHUHK06AawWgnHc/zk99LwtKfwAoWEAHEgB3gjSkwGeCUvHs8v3KNJhrJMD9m+Met2PK1L+Q9b0wsfhe0mkcfKACkExgpt/vwQiztKtxrOyAOvNJNgMa+mU8q3QD5N4guzpbiAagnI0g13pi50j8+0ED5Anwrin0OlDY3gnYFHzp2C4dl8uJw9M+iJDMjGXLli1btmzZsk/Z1tvMsmXLli1btmzZsmXLli1btuw3lX3SzIrXc8Nf+pWfxJv9wBe7UQKYFBsLbrLhl+SzZESwhxlPbdio48124KeevotvbC94bpY2EvbF/orP2w2ftxu+tb8Dk6JB0Au289J3/NLtc/zy7Q1ufUMXxpc3q2l5Pzfc7w3nfRsRNKGhaXESsn5pc+X4CIcBEIiJsnWGsDEsrtcTzaOBzAIR9ugZQYQtWFeoB8HygOtWSIlk2wH+0yPOkRYQ0bikKWc02SLNUbZTN8utrwJ7JuRY0ge8fSlidxnh1BJhdR0GLayGyJ9HidLSNv+dgpOlvTxvdAUsHsyOdJLHSLGMdvrFo6oYFPJYQzkIckEKJCpbFLdWVKmUbyUatHY2ynnN0Tf2i4Kb/QcouImtq0dRg43D7OwH9X0UY3M9DKsaQNCTgWNUH9CdRmrFLk7rL2FvxWBMRAlHGoKFGeUmeCnPuapGlhqNLIYyd/USqKyXTjnHdg0kc0Ur3T0sSrmSlxKNbV7TDaoIojy0IR7dZjK2i3fqvev4vE5lY3MMPNoNttJukf0xGQ9zKjS1NVVO4SJ8med4M0rGBLp20HX0F8AQoEUwpbzrJ0EPRm+mbaM306OJKjLRP4VO90qVoYiUHnX9jiwpGl0I9oEzfPj08RS9iGhzquRRmBd8FtaAAo2QJaaleQndRu+lKuWY27g3SMir81SWBE+pIyAF32mMJdYulGTLvR9jy4o08P2tMB0WGCsj9EL4gDHXBIVdRuAbIBfC+UbzXhoVTcacpWZIfJaaOpQpPRqpOg3gJxt3a5brU/7JwHm2rJ4DAMf9gv7uk/7nfdmyZcuWLVu27NMGK7owvvOrn+Ny6Xj3xjQnPt/veN7sTfNXb884hfHuvuOy9TxHFPjiescv397gp56/h8+2W4IdL32HKOF7p6WKvGl37NTxJpTuADAJRBnf3t/hb10+t9QSUvzy/Q0A4Hv3J3z39oTvvnvGeXrZuZLfrpsCkVMeL6pFYFPFqfcA7vcNrUmWWAWAjQWiJqhpL6kKF7i3888hxhapAST8/st/vDCHE6WYgIfqxNM5nG8Amf5RafC6A+GZUC9lRN154YOyj1mVoIhC9itm+nZN8aBsOn8qOy2bAihBajbE9zGtfA6wAtVRdseDSqWDWm2EHJAhVSgFWEE5RyHMGakyshdQKJzCSKVg158IsKSp56orsBmQQJ4OxKGRwIAK2+c+dCpgS9usNK4Ke186hBgIMdhavSKd+ZIKoOTA1gAchsPqfXSAIlIGlHRQ11Gc1HC4Y/4r1V5pzGd16jEcwkyPeMDJCDANBlBS9rUeV0UjN6feVwyhpGOwPDQeF5iO96ooFRgoZTpT2FIL+FAFTwIICZAlv6YsCwqM9rI/Xq400gSUW2qiVMeUIs2rfJagwa7Qi6DvDDmHEGPtooGGlIBBrmQAczAnWQuwmNMUZYgduMnqMZjXnxxozDKt3k7qSdTpjzm6GAAaZZTNydd5rWJ/NdfkefherwJ9wqQlIiejF+BFNv+FkUDb6IzPiT8D+O7VSRy8jQoufAO2Vx/TgQFE+960UrUE2WddH/UKTnmtMvdT9ZqSEmeCnAW8aSam2TtDDjaQuoBdAICTcfmlVQ1k2bJly5YtW/Zp2ycNVgDAed8gpWwp3lhFEFXCd1+ecPSGl7eX/FpOBjXFy5s7VAm/8vkzvnG54ZvXF2tPGKc23M4NX1xe8RNPX+LCJ377/mW2ceUDoowrnfjW/g4A8MQHvrXZ79/ZvsBTO9BI8XpueD22KfKlSujdwtUZHaVRulSV0AJcOZvpYShSMC0cV/LfmY1tgc1a6FWz4dEZqi/2NMpVPubx666mqC+UL+9UHUQuDm1xmCJKCtHUgQhnRaCWmx+HHwOs0B3o1+F4UAf2t5SOsJYX/Ijam9OB1JoY5U3HOKJ6SApP8vw94A6Eh5hDYyM+12bt8kHgruAXQG9+/f+fvTfbtSVJrsSWuXsMe+8z3zHnLlaxqlkUiiIECeoX6UEQNH2B3vQX+i29CHpsgA21CFECCbIL7CKLlZVZzJt55zPtISLc3fRg5kPsmyW2AIrs0wwDEvecs2Pw8PDYGbZs2VoNFU0PI9VUgGfzUrut5LHnZJ50PwY7A24YU0cwlaYEGdUnqTI8MiU5IjLCslHNCzLiPsO1kEDaNygrI1ARNVS9EtTJVmWjyI1OXAIEiEF1llX9yKlirCBYEn9MCWcSbZS/lXWR3FCsPzpeBRbk/VjZF4RKXJLyFHPSy5hPWcW6KclpEfDU9ZHnS9e6sokAiItLzeIhFkBwxioCsvCmJp+ZFaTgHbOSqipgsLpipImnQKCdBZuyEenzy1b1EOYPs14Li6hr4wvTRhN3UlZDmiPjdZz5+CjijcoEmVmP1msJyPvW9zGBSPmZOwIasmDr99xbCkLuYQvRTQn4wN4VLOBr/s6KNAPOuI2gJgozSO8HuwB21bgTAyoJ3FZBbRqMMJbCKMBfzTSjSDAHWYTGA/aAGQCaMYUR8L3MeXJxiU35vuAMZh2hNxWAxFNhZfBefph6K3M8iraKUVel2naXGOjeHx13iSWWWGKJJZZY4oHFgwYrmAH2BsEDO98BAKbJomkCjIk47FsEbxDvGxh90WsGQugY+1PJSF/uG7xtAlYrYU4wpGI1Dg26fsKbkxP0bsJVdwqn3PUTN2IfGqzsBEcBhhixeok3FLF2Ex6v7nEIDXZTiymavE1jIqZoEKLB5G3+u9G38sCETsGK+30nNn7RICqFPQQDYxiGUguA7q/V+OhIaOvEgCWwKdvk3IFRXm5T8hRLcsYdg0g+yElXTZmuEj5CSUpyNd0gnzOBFmyQBQVBIv5I+sIeOhbatEaqwOckTxMgdpzBimxdWL2k533Tv1rNNXWyETDbLrMyUlG+Pr4msnaQZNuOyCwM8gBsSe7kWDRrgcjn1LHY5BCgcxQbYSmwFWeByEC0Zn5vgBmYAMOZORFdBSbo/Ne6krCaVEfkTJI8ifsEUBgAahUpbSooVVzHQKrOk25fgSXS3lKdT51jmHRfJLCChf2h7IlU4QcEfDEsQoj1XKK6D7n1pgIF8iOXpipIkhsdq31mdQvSOqUqEcx2lDz7IFtsUtmGPKvgKmm7S0KT0jxXP3MB+eoWjMweCSjCiXVQeY5IAbK8ptN1MgBTrvE4uFXB3VZbBRoV0QXkOwFyX9gReKK5LXB6zrk8L5mNlIZYJd4y6ZiJ5qaxz8ZUgULBori+KPiSHITKDuWY5rhVBvIcxtTaQwIkFTCT8vVmhs0xHpDuw2zQaXdpySIT5TvXMDhEcHLo8PJdyoYwwcBtaTZHxYqYQYfEzODMxooNIXTCJGNHc2vWej1X42YFWNNcRGsyIMSuAETpOTT6HfPBvC6xxBJLLLHEEks8sHjQYAUigfZWXz4lqxh3DqNlUBckMfLSw5x6ltsbedH0e8J0wuCJMBmH6b6tjgvQaLC1LXabDq7zeNGfobHyRrpqPPaTw6adsG5GOBPR2wmtghk738JQhDMRJ2ZAbyd4ttmlpHeTMDiixda3H4AVALByEwwYb9wGu7HBMDmY5BZCDGuiVNIVsIBW1QHAuoiYKsJ1AkQlyUxVeI6ULTW5fok3QjsODeVkmo4q8IAkx1wBNbOkQNsFcsU66TZAfuakQaEghCRYelwAkzGgWNgWZqJ5xd6y6imo9abS8rPDwmSE9u3nOhbHYEV08zwhJp0A0upyIGFWjIDbUXZKMdrKQ6q5YTzAUzl+Ns9Ih2PMEjnp55fEIoJhQIAxcl05cdaf6wo1oerJl8QlU/gtZu0XaGKxUNQkmjwVdgSnhFTmNpIONN+n+Vzk86drYGjrBarkqmyXj8EEbnnm6JKXe92rnzRFqKwVdkBoOes+0HGiqfPKyYI3CTGktipl3kSCPCMJ7Kh0BGZsG0K+B5lZYQWkIbXszcltBu/ow+uv5iDfU22HEVZVVUI/em7YkDpuUJmjtA5YwRKe7SJDGCDVeK/PSr0W0oZOwSMr2iZ5LTGDJoIhgh3SdRQgIHcOKZiTW8EU0MnAQv6OKXMPAFGf3dKyJffCJDZJAnfq56ZqTckxCQsngSbGF5AVk7S/JHbUrNUmzXM6ZqVzkr4zohfQkBLrIp07t/go8OMYfi00EdGuSWPR//R71Y4Mty9fAtES/EqAiqTnkdrVvo+plteZhx6nfMYGmDaibyL/+9N5jML2CD2WWGKJJZZYYoklHnQ8eLDCbo/6dfUlLq4M6HSSF/ZVyBIOdm/RvxXQYvcxMJ1ppXMsL6GkOg/mYMAHg6l3mJpOLB8BfZE1uG4CbBPQNAGtC3C2tGk0NmDdTNi4Ea310kqgL8SOImBEeyKFoVg+NxEbO8IQw1DEtukwhHKrkv3owbsMVhzQ5L9bG+G18m6TDkJK3LLtZvpXfuaorIyKDRGbCA5iixrW0Ap9neSSsjJY7R4xe7nO1e+6gl0XPR2kkl+JHs7aADrR5cgsjqQHUQMmDEnoNAGU+dGPK/p6TgysijSiJERsOWtPgIDYxdnxYYHQSHU0OsAdFKwYhOqd70tQSjjKeRGrROQYrLCAsUDQeYsA7EEqw7OqueHCREjXHMp9SKciX5LD3PmxoVxJrnVC6iQsUfaJVVujKegIKfjDDkXbhLhUr3MCfQQgVGNLtAB2LN84uR2q+tyYzGLJbRyaaLMF4jrk82dQre7xV8Ayia7KcQoEFR3PALIaUKCq3aO0DjCSrgGgvxsGW1P0BVJLTT4QSiuMWpRSdY0MSdAF9GPpeThmR7BMOhutxicv4BqkSL9X9znNW9KG4dQa0ySNjAQq6QQagFwUFka6l3rvuSW5TiCvvXQOMJQZUSXXChpwYNTMpfx5mkMqmi0UITarwHzuUbalY6AirfUMaBRwhHL7jIAXMTGujoCO3H6Uvv8sZz2RfJ8avQ4F99hw+Q4LyKAZtwyPiNAS7KGc344E28i9MqOM22rrmJ0YIEJM4FX13ZSilj9Jv8tx52s0tFWLji3HYALcHjg8whJLLLHEEkssscSDjocNVpAkIXT8QqtVzn4zomukpO7V5eLenaB/59C/jfBrg9gYhHUsFcg+golh2oiwc6Ch0H+RtDG8vmSTxWQYUxsx9CEDAmQirGXcNR6bbsS6mY7aRKRtJAmBOhN/q4dsawN6u837pYhMuJ16DK3cwvuxRYjlKD4Y+GjgTIQ19X76r7alsP6cQYuc8FcuE0ywNiJGQvAyB8wk4m4paRu1dJ0SiqB95XZ+f7KYHmm7gIO8aAeAJlPKt1WyCmhioolxYjZIclao5MWBZF6NTgmK9NRTVe3W402U+8gFYCjzmDQOEntARDT1QxUaNEAGAUQUVNdBpaGRWw4qYC0xCMwkWa5x1ZgrGZZcZVdhzMzYAJQpUc6dE2a9xmk0oiOScKIECNX3JIn5Ra1yk6lwgKj6FiW50huS75NsyKp9MQdR8s9pXxelhaX+nIBoQqbaMx2dCwBVz5ecTvUYADkvMWIgYCRJgmcMFl2XiXmT2geOgIbZv+nndE4LASlNFJeGQHre+X7ZeUXXA6dkkkm0RqDrIzEs4nwesxaD6iokgc2kMcI6x6Tnzm1VyRljUh0cBQ85Uh5DxgTZqLgr5mEkQ2dLIvoaAW5MBjtm7RPp2azn0FfPvQITx/OZ5p5BuYWEaxZEHWkOM1hCs0SeAud2m3ofeIBccbTJz0R1zPwzl20ABQoSSJI0QlQYNZ+iEkXmXtgrsTe6PyEcFLyIAlyM54Rmq9O2L+cBhGlBXL4vZE4LoSmNVwBP/e6MjOgo698kEeEMXBjADjxv11liiSWWWGKJJZZ4gPHbcuQlllhiiSWWWGKJJZZYYoklllhiiX+UeNDMCnIMPBmEIeAr3MUT0ERs+hFPNveYosV+kjLT4azFcOGwfgVsvo0w3mD33CCcaAtHG9D1Ez6/eo+7scPNboUYCd6b7DqS/eu96jlMRmi9uRxm4AEM2xb7tkPbTcJEqNxAjGGcrAa0zqMxEbZqCUmsi8TAuGj3WNkJGydc4iE6+Gjz5531GHqHUXU7fLTwbDAEBwNG1LKhjyYzJ5LgJzNhCgY+2Pw7gFnbCBGjdR7WMLyKDgyTQ2gM/OQQg8x/chcBKuHIJMCXCtRTvnsiuKdV90L5LqVY0cnQPyWhQWVRpDBazbVDOcesSo1Suf1eF5Dq76WXvmLB6GehEfZFbCrNBGVtJMeCpPmX21GScwWQGRWzCmgAjGe4vbAiYkO5kpzGlsUqTWKTJIp7GbuZlNFRa3EkZv9E8Ovvv+40R2l7JmHvyPWpgCdDNA7S2g4VrSNPUqJO1NdJs1aL2QmNTlbNzrAA26A6AiISmp0aqDB9MnuJOLdIcDTawsCIkYRhUlk/ilWnVqYJABXB2Hz+ozVDQec8ryGWinqi7B9V5IutZEXvD6g0J1iGY6DMiIqtAJT5yAwAXQtpDirRz7xNrI6f2TqM2Oq6GNVaVNlMJp3HqJtIYhKlfWNhDKAVrZNoouie5PuXxprOb2bsirrtix3P2hOO/5U5rbxlMoOiWmI1jShtln5V9sgHOjARMFBGSWJw2fm+bDnrfsxarmr2SH3O1I2T5ixt4hi2C/L9rycIziI2BhQJITBizxhUMylp3phJvluiLVofSVdHnmdd5wEftHZRJIROv4+SI04EbP09hvm8LLHEEkssscQSSzzEeNBghTERFxdbTMFiHOVSmKVVIQwWb16d4bpfw9+05YV9JPg1w3eEs68P2HxrcHPfYv9Y3vCGXY/9ucO71YDTbsDl1TucNAN8NBj1zfD6sMJ2bLDddxgPTXnZTi/CQV/gPcEfLHyr2WxNW28iQiA4F+HU+SMBBd5bWE3EWucxbSxWbsKkb9U73+IQGvR2yvoWV80WQRNMr6BFBCEwYYwOgQm7SsxzjA6GGGOwmKLNFquTAjK2ciapdS5SsuhszGMMZOE1ueG6h3pCcY6oaNU6OGlfUEDjWPKAALBXh4eUr6ScuBJkTEr8FDFr+8gHQbXfUaSXeaOJLakbRRLLywklAc4QYgOEngoIomKBWYDQzhOf3BpWXQAAIABJREFUdG1iLViSsDoRMRPg9hEUqIAe1XGS2j+rvkF0ADVVklLNSwFbyhjsIYEdyOszumrM9byYAjKkHn6KBrHnIvQZ5W85Oa+T6EgFyDlq88h/U/vMGTClPHZyACWNBkoH1zF7WSScW0qqEzABFYDFAKgST8xaD7HM10wPwRwtkQRGVO0D0rLDs3muP6f0e+4Jkj/mz2tdCKtuJamFCpDWkJQEc1obBJr0We5imY4oAEStvQAAkRVUUk2F3GEylO++NO80URlLNUbStozIsUyKL2OXBaxgE+uc1m0Vsayt1NOSRUqz3gZ9MKdyAVRakjLwyOJugdmuOt7qHtT3KV130PUckdtl8n1OlszVqfR0WZcin494dkxQtTaMiHKWA0BabFqxD6ZIwMkEnMkBxskCBwNzMDPHlfpfMxGs2vym77ZYu83oOqu1RIxHAXEJmNa0uIEsscQSSyyxxBIPPh40WBEmi9u7NcJos6AcvAjgdW8sVt8R3L7Bxa8OOSkYHjV4+1OH2DDc+z3o62/x9Ksr+KdnAID98x77K4e7j5/g9cceV59c4wcXEy7bPS6anRzkFHg5nOJ2XOHdYY37ocX9tkdQMCJXKKE2kfrmS5VaPsPAHxp4w9mVIwsPjimTBA5rj2FysDZi023kGiaHYXJYdxM65+E3BpftHitVezQU0RmPtf6+C+J08hKniJpJ9OzRWY8pWozR4hAa3NkOB19AHyLGYWwwBoNhahAjzUQ8LTHa1us1RARvZ9VwTjoWgLzsB0JoJfkyE8R5YJRtk9tDilxYrZMGhmyTKrdRElxEIKw0GSKegxW5klr+NiNgcKpkysu9qdgXZqB5ghgSAyKdX8YTGyA5n8QG2WazHkMGVrisAzOV6xNhyLJukmtL1tS00qeeqq+50ksCaHgdEzEQK7cKM6iWRQIrdDzf18+eEk1uCCYBNUHZQ4ZzhZemUu3OYE2qLFdZfwIuZgyLipUyU59M2ylzh2wsApyR5noYKerjerlXlO7J7MJkXMmNJSeBISXNJZnOwNcRGJEYGTLfnKvaxyKSMZAkq6pxQHV2rWNjwyBDYHBGiwqTANni1ExlVxPMBwnqjB1iADqI01ECNtiy7JC+TgbM1jMAYTYkMGEiZaAA5E0BZioGigiPIouMfsBWSsyYKAwCTs+snGzmjpMZKJUAL6lGjAmFmZOSbk7PfgKBSMG7ynJ5fq8LmGJ1jcUkqFmBGjPzFo8ZTibXU+4hBYAnIE4kDBUvjlKzhkp9aNP52RvYlVyEbUZ45xB7g5i+5xNglZExuRfkSQVDE7CjmzuW7ytdq/ZAyuLR40RguMIH92aJJZZYYokllljiocWDBivsjrD+k7W81KcXz0leptevI05+eQN68Rrhzdu8z8mzpxjOfggAGB9v4P78Bri+Af1SP1+vcfbkEfxHl7j9wRo3P3yMP310hXjqcfn0DgDw8dktWuNx1u5x0gx4167xkhj7QUCBGAkxGPjJgg82J2aM6uWU1BrPK3OhsskjryCHYwSy2I09iICtEy86DgQeLPZdAFnG7aHDWT/grBMritYEtNbjst3BEmd2xav96cxxZKxaSdZuxNqNGJVZsfMtAkvbyOgtgl5Piq6fQCaisQEEoHEBk4+zVhduAT8WxxKeTLau5L3RxFd/N5i3JyRbUhWWTO0KKRk6Dm65ZPYadeWaLUtCXe+vlWEEghlMrlYnIMDtCotChDPn48xASrK51Cpx6KqBKXhCzMUdIpZjxobgV4Rmyxm8mCUmFsVNJAk3hjJGNkBw1T5KG69BHnvALPlOldh8DVSOF5MVZW4pkZYFEfkUYCRXlwFwOBozlfnISdho8v2kSMqu4NLaku4Lis0t2wqcUMBkVnFHtV5MlbDm8xdAJU1EFmfUVoHchaLHTokhVcl5HTlBjqSMF56JujKEih8rodSZkKmn7ISRk1vNlKOrWp4qgC0fP1bDqfC/fJyUpAcFw/I8cGbhmElFGRVAkLn48FmiCNiBcjtSBk1IBCHrqeEEiuD42ZSEmrlytkkUqZlCLOZsiQq0AVNmocxPKkBBGh9n9dhqvgNEFFWft8xaSO1kicFBmLFyErhRnzNZiwLCukrrPTrAuDJPsoGwMGIr10kRwM7mU5g2yH1ysSyNNI7MVmIwA3FSsFeBuDyIhhF3FjTqc6EHSACkgBUB7ftjBdUlllhiiSWWWGKJhxUPGqxorkd8+r98A8RYql8hAobAuwPi9Q3iNM72oVWP1VuPw4XFm591+Oj974P/9Of587jbIX61A33zLS6/vMLln52DW4fpaoX3P74CAPz1Z1cYn3isH+/w5HQLHw1aFwDIuayR5NwHg13XCmuCWHrrAYTJlKSESStzhWrMnWQT3GqGEiUR4uRGkv62d0Ag3B0sdqser+yJnN8yjIlYdxOs6mHshhb7oYHRF2JjeKZFcdnv8fHmBueNyNXfTT3ufSftL95iChahomOTghzMJDoHJPaWdcuIMREHyDVG1opzK1lDdBbcygs3UqWzooSLzaUADInBwKmFoNY6IIAMw2pfPVcJLQd1K4FsY5oIY+qGeJ1/JoSp6Hkkdsg0muw2YAZS1475GjQjlXuZxlNXNKnahwo7AZA2nemM4dfSXjKzPa1ZJim34wKaZGArnTOm/nfMrBBLoojsHmDGMsbUZpJYBylZi01V5UVKfoEYOCdwgC5bA5lnTYKFaVLGYA+E2Bb3CZpI6PnpFJazXWQeTF2pTra4QAFc6luYWg0MI/ZRq9J1wimgQmZqMECOKkYFitZK2eUDS8l0f4VRcbQQEkkiigYB1wlv2j0vveTkUF1nZREKku+D0FRrqWIjzFqmKlCqBrGyA02VSIdOHCKI9flSdkS+aMcIhuaaEXWkTpRqbSWWifxegQZEgK4zqthWaQPO1J8a+GABywiICkDERpgEgKzt7M5Tt/nUoNBs3TOCKfc2zUvq6sjaLrXmReWwkfVXUIEVXuffy7MfGwVMss1uudYEDJI3xUq2tQU4re5pDRpRWhckrTjsTGmPSsfuCCADjgwPI89Xdav4JCBujxbgEv/o8V/8i5//3RtV8Uf/x+///zSSJZZYYokllngY8aDBCp4m+C+/+q2fk3Nwn36C6bPH4FZe3F7/oAcbwPeE29+bMFye4/Kf/+c4/6Xag97sQHdbxJtbhJevgJevAAj7+PlfPgMAjD/6CPef99g9Occ3H58hrCNw6qVqBqBtPfp2Qt9OaFwAM8HZkFkNu6EVgcpgYG2E9wYcTWmxcBHBGxirrRXAzLbUOtG78IMT5oY3CDtCYLmdkyZ9u1r0c/qeF9cIoJFtbk5WAIBH/VY/EoDBEGPdTgjsxWJVhUCtYezHBqN3OQmsrU8BwKvOQLJGJcOwiR5vGbGNUj3MVfMqC3UMamKhqKeqbp2pKeBCKWFjzNppZEdG0jqQ3vVa0E9acKyJMNp2YBRoAoAYjDBFWFqOwmhKhV/nz5KRZMaXvyWt1GQ1KoOjD5NXBmILxA6YiBHbQu2uQQY20iZiRkmMyCPbHYIlaSIPGItcna/ZH5lRkPKoij3C1WfGaxU9yrjS/pnl0UjFPbpy/LpKL3oHQuWvr9XuCXZPiA3nBHrWT29IaPU56aR5lT6xIo7tNhNgEyizOWjlwc6A6/VOyPoBGWRgbdkAYA5G7UT1I4t5hR/zOfw+JsBMzDC1SFRznoec7gHz7DhseN7epKyCeQb64fFmEUr7EALl46VBxqRRAWiyrIBCbtfBzKIzXXO511SAMkrbl0Rd1kH1s6MZg6VOzkl/53rejDISUpIfCKGP+TriQHC7CmQAsiDtBy0Pum4zKJT+PCmYRMhiuXW7Teh4pmuSRTjrY/PRfa9AmISEUMWGoICsu8FTYYtkBhIwY5AwbPneTp+zKYNIxyIGHBDXEcHwbB/TBkS3eJcuscQSSyyxxBIPOx40WEHOwTTSGhFHeeM0qx48DDCXl/C/+zFe/WyN93/o4U7lc+YD4nULe29w9uwev/vT1/jVHz7CL16cAwBW35xi/R3j5IXH5i9fw//q1/l8/ruXAAB3fYPLf7vBVd/j8OPn2D9tsHvaYbiQ7aY1Y38WQOuAZjWhaQJO+oi+kTE0JuKWOkzBonUe3okTh9Mst3EB1kSM3uJ+J9dnK0HLk35A7zz2U4Pd2GC/60QSYtC3+skAAaBJWy20+h/7WKqc2g+NQV64p4PFl3yFNxuxjjjtRkRlRDQ2oNFxp1aTITiESNgPbQYljsGKMBoYF4XhwJCfdYjpejxZ+TyQUrerl3QGOEhPfVRBP67YHYgkVHyCJKd1uwA08SLO4EUki+gqPj0JgEI2Cnjijfye2CfEMKnf33pEZ+auM1HNMSaCobkgHqAJTSh/B+ZtJmyAOAKhl/OFjmE8SWtMfRoHQN0+IglwUzuSlOOXqnFKwPLtqMCPxBAAqmRLK8wUGe4A8KGMMTmSxEbWTGwrMKROyFkqy5zYFYl9MQFuD9FTaAG/5sxYSfuRLwlbYQXUqIAATTBc8nW9uJRAswGMY7BVd4Z6rUDAtJyBJ3cPAMHarBfANcA3q8hTbvNJv2dmAoQ9ktte0j6JAZDGny6JJEknVLoMkYRJBQBOW8Tq4deCprnqX86ZWiaSkGd0cX7PUf1MwBxxqf41nCv/ab5CR3nfrPNRi2bOwJsk0MoIfSxCoJif8wPtCijw0XBhvEw6J4m14AiBbZn76rLKPJRnILeJVACM3ADOa9QEnh0ogSXyC8H4GUyBqOyj5OAhrTFlG9VlzUKdiamRQa9qzURLmVHHlfNMAmBSG1y+51ULS2KisWNwE0FtBLmYLzFOBpXB1BL/AHG62f9/Zk78XfHbjrcwLpZYYokllvinEgtPdIklllhiiSWWWGKJJZZYYokllvj3Kh40s2K66vDuv/tDUODs4uA7gp0Y+0cGtz8OePajV/ifv/hjXFhx8vij25/gFzdP8dXLR3A24L+8+mv8j8/+T7z44SUA4M/uPsNfvP0IX789Rfvrj/DoL55h9XpCc32AeX0t5/jmBXCQ0rP7229w+cnHOHt+ieGJsBKmjcG0tgi9xXDZYTxlvLqIwLmUu9cnA0IwCN5g5ztwJHAkGFdKs10/4bBtgbsGiMBkAdbPxwuHx+f3eL65gzmJeL9ZYwoW+0nbQILFMDQiiMlAHC1MF3CyGTCpSKb3FnFSTYfByjluOrzbCf9/ezLAORHQ7BppAWlswEUrmhY73yCupNI3mAbea8uE6nIwAEoV0aoqmIvpUVgY0WsbSDjm10M0OlJLRKoIR9RcetEn0b5w+h5NCanUV38PVR+BHjMaqSTDExBJfgcQkmZI7djCyBVMKOOAJ2GGJK2EVH2myFkHwo6lxSPTzhmwowofNspGqCrEaR5ADLZFQA8t6g4J7eWvKPtUjpOcBHI7B80FQGv6POlcxYmhRjLCrEjihEFEQOOIWZtJ3Z4QnVSIQy8aGinsIBXk6Cnbv+aCfoQwgZgQHatjCuZtM8qq56R1YbhqL5CWi8jiNmPaANvG3OaR117qeYlSfs/rsy1ih+S+n1nBkym6MsoeEHti5G0zuyUJqaIwEGqtEhjVOqnmVhgsyvywek2eqrYKYYNkKgwBpYcHFdtCz1m5dtTClDONCpS1Wh8jsSOANBc82yTdDwDA3s4YMqktSOZVWAFZKyRTnlCYNVTmCEn7QZ8vjknbR0/mGGGj7DA/dxahZOWrji61gGgWLoXoXyTBXnEVEbbPsSYIO2U81CKn+jkFKhor1fDqqyQuzCqgWstpvHorWQUyauFdilQuW8VaKeADZgU7nTM9aNLn4Ujgvc0WzEssscQSSyyxxBIPNR40WMGnAW/+64MkvBpWqdSfPnmP/+mTP8NnzTs8svf4m/EpAOBvdxfYji1iILx/f4J/9f5H+G8e/Rw/7f8WAPCfrn6F68dr3MYeP/+DT/G//Sc/xYu/vcDqNxc4+1LsTS9/fgH61d8i3N4CEPCCXr1B30o2uWqcZH6GgMdXGD8+w/Z5i91TaenYP+sQOwZNhP6mCNql5I4YCP0KJ3eAU5cIAGAn17l/3OC7J2vc/7MbPD+9w6YZYVpGu5HMqbceh+Cw8y2maOGjwaN+i0fdFqNaA+xDgzFYjNHhfuzwdrvG9rYXDQwAw7bF1ESMLmA/NHAuYt2NuG8l0/Vs5LzEGPyEMViEWO6DDwbORgxegJMYDFwTcuISIyFMVlpWUgJ1zPOJUMtK/b1Sy89BDFARS5wJHyqln+uWAyBnF+Q16QHlBNkk7QQAoTXIYpKuJMfclOSJLIMRwY3+vUoG61aHMIqVox0IZtDhaQuOHavkJbcZ6LkMkPQu/Ioz4DDDdYKMm1haSdiUBMseqLgZJHCmKXoJZjqi5kcgdOJOUuar7B+T5WO6Fb4cV1wyJQE0I/K3iwgQiqtIAm/gKs0G1nmKZQ5M3T6Acr7iwFL+JnR5SVIDHOIGMH3I7TwibkvVeeRnzohOdZp0z45VPFNvQQp1FMlridM9pJJcqsNJnsNYdDPSv0mQUa7HCK3fiQ2uGUlaX/Qw3Gh7iK0HUkXG8CgnsDPxRm1FSuPOa6KW94hqN5qEIuvnknh+7jSfNViBMge5peMomU/3GkAWRp0dT/VGSMHDWtcBLkrLhFXnmASUpXN7EsthlLVix3KM6DgDbKmVYwY2RIL10u4xA8tSm5ACm3n9ZXXa+TFEWFR/N/Olk5/fak3PBGMrgCu1sDCVlhSu1hQixFEq2vK94wnNzkhr0hL/Qcb/W7vJ0iKyxBJLLLHEf0jxoMGK1gZ89ux9FoIEgMYGnDYH/GDzFhd2h39192P8/PojfPnyEQDA/moFMxJWXrQA/uT+h/jy80f4yZUIaf7B2W/wcXONJ/YW/9Xpz/F7P3qBP//4M/zRFz/CN78jbiC3v3OBzTfnOP9yQPvyHnj9HuHNG/D0PaWst+/QfNXj6qNnOHsiYMfwpMe0NjCB0V57mKCVRFMq8tEZuO0Ec/Cg0YvLiVNWxMUKw1WLN//RJX757Bz0ZMBqPeDxiYhjfrK5wdP+HgaMCIIB43l3g/VRqa3Rt+mJLX61f4J/8/4jvLw5lTHuGnAgTFMDMGG0EdNkEVKPvmFs2hErN6HrfNbcWDs5xxgs1m7CITjcTx0OXoCcQZkNPljc7ToRljw4qSZbhmlkTGSkCs+jIjhGgAGycSaoSUaqy8ZEWQcmZqkDIs6uJymYCUH9U/1kEdVRBYPJveF1op+1M0yVNDr5OTqAu6QzoL316rIAoPTeG0YYxU3FTwRzkPO7nST1JlQF8lQhVoAqgRixZRW25HkSpdsETZBjN/88tigihywJUOzKfGShS8OlchsBf1KquCLoWbY1UzVOtfr8QICymkcQ4Felip/1Oz5IckXYlVCBIFWYgCrBoxlwwU60AygQPJwQCprCVOJIyoSoJyeJalTjOBZnzT9Xn2W3hrJNbAhkAfJc2Aqm3FdxJElJrPzLVCfiyiZxVEDLI1cOVjvj2Ne6K1WpXjUwiGk+1loAU78REBXA0ecuX38CXMb8EBWmhOG5zScgmiw1w4C46OEwlH1Uzl+DG1kDJF1LRLZtpirxrsGK2NEcAEnnrJ6fGI/WVnXO2gY5acOwQ75GivLcmwlZSJa4Ag9tEedMKqF57et41MRDpi65hxy5iCaAgljXfD0vBvI8prlRwCkzlZiLzfJIQDSzMZIHmi3NQZglllhiiSWWWGKJBxgPGqw4DA2+/vYKJgkpQt7rTjYH/PLdY/yv4+/D/2aD9QuDJ9/K2+/5X98DzAirBocnLbavLe5eP8a/fiRtIH989c9wfrrDWT/gs5P3+MH6LZ62t/gXT7/Ey/M3AIDffHaJb96e4/rrDTa/6XH29QXWXz+F+VLYGeH6ZjbOeDgg/vpr0G/kbXPlHFZNA8QI9l5o6YAwMQAgsrzbckSMjLlkPmCsxcpafP7VD7D/9BQ3P1hhuFzhKwVDvn50hauLLS5We6zchNZ4XE8rdMbDab/Myk543NxjbUac2x1+b/MCZ26PX20eAwB+8eYphslhf9cBowHDYPQG7wenY2DcNh4XJ3ucdgM669HbCVettNsYMDozwZBYnO5ji9uph9cM00eD9/0a27HFzXaVr61tJBuyJmKYnLStsIhsGsNoGo/GyjUwkwASBGxaEQS1lapcArA669GaAEMRhhiHIMDJ3jd4c7/BODkM+wYcCdPBSoIEwBwk86ZQireS5CRmBhCDkSprcqSoLQhbcTORFoUI6iM4EsJK5iD2FmZUS9QEcDg5kVX2RRIwzEBFYivUiQgD3MbiwlElc8GwWoXKdrHRbSsHCLlYFvRHXQrCmvL5aapEQg2yswegDIiAubsHMHNYAICg7iIUBXSgETPmRGZ61K4qtW0n6THrSr6OPbXQhIbAapvq4cC9HiAl49oqxKlFIp20Ok9tsylF88R8KCCUPKdaYdfr8k0QodWq3WcWBGlXU2YD4cNtbCTEqIKxtlxfHpuHrrMKQEjf4CnzNQRWZxPyOh9pExflc6vAVA1oVDeCI4MmU0CIBBzonM1sTes2k3S4DMhouwRV56lFNpWdktYgedknPw+mSvYhLCAzEUJlg5uueT7XXNgHhgVIStdiuYBqlUhtskdNz5uZFFBKgGN6XnxiPtDss/r4CVRjI9dkPOZrLGLmapPspGvAIwnVcrUtN1w+VyHaJMhrUvuLnqO5B/ZPj9C+Jf5JxN8l8rkwL5ZYYokllnhI8aDBCrslnP5pj9CXF142wG61gtsSNm8Zm5cBq+92cK+kZSN+9wqIEc1mjeb5E5jxHG5nMVzLG+H07Rr3qxVuW+DXZ8/wx+cDnlze4bw74KSRDPKzk/d4srrHr0+v8PajE9x/3qN/e47+tYAF/XWE20W4Q4C7HWDf3CK8eJmZF+x91rz4dwlyDtS24KBJ+jgC3oP//N+i/0WH9Refwj8+wf65tJnsHvfYPl3h/UVEOIlAo7YVrjAXbBOwWY3YdCOeru/wpL/HF/07/N7JdwAEzHh9OMGXdIX9fQcepGWDR5lokZlweDM5bNct+sajcx73k7SJ9HbC2k3YuAErO2FlRgzG4cxKNhqYcNnusQ8N3q3W2E0tfDTZEQUAhsbDB2FzJFCicx6bRuYxsUY653HiBkQQWuMRj6gHGzfgxA6iu0EBUbOCXWzx3eoMY7DY+RaBDe7HFttBMtD725W6lJBQ00krvYkhkKqZEcBEonlxZEcItYqUxEh0D6hNTBog9ISQEjtKzAxCVPaFJCKYOxrEkv8dU+ylol3+KAAK1H0ibVPAEQEASnlbqPbleJnKzyWRY5QEKnScq9FJToENgw3NwYZUVQdgRsAe1IkhbWKUaWBKIlb6+/V4nj9wXMn7WmA8Jb0FMhavpeh0DTTREeuhJH/HrQy1w0me2wRcpJNXLQxkGdwSorWAMlFqbYh0DmMVhDE0A3hYtQnABBOBqI4Vs3YfvXa7N6Wyn5NkzFgKCSyQ9hFdry6CGgYUwIi1lksah7KOODFJZpoXZQzJrQJHY5wxPhToK2v0iIWR2j4yoyxdiwIcRoDOPEY9t5koa31ki1eU4yY2hIB7jGhiNU9qCwt1A5lke24VMAiMicRql7RdSFxdysCldYqFYUVlXmR//Zy4sk2tgARtdSKPCgH9kALBJjEsSNxGSHRg5BrSOSkDFrldTe8TG2A8X8CKJZZYYokllljiYceDBivcPuLRvxngNxah02q1E5G/Zhuw/m6A3Y6g7QFI1qZPHwPGACGAxwnt9Qh2LWIriY0ZCXwromvhvUPoHV6u13hx4eE2KpC5HnC53uO8P2DzfMT+kViI3g2SBbw7OOC+gbtz6F/3WH93irMvr9D8SoCAZIGawvQ9YC2STyY5Bx5HmMsLgAh8tkFcNUiCD2YIMPc7+K+/AQ8Dwl/9DcxXHU7PBCw5vTiFf3KKw9MO+0sLv3JwB4bvVUwOQsvf98B9x3hx+gTmcsDPPv0GP9i8BQD87voVPupu0BqPb9dnuN31GA4twl6zVBXGDHcN7g8W20ZYBC+NjME1AX07oWs8Nu2ITTNi8A6nbQFpThsBMp6t7vDerDFGi722izATOhvQ2QIuRCasmgmdm5fxT9wAZwIsMVZ2QsjbG9nHTnAmojcTGgowmrl0xqNb+/x3ANiFFq9HaYX5+vQSQ3CITLg7dIhM8MHAe5mDEAzCtkFqH6gp6wAE3AmM3Nue2BcpubIsrQp6a40TC1UwZfHSOFrEUZveNdFOOo85ctKDzDwowpnzRNBMAKqk3Uya7KSklJL1pGwf1lzkG1KbSE2LrxJkQIEKq60mqZWgGlNKdo3nubgnyYZs5fmlWK4w6TaUCnw6Tqpso7IvFSHSWlQ1NiTj0bYKGpWBklgH35NspxaFkqRW2yRdi4iqxUJFWD0Je4EV3Ei7qwUlt4ToRbekbnVhhywqOk/wNaprtwep1mdRznQfDM8SVrnnJOKgOlzTiZaHSVov8ehEFEEkah61MG46ZhKSnbUo5HmBtlWI7WkSmK0BimNGEHBE7LAsbJAkbtOUD7N4ZqUTcty6Q3ov8vNgWOY+bWLUklXbnuJoCkgIAA0QNozYWmFXKKsorz0wzEAwgcDM2dY3RRLSZSMAa9K7yWM0EDHZ79FjSWHqZwuARXpW9TvBcWHMsP5e/Z+cAjCeEXhVPWBLLKHx922vWsfC2lhiiSWWWOLvOx40WEGR0b49wO0bTCdyKbERWr7bBtjtCDAjnqwQn0gCun/Ww68IdmD07ya4+wn2wFk5PdpSCTMeoB3g9oTp4BBWkqTe9R3uzlZYnww46QecdgMu+j16ZQ0YiribetwNHd7dbHB32+L9T9c4+ep3AACbV1/A7iMoMEJvMW3MjBYMAM02wvdCxfYdCY0+tdhPgDtc4OSTK9ibA8z7W/B2C95JCwa2W7iXb3D/w5IrAAAgAElEQVR6dYH15SnCWQvyDL9xGdQZTwx8T2AH+N5iPFvjz8bP8e0zARt+9ugFPuvf46dn3+HT9TXeDCd4N6zx6v5ETrHvsuYDbS1grOTTOn5vGQfbC5ujDWhajxgJjTI7jGFsuhGrZsLKTZiilXaRSd7CIxNaG9A5n9u9QzTw0WDw82UbmRCZ0NsJg3W51cUSYwiy7R2kLaQxIYMZgAAWp/aAS7eFRQQc8Li5BwA8ae+wi62AFb5HYIKPFod0zKnH2604sXhvMQ5OErqUmPgkHipggNlLdlfTurmJkpBVGZsxEbbXSm8bELxBHG0WHox0lPVZOQ5XwqHpHBSTc4EkzkmIM1X1U5U3JfukwEoazpQEIdPfdbsyWHlmzIQCdhAXtwKggBVJ28ICsf2wp564sCTqLJgT7T1Vki1gJ87tOnZiGM9otlpRPlOHmOTEYAHPKg5qdD9TTlFX5ksCPa/YZzYKE0wCKwwyNZ+ZRLekSuJ5ppjKQCNJJQcCGwOu3GvYAGR13pMgY8VG4VTEJxVkVT2VWLNgLBVwRRkGHJAzbZ4EKAFJe1A+/hEbIgEvZHVNVe0i7A04i8bMblOl46HHClQAK6C030Qq58IcrBBNmKPsXTdgIIOCFPQep8PUIqmpzUW3T0K7ZQP9V58buZcJdAJs7xFdRJgMaLDCmKrGYo3JLUlsMXO9ATOIqOhR2LSe59ckz9z8eUoARqT53ygoyLbXUxiCXxVgUJ616vtjIhyexDnQtMQS/wCxCH8uscQSSyzx9x3H/gtLLLHEEkssscQSSyyxxBJLLLHEEv+o8aCZFWwI3Nmjvm4t9xlCWDdgQwhrh/1judTbLwymc4bdG2y+Mbj6ywg7BLT3iedr4HuGIdKeYwAs6uqZhntghEOL7c5h1/e4Xo+wNqJTcciTbsS6GfF0c4+Lfg/3UUT8MeHLH4sjybvrHrRz4n6wiqDVKD3vqRLGAG4agNRZgHlWxbQHgt0b7B+v0d6ssHp3iuZmgt1qq8vdDnS/A3yAvdmCQgC3DtZRFvNsLGCCARPQ3jH694DxHV5dPwEA/MvnG3zx5D1+dvENnre3eNreYTqx+O5EmBcv92e4Hlb47voU47CWanWiZQNIdqBS+bMYbAOKlIuUbBn32gbh+gnORTgXMI5yn4iAVTciMKFRpw8GMAWLm9RXrwKbiVHQtxN6J2KagOhbRCbc+w5jsBiCAzNhUhcIS4x1M+J6tcKT9h6GGJ2RthAAOLEDOlV8PHMHWIoI2loCANd+jSf9CmO0OIQG92OHKRpM6nhyv+8wjg5xtODJ5B75ZK2IiRAbArdCVY+k7SVNhO1zUz6sk5KrsvqlLSIterUBNSuf5yNOxcaQvRG7R2tAgaTbI1B2VAGERk7KRDAeRYcDSfCSZo4ix2FI7n0SBzWBEImVpVQiiQaiAfwaM+p6bl1Ju9SsgsqmlILQ5MM0d4xwO4Y7MJodI3TSCjLTnWCCP+H891mrRf0FUpFcaqZCsqElL1oc4twBsDIHgidwe9QGVFXI89+Tjgmr60fN1Dfa/VA969laNlnwOoAnFWDlSpBU92FDmYmSqu7Z+tiQtJql9hinx01Mn8S2IIDb0r+ROjJI2yrgRAOHvJm3ANXuHtpCQ1SxR5RZke/z8fEzC6k6Tj13htXyVp17ihRFERzVMZBaemZyS9ow6cPYo3NU8xytgXEMNkEeg4ZyC5awUiJ4kOPElmesBnaiRZLWSBJqrQVB2QDUYKa98gFjiQvzIjGfUntIskWlSAiOEdvEWEn3EeATD/e2UlddYoklllhiiSWWeIDxsMEKRxgvOpAvioOkGgHREsaLFrElTCuD3TN5m91/GoDTCf6uAQWL1bsO/dsR/Tt5o3cHi/HEgi1j2hB8LwkIecCowFnoRPXfDoTQG4y3DhQJe6WEv+8DmvUE1wR0jcfVZoefnL/CZ5v3AAD/mcWg/O3OeGzcMBOF7MyEbw/nGKOFjxZb3+J6v8Jhkn22dz38TYPYGkwnhPGsgds7uH2v13CC7v0k2hY+IjYGYeUQepMtOKMlUODsStBdR7g9o38n4zh8d4IvH6/x6kcn+MnjV7hsd/i4u8EX/TsAwPPuFi8OF7Am4stDg7i3amOoL/VBwYQIIEqPN3nkxJBdofH73mFqGOhCSQosI3iDvYtoGwEdmAneG4wHfQlPSZ3+u1fQo1VNi1U7gQA4E7GbGtztekyTzaCQdQHORbzuNrhYHWCIYcA474Rv/bgTK1hHAQ0FBBhYxCwC2hqPx919djzZ+g5DdBnMeN2f4ObQYze0CMFgWjuEvQXtVB9lMDATSW5GxdaSrUHsbJ6n0MQ5fb5yv0EQmjsZaT9iAIZCSRDVkYS9JLuSTJnS4pB49FGTI9WvsGN6nlCACpTWipnwZIBYVCZxzBpwQPk7mBE7xQ4anrUwJEFK4ws1fmYHmZJ4ToDFvE3EDoTmjtDeSUuIaG/o7hFotgCIMJ3o2mPK6y/rbxhkkVCKEOcHW52EOIM5CUTMoE8ksDclec3rsrRlIFTtJ5Y1sU6tEJSFTDNoAGRNChBmAJOMv0p20z2plgZFABNQCzimlgJEAVuio+IwYxixlUsNK4i4bCWQwglASq0YI80MPsio0wqXOZ6thzwInYva0hQCWkgSbuRc2SKW8vigdrRMDKpcQPgYGCPWdh2dcx2kGQhGXXM4tZzo93saWxwMQn9k1VKDMo7FZSce2ahCQZ5IiIMRpx8FxYqVqv6Szq3aKvU8Fd0Rngm1zp7JDAghf8fOdDAmg+ZmaQNZ4t+f+HfRylhaRZZYYoklljiOBw1WREc4XFq4wcBM8qZnJnnpCz1hPDGY1oTphLKN2+r5PZyNuA2E6cxg98TA7RyaGxGt6PYe9tAgNgbNluBXRvvEGV5FD6cNwa8IoRfQAoDa3Mm4wsogOoeRgEPHuDnZYDc1+OJMwIrn/S0+dXs0FDCxRWCDfWiy1eal2+F5d4MpOjTGY4gNXgwXeHUQvYhvV2d45c4wmU4SW0vwmzIWMwHDmUGzi3AHOWZojxIHlmuSCh7D3U9obhj9W7mI8bXDcGpxe3+JP/n4BP3lAV88eocvTgSseNLeY2VHfLS+xfax2I8mzQYAAggwIU7igJFcNbIFYBNBo5GER4XsuBJFZMOYJoPJMsZWGBFZD2LQiVZBP7kBjBHAZBhTo84uwaJ1Ho2NOEwO4+AQ92WMsTHwhjEODbb7DkbF9/pW5vn1+gStCWhsgKMIzwaGGL0VYKu3ExqK6KyHAWPjBnTs4TT77azHedtlhxRmwvvDCjf3YtU6bltgMFpRLpVU4wUIk3kA2Nrs/sCGxbnAVZnJRAjeSSLnTRF9BGDaIHOmOgQwsg5Mn0QrynrI942ASeeYBpOTela9h1psE8R53emvUimvqsms50kVYCZIhbzSEUgJL2sSbKa5ZgQq1hGriGO+jy3DbwC/ouxCUosiMqnOy04ATr/CTCS0BitYwQTyijdUYAETFRcHkkQy2XpKTk9ZPyGLOtb5ompeZE0HWy6ca/QnaV8AGVwEKXhADHYGoSPYkTJLJ4mM1kY4qcJfH9pkcVKAJ9H/SGBFdIKwyBwYxMDFLhPza6nXQf4zcwUQFdCRq0MICMWgvBBQvpRUayILpiZ9l2ydqsygpBlSR9qmAktTEo+pgDrZKlgZDqz6LgmsSJ+F1qi2Cs8VbVnAQdb/8rnTJCTmDDGiM8BBxpqBsSMtFcRqWqvvZnEC4SJKanQsSKCibmqQbVZrJlLzzsIe2QcvscQSSyyxxBJLPLR42GBFA+yfGFilfwMiPmc8YzgzOFxJJXV4FtA+EfHJnzx5he3UwQeD3WgwXDQ43FmQijY2tyPsIYAtobmPcHt5y7U7n6vd04nF4cJiPCexvtMEpwitERqlqUcn4MXL7WO8vhSRz/V6wKYbYUlU+adoMHqbHPzQOY/nm1sYYmzciPNmD0cBl61U/HvrcdoO+Lq9xL5dIbYWdlcSXDsAIEJ0BqHjTBWvHRTMyLCHoKKHDIoMu59g7wa9hjXMyHCDxe51g93HDr943uHFI2kD+fziGifNAEcRzzd32LQj7oZudn8mbzFMDtZGhGAQI+WWjXU/iljmocF0cAWAyDcXoMmAI4soYErCbJWopyplLa4Xi8NBCAbRGMAKG8LaCO6CJH0AyDKMJlPjoYF1IigYdf8EdlCmq8vfWyfZ7aqZ0BoRATVgnDQDDEWcOAG+WuNx2UacNwexb7Ujbn2P9+drAMDbwwbX+x57ZV740WLaOdBkYLdyUWZK/6VqMCFGzq4ugCRCktzb0p6g1PQ4UU6wsoggI1tUZhCDpIBtrLhBsM5x6AziZMSlhBgxiAtMbcsZJ5PbgHISm1gv1f1M+SmxJIkmIxSQVphUpdYKfm6BqI6VaP0Uy+fsxEKVDWP0BLcDzFixIvT0dgB4BwAqGpocUUYUgklbVasZQN1moePKoF/6W74RLEwZAqAMhVIux/zfVG63R5/rR3nulElAhOwaw06S2DgYmENaJ9r+YVNCK6CXGaqKOwGhQ3FTqQUqId9VbMv1SwW/EkKtABjW9oNj4CC3RBg98HGin8JxaVvIYIX+rq4fiVVQj98czAyoSgwW0vWcx40K1JgJZMoc1ewdVhAPQGbNmFFteQMVwAooArENcisKhcLi4XTtjsEmIhCBJpMdiASIk7HFxMDJk5fGnYCz5B4EBUfKIJJLCaKMNdnUAgBHRnOvosxLLPGA4rexLxbGxRJLLLHEP9142GBFy7j/jOH2QKOaE2YA7MAYzwnjOcOfRnz8w9f4ycUrAMDGDXg1nCJeEN64gJvDhb7olv5eioBfCVtDLBYZIIIZlf5/w4gNwa+FS8xOgQp9mXR7qeIiyottOBCaW4vwUpLUoVlhMKlqri/LlQMDIvDi9EmmPTebCavViNNegISr1Q5PV3c4eTbgu5MzvLndYLjtQFu5nW5LCC3BtYS4l/FIclde8u2s3AlwYxB7B3OQ7MxvLPzKoLv2oGDBRt7Q7/cCVvzVvsX56R5n/QGbZsR5u8d5u8/tLIYidr7FzdBnxoit+OBX/Q69mzAGi3eHDW72PYbJIWjS4SeLONic9Bh1EelVHwQo4EFqw4iREIKBcynBYzgbsWpUy8IFjN5mTQlmgjURPhhMo4N1ETFSBie8N2B2iNHkvzMTdvr5tmnhjGiVWBOx9w0aG7DzkiU4E9Ean4GKc7fHudvnVprh1OHed3g9nmDnW9xPHe7HDtuhxV1mXziYnZ1ZhbKrAJqU4GibjVhfcrb7NFNpJ4HRqjaTWGwC8rfaflOr+kbn0LqIaFjmnBgxGgGEfMnSuWFxtgBAyWb1yOrDTLKujSb5VK33vGlTrgeE7NCTLWGV8cBWkkqrn7NR1o1lhE7Wi52BCAAcYG8ZbieJsLAIytjS/Ab+YOhl/FAgiKpEX88TLRBBMCzzGxiAJXGbSGOoS+iR5Pvi2Bo16TakcAkoE8CCCNL24+JMF4QHIyyI6nghkrCXki5GmkNNyJMeQq7INylJTlo50ipW9CB0rWjVn9qoGhvfM2F6c6sOlAwSQh1marZM3sYpgDORtopU41NbV64YKfk8eo2pjSi1eWT70sR6S7oquv5Dp+fT70NimS8zaXvMEflDWqIINuleqENNBhImI/8/WAWZp0ZAi1gxdEAE8jwH9BL4Bb03ifkRCBygLTu6reXckmN9eT4SUymxMabTY/rJEkssscQSSyyxxMOKBw1WdP2Eze9e4/6+x3ArYIO7t1IVW0WETYQ9nfCzRy/wrL0FAPz87iPcDCus3IT/+Nk3+OtuxDfNY5SpaOD2Ckb0gAmcX36NL6yEXBRW8T9hdOi4rkXsjw0QGhH7ywkDMH8Bt1K9TgkYANiDUL0BgI2FX7WYVht8dyE7fXM14fLJHf7g6Qs8ffQCt2c93hw22VZ0d2ixv+lhthbttYG7L5XWRB9utgZ2KomNGxh2iLCqB7G/cgidVpsbgtsz2ttU9ga8X+HtaHHb9zjbHHDWH/BsdYeob9+OIgwxboYeo3donYchhjWlJL+yE573t/h88x430wo3Y4+7SXQ37scWu6GVd3imrEPxydktWpMsYgWkSP/ejT2G4OBVQNNHg955bJoRrfFYuwljtBlM8FHaOobgsJsaMJPYo4ayfwJEOGrlWoELmQML58QK1RILE8MGAAI0OBPRuwmtDbhxK1y7NS6aXbZGfWzv8YPuNbarDu/9Bnehh48G+9jiVufh5f4Ur+5OcBgaxGBE7sAFGE1M/GSFlbF30mbTS9sHpcRlWz3immiBCZm8bzi3MoAIPAltIDRaqbYCXkQjeiQchB1Tgx0gBtqSKM1EMjVCoMwWSdXt8gxp4sqQRNYyQl/o8jaJFRp5xjhZOebnRSvVijdKuwnNnzmWdWxGRjOpVkOlaUExZYqUGSB1yDkYnEAOZSSl8GtCiKkaDtCkCabOI1sFddQaOaulpkgMBMZMzBGxIAVcs2OIlO0S0+UJiKFMIUogRl/YTJgogx+o27JyO0zFkkiHrsRYozJJij2qaHZQeqYZwthJIpfEua1IhixIUJ61xL4IZU6LpofiB1QyejkvFcZEuk8zIEi/s5wCFMq8yowPHVtM16ptG2n/yQpbhFIbUlrPOod2SOsUueWEgDKHkUTrxJgCKlJppwmOEDttffN0BILoz17GkOxRYQSoMFlbhHL7SHouQlszL4CxYYSTI92NJZZ4oHHMuFiYFkssscQS/3TiQYMVl80O/+0Xf4mvdlf4zd0FAODVuzOMewdyEd1qwqOzLfahwb9+8zsAgL/57gmiJ6xOBjz6ZIv//uOf4182P8YvzUcAgNg49G+FrQECgr6QxqZUUZOKO6f+fw+4PaO7lpfD7r0HW0JstMpW0YgB0Y+oAQszyctoojvbkWFHrnq3JaaNHGc4b7F9foU//uctvnj0Dh+tb/Gj0zf4ZH0DAHAm4OX+DG/2G3z37gyHu0Z7wSlToq2q2QPywtveGjT3Bu2dvlS32mbzyMAEoL3VBCSU/cfQYuobvD043PY9wqXBupFydwIQRm9xGBvsxwbGxJxTXNse79o1Pj65wdPuDlfNFq3x6K0AERvXYuwtJu2tSTTqi3aHTmXxV7acqzMe19MKY3R4uZd2myHI8o5McCbiWSeA1aRv9V7/HYLDPjTY+RaeDbaTgBkH7+CDRYiE0TplbgBBhU45EAIkB4w2IjBhPzbwXvUeSBgJRAxnIpyNmRUDAM+6O3zevUNDHgfNtDvjceYO+NFKmEC7kxbfnZ3jelrhoNeT5ggArseVOJ6MHQITzrtDvm4AePH+XK55sgiDFU2LpJ2QwvLsbzSZ3P7AVsELq84PXsGO1KKRmB5JyLOJuWJOrrAKyDCibwtVvYm5BcK0QVg0Xqra7CKoi5hadUcZDOxe192BsuhnorkTKzvCE7gBYsMIPVftALJuQy/JpDkwbCz6Cqm9BqYCFU05thxDn0dlJ+Vns2aHsH5PxNK2E1v9/ugYMernrrRPZLwjJelMBTQwOnmQ62VGaZWAATVRwCQASJR/FX40iS3jYr6vARZkGGSjgG9Bn6oEFoxG7muUCycmUKV7QIEQA2VRSp5EqDWzRwBgMrnliEmPXWmTSFuG6Njk1qSaVZJACMs6HeVzNgxY5LVXWjjK4TNZw3EBd5QJApQ1yZGK4Gn199RiFpM4pwJoqWUlRlu0MCpx2hz6mJjBgAPrOFDafdooDJZOvotplOerBoqMIWnN86KxQQHC5Ej3Iq01vQ9+zSIImtYGMVCzbJZYYoklllhiiSUeaJi/e5MlllhiiSWWWGKJJZZYYoklllhiiX+4eNDMCkcR/9nmV/hh/wpfbx4BAP6v9nO83m5gCHi03uKq2+H//u5T7P9KmBfdtVSw/EmH/336AX7409f4H57/Bf6oOQAA/vzsE4xfr7B6TWjuGWZEdhdIldy4Irgdww6cBd2aLePkKxHxZGcwnUql3B4YzgfE1iD0gg3FpoifzaqzejemlhAPpFR3hp2A9jagvZUS3uZb4OTb/4e9N4+1Lcvv+j5r2NOZ7vDuG+q9Grur3HZs3Lbp2BDFEMjkkAFClCgSCjGJRCIlUhCJhMMfyT+RIP9EskSEQIqQSBAJCihGRArKRLCJ7cY27sbucg+urqpXw7tvuNMZ97DWyh+/tfbe53W1wdDd6XKfn3R17ztnnz2ufd7+fdd3sFxfzvnK/Slfub/j7HjFvanM2L86e8b3HX0AR/D24hZXTcW2y3Be97GaPtKxm86ybTJWVxXm0lI+ln2cnAf0Btq5QtWB2cMtzVEuPh1AfaQxjaaZB9w2p80z3t1ZJnPx1Uhsgl2TiVSh1XtsEoLiynqutiWPJgtOyzU+aLo43e1HM/+a0Ms73ro+o8pkivGk2KBVwCrP1Nb98i5S51tnWDci77Bmig+KiW1YxGVz3WHwzO0OHxRbl+NRPK2nAKzaAud170exbnLq1orJJDL7qrTo9kMA7zW7XSYMBoRNQFB7uv7racWTiaz/w8kRH1ZHTG3Nh9sjunh9jostD8orAGa25la+4la+wgWNUZ5JMnMAlq7EKM/OZ2TKUeq2Z2kAfKW6jQ+Km6bkalexaTKRjUT2h3caYx3eK7zXkX1hB1p7p3sqfWJWKM+QABJkFttXSkw5beint5OvhbLCuvAT11MVTO7RRjZirafLHO0mF3PFzFPNd7jJcJ7bTQ6totsZYVf4wUciGW32ia0xljPR4sWbIdAxmJSOJSLCdqCXYu2lasRZc1eMKPs9s2LM3gh9JGZvvsnAjOqlC8PpERlAkjhEL4iQmAZeyaAaEWBUlCIxYhv0ap7M93KegCJ4kU+p6HuQjiUERXBmYDSMY1KtHHiKr01vpyQLOtB62CZBolp9HqUuSqRmPgv7JpjpXMaY3XR9UoTrOL1mbFraR7n29JYoK0F8UXpWxSiZI1i1f27i++Pvnf5+VPSMohA3qjPXM4FClD0pE3rZU8gCdEoiSuM4CGNmhRqx4eK5U4mNAoQQxDTVRs2LjxG2I++YkMV9c0rkR47eR0PGQSR9GGHsuLnbk7IQwJROpC6H+pbVq/mKv/DyT/NH3v3Rj3z/L7z80/9Q6/l6nz/UoQ51qEMd6juxPtZgxVVX8cXdC9zNrvm+6j0A2hPD+6UAE1Nbc75dsP3SMbc+Hx8Eo7Gly2G7mfFXqx/g3/nkZ/m3730WgDfmr/F3Tj/B+x+ekH+QU1wo8puA2YEVPINQS/qI3XmCkQbC7jwhRmbubufUC4PLINsEihtpkFJaiMuk+UGJ6aWp5WG0l6draGfyvrcK3YmBX76Sh89s1VGd1xSXmvqrGav7E67uTDi/cwrAr926w0u3rnh5einmjlXLusvZdDk2SiiOsh2F6Vh1OY23PFnMeHI6ZVWIgWZ+rSmvJJ4xX3rs0xX2ydBUdHcWbK8KVvcM3UzAF39p2E0FhQmGIeGhcNDovcYLpwgYlhvLuip5MplKGkXUvxsVUCrgoq9EAj26ne0p21nRofUgtSizjs5pdo00621jcTdZbzL4ZH6MLjsWC0lVOZ5sOSk2nORb5tmOhd3iRmSjua3xKKamYesyll3BdV1xUwhY0MSGX0d9fuc0NVlvKhic7o0CE8276Qoua7nt1tuC83xGYR2rbYHrJBHEZJ43J3cBOJutmWc1i3xLoR2ZdlS66U1LfVAUuqPULRPd4IKm1G0vm3llcoHBU1eWq8mE67Zk0+VsOzlHu87262o6y3qXs3VKrhcjMK2Phty/B5VHEiecwmcKX/leKpQqWI1rPcoGobybEP0MIqjUaEkhGflnhKCoCkEjrPF0VSPmqI2l3WTQaMxmMNVNUo8wTpnoDQdVjCEVSr43ck+lY/EGfCHL6ySDAJIZZfp7SMWAFDWbAJNk9qkCQ2wmw+dVbJDT68ESUznSYIn3e56QS9noYH4qryk3pHOEJLkAVDbIG0TiYeQe00FkF/3FGI5Hdnx0QU2QZjrtDwHPYDirnZy3FC+qHbhGSXMNeBPQTuGtALIJU0mNuuoG00qv6OM59yQaESj4mv1N/3R6P51ED+OpP/7G7PtgMFzTPh551NiPpRw+aSlU6GUp4zjYBMKELP6Ohqo9oNJLBeN24jnr40o7hfcMxqkRNEpmyrIN1ct3uiKCRxG4SLsWlPgyhcJjpp3Ic/r7TuEajR7Lcw51qEMd6lCHOtShPoalwnOpEB+nKl5+KXziJ/99PnX7MZ8+eh8QR/6dz/BBcV7P+bl3XqX6uRmLd6RrqI+0xPdFRsT6gWL2O57wn7z+fwFwbNY862b86vZFfub8E3zw/inZeUb5VJFfy7kqrzz50pFf7CAEfGEJmWb5kkR3bm9r2onEIJodFJeBfDXM7vlMHuiDljhFuxNQYIg+FX+KYKCZy+9sNRh82g0UN47qfIfetnTzgnaRsTuJHgzHmu0dqO92mEVDljmcU7jO9EkaVdUwKRp2Tca0aChsh1We96+jx8GbC6bvQfUskK0dxbMa+85j3JOnAOijBWoxZ/vGbZqFoV5oTDOkULgcXKnoJlCfSsyhL8Je40IgGtlBKOKDdWpU9hoWhao1eqewm6Fr7I3+NJiNEp+ClDSA+BsUlwIYZZuAy6GdKLZ35PP1qccfdUyOt9yabbg/u6YybW/gWZkWg5ffyuOC5qqd0IZkwGlYuxyrPF3QXO4mXNcl2wiWbHcZXW0JtRFGQojsg1GDmcwwQ2OgVdgbIxO+yZBv7lClo5w0WOsk7lZ7TDpG7cm0Z5I1lKajC5pcd9gI+sxtTWVaCt2Kmai3XDTTnsGSgItMO5zXrNqcy9WE3SaCTjEVguebvDjTrHc6zh7Ley6CFQk8gEFb78t47BG06JtopwTQcrGLjTPutpTrkBctZd72oNCmzoXBspbzrFdmiDHVYUgPGTfmic2QQISRF/IZAC8AACAASURBVANq8KTR0Q+jfyutww9MjRATJVQQoEbGmtzLCdzRHXv7IA28fCd0VcCn76DoDaITm8FKLG0PSKSm1kQ8wxFTXRBfiDhOiDP96RwqJ54Ie9mkyZskno/wnJ8Dyeg0GWvGa56YFbqRxjvt614KBvSAShgnzKjhe02N3vN5ZBNkXvwVAGU8JvNDfG6IwEu632EvmliPQIr02zmN3428WdJ17Oks8byOI4/H/wXqYb/3jE6Tp0Xy/dGj5dX+55UJgydGG6NL03iLRqnByk8PdCh6sAINphAWku+0sDsUhHoUy6LifRSjh0Ore4BR2B0KZi3v/OE/+YshhM9wqG96febTZfjs33zpG77eA9Pi69fBaPNQhzrUoT4e9fYf+8/+kZ9HPtbMCrsB/bNHfO7+nF+5LwaZD25dc1Js2LmMt5+dwttTdAur+7GRP5VmQXeQXysmHwSWf/c2P8nvBeD33P8yv3P2Ff7lo1/mByfv8Pm7L/F3L17hncen7N6W6NHwtjwU5hegVzV629KeTtidyOvNIjYkeaA5DbRHmuJCk91EsGEnhpyJLq6dRKSm53tTB7KNIhiFbgVccaUStgXQHEF9bPFZRfnUYnYd5eMtpXgy4jNDN7XszjK2tya4AvJEb49XvCunXGXSYD0+CrSnjumdNbMYj3r5+pqr2QT/FY1pFMsHlpPyPoWN5pLXN7iH71Ntd5SLGd3ZDHNTo1rpvkKZ4auMbpKxu2VZPTA0C3CTfXDM7GLKhlF7TYMKA+1dd7KfupbzpmOKiWlDTDmByYc7XGXAD7R05QL5dYNeNeinl2AMYVLS3hNAZnc7Z3ecsb2T8+Hpgodnt5gcbTmeCvPiVrWhNC1WeV4or5nZmuNsQxk7j0y5PRnGab6h9Yatkyb6fDtnWUsUadNYXGeEnh9lJMEriToEaQ5jU2M3Shz/kdhbby27KusN9GAE1MTGXuWup65r68lzuQ6LyY7SdszymuN8S6Ydm26QiYDIZTqvqWzLPK/JFp7rTNJIXJBmMSWgKEUf9wrCXulaQ9iZvokLPsix9OyCCGYEidcMfgRY9cczGDPiFGpjcVs54E2WUc9a8rxjWgrIZrRnF4G3JhSEJjanBpErdANtnuQxOSIxkO33qOMI0nGkaN/nN6OGeRwRmVYQG0SzE8BDd2LgOchAIhMjNvtdGz/TgxXRJNQrukpADWFWxf0zERiIYIOwFgZ5QTLyTABRzxBRqge+AmrALvaSLkYnwgQ5fhP6mfoQ1x0yRWgU3iVGQJKqpZM4Wo8a/VbD24ltIWCKnPg0HoLSdN6hjN5LEulZE0FYHj3zQgXZ5Gh8hk73QEXP8EnsifgZYW8oAqN7KFUYPqNidGrPmkjnB4ZYVTWAjn0lACFmzQYNPoK4KrIsRD4i328qxVe3cRDagI8AhVJBGEkA1bAfIaa5UBtoFKYdmd4Cvgz47cf6v/dDHepQhzrUoQ51qI83WKHbwK03Wybnlvptie1899UJbx91UGuyK0NxqWiOYPtCfKI+arF5h/Oa5cYy+2LO9GFgXd8G4K988og337jHD5+8zWcmX+UPnfw8P3b0eb58/x4/df8HAPjVF19g+V7J7njO7MOKbN3RzLO+ic6WCt0ILb458XRT0Scn1gEqkHmF6gLtTNF6hWklBUTKky1FO60bg88Vq/umj2Z0GXQVuMqwOdMUNzn5jSdfShNt1i3Fo5riqWZ6VOCtRrkgkhWTZqDlITpbdjTHGes7huUnFjx5RbjGr957xu5oxQfVLWlCbWDzQsn8VZk5OvnCCvPVR3Tnj+H8MfbJMX65xKdG1Bi00eTGUJQFk+95mfosp57HhIdCmi/VhSGar2NPbqB86MEcuxNgwtR+ACs2Hbrz6E1D+OJXsRFIEWE96OlEaO11TXd13Y8b+1Aa8XlVsZhUuBdO2d2tWN3L2N7NOT8RKcwHRx2qlHFzcrJiUdYc5TtOCvEmqUyLVY5tNCCZ2prTbE0Wu7ezYsXWZay7gk2Xcd1UXG3LfZmK1dK0dJqgA50zvWQgDgVJA9hqej382FOB1MjK51QQQGpbyT7s1jnaBvKiZVbV5Mb10a4gzVDdWprOcjrdMMkaZnlNFv0kfFC9f4jVHk2gsF3/Wop3vWkKNnVO2xmR6zQGF5ulfpY/eQsY2dFQyHXUmUdrYYwQFK7ThE6jImtBNQbfaraFpZ1YqklNYV0fg+udpquNNHsxjcS1GtZJJqLQ8ZwpF3vMEVNCBmRiK8TZf4Z/9+cqWm6kxtRnw/uJNaE80ELqgHvJQvytmwGY8CYyOUhNrACVBIlBRYGO3xnBgCul0U7RqGo0+5/YJL3sJDJdRFoSXxuNmxBn5Rk32WlXI+MipP0OCUxQeBV9FoqBaaDTjP9zUqEEEA1ghor+CxKhrIDQqj2wIBg9pC7F/fR2ABV6ZtLo/thL5PCqZ20J0jkAG/1KnwcgxuyTUYUxuyudpzh+VQQWBLxSoxgSRN6UVqeRBJAIPtJqaOh9KIThkgC9eK29yKH25C5B7Ut2Go3eaRnbbWS7pEP0sJt7zOrgn/1bocZeFweWxX49H2kKB7bFoQ51qEP9VquPNVghRpWaYul6PwflDO0sx+zSAz5s73omD1aA+BQkyUMXNG/pO/gv50zOI+thm/OFq1f5lbMH/PRLr/OvvfA5vrd4n99RfZXvfukDAN6+d5uf/e7X+Vufep3Lt+dUjzPMVuQcANWTYdZyd61pjuJDdwQbmkUy2BQgJTWhybQwW2uqJ3qIS/QCzNhNPMYc2rnIK5oj2G002UaTLaX7ypcF+Y3D1D7qwiVG1dvhgVxFNkcwimzZsag92dZyvakAeC875uWzS1559QkAi2LH4xdnnL8aZSKzOUe3SqZfnBKeXhDqGlUUKCedSQgBX9fS6KzX2F/YkFUV84WASqHICWVGMIaQaXxlUc2It61Vz3aW/fUCuvjhgV1vO5RzqE1NcE62NyqVZ6jZFCYVtpLjCtst7loiTNnt4PIS3v+AyWLB5M4t2ntHNCfS3W1vWbrKojxsb5csp4G3jxzERk1lnnLSUO8ytA6cnSw5Kbe8UMn659mO2/mKl8pLNIFn7ZTzyYKLWhg6N3VJ4wxtZ2idwTlNOzfUq6yXWahW7/VSyQByr8FLTXWrehmCymIztZV41W1t+v2EYbZa2BjQNfJV4CaKSSayFwCNwiuFUR6r/Z7EBKA0LTPb0HrDusu5qCds2px1k7Gt5Tx2nSZ4jdIepSDPOzLjWEQWj9Ge1pneO6Pzmotqwm4lsqqwM6hWobaGrtOsnaKrWjIrY20yqWlzOb4kc3JO40z8ersZTDmJjB3dDucwRPlCahyDDgSr9sGKJIdQYfCd0OxJQ7qp3OO9LIYRGBG3nfxpdC1eJ8NFjL9GkokkNZGTAiBSp3hhhv1Oh+IjyBf38yP6749uzHs2gYAXIYAaMy9SGWHFKKV6zwVCIN2SSdKg/IiRoJB4TuQ7J3k4pH0d+3ik6NZ03MGAy0PPBvNZEHaHidtIPiUJxEuHk65Zkno8x/gISbqRzsVHnSgV5AL1+LHqPw9EXxIlRrTOfO020ratj78TuyUaa7ZqD/zaZ6goSPHHYQSajMaiblT/k8bKcNwQSod9ts+gOtShDnWoQx3qUIf6uNXH3rPitR//42QrSeMAcFnMqHewvaOozzzhpOH4ZA3AUSXGkqnhqp3lncenmDcloaG4pH8g3d4OtC/XvHL/GZ8+fZ8/cPxLAPxgsSbD8Cut4v9e/RP8P0/f4Nce3sN+IM3V9F1Ftg5kW6GCtxORcPRpIrkwCVwZcJUsI7N1kcJdK/Ib1Tc3vXmfSw0CdBNFfSsITTzQyyHS58ULg5423lUMWnci5dzJvth19OG48XSVLHD5XYbtaw33HlwyyVruVksq0/IsJmV87s1XKD+wzN8NzD5oya5qulmObuITt1Ho2qG3rYAJj57gl8u966esReW5/J5UYlaX2CeTilDmQqNWilAYXJURMo2LMg/dhSgTCejGYVa1MEhs9JRYlLQzi6s03kAwiuLKUT6SsaDeO8c9fba/T1mOKuU66rNTQp5BCHS3ZnTzjO2ZxUVavLfQHEkyjLeK3Rl0U4+/LRfs5HTFS4trXpxcca+4ptAttc+47gQ4uelKti5j5zIaZ9k5SwiK67pkHRv97abAd0ND9fztqhSD70Wth6bKjhYMDLOzavQbGROo2EQWjqzoKPKOIpPBpFXAB4XziswIYKFV6IGFSdZwWmyoTItWnmU08Nw5S+2iZCgoMuN6wGOWiY/GabaO56Fi68RnRqvA1NZctxXPdjLWPrhasF0XhK1BRQYKucdE1stkukMBbWfw0eNAqdCntrTLHHNt+/tD7iu11+CmHrIHLdIM/IiJoDo1NLoIQNG/b+ReVIG+ERePiXhPx/vN7CKLavcc00HRSz2Sn40KQxOuOtmeyyUBInlYPG+YqYKwLsIIhk6mnUGPjs0G8ffwIxPPNFYU+7P4qaJUQbn4mQRGJJZBMstM4ys1z+l7rYlMgMgiSUDFGPxJXkJyTiNAEQEcbxPTgr7RV13cl/6LLQzHa8LeNe1rDGIkj47+GEfLjCQhe58HVKN6GdMYPAxmWD6Y6CmS5E1pXZ2cw/R939+Oe6ANg1xk7AFCfH00NnyULe0ZwN6qyd6q+PJ/8ccPnhXfovpmeVb8g+rAtvhHr38cFsZHsTq+UXVghxzqUIf6rVb/OJ4VB57ooQ51qEMd6lCHOtShDnWoQx3qUIf6tqqPtQwkr1r0D12zXBWEtRyKWWvsWmZfm7MOs5AUhERJbzpLlbcY7dk0GbemG16/94SvxHXePK4onmnKpzB7CO5xyfnxA/762Qv8wqdeBuD3v/g5/uD8c/xwMeOHiy/zh47+Hv/zne/j//3EJwH4+x/e5+pZRXYpnhnZUkwh00ycb8UkMxiZ4RUPgkG7HWygvhVn0rxCO8iulSyLMC2ydQAVI0MzmT1N2nSXB9qFzOYS4oxs0ruPddgamLeETmOfZczesSzelenn019zrC9zHi9v46eOh/MTjhcbTkoxn6zONuwmOfWdjOWTnGyZ087oj8FVoacrmx0cvXWH+TsbzDJKNbyHTqZQVdsJRSDPCDZ6WswLXGXxmfhttHNDV2hcoejKuP9KpEAS9QqmnspM5IjeHyzRoDTgs4BdZ+TXEm07OV8wffQq+ZM1+npNuFnhrq4IS2FGjJkgpiyxsynl6TGYOJNsDG5eoHcdaCSVZWZZ3xNmxvpByefuHvPF23e4f3LN64sn3M5XzIycgxO7oQ4WFzS1t7TBYPDcdBWXjbAvHq0XbFtL5ww+MgbMSG+vVOh9I9rW4J383S8xYmT4FOnoGab0lYwjAL+11I2hzS3blH4QZ5hdSu5QoPWQwpDlHU/LKZOsZZI1PRNDq8A0k/OoVWCR7ci0I9cdlWmFrRGx0nWX0wXdf9YHzVG2ZW7lPGXacVFNWG4LdpucsJboUhdn9DeAiZKQrjVisqgDJh6DnbV0XhE2g5eAK0NvwKmCeEmMZ/rTzH0ql1wtR2wL/DDhHpQC7fEp6SWM2AeAbyU1xefgGjCZGpgeDIwKn0e2QvJ2SHIS5DW7AeeUsCv0aF/iQsKaGCQiuhvkZT0bwwZo1MBiGrMzumjCaYMkdTyvkIjyEmGZ8LVwtx6tsGfvxO3rEFNXkiQnmgyPCQzp34E9mUu/+U6JREXLmA6ReaF6Xw2GhJTxd934OBI7xjOkz+zJOEYyEfhIX4+QpXUkr4nnthWlGX36ypj6ogNBDclJKiiJ7B15qCinxBvEQO9TksgZKvp4aAiFQ9mAMh4f71EVvUi658yMD3WoQx3qUIc61KE+bvWxloHc+97T8Af/4u/jpiu4asQH4PF6xtWqQimw1hGCYrfL8Imq3Bh02aFNoFtnqMxz62xJYaVzaJzh4mpGOC8on2rKZwHdyMPi5q48XG5f6nj9jQ/5N+//Iv/q9Eu8YGe0wVEHMbhc+o7PNbf428tP8TOPP8nDt8+wl5b8Wj5fXAXaqcLHNA6zk4dtn8n7XQXdZKCWB0MvCwEBIfIb2S+0SF9cORjpeQMhE108RNlJHvYSEXwW8GWAWUc5q/Fe0TyecPQFeWJePOxQLrC5Y+lKcJVidxpobkv3pGctWd5hradpDF1jsXlHVco5OJlsewPEZV3w9MkC+2GOTd4ifpCp2G3A1LKfqflyhRITTi3H203kb1+EIeJVx+PKAvZ0J6aPQeGTUV0dm3MdULnH5E5SA9JD/01O/kxTXCiqJ57qmaM432CuxN8krDbQNpDl8ttaVFEI0DKuEAhtC20HeQanAobsXj5mczdj/UCzvePh/o57pzfcm4qnxZ1yRa47MuXQKmDwTExDGwyrTgCP83rBqi3YOcuuyzDaY5XvwQKtAq031J3FRTNM53Wf1tF5jVEBl+4DryUOsfsIUlWnpDGzoyY1NV61HgwA7SCFwHpUJpGTWUzrMNqTm8EA02rfm5Imr5ibpmTVyDFumkykIlbOg1KBWd4wz3b9MXbexHjYiscXC1yrJYEEpEO1HmUDodHikeAhlLL9/KgWD4vVoOFXpZMEEYBOzApV8hHwkqbRyxVI0aPPyQrG3iFKJBrSPAeRSuhAyEffr16abYkATWaTw/pFViLgkCSDROPE+Fm7A7uK4GQWwYeRT0Hax9TgKk8frQrD/vsxRK0YDCxJcgQBWvqo2V5eMGreUzzs8338CKD5GiAgxffqQPIOSfsglzEuODLUVLtB2qRjkoab+n3piglDzGjaz95Tgz3gQT23vyFJrMbJIUm2oejTafYkIemceETS0Q7fKT1449RgKDuON03nI5mbKvb3PS2WTGmT18XeOgJkAZ1JpLHWIsva1cP4bm4K8PDuH/0TBxnIt6j+/5KBPF8HWch3Vh0kI4c61KE+DvVtGV2qlHoJ+IvAXeTx7M+HEH5SKXUK/E/Aq8DbwL8VQrhUMs33k8DvQyZLfzyE8Ev/oO0cZxvuFddcFzIT/UJ1zfVRxa7LuKorLqJhZKilsTFLQ1gbutKDB3OR83RzjD2WWeBbxyvu3rpmM8u5uVNRPyoonmmydTTOBIpry8Pzl/hTL9/hf3n1B/gD936Zf37yJV7LxDxypuEFW/Njk8/z+ZPP8jde+DS/srzP58/vA7B+c0F2E6NXnRhzijO8rN9uwMfwCp+pPmUgMQq8hXaqKNogjf4O/O65ExPA1rLeYMCb/Qdilyt8rnBFwfZeTnvaoRYty0/GWfbMMn/PMTnv0C5IcsnCsLon53H9kqE+cbjjhsmkxk5lB8rodaBU6E0T50XN/MUn3JyVNNE4rnOaDiiyjrqz1DtpWEOa0E9a/sgI0FlyKmS/0dYBbT1F2fY+JK2TbTSZpd1ZiQdtNZ1TvSkmQCg72hNDvbWsri35dUZxeUR+I2kg5aVDN2JCajdODE87j+6iwaYL6E00FGlaeHpJ2GwJSwE7yusl5dtT5g+O2dzJWd2f8OROxfu3zgDIjndMq4Yyb5nlDUf5lpN8y8JuR+N7S2Vati5j1Rbo6PtgkwGmCnRBx/cCVjm6YGjiOaidxWpP5zW7Qjwx6tb25ygE5LwHRdcaXGNk5rcdnWMvTWbfYI4ZOq0mOCXpH414RuR5R62spHsgE+t1Z9EqsGkztk3Gdpvj4j3ZRzZmXmbNveJJ7qiqeE9ONyyKHcf5lkW2Y5K1bNuMy7Xc25vLCpySOMdkXVDr3iuhyTJmx1tqFXARxKkmTR932bWGrrT4WqM6DY4BUGjjsIsMpeQP01fqq72AjqGT2FlhZox8apIpq49RljaAUcNsefK8iIyD4CJzYTRj75zCKkkMMbUwFPpEk9hP+wjyyUbZB1xyWV4lUCQyMUyKpXXR/0DF2f4umYzus7GU9XtMsP53NPZMyz3vp0Hu5aUINIyv13g5U3bDKkPWgxXeKEltybykvqSPjcfr82aZiiHqlOeOBVCZgEOhR0lHAEqMIEVBSF8/EXzQRoxpfStmrM8DDkrp3mB0Pxp19PfY6NOrka9FBHT06LXEiInHq4xHm9CznPzouINXqEajjhoOJfWteh451KEOdahDHepQ39j6ZspAOuA/DSH8klJqDvyiUup/B34c+D9DCH9aKfUTwE8AfwL4l4A34s+PAH82/v66VTvLu9sTTvMN667oX5+apn94C0GhtScZrSsns5ZBK8LUETaa/NLgb6TxOV9m5Mc1i+mOB7evWM4Lbu5VbJYZ5bmcrvwaqsdQXBW89fAV/tRLL/DXX/s0/9ztNwH4g/Nf4WUrwMX35yXfe+tN/K0v8IsvyD78l0e/ny995QWyC4vPxVhTeTCRrq06yG+kKclW4obvDf2DuCvkp51JQ6M7enACJALVbj1m6zG1k45UCa04NdrjZ/r6rGT5omX5qqU9ljO1eg1caZg80hQ3nvKioXzisCvpjuw2pz61bO8Ybm5l6ImAFInBQqtRW4PuwFUevRiYGCBpDcezLa8fP+WsWOGD4mk96xkFjTPsuozaGTo38KO3TcZuJzOIvpNZV1cbVqvp3sO+LABqZ7Bb1UeAhgy2izjsS48pHPmswVcd9S3NbmvRGzmGbCmmjEGB3VmJCGzoo1OVg3ydElsC5dNj7HWNuRDmRNjs4PEz8vWW7IMJs3em1Gclm9uy/fpkxu4I1lXgfO5h3lJOGs7ma46j3GZiG3TscGyMlkjmliDRornuKI3u0zp80HR2YFYM8gqRZzSjqfXO6/5e2XUZmzaj7QybnXTZSgVcZwgTYayk2E41nmVXQeJXA7jO0OmAU4HEa1cKms4QgPWqxK+tpJwkYCquMygjDZkDbyyrdYx4bQ2baYafKo7zLW8snqAJXMyFTfWmvcu2zmi2GXnVyj5c55gYXaqWlm5mKMqWOo0dr/rkEB0lI20uEamhE7mR84MJ4jgaVHWqb/T7S5GAgahoSkBHYje5Vu8BC2mYjmM55UV6GQYK/LjTrQKNV2RrhW4D2oUe3AMBPl2rhHE1+lgfZxqlFVpDMEoYFpre2FF3US6Vy1eGbpQohvaYGHHs5/tggQwFPUSp6iCjNskhRp9Jywclsb39++P7N53YEbMjqABW2Ab9OrwiuJFzcLoeblhHGJ0MNTqGVGG0j73pZmRUfA3vMDJ/UAGFGsCM0fVDIZHJIZ109pI8+mNMrI9U/TJheM8/97l0zN7gW9gktpPTe+a7ulFfwyL5Dq9v+vPIt0uNo06frwPr4rde/WaMPg8sjEMd6lAfx/qWyUCUUj8F/Jn488+EED5USr0A/K0QwqeUUn8u/v2X4/JfTMt9vXVO3nghfPq//cPMsoYmagOs9mgCtbNs2oxNneO9YreOU5zLDNUoQiHSAec04SInv4yNlYduEuiOO8rTHbNKuo3OaW6WkaXxrKC41OTXYHYi36hPoY4Siexsy2u3n/Hbjj/gR+df5HdXzzjSVb/fv9ps+ctXP8wXbu5xrxJfBBcUy1aoEzdtyVcen1EvC+zTTICMrRLZRCw/9O/SRHdgNwmsEGmF3XmJSWy9SKVbj65jw9t6VN2i6g5CwE9L1q/OuPwuWfHmgSxnNppsrSguoLzwlJcxmjTO4tYLw/ZM0U3BFYMPgG7AbmU/vJH3w4iCrjtojgPdCw3HpytuT9es25xtGxv5NqPrNN5pbObkOgXodplQt0Eo2J00lHYt2nHdMcRJxuayj4uM76WZZ1fItfalJ+QestBHeQIy4x9kRapLP/RyCJlNH3wFsjVky0B1IR1GdV5jn65Qmx1hV4N3qLIkzKTJ9vOS5rigXVh2x5r6WM5TfeLxJzKlX81ryrylyDqyyFTwQe3Hh9qWEFQvu4AB0JhldQ9U5NpJEo7yVKbZW67QHbW3bF3G+XbBsi3691tnKExHFzTOa1o/sC6azuK8ouksTW1jMycgR2+JEMAYj3OaelmgdhL12FP5I5sinVdiUkRPgS8dpuqYTmpOJlteWzzjTrGk1HKOnjRzHu9mvLc85t50SRc0714ds3wqaSLmyuJOO4pZTdda6QW9QkfgzBiPtTLGvBPwxndaxkICUpwa5A9OwApMGJpIr9Bb3ceWqiD37OAzIADEXtypYgRgJGlBBH9icsfz9g/KyVg3u33WBCF+D7jh+9zlimD3l0mNdUoeSQkcEOVWlcQ9++jJkBI4IO5/FgaZhQpxln8EHDQROLB+aMjTmCycgDFG4AMXGU+9J8RY2pFSRUagg7zOIKNwCjrN1yIK6Zx+1GthfzterqsayW1Q7EudUsrJaB+U9SKX6cfsaGNJljJmjiQmSDrWBJCMUePkEzN6TTU6pp2wD8Z6erZTSg4ZR5yataY7a3n33/uJgwzkI+qb8Tzy7SID+c3WAcD4zq4DgHGoQx3qW1HfljKQcSmlXgV+EPh54O7oP/xHCC0T4AHwcPSx9+JrX/fhwDnD+cWCp2bQ8KdGScem0zmNUmDiw6I/bnoZQZZ33JtteFZN2OTS2JSPLPmVIltmNDeWp0cletKR5R2TqQAXOx3YTS3NwlI+1RSXgelDmHwg6/XZjIdHM75070X+5svfzW+/9x7/yunn+NFKDuV78yn/1Z2/z/XZZ/dAjHF99bUVn2vu8X9cfS+PdnO+/Ow2N0+ErWGuDdlKo5zMhCYzvm4yPAhrpyTm0Q6xidK4y3nSrTAxdBsoL1qyZxvmv3ZBthQJxLOmYP3A091p8ZljWxvMtWX6nsxMTx55imvH7P2G8sLgc0Uz1Xta8qCF5aF8gEsoblzfWOjG4wtNfZSxO73Fu7du9Q0S0MdEKg31JLIXOkXWjTT3Shot1YLdSFxr7F/77aeZ4z7qrxO6fjpPPlMEa3CFoZtIlGwffZglBoVo+IMJkLM3C94tVE9/rzuFaRSbpXSg5d2K8qKketqSP92gL5aEpoHHEpeqzz1lUVAuZkxnFd1RQVcZ6mPDbGNKBQAAIABJREFU5k5kmJwU3EwDfhIBlRgzOm6kVObRmRcGUWtQZogWvXW8wnlNZhy5cUyzhllWo2OHepxtyZTjyG7wQeOCeFy0EQ3TymNU4DRb96aYtbc9G2PdFdTectVUPF7P2DYZTRPNbiNzIYEVIShM1RFi05pYNkXZ4pymaawwG1otTWiqVuN8xrLVbLc5tTNcT0tenl4C8F2TR7xYFNwq1tzOV7TBsMh2fCUTuc3T+hS1NjQ67/eH2kT2B7jc4wrdnzOFzPJnedcDLipKSFxnhKikA1r7PUCmKzN8o1F1MmAdGmm7HYwYwwho7IEEpQZWhY6ABQNQgI8eGBPZV7MVYK43CfXC6rC76HfhQu9rkYwbTROXA2jlQMcGncpHDMKDKyOTpIsyByIbowt9Mx+MxJ/ugU7pugW9d5+AyLkE3HCRdREBoDEbIv1OrAI97H+/k62OTDEVfUZGjbqKPyOQI7Fi0m5hQy/REHBJ9QyGHlxKBqIqgFGEOFZVJ99xIYEREdwY2EEjJkUCm9I+QQRh4o4qZL4/LfO8j4wKAtyl9Segw8V/R6BCd3E/opGpt0GArJ3hUF9b36znkUMd6lCHOtShDvWNr286WKGUmgF/FfhjIYQbNeKmhhCCUuNps3+o9f1R4I8CmJNj/GUhVOXxA7MJKCsARghC1U2N02TaEILCe9XPRr94fM3NRDrYR9kJ2ZMMu1IUlwq/znClpc0D9XzgUysbCLcaNpWlXZiY1iFv222guIBsZWjOT/iZ+TF/6+xTPHjxAoA//MrP8UcWD78uUAHwWjbjtWzFv1D9NB+6hl++e583d+J58dNPXudL79wj/zATnbsGsoCLnhaSLBAb7iL0rvcqjJojn5gIivy6ZPphzuKtLeW7VwCc+WOyVc716xndncDs1ob8bsfVHQF1tu+VTB9mzN/vyG86zOOWMjf4PKZ5lJr62NAV0XPDgWl0D5YEo8gvGiZfWoI1dMcT2qOc+ig9YIfeZLOdRTp/Bygx25Rt0AMRsoD8JJkGRDAiNmVpwtI0A2Bjmsi40GKQ2E117zPgqqHZ8bnIcdIMOQx/Kw8oRbCBzgZp9IB2rtje1mzuFJQXGeXFjPy6wV6JxENtdoTtDrXaYFYbzEVG4QOTMmd+ew5Ac5LTzA3NVOMqgzeD7EcOQvbbFXLdi91gvghwflcGRcg9qhCT0bJqmBbCrLgzXVGalqNsRxHjKbYuY9ozLzwzU3Nq1x85TttgaIPhuquYZSecb+Zcb0taZ8hMTHtRAaMDwTruLFa9n0k6hHleS2JPl3GzK9jWOfU269MN0ux52FrareW8tlzPKi4WMhazM8fENNzOV9TeUnvLWbHCnsr2P7suqZ9V0GhULgBDSDPzIMBFp3EmeRREEKMz6Pi9Ya0XAFQPUgYdAQtICSngMoNTwjDpRlKJYA1mExva2MgGHQY2CfRJJD2bZ+ihZZeCAgOh8DircJ1Cd8NYV53C7SJo10W/jOdkBikhSDcCaCgv96KsQKQkaiPb8wa0EiPgtN8qNs8BFdkZA8AY8iCsNRMETNAMppmAajXBenySZyS2wFjCMfpRXsnneymEkvVGZoty0rCPjYP7Q23TiZZjSnsRDCKdSX4Qgf3vEB+ZMRDNUuUi9MkdjVwD4uu96Wzcvg5KPpf5wURzNKbkGBixJNg39xxXUPugx/OyKSMHH5z8H7Bn15HO46H26pv5PPLyg49nuNpvJBt5vg4sjN969RvJSA6si0Md6lDfDvURkQDfuFJKZciDwV8KIfy1+PJ5pFsSfz+Or78PjDmUL8bX9iqE8OdDCJ8JIXzGzGbfvJ0/1KEOdahDHepQvyXqm/08cvvWgclyqEMd6lCHOtQ3ur6ZaSAK+O+AN0MI/83orb8O/LvAn46/f2r0+n+slPofESOr699IHwqRtnwtRn3JBM7lgVAEgom2hEoiO0Mhs6xF2aK1R2vYbXPOr+fcWax4MJP4jfLljvcnx2yvC7JLg6kVZqcwW4XfDrNorgz4mUNVjm7S0d7REKMQVa2xW4Vdio9BcaVQ72RcvnUPgP/6e/5Ffv6NL/NDi3d4vz7hw/qIZVvwykSYF29U5/z+2RcplWaiM161Ez6Z3cBMjBt/dfH3+HNHv4v/Nf8+/CpDbyRWMmnPhZrNEJ1n4wycGmZB+5k9p2jOFLvbhvpoyu1flgWKR0tOtxX5smL1oGD1qiU8WPHSXaHe3xwVXJ4uaI4zJh8apueG/LrFrmRG3mw1kBNODM1C4yqoj8VwM1278sJy3Hn05YrsvWfYmykgMpRgwDUavwVTa4lj1cIaMHo0hRgGKrvL4/vxOqkQJSNjOjYM5qsKVAjR3DRgGmFapJnkxMrQnfiSpJnqFDEbNH1CRPq3z0L/eVdJPGw3UdSnms1Kk60seTRzzVee/KbFrFtJFWla1GoDqzX2SrxMsiJnmmeEqsBXWUya8KjoTRCsxmcGXxi8Vditw5UGn8lJWb1gCQa6ytBNhI2ym1RsCvn8+eIYUziyvKMqGowOWOM4iQafVnsmtuHcLLDakSkff8v9NDENmXKcZSuRk2RbzvMFV3XF85OUuXZ8cvEUqxxbl/NkJ2DjxEpMqQ+a5aTgqq54tpmyjiafbWPF32BrhdJ/Y9nuDO9GH5rOa47LLXfLJe+tj+mC5qTYcBSjTz9x+xlvcYu2seRFh9aetjW4VpoL3xiRFiRDxXifdI3p/Q1asx9XmwweU2qNHvmFJO8Blft+OWcCBItuVM+qCHbECAsxlSOyn5RD2AN+f9Y91EoYP8nUMn2D6wBZHKeV6llIe+M+E/aF7kBbkYrhh/GOFvZSilPW0WOm/16JcoQkjxBGwyCx8I18B3k7zPwrPcg8lAOfa0JIbIO43+PYED+sr5d2jM9Bp3vGhRhuRunMmMERVM+6UCAMiLHBpY/fGSFKvTRDClEnn0/LBB1QYz+VENkUbtjtkZesnHeUMDIiy2/P76K/JsLECVkYDIh6+sdwHD1zRY3eZ8RoUUGUKCOCSrrWIdsfs9/J9a14HjnUoQ51qEMd6lDf+PqmGWwqpf5p4KeBv89A5P2TiE70rwAvA+8gUWEX8WHizwA/hkSF/ZEQwi/8RtsoH7wUXvkP/rg8BMeHdl8EXB4fQHUQ2vTIVE1Pu97XottZcAo7bblzKkDA7WrNtstYtTlPLue0qxw6ha41upanQVMDQeEK8TgI846sarExXjOzDuc123VOuCjIrxXFM9VLINqZojkKdLNAdqMxO1lnknF000D4xIbbJ0t++9lDPjP7Kr938jYv2oFJ8tit+bMX/yR/+8nrvPfsmGaZo1dyElSr0PFhOgzP/n2MKYwebAO4iScUHr02HH1Z3jj5YkP+bIPqPG5esHp5wuUbmt2npAF8MYIWl5uK1fmM4twy/SBQPY0+BFcdygW6yrC5a8WEczbIU3z00picK8pnnuqZLL+9nfXvJxDBW4XLFc1cJCVjnX1qxrylTzfofStSozZiYfevQ39idC3xselz/foTO97J+fKZvJZo8+G5JkIFWSZFzLbz6BtghW6uo0GnGY0ju4H8JpBtAtnSCXhxvUNfS/xpWC4JTUtwDqUUaC2+F6mMkdezDGU0oWlRRYGaCCDiby0IxuArS7vI6SaadqLoikGq0lXgC2inAV94ofPHdBdtJP1AaU+WOYzxZMbho8nm8WTLSbHhtekzKtMy0Q2X3YT3t8e96e2qLQhBUdmWV2fPyJTDB8XD7YnsY1DcKtZUpsXgqX3G02bK5U70PhfbCdsmY7MqCDuD2u0TwsK8Iys7jhcbrm4mhKCYRDNOgE8unnK+m/NsO6G0HYXpqJ1l18n9styWbJYFoTHiPxLHSGp4+7GgR+BC6g5750sG48b0khU5GkT1wjLDrGKzrePYSFKRIGa2emziOupAdUcvV3BFNMAcjVXxkNgfs0n6NW5ikxRMdcnXZvSdEO8du4n+Fk7uvQQEJ7DQ26+9R2AkS0hGovGnv/eCLJPkVETfi30X0pH8I20jAQnJyyLwdZt4udeVJMH44e0wSl0JZvh3iJGyfYqJU9CpvUQW/GgbQQCZwWtkODcgYFAwETBPUpCxoSfsATKAROwqBlAm+WA41X93jH019sZd+nsshUkmobOOd3/8Pz8YbPKteR75uBpsfiPqIA/5zquDRORQhzrUb6a+LQ02Qwg/w9coifv6Zz9i+QD8R7+pbWhJoBiDFf1DaWxUn5+d86s4jRiz7VWjcTvNB7U0Ts1tywvzG+5ObrhVbbjaVTTOsG0y1hsxPayXGWZl0LUiWypca2lnmq6KDd68ZlI0zMqa5aRhdyunOSoon8gTbXEJi3cDygXspiUZRfYxFEqx/ULF5mTK/3b/Ln/j/vfzl1465586ewuAf/3ol/hUlvMTZ5/jR6a/zs/efp3PXb3IW5enAKyuJoTLDLNVmFZM+JST2dK+t4pJAroLdBPD7kzTzQLLV+Lpygpm71vmv77EfnjJ8YeXTB+ecvFYGsj3vv8ORy9f8z23z/Fnj3n2ySkPn5xw9Ug69cmHBZPzQHHtyJce5TXNRvV+Dl0F3TywfCWwvq8ornLyq9DPyqIEqNBdZDYYmbFNQEC6hMDgSRFnqnsNfhoHo/GQ1p0+hwqoUmajJZZ0tP5kUZJ8KeJ6EqtBMfKPCFEbr8DEtBHdiudGN5V98zZADt0sNrBeGhCzU5haY7cau87I1hXVU2GYVI826JsterkmNA2haWWaOE4Fh7aTQ6xrGT/awHaL7iKF5WaJVgoNZIs5ocwJZUEoorfIJKebGFylqeeGZm7wObQzuaHGjWybBxoNGxPE1A+4nh7xzrzjgztH3JmueGP2GIfGasemE+bD1a6i6Qy5dVw3JVoFKttyU8vFbL3m8WbOothxWqw5zrbcK284ygRsyIxj1RRcas82z2htLv4TKep3aWlrw9PaEqKp4M3Osl7L+s+qFXfLJbOsxsYL2QVNFwGXZV7zSM/Z7jK62ooBb6f2UiKEMRDv0z62dNQhpgYxzaQHRXChT67RpUNPW1zIMJsR2NI30RIvG9oENoYYhxrHWmROqU78JnrTy7gqb6OvihnACRXHaz/eo5eNMkAGLo7ZcYUYkawc2Fb8X/pElJgconwkA4zWK3/EcxVUz+4ae8X0hpZdOldyHMP7A7KiULHZH7aDV3v3YQKk+1QMhvd634xADwz1FZv73gxzlOoiAPcostQDhhHDRRglqhuhMGEEXhDHSKvEKHUEcgCDT0ZKlxnvNB/xzzFQ0Z9f+XwyHfaR8dUDJh781O0nlHyH17fieeRQhzrUoQ51qEN94+vj6QiVSgfao5QbOXrY65TMzlk/NAPpwbBNFGGZzVIBmalt5Gn2qV/gA+hFoDQtL87qPiVg5+R0XTcVj27mrC4n6GuLqRXqysB1jP28zliXHl11KCPmnu5WzTaLrIHMShzoTaCbauq5FvlCMujcBcoLx+S8ZfGOYXec8+Gdl/kfbsuszV/77k/zB177PH/i1t/jxyY1P1R8lrcXv8wX7j4A4LPLT/BLT17k6dWMdpWhtiJnsauRCWgEAfJloLz05Nea3Zlmd1uOdfWKpz42tNMjZu9XlF99hv3SQ+4+EuPH6uk9nvzAKV/4bYbvv/sBv+vOV2jPDB9+4giAX7u8w6MPTqi+mlM9CRRXgcWFQ0VApqs021PN9o6inQfqUwGPslVkwLiYYuIDduul8c8VKqg+erSbRtlPGsWxYeQ5ttDerPOYke2G374QcEGMB+Pw6oamVDn2GyXou8FkihhiIzdEzAZMIyam3qZISNkWIEZ8NuAnEs6AB9VqdAvZjRxUebEgW83Jlx679ditQzce3cQI2kZACdU6aFoocvltYxTv5Q2h6/DLFeoiaXDUkKiYWbKiQFnLbDGlO5ngC8PuVMaqy1WMnFUxxhKCHsaRzzTNouDpk9uc3zrm4a1jJkUjzKJ4T908m/ZxpanpCqXbuzdVrQmlo1jUvHByw93Jsjf5LE2LLgKF6ViVOTdFyW6bCzMKYCez6GFrhmSIncZFWdZbl7d48eiaRbbDB8Wmy9HK42N3N81qzmaaTZGxazLq1vbSE78bUZES8Bln9UMaczA0n2MWQ6fjhRX2iJm26HmLU5mAIGOWvoZQOoLRqEzhYyMa2oECpVsBnXQDqtnr7TEKXFo2gXfPORIJOMAeiDDG8HqWUh5/ImiiuoHFEbyADTqBg4qBeTFmOigVwZEBQE7msIOsREXAYfTvEbDhne5lGiDHq5sEnIiRpUL1EcXpc2lflAq9EShj0DoocFGi0qgB4E4bUaNxmYCXJPtJ/4/05pxqMEXttx8ZIkoPcpeegRH6c6QiICZyvWEde5GkXvXAxHAhI2gUX9ed6o+3P/gAevnx/u/9UB+fGpt0HlgW3xn1Gxlz/mbqwNA41KEO9Q+qj/fTjAmYoxalQi/BCAHa2uKjHr13908+Bc4MLvyJ6uuHCEB9bbk0c5rOMitrjoods6ymNF3vKWFnnovZlIdHx7x3dcz6skKtDXYtT9X5lYZg8JnFF4GuDEL3joBKfafjqjDYtaGrAt2Rl+atje71G83kw4zqcaBYeqqLjuoCuneiD8EHJ/z3n/xRHv3OBf/G6S/wRhb4VNbxg7lIan939RafO77HF7YP+PXNbc53c27qkqttySbONrtlhqo15VNN+SSQrwKTR4PefHvfs3uho5sYVi8WLO7c4/jNCTx8BMDi73Zky3s8vTniZ9+Y8u6rJ/zQ2UN+aP4uAD+yeIuv3rvN33nwCd5/75TJr+fM34XyInqHXHZkK02+NOxONe0c7BbKywhWdKFvqFQXyLYes/N0U0MT00HW9wW4cFMv+vhO9Q1EXyoQ8gDRPyD4UZPY6shuCHinUF3A1KofCz6yJlLsqVDn2WtMOjvMvOs4E50aC3ktkHmFN2DaCFpECYYvAq4Q6QVZgDzAtCPoQH0mx7h7IFGYZqfRjUY3WZ9iAhHg8mDagG4EEDG7YUZ++miBrh3ZxQa1a2CzJWy2vZTErUcpH++DKUtsVZHfFpZOmBT43OAmmURXZppg6BvYnpnzyLC5m7M+PeF6GgSIiKyC6okmW0pKjgBQ0E0s7XS4TsVVwBWG+jjnnRcmvH/rmOPFBkCkG1G+cctumGQty6JgE8GQ9aokdJqQPCdig5fq8umcurXcmm3QKrCsc4we4l1L25EZxyKvWeR1LxGpW/kBJFVIBbxXfeTq2JOjbQ1tjGwNnSYECKMYU7UVb5F80tAtAm5nxeOmj/sgxnSGvqENThHyOFZyGZ+6URKD2sWeeiBm7KeJJBBg1HsDeKP2WEhjjUj/NalCZGog4K5PYIX83culDDImRjKRvhH3kcmRDR4vyo7kLKPjHqQsw/YhJpHkCl8kdslwfMqBTwiA2U9V2fOISJtJ7/fbFhBDeWJMaQKiBm+f9Bm1x4CgBx56X4xRmkgYAStEf5Ik+0nnMKSIZi8xsyE8t/7RdcXtX+d+keQbohPiTv+9JwC8wa4PzIpDHepQhzrUoQ718a5vmmfFt6LKTz4IL/6p/xCtA1kPViic0zIzmmZzxzOMnZJGytNHIkqU3xCPGSz40qOqjmLSkmcdVd7y8kJ8Gu4UKwrd4tBctxUfrI94vJpxE40Tw3WOWWl5ME+znIr+odvPO7JZ0+/3UbVjmjV08Um37ixPb6bUlyXZhaW4UBRXoWcdmCbQlYrL71GE19d83/0P+ZGTt/k9sy8A8JKpKZVGK8W581z5nI0vuPElj7pjAB63C3Y+41eu7/OFD+7BVydM3x8aj+3dQDcT/wKsR99YFm9pTt+UKfXinQtU53BnC9YvT7l+zbD6hOPsVQF0PnPnIa9VTwB4Z3vGz5+/wtP3jikfyQbyS6ieerKtgBL1XBMslJexEXTS1DdzRX4TyJeO/LJBdx5XyjpWLxZszzTbO4F2EQGhUZPRz2Jaj44Gq8ErgkuOnCPgIgjlXzUDWJGMDpO2X0W2x5g635X0TY9upRkbS0mkmYnX3cZGMU7W+xg56oqA///Ye7efS9b8vuvznKpqnd5T9+7eu/ee7dlj7ImPSRwlMQEr4oaTQHABSLnjJhdISCAigZC4yR8BEiCRO+AGgSUEUjhGDiYOcaLYTpyxxzOzz927u9/TOlbVc+Di9zxV9bbt2InMMJ2pn7S1u/tda9XpqfXW7/v7HqrsYVBHcBHTyEasy8wJRY7bhRQ1sciaOiNNbwLVqyE+0mRfh8ULYYtU20S1i7htwG17zE6uo/7qhnBzS2pbpqWcSDj0eoVqatJmBUaTnCFVI8apTh6MItaW43sLuo2mPdfS6OavluY6sXjlqW46MQeNibBw+FX2WImJ6vWRWFu6i4rde5bjU8XpsVyceNlTrzpWi5aLcq9ETZuZTvenhtYbYhSQoO8sIQh4AZD2FlwSZoOOhN6g9Ag2GBtYNh1nTcvSdegsyeijGcxYS8Wk0EqADk0a4lmP3rHvKjpvOHWOGBV9ZwfZmdlrwjLiLlrqpqdtLf3RQfv7JAiYNDKFYIz49Ap90rJGJ9P2wjpQiWzyOAICUwZR+fdkx++l8UMYgAzlMwviDSNJ3YFp0yB/ipUAIENpBPwNIpeI1QTMyOyB0nyrxMP7wYz7UfYzOvCLkdlRQBnS6CETH8i0hAEjZqAUatR4iIWloKbbSxPmw+T/xTdj8ity8J9ptXjPJB6wrqaMlqksY3p+opX7HSXXbHjPPyw1M6mHsrcikYFhHZTfYQD6JMf5O//xX5o9K75P9cPsWfGHqZlxMdc/bs3si7nmevvrB9Kz4vtRKSn8yYFK9G1ufEwanfl7/bBxBXnwdBFVSZNczM1ifo/qFOao0SdDPGjaneNkI/dV5P4grITz1ZFHiwMX1ZHH9Y7Hlzv2ZzWvHq0A+GJ3zvVuyelQCTU9T8aL/p1eU1WBzWLQC3Df1Zj8NGp15BvvvCY80tycFtzcrdjdVVSv5el6+aVm8Try+NcSx89X/ObTH+PvPPmI//b9PwXAzz39jD97/h1+fvEdrnTiynYYepw6AAImBBLnesHNo1/h/3n/nP/qw1/gb/72R1Qfi0bBnBTaa/wqEc4SvNNyV1f0a/n5xeUTNt/eYl7ecfbils23Vpze33D7o48B+KvfeIz9kR0/++wLfmrzJf/W13+VV+9v+J2d/PzjuytefHFO/aWjvgYUdBdwfJwZMdmNv71KuL3G7jRnH2ua1x53L4312XcDi9cVh2vL/pnm9EgRlnFw5h+o1K0hDmDUKIHA5HWR/5MGLpHKJDiDGdpL86aimGOmiYFnLGaHEYxRxJy0IO8vIEaeSPtEUgrV5eark8mqOUl6gBgYamKVCAv5kHZlUS5ia4+xEee8eDlO0ieObYXWkRg11gZC0PjMLNqe1aig0CeFOWjs0WBOFW4n3iPLl+dUdx637dD3R9ThRDq14oEBoBXJB9SxBSNMgbSohbIPqN5DGzH7E8uYqDYV7uBG7xGgug/Ur46YV/cUiY6uHMVGQJ06UtdhnMN+VVG/WtM+b9g/kXNweLemu6i4Pl+wvThxdXZgVXWD/8Sj5Z4+GlxGkXZdzbG3nDLz4uAlqSfcV5LKAQ8aVJ8svrf0wdAvNEvX43RAk6itdP5lW1YHumixKqBVYuPkPB2DY+UqMed1FSFq2spyzCkivmvQraY/OOqmx7lAihpfpvetkYSJiYwCNUooVBXRLhCDIjaK6DMroxxP9t+Ra5JlAOX/GbzQmXlk2gkLY4IzFCYGmRUR3gAPyGBFMgKAmT49BCrKy3PCjkpynqeGt8lONzjxAWECjGTQRJhKI/gYnQB7aYLvFLBlkIKlNIIFJjOM0uSFjNvIOyvbmxzjAAq4wj6ZHJyLkBQpJZHoZE+LBzUFOKbSGMjMDvmHZBOxvDa98Z7Je1NOTEnTn1cRVdI+oiJFRZyA8roz9GdvGJLMNddcc80111xzvWWl/+CXzDXXXHPNNddcc80111xzzTXXXHN9/+qtloHUH34tvfcf/XvZ2C5Pq634E4Dodn+XORkQlwFVy+gvFSnI8EOFubOYEwyO9YbstJ+3sQjYpWexbHm62fHh6obH9Y4mjxD7aNiFmutuxYvjhtf7Jbt9Q3/Is+ROo1YebWVKF3stKQa28HlhdXHkanXA6EhKij5qrrfC3Di9XlB/Zdh8D9y+SAygvRDs6fBuonvW87UPXvPTV1/yQX3DxpxY6pZ3rES0PjI7vumOPDbymZ/5Hf/j7pv8p9/68wDsPzmjvtYix2gS/XkkVRF9kDFi/Uqz+TSx/ryj/nILr2/heEJdicykf/+Kux9dcPvjoH58x5//+rf52fVnPDISyRnQfNZd8at3H/KbL59yPFY8vtgN1PzOG9recbU6cOwdx85x/HhD85Vm/bm8ZvNJizn0JKvZv9+we9/QXUC/yUkZeWppWoU5jWkKZQobmpQ19UnMLnWmfU/p2MWB32f9eS+fJSc9075dGibZIhXJ/iddloX4MiHOU/I4fnTMBpbDGiySoSrvYyVrTyQjSWQiJg1TVe0isdfoSqaoCtAmDucxZkPBlBQxyP+JakjNsLcG04E5qiFGtdpFmtc52aYL6C5k6n7+TPdwlKw7jz72oBRxWdFd1jkJITOFDgFzyMwNH0ApkjXC1gBS16GaWlwbY752dUU8F/bH4f0FxytDe6k4PUr0VwF33tIsxHfj6WaHJnHZHAYGxME7XhzEDPb+2LC/b+SYFb87SrIXI0TVBJplR+08zgpzosoyD2fCkGJy9G6QgKwzs6ILhpg0p2AH+UiImj6Pu59/eYk6GpJKmIuOqvKkpOiySWjcuwdrrEg2hrSPZUAvvUTJ6kgMhhTB5O+MlBTBa/Fl8fKm1Ofvtje8F8xe57U5ei7IGxim+sUY802jTkkVUqPcSY3yErKsQw8SEknmKete92LYOkDkRQYy9XkozAqKwzS3AAAgAElEQVSVDW6nMhEH3VmWS5ksIzEpm3wWxgwPYk1VNya6jMeQpSIT79SpxKLsTKrSaJBZoq+rIDG0nYajEenFxPNo9P3In5eZGVPfi2REBpKcSOyIck9OfTeKb8aw7akPhwJlIjqzPACin8jbADqNOev47l/4T2YZyPepZhnIH03NcpG5/qjq95OP/KOYg84SlLnm+qOpH1oZCAn0SRrqQu6PBlKbXeTJ2t6s9R4qKpJXKIs8BIbxAVq5QFxqVNKjWV2UBqIYlsWDJm4t26Ziv214fb7k2dk97y0ECHin2vJudc/Xm9dcL1fcnze8atd8tpNG/rMXl6STIfZOHmQ7hekUkikozcPxzvLp2QKz8NR1z6rpOF9JlON60bJ7VHO9XrH4StO8SjS3keZ7Apasv9S0v+O4efIef/XJU8JafBDQCbeQ16wWHe+f3/Gz55/zz5/9Bn+yNvzF80/hm38NgP96+Wf49HuPab50uJ3CtAa/1IP+vD9L3H+kOL5Ts3i/Yv35huY7r0i3cg7s9S2Pv7hk8+kjbj5d87/85B/nlz/8iJ945wUAP3v2OT+9+Iw/vviE54/P+aK75MrueMduAdjGho/bx0QUTgWcCnz7/Sd8ur/gd56/A8D9P1iy/rRmcR1QEaq7lNMg8nWy0jzYncLtyP4TaTD8840aDAAH4KKOpKJP0GloFFKdUCZlT4Tc5QQ1pk8kxKQzG3WC6NFNK34XJss9pjGHKj1sQGSnQcfRQNMqxJQvxzxGowfwDATUUIpBA59STkjJYIZqAkoltEm42lNXnsoGfG5s+ic5wjRoTlFxv69Qe0P9OsuBjmJ8ato0SGHUG8dgj4n61mO3HaqPmDb/sIAzCcK6IjYO1UviBQp0ji5FKbqLGpXAHDz2eo86nDD7vN7vlywul3QXNcfHhuMjy+mxYb8Rj5hP3nNUlaeLhkoHHjV7LqsjPnejjfVc68hu3xCjGo1Wx1MOXpGOhlOsaa1D64Q2cfDCcUbOY+38YLpZ2cC9yYa1SQ0yLq0SRke0CawrATMOVxW7XUPaOWJniDbIZ+dT0IGYAmephOp0Xkv5FJ0M0STUIqAVY9JFMQ4GtM2Wk1VA6UiqMmhRALe8f2Gt8Qcr352RwZhRh5w0MjWrnDbeJY40L/9iljk06nEEK0qspryuSPEEcCjeE5S1VHAGL+sp5bQalaTz130aPic6hScJPh2R1+YYasjggGaUdiX9QOqChgcA/SQSFBiTXgpAYOS+NwUM1BFjEsYqepMkLSaoQd6ivBoAixJ3SzmWXFMwQ1VRvFXSJPllyJ4t3z8CTgzvUbJ+YzaVVYohIrccoznrmGuuueaaa6655nrb661nVjz7S//+gwfjog+OdSJWEbUUO/XUD/l4Ak6oJEabBawok7M8qaY1Y7oEQFTYQ3mYzA1YnnqnRcBtOtYr8aA4X5x41Oz5YHmLU4Gl6QhJc9vLpPj/fv4j3N6tiPtsf5+146VJMAedteEy9Y+NTNTNSoCG5bJlVXd03nC/XRJe1TQvDYsXsrP1XcKe5OG2W2mikzQKFRliP2Ol6FfQnSfMN3b8q//Ub/AXH/11TD7gv3H6EX7x5Z/gV7/3IXxV47Z5n3IjH11OsnDkqE3N+tPE5nPZx/r5DnWUB+Z4seL0pGH7vuX4RI7x+Mzz7kev+XNPvss3Fi9xKnDjV3xQiadGnwyfdVf8zuEd3qvveK+645v1F5zpE6+jsEH+h9d/il/6+Bv4T1Y0LzVuD+aUaC8yWFHJOTQnabjLtRtKjY1DaCA4CMs0NBjR5ebMRahFI65tHBrdFJSsqxJZWZqmofnSMiXvFaYDu8/Mi9KAesRwr0RKliUcx9eMMYUPCR+xXAczHufwM5t9NICwyNPxfAx24XGVHxrXpupZOEnbOK+OnIKjDZaXOznHbevoW0tqzRAJPDUh1fm4muvE6kVg8cVRfC7U2DyFhaW7sDJVf1OXD3QrRb+Wab3bJZZfeZqXR8xrAa7SdgfGoBYN8XxFf7Xg+E7FKV/n/Qfgl4lw4UHB+urAs7P74Rj7aAhRc+glljQmhfdm/Hln8W0xKlXjeDz7RIAwGFJSaCPXPyaFmTBYAKyNGB2pbKCyHqcjjZX7QavEzWnBq5sNKSmaRTcAIACdt8SosomqwneG2BtUZjKpTpHqBOsebSW9Jk1BBQS0kj8ktIuSdjLZvwK8KAWnYyUGxDAmp3iFvTfCAkrjmhqYFWrC9HBpADqKJ4UKDAabKqrBhPOBJ0X5jMQQTzwYRHoGM9qRYaEwxePFCMAYFvLdqwowZ6fsi5yuoxHWQmbOqSmYUSoJCK378TzGSsABAbllH3ARW4/+D9rEwRumOzpSZ0b2ShiBcUlEmTAkYDRNVQL+qGUYgbNJYsnwniH6NEmyVdn1Xj+Iy33AyjCJ6vJE97rhk3/nP5yZFd+nmpkVf/Q1syzm+kGrmWkx11z/ePXDy6zQibgMlCQHENBCdyNwoWzEmDga6wExN16SDII80JYnZgumDlBFUsxTyQTJa7zJjUN+wE3ZhV7tDL2vudkKd//GbvikuuJ3No/ZNC2PFzvWrmVtpXl/vDxgdKJdj6e/soHWy+cfDjXdbYXZG1RUmL2CvSbus2niwnHc9Dy5uufq6Wt2lxV3zxZcvxIwpHppaF5Z6tuEbRO2jQ/p3rmSloSKw+cb/ruXf4bf/ukn/NvP/i8Afqb+nHfeveej1U/x9z54j9/68gn9iwXmWM5z/owmkK48/Xvw6pnj/qV00csXVzSvI4uXPe6+Y/m9e5ovLDEbR3YXFXdff8ovfvQE3j+yXLbsbpcD88OYiO8N/U2NvehYLFv+pR/5Tf7p9bf5uVriU7/+9H/l588+4q997cf5m5/+CKdPVqw/1lTbIleQaNNooT2Xxr3IQoAhAlR3Oe6zlXjRsSETRkO0ilAbMb6spzmFhSkhDUOq4jCJBfl7AghC0fcLje7G5s4eVZ4OC1gRLQKsKUkgKZsoKQwlnWSSxDuAcyonz+TbYrgHTCfMj6QMsTbEynIqkhfg0ARMHajrntPKsnQ9C9vz7EwYMiFpScYIhpCkSZ9Gep6OFd3O0Z0bQm2xhxq790SnCbWcyO7ccnyk6DdqTGCAQY7TnQmwqDsBBNtLx+KRYflckLXFdxTp5o6426Oub6lfLKi+2LA+F2bF8tWSbqVpL+X+Oz2q+K13V7hzYTVYG1kvWpaux+iIDwY/MSi1NnDSDq8c6WBQSUOAlDSxxNhWcqGUzkaHQfGAgGASvY1onfB1jw8aZ8OQJvLu6p5KB4xKtN5iTcCHETCxmUrjg8bohG80PmqOtZyDcOckAtNrQjGinMbwMsHhNISoiLnRHZrcfP2qymNsINU8aICj1wSvxPjVj/f5VOah8ufLwszrcCLrKNtPCJD5IBo1r99k5bXJ5I8J4/uGONDpLTaRa9hTQnuFqTKLo1eERhI2IIMSSouEpbAq0mTfYLiBVMz/TcAKVWQpRg2gSwpqMEItLIdUPwSHyNtPaDlH6cGhP6y8PyoqUvsGQPbmxZyCENPPiKB6PZqkTr+WjKLbVlTXv0/SzFxzzTXXXHPNNddbUm83s+KjD9J7f/nfBRiZE71E+wEytW6yBGJaeSKlCuCQteKQJ2pVxFQx0+cjKUGMWhz4EeCipIhI2sTDDEChRyviIpLqiF31NIuOy6WM942O1Mazdi1WRyrtpYnJUYw7X/N8t+H6bkU4WWg1+jgmm6iQY1Aft1xc7LlYnHA6cOilWbveL9nfLjCvHdWdwh7HqeYwxezFKd8dEipAe67Yfh2e/gmRafwbX/vb/MLyt3Aq0ifNLx9+jP/5q5/i2y8kzcN/tUC3Ws7vWU+z6ljUHW2fExjuG9SNo/lKs3yRWD33VHc95k7YJ3p3IC0bTs82HJ46+pVi+SoSskQj1NK0VPvI8UoTGsXdNwPNe3v+7AcfA/Bzm084N3vuwoq/vf2Qv/Xl12i/dc7mO+N1CBW0l4ruIuGfdHKdWrmO5qAxB4UrjIfu4XQ3ujFWsFDYY80kWUaNPiY6sxhcjlAFiSDN6yghzWDqdAbHineAGlIQkmFoAMtqKvGSJUZymFKXaWz5exop5+X6ygcwTLuHps+MjU10wiSJVSIuJK7X1p66zp4VKlHZgNERq+MQ69nlqIhj52hPDt8ZzPOai29BcxvxjaJfyT52Z4ruPNFvZBvDfpfbNDOGSgyrPmncVlG/lvdffMez+GyPeXlLvLsXXwutUbWsd7VakpYN/nJJ0kriT983HJ7K+/uzSHjUs7k8YDNIYXSiDPErG2h7y+FU0e5qATELQ6ZciJIKMZ2Qx8mFKqkyCszCY0ykqjxVThN5vDywsP2QJuKj5uV+PeyD0RKH2geNy4wNrRKnDArd3q2Ih5IBWrY5Xn+JOp3szyTt5IEMwitUHdBOGCIqb7dUv61E3pQbeHPU45rzo89FWUclyhMYY0FzFbnHA8ZQn9+nofhdPABDSiNfQDk9YRnFLEcqXjFGYlH9cmQaJZO9XQorSj1kJCWTMsgsxzgcU9mEkXMXqyJBye+zD99f0jhSYeOU74SohkhkNWHNvbluBmChRNJOr1eCaVTpm2DLIDMpQIaaADX5M5UX4O+3/vJ/MDMrvk81Myv+v6+ZaTHXD1rNTIu55vrD1Q8vsyJlkEJPHpiTGuIidQ/q1g5aa5CH2WgQQ71eJudTin0ymmgTsU4kG8GlQS+ss5mdrjwkRci6/6itNAHloTc/5GqvSZkRsasq9msRqK82Jx6tDpxVJ6yKOBWJKGojT+Ur2/Go3vNFc86ur7g7LIS2fZSD0FuLaRXhZc31ybI7azhbnVhVMp19Z7PjfHlke1Wz3zUcD1Yeou2kkwgK5TVmq1m8VFS3ic334Kv0FID/YvfP8r2PHvMvn/9dfq6+5Zvn3+JPL77Df38h8aj/U/2T7D/boDtF3DpOSeFs4PF6L+dkdWB7WXP/eMHpSc3+fUd942heyzlYf7HA3hypXx6oXmvRWb/egs+dS+UgRpKzrFcNyRk2ny04Xm34lWc/A8D/+ewnWD7d885mT2N73j3b8t0PK073mWFyKx4VZfroGon/TGvZRL+2dEeLPxjMSWG34m1hSmpnlwZAqpj9CYAxmXZaaZiShtAqafqdrIuwjIQQ0HVAm0S96EnNeAn6laVvxwZ9MBhMSmIsQZqeSdMp7AsGXwylhU2U1AREyftV3k/phbKcJE2kKOZUmCRK1r4zhKrm0BQQJjdnmsH3RE1YSkWKoF3EnwfuPzKc7g2hgX6dQZ8qkrJcSldB2AlhbKyVEoPAQrmPTaBdabpsGNuvLcunZ6yer1h8eYG+O6B2B1Ir6z18/iUYg100qKahWjQ0X51xfCbMi927hsO+Ztsa1NJjK8+i6Yf+r7KBRdVjTeRgI31viEETsifAgyqdd4k9LpVNEgGCssQsF/FW7tnnwbCsO87rE1oljt5x6u3gJ1GiaH3QdDphdcSaiMvRp4tlxxEGj4TiZVBiM7ULWaYizI/BB6GsJ2R/ldcCvhavCzUaByvF4KGQalmL3iZ0Bvd0L0257hWqz8DZVCoHo88PE0BiOHeTn2dQAfNGk10a9QlYUT5P97KuTcsAHJUI1rKeUwRQ4tM69U4eVIByj6pUWCPqAWNBB3mNbuWmKR4UlM/X8v6UGRcDGPgmaNVrUqGiTJeQkvOcYjnO8XfGFJxQQU2+D94AXArbRI2Xdow4lXJbRX/29g4i5pprrrnmmmuuueBtByuCQu/Nw+meFwpzSTcorILBdNGJxj9UoxFaeeiF8vCnMi1fiyFcnj6HRjrItPSDCz+AqgIJMzw4BisNq+5kG7pV6M4Q8oPprtd0neX+JBTv2kkyQJn6rquWi/rIZXPgvD6ydh3HleOYWQu3yyX9fYXZGsydoT8ueNXUXC/kibpe9DRVz7LqqS8Cp5VcZmcC1pTJciREzam3bC82LD+x1DcCWAAcd2f84vWf5Ne/8Yx/8d2/x7+2+TV+ttIsL/+G7EO/4H87/DHiqxqz16TWcR9Wg3HjZtGyrDvsZWTXeNp3LMe9RWfA5e71gsVXDW6XqO8ibueJjcNsT+N5bT3+aoUKEZVg+fGe5adw+Q9GKcn+3TNePj3n+F5g/eE971xuef51OU/9K0t1L428PShOdzV+6amz1KRqPLEK+KWhb0XGECuNy0aq5sS4rqI0OLofJ6BJMzQsZYocu1FGoltNrDVhYfB1JK0UxgaqSq5TVXnCUtP3Bn9yxC7r6zstzRIMjeEw0S4Nfpk2l+l0nExaJ6EApWFMhmE/0UNfPX6GL3IYhdU8AGSGzyqAnx0bo6QgOfAbkcB0Tz3dIyV+D1nnn4IWA8CUm+3CCHjQRIupYrIJmijGoHk9H1eG0xPL/n3L4qszFq/WNK89rrB0PvmKdDoRjye00tB2mN2B9d05AG57jj1VHI6W9sLQn1n82qILKyYp1k1LYz2rqiNEkb4ce0vnZa15b6TJD5qUxNTR92ZI3iAiIEL2xElR42EANLvWcjw5tpWgVX1v8J0ZgB+tkiS2RDFMVCo9WCvOBFh2HKlIXg8AapFxWCspFbqAHl4Al3J8AFFrWc5TsMlr0pTZkI0lbR2EDeS0gLFAqhSxV+gWtGNIvnlTXlaAsd+LtFcMWjOmkNdjGtZXzjYZG/UJaU13EBoBK8qaH/xfJsdgQr4XJu8dvCqSSKwSPNxG2cGYAcAAiYeAAZPX4DPqUHyPSpU/F3PMAsqVlwxART5Hxag2qgEQGfw+4gjkPFCJmOxDU/wweAjqqJTvyfPRZ2Ouuf5JqL/y4S8Nf55ZFnP9INQ/LFlkZl3MNdcfTb3VYIVKErmYjHqoD548tKmQ5OF2oPZDbMHaMlmU5mvQ0qvxwXdIYch041jLe/zS0DdxeL1QsHkQPZqqSPAqU6r1IA0BUAdD1zd0d5lyXqak+f22DmzWR86admBbLF3HRSMykrPmxKvlinu9Rh0NutWokybdy0GcmopjE9BNwFWeGCTSsFcGnR+maycmi84E3MWJo1+QzGjSuf48Ud85Pn/+Pv/ZB0/4lR/7iL/w9G/yk5X4RfyZzXf53ruP+O3+KamrJBr0peOQp7DHZYOtxczROU/lPGkNtZWT237dcHO7JB0M7tbgdpZQg9sKK0L38p9fgz0Ik2D5MlJtA9WNUB+W37tj8YWle7Tg/sOKm/6c9I17Lt8Vv4XdWc3+pqG6NmIC+dzil4bTRWaoLD1aS1OodCIAba0Jtfzc7cc0hlArdJFrlCltBgBiNTK5CwtDrrOCA8S9ISw0/mgIjYAjIGCJczmdo/ZDQ9y3lnDMDeLOYJIixUmD1vOgeZKUE/HfSLkBnDKJyjpHCzMjmQTu4c8LIFISP3QYP7tEvpYEB2kyC2oi91TXGvwqEi49eumliS5Ncq9QrUGfsplhAV0GgDE3kDoRagi9JtVxkNOYhUcve7orzempY3drqG9q6huRgZxdNrhdj3m9A6VQ+yPpdIKcTFOnBOkCe3Ic3tF0O0u/NuJhAuwWlu7cslq0nC9OrKsWrRIrZ/D5Yh96R4h6AOOMTnTe0GWZhu+NmF4WKUaJjJ1IDLzX9LsKvBY2jE6D10HQaUSQcoRuMFqiZ4GqCqybFq0jfW9RKg3ABojHi9aSBqJVQutECPqBAWisNH1lBtlHjJoQzZiUFBWJiMrxqESNrTx98U+JiuQUsVLCYOsV+jQm16iohhQMierlAaugMAL0RM40rE0YgIKBDJL/G6RWTnw4YsUg3yjGuQOzQo/rNxUmEgz0JO3H7/xyTySdRoDRK2JJ4Cj3+nStMgE5ohL2RP6zXIj8grL/E+BwuLZZUpSCEm8MIMWEUvJiFWQtKA2qn4CC5XvHjudmev6mzIz2Mj5k0s0111xzzTXXXHO9hfXmo9Rcc80111xzzTXXXHPNNddcc8011/+v9VYzKwYjtvhw2h0tpGUiGUWohDY8TN4MmV4rBpMKiMgUC3KMpB6nVIW2rD2kkrBwUMRKZ7O1MeIyFraFjSgrE7QUxMQzTCasqtfoo+iidY9M98zoaB8qy83ecb/0WOdxLtA4z8VCxojn1ZHNZct3gP2uIewc+qjHBIiDIh01yVraJqcIqCQ66DwlPblEmsa3NoH2SqE7ORHNq0RzHWleQ/e549de/Bj/4JtP+de/8WsA/MTiC37+8XfZ9xWfhyu4tehW4W5lVJ+2hlA5+kVEZdmMqzybRlgRT9db+s0WnyRSsvOGp+sdrw4Smdl7Q+cN1kS2h5rQGrZfVbh7w+KV0AJWLxZUdx4VE8tXAf9dy121YfM1mai/c7HjsOi4NRuSstS3Shgg2cjUHw2+iuM5SMAiMDD7Kz0wAXSmvIsR5sjSQY2mfCrI9TSnIuGYJo4o7E4RGkVYZAbM0nFqAvWqw7nAou4wOhGajnYp+3isGvqjEWmIZzDOmxr2qQB04yS7rFkQmnmZEA9MEJQYtDJJUVBK4ijzPVXWUpmAF4PPEkfJ5P4wLbiteLR0yhJWWc5STBo7hWkVdq/ExDSM9yuMFPZkhNlhWojOEKvs/bHSpHWPqz2q8fgLw+6p5bDNiSSPa9yuZvligdtH6usWc71HZU8L9kfqLzXmuMTta9pzjV8ouo18vl9o2seGm03F8bxivWipszFmkWZplWDy58Z61nUapFmdN/RhTF/w3tD3ZvS8yNP51FlUq7PRpCJl7xE0gxdFylKMhCbmz/Q+UZlAtQicnB3ukTChlJlsflpK6zQYdQJoGzAm4kzARy1Mngix0MqyqajOhsQxKDR69EwxOUXGKrCR6DWx0cTjhBFQtt2rITlo/Md8DoqpZvn+LmspqMn3eGYfGMSoEoY1F20mSuhEatUDk87BhLaTz+1XE0YQYE+yn6FSIvGzkNS4XRVBo4iVbK98tw/3UyJ7xIifRTJKWBD5+z2V75LBAPd3y6kGdoXK+5WZRimvrxQBrYd7gvhQSVIMQokiwVFZfaSmqo93WvR19bu3Pddc/4TUVBICsyxkrh+8+r0kIrM0ZK65/tHr7QYrcpUHX5CH2+QSYR3xyAOwmkTxlYc67cXHoGiDBzd5LUkbA/0364anD8Ta56YtP+RGp4g2EfrcqDud3ehLI5LApeGBmZSgE1mBDoqU5MFcp9IgKqJXxJOmNY7WJfZ1YL+Uh8+rtePxYs97my33dcd22XDcV/hTvpy9gA4qIMaNEYhamub2dzcQvkn4s0Cyie4y0461It5Cc1PkF4rd9Tn/zV6MXP/Cz/wtfnrxGbwHv+y+wSevLuluGswum/Hl5jydDHGvCXXCO0mOANiuazZNy9p1PF3ueFTv+WBxQ58lGn0yHEPFPlS8bldsu5rbqwXH1nG9Fa+PuxtLfd3gthJp6HaJ9fcMW84AOL5z5Hx9YnF15KgbVHBUW2maQSJMo1FDOkGsEnEpaTAA0UZir1G9IoZRIhFdoaVL6oBcNGlQdKsHaYU5qoESb9osW2oVaZflEU4kJ925pV0G7EJkM03Vs6zFV0PrRL80BG+IURG9xh/GplhlY1dzUhP9/ihfMR2DPp4EpphsDoE5o36/0PSjzU1Xvq2SS2PSQ3zY/GmvhhQVewAVNPFeD42orOdy7IxRmJNlGKvxtkABbUlhyedpr+gPmnbp0CsBLZqrjnCeJRpXFeloOLxrqa8Ny68Mi1c11bV4Wpi7I+p+j9sesNcLFpuGWBm6c9lwvzEc7iT6tDu3vFovoBZpUGnci6eE0hFjEl3dUTs/HENlxQ/G6IjTkZOXeNciE4lRiQSr8bS1I7Qmm9w+bMQpzbCVE5JKeErQhKSoTBh8NbRKhJgBndIUJ0VIavSpiIpU0AYdB08LpRJaRzGcLWabOZbTVV7eFxUhqsF7QumUgYskfh8mEE0k2ryYimcHEKKsb/Vms17AsH70YijnYFi/EWKRR9jJudEQM0A8nLMJYD18fhJvGblXR+AQwJxSBhElWjU047GXbRMh+gJaK0KVhvWq+/HfE7Lep8kcQ1qIAkEgeIg0mJwgpOII+k3lP/ltsZGfJ6se3M/l54jX7XgvTQBEFESdqF/PxMm5fnjqr3z4SzNgMdcPfE0BjBm4mGuuP1y99WCFsAV4kHkfLeAiugmD8/qIE2RzPK/pTmaMj5w87MZaJs7loVPlKLrygG2Pami6ymRZd2PMHVrAiwJ4hDqRJg+8Mp3MIEfxq5hEOSYtD9oqR7Amk0gnzfGUkwU6S3tmebTc82hx4Kxq2S2rYbJ7aCtOp9wQ9Xqc/KnR20MiOtUA3OhOHrr9Ohv0LRT9maLfaOqbRHWf2HwaSVoSFv760x/lgw+u+ZPLj3n87o7fPHuP37x9yvNbAQpO2xq1NxIPelI5dULDtSy5Q1WzXUlUpms8l5sDn9SXnFfCHrE64qPm9WlFFwzHzuUmvsOc7wDon2m2x4b7+wZ17Vh9pqnuEpvv5FjN7YpXTyuas5bl5ZFDUKjohutkD1AaimSkcfG9wj/KDWojAE5SBkKZ9qrBZyCZBHUWx+duP+okxwkko0EpzInB90F7UKeydmWt9DuDXxj80nFqIqeNx2UTUGsDzgXqyks/khTdwg7miaGXSN24zNG2GUgozAi30/L3DLSU623yz1PZ/ZjXmcm+LoOZZpLGqUyBS/8zMCsU+iCmpHYvpqRuK6d19AtgiH6lTKlLEwcDuCOfx7C/Za2aVqb0sdb4haE9D/izjsVCmBNPHt8Tk+L2fMH2pqG7MJwuHc21fHBz07D8roKX17Dbob/SaKNxK/FHiZsV9fWKw1NHd6bwS4tfWol1LUBK6cddwtvEqW5QTRiMdq0T1oLRkWXdoVUa/g/Q9pam6rlcHjltLIe2Yn+s6I8ZJY0qJ2vke7V4IgyJQ3B/bKiseJyU77GSIjJeGDHOLV4VMY6+FwV08BMww7owmHRGq9Em4pz4p/SK0UC0XLGY6CcAACAASURBVPRCGOgz/UyBKtHQjmz+mRli5X6ZrJXCuCkpTImxR5/6spiY3zoBvZIeWQ9JP2RlDL4N+c/lliz3uu7LC8d1NgDQafz9IeBiInXy/ezJwG1hIOXfNwNjqRu3JfuhSFaNazo9ZBGhBSRJ5XpNTDSTffg6OWZJHtHo4ZeYCgJ0K68esimG+ykRt25INZprrrnmmmuuueZ6W+vtBiuSOMRPae86Jwt0xhI1aBdQJg0TRZKSB3SViGtNt7b0R6FmgzAPpInPD5wmEU0cJ2wINVcFlZv8HJEaefBwaJQAIElBrOS9KfcloUpZ+pHAjA/ew7Q5Tf4Nhhi7lMGI4BWve2lIrhYHFrZn5VqabMZ5CpZ9X7PrKw6tgBh9L9N5XyaABcTotIAnpRmt8+TVRfwF9JeW49bQvBpBC4CPP3nM/778Y/wLj/4eP9l8xjfqF/zU6h0+f3wJwCfHKz7fn/PZy0v66xq7FdDC7vN1CkqYBZUswevlitcW/CYfdC0XVe3l58or4tqj68B6I93+1erA+cUtp7Xj5WbFzmzYfFfjtpn23inaY8XxmaZ+dESvPH5tsJn9MU0VSFEadG2g6ECUCmCiNB/oHBOZxjQFlbKRpPw5RcBCyshXMLnRcWIsOQAFw1pNmJOs4SqDJaEy+JWmX2d5wTIK8FbJeVFathXzPqagh44vlX3RknYjb8j3SV8kFg8bnLLutEfMIXs5xoFplKSZGtbIlMIuHy1NqdYkpQdgJmkIdX5NjlmMTprXaUQljKyPoYHM4N3QgGbzUNMJG8rvLf1es7vMKTePJXq0yH7u1wu6y4rDVs5Rdec4X11x9tsO/fKWtN8TtwfUThajurtnsb/A7c7pLmr8UtNuNH45OY9y2gmVfC+E2hBrK/GbQFtlQNJF9k3zIMlDzmMGj3Rgtegk4adxbBdykjpv8N6IUWeJJ035miCSjMO+5qhqrBNjWGvDg1RMXdgSOqKTIiZFzEacIMAFQMhpJUpH6jpg7cPUCIUwerRJxJjGVA/FCIJ4XSgGA06i8hpJUcmfXUalSiNfvnusNPQ6J+fE/L2qvMiFYuABY2AgHPTjdYhmwAeH+FYQUKcYwKYOVEgjswSGNKik1QCoTUGPsh7lOz0N5spajQu+pJ3In9VD56cooEMyacQwzQhYJKVEJZLUkBYy3TYwMvKGiyHAeWGgFJaFepO1MYDhUF0b/IK55vqhqjktZK63qX6/JJGZcTHXXA/rrQYrVGJIaBi09YVyHzS+dYSFIbokDVWu3kZMFaWhaDzBBXwrpyIcLfbOCFuiz0kjuTkrk2KZPieSzw2uldeWKd50gk3MTZZWQwMYGvEHSDlaT6k3Hpb9ZCqd3eqntHp90qTguK2WpKRYVR3rquW8kiZ+ZTse1QfaaDgFxyk4umCGaSxAH02OZ3T0QXPqHG3rhubH2IC1ETYt/kqzv6ppXzmq29x0feX41eZDfNT8c49+i59sPuNnmk/5U833ALjfNNzGJb9y9aP88lcf8fzLS9IrN0z87QnMMVElcPvcUCjoNtlPYgmhGuUTykNoKqKD/bnEP95frKnOWpq6Rykw75w4Hhekl1nmcYT6BpK2nGyDqgOpScQ2N1wue5XkyX9oMsU8r6XQSYoKGnCSkjAAFEyasqEbyw1ISS+wiWAVsREpicqMB5MbYFsp0g5MlzLwlbAG3FZRZXp6qHN8bvbFEImGTJ5hgm+pscmJExZPrNIIfuXPedCc5Y4q6lHqpHs1RJPqPPEvYEWagGxyL+RtNoleRUItvhxJTwAPl/X9Vs7VkEASxia1XOMiN1FhZFxM00h0D9UWTKfps6ToJm64X3o2q9MQl3tYdnifmUYnS3dZ056fs/5yRXXdYV/vUAe5X9LxSLrfYmPEbJfEZYU7r2ivHH1u+HQo7ANhJ4Va1k/ICUHRlUY4EbNXS9dEVI5vlcZf8WXUwg5SiYXrcaucjuMtXTC0vaWtLH1rB6CiVGwluSNoCzaibRy8dgC0jgPDQ9JCJGa1ACWQcrxpIqk4pAFN44zb3tIHSaUxNghQUVKM8poY7gkPhNHgRwAAacATuYl/sxNXiPcFAiwkG8dFHBWh1XkdlthnIMuBSpSwCtn+oqxHPbyEUNZ4BcZl/xgFsUiKLAMzo9wXwlJ4uJOmTQMYbizEcouXiFDFCA5MLpNOQCgA33DaBzZW8SbRiVHi8vAyj+8p2zJJpIJle0F8ZwZwUk1ej/xOqu4Vp8dzGshcc80111xzzfV211sNVkAxN5w08tnQsLoTqnuojUgySuOTSsMF/SKS1h5TB2yeXAedSHuJAjWZrp+MGifD5PdPou9ilSdpWWddmr5kGHwOVAAzMYErcauDN8akzImhQY1VIlo1NIrygoTqFP2u4jYpDrXj0Dh8npyuXcvGtdQ6sDA9PhpszqJ0GdWJeaN9NOx9xXW7ZNvVbE8y6fXeoJQY9G2WPYu6Z7+sOS4FKKhfGtLnDX/n9CO8+tqaP/34Ed9cPufr7iUAZ/rEj7kbftx9xTcWL/k/1t/k11fPOK6k+/O3GrtT2MwscMf0IM5Qe+hXcty6l0a+vpNzWSb2/dLRnzkOm0RoIpx50kUQ2QbgdgpzlPOpt4YYERp2nob3eTul4Yl1blAyyyalDFRlw1TtRs0/5El1NjBUOhFCZg6USTCgamEnJK+FhdBpfDYx7VuFW2jZz5bciCVMl0aWTm5EVIkMLZ4Sgw8Bk/hdlVkVDJRyv0xjM6XSsPZHsKKwMZI0dkWukj8/6ZRjRTOuUSjrBaSwskZT/i9UibDiwWvQoLrJ/uZmd5gU56ZMGYVK6cH9Ix+Udz8WSUhmo9yUSXNFaBy3l47DuYBXy7rHLrNnxXlie17z+mLD/bWlvnYsXi5ZvJZ7on7dYl/ew6lD3e8xx5a6W6DiEnOWfWiKCaOS8xWdIrg3AEwrLKpoITSG0OghpjYsI8FYutRwXwlTZrlqWWfD2ZTUICFZVD2HytH3dpRwRIVPCLvKKwhGwIvhOgpu0GcWjsrmvsMaYgQbnAuY/GU0jTYtkhUBOgw2gxUpjvfD8Lo6iN+L15PoUwTEKlIWhZj4Tr/fFCO4YRJUcZCNKJVIS/FlUVr2Xx3MYOCZtBqkVOWzyndwKI16nYZrEquJcWY+zGgZjHEHjDGCmrAzQg2mUtkYN2GPipi3GV0BBhkiiwfWkZwcBv+I3wuEyPuiMuBAjk1NRXYIgngUBlPIoE8Vh+sZ8jbEZDOf8wkzL5kk5qlr/3tsfa65fjjqTQNOmNkWc70dNftazDXXw3qrwYqkZBrORDJRAAN7Ao7FEE0ND6bTB10xN9T4dSQs88O7i6Q6Ebw4rheZCYyeFeVhNWUQJBkEtMhghsrU4hSUTLN1mVjnTZcpct6PB0AEYI+y70KlnxhADsyM3HAeNT46QmfoWsehFYH9oupZ1y218axdi1aJlRV9v+dhc6JVZGF6ruoDVsfBsG8bJC0gBIW30rjUTU+4zBr6+wZ7VJjPKz47PeHF7Ya/ffU13l/dAfC0vufn1h/zM/Xn/DOLb/P+uzf80vLH+dbTpwB8fnfO3e0Stg671didfgBWJA39mQAL5iCeF4uvEm4PVZZ5NDeR+FyJv8bScHxX011FwioDMpW8zxwz5bzWJDv5eaOE8VDWTm7GxcsC0lHlabkmLQIhKgGOSkWFdhFlIjYnLYSgh4l4Sgpjg0yyk0Jr+flAyw+G08rhbw32lJkNXgAcc8rMhh5ML+yLMgkemmZkbU8nzGhpyIp8QfVqpKXnZIEHYAWyzVglaQQnppjl80XWIY1fSQUpnVG04vHil8WTYmI6Oh2qp/I5b0zaGdd+ScGQa/JGp6dTllQJu8G0+T4B6mtIVtEfHN3OcH/mMUtP08gNt25anmx2bJqW/XsV292C/W1F9Tp7WrxcsfmsYfFVi9meUKcedeyoXoNpBRkLtZmYNaYsX9DD9020E7Ndw5A24RdycH6lCY0YlYY6ERrLbmU5rOTztU64yuOsSFqcEYmHDyNQoHQi2DSYcz4w6IyZ1dAbYqcZZF3wUKYApFWPcbIuO28HwMIHTYgaoxJeJbROGJNQORnFe0knGda1gWiN+FcAyWdmEZGEzuyI8bs36ZT3aSKdiGrUeWjxaIk2ZqBE4SuDb7JJaWXQJzXcKwXoFYZBAdFGMFrkRmrwZClrTL6Px+0mPaZBJZONNzuF20PdgmkTOgfLRCfXOSxGMDnZN9ZwYcEV9kWafL+PJBd0YUi4OICi5TwonVAmDiazIgfJx5jTUFJC1oCLIuubXOf+9wFK5pprrrnmmmuuud6m0n/wS+aaa6655pprrrnmmmuuueaaa665vn/1VjMrUKMcYzoZK9OoIa6xHd3gh8SOBE6LYV+/MvSbMgFNpCrizwKx0pP4yTExRAWhoafsai+TtXFiPNDXM/PDr4q8QA37oHs1RkDm4xjo7jkGUvcppyKMuniQqZ9fAEkTg8Rq+lYP0aUHW3PnFkMMZmUDtQmDTKSU1ZHaeIkxVIk+miFdwJgohn+dxZ9kaqvy1LCcJ7sHc1A0Lwx+t+Ljlwu+Vz2RY2gCv/ToR/lz736XP7f5bf5Y9YKvX73m8/NzAD5/esXfPzzjO7vHXB+XbE81MSm6To4hJThbndjUHa92K07HCr9ucFuF28r5qG8S9X2k2gWiU9ijYd8b+lWWKNSJsBB5iQ4QO2EZ6JUsghQVsTXogxFvkRz/aU6FT53lFVYRWjX6QpQfawhNIDaKVIO14gNQZBcxaJI3g4dASoqq8rhMwU9Jsbc1na0IRyOT6F7hO4k9hWzImf1TyloRY78Je2EiFSnT/7KPphO9fYoKupGBNC3TyXTZnCZ0+byGo0PYGmViXWj1RXYVgFZhtKzb5PKEPY3TbKHFy/0TYZgQDwayKU/CCytDQwppjL0st1aWYiWdhOKf74tqCzrLiexR0x0cYWHZL4RptK8X3J+fOF8duVod2DQt3aXh7olIku6e1RyfOpbPlyxeNdQ3nvr1CX3osDdCszGVJTkDKaG3WR9mjfwbkJzJzA8DRhGtzvKcLPlZadpzTXSJfi1soHBUhF2WibiEryqOdeBQB4wNOT40m73qiLWRqvLERmXWkyZ2eft5yo7XebIva2mIms2le4XvFH02A+2ceF+A3PNaJ6ps4KmUmHhWmVnRGUvbCiNIXp8wxhNy+o3HokwEB6RA7LNMpVzHHO/L1Iuj18IKoFz/KMeSFNqIPC85WSi+tviTIe4Npp3cD4rBVGK412FguyXNA9bWwHzIjLvCppB9KNK7zLLaK5FlZWaF9mmILhWWkiKU5CcYt1UYc/mwp4a0w9FHYUioWOKry4tGHZnKaSBTllLxKklJtm9sFBVI8coJitSM5sRzzTWX1GzAOdfbVr+XCecsDZnrh63e7qeZJIaF06dxoaUnQlKDX4Se6JwLPTcpaa7cVhIZ3E4eFPu14vQOxFUgnnniQkMUU0udKde6y6kK5XMLoDBohhl0y6lGoh91Ii6Kxj43Emps2KIdD8OvFO5eYXLSiO7Fy8DmHknnyDrdKUIPwRuRpBQsQid6Y+lNxaEajfjSG35rSiMNgY04GzA6jmZ7mQauTSL0GnrzgNWvXBrc5s1B9tfuiiYGknHcfl7ziy/O+fUPn/FvPvtV/pX1t/jI3ch5rl/z84vv8NubJ9yGJYdYE1AcsiFFnwwbc6LWPZ+0j7juVvz65XvcHxq2O3nN/rVj8aWhvtG4Q6LaJ/xrSRQA8EtJYTGnvBa8wkcIdmwGKJKcoLAHNaSDlLWScsqLORWwatTBJysRr6Ex9EuLX/jBFwCQRjIqQokt0AldBxZL6XyckcSItFL0Jg0JLdEr/GoqNyk691EPX9ZKMZ5UXvYvLLKZpSngnMopHOLhUlJrBrDBpyHBwxR5gxmjF8X/IksbFvL3WKVBplESb1RQ6JQg7wdpTDmQpj03eV5JH5eBxLGyyWpKowfANLXEj8eerHjLDD+L4i9gTmKqWiVF3EvaDEjD2t5bnl/UNOuORd2xrjueXYpk6bB23J4vuX3SsLuxNK8rls8dyxc99bV4SqjOo9peTEFv76FtSSGiTAYTrAWlsZUDrcEKsEEosZ4W/3hDf1FzfGRpL8SI1Gcj1VhLSlB0htBEfDWJQwEwibDwNIuOZd0Tqx6tIOTG3+iED5pj6/BepBmx1+DF3BUYwAtz0HDKMhabCHVJrxHpQag0ddOLF6aOLJzP61VedzwYAfoQ+UqJb41RFqpzYZA8paSGmN1SwWvxX/AaejWCUr2CTg+gVnAJtfTYnKpSNT3BRryLxKMRb5miIOlHcE95hbLZu+JN1VG+l4pXjfx8vF9A1rIAYvJdHK3CHjMYkg2dzSmJxNBk0KzIssi/BvIf1IN/ZHzNBGBUHnTSxOGLR5FSJKXi3aEFiBp+z2lGjQ+DrGz4/CgSIX16+O9zzTXXXHPNNddcb1u91WCFClDdZ6AgPw+LS38iuoTSKntWTDTDmYmRDENEnfYj88J0kIymBeKZh+zmH/XoCK86TazEqd4ec4RpmPQWZaqWwQiVJ/pDSkQTSXXWd+fGDp1GrzyT8GdGAJKTNN+mVYPhZ2nMdJdNRE+jj8WwA8U0spiLThrHUkklooHeJE5NwDY9i6zzN1p041pHWiqCQhqJIoZOkKqEV7Idk8/DMC3vxZG+uq34+PoZ/+WxgR+FX1h+G4CNTjwzAVc955DGZTiFRE7JolXkx6rnnJLjJ1ZfcogVdxkl+fR4yW+8eI/Xr5a415bFC+kOCnvG7sAqhT2C8uLZ4LcyeYfRaM+05LhahthNuQ4MPg3CbiiT1VEfbw+KUCkxU1zprGHPjU1SqG70olBAdJb9Sravlh5jI9pEtIsSZxjl81Nd1qv8TBv5eYoKrdPQqsRei8lhBjLspid0ZoybPBppmnpFPGiMy839JD1Hmrc0NHAqStINkAG67Glwgm6j6DeKaCfNXZq8No2fOQyKLQQ7eW0cvTHKWopaYUJmoxhGHwPkGpWJtQA2Ar4Uj5h+A7HNEZNePs+0I0CZOoU9Kvy+ols6TquG7abn4kxYE9YE3jnbcVqeOD2x7LcNh2c1i+cCWgA0twG7C5g+4mJE7Y6w3ZJ62Ug6nlBKkUy+CbWGEEidAFPJe/SLFYvLC6p3Ljg9W9KeGfrlCJLGOpt21ppQF1PJfA50InaaoxcAwNnAummpMktn5WQ7bbC03tJHTdtnptVJGCbdydEfrKzFLns5eDX4bpAjTfuTQelEXffCCCpLUUdWtaSsCOtjBCxAgE8Q3wmtElWORPUTg06TfXG81/jeEvaWNHxnjB4y5pS9IU6aPpuUmpWXxJM64JHvZAEf1GjP0St0JBu6TtbQdI3mvw+A2RtRviMIIV4sqhL/G2DwrnAHASvMqazNCaMpAyLRMtwIaZoGUr6Di7GsVzmNZGSYEAxpiCPOUdoFEImjmTM6yf2f5N9lMWnU0Ux+H8w111xv1u9lwPlmzeyLuX4Q6/eLPP2DamZkzPW21tsNVkSo7mQKWxzgVSjNI8O/h4YHRmVDUkfMD59KTNRAplz1LaioaYMjrMLwUDkwJ6pIMDJxU1Eeak2cTORTptEbeTiubiQ+taRQxCaNk7xCk44MYIauArrpicEQeo1vNarTmL08zLq9ABf2IMaLViv8Uo1xpzCazJXo1cQAYEAGUopJooEYFMFEfKZca5WwWlgZWidCrWlPjpipxarV8iCdzy8Koh/PQVJQbcWscv2JZtc+4j+Pv8Dff/8ZAF9rrvmZ5jOWuiUmjVaRQ6xZaplkx6S5jw2VCjSq55HZ88Rs2ZRuAdAk/u7j9/mN4wf8xt0zfuOzZ4Stw97n5uaoBjmNOYHpI+kO6hy/GhpFqPN5UiOoFTNQEI2Y56k4gjGFtSEvyNP8FuIRzEliRkOTr7P7f9l7k19Psuy+73OHmH7Dm3OqysyuntjsbnY3J4OkZEmGIWhhy14Y8MreeuGVDcLwXyB4Z/8FBmQvuDC8tSBIhmEYJEGZEukWze5mj1XVNeTwXr7hN0fEHbw490b8XqkpywJEd5K/A2Rl1vsNEXHj3vvinPMdcoItRSU5dUVYJmrATMRdzVE3CuqpvaQHgXyXlaMs3EDRacoekxIXHxWds4Qone+zZsP1dsKmkyS7nRTiJNEbXGNwTuGmeqAkFctU4HHJejfNY7sb14PpIsVWxE1NQhhly848dgNKaL8BvIc0GhKnkLvJ4zhKUSHRdbS6P2/hXtKloiSiKuxRVcqECLEj6uge3SWdW7ESwVW3U/iN4SrRpkztmE5aSus5m2w5m2zZnBXcPpqweSbuN+WVpVhaTAvFqqZcBeprh9lKscIsWpT30jLvHcp56B04eT0sloTNhrDdoe8WTJYXVGcz+hOZbLtTSz8VpIWbKHQzIlJkDCSRD13Btte0taPrLWVCPewqS2U8hfE0tmemA7qJTGzHopNr2PQlO2fZdgW7bUlINKghnEIF2Wv6LGpZeDqXxkkHmrKnLntc0PS9Qe0tB5v2CoAQFVoJwi27iOQCWlX0VAXsTGAXkWQbiFYPVqnKG6H1LBUhUcN8p/FlgCLDeSIYKbrmdeODTgWzcX7CuC/JD1OBYX9PV+P7dJfFUsXdhkpQRSDfK8VuNdgq6/7+9+diXSgEpSTXNp5PMFH24uxklRBD2dY5apWKddmBhkQZSa/7VBBVjFXB/YvxUtx285/B+TrEIQ5xiEMc4hCHeIvi7S5WREEZEEFlznFqqIWSIUHvpxGSnSJ6fJBVDkxKpEalf9BdpFwotFP0G0u0gkDID775QTEa8FVKaKMaO7lBHlRj4vEXW0F/5ATP16OC/KBbkVAQAL7XxFmPMhFdeigCMYCbJW741mCXhupa9BtMFwc7VBiTw3zO0ai94sVeAjiKzxPcSGMBSYIVYJUkxwCF9WyNdGn7RfLt8yOCg2Kvm15EQiX2pNUNTD5VrPUp/8vNNwE4Pl3zlYvP8aXpJcfJfuN1d0SfBnnZ1wQUU9PxrL7mc+UVz4o31MqTmtE8sTO+VFzxtycfc30K/+uDX+R763f4k2spiFwtp6yvJvSXhupWYdfiEpMT8YwEcY0Ck+6ZHTv2Ykcp98rXcUiCVLrPGe0iNJ099EWaX24iUPLMnVe9EmePZEuqnKb3CldaTO3QRYLTd/dboiEoQlRYFWkKx1G1Y5LcXbSKuKCpjaMyjj4YWi8JKcCk7nBBU1YOZgzWqyF1u3fbEm08MWiB57cGejU4Zdit8PbjTaRcBapFSJbAqdBVZD0LldbDiFgZefyjHSlpneQxBFnDQ/FCjZ//rC1oroII/UoNHH2fkEShlHmnErpin4YyaM6kf2sHvpcx8rVh0RSo0rOa9MyallnVcvpwS3ch43D1zpTtpkpIFoXaGuyqxG6k2FAsppg2DjQb20ZxkUiIrfKup7haoRZrwmIJr99gVxvMzTR9fkZ/VNLNDe2xppvLNe2PszYQO/CdJZSGXVWwS2OwsKIpY0o/aNVMyh5qBvRFbTZoFdAqsuorNn3JclcNCIyuLfCdJm4N7Ax9lCLXUNDQkX7eUhVu0LHI8xMEYVEYT+/N4HrTd1YQQyl6HagqRWkdpfW4whMzzKcCbQJF4dlMK9yyoFiY0UVpoYlWdD+ilfUYbQQbIa1ZPwtEozHJTSmUIx0KBEFEQGxys2NH+j1B+mcubmmtCDoKqi4VIH0dx2LFbrQcHmiGQRB32kehU9lxLx7mt80uQyPqTe0hPIRapYb6Qz6nQS8mILoUOg66G1kHJi0IeW2P2nKIQxzi/3v8q6Av9uOAxDjEz3P8yxAZB9TFIX6e460uVkQDrt7jH5MeEv2YUAaTRTP3Htz8qPkQtTwUDg+CJRRRoVykvEtwZKOGpAwEEhxsJKSufNDpITZTJAIiNJgpBk4aZRn6bnfqXqc5U0hywSTcabojQ6iCCA7aiCo9JlFSYhFwdSBaSyh16hhHeRBHiiXRKfRABUmJeGAvcZCOXCiiJORlwOzrLQSN1oHOGcqkZ9GUPTYlPouoEt3AoJIFX+50AkQr5+inmlBo6itF80rhFpLcLc8L/vBmyo8vLng0W1Jqx/VuyoubIwD6bQEqoovAo7MFz+a3fHP+CU/LN0wTuuIXy1c8MoELM+XCwBePP+T17Hv889NzAH7YPubby2f88O4BL94c4xYlZqUp75LOgGNAQqj9AtMeQibrP2Q0jI9jYu1bSVh00hbJaJ19yHmo8vgm5MZODZabWczT7TSxUJjCUxQeZ8LA81cqDuKcWkFlHaX22FR0mJoOrSJHdotWke8vH7FsK7qUgBap814VPSfNjtNqw+N6SZUywJWvOEpiKH00LPuau77mzy7FYna7KQlry/bGUl8a6jcRu4sU6zCMUQ5XKaGJzGR+D5SgoAYUU6ba7I+T6SO+GIsdGalhfO40gyoi0UpXOyOEMvRe92PxLypSt517McDw873p1WAPq7wW8VVtaBcFu6bibtZxMt9yMVkD8OzklnAsX1oaz9YVtM6yTkWh5brG7QrRYmg1utWYnR72Ibux1Nc19c05k1ctxdUG1lvUTuayfXWHWVSU04rivKaba4IVIU4AV6c9qFCY9HcoxgJjLnxGA10R2TaBmyrwsnGUSfNhWndY4zmudpTGE2OPbiJtKYt2Yz1mHlhvK7plSUzil2pj0n2Ebafp5n2iiEjxS+e9V0WsCbigUSoSgsZlkc0UAblJsVLps3Gg/CgVaaqOi8mGbma4nk9YVFPUOiGlNjKeNu3bQ8G4CcQqa4MEgo6gRPA0TjyoONirql7W66A3lFALA7IiFxazSKxT6BAJg8ixoCLcVAqRcZ0KFvv2x0b+3/SRmIq5On1ehUgoBNEVeiXFTJUKeux9x/78zeiKOL4howMz4m9f84iE6lD9wezrEIc4xCEOcYhDvN3xVhcr+OYruQAAIABJREFUgoXdubovGOgR9EQqAKggiUos08NsUIJUiIpYKlAau1Fjkm3ANalr3sWBHiI0gfTAWkpnrZ+CnyQIejmiFvYdGkAoKcqPD5PKJV2FmGHE4zHkDQJZ95WRZLqKuIkRSgqgqoCpHeEisJ1Y+jtDuVBjx96PSaHuk6uCSjDsdA4BSQ7FMSNANTpWwH3RNucjnTNUhRtE9mbTHV1l2KpKBOCc4p7Kf1RgPZSezhqitdSXinKRzjFo3KbkzeKU67MpVd3jnIYPpNNcbXPHEF6cV7w8OebDi1OO91AFp+WWp80N32w+4mvlS75UVDyxM55YGYh/u/4+t/PvcPfY8J3uMT/YPeHHmwd871oS8dWuAq8pTaBrLTGIKGFMyZldGEwH2iu8JxWn4tCFleJGRNUiOOlr0X3IcO48J6OWOegGpwF53eykyGFXBmcE/VOWnqrqB+SD1iEVKuJAA9m6gi5Xzkqhw4AUoj66PWGzK/Gp2BGCShoComvwsF7xrL7m1EoSbggYFahVj1YBQ2QdSj4/fQbAdTdl7Uve7KZ89OaE9adTqktNdSuHL9aRYhMxbcBoRbFh0PoYQglaSbsRIr8/31UUGpYgKXJSfr8Qop0iREHvhKT/kot/yinwcVh3yjNo08iBGCkVewcd0B05KQXUVhFXmnhdcDmvuT6W+TiZtEzKnmkpxaHG9pxWmwGxtTsq2LqC3htab2h7S+8Nzsl96nrDZmNRa0v1pmHyqmbyOlBfpWLFYofa9ei7DXXnqN5YQqEJVXILKTW+0fQTTT8RZIsUTdMlmLSveKEehFKncSzoEq1pNwlEE3mV9GmUgqLwVIUUrkrrOWs2nE62fBROca0V0de0r6leYRYW7xWb2qJLcS3J1A9jAloZ0aZIziK9sYOwbxb5DUHTtZaidKO2CuC9prcGFzXn9ZrTasOnZc/NciJjuKhQO6HDCfUiFwA0Lv071h5sJKT9XteiC+Mz1cRr/M4IjS2v0z2dmiHRV3Essjk1zDWf0Dm+El0kX8ucHeZSQgaFjLrygirScQ8V1CdxTiv3T4rJo3BvquekdRRHpkc+xTye7KFDwv11FauAXR6KFYc4xF9k/MuQGAfUxSF+nuPPQ10cEBeH+HmIw9PMIQ5xiEMc4hCHOMQhDnGIQxziEIf4uYq3GlkRi8j2ccBsR7tKcXaQ1zPcXHdK4MdINy53x6OO+ElAdxqdOq5ZGI3ES87uAtpFVKJZSCcu9bl0olIkQc0cg7OEgtDId4x2kXsw+AjapM5dOm/TR8yd8Jl9gnv7RtEnO0vfRPxcoycOc9zhaoOfWcxKrtHulPCm97r8IN+v9njPoRxRFbrwCba9dw3JvtRHhfcarcYLtEZsTpPbJH1n8U4TUgeTZJsYTUTVnv4cdG+pbtJ96hIUf2voNzW7SUksI5O7sdspWgagW4O7q3l1V/Ky9qik7RC9YN9nJ9/i2cktf+vih/xS8xFfK64AeMdWPLU1T4Gvl3f46Q2fnGz49vlDAF66E1a+xqO4cxNetUd8tD7ho5sTADZ6iro2A9UjbEe6gZxA6n6mTm6cO4ICv0uw+VYPCBpBicRBvFPmQUZWKIgW32s2XmErh8liq1oQLyEqNl3BtitwyRISREcEGFwWFldTESjMSKJCoZQgY5ZdxZURKMC1lb/nZsfS1xgClXZcFEtq1fNeLWP4vLqmUI5a91w+mvPPnz7je28ecnM9k2t8U4p17EKjPWLtmRhB+2KuoRTIu+7jwO/P4JCoBcWkk5hn3Cj6GWSTGN/EwTJS9Ghk/Ya8XnaJshNHpLxKCKtxMjMgf7KY6rBeE+0ku/eYVtZPcWfxV4kiMalZZgRSIcKzVdUPjhdFcsDIURWOpuyHbrjVQo9wQbN6VnF907C4LGguRbmxvqpp3njKux6z7dGbTrrxyfpUtR2xsIRZgzup6KcWN9F0eU+o85zaG28DaIUv03gXJu1rllCURAObJrI+Spo0tcMaz6zsOJ5vWdmKrrWQxCX9xqLXBrNNCCRr6IsgmhGIMHBvAkpHrPUYEzB2FHksCk8IShBUaf4aE4a57HrLdqN5FTTtxHLRrLmYrAfNjWXdsd5U9JMCtTYi9pr2CLtN36E0McYBdRGcFu2fjHozHq8i0YrjivJKhEUzKiEJWWoFuIiKSqh6g/X1HtUvoXd8FUfNpETN8LWisCLMK78L0hgl2p8KQJd1N8Cn9QFpXWjwRUJfFHsTm715HcffdXpP4FMp+Y/ZfIYLdYhDHOIQhzjEIQ7xlsVbXaxAR9RZi+sMrk2w+U5EHXWywItK4PZmtw/5FeGzYEWLQB4Ss+iiUEK8AV+OvOasSwCfoXO0CYZex3sCaFnQT0XwWbk9fc4PGZX8LJ9rfvA0rcJuBOaunfxtOnnwBUlE3ErTnRj8WSdaFmXANQkyvjWYtRZ4/VbdEzMcOO42iZCaCNkVICh8hnwbSZCrSi46c6h7L8cIyYawKnqMjrQm4LymT8r9zhjixqI60WNQZaA7CWSxjuyiUazl+m1l6I/ioOURKkkoTZeKFkFhdgbfCLwdxDbUbiCYih8dHfNn7zzm4mLJL569AuCX5x/zt2ff5ZulZHJGaZ7bGU/MUq4l3hIIeCIhRj71ij/aPeOfzL8EwD+tnnNVHBFuLHatMa1CbUfhx6wREApJNnyjUbVDTZOIZWkIGyMaKTlRNuM8icllxm5l3vmdwW01blLQp2JDVztJMIMidlIA0ls96GZ0CSOeHQXKXs7HHflhrsVg8c7QdpbXixlax1HzoupYtyU+KqwOHNUtR9WOR7WM0dS2HNstn68u+Ur1gq88eMHL0xNedFLQ+eH6IT+6veD6bkq/KjELg9nI/crihoMQbRGEbtEp0QLJThc6YteK6laoTHYbQSncJM25udA/hkRRJzpOmgeetG4H/0rG4kaOOK7bXJzYfy2LNXolwoWqV8n5I983JeKOdixm9mWkTdcYqij6MiYOdrO28INeRFP2Qh8pes6aDdv5irsHNXcLucjVbUl1ZSlvLM1VRXPlMG3A3gmlySxWcLdEX91Q1RXltCHWFe5cKgndvMDXSkQdDbhK1ncwahiHUUMkjaUSAc9+IVmymxe83BZUs5Zp04oDjQnMajmH7axgva7xy2KwGcWZYWBDLYKUmIgvDTbppdhc0DEeZSOtslL83JuHAK43eKfZrCraXUF3bJhXLVUS8yymnknV0R8ZFuuablPitgaz0aNosleQ9gvlIQQrxcO0nlQRZD+vvFADgxJtjr0ihPKK2CsMiqAiOipUMiEaNEi2ahBXzuLFkCiHmuT8pEWvoh/nbihIwqtyP7SL0Mt7fPr9It+n0EUqQiXr0/19I5o4roc9PaRxTh8KFYc4xM9T/CyKyIEacoif9zjQQw7x8xBvd7EiiiOAbRwhWykGRVdadCsPsMND4Z74ZXYFCIWij6IsH/a57Ht2jD7x4HWfrSfTg2FMyAUHODmX3PH6rHWi7pNmRLb1S+KbsZBCQSzivjMqzkkSofvRscJ0UZI4gC0US7Brza6tcMceNXGYWg4abMCV0kV1Oy1JBRlpksZJS6EGr1Lym4Y0zYhQBnyvaYGydFjrsSYM4+SDpncGreOgq6CUqPkDWBXpd+IsoXZJVaEK9Ef5oV2ES+02oywAFG6WxyiiGkk47Havu7lVEDKHXpKHahmpbqF/U7M4qvj9mQhs/u7RL/A7j36dr56/5jeO3+fL1Us+Z294ZuVGzXR9bzqdGnhqP+K9UlAFT6sb/tnRc96/Oef21RyzNFIw2hPQ1C3oVmGMdIb9xBCytkgRhEPvFdGr0fEid3FtFAcXN4pPmlYRloZQyTn6pFmgnZLCzU5cTcZOL8O9lfdLkq+TVkI/04MwaPAK16uBAw9wVwVJ7JJI7W0RiVXg+8eSoJalOD88mK753OyaL01e88Au+Y3pjwH49elP+OT0jKt+zoe7Mz5YnvNiOcd7zTwVumZVy2m14bQUZdG7vmbZ1aiU5GoVuVzPuHp9RPGqYPJCinX5JN1EiaBhFaQgkZEQWRC2lOJiRiupKN3xe8UKGAo6KmmhDMWLtCcElcRUi2RVuadyaDpQ3YjKkDFUoyhukd1k4lDQ6OtA18gN2lQepaEoHbOmpSl6TidbTicyJuuzkrtHDct1yfqqoHpTUqyhvpZCwuS8prrcoO/WxNs74mJJdI7iEyl2lEcz4rTBHTeEyuBrQz/T9I0eij5uou7lsMqTrH3lh3arcKuCfmq5OS2xlaOsHE3StJhXLWG25lUzZ7ct8VsDTqMyeiukMXfiLOOtoBpi2nO81xgTcM4Qg8LrSGndYGRRNx1tW6Tis+F22dD2lioVPUrjMSpyPFkzr1rWs5LVrmKzrPCbXEGU+4kH1Wux5gV8EhENpczvWIlgsQJiEQhZzDWt1Wg1KD0I7GYk3aA9FFNRQ0fR4tjTR8nCn6EUVFqw938fRKsGx5CYCtXE5GxFmos2ErvketMkB5GEvMjnsy+oOdqXpkJNiIOF8iEOcYhDHOIQhzjE2xpvd7HCK/yihDKgrGRrxgbCvCdONC4p2etODbB6laD3ugfJqxPkPHf09xAQ8tApHS3tQKfR0i6hFQZkREZs5Nfl5wNdIOeoanzgDUUkaDU+xO5Zl8ZSuqLKJUh6L4JtuVhi2jhYcNqtol1YulONn2ULQI8qA6b0+FITvEbpKDl+pml4ebg1S0Ox0qg+nUO2AKyMCMh5xa7RFLXDqDjA3redoCiCV8SgUDlxzM1vHUXI1Ct0q6HVyc40UThyp7oEu5ZrND10uVNtgTpIUaXSqdOdEop0H9w80h/J5+0GilUckBoSBb6+4I9PL/j9d3+B+eMlX3vwil85+giAr9af8K69ZaIc71jFsW441g1/PZ3je/aP+I3pj/jhxWP+8enX+PDulLvFVJI0QLWG4lYnGpJ04YNVdCdy/H4eoRHBvwENENRAR/AZ0aNG9IsKgrTI4n92JRQh5XLRDcpVFLoFY0c1Fyv6yUixgeRmk+kNQ1FojGCNzLU9R4RoDe5aBnlrYKsiN+Up358+4fdOvsA7Jwu+fvICgC/WlxybNd+afMgvNR9xezThlTumDQXHRhLxM7tirrecmA0nescuGm79hD7xMAyRl+6Ybz95zu+/+AJvJmfMP9CDW0d5m8bzJLn67NFqIJ23jcQoiVrMdKe9XE2sIZN9af4T914LImIawyh6GGdRihZpHFWmb6mMThgLHsMe0CtMmqO+1fg2o6EMEdjZgl1ZoWtPM+mYN+LEUltHc7KAE7g7q1msGjbrgtVCxqi4q2guS+qbY+YfnGCulvDiNf7mRk7g5ga0wZ6doOoarCEcT3Hziu5EstzdqQj2ukYNNr2EcU5kRJjZKfq2pD82+Jnm1sggTKqOJ9MFzVnPqqtYdyW73rJZCbdJa4gRsd6NQK+JPjIAu9o9y4pELYMReWF1gKrHmEDXFgRvaLtInwpvWseE5AqUWlxNauu41oFdKdeYqWgxKIIC3etE48oTQQnlotL4uUbVHm0DOrvFGJk/0UaCAtUrQlDD69olFFwqYsjN3UPbeSX7bEab2IhSaqDfoaE3YAqZcyoVYZUT+h8w2qH2QIjp94XCZRCKUig/Iity8XFfoFlcUg7FikMc4uc5/lWsUQ/oi0P8PMbPQlwc0BaH+DcVb3WxQjmoXtmkvSAPZq4OqMahrBQwgor4ShMyuiBZ15k2P3Ryz/p08LPXUqQQbntE2A85E49o0kNpRlh03KOB5AdGcRpgoIQABCedsphcTMQtgoFbHm0qGiSIsaoS0mIoVqTkfB2pbkWzo1hruhM5ge5YE5qAV0I/0YXHFMKJyar4oTeorREqxZrBCm+EU6eErTWEtaabWNzMMJlJxz1GcZoI20T1ALJ7B4ArgkDijeg5qF6nY6QEswq4IuIbcS4wm6QPMij8J9izjfip0AdCKfcuH8M3gTj1uAug1VSXFruF6lqOUS0i5QtH/EDR/VizPT/l2xen/OHFlwEoH2w4nW+YFD3vza751aMP+c3mx3wzdWGf2xnPredXyx/yhfI1P7x4zJ+snvJiewzA9XbCi8tj+tuS8kbTvFaUizjw5/uVFC7cJEiSbaStn51ppFsqmgK6ZyyK7VOOAqkLK1z2UEA/HREygwNGmmMxdXHLZU588v2MQ+c2ZitEJMFxk+QMkzvAjhEFwj4ax+Cbgp/M5/z4VHQ/ZicbnsyXfPnoks81V7xT3PKV6sWgEQOgCVKciJbSeia657FdDiidQgU+Z2/4RvUxvzz9Kf/49Ov8QfMlmp8Kz6NYQXWjCFbjTvwwr3KBMno9UjmCQvVaKCZ7KIKY1qHulfx4j+Mv6zbeKzwCg60tCI2FqO7b2vpxnIZueypiZDqBHugDGbkl4xitZV0XbCaS6NvK0dQ9s7rluNkxqzrW84L2XMbAe83NokKtDctnM5rLKbOPz6hfrORrb5eEm1ti20HXE9oW9aaksJZylhxNzuaEpqA9r9idGLnve7oeUcl/okloJm3wXnHby3pY1g4fNCf1lpNqy8OJUIVujgS6sekLfNBsumJw/AhRDbfBuz26hdPETrPtNTrdR23i4C5SlG7Yq3JRw6XCQecsVocBcaEAWyRUWdDoIlCUDtcY+qIgrAw6rQO7UZQ7RVQKt1X4xuAno+4GVjQ3lAnEQg0FZp+Qe6FLVsV71tP7RSupGuzh5PapR0AwaV/P9JyEElLJxljOMf3MxwG9p0IczgVA71luRyXFueH3hxY63VBMOcQhDnGIQxziEId4S0PF+PY+0DSPnsUv/Se/TdTgk3BhKKE7DoRJgCJI9zXu8dk7LTDZXh4W7VYezu91pRJFI5rUxc1+9ulB2ySUQ+6ymVYSyv2Imdse9xKZfV5xOs4+IiNrMQgMPek3ZNg7e927lNCWd5KU6l7e083kjd2xdPV9HYmVJMfC1Y5iRQjQa+zCUCxHy9P9CAXD8VWURNZNI+5csmjTeLEh3FjUzqTu4PgwHZMeSMyJZadFlC4/QNs4WCLSJzvCVo3c74RoCc14D5VX6J0aOoq+DsSZp5x2FIVnuykJa4u9kxpcsVBMXklBp1gFtBfbUNfIGGwvNP1MigXdScQ/bvml9z7lP3r0xwD83en7XCRBSoAbv+G7fc0H/QMAXvXH/LO7z/H+4oxXl8cUP62YvFTobkSZuEYsbt00is1tEYlDOz7d1JA0HIrULfVgNnKOmfqhgszxkJPwPdi58OJlTqogyY7dZSTROHe0SwWoz8x3X8kY5Pm3H0ORLSfnaS5k3Y5QQT8P+FPH0dmad4/veDa9oVCBhZNFueorPl0dY3XgolnzoF5xUmyoEpflzK55Wr7hi8UlZ7rjLhT8D9d/nX/4o6/JJb4/pXkp9Jbdg0BoAmbeUyQ9iL6XCwoJws8udfb3k7W05nUrRbNM5ZK5Oo5H1hcQod2Im4z3Mt+7LLqY9WaGsUrIDd2mz5u9gknufH+GLjCOYyBWAdV4qqanLntCVAOSaVa19N7go+LN3ZR+UVFcW6or+cLmKjJ96bCrHrPuMXdr2GzxN7fEPiX1WqGsRZ+eEB6e0p1P8LUITYJ077upojsStJlvRM/HV+OajRNHPeuYNS2PZkse1CvKdB9vugkuaLp0US5oWm/pks5N7w3briAERbsr8a3QxPbvESYOqDDgnkBnCAqtI663BKfQRRA0TFSD1XJo5Vjz8zWV9YQIq01Nt5HJba4LyhtNsUrfWYhVdUjX6Mt0L5q9apbem0e90F50J/tQpvzpTCNRewVZnQsN4+8foX7tzb1cJMxIClLRIlGT7C4jLBiKtMGMaIpQMhTVc8E+Jr0ldOT93/6v/ijG+Osc4t94/Pq36viH/+jZ/9+ncYi/5HFAWhzibY4D+uKvbnzwX/7rP4+81ciK/OC/D29XEUyr8bXGNeL2sd9lVUF4xrGOYCNdLYni0KnOD5Up0dBdQlDosSPutcD9s6ZEVAqjx44sYeSw50Qv7r0+6AxEBki67kdIf2wlCcoFg6iFRz8UNRpBkvha4SaKYiWihKZNsPmFwnQCd/Y1+EYPD9BDJzhrQSAibjGJEuZzk+PKOekO7FpoDv02QcofOqgFPRGrQECj4x402Sn0Lj1Mqyjc7jgWGobHfxuh8ngkWbrXqc73S6XxB6IeUQd2owmtpus06mzL2cmacAy8I6/33vDmeoq9LGheGZrLSH3jqa/lC8qlJliVigqa3WnF9z/6PH/v808A+OOv/Cn/7vH3+Erxmq+WE07NhK+x4bH5EIDbsuRRcceH8wu+e/yEP5485e5oSvVGEqbyVu6L7pQI8tUK18RR9b8S1AkamVs2EktQJuBKWZq+SzQaEt++DINYIMi4KB2JTqg2uhWB2SwMWaykK5udS4LlXof2s4WwIalOkVEYef7m+ZFRGsUKyluNf12ymxX8oDnme9OnYFOBCtBbRbHQRAOvKkHKxMZjkp5DVXc8PFrxjdNP+VtH3+eXq0/5z85/d3Ak+Z+Ofo1L85DqWmE3ShwfplCkzrpOIo19ZwlGDx35e8WKTv25hYPIWByLRua76VSiRKT3qCSVknfMsKcZQEo4VRzGKO45Eg2xVyRSA9Ijfb7XxK0mrA1tWbCrAtiArZJQ6kxRFY5J0TN7cIN+ENl+ruB2K7or15uKq8ua8ragWEJ1d0R1G5h+2mKv13KQ12+gd7hXl+jFkvrVDKwlTuQ7YlPijmp2D0pWTwy9V/TTESUQPKiuoF1ZdmXNzXzCp7Mjnh7fyX0g0gVDqT217XlYLXHR0CfbF60Ca1fRBcOb3ZSbTcNy1eBTATWuLWqnYadFc6cMhEJhEvJC6ShoLqeIO4PvDN6k+T8UYRUqKnbbksnxmmezO9yRYd3LonsxP2I7nRBeG8q7pJmzY9CkkOKM6H2EMhLqIMXqwU0kglb3Cg5oBmHie2gek+bBfs0qKClW7utcqFHnBBKaJwmEukkSiN5zuRrcpBLVKSP58hdGlcSZt3snc4hDHOIvRfx51JFDEeMQb0McBDsP8a8T+v/9LYc4xCEOcYhDHOIQhzjEIQ5xiEMc4hB/cfFWIyuige6IQTATBDZbrMBshZ/ctzp199OHVFJat4mi0HhRf89aCb0eIbceEXZ0SVMiw3sLgfOHIO83BXi/pw+Q3h8TGgC4dw4q7HVcI0lD477Dg91B7FI33KjBaQAEth+qiJsHfKNwMyUik6JnKGKVbe4Qg9+NbeTB4i4hPtxEOvzR/Axkhcp/C7zd7PY6wdbQnypxu6g80Qa8Gy01VbKSFCcSLZ1EHWV8Edh09JHoYxKgTJSVIiM/VEKk3KcpRROJjHScooWwtHS7KXePFbNJyztHCwCeNAvCY8Unm2N+en3K66sJ1StL81qmfXUXEionYteB+dLTXBm270un+R+8/jX+9/e+zG++8yH/wdn/xXN7Q6XAJGTCie74xfIFz4o3fKl+yfPmmj998A4/fC00keXLCfVrI+4dWdS1VQO3PJSCtvBVuvYg44BhQE9Eo/BVGLntRcDWo0qm1hGTHFj63uA6S9dY/CS7iSRXnCzSmekcex1/5eWwyqX7nf7ACDHPArB5XoQsALqReVEuIuVdgrwrQzDiSAAyr+0uiEaHlc61rw0umbG4uuan0zkfnp/z7cdP+a2H7/PvH3+bf28mFfizz6/47/Xf4OMfPMQuRdC0XxXskk5BUSSnGhvAQWycrOe9qRO1ko64jQk9tYe0iGTBBqFvMZ63TtepAoSeYX4O350b6vtuDyoxB+J99EV+cxZD3O97Z5SVaRXsFFFLZ9/VciNuWoOpPEXhmU92vDNb8GiyoDmVuVBpx+vPzfl0dczttuZuU+EWJeVlQ30tmhKTl+cUm8D0/QV6uSWuNsTbN0SfW/YKay1HZ6fYrz9he27YnY9uIr5SaQ7Imo63ltu6ZnEhb6ibDu81k7qlSaiXxvRMEwznpNjwuFqgiawmFbfzCZ/Mj7lrZSK8WUzpNiVxY1Ct6OAEpwhFEk+u9vbqjBRLGhN5oCOG2EO/LVgUNWfNhpNyO1jxntdrXh/N+eTohPXrmvJWY9cMCJpsqWxaJbauM9lrszAwGWlkx3v52cjuHoKUEDHMYR550FENyD2V5smgU4QgqEj0zODFBSTsRkRZFuCEvXW6D5YKspbt5oCsOMQhDnGIQxziEG93vNWaFdXzZ/HJf/1foJzQHgDMRpL2nPj7Sji+g9tHEQeKQyij2Ewq7ukniE6DQnUKsxXhx2hG7nZoIjELSAI4yT70NukMtMndYR8mXI4PuFnHIqY8XiWL1SywlosN+wWNaMdr8KXwk900iVcmPr7Z5gRydDzJmhjZ1lHvJVW+HPna9wo6ZPh/Pl+Bw9uNCHuCFDm6kzjYpiqdqAZJDC86NdgaisUfksGlotAgpqmj3BObihb5HPz+0/eeToNXqESLsBtFsRSthlBAdxpxR57JA4G9f+7shvdm10xNSxssr9s5Hy9PeH0zl2t43chYdYrqBprLQPPGY1eSFeweVqyeGBZfDJx/5Q1fP3/JlyeveacUB4ZnxRvmesdUOfqoWcSKl+6Y72yfAvBHN8/50esLutcTiluN3ah7VIp8X4U3n+ZWAWESiHY/ORLYeC6i6XK8iVqLKGFReFTi77etxSdRxLAs0LvkiNCnoliaX5ALVCk5z4UqNQoKDsULlQp2VVoH2fmmlblRrIXyYjdxWHsZ1h6VGrUycvFDMdh+Rp10WirRWnEPO77xhU/4T5/8AQDvFVd8t32X3/nkN/jRTx5jk1OJO5IT1rOequnxXuO9FivdoPGdjEGMpHWtBk2QgfZFKuI4EYLMBRlJ+NQe7UUNYrg/K/ZpJNlxRH2GKhJ1/ExSqYYENX8WxnUbbEpckb0j09lU45mfbDhudlw0Ir7wdHJLpR1tsLj0Rbddw4vNEXeJKnJ7OyWXdl35AAAgAElEQVRuLJMPLc1VZPLaM/lkg7mS4l68vcPfCqXDPn5EPD2iP5/iZnIj2xPD5oGmPdujjQUGXQ+X3IhioocVk56ychwne9bzZsODSvRKGtNjVGAXCrZevv+6m3K1m/JqOef2zYzsKDIMYhFlrwVUGVAmUFQOawNNKWu2c4btrsDtCtCRyazlZLLl8VSucV60NKbnqp3y0fJE9D9uaoq75Eyzk/lstnnPJ1HpxkKCaBql9Zn39HxfAyinwSliEdIcUsPePvxu2KNWRZV+Lw2aRXGYZ3mc9d5c1J0aC95xnCtjgTFiN7LWv/Pf/vZBs+IvKA6aFYf4eYoDLeQQf1niQBH5yxF/dTUrTITjnhgVfXqI7XuNWZlBmDBrUOwnDflBTztFcFYSlCwit6eZMIjnRTWKnCFJhq8RQbwqoFLyGGxKjgqDzXag0rBFewb0xj3uvI5SNNkTOBTBTDVY2g2uJdkNZCtoCd2LZoWvxNLRZeRFLcUbs1ODk0TM2hlmHI9oRgE45YUrfS8ZU+nhvAiEicLNFcUiPXRv5fuj1fhCoyov3PLs0FAq0SRdFbDTCSmhBqREVAqdHBOUl6Qtxjhqa3i1h6pILUgbUAXDe/pCEY0ZUCX1a0W4tXR3RwB873TCTy9OeXK84Mnkjsf1gnfrW7bnAm348cMLFl2FD5qbuymr1zXTnxac/CSJFr7c0byE6auam8sH/B9Pz/g/H32Od08lofvmySd8a/pT3i1umOsdj82ax2bNc3strzcf8Senz/ijh8/54PqMzaoibiw22VHalcLuoLwT+1UVBIHjGjOIgIpOxaj5EVUk7MabFGzEWYuvHXXTYXXANh2+lmtoS4/rDP26SAUtLRokm5w85XsAII4hOjCgIob5muaPbxSulvk/zOEk6KcSmiFz6vukTRoKERmNZvyufSSCToieYgnlncJfVXz3+j3+u6/OAPgPn/7f/PrkJ/zH7/wR/zO/xo/MI+zrYkgwnVdseyPaBUERE+IibpLFrEswhj0703uIHZtcPlJS6LVoCCgTGYURUxHBqdHtJ4v3yoQeuuHRpGOFyH4tOOa8O4mWRj0WfQZrW6mpjAW+/HKvRIwyKOJWs9jNWVQTXlTiTPP+9JyTyZbG9syKltNyw8N6ycN6SZEGe/2kYuVKfvzeBZc3c64va5qXRzSXUrybvnjM5IMFfPgJ7uUrePkKDVSVCKVO3nlM+bWHLJyln6khkR4KxZ1J+1kqWOqCnYmsJzIRXjSeatpxPN0OjiJz2zK1Ujl73lzz3uQNt/MJ367eZbGr2GwqsUIl7Z9pjsUIWjPYmb47T7oZKrDuKz6+PWa7rljfNuy2JYudXMNJs+PxdMFpueH0fMP2pODFg2NeLWWubTcVu0VJ9coOSDJx5JBrzEK03kgxgiIjO9I9dppIQEU9IDBiEVFBxkQFMBl1luaBirJ2YtZdKsZ9OmtbRBPHgnsS5MxOVsonpEbMKCA1uAsd4hCHOMQhDnGIQ7zN8VYjK+ovvhuf/jf/+f0fqohrLbEzqK0WmzmnRkR4piOkBzrl1GD1BiMSYV/NI5KLG/kHklyHAvzUQ+PRRRgSodAZzK0V6O9eYqb30QIp+Qtl+p4mDN1e5ZMDhEvn6EehS2AQ0hwFNMHVcVC0j1ZQDvnas20fKu5RUUYnk5x8uXoP3pzEGP0kEItET9ARldT2q0sjQqJlpD8OxDqgGzdQErQJWBvYbUv82goaYg++LeMiqAYROxQI/gCrz/dBIbQbHaEMg13lMIy9RrUGu9BU14KyGB7qS+hnkf40oE9bzo7XPJyueFBLN9pHhVGRUjtC1FzuZvzg8gH9jyR5O/sOzD9qMTtHe1axfmTZPlLsHiTBvyc7fuX5R3zz6BOel1f8YvWCWnnMHv9gGUo+6C/4sLvgqp/xqp3zw1uhiVxeH8GnNdOPFcUmDufuK3ERgdTVrcbOPSlBHMRWrdyz0ATMUYcxgaoaaSJWBzpncU7jeovvtCBeUsHDrnUSeJXimN2kBC05mmQkiPZgumznq3BNOn4hFKUMfR8tHBnpA6W4oUQzzi/Vj6gDlShUps3Hj/hSsXknIT6+uOLvfOHP+GvzH/GT9iH/2+uv8JMPHlJcFcOxpOAX2beDtIt0jduM7IhDR3sowJAKQnn992qkiOm9okZGQHVJBDbmdTau2UE0UeWx26OixPH1gYbzWdtQNVIBBuHEFMN37aOy1Fi4iyY551QeW3maScu8bpmXLbNCigHHxQ6tIloFumB50055uZpzu5Ab5d7U1C8MRx8Gzn/vU9yHH8Fnfj/YZ0/xD4/ZPJ2yPTNpfqa5Wu2tWTOiAmIqiUcdcQ34uUfVHls5qrpnWsnGdtZseHdyx4Nyyav2iJuu4Xo3Zd1JcXG9K2l3BWFnBHlVRHTpqZuOZ6e3AExsR2k8n66OebOasLlr7jlp6NJTVo7z+ZqLZs07kzs0kTY5mLTB8GpzxI9fPiBcVdRXGrvhHiImlNAdJfHmKgjiY3gRWV/9SPVAkehwoHd6QL7t73X3ItOE4jiOvon3UDiQ9/c0xp8BopW3ska///cOyIq/qDggKw7xtsUBfXGItz0OqIu3J/7qIityqIhOSYW1kiT7WuMnBt9pSdDy09zeg6vqFHal73PTUyIYyij0kWTBSWBwZTC7mJAL0k10nRbV+AwN1sm2M3VINSM6Ih8jd5PjTuELUEGP0F8TBWpfxUG7QfdjBzNqJciRNgq0P3XK3URed+nBNhrR2Yi5O2f3kAtBEi90smFN1IDYj/B/VUai1kQnn4vl+HlfRmxGVxiN71XiVyd+ee0GigJT8MoO7hByDcnGM7lx4FTqFqaH+kxJAKKHaJQ0VUMWBABVBLFQrT19YUFZisXo4GDXMi7lwtBfTXgzr7k8OqaYSXI0m7RMyp6jasejZsnXjl7w3uwN3zkVN5AfHT1h+6Dm+H1PddNz9GFgcmnYnUlmu3kx4Z8uv8CP3znnK2eXbE4rzuyKcyPFkBO9Za47vlV9wq/WHwOwi4Y/O38EwJ88ec4/Ov4qt+Gc6SeKqo2YXRTdlWTg4FIyGLNuyWcSQHGMUfhW4foKbyP91KJSwtxMO0JQlKXA5WlAqYhPdJ12VwgaIa0R1Wr0VlPe7XHsEy2hWOd5GAdkhui/5Hua55nc06G4B6DUQCPJlr3DXLDgo2ghBDtaA09eyDF23Zx/0P4St19q+Mb8E3717CNaZ/nEXQBQXBuha3V5rUiml8/RrjNkXglyoRiTQYB+osS69sTdpx/BWHioArFCnCucQvV6cAfK6ynbGw/uDG4fViEHzKiLuE+HSocRSo4aEv5MJ5Hv514SqwcqS/qeDoLVRGMJZWRT1KzqyKfluC/ZymOs53S+4bzZcFTs+Mb5Cs7lGDfvNlx9ecanVye0x085fv8R0z/+KXEnCypsd7iPPoaPPmb+6WPqzz+iPa9oj0br08FGs1K4KTImXR4CSdI7pYk7jbOWXles0rbwqjjhR9MLHpysOG82TGzHO9M7SAid265h1VVShLhpwClCb9k6zQf+TOZC4TlqdoSomFQ9/UR0XGLae8KqYLco+OSu4uXkiE+Pj7iYrLlIBcyzcsODcsW83PH+0RnX9THVK4tdJ/TILt2zqPCtwddaNH9yUSsjh/J8yIievLfbKDbbiqE4LVZIe2siJnSTl71PCh7jvAwG2QNjKlykItZwjJjW1GRv/h3iEIc4xCEOcYhDvIXxVhcrYoQY1VCoAPBeo1LxQpcOYxWh0oSUhASXkjIboFb01qA7jc4c/sG6NHPzo4hIKknQAWKhMZuk4bAS5IKvFb5OD8RTL8l9EwW5YVSCEcvnTSeJt9KS/NlExcg0ELHPS4WShGoITuH3tADMVuwwc2fN7omM6lYRqkQPyXaJKhUcBmE4EbMMpULvFMV6LFoAxIGiIcURVExFnDROfuwQ25XCtArXKUIaA98rtqVB24A2EVWKON5gbRoV0Yg2g7JiPRi9QiULQbXTgmZJ9JmogSD2lxmdEb0iVB5lIqry9Ofga4NdpiR1myz/HJQLKNYaf1MSChnoRTPltg58UgV+NO945/yO5/Nrvnr8EoDqa44fnD6kPZ1w8sOK5spRrOQPQHNpKJeW9atz/uDpEYsv1Lw3veZLk1cAPLBLHpgFD82KR7pnri2NKvlKIZacf63+hKflNf+j+U1elw/wn2rKBdgtAw3DdArtZOx9mccuDnoP2d7W7BRma4ga3GwsfK07IyiD2qF0oKocpXXUjUtzVeGDpncGrQNGR5abmu0bgU4op4ausPD5Rcw1a17kNaN8HNFHLt4TkRVOflpTpRp0HfI55uQtlKJ/4CZScCgkf6S6AfXDht/tf4Grz8/46tFLvnr6imWC9i/dEWYt9BYVpFil4h6qQSU9jW2aw0UuFMgbykKhvWI9V6g6oaQQcceswaJMkDxUpwKeImms5PWEFDEyjyPmH45jJNUGxgLIXgEz2ySrlIBmyojaK/hkZM1QaMl/GPcA+X4FSkmxVZvx86n4ejmd8nLuKY5azo42XEykMnZU7Pjq6Uu+eHzFd04e8+HLY2Zf/yLNpRzk6MMW+3t/Suw73IuX2O2W4viI6TTBbJQiFoZQGty8ZPVuST9JxSuk8CbXnPbCqNBhb09wBlTBy3rCi7OO45MND2crHiRdjncnd4RGcVTNeZ8zdtuSsDPEVtNu5By6oFjVE1QZmM53VEnTwhWJMtRa4sagFxbuLFdXFZezY5ojKchczNc8ni54XC85f7TmB3XLh8057pXMtfJuRACZFsJGCboo/W4IxXibxeI0DoXiHKGMoBWEuGdhq+6j+xJDaEDCOQaEnDJJ5DQVQ/Jc2ad9uGakFR7iEIc4xM+Kv//8dw/oikO81fE3f+s7B3TFX4F4q4sV+8lATP/2Tg9Cg0qHsbtPFtuLxF6hVEQVkTgPBKcJfW5L7X1vpizkw6ROatCRaDRonTq6kthn5EOnEBSCDfJ3ocQ5pErFjL3CiElCaHrPoUEcFSIh0U1y8hLTA3GfqAKhTMeOpHOQz9stxC4XLaT7DVIwCVmELTuaNJ5YaFAaux4V57Ub/8j1y2vZySLqhH4IkiTFPh0vdbP9RI4VqoBrkhipjqMmiFNSBKmDJIdFIAYIOamwEd2JMKTqc2EkC3WmRL1XxF6LC4QCVQbCLNIn7RDfJIHUfhQctZsRRSOihoZoDL4q+Pik4acPznj0UPjvnz+65pfe/ZQf1g+4ro6ZfFoweW2obpILxbLn9PueyWXB6lXJdzfP+fGjCz54IF3e59MbnlY3PCrueLe4oVCOx2bFO2lePTINf3f2fSafb/md8jf5wckj7GVJeasGZ5mBWpG7tGl+DmJ6e3B7u0n/DnpAwDgnQoO+NkQbcI2lLTxdchRpyh4fZD2U1nPebCiN50bLZPReE7xG6YjrDX2vUWuLToKBKohgoElCm3YrFJLsspLneRyYKePPcqLtSyXUl6minyr6WcRNAiEVZ4qlolyC+knJ97qn3L1X88XjK54k15fdg4KuqjB3JjmeKAiiwQLQz+UYdqOk0GOlAJSRF9UyYDpFVJbtuwrOWuq6JwRF38kWGbwa9hhlk4tNFj6FVFlQw14xFGs+A8/XLfK+RL0ZNGzNWJzIH8kOPfLhscCj1F5iqsb3ohh0DNjT/x32FR+JOifXGjcpuJpPeHV0CoCd9hzNNzyarfg77/4Ztw8n/OmzJ3x6LRow1x9NeXz+Kxz/k48JdwsR40yCnPdCKaqTE+z6Ge1ZhUvONP1EEywUq6S1U0MwcUyqVV6jmrCsuLsruDuZcHki0IoH0zXn9ZoH9YpwprjZNSw2NbtNSVjJl+hWwVbm/MppikmHtYGilKqPLTxdafGLErPWlLeauNB0N3KfP5pOeHl8xBceXfGF+Ru+dfYJx+WOH0yEurW9rTF3lupKi2tIcg/Jc3UQkt0TpY0GERxG5nwo4yDWrDxovQfzSe8BCOn3wIA2y2uoU4miNArCBrP3FUrm/r9ALznEIQ5xiEMc4hCHeMviIMF1iEMc4hCHOMQhDnGIQxziEIc4xCF+ruLtRlYgiIoYFCF1PaNXqfUIBEPQEa3inuQh0GvpWtmAKQOm8lAlG8TUUXad8P5jUMStHYQ45U0Qq0BvI77Kgml7Noc7QVKEWieLU+n8Z467q9XgTOAak2zm1CCgCanr6kWzIuaObWrOBRvxTSBqPTgvKDdqNWQFe7uD4CHY+5BhSMKIlUDuMdLtcxFMPoetOFXcc23oGOgyoWTk1idKiAZI1qZhJ8KZwRrcVOMnkVDtcfw9mLXGq6S7WQr8XmdNDKuTq4DFJDFQ3at7DgrKKWKnBpi5b1LHMUGuQ5WvNaFEenUPgaI70Ns4jGF4pehe1Fw9kA9ef37KO2d3PD5a8uJLisXxhO1DS3MpXdzJS8Psox3Tn66pLwvspmbzZM53ngok/f1HZzw9ueO42nJc7GiD4VG15Iv1awC+XL3kPbvl35l8wNHzHf9w/g3+9PoJr6+PcFs7zFWSlkd2osidVpA5FA0JyaCk07pvg+llTeggdKTYanpr6U0SLaw9MSiUEvHBXVfggh41YIwjAkZFQqXQKrKdFng30gtCVPSdhq3BbDR2rbFbBnSI7qXzK7ooEdMLBSMjL4ptwBcKv0noB6/oTkdLzGiguBPb3OkHlhftQ+6eNVzMhL5wcbxiWfWsigmxTRo1QJxJN10XgW5rRf8hyFpSraZIdKHJp4ajjxzldwN3G8v6ec3uCdR1T5nESttdKfoBIBSyQR107z7omCxnNdGD0uN6y0KlJol06j7dm7weg3ThsQxipaEA9qD8KusX7HXQBymeEkFraCBRw0jvH85BCTJJJ2chu4byVg/0NV8WLJuG6+MT+q8Ynkzu+LcuPiScy0FePT/ijz//lLv3PsfZ9x3T710SPvyE2O9tXAAx4m9uMH8WmE6nUMlci9ktaVLRPpywPbfsTjVbkXDBTQOhFLSCXSnq3uCXmtsb+fzNfE49b3l8suC02vBk2jMrO+7qmvVE3rNdi+OO3mjUytD3Fa4KA7Wnqjsmk5a+8LSTgl6XyTUpz1VDWDf8oH/E3YOab55/ypfnrzkqxX716nzGy+Wcm9kR9s5SJFrIoC2ShWYTfSfa0Z5WfpD2qjpRDNP+N/zO2o9MF0q0qrye8n4blXxfSBS9fRRONCNt5BCHOMQh/rz4+89/91/42YEacoi3Kf7mb33nz33tQBH5yxFvfbEiOEXAjLagUaHMmBSL3sFnILYBlNPEXgkEPtvPAcqA1hGfbBCJarB8zLDaUIgSPEUgFIGu1uidUBbyMbLtaYhakmcTB1z2frITbaSvNH7v85nXLc4De3zkrMdWgp8IlSNkgbU6Dqr8GZKf9Rp0ohHcQxp3EDcKszH4Jg7QZb+nSRH2RUGjJETDA3E+p88kY9lFQjuFCpGoRA+jnym6EwbHknyeRENIFqy6FP0JAFMEYhHw4tuHbhWmi8Oxh2MGScZVBNUbobdk7ncWIUxaG8FGtEX43uTkWVGsI7qHoo0U60iZ7FlX3ZQP3i2ZnW04nmxp3u1ZnlYsHkkxY/ugINiG+cctxfWWs+95pq8q1i9lWa2eHfODJ1P0tMfYgPeauuk4mnwBgGfzW/7a6Y/5G5Mf8LXyFSfnG74ze5fvXzzmuhPo+6IXj9DOG3auIETFti9wSUuhcwaloO8NbWuxpYOoBleWdlnJeLm9osf/096Zx0qWX/X9c353qVvLW3p909Mz41ns2B4ba2zMYhscY5YAQTEoRnFEYiRIHCWgBCUkwRBFjpClgILZhEBsMRATVodYBERMTIAYsI3t8SyeGWb3TE9P72+r7W4nf/x+91a9nu6exd39+lWfj1R69e69VfU793dv1e93fud8T+GQRnNiGDV6pahLWO+mvupL5js6imtcVBNFPqXKRZUPqw/lQZ0onaTEiVLVjmkZMRpmTEehAgx4zYugyRAFHZFoKsSjoBmxpcG5pl40Nne4Upj6bBrqRCmWgS0h3YClsWM8Xuapo/5iPXxwk+VsSrzfVz7J8wjnlOW+997105xuXHCku8m4SohdxWbe5ezEV8F46tgBIOXQ3UPiYUW6lbI56TJci8lW/Ilyrgbxzpok9eKxdT1LDakqX8IXwndOJd4hOrtdoHRoHBGNQkqTzmkS1CElIp7db211jbBBGhHauRQaZO79Fa+T4ObujTknJ4Syl6EKUDQNDrvga0hD5Y+qE/PY5CiP7j/MLTee4ZYlX4r3lu5Z3nTno3x49fU89oo1Bq88wtLTa6ze4/fLJPdiC5MpOplSnTt34TQRIFtdoXPTESY3LRFNvdH5iqPszzQ7wE+40+Cc082EKkl44kDG6YNDVroTOnHJ/u6I/V3vJdVVYTPvcHazT77Z8aK+paMKF/m4cMSdkm43J0sLhknldSwmszK3birImZRn833kZcSrD5xkreNTjm7K1hktpzw4WOP45jKbJwdEG3Gr4SLNd1FIn2v7t9XlCJ0WHAp1HNI5klnaRlvppelHCaKtoX8inf0uSCi3i+qs1Hbz2jlHs2EYhmEYxl7kijkrRORm4FeBNfww+udV9SdF5H3APwVOhUN/UFX/ILzmvcB349cE/6Wq/tHzfY6GSRg7JgZecUwi9ZPqapZL3qqzB1FLxaGltIPKPHG4uKbOo1YfoH1pyBmOSv8mVQ+vY5FV1Endrhy6ifMaFKV40fZYdopbNiXtwDsw0po6UjSeTe7clDb3vlGKb3PPS2/PrMyhX71rJvqaeAFGL+w5d650bgBbQxyE4uqh+KoTXZ2LvFBKnWlYSLPSO1edoMmTR/1qrZT4KBBm7XVls5IuqHMUK/4NGhX7ZCjo2FGNHVUnnkVfpDUuqZC0ou7jdTVGbucAXHeel0aMc361W8KKdRNtUIfz0+x3uRcZbZw7zQOgdxykTBkOI/K1mP3LQw4sD2HZr+ifXe5zJu2Tr2QsPxHTPT6k+8yQzml/HWTnemyfislXYqrMd3WeZZzo+tKozy7t49iNK5xcW+YrB49wS3yOL+s+xh3pSYYhLGS96pG1yeoQUTOsO0zUL7lvVxmFRhQasVF2OZJuUKkjCR1/79ZR8jpmM8/YzjtsjDPG45SyCKvcYy/ASRUiN3JftUPHQTskFVziJ9ZJUlGrtJVGwinEibLSmXAgG9KNCs7lXTbyLueC6OFoklLXQl1FTHMHhUNyX3UE/Op+su2dFum2km3UxFPBhQiJfJ9QZkrZ9xVosjNKNIXR1L//SYH+YMJyNmXQyal6/p5d7nhnReoqbumf5Q2DJ/25kZJ6LgPu8RsO8aHsy0hGPVYen7Dy2JR4nLK5nTK6xR/nBgUSeUdFP8vJYu+gKerGaRRTB93EqvaipQpt1RXx80mm/YRiFFOOIqKJa52BrpxFK80Ecc9zVsT+u6FxVuxYN290DlCI/HebK3dO/L1gr39eBYdRE4UFIfKoUOKRsvS4ozzZ4alzN/Dkiq+60lsZ8y2338+33/hpnj64n3tedZTHz+7n9F2+nEgSKsh01pXsnLL6mVPo08epRyPOp1rfwOUFvTPLdB8NNW6TmPzwgNFayvCIo+iDpswiDGohGgIaM8yXGC5ldLoFvWxKN/FG3DjY4JbBObaWOzx67iDnNvpUo7lKRNsxxSiiLCK6/Sndbo7rTZkWQZukFvJJApsJbjvmbLnKZ6Yptx88A8BatsVaZ5M37v8Cm8sZDy2t8fT6CuNtf79q7suqSh4c3VWIcmjLTofzPAUQJIG2nHYTMRXRas4gvt+Cz9Z/RuSFd6Wc9W3zf/Ma1JcLNjxXazxiGIvAhaItwCIujL3HpaIuwCIv9gpXMrKiBP6Nqn5GRJaAT4vIR8O+H1fV/zJ/sIjcCbwLeA1wI/DHIvK3VPXi60OiPkpBBdVmtD5LeWjQyrUDXpfUaKdCNfKrYCF6ol2pbAQbG++FU7RTUyaKm4SJS+5jdyX3yv+a1P64RoBT8CKezcp+ESbyc2VB/bKYH4g2VQU0hCpr5Cf2GgXhzWYyMVdetYnCaJuZC80SeVO2tAn5bss2ytzqW6i04cPBdVbNoymJGaqRVFEQAax3OjNEd55mKWmrh0BIRalmwpZSKcm2tJUYqq5Sp7590dhXfqhjoQoipFWmVL0IzWokrdFYvejmyM0i8CvvzGnKzbarmLrz79zl4qMsXBPV4iu+1J2QHlIK8dg7cMCX6Oys+9DwSdXjZBGRdXNW+z4kfG1li3MvL1lfHjBdTVgdLNE9VZCcDSv6Tw1JNzrkqzFFV6hS8RUvukFwcJByfHSI/zXpcOKGZd688gg3J2dYdSNWnW/EajQkk4K+FOyPCnoiRAiJzCbbhfrrpkA54LpE4qjCtmPLn6NS2NKYU1Wfp4oDPDk9yGbpIzaOT1boRgUbRcaZSZ/NSQdVYZL7Wa2IkqUFS52c5c6EWComVcK08hdKUUVUKiRRxc29cxxMttgoe2yWGQ+Jj+8/2b5fhXT8faI9qJd8h437MeVmRDEQyg2hs16TjJTeyVBycyLkK0LR91UOqg4kI+j6oitUnYytfTF6WOh3ctKoIomqNjVsUsWczfs8OT1IHa7YlWjMjck5AL5h6V6Ovv4cP558LeNPLbH6SEX3TElUREi4IcY3OKqlksLVrYBrFpft9R9H/sJXFSIHVa1Uc5EXIkrsanqdgrLvyMuIfJpQzKX7SCmtM9CnV83uJwn5AjvKwfLcax5oJ79tsFmY5NbxTJARvIOzcarCbJXe5ZAMlWRTcIWjfjYN5znld6d38TV3PMyXLj3Ba294mslawmdufhkATwwPkLqSZ4fLnN3uceY1h+kdX2PlSe9s65yZ4rZz3MY29foG9dbWcxwZ0edh39EbyV59hMmBhMk+YXIo9ONBH23lHc4KlTAdpkw2Om3J2ZMrA1526Ei/zo0AABvRSURBVBxr3S1ese8UZ3tDzoz6bI28M2G63UFGEXI2ZTSMkW5FnBVt2lMnLel2CrbijGo7wY0iJif6PDD298Pjvf0cGIx4xcop1jqbvHLlBCudMRur3nE2rWKmVUReRqgKeenTCadN9MgwxW3GPvVvMquqo27Wt3US+irx6XlNBaT5csVVpq1Aq1QQVTIrIRz6vVgyZ8UcV348YhiGYRjGZeeKOStU9ThwPDzfEpEHgKOXeMk7gN9Q1SnwuIg8Anw58JcXe4EIxJ1yR+nSsox8VMQ8dTg4EKU1FSHiogrOijbnOPzfvGfsHSJxWlJXYbV5EkHu/LGlIE2qR9w4Gyo09e8tRVhhmwvdlgofri3BiRHCt1uCg6TqQFXPQojbvOp8TiMjDqvbxczGZhWu6tXU5c7Pdmm7BOsjL4pQ4nOircMAoKxpS/Jpk1PdOFeAupkrBweAP3+zfmja11TiiCb+bzKchSpXXaXMIM29HkGbWgJUmVB2I8qBo1wpIfERKFoK2vSVC2kN4f2aiJUmJaixu3EOST2LoGnOExGUXcUlgCp1Km2J0HTLO7HiEWQnHXmeMe6mjJf9RL+3PGG1Nya9seJst8/pXkb3ZIfBMX9bdU8XROOSLK9IejFVx1HH4jVLgGIoSBWxVa3w55OEEzcs8cqlE9zePUUWwnhGdYeOK+i7KUfjcyy5Casu52BIZRlIh4HzzolozoHRcEs8mPuvosiOc7r3GE2sxrB2rLqa9drxTLXEU8UBzpYDnpj41fJpHdNxJYfTLQ4nm9QqTDThXOnTVLbLDscmq6SuZC3ZZC3ZoOdyMtfnmXQFgHNxN0RXuFA6GCTSWYnQQUnhQjWQJUfRd2RnlCSkiXTWlWgCbp9Q9mCyX0g3FVf5/dlpX7VjWweMlwo6WU4nKYka553C2XGPJzb3U9aOqnZ0k4IbBz5F4av3PczX9x/khrvW+cl9X8eTh29k9cGE7pma3onmpnVMpwnlNGK9kyKxb38TZSJB60ZDREWTCtKUPkX88WmnJEsLep2CTlIySf0kuKocZR5RTyOvUwJQS4jiYqYLE1JqUO94aLu8qTwSPIjzZSzbFXnXfJeoPz5UWW3LhuC3SemjwZrUrtj75ki2gAd7/PHwTh659SCvXj3By7IzfMXSYwC8tn+MZTfGhVCnT99xG49uH+SBk95pNTrZJ3t2wOCpfQyO3UDvoZOUT3zhOddseewZOusbZFmH+pYjnHm9r0aSr4IcyomT2XxxOk6I1hPSc6HSUprwyHrG8cPLvPzAaY72NjiYDdle8s6KZ/rLnN3oU5/uEG9F6NBRduI22qroF/QGU5b6EyZJxcR1kGEMZ0Lq13rKU0mf0wf73LxvnX3ZiFhq1no+TSSRGjfnOSpDR4wr38+nxgOOry8zOdGHc6ECUznr36YP6sg7NvPlmcNC536TFK9zIrX/jWlLVDeXQ6KtZotxdcYjhrHoXCzi4nJjERzG1cIiL/YGV0WzQkRuBV4PfAJ4C/C9IvJu4K/xqx3n8AOHv5p72dNcejABQBQpaciZByiTiqkk1LXzOeHzaQHgQ/+jGumEnPPS+WOCQ0AdPrVDfEJ5q2UhXpwN8GX/ioiqcGjh2pW+KERWSBD01Ob9K5k5RkIbWsHMMGnYsTQaeZEJadJGwq4m6qAuHG7sS5EqzHQjGoHP3Ast1on4EGL1+hGw08nQTGKqTEi2fSpE04xo4h0gVSWtNkVTphL86l7rHGjeay69pYq9OGidNn8JZUT9/mRbQq62F1J0uRDlsygSXwLTh6pLGVN3vKhou/Lc9FX4zCZKpgmh9/t9NIm6tjv9eaobp09Ib4m19bNoPNPVKKuZIGc89qvOdRxRn/UnbrySMD6YkvVyOt2C6c2wuZow3e/3d09kdM/UxOMwKU+888O1QqzQOadI7ciHfe7f6PDo/oMcWt6ml/hrrQqTnUhqenFOFpWspmOWYh+9sT8ekkhFoRE9l3O27DOtY0a1Xw3/kt7TZK5gNRqy5CakQM6gdYYcCIqpfVdzI1sccCOGacIrs2cAfMpJnZBKRSIVkdQ46iB+6tv3ZOcgozplLdkInzNmyY1xK/48Hs62eaK3n41pxsaoS36eM9GlNZUodRFRdiuqbkwxEDrrTZqId1x0zvm+m+6HSSytGKwrIR4KejqiHDlGvYRRUvuoK5ilR1X+fiSUhHym550pT27uY/umjK/oPcp33fxx/rD3JfzFodvJHs5Ig+SCyyHdEHQYUcdRENOcXVPzpSNFva9Tmqim5nqNoEhh2q3RTo0kM+2aKKmJkgpx4WuiEjStqcMN68KNpbWGa91HLjURZU07NMJHVsw5LOYdF35SHFJEmjSupn34e1ojP0mGkF7QpL9NIRmCeyzhC1tHeGL1ENnSlNtCikQvzoldzQ3ZJkc753h19xle3X2GN+/zDrNjt67y0NYaj5/Zz+kzPdITR+k/c5TsnLchHtdkp6YkXzhN+fQxGA6R7SEH3MvD5y+xeVuPyeEKHVTEWelT9lIlZEX5e+pEzHiyxOfziAMrQwZpTj/2F8vh/jZZXHIqGTDZ7MDUO50bfZW6TtguIjqDKXFc01meMnWgkyZFzxFtC5O8z8Mjf+93OznLmb+PunHBIJmSRQWJ1HSjgliq2f2ajjiQDfm8u4Fh2qPaiEi2ndcOmSsb7dNF/HdY1fXfw3XapPmFurQxqKhPMwzf9d4IkH45c3oZO7iS4xHDMAzDMC4vV9xZISID4HeB71PVTRH5WeCH8WPjHwZ+DPiuF/F+7wHeAxAfXPETZWhXOLOQu1wUEWVTsSDSdsLfhGXHSeUHgAJ1JW1kg8Y+8qJ1dNSC5o6CuP2MJKlIk5Kq9lVFRLxDwLWODW3F9VSFsnR+VTlM0lRDDnuTsgKzVVFoozokqplfLK+j2WC1iiJ05IIWhBfmi5o0kRKcl4r3A9k5DYf2eYhE0ESpMl9RI96eTQCjCchU/fsH4U1RqMNn+BDlINjnQnh544CBNtqiWRFs0lIa8cp4BCpCvqJUHSiWoJ7MqpFI6dMwoqm3q8yEYkm8k2ReXT9IlvhV52aSNoveUCeIzmaRUs+iaKKKILY6O75N3SFMQBvBw5o2+qMh3nZMJxmjfQmuX5B0SjStyPuNYGDK9KSjs+GIxtquWjfOChVfsUVqJZoI8Sgh34p4ailD5iqnqDKL/hFftaO5jqO4RlyN1o4orphud6AQpPQXzh8cuJMkqeh1Cg71t9uJ1CBM3m7r+lTtnsvJpOBAvO3/Oq/LseQmnCqXWa96bFQ9nNQcirdYjfz+Q9GIJTdmve6x7Cb03JS+5ByItnld5xgAw0HCw0s38MhkjQe31jg5WmJcJEzmdAK0dmjlU6LUleRdoeyHSKbM0XsW0m1FFKb7hHKgVOE8NqvT8dCL0lYTQeOIqtOkRdU+MqeSmdBhIdRBl+PZjZQPTd/IU0f383Wr9/OPDv8lt/bO8JGV1zJ8aBWAdD0Ig241KUc7naAa+Wuk0WJpohJaH6TSOinrNKLqRD6lJQuOseWKaCUniis0hbqIvIBvk7IU0kTq2AvNNulczbWqyZyuQXNvz9+PzKV7NNFaqjv3Cz5NxhF0XmYaF+Dvr2RbiIfQO+6oT6XUacpDqz7Kpu7UiArarciWptxx6DS39M+xlvqogzt7z/AVS4/CjTCpE85WA47nqzy8dQiA+48fQR7qs/bJowzKkvLZE+h0itzzMAD7/qbDvjtu5uzrVti6OWJyY4RbzWG1YBIm8vFGRDQRkg1HUfd4dj3D9QsGS95ZcHhpm4PdbW4arHN8dZkzwx7D7Yx6O6Q9TR0ydUxLoRwU9HpT4tW6dbAV44R6GBMNHXWZMs5ixp2Mja7/3UmSKkTO5CSuZpD6+60X7rfVZMzLemeZHEh4Oi7Z6PaYrqdeQHjcfDf6c+wK/50jta8MVTXfSyGST2OFJgUxoXXOaenI+jnjkz2MnVzJ8cgtR/e8XrlhXBO82AgOi8QwrhTzkRcWZbF7XNGlFxFJ8AODD6nqhwFU9YSqVqpaA7+AD60EOAbcPPfym8K2Hajqz6vqG1X1jdFy/0o23zAMwzCMBeBKj0cOHYjO320YhmEYxhfJlawGIsAvAQ+o6gfmth8J+aMA3wbcF55/BPh1EfkAXtDqFcAnL/UZqrNIiZmQnc8Zdk5nwQqiyI5ckKYtYZ/MpQhIE6Uhs0X0kMZRN8r+aUnk/OfUTohcTVW7NhWlVr862URc+LbVbclMmWuA1tLKabRaC89tqt8+p82hTttw76bCyXyQhoR8ZlV/HuYjAmZv6N9HxGtVNOVLCcdLPUvLaKt7NBEqTVk9BW3+qlz4g0JI+nx1AwkCn8JsBdeVQiNf1gp1lqA5XrSwCFVV5g2dJ2jv7SjneCk0rEy38fvMVsCbdstcfyi4Stu2aeRXQ6uuo04j6JTEcYX69HaKXkTV88KljdrhfD57ex5CFEmde9E9jaNWHHJHO0vf1jp37Sp5GYecAxVKp7jtaEfofu4yiqRmkqWUlaPX8ZUs+iHNJHUltQqDaMogqP0diLfbj45CGMpEEzaqLg5lyU2onO/IRCqW3YSCCEdNSkUvXDQHI9+I/RQUepph3eFEusyoTKlU2vKrhUYz7QdRiGpwQh20PeqOFw8UBVd4W1WYCQqGsr7NudRCqFFcK/0gs+oKDTXtd4IUwvZml6f3rTJZTrg5PcvLsxOsLd3Ko12fD6FbUShJ2XxfnHfxzUUSedFD3alTo+EzFeqCHWUmAarcp6O5xOtQiFOfwtREUzUhZLWPFtK5yxbOi5hyc1EV880M32s7BGjno63UR0npXJpa8z3j2zAT4G2Ec+sCNJzoOvHvXU6FSSkcz5aIpabb1kaFw9EWh6IhmVRUCFt1wr3dmwAoNeK+0y8jX3bQ64KLoK7Qwr9ei5zo6RN0j/aZ7E+YTn2VmjipKLuhWydNSoX4SDNx1BIz7cwEYeNOzf50RF5HFFVEnsdMg2gqU9eW961DdFISVRBKOldl1KaNuVIgd/gsQv/+bQltII5qIldT1o44aJrU6kikohfn9NKCcadkksbUzO7pJjpGVFuRVZ/6M0vzmE+HayPxmigc8eW3mygi4+qMRwzDMAzDuPxcybjFtwD/GLhXRO4O234Q+Icichd+6P0E8M8AVPV+Efkt4PN45e7veSHK23Le5E/VOxl0x2RP2gmu7DgWdqiSNcee/yFu5tiY/5xKxWtjzH3u/H4IIe7atOG8WbRK+LzzbXhuW3e+bvZoqnKc32htHDDPM15tyqNKcE7MVyNonSBhMjifB8/8+1/oM3ac6NnEqNWGaBwYzf7ms+vZa2DOydEIzF3KHrnI30shs/dsHROXeF0jygmhTSEcu50oqLRCrJTSCoy6yk8gGwfPjvd0fgJaRyH8PtGZ6KGoT0VyOhNhTWaOL8Jnq4JESl0KOIc0+7MKl9R0Mh+aPkhyuvEsLL1xUPRcTi+akrmCRMpWJLEK3qtEKjpSEkndPsBPvnIiCo1JwgS0wFEhjML1Xqhjve6xXWWMq4SijnwKVetofO5J1lBKFXZO1NqJHOx0BMDMKdY48pprNVy/O+6ROQclsRKnJYNkSiIVQ01Zr3ps5+ksRai9HmXHNd/e0uE7QiNvj6NxKDTXRdiPn9RXaVOdI7Qh0ra8afOQ+UY25Urn7vuLoj716Tm+w+Z8y+wS3/H1d4FrXxRfuDG0odHgaEqi1slM46VO/JdRndVIVrHUyRkkUzpzJUyGmuJq/yajusPJcokHxjcCcGrUx40cUV5DUUL93K9/WV6i6Dlf5jXyt0RdO69FQnCABmeLunAvJUoU+c+MXE2twrhKGJUp0zL2Tug5JwPOO4matL5KpS1Bq3NpZBqOw2mbghFFNXFckcQVkatJXEUaVcThxDmpqXCUtX9UQUNFGiFkeO51OvcdfMF+Os9h758/ty+vc67KeMQwjKvP+WkjlhZiXAne+qb7LRVkl7iS1UD+HxceLv3BJV7zfuD9V6pNhmEYhmFcX9h4xDAMwzD2JqLPWdbcO4jIKWAInN7ttlxhDmI2LgKLbuOi2wdm46JwvdjYV9VDu92Q6wEbjywMi24fmI2Lgtm4GFwvNr7k8ciedlYAiMhfq+obd7sdVxKzcTFYdBsX3T4wGxcFs9G4ElwP53zRbVx0+8BsXBTMxsXAbHx+rBC7YRiGYRiGYRiGYRjXFOasMAzDMAzDMAzDMAzjmmIRnBU/v9sNuAqYjYvBotu46PaB2bgomI3GleB6OOeLbuOi2wdm46JgNi4GZuPzsOc1KwzDMAzDMAzDMAzDWCwWIbLCMAzDMAzDMAzDMIwFwpwVhmEYhmEYhmEYhmFcU+xZZ4WIfKOIPCQij4jID+x2ey4XIvKEiNwrIneLyF+HbftF5KMi8nD4u2+32/liEJFfFpGTInLf3LYL2iSenwr9eo+IvGH3Wv7CuYiN7xORY6Ev7xaRb57b995g40Mi8nd2p9UvDhG5WUT+REQ+LyL3i8i/CtsXpi8vYePC9KWIZCLySRH5XLDxP4Xtt4nIJ4ItvykiadjeCf8/EvbfupvtfyFcwsYPisjjc/14V9i+565VABGJROSzIvL74f+F6cO9hI1H9g42Htn7v2Fg45FF6ctFH49cL2MRuMLjEVXdcw8gAh4FbgdS4HPAnbvdrstk2xPAwfO2/SjwA+H5DwA/stvtfJE2vRV4A3Df89kEfDPwh4AAXwl8Yrfb/0XY+D7g+y9w7J3hmu0At4VrOdptG16AjUeAN4TnS8DfBFsWpi8vYePC9GXoj0F4ngCfCP3zW8C7wvafA/55eP4vgJ8Lz98F/OZu2/BF2PhB4J0XOH7PXauh3f8a+HXg98P/C9OHe+WBjUdsPHKNPS5i48L8hoV223hkAfryEr/VC/Fbdgn7PsgCjUVC26/YeGSvRlZ8OfCIqj6mqjnwG8A7drlNV5J3AL8Snv8K8K272JYXjar+GXD2vM0Xs+kdwK+q56+AVRE5cnVa+tK5iI0X4x3Ab6jqVFUfBx7BX9PXNKp6XFU/E55vAQ8AR1mgvryEjRdjz/Vl6I/t8G8SHgq8HfidsP38fmz693eArxURuUrNfUlcwsaLseeuVRG5Cfi7wC+G/4UF6sM9hI1H9hA2HnkOe+43DGw8chH2XF8u+njkehiLwJUfj+xVZ8VR4Km5/5/m0jfwXkKB/y0inxaR94Rta6p6PDx/FljbnaZdVi5m06L17feGUK5fllm47J63MYRtvR7vJV7IvjzPRligvgzhencDJ4GP4ldg1lW1DIfM29HaGPZvAAeubotfPOfbqKpNP74/9OOPi0gnbNuL/fgTwL8D6vD/ARasD/cIe/HaeaHYeGSx+nZhfsPmsfHI3u7LRR+PXAdjEbjC45G96qxYZL5KVd8AfBPwPSLy1vmd6uNmFqre7CLaFPhZ4A7gLuA48GO725zLg4gMgN8Fvk9VN+f3LUpfXsDGhepLVa1U9S7gJvzKy6t2uUmXnfNtFJHXAu/F2/plwH7g3+9iE18yIvItwElV/fRut8VYaGw8sjgs1G9Yg41H9n5fLvp4ZJHHInB1xiN71VlxDLh57v+bwrY9j6oeC39PAv8Df+OeaMKAwt+Tu9fCy8bFbFqYvlXVE+FLqgZ+gVk43p61UUQS/I/mh1T1w2HzQvXlhWxcxL4EUNV14E+AN+HDDeOwa96O1sawfwU4c5Wb+pKZs/EbQ1itquoU+K/s3X58C/D3ROQJfNrB24GfZEH78Bpnr107LxgbjyxO3y7ib5iNRxanL2HxxyMLOhaBqzAe2avOik8BrwhKoyleoOMju9ymLxoR6YvIUvMc+AbgPrxt3xkO+07gf+5OCy8rF7PpI8C7gyLuVwIbcyF9e4rz8sy+Dd+X4G18V1DEvQ14BfDJq92+F0vIKfsl4AFV/cDcroXpy4vZuEh9KSKHRGQ1PO8CX4/Phf0T4J3hsPP7senfdwIfCytW1ywXsfHBuUGs4PMn5/txz1yrqvpeVb1JVW/F//59TFW/gwXqwz2EjUf2PgvzG3YxFuk3DGw8MnfYnu7LRR+PLPpYBK7SeESvAQXRl/LAK6b+DT636Yd2uz2Xyabb8Uq+nwPub+zC5/L8H+Bh4I+B/bvd1hdp13/Hh6oV+Lyl776YTXgF3J8J/Xov8Mbdbv8XYeOvBRvuCTfnkbnjfyjY+BDwTbvd/hdo41fhQyrvAe4Oj29epL68hI0L05fA64DPBlvuA/5j2H47fmDzCPDbQCdsz8L/j4T9t++2DV+EjR8L/Xgf8N+YqXTvuWt1zta3MVPfXpg+3EsPbDyy6+19EXbZeGSP/4aFNtt4ZAH68hK/1QvxW3YJ+xZuLBLa/zauwHhEwgsNwzAMwzAMwzAMwzCuCfZqGohhGIZhGIZhGIZhGAuKOSsMwzAMwzAMwzAMw7imMGeFYRiGYRiGYRiGYRjXFOasMAzDMAzDMAzDMAzjmsKcFYZhGIZhGIZhGIZhXFOYs8IwjCuGiDwhIgfD87/Y7fYYhmEYhnH9YeMRw9ibmLPCMIznICLx5X5PVX3z5X5PwzAMwzAWFxuPGMb1jTkrDOM6RUTeLSL3iMjnROTXROSDIvJzIvIJ4EdFZL+I/F445q9E5HXhdX9bRO4Oj8+KyJKIHBGRPwvb7hORr77A522Hv28Tkf8rIr8jIg+KyIdERMK+LxWRPxWRT4vIH4nIkat6UgzDMAzDuKrYeMQwjItx2b2VhmFc+4jIa4D/ALxZVU+LyH7gA8BNYVslIj8NfFZVv1VE3g78KnAX8P3A96jqx0VkAEyA9wB/pKrvF5EI6D1PE14PvAZ4Bvg48JYwKPlp4B2qekpE/gHwfuC7LrP5hmEYhmFcA9h4xDCMS2HOCsO4Pnk78NuqehpAVc+GxYTfVtUqHPNVwN8P+z8mIgdEZBn/Y/4BEfkQ8GFVfVpEPgX8sogkwO+p6t3P8/mfVNWnAUTkbuBWYB14LfDR0JYIOH7ZLDYMwzAM41rDxiOGYVwUSwMxDGOe4fMdoKr/GfgnQBf4uIi8SlX/DHgrcAz4oIi8+3neZjr3vMI7TgW4X1XvCo8vUdVveElWGIZhGIaxl7HxiGEY5qwwjOuUjwHfLiIHAELY5fn8OfAdYf/bgNOquikid6jqvar6I8CngFeJyMuAE6r6C8AvAm94CW16CDgkIm8Kn5mE8FDDMAzDMBYTG48YhnFRLA3EMK5DVPV+EXk/8KciUgGfvcBh78OHUt4DjIDvDNu/T0S+BqiB+4E/BN4F/FsRKYBt4PlWMi7UplxE3gn8lIis4L+ffiJ8hmEYhmEYC4aNRwzDuBSiqrvdBsMwDMMwDMMwDMMwjBZLAzEMwzAMwzAMwzAM45rCnBWGYRiGYRiGYRiGYVxTmLPCMAzDMAzDMAzDMIxrCnNWGIZhGIZhGIZhGIZxTWHOCsMwDMMwDMMwDMMwrinMWWEYhmEYhmEYhmEYxjWFOSsMwzAMwzAMwzAMw7im+P+G0yO1aogAhQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "x_cr = train_seismic[:, idx, :].swapaxes(0, 1)\n", + "x_crl = train_labels[:, idx, :].swapaxes(0, 1)\n", + "\n", + "plot_aline(x_cr, x_crl, xlabel=\"crossline\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model training" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Set up logging\n", + "logging.config.fileConfig(config.LOG_CONFIG)\n", + "logger = logging.getLogger(__name__)\n", + "logger.debug(config.WORKERS)\n", + "torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up data augmentation\n", + "\n", + "Let's define our data augmentation pipeline, which includes basic transformations, such as data normalization, resizing, and padding if necessary. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# Setup Augmentations\n", + "basic_aug = Compose(\n", + " [\n", + " Normalize(\n", + " mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1\n", + " ),\n", + " Resize(\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", + " always_apply=True,\n", + " ),\n", + " PadIfNeeded(\n", + " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", + " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", + " border_mode=cv2.BORDER_CONSTANT,\n", + " always_apply=True,\n", + " mask_value=255,\n", + " ),\n", + " ]\n", + ")\n", + "\n", + "if config.TRAIN.AUGMENTATION:\n", + " train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)])\n", + "else:\n", + " train_aug = basic_aug" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load the data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For training the model, we will use a patch-based approach. Rather than using entire sections (crosslines or inlines) of the data, we extract a large number of small patches from the sections, and use the patches as our data. This allows us to generate larger set of images for training, but is also a more feasible approach for large seismic volumes. \n", + "\n", + "We created a customer patch data loader for generating and loading patches from our seismic data." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/data/home/vapaunic/repos/DeepSeismic/interpretation/deepseismic_interpretation/dutchf3/data.py:460: UserWarning: This no longer pads the volume\n", + " warnings.warn(\"This no longer pads the volume\")\n" + ] + } + ], + "source": [ + "TrainPatchLoader = get_patch_loader(config)\n", + "\n", + "train_set = TrainPatchLoader(\n", + " config.DATASET.ROOT,\n", + " split=\"train\",\n", + " is_transform=True,\n", + " stride=config.TRAIN.STRIDE,\n", + " patch_size=config.TRAIN.PATCH_SIZE,\n", + " augmentations=train_aug,\n", + ")\n", + "\n", + "\n", + "train_loader = data.DataLoader(\n", + " train_set,\n", + " batch_size=config.TRAIN.BATCH_SIZE_PER_GPU,\n", + " num_workers=config.WORKERS,\n", + " shuffle=True,\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up model training" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, let's define a model to train, an optimization algorithm, and a loss function. \n", + "\n", + "Note that the model is loaded from our `cv_lib` library, using the name of the model as specified in the configuration file. To load a different model, either change the MODEL.NAME field in the configuration file, or create a new one corresponding to the model you wish to train." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "# load a model\n", + "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", + "\n", + "# Send to GPU if available\n", + "model = model.to(device)\n", + "\n", + "# SGD optimizer\n", + "optimizer = torch.optim.SGD(\n", + " model.parameters(),\n", + " lr=config.TRAIN.MAX_LR,\n", + " momentum=config.TRAIN.MOMENTUM,\n", + " weight_decay=config.TRAIN.WEIGHT_DECAY,\n", + ")\n", + "\n", + "# learning rate scheduler\n", + "scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS\n", + "snapshot_duration = scheduler_step * len(train_loader)\n", + "scheduler = CosineAnnealingScheduler(\n", + " optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration\n", + ")\n", + "\n", + "# weights are inversely proportional to the frequency of the classes in the training set\n", + "class_weights = torch.tensor(\n", + " config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False\n", + ")\n", + "\n", + "# loss function\n", + "criterion = torch.nn.CrossEntropyLoss(\n", + " weight=class_weights, ignore_index=255, reduction=\"mean\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Training the model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use [ignite](https://pytorch.org/ignite/index.html) framework to create training and validation loops in our codebase. Ignite provides an easy way to create compact training/validation loops without too much boilerplate code.\n", + "\n", + "In this notebook, we demonstrate the use of ignite on the training loop only. We create a training engine `trainer`, that loops multiple times over the training dataset and updates model parameters. In addition, we add various events to the trainer, using an event system, that allows us to interact with the engine on each step of the run, such as, when the trainer is started/completed, when the epoch is started/completed and so on. \n", + "\n", + "In the cell below, we use event handlers to log training output, log and schedule learning rate, and periodically save model to disk." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# create training engine\n", + "trainer = create_supervised_trainer(\n", + " model, optimizer, criterion, prepare_batch, device=device\n", + ")\n", + "\n", + "# add learning rate scheduler\n", + "trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)\n", + "\n", + "# add logging of traininig output\n", + "trainer.add_event_handler(\n", + " Events.ITERATION_COMPLETED,\n", + " logging_handlers.log_training_output(log_interval=config.PRINT_FREQ),\n", + ")\n", + "\n", + "# add logging of learning rate\n", + "trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer))\n", + "\n", + "# add model checkpointing\n", + "output_dir = path.join(config.OUTPUT_DIR, config.TRAIN.MODEL_DIR)\n", + "checkpoint_handler = ModelCheckpoint(\n", + " output_dir, \"model\", save_interval=2, n_saved=3, create_dir=True, require_empty=False\n", + ")\n", + "trainer.add_event_handler(\n", + " Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Start the training engine run." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-09-25 18:19:05,213 - ignite.engine.engine.Engine - INFO - Engine run starting with max_epochs=10.\n", + "2019-09-25 18:19:05,214 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.02]\n", + "2019-09-25 18:19:27,468 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 50 loss 0.7012420892715454\n", + "2019-09-25 18:19:46,513 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 100 loss 0.28899359703063965\n", + "2019-09-25 18:20:05,579 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 150 loss 0.173098623752594\n", + "2019-09-25 18:20:24,618 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 200 loss 0.17099666595458984\n", + "2019-09-25 18:20:43,668 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 250 loss 0.08583173900842667\n", + "2019-09-25 18:21:02,722 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 300 loss 0.0811498835682869\n", + "2019-09-25 18:21:21,772 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 350 loss 0.14504070580005646\n", + "2019-09-25 18:21:40,824 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 400 loss 0.12912097573280334\n", + "2019-09-25 18:21:59,878 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 450 loss 0.10280831903219223\n", + "2019-09-25 18:22:18,935 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 500 loss 0.06620465964078903\n", + "2019-09-25 18:22:37,987 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 550 loss 0.08374416828155518\n", + "2019-09-25 18:22:57,039 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 600 loss 0.0626806691288948\n", + "2019-09-25 18:23:16,098 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 650 loss 0.061261530965566635\n", + "2019-09-25 18:23:35,156 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 700 loss 0.06074289232492447\n", + "2019-09-25 18:23:38,481 - ignite.engine.engine.Engine - INFO - Epoch[1] Complete. Time taken: 00:04:33\n", + "2019-09-25 18:23:38,482 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.01052125719751]\n", + "2019-09-25 18:23:56,938 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 750 loss 0.06132698804140091\n", + "2019-09-25 18:24:16,004 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 800 loss 0.05830317735671997\n", + "2019-09-25 18:24:35,077 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 850 loss 0.06894852221012115\n", + "2019-09-25 18:24:54,142 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 900 loss 0.05653904750943184\n", + "2019-09-25 18:25:13,209 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 950 loss 0.06860452890396118\n", + "2019-09-25 18:25:32,281 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1000 loss 0.05137062445282936\n", + "2019-09-25 18:25:51,346 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1050 loss 0.058020830154418945\n", + "2019-09-25 18:26:10,416 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1100 loss 0.048042453825473785\n", + "2019-09-25 18:26:29,489 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1150 loss 0.05230626463890076\n", + "2019-09-25 18:26:48,575 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1200 loss 0.06947802007198334\n", + "2019-09-25 18:27:07,645 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1250 loss 0.0545332171022892\n", + "2019-09-25 18:27:26,710 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1300 loss 0.051540959626436234\n", + "2019-09-25 18:27:45,788 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1350 loss 0.06202984228730202\n", + "2019-09-25 18:28:04,866 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1400 loss 0.044138021767139435\n", + "2019-09-25 18:28:06,337 - ignite.engine.engine.Engine - INFO - Epoch[2] Complete. Time taken: 00:04:27\n", + "2019-09-25 18:28:06,656 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.0010000237825795594]\n", + "2019-09-25 18:28:24,347 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1450 loss 0.051229942589998245\n", + "2019-09-25 18:28:43,423 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1500 loss 0.06309280544519424\n", + "2019-09-25 18:29:02,499 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1550 loss 0.05887899920344353\n", + "2019-09-25 18:29:21,575 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1600 loss 0.09512709826231003\n", + "2019-09-25 18:29:40,655 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1650 loss 0.05940958112478256\n", + "2019-09-25 18:29:59,732 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1700 loss 0.07722700387239456\n", + "2019-09-25 18:30:18,804 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1750 loss 0.05359434708952904\n", + "2019-09-25 18:30:37,883 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1800 loss 0.07005228847265244\n", + "2019-09-25 18:30:56,956 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1850 loss 0.0506112277507782\n", + "2019-09-25 18:31:16,030 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1900 loss 0.04246119037270546\n", + "2019-09-25 18:31:35,136 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1950 loss 0.04035837575793266\n", + "2019-09-25 18:31:54,217 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 2000 loss 0.05233351141214371\n", + "2019-09-25 18:32:13,293 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 2050 loss 0.04997049272060394\n", + "2019-09-25 18:32:32,368 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 2100 loss 0.04571027681231499\n", + "2019-09-25 18:32:34,603 - ignite.engine.engine.Engine - INFO - Epoch[3] Complete. Time taken: 00:04:27\n", + "2019-09-25 18:32:34,604 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.01052125719751]\n", + "2019-09-25 18:32:51,553 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2150 loss 0.04112791642546654\n", + "2019-09-25 18:33:10,627 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2200 loss 0.058870673179626465\n", + "2019-09-25 18:33:29,718 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2250 loss 0.05149131268262863\n", + "2019-09-25 18:33:48,793 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2300 loss 0.04544535651803017\n", + "2019-09-25 18:34:07,870 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2350 loss 0.049925826489925385\n", + "2019-09-25 18:34:26,948 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2400 loss 0.0426165871322155\n", + "2019-09-25 18:36:40,503 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2750 loss 0.039714518934488297\n", + "2019-09-25 18:36:59,582 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2800 loss 0.06449242681264877\n", + "2019-09-25 18:37:02,578 - ignite.engine.engine.Engine - INFO - Epoch[4] Complete. Time taken: 00:04:27\n", + "2019-09-25 18:37:02,892 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.0010000237825795594]\n", + "2019-09-25 18:37:19,078 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 2850 loss 0.04923075810074806\n", + "2019-09-25 18:37:38,158 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 2900 loss 0.04656514525413513\n", + "2019-09-25 18:37:57,228 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 2950 loss 0.04173054173588753\n", + "2019-09-25 18:38:16,295 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3000 loss 0.045953549444675446\n", + "2019-09-25 18:38:35,370 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3050 loss 0.03728904575109482\n", + "2019-09-25 18:38:54,446 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3100 loss 0.04939805343747139\n", + "2019-09-25 18:39:13,525 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3150 loss 0.045213744044303894\n", + "2019-09-25 18:39:32,600 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3200 loss 0.09391524642705917\n", + "2019-09-25 18:39:51,690 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3250 loss 0.04950859397649765\n", + "2019-09-25 18:40:10,766 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3300 loss 0.04418589174747467\n", + "2019-09-25 18:40:29,841 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3350 loss 0.04449845105409622\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-09-25 18:40:48,921 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3400 loss 0.043800223618745804\n", + "2019-09-25 18:41:08,003 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3450 loss 0.039736416190862656\n", + "2019-09-25 18:41:27,081 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3500 loss 0.038158830255270004\n", + "2019-09-25 18:41:30,841 - ignite.engine.engine.Engine - INFO - Epoch[5] Complete. Time taken: 00:04:27\n", + "2019-09-25 18:41:30,842 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.01052125719751]\n", + "2019-09-25 18:41:46,253 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3550 loss 0.04899909719824791\n", + "2019-09-25 18:42:05,331 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3600 loss 0.04364394396543503\n", + "2019-09-25 18:42:24,410 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3650 loss 0.04946207255125046\n", + "2019-09-25 18:42:43,516 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3700 loss 0.04041216894984245\n", + "2019-09-25 18:43:02,598 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3750 loss 0.0504840649664402\n", + "2019-09-25 18:43:21,684 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3800 loss 0.041116222739219666\n", + "2019-09-25 18:43:40,764 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3850 loss 0.04205234348773956\n", + "2019-09-25 18:43:59,841 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3900 loss 0.0413297601044178\n", + "2019-09-25 18:44:18,920 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3950 loss 0.0376584492623806\n", + "2019-09-25 18:44:38,000 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4000 loss 0.03894781693816185\n", + "2019-09-25 18:44:57,080 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4050 loss 0.03995727002620697\n", + "2019-09-25 18:45:16,158 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4100 loss 0.0402936115860939\n", + "2019-09-25 18:45:35,237 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4150 loss 0.03892284631729126\n", + "2019-09-25 18:45:54,319 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4200 loss 0.03852567449212074\n", + "2019-09-25 18:45:58,843 - ignite.engine.engine.Engine - INFO - Epoch[6] Complete. Time taken: 00:04:28\n", + "2019-09-25 18:45:59,922 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.0010000237825795594]\n", + "2019-09-25 18:46:14,592 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4250 loss 0.04775504767894745\n", + "2019-09-25 18:46:33,692 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4300 loss 0.035263791680336\n", + "2019-09-25 18:46:52,785 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4350 loss 0.03407309949398041\n", + "2019-09-25 18:47:11,875 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4400 loss 0.03757726028561592\n", + "2019-09-25 18:47:30,973 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4450 loss 0.04868190735578537\n", + "2019-09-25 18:47:50,070 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4500 loss 0.04181281104683876\n", + "2019-09-25 18:48:09,169 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4550 loss 0.056338150054216385\n", + "2019-09-25 18:48:28,273 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4600 loss 0.0411737821996212\n", + "2019-09-25 18:48:47,373 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4650 loss 0.051507413387298584\n", + "2019-09-25 18:49:06,472 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4700 loss 0.04746434837579727\n", + "2019-09-25 18:51:01,204 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5000 loss 0.03539836034178734\n", + "2019-09-25 18:51:20,303 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5050 loss 0.03780356049537659\n", + "2019-09-25 18:51:39,411 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5100 loss 0.03860008716583252\n", + "2019-09-25 18:51:58,510 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5150 loss 0.031131915748119354\n", + "2019-09-25 18:52:17,611 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5200 loss 0.03526508808135986\n", + "2019-09-25 18:52:36,711 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5250 loss 0.04343230277299881\n", + "2019-09-25 18:52:55,815 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5300 loss 0.034633275121450424\n", + "2019-09-25 18:53:14,917 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5350 loss 0.03684268891811371\n", + "2019-09-25 18:53:34,024 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5400 loss 0.038611602038145065\n", + "2019-09-25 18:53:53,123 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5450 loss 0.043228939175605774\n", + "2019-09-25 18:54:12,227 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5500 loss 0.03565002605319023\n", + "2019-09-25 18:54:31,330 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5550 loss 0.0400361567735672\n", + "2019-09-25 18:54:50,435 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5600 loss 0.04023653641343117\n", + "2019-09-25 18:54:56,493 - ignite.engine.engine.Engine - INFO - Epoch[8] Complete. Time taken: 00:04:28\n", + "2019-09-25 18:54:57,614 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.0010000237825795594]\n", + "2019-09-25 18:55:10,763 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5650 loss 0.029134199023246765\n", + "2019-09-25 18:55:29,876 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5700 loss 0.0546630434691906\n", + "2019-09-25 18:55:48,970 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5750 loss 0.035139769315719604\n", + "2019-09-25 18:56:08,062 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5800 loss 0.03422367200255394\n", + "2019-09-25 18:56:27,160 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5850 loss 0.042337819933891296\n", + "2019-09-25 18:56:46,254 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5900 loss 0.037118345499038696\n", + "2019-09-25 18:57:05,353 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5950 loss 0.03742925077676773\n", + "2019-09-25 18:57:24,441 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6000 loss 0.036784060299396515\n", + "2019-09-25 18:57:43,533 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6050 loss 0.032838501036167145\n", + "2019-09-25 18:58:02,623 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6100 loss 0.036310210824012756\n", + "2019-09-25 18:58:21,722 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6150 loss 0.03435125946998596\n", + "2019-09-25 18:58:40,814 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6200 loss 0.035941898822784424\n", + "2019-09-25 18:58:59,908 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6250 loss 0.032288145273923874\n", + "2019-09-25 18:59:18,996 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6300 loss 0.03844328597187996\n", + "2019-09-25 18:59:25,812 - ignite.engine.engine.Engine - INFO - Epoch[9] Complete. Time taken: 00:04:28\n", + "2019-09-25 18:59:25,814 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.01052125719751]\n", + "2019-09-25 18:59:38,194 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6350 loss 0.036316316574811935\n", + "2019-09-25 18:59:57,292 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6400 loss 0.03244319185614586\n", + "2019-09-25 19:00:16,389 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6450 loss 0.02924899198114872\n", + "2019-09-25 19:00:35,479 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6500 loss 0.03716830164194107\n", + "2019-09-25 19:00:54,575 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6550 loss 0.029776113107800484\n", + "2019-09-25 19:01:13,666 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6600 loss 0.03399008512496948\n", + "2019-09-25 19:01:32,757 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6650 loss 0.036496154963970184\n", + "2019-09-25 19:01:51,850 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6700 loss 0.0327572338283062\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-09-25 19:02:10,947 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6750 loss 0.03194150701165199\n", + "2019-09-25 19:02:30,038 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6800 loss 0.029924677684903145\n", + "2019-09-25 19:02:49,132 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6850 loss 0.03403583541512489\n", + "2019-09-25 19:03:08,225 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6900 loss 0.032861556857824326\n", + "2019-09-25 19:03:27,314 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6950 loss 0.0374293178319931\n", + "2019-09-25 19:03:46,406 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 7000 loss 0.031759023666381836\n", + "2019-09-25 19:03:53,986 - ignite.engine.engine.Engine - INFO - Epoch[10] Complete. Time taken: 00:04:28\n", + "2019-09-25 19:03:55,106 - ignite.engine.engine.Engine - INFO - Engine run complete. Time taken 00:44:49\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Evaluation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will next evaluate the performance of the model by looking at how well it predicts facies labels on samples from the test set.\n", + "\n", + "We will use the following evaluation metrics:\n", + "\n", + "- Pixel Accuracy (PA)\n", + "- Class Accuracy (CA)\n", + "- Mean Class Accuracy (MCA)\n", + "- Frequency Weighted intersection-over-union (FW IoU)\n", + "- Mean IoU (MIoU)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's first load the model saved previously." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", + "model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False)\n", + "model = model.to(device)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we load the test data and define the augmentations on it. " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# Augmentation\n", + "section_aug = Compose(\n", + " [Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1)]\n", + ")\n", + "\n", + "patch_aug = Compose(\n", + " [\n", + " Resize(\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", + " always_apply=True,\n", + " ),\n", + " PadIfNeeded(\n", + " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", + " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", + " border_mode=cv2.BORDER_CONSTANT,\n", + " always_apply=True,\n", + " mask_value=255,\n", + " ),\n", + " ]\n", + ")\n", + "\n", + "# Process test data\n", + "pre_processing = _compose_processing_pipeline(config.TRAIN.DEPTH, aug=patch_aug)\n", + "output_processing = _output_processing_pipeline(config)\n", + "\n", + "# Select the test split\n", + "split = \"test1\" if \"both\" in config.TEST.SPLIT else config.TEST.SPLIT\n", + "\n", + "labels = np.load(path.join(config.DATASET.ROOT, \"test_once\", split + \"_labels.npy\"))\n", + "section_file = path.join(config.DATASET.ROOT, \"splits\", \"section_\" + split + \".txt\")\n", + "_write_section_file(labels, section_file, config)\n", + "\n", + "# Load test data\n", + "TestSectionLoader = get_test_loader(config)\n", + "test_set = TestSectionLoader(\n", + " config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug\n", + ")\n", + "\n", + "test_loader = data.DataLoader(\n", + " test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Predict segmentation mask on the test data" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-09-25 19:50:17,990 - __main__ - INFO - split: test1, section: 0\n", + "2019-09-25 19:50:25,205 - __main__ - INFO - split: test1, section: 1\n", + "2019-09-25 19:50:25,851 - __main__ - INFO - split: test1, section: 2\n", + "2019-09-25 19:50:26,497 - __main__ - INFO - split: test1, section: 3\n", + "2019-09-25 19:50:27,142 - __main__ - INFO - split: test1, section: 4\n", + "2019-09-25 19:50:27,789 - __main__ - INFO - split: test1, section: 5\n", + "2019-09-25 19:50:28,437 - __main__ - INFO - split: test1, section: 6\n", + "2019-09-25 19:50:29,093 - __main__ - INFO - split: test1, section: 7\n", + "2019-09-25 19:50:47,011 - __main__ - INFO - split: test1, section: 8\n", + "2019-09-25 19:50:49,830 - __main__ - INFO - split: test1, section: 9\n", + "2019-09-25 19:50:50,473 - __main__ - INFO - split: test1, section: 10\n", + "2019-09-25 19:50:51,115 - __main__ - INFO - split: test1, section: 11\n", + "2019-09-25 19:50:51,759 - __main__ - INFO - split: test1, section: 12\n", + "2019-09-25 19:50:52,410 - __main__ - INFO - split: test1, section: 13\n", + "2019-09-25 19:50:53,061 - __main__ - INFO - split: test1, section: 14\n", + "2019-09-25 19:50:53,705 - __main__ - INFO - split: test1, section: 15\n", + "2019-09-25 19:50:54,351 - __main__ - INFO - split: test1, section: 16\n", + "2019-09-25 19:50:55,005 - __main__ - INFO - split: test1, section: 17\n", + "2019-09-25 19:50:55,666 - __main__ - INFO - split: test1, section: 18\n", + "2019-09-25 19:50:56,312 - __main__ - INFO - split: test1, section: 19\n" + ] + } + ], + "source": [ + "_CLASS_NAMES = [\n", + " \"upper_ns\",\n", + " \"middle_ns\",\n", + " \"lower_ns\",\n", + " \"rijnland_chalk\",\n", + " \"scruff\",\n", + " \"zechstein\",\n", + "]\n", + "\n", + "n_classes = len(_CLASS_NAMES)\n", + "\n", + "# keep only N_EVALUATE sections to score\n", + "test_subset = random.sample(list(test_loader), N_EVALUATE)\n", + "\n", + "results = list()\n", + "running_metrics_split = runningScore(n_classes)\n", + "\n", + "# testing mode\n", + "with torch.no_grad():\n", + " model.eval()\n", + " # loop over testing data\n", + " for i, (images, labels) in enumerate(test_subset):\n", + " logger.info(f\"split: {split}, section: {i}\")\n", + " outputs = _patch_label_2d(\n", + " model,\n", + " images,\n", + " pre_processing,\n", + " output_processing,\n", + " config.TRAIN.PATCH_SIZE,\n", + " config.TEST.TEST_STRIDE,\n", + " config.VALIDATION.BATCH_SIZE_PER_GPU,\n", + " device,\n", + " n_classes,\n", + " )\n", + "\n", + " pred = outputs.detach().max(1)[1].numpy()\n", + " gt = labels.numpy()\n", + " \n", + " # update evaluation metrics\n", + " running_metrics_split.update(gt, pred)\n", + " \n", + " # keep ground truth and result for plotting\n", + " results.append((np.squeeze(gt), np.squeeze(pred)))\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's view the obtained metrics on this subset of test images." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pixel Acc: 0.821\n", + " upper_ns_accuracy 0.979\n", + " middle_ns_accuracy 0.894\n", + " lower_ns_accuracy 0.969\n", + " rijnland_chalk_accuracy 0.794\n", + " scruff_accuracy 0.309\n", + " zechstein_accuracy 0.662\n", + "Mean Class Acc: 0.768\n", + "Freq Weighted IoU: 0.689\n", + "Mean IoU: 0.638\n" + ] + } + ], + "source": [ + "# get scores\n", + "score, _ = running_metrics_split.get_scores()\n", + "\n", + "# Log split results\n", + "print(f'Pixel Acc: {score[\"Pixel Acc: \"]:.3f}')\n", + "for cdx, class_name in enumerate(_CLASS_NAMES):\n", + " print(f' {class_name}_accuracy {score[\"Class Accuracy: \"][cdx]:.3f}')\n", + "\n", + "print(f'Mean Class Acc: {score[\"Mean Class Acc: \"]:.3f}')\n", + "print(f'Freq Weighted IoU: {score[\"Freq Weighted IoU: \"]:.3f}')\n", + "print(f'Mean IoU: {score[\"Mean IoU: \"]:0.3f}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualize predictions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize the predictions on entire test sections. Note that the crosslines and inlines have different dimensions, however we were able to use them jointly for our network training and evaluation, since we were using smaller patches from the sections, whose size we can control via hyperparameter in the experiment configuration file. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "image/png": "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AAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAA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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig = plt.figure(figsize=(15,50))\n", + "\n", + "nplot = min(N_EVALUATE, 10)\n", + "for idx in range(nplot):\n", + " # plot actual\n", + " plt.subplot(nplot, 2, 2*(idx+1)-1)\n", + " plt.imshow(results[idx][0])\n", + " # plot predicted\n", + " plt.subplot(nplot, 2, 2*(idx+1))\n", + " plt.imshow(results[idx][1])\n", + "\n", + "f_axes = fig.axes\n", + "_ = f_axes[0].set_title('Actual')\n", + "_ = f_axes[1].set_title('Predicted') " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "seismic-interpretation", + "language": "python", + "name": "seismic-interpretation" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml new file mode 100644 index 00000000..b5c1f7f9 --- /dev/null +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -0,0 +1,98 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 50 +LOG_CONFIG: /home/vapaunic/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/dutchf3 + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + + +MODEL: + NAME: seg_hrnet + IN_CHANNELS: 3 + PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 10 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "section" #"patch" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + +TEST: + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/dutchf3/local/output/mat/exp/237c16780794800631c3f1895cacc475e15aca99/seg_hrnet/Sep17_115731/models/seg_hrnet_running_model_33.pth" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 # + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml new file mode 100644 index 00000000..d4875ec0 --- /dev/null +++ b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml @@ -0,0 +1,59 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 50 +LOG_CONFIG: /data/home/vapaunic/repos/DeepSeismic/logging.conf +SEED: 2019 + +DATASET: + NUM_CLASSES: 6 + ROOT: /mnt/dutchf3 + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: patch_deconvnet_skip + IN_CHANNELS: 1 + + +TRAIN: + BATCH_SIZE_PER_GPU: 64 + BEGIN_EPOCH: 0 + END_EPOCH: 10 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "none" #"patch" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 99 + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + +VALIDATION: + BATCH_SIZE_PER_GPU: 512 + +TEST: + MODEL_PATH: '/data/home/vapaunic/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_8.pth' + TEST_STRIDE: 10 + SPLIT: 'test1' # Can be both, test1, test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 99 # + CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right + diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml new file mode 100644 index 00000000..06f3cbf6 --- /dev/null +++ b/examples/interpretation/notebooks/configs/unet.yaml @@ -0,0 +1,59 @@ +# UNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 50 +LOG_CONFIG: /home/vapaunic/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 6 + ROOT: '/mnt/dutchf3' + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] + +MODEL: + NAME: resnet_unet + IN_CHANNELS: 3 + + +TRAIN: + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 10 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "section" # Options are No, Patch and Section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + +TEST: + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 128 + CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right + diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py new file mode 100644 index 00000000..7d49708b --- /dev/null +++ b/examples/interpretation/notebooks/utilities.py @@ -0,0 +1,309 @@ +import itertools +import numpy as np +import torch +import torch.nn.functional as F + +from ignite.utils import convert_tensor + +from toolz import compose, curry, itertoolz, pipe + +import matplotlib.pyplot as plt +# plt.rcParams.update({"font.size": 16}) + +class runningScore(object): + def __init__(self, n_classes): + self.n_classes = n_classes + self.confusion_matrix = np.zeros((n_classes, n_classes)) + + def _fast_hist(self, label_true, label_pred, n_class): + mask = (label_true >= 0) & (label_true < n_class) + hist = np.bincount( + n_class * label_true[mask].astype(int) + label_pred[mask], + minlength=n_class ** 2, + ).reshape(n_class, n_class) + return hist + + def update(self, label_trues, label_preds): + for lt, lp in zip(label_trues, label_preds): + self.confusion_matrix += self._fast_hist( + lt.flatten(), lp.flatten(), self.n_classes + ) + + def get_scores(self): + """Returns accuracy score evaluation result. + - overall accuracy + - mean accuracy + - mean IU + - fwavacc + """ + hist = self.confusion_matrix + acc = np.diag(hist).sum() / hist.sum() + acc_cls = np.diag(hist) / hist.sum(axis=1) + mean_acc_cls = np.nanmean(acc_cls) + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + mean_iu = np.nanmean(iu) + freq = ( + hist.sum(axis=1) / hist.sum() + ) # fraction of the pixels that come from each class + fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() + cls_iu = dict(zip(range(self.n_classes), iu)) + + return ( + { + "Pixel Acc: ": acc, + "Class Accuracy: ": acc_cls, + "Mean Class Acc: ": mean_acc_cls, + "Freq Weighted IoU: ": fwavacc, + "Mean IoU: ": mean_iu, + "confusion_matrix": self.confusion_matrix, + }, + cls_iu, + ) + + def reset(self): + self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) + + +def prepare_batch(batch, device=None, non_blocking=False): + x, y = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ) + +def _transform_CHW_to_HWC(numpy_array): + return np.moveaxis(numpy_array, 0, -1) + + +def _transform_HWC_to_CHW(numpy_array): + return np.moveaxis(numpy_array, -1, 0) + + +@curry +def _apply_augmentation3D(aug, numpy_array): + assert len(numpy_array.shape) == 3, "This method only accepts 3D arrays" + patch = _transform_CHW_to_HWC(numpy_array) + patch = aug(image=patch)["image"] + return _transform_HWC_to_CHW(patch) + + +@curry +def _apply_augmentation2D(aug, numpy_array): + assert len(numpy_array.shape) == 2, "This method only accepts 2D arrays" + return aug(image=numpy_array)["image"] + + +_AUGMENTATION = {3: _apply_augmentation3D, 2: _apply_augmentation2D} + + +@curry +def _apply_augmentation(aug, image): + if isinstance(image, torch.Tensor): + image = image.numpy() + + if aug is not None: + return _AUGMENTATION[len(image.shape)](aug, image) + else: + return image + + +def _add_depth(image): + if isinstance(image, torch.Tensor): + image = image.numpy() + return add_patch_depth_channels(image) + + +def _to_torch(image): + if isinstance(image, torch.Tensor): + return image + else: + return torch.from_numpy(image).to(torch.float32) + + +def _expand_dims_if_necessary(torch_tensor): + if len(torch_tensor.shape) == 2: + return torch_tensor.unsqueeze(dim=0) + else: + return torch_tensor + + +@curry +def _extract_patch(hdx, wdx, ps, patch_size, img_p): + if len(img_p.shape) == 2: # 2D + return img_p[ + hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size + ] + else: # 3D + return img_p[ + :, + hdx + ps : hdx + ps + patch_size, + wdx + ps : wdx + ps + patch_size, + ] + + +def _compose_processing_pipeline(depth, aug=None): + steps = [] + if aug is not None: + steps.append(_apply_augmentation(aug)) + + if depth == "patch": + steps.append(_add_depth) + + steps.append(_to_torch) + steps.append(_expand_dims_if_necessary) + steps.reverse() + return compose(*steps) + + +def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): + hdc_wdx_generator = itertools.product( + range(0, h - patch_size + ps, stride), + range(0, w - patch_size + ps, stride), + ) + + for batch_indexes in itertoolz.partition_all( + batch_size, hdc_wdx_generator + ): + yield batch_indexes + + +@curry +def _output_processing_pipeline(config, output): + output = output.unsqueeze(0) + _, _, h, w = output.shape + if ( + config.TEST.POST_PROCESSING.SIZE != h + or config.TEST.POST_PROCESSING.SIZE != w + ): + output = F.interpolate( + output, + size=( + config.TEST.POST_PROCESSING.SIZE, + config.TEST.POST_PROCESSING.SIZE, + ), + mode="bilinear", + ) + + if config.TEST.POST_PROCESSING.CROP_PIXELS > 0: + _, _, h, w = output.shape + output = output[ + :, + :, + config.TEST.POST_PROCESSING.CROP_PIXELS : h + - config.TEST.POST_PROCESSING.CROP_PIXELS, + config.TEST.POST_PROCESSING.CROP_PIXELS : w + - config.TEST.POST_PROCESSING.CROP_PIXELS, + ] + return output.squeeze() + + +def _patch_label_2d( + model, + img, + pre_processing, + output_processing, + patch_size, + stride, + batch_size, + device, + num_classes, +): + """Processes a whole section""" + img = torch.squeeze(img) + h, w = img.shape[-2], img.shape[-1] # height and width + + # Pad image with patch_size/2: + ps = int(np.floor(patch_size / 2)) # pad size + img_p = F.pad(img, pad=(ps, ps, ps, ps), mode="constant", value=0) + output_p = torch.zeros([1, num_classes, h + 2 * ps, w + 2 * ps]) + + # generate output: + for batch_indexes in _generate_batches( + h, w, ps, patch_size, stride, batch_size=batch_size + ): + batch = torch.stack( + [ + pipe( + img_p, + _extract_patch(hdx, wdx, ps, patch_size), + pre_processing, + ) + for hdx, wdx in batch_indexes + ], + dim=0, + ) + + model_output = model(batch.to(device)) + for (hdx, wdx), output in zip( + batch_indexes, model_output.detach().cpu() + ): + output = output_processing(output) + output_p[ + :, + :, + hdx + ps : hdx + ps + patch_size, + wdx + ps : wdx + ps + patch_size, + ] += output + + # crop the output_p in the middle + output = output_p[:, :, ps:-ps, ps:-ps] + return output + + +@curry +def to_image(label_mask, n_classes=6): + label_colours = get_seismic_labels() + r = label_mask.copy() + g = label_mask.copy() + b = label_mask.copy() + for ll in range(0, n_classes): + r[label_mask == ll] = label_colours[ll, 0] + g[label_mask == ll] = label_colours[ll, 1] + b[label_mask == ll] = label_colours[ll, 2] + rgb = np.zeros( + (label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3) + ) + rgb[:, :, :, 0] = r + rgb[:, :, :, 1] = g + rgb[:, :, :, 2] = b + return rgb + +def _write_section_file(labels, section_file, config): + # define indices of the array + irange, xrange, depth = labels.shape + + if config.TEST.INLINE: + i_list = list(range(irange)) + i_list = ["i_" + str(inline) for inline in i_list] + else: + i_list = [] + + if config.TEST.CROSSLINE: + x_list = list(range(xrange)) + x_list = ["x_" + str(crossline) for crossline in x_list] + else: + x_list = [] + + list_test = i_list + x_list + + file_object = open(section_file, "w") + file_object.write("\n".join(list_test)) + file_object.close() + + +def plot_aline(aline, labels, xlabel, ylabel="depth"): + """Plot a section of the data.""" + plt.figure(figsize=(18, 6)) + # data + plt.subplot(1, 2, 1) + plt.imshow(aline) + plt.title("Data") + plt.xlabel(xlabel) + plt.ylabel(ylabel) + # mask + plt.subplot(1, 2, 2) + plt.imshow(labels) + plt.xlabel(xlabel) + plt.title("Label") \ No newline at end of file From 37bbb21d3fd1602909bb31474bd0dfd0cc81fa10 Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Fri, 27 Sep 2019 12:17:25 +0000 Subject: [PATCH 053/207] Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 --- .../dutchf3_section/local/configs/section_deconvnet_skip.yaml | 0 .../dutchf3_section/local/default.py | 0 .../dutchf3_section/local/test.py | 0 .../dutchf3_section/local/train.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename experiments/{segmentation => interpretation}/dutchf3_section/local/configs/section_deconvnet_skip.yaml (100%) rename experiments/{segmentation => interpretation}/dutchf3_section/local/default.py (100%) rename experiments/{segmentation => interpretation}/dutchf3_section/local/test.py (100%) rename experiments/{segmentation => interpretation}/dutchf3_section/local/train.py (100%) diff --git a/experiments/segmentation/dutchf3_section/local/configs/section_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml similarity index 100% rename from experiments/segmentation/dutchf3_section/local/configs/section_deconvnet_skip.yaml rename to experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml diff --git a/experiments/segmentation/dutchf3_section/local/default.py b/experiments/interpretation/dutchf3_section/local/default.py similarity index 100% rename from experiments/segmentation/dutchf3_section/local/default.py rename to experiments/interpretation/dutchf3_section/local/default.py diff --git a/experiments/segmentation/dutchf3_section/local/test.py b/experiments/interpretation/dutchf3_section/local/test.py similarity index 100% rename from experiments/segmentation/dutchf3_section/local/test.py rename to experiments/interpretation/dutchf3_section/local/test.py diff --git a/experiments/segmentation/dutchf3_section/local/train.py b/experiments/interpretation/dutchf3_section/local/train.py similarity index 100% rename from experiments/segmentation/dutchf3_section/local/train.py rename to experiments/interpretation/dutchf3_section/local/train.py From 5e1cc59362c6d3a872e8811919218fa06bfc2dfe Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Fri, 27 Sep 2019 13:31:50 +0000 Subject: [PATCH 054/207] Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 --- experiments/interpretation/dutchf3_patch/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experiments/interpretation/dutchf3_patch/README.md b/experiments/interpretation/dutchf3_patch/README.md index ec5808ce..72a36dc5 100644 --- a/experiments/interpretation/dutchf3_patch/README.md +++ b/experiments/interpretation/dutchf3_patch/README.md @@ -32,10 +32,10 @@ To split the dataset into training and validation, please run the [prepare_data. Example run: ``` // To split data into sections -python prepare_data.py split_train_val --data-dir=/mnt/dutchf3 --loader-type="section" --log-config=../logging.conf +python prepare_data.py split_train_val section --data-dir=/mnt/dutchf3 // To split data into patches -python prepare_data.py split_train_val --data-dir=/mnt/dutchf3 --loader-type="patch" --stride=50 --log-config=../logging.conf +python prepare_data.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=100 ``` Please see `prepare_data.py` script for more arguments. From ebfb1e140013abe7568d21edee3733e401b9f414 Mon Sep 17 00:00:00 2001 From: Mathew Salvaris Date: Fri, 27 Sep 2019 14:53:41 +0000 Subject: [PATCH 055/207] Merged PR 315: Removing voxel exp Related work items: #17702 --- .../local/configs/texture_net.yaml | 64 --- .../dutchf3_voxel/local/default.py | 109 ---- .../dutchf3_voxel/local/test.py | 481 ------------------ .../dutchf3_voxel/local/train.py | 302 ----------- 4 files changed, 956 deletions(-) delete mode 100644 experiments/interpretation/dutchf3_voxel/local/configs/texture_net.yaml delete mode 100644 experiments/interpretation/dutchf3_voxel/local/default.py delete mode 100644 experiments/interpretation/dutchf3_voxel/local/test.py delete mode 100644 experiments/interpretation/dutchf3_voxel/local/train.py diff --git a/experiments/interpretation/dutchf3_voxel/local/configs/texture_net.yaml b/experiments/interpretation/dutchf3_voxel/local/configs/texture_net.yaml deleted file mode 100644 index f95b18ce..00000000 --- a/experiments/interpretation/dutchf3_voxel/local/configs/texture_net.yaml +++ /dev/null @@ -1,64 +0,0 @@ -# TextureNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf -SEED: 2019 -WINDOW_SIZE: 65 - - -DATASET: - NUM_CLASSES: 2 - ROOT: /mnt/dutchf3 - CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - -MODEL: - NAME: texture_net - IN_CHANNELS: 1 - - -TRAIN: - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "voxel" # Options are No, Patch, Section and Voxel - STRIDE: 50 - PATCH_SIZE: 100 - AUGMENTATIONS: - RESIZE: - HEIGHT: 200 - WIDTH: 200 - PAD: - HEIGHT: 256 - WIDTH: 256 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 - MODEL_DIR: "models" - - -VALIDATION: - BATCH_SIZE_PER_GPU: 32 - -TEST: - MODEL_PATH: "" - TEST_STRIDE: 10 - SPLIT: 'Both' # Can be Both, Test1, Test2 - INLINE: True - CROSSLINE: True - POST_PROCESSING: - SIZE: 128 - CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right - diff --git a/experiments/interpretation/dutchf3_voxel/local/default.py b/experiments/interpretation/dutchf3_voxel/local/default.py deleted file mode 100644 index e9bdf617..00000000 --- a/experiments/interpretation/dutchf3_voxel/local/default.py +++ /dev/null @@ -1,109 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright (c) Microsoft -# Licensed under the MIT License. -# ------------------------------------------------------------------------------ - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from yacs.config import CfgNode as CN - -_C = CN() - -_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models -_C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR -_C.GPUS = (0,) -_C.WORKERS = 4 -_C.PRINT_FREQ = 20 -_C.AUTO_RESUME = False -_C.PIN_MEMORY = True -# TODO: this should be loaded by automatically figuring out the file path location -_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" -_C.SEED = 42 - -# size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only -_C.WINDOW_SIZE = 65 - -# Cudnn related params -_C.CUDNN = CN() -_C.CUDNN.BENCHMARK = True -_C.CUDNN.DETERMINISTIC = False -_C.CUDNN.ENABLED = True - -# DATASET related params -_C.DATASET = CN() -_C.DATASET.ROOT = "" -_C.DATASET.NUM_CLASSES = 6 -_C.DATASET.CLASS_WEIGHTS = [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - -# common params for NETWORK -_C.MODEL = CN() -_C.MODEL.NAME = "patch_deconvnet" -_C.MODEL.IN_CHANNELS = 1 -_C.MODEL.PRETRAINED = "" -_C.MODEL.EXTRA = CN(new_allowed=True) - - -# training -_C.TRAIN = CN() -_C.TRAIN.MIN_LR = 0.001 -_C.TRAIN.MAX_LR = 0.01 -_C.TRAIN.MOMENTUM = 0.9 -_C.TRAIN.BEGIN_EPOCH = 0 -_C.TRAIN.END_EPOCH = 484 -_C.TRAIN.BATCH_SIZE_PER_GPU = 32 -_C.TRAIN.WEIGHT_DECAY = 0.0001 -_C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR -_C.TRAIN.AUGMENTATION = True -_C.TRAIN.STRIDE = 50 -_C.TRAIN.PATCH_SIZE = 99 -_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 -_C.TRAIN.STD = 0.20977 # 0.20976548783479299 # TODO: Should we apply std scaling? -_C.TRAIN.DEPTH = 'no' # Options are None, Patch and Section -# None adds no depth information and the num of channels remains at 1 -# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 -# Section adds depth per section so contains depth information for the whole section, channels=3 -_C.TRAIN.AUGMENTATIONS = CN() -_C.TRAIN.AUGMENTATIONS.RESIZE = CN() -_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 -_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 -_C.TRAIN.AUGMENTATIONS.PAD = CN() -_C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 -_C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 - -# validation -_C.VALIDATION = CN() -_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 - -# TEST -_C.TEST = CN() -_C.TEST.MODEL_PATH = "" -_C.TEST.TEST_STRIDE = 10 -_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 -_C.TEST.INLINE = True -_C.TEST.CROSSLINE = True -_C.TEST.POST_PROCESSING = CN() # Model output postprocessing -_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels -_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right - - -def update_config(cfg, options=None, config_file=None): - cfg.defrost() - - if config_file: - cfg.merge_from_file(config_file) - - if options: - cfg.merge_from_list(options) - - cfg.freeze() - - -if __name__ == "__main__": - import sys - - with open(sys.argv[1], "w") as f: - print(_C, file=f) - diff --git a/experiments/interpretation/dutchf3_voxel/local/test.py b/experiments/interpretation/dutchf3_voxel/local/test.py deleted file mode 100644 index a68c3618..00000000 --- a/experiments/interpretation/dutchf3_voxel/local/test.py +++ /dev/null @@ -1,481 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 -# url: https://github.com/olivesgatech/facies_classification_benchmark - -""" -Modified version of the Alaudah testing script -#TODO: Needs to be improved. Needs to be able to run across multiple GPUs and better factoring around the loader -""" - -import itertools -import logging -import logging.config -import os -from os import path - -import cv2 -import fire -import numpy as np -import torch -import torch.nn.functional as F -from albumentations import Compose, Normalize, PadIfNeeded, Resize -from cv_lib.segmentation import models -from deepseismic_interpretation.dutchf3.data import ( - add_patch_depth_channels, - get_seismic_labels, - get_test_loader, -) -from default import _C as config -from default import update_config -from toolz import compose, curry, itertoolz, pipe -from torch.utils import data - -_CLASS_NAMES = [ - "upper_ns", - "middle_ns", - "lower_ns", - "rijnland_chalk", - "scruff", - "zechstein", -] - - -class runningScore(object): - def __init__(self, n_classes): - self.n_classes = n_classes - self.confusion_matrix = np.zeros((n_classes, n_classes)) - - def _fast_hist(self, label_true, label_pred, n_class): - mask = (label_true >= 0) & (label_true < n_class) - hist = np.bincount( - n_class * label_true[mask].astype(int) + label_pred[mask], - minlength=n_class ** 2, - ).reshape(n_class, n_class) - return hist - - def update(self, label_trues, label_preds): - for lt, lp in zip(label_trues, label_preds): - self.confusion_matrix += self._fast_hist( - lt.flatten(), lp.flatten(), self.n_classes - ) - - def get_scores(self): - """Returns accuracy score evaluation result. - - overall accuracy - - mean accuracy - - mean IU - - fwavacc - """ - hist = self.confusion_matrix - acc = np.diag(hist).sum() / hist.sum() - acc_cls = np.diag(hist) / hist.sum(axis=1) - mean_acc_cls = np.nanmean(acc_cls) - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) - mean_iu = np.nanmean(iu) - freq = ( - hist.sum(axis=1) / hist.sum() - ) # fraction of the pixels that come from each class - fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() - cls_iu = dict(zip(range(self.n_classes), iu)) - - return ( - { - "Pixel Acc: ": acc, - "Class Accuracy: ": acc_cls, - "Mean Class Acc: ": mean_acc_cls, - "Freq Weighted IoU: ": fwavacc, - "Mean IoU: ": mean_iu, - "confusion_matrix": self.confusion_matrix, - }, - cls_iu, - ) - - def reset(self): - self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) - - -def _transform_CHW_to_HWC(numpy_array): - return np.moveaxis(numpy_array, 0, -1) - - -def _transform_HWC_to_CHW(numpy_array): - return np.moveaxis(numpy_array, -1, 0) - - -@curry -def _apply_augmentation3D(aug, numpy_array): - assert len(numpy_array.shape) == 3, "This method only accepts 3D arrays" - patch = _transform_CHW_to_HWC(numpy_array) - patch = aug(image=patch)["image"] - return _transform_HWC_to_CHW(patch) - - -@curry -def _apply_augmentation2D(aug, numpy_array): - assert len(numpy_array.shape) == 2, "This method only accepts 2D arrays" - return aug(image=numpy_array)["image"] - - -_AUGMENTATION = {3: _apply_augmentation3D, 2: _apply_augmentation2D} - - -@curry -def _apply_augmentation(aug, image): - if isinstance(image, torch.Tensor): - image = image.numpy() - - if aug is not None: - return _AUGMENTATION[len(image.shape)](aug, image) - else: - return image - - -def _add_depth(image): - if isinstance(image, torch.Tensor): - image = image.numpy() - return add_patch_depth_channels(image) - - -def _to_torch(image): - if isinstance(image, torch.Tensor): - return image - else: - return torch.from_numpy(image).to(torch.float32) - - -def _expand_dims_if_necessary(torch_tensor): - if len(torch_tensor.shape) == 2: - return torch_tensor.unsqueeze(dim=0) - else: - return torch_tensor - - -@curry -def _extract_patch(hdx, wdx, ps, patch_size, img_p): - if len(img_p.shape) == 2: # 2D - return img_p[ - hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size - ] - else: # 3D - return img_p[ - :, - hdx + ps : hdx + ps + patch_size, - wdx + ps : wdx + ps + patch_size, - ] - - -def _compose_processing_pipeline(depth, aug=None): - steps = [] - if aug is not None: - steps.append(_apply_augmentation(aug)) - - if depth == "patch": - steps.append(_add_depth) - - steps.append(_to_torch) - steps.append(_expand_dims_if_necessary) - steps.reverse() - return compose(*steps) - - -def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): - hdc_wdx_generator = itertools.product( - range(0, h - patch_size + ps, stride), - range(0, w - patch_size + ps, stride), - ) - - for batch_indexes in itertoolz.partition_all( - batch_size, hdc_wdx_generator - ): - yield batch_indexes - - -@curry -def _output_processing_pipeline(config, output): - output = output.unsqueeze(0) - _, _, h, w = output.shape - if ( - config.TEST.POST_PROCESSING.SIZE != h - or config.TEST.POST_PROCESSING.SIZE != w - ): - output = F.interpolate( - output, - size=( - config.TEST.POST_PROCESSING.SIZE, - config.TEST.POST_PROCESSING.SIZE, - ), - mode="bilinear", - ) - - if config.TEST.POST_PROCESSING.CROP_PIXELS > 0: - _, _, h, w = output.shape - output = output[ - :, - :, - config.TEST.POST_PROCESSING.CROP_PIXELS : h - - config.TEST.POST_PROCESSING.CROP_PIXELS, - config.TEST.POST_PROCESSING.CROP_PIXELS : w - - config.TEST.POST_PROCESSING.CROP_PIXELS, - ] - return output.squeeze() - - -def _patch_label_2d( - model, - img, - pre_processing, - output_processing, - patch_size, - stride, - batch_size, - device, - num_classes, -): - """Processes a whole section - """ - img = torch.squeeze(img) - h, w = img.shape[-2], img.shape[-1] # height and width - - # Pad image with patch_size/2: - ps = int(np.floor(patch_size / 2)) # pad size - img_p = F.pad(img, pad=(ps, ps, ps, ps), mode="constant", value=0) - output_p = torch.zeros([1, num_classes, h + 2 * ps, w + 2 * ps]) - - # generate output: - for batch_indexes in _generate_batches( - h, w, ps, patch_size, stride, batch_size=batch_size - ): - batch = torch.stack( - [ - pipe( - img_p, - _extract_patch(hdx, wdx, ps, patch_size), - pre_processing, - ) - for hdx, wdx in batch_indexes - ], - dim=0, - ) - - model_output = model(batch.to(device)) - for (hdx, wdx), output in zip( - batch_indexes, model_output.detach().cpu() - ): - output = output_processing(output) - output_p[ - :, - :, - hdx + ps : hdx + ps + patch_size, - wdx + ps : wdx + ps + patch_size, - ] += output - - # crop the output_p in the middle - output = output_p[:, :, ps:-ps, ps:-ps] - return output - - -@curry -def to_image(label_mask, n_classes=6): - label_colours = get_seismic_labels() - r = label_mask.copy() - g = label_mask.copy() - b = label_mask.copy() - for ll in range(0, n_classes): - r[label_mask == ll] = label_colours[ll, 0] - g[label_mask == ll] = label_colours[ll, 1] - b[label_mask == ll] = label_colours[ll, 2] - rgb = np.zeros( - (label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3) - ) - rgb[:, :, :, 0] = r - rgb[:, :, :, 1] = g - rgb[:, :, :, 2] = b - return rgb - - -def _evaluate_split( - split, - section_aug, - model, - pre_processing, - output_processing, - device, - running_metrics_overall, - config, -): - logger = logging.getLogger(__name__) - - TestSectionLoader = get_test_loader(config) - test_set = TestSectionLoader( - config.DATASET.ROOT, - split=split, - is_transform=True, - augmentations=section_aug, - ) - - n_classes = test_set.n_classes - - test_loader = data.DataLoader( - test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False - ) - running_metrics_split = runningScore(n_classes) - - # testing mode: - with torch.no_grad(): # operations inside don't track history - model.eval() - total_iteration = 0 - for i, (images, labels) in enumerate(test_loader): - logger.info(f"split: {split}, section: {i}") - total_iteration = total_iteration + 1 - - outputs = _patch_label_2d( - model, - images, - pre_processing, - output_processing, - config.TRAIN.PATCH_SIZE, - config.TEST.TEST_STRIDE, - config.VALIDATION.BATCH_SIZE_PER_GPU, - device, - n_classes, - ) - - pred = outputs.detach().max(1)[1].numpy() - gt = labels.numpy() - running_metrics_split.update(gt, pred) - running_metrics_overall.update(gt, pred) - - # get scores - score, class_iou = running_metrics_split.get_scores() - - # Log split results - logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') - for cdx, class_name in enumerate(_CLASS_NAMES): - logger.info( - f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' - ) - - logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') - logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') - logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') - running_metrics_split.reset() - - -def _write_section_file(labels, section_file): - # define indices of the array - irange, xrange, depth = labels.shape - - if config.TEST.INLINE: - i_list = list(range(irange)) - i_list = ["i_" + str(inline) for inline in i_list] - else: - i_list = [] - - if config.TEST.CROSSLINE: - x_list = list(range(xrange)) - x_list = ["x_" + str(crossline) for crossline in x_list] - else: - x_list = [] - - list_test = i_list + x_list - - file_object = open(section_file, "w") - file_object.write("\n".join(list_test)) - file_object.close() - - -def test(*options, cfg=None): - update_config(config, options=options, config_file=cfg) - n_classes = config.DATASET.NUM_CLASSES - logging.config.fileConfig(config.LOG_CONFIG) - logger = logging.getLogger(__name__) - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - log_dir, model_name = os.path.split(config.TEST.MODEL_PATH) - - # load model: - model = getattr(models, config.MODEL.NAME).get_seg_model(config) - model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False) - model = model.to(device) # Send to GPU if available - - running_metrics_overall = runningScore(n_classes) - - # Augmentation - section_aug = Compose( - [ - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, - ) - ] - ) - - patch_aug = Compose( - [ - Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, - ), - PadIfNeeded( - min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, - min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=255, - ), - ] - ) - - pre_processing = _compose_processing_pipeline( - config.TRAIN.DEPTH, aug=patch_aug - ) - output_processing = _output_processing_pipeline(config) - - splits = ( - ["test1", "test2"] - if "Both" in config.TEST.SPLIT - else [config.TEST.SPLIT] - ) - for sdx, split in enumerate(splits): - labels = np.load( - path.join(config.DATASEST.ROOT, "test_once", split + "_labels.npy") - ) - section_file = path.join( - config.DATASEST.ROOT, "splits", "section_" + split + ".txt" - ) - _write_section_file(labels, section_file) - _evaluate_split( - split, - section_aug, - model, - pre_processing, - output_processing, - device, - running_metrics_overall, - config, - ) - - # FINAL TEST RESULTS: - score, class_iou = running_metrics_overall.get_scores() - - logger.info("--------------- FINAL RESULTS -----------------") - logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') - for cdx, class_name in enumerate(_CLASS_NAMES): - logger.info( - f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' - ) - logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') - logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') - logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') - - # Save confusion matrix: - confusion = score["confusion_matrix"] - np.savetxt(path.join(log_dir, "confusion.csv"), confusion, delimiter=" ") - - -if __name__ == "__main__": - fire.Fire(test) diff --git a/experiments/interpretation/dutchf3_voxel/local/train.py b/experiments/interpretation/dutchf3_voxel/local/train.py deleted file mode 100644 index 0c1776cf..00000000 --- a/experiments/interpretation/dutchf3_voxel/local/train.py +++ /dev/null @@ -1,302 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# /* spell-checker: disable */ - -import logging -import logging.config -from os import path - -import cv2 -import fire -import numpy as np -import torch -from albumentations import (Compose, HorizontalFlip, Normalize, PadIfNeeded, - Resize) -from ignite.contrib.handlers import CosineAnnealingScheduler -from ignite.engine import Events -from ignite.metrics import Loss -from ignite.utils import convert_tensor -from toolz import compose -from torch.utils import data - -from deepseismic_interpretation.dutchf3.data import get_voxel_loader, decode_segmap -from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, - tensorboard_handlers) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, - create_summary_writer) -from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, - create_supervised_trainer) -from cv_lib.segmentation.dutchf3.metrics import (FrequencyWeightedIoU, - MeanClassAccuracy, MeanIoU, - PixelwiseAccuracy) -from cv_lib.segmentation.dutchf3.utils import (current_datetime, generate_path, - git_branch, git_hash, np_to_tb) - -from default import _C as config -from default import update_config - - -def prepare_batch(batch, device=None, non_blocking=False): - x, y = batch - return ( - convert_tensor(x, device=device, non_blocking=non_blocking), - convert_tensor(y, device=device, non_blocking=non_blocking), - ) - - -def run(*options, cfg=None): - """Run training and validation of model - - Notes: - Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file - - Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py - cfg (str, optional): Location of config file to load. Defaults to None. - """ - - update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) - logger = logging.getLogger(__name__) - logger.debug(config.WORKERS) - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - - torch.manual_seed(config.SEED) - if torch.cuda.is_available(): - torch.cuda.manual_seed_all(config.SEED) - np.random.seed(seed=config.SEED) - - # Setup Augmentations - basic_aug = Compose( - [ - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, - ), - Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, - ), - PadIfNeeded( - min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, - min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=255, - ), - ] - ) - if config.TRAIN.AUGMENTATION: - train_aug = Compose( - [ - basic_aug, - HorizontalFlip(p=0.5), - ] - ) - val_aug = basic_aug - else: - train_aug = val_aug = basic_aug - - TrainVoxelLoader = get_voxel_loader(config) - train_set = TrainVoxelLoader(split = "train", window_size = config.WINDOW_SIZE) - val_set = TrainVoxelLoader(split = "val", window_size = config.WINDOW_SIZE) - - n_classes = train_set.n_classes - - train_loader = data.DataLoader( - train_set, - batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - shuffle=True, - ) - val_loader = data.DataLoader( - val_set, - batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - ) - - model = getattr(models, config.MODEL.NAME).get_seg_model(config) - - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" - model = model.to(device) # Send to GPU - - - optimizer = torch.optim.SGD( - model.parameters(), - lr=config.TRAIN.MAX_LR, - momentum=config.TRAIN.MOMENTUM, - weight_decay=config.TRAIN.WEIGHT_DECAY, - ) - - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) - snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler( - optimizer, - "lr", - config.TRAIN.MAX_LR, - config.TRAIN.MIN_LR, - snapshot_duration, - ) - - # weights are inversely proportional to the frequency of the classes in the training set - class_weights = torch.tensor( - config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False - ) - - criterion = torch.nn.CrossEntropyLoss( - weight=class_weights, ignore_index=255, reduction="mean" - ) - - trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_batch, device=device - ) - - trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), - ) - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), - ) - - def _select_pred_and_mask(model_out_dict): - return ( - model_out_dict["y_pred"].squeeze(), - model_out_dict["mask"].squeeze(), - ) - - evaluator = create_supervised_evaluator( - model, - prepare_batch, - metrics={ - "IoU": MeanIoU( - n_classes, device, output_transform=_select_pred_and_mask - ), - "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "mca": MeanClassAccuracy( - n_classes, device, output_transform=_select_pred_and_mask - ), - "fiou": FrequencyWeightedIoU( - n_classes, device, output_transform=_select_pred_and_mask - ), - "pixa": PixelwiseAccuracy( - n_classes, device, output_transform=_select_pred_and_mask - ), - }, - device=device, - ) - - # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler( - Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) - ) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - logging_handlers.log_metrics( - "Validation results", - metrics_dict={ - "IoU": "IoU :", - "nll": "Avg loss :", - "pixa": "Pixelwise Accuracy :", - "mca": "Mean Class Accuracy :", - "fiou": "Freq Weighted IoU :", - }, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - tensorboard_handlers.log_metrics( - summary_writer, - trainer, - "epoch", - metrics_dict={ - "IoU": "Validation/IoU", - "nll": "Validation/Loss", - "mca": "Validation/MCA", - "fiou": "Validation/FIoU", - }, - ), - ) - - def _select_max(pred_tensor): - return pred_tensor.max(1)[1] - - def _tensor_to_numpy(pred_tensor): - return pred_tensor.squeeze().cpu().numpy() - - transform_func = compose( - np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy - ) - - transform_pred = compose(transform_func, _select_max) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, - "Validation/Mask", - "mask", - transform_func=transform_func, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, - "Validation/Pred", - "y_pred", - transform_func=transform_pred, - ), - ) - - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - - checkpoint_handler = SnapshotHandler( - path.join(output_dir, config.TRAIN.MODEL_DIR), - config.MODEL.NAME, - snapshot_function, - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) - - logger.info("Starting training") - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) - - -if __name__ == "__main__": - fire.Fire(run) From 02866181c23887920587090cbfbd46428b24f70d Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Thu, 3 Oct 2019 21:38:13 +0000 Subject: [PATCH 056/207] Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 --- environment/anaconda/local/environment.yml | 2 + .../voxel2pixel/README.md | 0 .../{ => interpretation}/voxel2pixel/batch.py | 0 .../{ => interpretation}/voxel2pixel/data.py | 132 +++++------ .../voxel2pixel/tb_logger.py | 0 .../voxel2pixel/test_parallel.py | 0 .../voxel2pixel/texture_net.py | 0 .../{ => interpretation}/voxel2pixel/train.py | 17 +- .../{ => interpretation}/voxel2pixel/utils.py | 211 +++++++----------- scripts/get_F3_voxel.sh | 15 +- 10 files changed, 163 insertions(+), 214 deletions(-) rename experiments/{ => interpretation}/voxel2pixel/README.md (100%) rename experiments/{ => interpretation}/voxel2pixel/batch.py (100%) rename experiments/{ => interpretation}/voxel2pixel/data.py (71%) rename experiments/{ => interpretation}/voxel2pixel/tb_logger.py (100%) rename experiments/{ => interpretation}/voxel2pixel/test_parallel.py (100%) rename experiments/{ => interpretation}/voxel2pixel/texture_net.py (100%) rename experiments/{ => interpretation}/voxel2pixel/train.py (92%) rename experiments/{ => interpretation}/voxel2pixel/utils.py (59%) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index f108de52..4f3d2f22 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -13,6 +13,7 @@ dependencies: - opencv - scikit-learn - pip: + - segyio - pytorch-ignite - fire - toolz @@ -28,4 +29,5 @@ dependencies: - albumentations - black - pylint + - scipy==1.1.0 diff --git a/experiments/voxel2pixel/README.md b/experiments/interpretation/voxel2pixel/README.md similarity index 100% rename from experiments/voxel2pixel/README.md rename to experiments/interpretation/voxel2pixel/README.md diff --git a/experiments/voxel2pixel/batch.py b/experiments/interpretation/voxel2pixel/batch.py similarity index 100% rename from experiments/voxel2pixel/batch.py rename to experiments/interpretation/voxel2pixel/batch.py diff --git a/experiments/voxel2pixel/data.py b/experiments/interpretation/voxel2pixel/data.py similarity index 71% rename from experiments/voxel2pixel/data.py rename to experiments/interpretation/voxel2pixel/data.py index 06505716..b1dc6f7a 100644 --- a/experiments/voxel2pixel/data.py +++ b/experiments/interpretation/voxel2pixel/data.py @@ -101,92 +101,84 @@ def write_segy(out_filename, in_filename, out_cube): timeslice_alias = ["timeslice", "time-slice", "t", "z", "depthslice", "depth"] -def read_labels(foldername, data_info): +def read_labels(fname, data_info): """ Read labels from an image. Args: - foldername: folder which contains the image. + fname: filename of labelling mask (image) data_info: dictionary describing the data Returns: list of labels and list of coordinates - """ - files = [f for f in listdir(foldername) if isfile(join(foldername, f))] + """ label_imgs = [] label_coordinates = {} # Find image files in folder - for file in files: - if ( - file[-3:].lower() in ["jpg", "png", "peg", "bmp", "gif"] - and file[0] != "." - ): - if True: - tmp = file.split("_") - slice_type = tmp[0].lower() - tmp = tmp[1].split(".") - slice_no = int(tmp[0]) - - if ( - slice_type - not in inline_alias + crossline_alias + timeslice_alias - ): - print( - "File:", - file, - "could not be loaded.", - "Unknown slice type", - ) - continue - - if slice_type in inline_alias: - slice_type = "inline" - if slice_type in crossline_alias: - slice_type = "crossline" - if slice_type in timeslice_alias: - slice_type = "timeslice" - - # Read file - print("Loading labels for", slice_type, slice_no, "with") - img = scipy.misc.imread(join(foldername, file)) - img = interpolate_to_fit_data( - img, slice_type, slice_no, data_info - ) - label_img = parse_labels_in_image(img) + + tmp = fname.split('/')[-1].split("_") + slice_type = tmp[0].lower() + tmp = tmp[1].split(".") + slice_no = int(tmp[0]) + + if ( + slice_type + not in inline_alias + crossline_alias + timeslice_alias + ): + print( + "File:", + fname, + "could not be loaded.", + "Unknown slice type", + ) + return None + + if slice_type in inline_alias: + slice_type = "inline" + if slice_type in crossline_alias: + slice_type = "crossline" + if slice_type in timeslice_alias: + slice_type = "timeslice" + + # Read file + print("Loading labels for", slice_type, slice_no, "with") + img = scipy.misc.imread(fname) + img = interpolate_to_fit_data( + img, slice_type, slice_no, data_info + ) + label_img = parse_labels_in_image(img) + + # Get coordinates for slice + coords = get_coordinates_for_slice( + slice_type, slice_no, data_info + ) - # Get coordinates for slice - coords = get_coordinates_for_slice( - slice_type, slice_no, data_info + # Loop through labels in label_img and append to label_coordinates + for cls in np.unique(label_img): + if cls > -1: + if str(cls) not in label_coordinates.keys(): + label_coordinates[str(cls)] = np.array( + np.zeros([3, 0]) ) + inds_with_cls = label_img == cls + cords_with_cls = coords[:, inds_with_cls.ravel()] + label_coordinates[str(cls)] = np.concatenate( + (label_coordinates[str(cls)], cords_with_cls), 1 + ) + print( + " ", + str(np.sum(inds_with_cls)), + "labels for class", + str(cls), + ) + if len(np.unique(label_img)) == 1: + print(" ", 0, "labels", str(cls)) + + # Add label_img to output + label_imgs.append([label_img, slice_type, slice_no]) - # Loop through labels in label_img and append to label_coordinates - for cls in np.unique(label_img): - if cls > -1: - if str(cls) not in label_coordinates.keys(): - label_coordinates[str(cls)] = np.array( - np.zeros([3, 0]) - ) - inds_with_cls = label_img == cls - cords_with_cls = coords[:, inds_with_cls.ravel()] - label_coordinates[str(cls)] = np.concatenate( - (label_coordinates[str(cls)], cords_with_cls), 1 - ) - print( - " ", - str(np.sum(inds_with_cls)), - "labels for class", - str(cls), - ) - if len(np.unique(label_img)) == 1: - print(" ", 0, "labels", str(cls)) - - # Add label_img to output - label_imgs.append([label_img, slice_type, slice_no]) - - # except: - # print('File:', file, 'could not be loaded.') return label_imgs, label_coordinates diff --git a/experiments/voxel2pixel/tb_logger.py b/experiments/interpretation/voxel2pixel/tb_logger.py similarity index 100% rename from experiments/voxel2pixel/tb_logger.py rename to experiments/interpretation/voxel2pixel/tb_logger.py diff --git a/experiments/voxel2pixel/test_parallel.py b/experiments/interpretation/voxel2pixel/test_parallel.py similarity index 100% rename from experiments/voxel2pixel/test_parallel.py rename to experiments/interpretation/voxel2pixel/test_parallel.py diff --git a/experiments/voxel2pixel/texture_net.py b/experiments/interpretation/voxel2pixel/texture_net.py similarity index 100% rename from experiments/voxel2pixel/texture_net.py rename to experiments/interpretation/voxel2pixel/texture_net.py diff --git a/experiments/voxel2pixel/train.py b/experiments/interpretation/voxel2pixel/train.py similarity index 92% rename from experiments/voxel2pixel/train.py rename to experiments/interpretation/voxel2pixel/train.py index e2e37679..250311b1 100644 --- a/experiments/voxel2pixel/train.py +++ b/experiments/interpretation/voxel2pixel/train.py @@ -15,7 +15,10 @@ import utils # Parameters -DATASET_NAME = "F3" +ROOT_PATH = "/mnt/dutchf3" +INPUT_VOXEL = "data.segy" +TRAIN_MASK = "inline_339.png" +VAL_MASK = "inline_405.png" IM_SIZE = 65 # If you have a GPU with little memory, try reducing this to 16 (may degrade results) BATCH_SIZE = 32 @@ -42,11 +45,11 @@ network = network.cuda() # Load the data cube and labels -data, data_info = read_segy(join(DATASET_NAME, "data.segy")) +data, data_info = read_segy(join(ROOT_PATH, INPUT_VOXEL)) train_class_imgs, train_coordinates = read_labels( - join(DATASET_NAME, "train"), data_info + join(ROOT_PATH, TRAIN_MASK), data_info ) -val_class_imgs, _ = read_labels(join(DATASET_NAME, "val"), data_info) +val_class_imgs, _ = read_labels(join(ROOT_PATH, VAL_MASK), data_info) # Plot training/validation data with labels if LOG_TENSORBOARD: @@ -138,7 +141,7 @@ use_gpu=USE_GPU, ) logger.log_images( - slice + "_" + str(slice_no) + "_pred_class", class_img, i + slice + "_" + str(slice_no) + "_pred_class", class_img[0], step=i ) class_img = utils.interpret( @@ -153,8 +156,8 @@ use_gpu=USE_GPU, ) logger.log_images( - slice + "_" + str(slice_no) + "_pred_prob", class_img, i + slice + "_" + str(slice_no) + "_pred_prob", class_img[0], i ) # Store trained network - torch.save(network.state_dict(), join(DATASET_NAME, "saved_model.pt")) + torch.save(network.state_dict(), join(ROOT_PATH, "saved_model.pt")) diff --git a/experiments/voxel2pixel/utils.py b/experiments/interpretation/voxel2pixel/utils.py similarity index 59% rename from experiments/voxel2pixel/utils.py rename to experiments/interpretation/voxel2pixel/utils.py index 011b0041..e700ec5b 100644 --- a/experiments/voxel2pixel/utils.py +++ b/experiments/interpretation/voxel2pixel/utils.py @@ -53,190 +53,131 @@ def interpret( # Wrap np.linspace in compact function call ls = lambda N: np.linspace(0, N - 1, N, dtype="int") - # Size of cube - n0, n1, n2 = data.shape - - # Coords for full cube - x0_range = ls(n0) - x1_range = ls(n1) - x2_range = ls(n2) - - # Coords for subsampled cube - pred_points = ( - x0_range[::subsampl], - x1_range[::subsampl], - x2_range[::subsampl], - ) + #Size of cube + N0, N1, N2 = data.shape + + #Coords for full cube + x0_range = ls(N0) + x1_range = ls(N1) + x2_range = ls(N2) + + #Coords for subsampled cube + pred_points = (x0_range[::subsampl], x1_range[::subsampl], x2_range[::subsampl]) - # Select slice - if slice == "full": + #Select slice + if slice == 'full': class_cube = data[::subsampl, ::subsampl, ::subsampl] * 0 - elif slice == "inline": - slice_no = slice_no - data_info["inline_start"] + elif slice == 'inline': + slice_no = slice_no - data_info['inline_start'] class_cube = data[::subsampl, 0:1, ::subsampl] * 0 x1_range = np.array([slice_no]) - pred_points = (pred_points[0], pred_points[2]) + pred_points = (pred_points[0],pred_points[2]) - elif slice == "crossline": - slice_no = slice_no - data_info["crossline_start"] - class_cube = data[::subsampl, ::subsampl, 0:1] * 0 + elif slice == 'crossline': + slice_no = slice_no - data_info['crossline_start'] + class_cube = data[::subsampl, ::subsampl, 0:1,] * 0 x2_range = np.array([slice_no]) pred_points = (pred_points[0], pred_points[1]) - elif slice == "timeslice": - slice_no = slice_no - data_info["timeslice_start"] + elif slice == 'timeslice': + slice_no = slice_no - data_info['timeslice_start'] class_cube = data[0:1, ::subsampl, ::subsampl] * 0 x0_range = np.array([slice_no]) pred_points = (pred_points[1], pred_points[2]) - else: - class_cube = None - # Grid for small class slice/cube - n0, n1, n2 = class_cube.shape - x0_grid, x1_grid, x2_grid = np.meshgrid( - ls(n0), ls(n1), ls(n2), indexing="ij" - ) + #Grid for small class slice/cube + n0,n1,n2 = class_cube.shape + x0_grid, x1_grid, x2_grid = np.meshgrid(ls(n0,), ls(n1), ls(n2), indexing='ij') - # Grid for full slice/cube - full_x0_grid, full_x1_grid, full_x2_grid = np.meshgrid( - x0_range, x1_range, x2_range, indexing="ij" - ) + #Grid for full slice/cube + X0_grid, X1_grid, X2_grid = np.meshgrid(x0_range, x1_range, x2_range, indexing='ij') - # Indexes for large cube at small cube pixels - full_x0_grid_sub = full_x0_grid[::subsampl, ::subsampl, ::subsampl] - full_x1_grid_sub = full_x1_grid[::subsampl, ::subsampl, ::subsampl] - full_x2_grid_sub = full_x2_grid[::subsampl, ::subsampl, ::subsampl] + #Indexes for large cube at small cube pixels + X0_grid_sub = X0_grid[::subsampl, ::subsampl, ::subsampl] + X1_grid_sub = X1_grid[::subsampl, ::subsampl, ::subsampl] + X2_grid_sub = X2_grid[::subsampl, ::subsampl, ::subsampl] - # Get half window size - w = im_size // 2 + #Get half window size + w = im_size//2 - # Loop through center pixels in output cube - for i in range(full_x0_grid_sub.size): + #Loop through center pixels in output cube + for i in range(X0_grid_sub.size): - # Get coordinates in small and large cube + #Get coordinates in small and large cube x0 = x0_grid.ravel()[i] x1 = x1_grid.ravel()[i] x2 = x2_grid.ravel()[i] - full_x0 = full_x0_grid_sub.ravel()[i] - full_x1 = full_x1_grid_sub.ravel()[i] - full_x2 = full_x2_grid_sub.ravel()[i] - - # Only compute when a full 65x65x65 cube can be extracted around center pixel - if ( - min(full_x0, full_x1, full_x2) > w - and full_x0 < n0 - w + 1 - and full_x1 < n1 - w + 1 - and full_x2 < n2 - w + 1 - ): - - # Get mini-cube around center pixel - mini_cube = data[ - full_x0 - w: full_x0 + w + 1, full_x1 - w: full_x1 + w + 1, full_x2 - w: full_x2 + w + 1 - ] - - # Get predicted "probabilities" - mini_cube = Variable( - torch.FloatTensor(mini_cube[np.newaxis, np.newaxis, :, :, :]) - ) - if use_gpu: - mini_cube = mini_cube.cuda() + X0 = X0_grid_sub.ravel()[i] + X1 = X1_grid_sub.ravel()[i] + X2 = X2_grid_sub.ravel()[i] + + + #Only compute when a full 65x65x65 cube can be extracted around center pixel + if X0>w and X1>w and X2>w and X0 Date: Tue, 8 Oct 2019 07:12:59 +0000 Subject: [PATCH 057/207] Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 --- .../01_F3_training_and_evaluation_local.ipynb | 989 ------------------ 1 file changed, 989 deletions(-) delete mode 100644 examples/interpretation/notebooks/01_F3_training_and_evaluation_local.ipynb diff --git a/examples/interpretation/notebooks/01_F3_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/01_F3_training_and_evaluation_local.ipynb deleted file mode 100644 index 1447fa51..00000000 --- a/examples/interpretation/notebooks/01_F3_training_and_evaluation_local.ipynb +++ /dev/null @@ -1,989 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Model training and evaluation on F3 Netherlands dataset" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this notebook, we will train a deep neural network for facies prediction using F3 Netherlands dataset. The processed F3 data is made available by [Alaudah et al. 2019](https://github.com/olivesgatech/facies_classification_benchmark). " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Environment setup\n", - "\n", - "To set up the conda environment, please follow the instructions in the top-level [README.md](../../../README.md) file.\n", - "\n", - "__Note__: To register the conda environment in Jupyter, run:\n", - "`python -m ipykernel install --user --name envname`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Library imports" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's load required libraries." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "import logging.config\n", - "from os import path\n", - "import random\n", - "import matplotlib.pyplot as plt\n", - "plt.rcParams.update({\"font.size\": 16})\n", - "\n", - "import yacs.config\n", - "\n", - "import cv2\n", - "import numpy as np\n", - "import torch\n", - "from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize\n", - "from ignite.contrib.handlers import CosineAnnealingScheduler\n", - "from ignite.handlers import ModelCheckpoint\n", - "from ignite.engine import Events\n", - "from ignite.metrics import Loss\n", - "from ignite.utils import convert_tensor\n", - "from toolz import compose\n", - "from torch.utils import data\n", - "\n", - "from cv_lib.event_handlers import SnapshotHandler, logging_handlers\n", - "from cv_lib.segmentation.dutchf3.engine import create_supervised_trainer\n", - "\n", - "from cv_lib.segmentation.dutchf3.utils import (\n", - " current_datetime,\n", - " generate_path,\n", - " git_branch,\n", - " git_hash,\n", - " np_to_tb,\n", - ")\n", - "\n", - "from deepseismic_interpretation.dutchf3.data import (\n", - " get_patch_loader,\n", - " decode_segmap,\n", - " get_test_loader,\n", - ")\n", - "\n", - "from utilities import (\n", - " plot_aline,\n", - " prepare_batch,\n", - " _patch_label_2d,\n", - " _compose_processing_pipeline,\n", - " _output_processing_pipeline,\n", - " _write_section_file,\n", - " runningScore,\n", - ")\n", - "\n", - "# set device\n", - "device = \"cpu\"\n", - "if torch.cuda.is_available():\n", - " device = \"cuda\"\n", - "\n", - "# number of images to score\n", - "N_EVALUATE = 20\n", - "# experiment configuration file\n", - "CONFIG_FILE = \"./configs/patch_deconvnet_skip.yaml\"\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data download and preparation\n", - "\n", - "To download and prepare the F3 data set, please follow the instructions [here](../../../interpretation/dutchf3_patch/README.md). Once you've downloaded and prepared the data set, you'll find your files in the following directory tree:\n", - "\n", - "```\n", - "data\n", - "├── splits\n", - "├── test_once\n", - "│ ├── test1_labels.npy\n", - "│ ├── test1_seismic.npy\n", - "│ ├── test2_labels.npy\n", - "│ └── test2_seismic.npy\n", - "└── train\n", - " ├── train_labels.npy\n", - " └── train_seismic.npy\n", - "```\n", - "\n", - "We recommend saving the data under `/mnt/dutchf3` since this notebook will use that location as the data root. Otherwise, modify the DATASET.ROOT field in the configuration file, described next. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Experiment configuration file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We use configuration files to specify experiment configuration, such as hyperparameters used in training and evaluation, as well as other experiment settings. We provide several configuration files for this notebook, under `./configs`, mainly differing in the DNN architecture used for defining the model.\n", - "\n", - "Modify the `CONFIG_FILE` variable above if you would like to run the experiment using a different configuration file." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "with open(CONFIG_FILE, \"rt\") as f_read:\n", - " config = yacs.config.load_cfg(f_read)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## F3 data set " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's visualize a few sections of the F3 data set. The processed F3 data set is stored as a 3D numpy array. Let's view slices of the data along inline and crossline directions. " - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of inline slices: 401\n", - "Number of crossline slices: 701\n", - "Depth dimension : 255\n" - ] - } - ], - "source": [ - "# Load training data and labels\n", - "train_seismic = np.load(path.join(config.DATASET.ROOT, \"train/train_seismic.npy\"))\n", - "train_labels = np.load(path.join(config.DATASET.ROOT, \"train/train_labels.npy\"))\n", - "\n", - "print(f\"Number of inline slices: {train_seismic.shape[0]}\")\n", - "print(f\"Number of crossline slices: {train_seismic.shape[1]}\")\n", - "print(f\"Depth dimension : {train_seismic.shape[2]}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's plot an __inline__ slice." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "idx = 100\n", - "x_in = train_seismic[idx, :, :].swapaxes(0, 1)\n", - "x_inl = train_labels[idx, :, :].swapaxes(0, 1)\n", - "\n", - "plot_aline(x_in, x_inl, xlabel=\"inline\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's plot a __crossline__ slice." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABCsAAAFfCAYAAABnQEYhAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nOy9a6h1a5Ye9IzxzrX2/r5zqrqq6CimUzEaRJT4RzAdf6gBFYOx0T9eIiGiSFAJImpQFDUGL/mlKP6QgBfoqEETwVabSMQE8UYiiEYFpZE01V6gL1Xlqe98e68133f4Y1ze8c619qk6Zfqcb6fHA+fsvdea853vbc5vjmeM8QwSERQKhUKhUCgUCoVCoVAofCjgL7sDhUKhUCgUCoVCoVAoFAoZRVYUCoVCoVAoFAqFQqFQ+KBQZEWhUCgUCoVCoVAoFAqFDwpFVhQKhUKhUCgUCoVCoVD4oFBkRaFQKBQKhUKhUCgUCoUPCkVWFAqFQqFQKBQKhUKhUPigUGRFoVAoFAqFQqFQeNUgoj9ORH/vF31uoVD45UORFYVC4QZE9KeJ6D0RfUJE3yGi/4aI/j4i+r7PDCL6dUQkRLR9EX0tFAqFQqHwZxfsPeSv+7L7USgUvlwUWVEoFF7CT4jIVwD8+QB+H4B/DMC/8eV2qVAoFAqFQqFQKPxKQJEVhULhMyEi3xWRnwLwtwP4u4joNxDRbyWi/4GI/l8i+hYR/Z50yn9pP79DRN8jor+SiH49Ef0XRPSLRPQLRPTvENHXvvDBFAqFQqFQeJUgoq8T0X9CRD9PRN+233/N4bBfT0R/wt5P/iMi+kY6/zdZpOh3iOh/JKLf/MWOoFAofF4UWVEoFH4giMifAPBzAP4qAO8A/A4AXwPwWwH8/UT0t9ihf7X9/JqIfCwi/y0AAvAvAvjVAP4SAN8E8Hu+uN4XCoVCoVB45WAA/xY04vPXAngP4F87HPM7APw9AP48ADuAfxUAiOjHAPynAP45AN8A8I8C+MNE9Ku+kJ4XCoUfCkVWFAqFz4P/C8A3ROSPi8ifEpEhIv8TgH8PwF/z0kki8jMi8kdF5FlEfh7Av/RZxxcKhUKhUChkiMgvisgfFpFPReQTAP88bt8lflJE/mcReQfgnwLwtxFRA/DbAfy0iPy0vbv8UQD/PYC/8QsdRKFQ+FwoAbxCofB58GMAfomIfhyqY/EbAJwBPAD4D146iYj+XAD/CjQq4ytQovTbv+y9LRQKhUKh8GcFiOgtgH8ZwG8B8HX7+CtE1ESk29/fSqf8LIATgB+FRmP8rUT0E+n7E4A/9svb60Kh8P8HFVlRKBR+IBDRXwElK/4rAP8ugJ8C8E0R+REA/zo01QMA5M7p/4J9/peJyFehHg66c1yhUCgUCoXCPfwjAP5iAD9u7xKedprfJ76Zfv+1AK4AfgFKYvykiHwt/feRiPy+L6LjhULhh0ORFYVC4TNBRF8lor8JwB8E8AdE5E9BoyN+SUSeiOg3Avg70yk/D2AA+AvTZ18B8D0A37W80d/9xfS+UCgUCoXCK8WJiB79P2g0xXuogPc3APwzd8757UT0l1oUxu8F8Ics6uIPAPgJIvobiKhZm7/5jkBnoVD4gFBkRaFQeAn/MRF9AvVG/JNQnYm/2777BwD8Xvv+nwbw7/tJIvIpNI/0vzbF7d8E4J8F8JcD+C5U4Oo//MJGUSgUCoVC4TXip6HkhP/3NQBvoJES/x2AP3LnnJ8E8G8D+H8APAL4BwFARL4F4G8G8E9AnSrfgjpOyhYqFD5gkMi9iO1CoVAoFAqFQqFQKBQKhS8HxSYWCoVCoVAoFAqFQqFQ+KBQZEWhUCgUCoVCoVAoFAqFDwofHFlBRL+FiP43IvoZIvrHv+z+FAqFQqFQ+JWFehcpFAqFQuHLxwelWUFEDcD/DuCvB/BzAP4kgN8mIv/rl9qxQqFQKBQKvyJQ7yKFQqFQKHwY2L7sDhzwGwH8jIj8HwBARH8Qqtx79wWhffyRbN/4xhfYvUKhUCgUXgcu3/q5XxCRX/Vl9+MV4nO9iwDAmR7kER99Qd0rFAqFQuH14BN8+4d+H/nQyIofg5YScvwcgB/PBxDR7wTwOwGgff3r+NX/8D/0+a5Adz5LwSWUfhey48l+Px7knwkOB6Tj7HO6F8DyWUEtcujrvT7m89Ox0e/PuNZNf45/35sn3BlmnoMfAjdjodvrLMfcrAVux/5DdIJGbuT4/aH9wybxZc4/P6s/33fuPwsvrGO+/vL9eKGdO/1a5vx43nFBfJ0YsSZ393g+9TMOIMGL+/l4+WXQqa/R5+P96Oub2z9ci1767oXjY/yHz2J/0AvtvLRX7z1nrO8v4vts9nunLmtxOH/ZS3kuP+NZQgJgzGec8J3Nn56XN88menl/3KyZXfs4rhefB3Ln+O/znLtpy7v+OZ9vMc6Xzrv3bH/ps7yv7rXzGevzs7/rd//sD9jlworv+y4CrO8jj3iLH6e/9ovpXaFQKBQKrwj/ufyhH/p95EMjK74vROT3A/j9APDwzW8K8ot1HJR+z0YVANkORMMAqBOo28f5BXkTjE30nE2ANs8lFtCmF5adIZck/9HMWHGDoBMk99Ff7q/zLZMGQax96vNz4fSu733c1bijbmNlvYyPkRgYm8TnNNJ7rl976Lnk7R7nkNK1zWAZTdvWfgmoE2STu4Y6ddI+pTnLhoMf63NP3a7Z5riFRfuwa595t8/9mCb2t+h1jgaCXzsTESTr57ZOsnPMAV15zjcD4zzmfvHr+O8DwHPTvvo10vWkCcCyGNCReSW23zot8+zzu0Dmmt8jVsjn92DwtgvCaIt1SHvFPxO2e8Pa4T01MnTuIaTj3CjWYJwH5KRjFO/XmNeZHbF5YNG+JmNZrgxceOmnzlvuo/3NAmqic2jrhPfaGb8v+ALwhZa9xFdCu8z7IeYFwDgB7dkuMwDqMud1zMv7nu0n3TdjS3txA/oZGGfBOH0G8WXDBOlaDXseid+vTdaLtdTAYoTTes/K4Zp5PbwJm7+XjFvx5w4L8Ow3+rypZdN73p9bJDrn23v7uwP7W+2nANjeE4R1fn2OZFvvVR23dWATULNn6iDI+2bPB9sn9sxBJ10XHwdLWoe0b3YCX+z5Ypfg6xxTPEsyJ7LN+QnSRua/D74O8RzDC+TJpn1a+mknxDUPJFm+J2nof+Nk//6cxnI/EOs9wNtA29aHtwhhdLpd58KfceT3ka/SNz4npVUoFAqFQuH74UMjK/5PAN9Mf/8a++w+CJCTrPaAe/qydzN7QrfjmyUWI1mNFf19bNo+mpEV9mZJBFAbSlgAkNNAzzZFE/3ODeFO03CAkhsQQMxyCXLCX0aXF2NJdkgiGvJLsOCzpVLdLiSAwjLRl1kxO5x3WkkfSeSH/U4tGQVM4CswOkF8FyXDKjyBYYBpn+VgVAkLiAFi98Km8+1vJzxujK9BOp5BSmi4AWDjkLOsx7uR7h75rsY3yAwiwTTM/XsCGBzkwSLx4uu2EwiTcMiGjRr2MgmMO2EXixf7iJiuZNwfjBykQ5ywyJeg4z3Ah/k1okLOAnHDtOXFJ5sTMQIKej9A976SEADCEHyhb52AYXOe1kH3eDIyCdOA9XOToS1G8CAb1wSMs7XFhLEdxrsJwLqeToz5vI8NiawE0OjlqARW0i5+Oj9zUsNynDBJvqOhivVvb2/53J5fSmLxbT8GwgjW58AkDvK+6A/5geTMi/0v74V0AfK1EdLnXj4XAJro8/YBQZgoYemTQBgPk9iTxgCLrgV8r2CSfN4PWq8lQ/cJ+X7xuWna3zDqnQTkNH/D51DvYycLJO2FuIl9aPmesfmUTBLx7b0Xp3K6ZpriuK8YCzFHkp6HYl1x8iT/2+T7m9Lf/pyw5zaEMOx+YxZQ/PukNzwdCc/C58HnexcpFAqFQqHwy4IPrRrInwTwFxHRX0BEZwB/B4Cf+pL7VCgUCoVC4VcO6l2kUCgUCoUPAB9UZIWI7ET0uwD8ZwAagH9TRP6XF09g80Z5iDmgnrGB6Yk1j/uar57cmxadgew591SQ5IHEhWcEhDnKhnuDD25NcU9eyu1fPIi5/fy3e+Szx69rKDU2Ce9+5zW8mK5044kmmVETJOo9hofPA+bZ1D9GJ/BmEQXmvWMP9fbriH4me5q6rmHV/XGGVGevp5gXVTwqpc+F8HHldAf1JtvpF/Mu0wynHhuWlBbqAK5k0TBz/j1NYmzT8+qRJXnpuR8+P0Ys2PLRJW+E6YX3MWtqBuDh4kuECs2t4foOI6XGRKRKvgQB4mkYeQ/l9I8j3KEtQN7rsoUT/D5Y5rU7QGQRKg8jvgeLLh0LsFvYQKQyMehq4/Y9c7yGIKKBchrQTOfxvsqMNhgpFeWwJrxjSZXqb/TeGA+aYtDPdj+n+erQwI+Yq52WdJpIqUp7IdIUYPvU5+CYcuFt2P6lXb3iOcIlR0541AUNROoFgZZ0G+qaqsIXb3ue28+4ieKhke89YH8kTb8gLOkFvN/Za/7sY0SU1P5GdJkfxpynK0HOAzgPa0QgAxhf0b1CJJCrLWYn9Lf9zrMXFgnFMwXPI5l8T/U51rxPxhm3zxQhRDoVAH5isKeVDUBO+hzwVJTxYP3IzwxJUWXDP6KZdsayRo35s9v3cpvX93Q/uhJatwgXSZE6A6DoM+La0uZeyM/19l6PbZe5meKfmjSv4zwjToT0H0/50FwRrwif+12kUCgUCoXCLws+KLICAETkpwH89A9+AixcNoUSe3i2/5d0KGgQJAvu+cuovwt6iLq9SMJCrfmJw4B3zJfpmR7hfTrmIi/5yP6Sy9qPMPKSTkDwH2aESCInXKsh4mIOL8/6ok4RFhyhyMkwIYaGgBMgZ8F4Ywac60M86bXbczIYk3SDNmI/nRzy4/J38ZJubefUBzdMPYxb5hzw1fPE0/FYOCENt7d1UiNsXR/PV5cNEZq/pFCkvo4cmn2an4tdm4aTImtf3BiMsPw7kdd0+Mmpn743pOm6hgETfZykTtY1OV7HiR0/J3ixo5aH7c1JvM35zekr/dH7p3tb51DiPDeit08J7dmN7MNAsyEvAHVZ5sjvmf5oY2wU6QG8z2t4ChEZ08ZXbbCbNsT1KwRhQn/UORxnTcmI+4mntsmRVIz2/ANPXTJiIuab0zxezeBEImh208V4JtA17Yc8F77HE/EQ87PDjGxZuJD2LDF/3kW+AGJpUz4nNGb3XaPD01WyEZt1NafBa4a2k28D2M66h8aJp27HDoxTw3hI88fA/nbE+e09h7bKOAtympvPCe2aQkbdxhxkxZwjJ05GQyK1yJ61qkfiUxpzCqC9p4WQ2UlATDFPk3TS74/3LHn6SMeSJpa/DyQti/jc19hTCR8o7rtlDvLzQmzve7O2XrwD7QlGXB1u+NSekyqRktTmOhZ+eHzud5FCoVAoFAp/xvHBkRWfCwLQRV+mxQyXG00KezlejGSm9eWSEfnv+XwVc4MZw9MADJvMc99FBTSz9kD2th+No/B2uk6DG/tuBDdrA2bkDe1f2P3uHfT+H7xsc35oki9CQBf1nHsfLX9dmgAnNczcUJcTAzuFkR86Gem67rX3eTsKxAWRZJ5PutJKFIgZfT4Gmi/5lDQ0VBQP4VWeuhlmSJNgPEy7K172zcPq2gI/iKK/IO0B2zbSCNzFvLFY2AfqRjTQXP+s9RGNEkIAMhsaYWixEyNJcNTOVcMteZCTobuQN0SrngGAzckmzM9IpoHOV5hHX0JkcgHp/KkWA4NEjUU244l3if5k8ibGDSdibo0n1wYRE7sksT7YnLSrNjA2UtFL0jGCLLpg6PenT1Q35fQOU6+C57jHNvc8CKGhEB7sPfeNZv/zWOxvP5byGto8LuuG2T8gfU5zno6EhRqtJkjJwP6IIEVcQPcmCoXW851U4+skMOLrZh54msdRTxot6Zl4+t6874PUsbXWtijmR5p2Sjg/96YBflOtxefsQMxIoxAtdaIiz50TkjH/koiYvE4y23BNiSCVfI/eIdT0IpjRUqKEJw7r7fe6Coyu+zqe3Xa+fDrn1Meg0UHpH5PDXsiRE0GkpT5nstbbbde5No2A0Qjb8w/wwCsUCoVCoVD4gPG6yQpgWkkRTo14gZuWdDKSM2EQL4UEci9ufDdf/kASKQj5mJ68YTeK8G6wDu0fDYSDTiOoZRHSXGyWnCaQDNSjketkRER1ZALG+++kgIUfxwvvmO0KmxGTFOflNIATsJ9XI/kmfJygaRKMQx9pjRKIv5cpnF7VZHgA6hn3g8bZfu3q8R/ng5fUJ5BTv6DpLWu6DSyyBgtCDNGuF2keZnjISdDbwUjwAQy/ZjLY/evkQReaRmFUHfF+A1O8cRNwn59H9I97kg8GZDZwcj/8/O1dMtR9PpPh4/fAOGnagO7XVB3DzuMOiEiIc/YHbeD6FROXzJFFWNfTo0aW9AqaqUYgjYKgTlMU0fecrwMoSCHZLCXI+3aZhrLPO8ns0zgjiCbxaAO/R6x9vtItiSC48ZzzNa0zTfKgW4qC3ks+LJpr7UZ8EGDrHI2Tzu14kKg+I6cx0w8YoG1oqsVQ8URqMm9HG0sXwvDqRNdswRuciPN7M1f3SM+j7R1FhMa8L2cUzdgQ9xanZ2d/czDs/blgyOQCCR0qquhzNlcYyjlMTgBoFAqF2GiObFNhVVGiIj3rZgdiKJNsOTy8Z5SRaITXnp7votEdvE8x0uW5ZuPN0ShHQkmvQfF5JjoBRGRKlAVm3V/5e5/DGdU195qPgXu+cKFQKBQKhcLrw6snK6LcYoQ6S7yIBrJWgx8H6Ett5AmbYeQaCmb0Enm5xhe8VOGdnSX21LOXSIqRiA9vm6CGQnghMfPjU1UGUCIKInLDy/Gl8PZBU7Zi3ItgwDToMQ06zw9vF8JoKcR7U+X/qPpwL6bYrYFG6/h8mE5QeNTKvdNz6cCDgRvh+yeZaRCnMQ0ui5jRyBmf1znXN3oYDZBjL8KimR+5VsKAXdOrUBBu9wHbOAWrgYVkgEgiSUiWvXbjHQcgA8jEzVL9IK+tT2r2Th/barfH+lx6H7Nuh+9H9+jG+H2OSFNLvFpEfzNmtRwnh8IVf2SmROfLKulcL25h0ky7OjI+GV6i0aOQdp0Yuqrh52kf6u2mILXGeUxjzqOorC86v+v4Qlegz7GEoWwVYlzrw+chSq2eBvihzz3Y9SJLqdVgGARk+6mdO7gNPJ46mAfGYOw7o7V537Wmn3t7zAK2zcAkYB4QIfTBGtRk1/Zzeif9XWYpZRGaxTGEtByzAJfOoKbtRZWJJrhcmpaa3cac/6e5WUPrxAjK4972cUfamFVO0kFYRQsCpBNGVP/JGxzrPvO2ljbs0EHAlbSf+Xk4ZoST+LmJ9BQGxMiBcZaV4KQxSYZcdvVOOWGQ7c1DdA6ASbT6/et7E7rHsJOVP7WH98kf4mncQFScorb+GzOeNvBjEhgqFAqFQqFQeIV49WRFaE5kbGMxPG7QUlnRq728h7c8GUtkxq0JeFJEPNiLopXdBAnGlTH2yYTQefivGDdie3qNceVZftH7bdeNk4EZaeHHdoL0pAEh7g1MxpoPI16y04s5AIFpdzCF2B93gDxt4JlmxEUyNJeXfouaCI9sNqQPZAgIa0lN827nKIWsB0FQg4UY6E1mukgqWUlXjhz4GC/Lja0b6Tj3jP3o0BwbeYqEkU3iJS/J7K/wtk+D3z2oS2RF3kqxHsmjauNbUzn899RJ/w605PXn76Lv/j/fKo9jknliRIUQkHP4fV9kgyencrjhGQNDePzduAUAEUaUDY7OYBqY0DUQ4XWNWMkOjxbwMoxe5taNViIjcgZBxjRAxYRU5VF/0kknSNL9FCSWAGQ5NwtJYdEM1JS9WYR2fXrTYjrJ4PcTp5/cBoaTFDxzb+SkcyVCU6DU+ihCGJ3xtDcdY2fIU2Ka/B4yHYXQU8iG8GbM0zY06sIjRzxC5coqFHwQEF50OXxJzh1MgtN5D9KDWSCJTOldN/x4a6QF6dgBYHQGkWhpTU9lOUSm3fCGgzCcwPJnaX625+ew3VDi+83XZuiz0TVygsj0pbbnZERPEEL/IyMiNBgrGWJrEUSF74NgfNLcNrE9OfcRSP/daKexnAaB/lsC/fdCOsW9EFPm28/WNe4LFmzbDIthFlyb4Ee//gn+9M3ICoVCoVAoFF4PXjdZ4V71/MINrEbSsYLCIH1jDbE9O7Dlc1I7bsSRhGHhxhTxNKx642mgQD2dlIwuZvV8Zux70xf09EIPAGO3l3AzBojHYiD6C7kKEqY5CO8iRT+kQSMTTGhxIXayoTMAvvA0xK8ughdvyKtRndq45wiPFIpjJHoyhrNYZDb8AQB92ge0my7JIdVgiSiwcOhYUpunnN8OokiliL5kQsedwknTwNMvIj3AyBHva/xn3l4PS/c+ZQIj2SvzZ1+GMb9LUTphVPpBSd8kzpN0TGpwPNiaRzpCigACrGwApnFunm06TeNHdr4lHly88l1TLRInje6MM8Lis3FKKXXE0l+ceNzP6ybz/opY/8WFGSna8rSPNdVCv4+QfWvrnp5Df8Ox3rnNOCYLbAKQfe0jhs5JJ1EjMukcAE5UWVMDQZpNPQhbjqse7PoXx4CmI1m16GvYWo4TbjRkAIQI6CT2ELovQBKzJMF43DAEuD4O0CVFTrAAJ4uq8NSyTLJGNITNWdprdNUcJY9U8PSrSOcx4c1InbLqQnMNdJ941BVkRkFlEU9/boXQ5kHPwtN7XhKg9PSc0NDYZApgejrRyZbA+5efq04ObQI5C7CvBKk0QT+l/SM6j+Mml5Ai4ox2iipC/nwhI2UEwKXNalH+78R3Tm/vD7BQKBQKhULhleBOUnOhUCgUCoVCoVAoFAqFwpeHVx5ZAfATmafNXZQaZg73wu7Z1a5esXGiteqHUGhEZFG88P6b13mIq95L5FfnnO7wug7SEGlo6K7m6GNGVpCgNUFrA8IEGoRu4d+AebL7DBWXS7tJQ6FNgKYRGLJZ2HpEDBy8m6eZF74EQXj0h4cf7xTRIbgw6HLwwh8847LJ1Ax5CR6yDZg2weFgWyd0gJKIKaUQfL7A9C9cJ8CaO6kgH5qF2Hda6DcyEb5jlEKutBHpMd4XSeKewGzP2hGeYdhLeomox3h4FAugGhl8mJ+0TjRo0dvw60TffRySImLcJU238wis0Srazoz68HOXe+LQzjhZBI5704d5rz2yxO8rr9BwXfsKzCiXG2FDP991WpaogNnGMYLG05BiW5tIZlSV2NXz3FijB6bYKcX3kbI0ML3583bC8GpCkS6DmQKQ++RbeRNrzz37mClPQET0LGlAKeXnKP7owp0uGkliFU/yPXcnjSKLLXoEi4/Xj8nVaSJSIcZDIXLbuneMgHfa1th4KV9MYuKmWRy4zQXL1XJuIjvG/LlEPKTPc0WdLJIZYI+S8UgK+5krmshs61g2Wvubfsq6HjbM2Cd8hVZFSutFA5CnFOWCuX+FZsUPEKE/YCnr6xFiI/3LG+342O25QRZR4Xs9NI2y7pDf25jzOTbBOAHPl4qsKBQKhUKh8Lrx+smKnSD5BbkBYrnKvK/HAvpiyheaL7Ge133MW2YoebDhxsj20FyYCJ9Ary8ptJdSSoWr2/dkBF/PQ9NKLPReLm0R0AyRP0A1KjgZwQTV3WgDfBqQzaoDhAFqY3LCxA2ugdABwDCiY1hudBPwaWiINwA8kOZODydCaNXXIAGdBjin36R5dhKnbSOIEiVxpq6HdLY+2BhzOHW+1Kdths03gZgeCE42B5uAW0e/NtWA8D7sDBFEzv6NAGQaS8w7MNNtslirp+YAi/gpCKoRwFqlgbcBTvonBCQyS62ikVN+jqKLdo2R53o4IYJIDVp0IfISZK0JAPTMRtqlceYUnEGL0aWfAfzedArMAD9qcTjU8JKZ/uLdc/FY62MWH+Udh8oFFIZ66H/kOWmWomClKLVyjcQY+YmsQgRAdFt1RhqWEpPjhAUkmOkHNI1c1S2wNvZJtAgBzHRLRsT9h5k+5P1wI1rm96C5V8fZ5nmzlBgA/c2hHHB+zjnpcUhx0DQIT6egheiSZlUl2Eg+21OZzPO1cYKiXeY6Cuk8teeVBCCvHDNmn5ywGhtuyIKYL0aUGI0hmA7HcEIqkUrcMYkNnzerBMJ0u0Hv7Vlf1yxorKTunENpoiVLgSihnEvPAkbg5fLCee3JU32A7dP1fvGKNDGHqY/cfUxQAtqIj5jrJPYag3LSasy54aEbi/qdiS8UCoVCoVB4RXjdZEU2gpJXKsQi7+gcDE+/d8NB3Gt2MLodnUyQMB0iahBg0PIC7dUHADPAur+cUvQNMC7igYDWNPffqzOEUeEESiI/UqSFfydMarSRR3nYtZmmSJ8buE64hIeP4kUc5AanTAPxpGQKnzu8esCg9Hbtc2c540FoJAgmaUFsBrgb/DnColmkCAu2s1qqm5VpJAIuH7XFKtwsR777+GAVD+hAyJCAQCFcKmQRK0udwNThgUkK5O88ksFIktAusaiR1saMtsgcw6BFs4RsnZyw2baBfmIwj1nBwC7l42IW7HtTscJhFRKEZgUF7+Ihysfn4Pr+pPPuYqxpznUSDxZdE93zzxzrdBRyFNdx8TlqqufSTiOCRMRy6fOc+N/XnbRKhn+wKxHmFTtyBRYAM3rHCD7eBhpP0Yn9qszA/ty0neuB0Gl6jRBUtL1OaS+3Tznu8TBsD88Or5CSy3Gu0SFWMtPvo7y3rLIIdQRZtNxvDyq+yQ8zZKFtHc2fBTLXlGhde/2eQv9mv7QpzukLAlun0wBYYu+KC1Lamg0jw7qRVTRSFBopMcQXCpIKANqT9Ss9b4XV0B65ylG2nWO+0t5yAU0n/+y5G8+KnXUdvbKLrSWarESv700ng3NZz7Rft/Oc6yBUBWhNcL3qM2f3ErCuj8KIewRJv2ZdDNdFIS3rmm49X3+v/pG1TCa57QSFsSAsGvEkaZ5j342IQvNSwMfonUKhUCgUCoXXitdNVkDD1l10z/92yBkhGDcjL2QtY+gv8y+82FE2qv1Xj7ogwYBW+nDiQ7/Xl0tiJQ5yiC+8OzsBu4v/macvCQ7GgV16yFEAACAASURBVD4WSn2J1AZtQ1gwNl5fyJ2YEPcOznBvH1d4DGUSKj7GsTHGg6C/6fBIDzW2Mlnh/82yqdPD6AZb0yyaJmtqg60FrOSjr4tEmUU1ylobOJtR4Ya+G/v7pWFc2xR89OaPZEMCdVrFWPOiWHnTm1QVf/PflFRYjEWxaJFkIeSykNEfwmIgAsAYNrc5JNzCD7wKANEsScksUb7SU4pEKMgSLUNJQYoAwLZ1jMEYY0aGgORFckT7xejnFnPoaUxiBmJjwWYCnE60EAk2HuhC8zaxPgyZxEUjrRCxRJcMCkLGS2m2VM3C56KxkhR+XLe9wm/12Mu+QQS4XttCGp1OPVUU0bG3JHYrAJ6fTxZFRLrVB0W0DOzv/bkthM66RwA6DY0kIok5zusoMtPDfFzez7ePzxiD8XDacdkbRAjX3hZR3kaCfTBOrWs/0xzmHd0fGNfHxIxGH+X2byKMFFVG1tfxVnNpmAQPj9dYz+vesF8bmAUnK216eZ4bmFmLAzMPW1OAm9+/1qUcaUCIShZeinWz8XkJVscYjOu1WbSUEX6nvlTCGIOi3OvjaUcfhL232Cu+FkyC89ZviB+/f3R+gWtv2Pe2VKQ5nfa4167Xo+qvtWN76fLcbsfuz9Pjvzs5zcei/ryqCzdZSKVVlVf7uychVOw8o+QKhUKhUCgUXilePVkRHrrwHurf3T12ZD9TeoGQ6MuclbK8ke0XTIPVPL1LWDmbyrt5yCi/fPo13DOcNQiOOdqdIhrDvb36RxocEIZ/LrXokRZ0hb4Jd1ktFsJCJmi/Z1CBsGhOf5ufUcfMX78S+Ero1+nNi/mxLsU40mXd4xeh4GYfhQmb5gjDujy0/yKE/egR9uoDlq4iT21W47gSmivtu6cy7wW/lHthyb2/a19yDvyNEQqdXyE1UAWAdC+fkPaKL03eK77X7Pd+YHRG09vvmo3IqDrhRi5mOo95no97ljczjgEjNWbqzfm8o7XJUrkxNpLxBswolTj/wdpu0+BxPZP8We9aBScM5xTdMUsszggYMoM0t8NWVWfbelx/47EYkt7fPvRaI6UUPZx3MICTGcVbG9haKuVoXXvYdB669TUbsG/PVwwhdCcnzFje7RpjMHaP9mHB6GpIRxqPRbuInQsjbYiSIc6C1hB9H4kUe/f+AWMQ3r0/QwZjdMJ4ThFFTa8rnTQ64h7flkop+zoIsBAu4uWSfcvnvWTPSdoGTo8e4dTxeNLfGw98/Pi8zF0fDLx5jvVuRqw1Hrj2hj4IJy9n6pEwnbEPtjngIPV614oYe3yOmDv/uW3dyLWVANmM1BmuU2KEhKepcdpLvv5P1w1sc+T7S0R/b21AJF0zzbOSZnZ/bTnXEDEPbATe3nnZZz4Pee2PJXo9egrQe8TJxEEcx/toZBhZTmNNT5OOlir6FAqFQqFQKLxGvGqyggRgy60OvYiLeaoJEH8j9ygKYI2RjbKcmOSEYKZ9+N+W8++RDdJE8+bNsBSSKVhn50xjWzuaw33RNNrBQ8FDJM6IgRzOO0OFZYr9eVg+SXi7c7j3MWzfZwLAEt0gNJZcbbpw6HyQkQB8pdl+bpGh3vZm3mePKkjpAdJkPe0QBo6OGeUyeC0nmEOe2xSFvBF0JImolMTL+FcRWSLNMjwIoJ6JK1tvtw86sJQh9LkR0oskPYTQZbBr+LDyHAVpJdDc/EPTUxBwivgt1/dBkaUYsKzRPtBoIvH9MYCeyLn+MavRzCOMPk8ZiN8tGmRGXqTLi2mXXHmSMnk/DFIh1vTdCKJurqFrPQgBu5cp9ZKXlsrEm0WQtJkyc6+PiwYJgMtZ60iqF1q9+mxef2CSLB4hcL22VT8FwPnBiIxOYQCPTCjI4do+3stMlxi+Nml+dk+h8NQZL+d5TMtyUrDr+XQlnBJ5GGKfgiiJC6z7XZrMPefHZFKxE9qVEAKjpASlTL4ltEcuX9Eb6tIE77eH2Bfbedf0JdObGYOxOZmRSKyjAZ4/650xrqypQDutxLA/e61/S8rRJkHYefSDa++QX9uJ2dgPt6yO90OCaML8t8CJ53zNk6Ye5fa5vUAEHEgGjWyihRwcY/Zx7bPvV5pjEyWhZE9pU8DUrvHnfxbybQI+d+yXV/3Pe6FQKBQKhcLrJiswgO29v+DZZ6KecycHIv/cvo53V9Lzs7J6fC/20m5kgYv+ceQnE/qDLDntSwUJcaNDpmEhWIx12jHV6AehPVmUBIwgMJIi8r3bNGbFBA3HWcmGqGbi7R81BdJ8eYWEeMGNaA6CnEZ4ltEpqmm4UN8NIeKiin6pbJjk/jjSsRg0q4B0CoLGvx8Peixdk1iqd9Xz+E2MUDabEzfSMtE0CBhydw2OkQ9xfPo+2um6PjEn/nWMHWFkScvfJ+P1sAlD1+QoCJnD8l1UkQhj11aP9pc82X5JRIZH4XSvJuDzD8SazgYQZMMxRaZDiZj2rMZRkCupf9RTcwyrcIOlYsQc29zL42x99T1u8zWcrDmIR05NCVp1Cj5tUakFogSBpLQj2nXN3r05AwKwid9y0hu4PMwHhUcE4TBNJKrDEMQSybJv49mRIooWrQ8ba1SV6Wker2msPOfOydhJVGISa94p6ycN7/ici7xXlqifINxS2/bMhBD6czo2XaqfH/DcZBKBg+bcpTVx0mXRzDCShHbC5ucngpJMVyRS+lwI0+/35vtH73nVC5paJNrIYS1sDfJ8kc1j29O9fOf5H/8ONFlEWUmAPT9f073tc+xb/+lxzD3ryHPiHyWtHG+D+hSJPhKUGPZY8DS+JGQ6ToLxhtG+VwKbhUKhUCgUXjfqbaZQKBQKhUKhUCgUCoXCB4XXHVmB6VniXHLAvZQWPZGLPwAHTzalzzA9cF5Sb5gHjbL3bAda9pgDS5nD+XmKYkgQD7/e7EsBtncUpetyqHdU20hRHMJQT9/g8EoT04wSOOgmxLwcvXu3EdKAh62fhoa8WxUJGSmCw9FS9IR5MWc6BE33YrrknMOUOuBebJ7RKuNNBxgYVoa2W3lLOcnU7mDR/rLm8x+FMRfhSB/7nbDw+L5bGdw8d+4BBs2qAjkIhJIn3jymS5SN7cH409c2WqDpgc8CqQ4+XOcgVxDO8BRCT+aVBQD6XptpRAQcS5XOijMeCWTnhcdZxxx7845mn0YAHSIJKI8zfeZj8o6na8xypwevfk6Fsq9HmiRPDSLxiARaioFgaBTFeKd7zstz5ueCuBAu9F7zEpMjlSMGLBojPO5znqc4iyyRDx4hEZUe/Hybr7glT4j7PiIzWMDnPNHA4bGz3N/cJTR2loidtJ/GSaJsqkZGHG4N0ciu9n7ugxwhs1nkhc6ZtrMdI4Fw+8yN70eKOsnjB9L6adss0Pvx+ABlCmHlSGW5F1nhOjYpbcZT1SKiziLgctoZCICX1yVtdFbf0GMlpWqRpMiHrP8zgHHmdZw5JTDuwXWIMg9d0g+P2kozhQzL/TZOwLg0nL97mLdCoVAoFAqFV4bXTVYQpkBlMprZVdMJiGqb/p5nVQ34oIuWX3aTVmAYl2OD5tYDkUce9smguCYA9LNMDQvPD09GORtxQBdEPn82GqRhNehotgWoMUG7Gmn9TJECEMKRMah1ru6GHueX5lxK0XO0yRoiN4TtACvF6OQFiRIbfDkYb8BiHOe+5ZKGI3QMZkg5nQZkM+uM1QLZtqlFEE1lzQWZZT2pDbc1MK6sOewp3NpLIwYpFLoP2YKy7z3NROhW0yIZjCBAsgq/p7vYHhQ3jmAGMhv70DCJgCx6mK1uSsZx5kNC5yOdnw11AFbEVY3B3L0TzdQZopu2yKbLyTixtVhKdrJYCd3Up34gtvI8+f2Y9jUN0so2bnwdyTWWMOQBANskrQYboXIFyMaQ7ydswIAE0aDt2ZgwDfLDtrrds0AIuEbYfTx/9AaXvIeFEumDua/okLqU52STpY/BE0g6/wW+jYzYA8SMZQoCxOdwPFi50GNlEGuXrPRre6YQpM17gTq0dKkZ2GOTSKdZNGewbuH8mZhhrWQQFp2b6K+RdCSzTU8TiUZ976V9saTg5HnN1/dnK2OmiKV8wZyi42lOkV5l7ebnPYBJUqT+K/Fjz/PQDU73v/96XE+fW4JVmlpFg3VIsqSuHEuo8jWte6FQKBQKhcIrxasmK4TsZffwItfd6Kb12DjgSFbQ4XtSQmI1yKaXlU6YL9YNoC5L/vs4iRl3sogwLsa7iHqrOwAizc92AcRkRGWjLqIW7AWeLK9cNSwsKgC35Ij3/6Zkp3uT7aU/xAEBiPBqrBEgJJOsEDUuOQni5RxroamtQCliJNajAeM8r31cL3puKipHaggTm7gekkDdriJ/6LRqbLiHcQtLHfTMi0c1+mPzqzoCq8G9jN0N0CwEEHPhHx8M9ExixGfpGiSzXSdC8nwBEBpTdJCggqq8XkO8dK93Kxm81I2UcWFHIHRY/PdchSb0GFwnwIapBq79bDIjcHz+2pjik6LCgN5PSuONeVnEKrWPcprf+X7KWMg0pD5adE23KKCsK+Ln0aApxku34+ZrinDxeUt/x/xzItWWtbTxepSPkJWvtO8PUUQgqHhjkBX6kzfB8GcJAW2bA4nys0ciJ2E4MebkSCaFmlb64DZLpnrFkJirTpDOuF547sm8pzuBLhyRQNIE/Gwio/skZ5b7LJ0f2iIWRSWnVAXp+FOwCHA6GbMQUU48HDR6lmddJlB8Tnz98/w4PErL9+CYosfa+Cpye8P5pGgcMhJ0EkbzOR8nH0m9ps/aIwlKmazyNfb9eqEbUmb/+AVWq1AoFAqFQuGV4FWTFfEul8pvumhmpE24F2ub39Mwb6wbIG2mH/i7I18oRMw81Da/iIfxdhJgmOc+GYvzBRwpFSG9hAtBorKFHAgTNRL4msZoxg2g1R4inB3zvBz2vkyQkxV3wvLDO7lZxY0wDOZ3s/QrIUwb0uMF05MNEozNDWIJY5QmBxJ9G+dkqGeSwgw1tpdvYTNoSL24A5hVU/a0YEI3nl3ZWEPpdzNUScKbHtdLXljt02EdgGnYuPHm43LDcE9zkAYTHtqD6OWRMKFB0xB3A8enhlYCQ49Np98RUowwf9i+H7a+JhoYoeN+Piebadd+xDylOYAbhZtE9QXZ2cgGjpShgJMJRgIRY03LySkUJthK21AiYyeNqgFuU0+O1iFrFA42TOMfiP3nVVBGV/LLia9mYxsySQBqWh50CEXJWAcZKQNClK48glgt6WNp2JHbEmglDJJZ0tUWbHRolQof+kEA1veeDFrGCtIx0TbJITl0kZzsEvvO7v/4HlBCo3Xg3LXcJ8lSOlSE0K0SxbA8ln5xdtDuB5/jVAkn5kFoqdDBjPibrVxoTt8ag2flGquSMXLkUbqmjxEkQciI2B71qU9zmEm/INR4Rl9F5BWA7m142ddD1E8ujU0nJYRAoqVtZZJW3r/4LBGReQxLFROZa66/YM4rz1K6C1mxDfDpeOMUCoVCoVAovC68arIikwjZi0+Lk8mM3pxLnV72NJyZ1pddN+iARcE+8qk3qDHFati4BkVc8UozCiJ7aLNX1bvGmMZ2hOCrfgU/z+gEaVM/wwmW7gZk9v7mYTcs0RpHJ96Nczb3wQ1xC2G+Mc3Yvhv24m2fiZcQJCSLY14o5vB0+13+fsm5f85aBoScFy/NDGs32nvy7loodLvYOULghhX+/u8aIeYt9i9nuDgAWr322r9ZwYB2LHoR2burEQu06HLoF+4ppsWrf0M4JUJCEvsT+e55zy+ETTLeCZNwSbZ0pHYMmqRE7mInwMY2BgMXQJkP8zj7/ZIiPHI6ROzxhiD/kH6Ka5+QQJAM36WD8xpxrs+z3YfqrdcSqJTmXiw1qJ27lkRlQUulTdkM5N4JzIJBSiSM3pYbhqAGdUbwj4OVS9hbRCcQy33NFKEoeRpz4BcAlpD+cU0LlZk1kmUfgATSlaxZ9BRIZqlON9xzxEX24KdICt4GOrR9r87CNnfMgjE8i4LCKA6igPXv06nfEg/xO5nBThEpNUZbSAIvRRtEQi5F6iR0TxE6y8IYQeSkk09Jfg5b5FZeniCEPF2MoMTD1l9eD879QuwvIsHgsRAuk0gSW4d0XtprOUXNO0wHstnLswIAn/r8ngTn836TLlcoFAqFQqHw2vCqyQr1tpuxcwhxD12JRAAAyVje5kuqDAAe5uvexixCaO+u5HoMF39PpUk4ACsTYCdO8bMZOjySF137TCZiaO1b/v1arhNg06Rw4zy3c1v+0saWvem5e5KIGagRTv1gDPr5hFQOVmKecTzW5w5zzEB6yU62Fhjgp0nqqDd/5maHnoCTAF0jTZbUHot4cDFUPXHmcvta9uxtp9tl8rY8dSfrn0wCgUJ3YMn0yCSVeUrn9RM5IX7sXPgQ5vM1NoOdsnFlcyWcBAWXfts+TtEUuX8uRBlEBx/a9h6leclRRBhsJSL1M494iXz5pMER95hg0VBRMmSWE450CLfXW9IduLo1mwc526Ucjh/rOq+je0E0BShFwIwmqovi2IYaq7C+bPYM4fRTKKUX+F7m6I+ktYOnIzmxaQbvzV7z/XRd1yG0Do73aU5dyQRVk0PaUjou9VeycW6GOCWyYklJSM+9wXxDJEZwSJ4TfzjCDGcyskeUwPHL+nz5nLm+DDppRA7SsydFX2FbyaF4PpNFTnRCCN+mCRBrh/x+9E5kotCf8UEK2JyQTDKPRdMKfZ5Cd0Sfa9I1JiYiaUj3T28yoz7G7Fe0Y88KZQB1DpetkqI6ctRF/G19ka5rIJbiA+hz/XrZ0K8HdqNQKBQKhULhleFVkxVogv1H1FqMF90O8DNP77sdSnsy6mQad04mLNVAxA1hP1mNAidA2sUMZ1FCwQ2JReDSX4DjZxKHM4MxvPl3HGAkqV8CcE8GfLfvNrKqBPrCmm1GGvrCPZCM6/xSbn8fU0eWYwVBpGi/jaBJE3sUWkTyCktKtTnqb8hukSN9tg1QRCPIZmkiFtVALt6Zpzgbudvyqm/XT8ach2pnA1NoNYrp8NNzws0AVSdoug6r0bmSDJMsibWx4/hKU6TT53rIpC8IljqSjFxyI1xi3e9V5Fj67muHaU+G4X48l9LecbKlp2keab91Ag2J6h1++tgmYSFJwDT34bj3SBDqkTJozmoOi7p3Xzj5hzkmvz+W6g2Nb8YapJFF5NB++x0Sf6FCtzauRBbENdM853kLQrTRzVzklBtf8xjrSONzgzwZxz5xZBof2LDO1bKX3Ti+mULtThb2PEDXl0L34uaZkO99nilvC8knkwuaF0UQcZMYoyCgjlFfMmQK7CKNz/e4a6+44Z/bSGRzJteWqKbbR4a2QXNSfIyxLzwSxZc27q90/tXSjXbC8Zly1F1ZtIJe2Pc3Wi1+7NCOuN6OsJFDogFvNO4sbqFQKBQKhcIrQrleCoVCoVAoFAqFQqFQKHxQeOWRFQC9UfdorkTQT7JUEhCWJd2ATGcCmB63Y8nGLDyIFMoPqHeuNVo8szcREpQ8jYzQM/Br8A4M8/IRWTqLe+82RMg6m1ZGRHBYX2kH2tA2pNEi2mg+RrSBJXff+2LTtFQNoSxaZwdk/Qjg1vMJBoaJX7pHO3szc2SJa0aEF7YlgUwAUYrPB7mJebcFeKPCeyGsdxTIO4jsIfK4h+bYu+AfqRCd53KPY1i6z59vJffYDvOS+zVStIbsrNcjC+NO+RREs4+jE4a7mu+kOCB5bLMew3LNe6Urfe3Sfl/SCFJ0SPYwH+FaHB4ZsEgsbOaRHjau1IGIQABAIrPCgtzpT4pEkMPn5D/tmLWaxNpXeeHznEaV03MiOsfa5q73DXsEhN+rNMfi+1vocJE0oKic4h8veifreP3+Etd6ce98jkpIHvq4Pg7t5ciZ8K5jpmIcvOlyUNl0YeBox9fL22ea51jkwqxogkgX8/GOE9b0DevTss9SF5YKLzkKJ8YsaxRLpzVaIreTxUfz5WhGz3xm+c6XAg+WyA89MO4HpjV15g5U+JgidUzyOtPa70jJyWuZnwVLNE+6/yXNpVVCimZ31ZLpj5/dz0KhUCgUCoUPHa+brBiAPLeZXw7Ei96sNCGa332a33sFA8De//s0v7wSQ4SpEyAgq0phFRA2wi5ajWOp3HAwIoPwaKKim5HnrySEGwxeFjXIipNgPEh8TxdNIQhb/InAzwhtAU9ZyYQLdSNITLgx2vaX9+1gFGQjHICXC5iaG5jGSBojdZp5+IL5Ip9zxWn2LYgCEis7K9NQ7cmAvGp4eH9jBnAT0Fm/zGJ1UbFhpwPL4Eao5fbvdu1cTlKSoRAkyViU+2WnNfz8aPxsY5aBzOKiqZ8YpG1tSp4cxfYizSjls2dxvBBJ9L7nfHbROSDTCQAl49K+X8Q0j2HzQ+cryDzbj6EdYhoQchbcLcuayCk13s1ozik2ab59PwGYujJZ+2QeusK/Y9ymMIimO0XVka7aGrHn2ywhTEOr6YCBHmleblCm58ZirK6dCqmBHJ5PlmGU2sygMfVYooQuJyO2pXkDIMf7Mc1DTsOZF7D2jMyjNOdxiD/TFjLrzkWM4BJO5BP0WSIkEE7t3yEDFnIq/R0Cr/51888yC4FEVGDd6349ObRzHAL5M3HO4UoqzXXEkXjI99dhHMv3x8HmfWDaNzhJlMQNjaTtsK6ZfEp74HaPr/c8Cc3v7qS30cAUMS4UCoVCoVB4pXjVZAVdCadf3KZXEm6c5r8xxej0LM2TNpJBmr1Q2ks3m6AdX+1oUe+hMNDNupSTANvQlPvsBbvMN3fPIwZhCtKlShvhAfXPGqYxyABO6uambWBcWKMK7Jz+ntGeCe2JQFeY51vC0Mke6ngXzkYebExtGlBZswAwI22b2gljw1J1ZXmZT577abzIzN0/vpxbe+NhqJdZdC35mbB5/voOtGf1Uva9aeTJeRwMh1Tl42IWZIpskNbietStckWGkzA+AQSM81xDaQJ+TiSA569no9oiQEjIjNdJnAWRZV51cg2L6H7qL63fZUNcfL4BE3FM4/B5d8MOSxeiv7H2zQ1imyMjL8TWTkwwMUqXNtcOkVUXJBMXuwkqLt2aRtk0vmSWzARAba51JrWED3NxIL5ujHgfn+9xOpAVrGSB3xdThHL+nBFUiTTYaSUAnQTziIO2znNUNblj5C4khF+b0jyyr8PBSI0G7KfNTxCFhwpB8NvghbAButIa3bB8aWO3UrrCa8TWXbgYqcOJiTYFYY+2PnCY7yRiu5IEhwgNzH0sPO/z2WD6iNbzl2eQ7zXf93eQiYxjtMrdPZQ1LVK55+7ld/24o7iy3GmjY90XQaAf7nuy623QgTgJ00QjZM4vhFEVCoVCoVAovBK8brKiAw/fpoMxTOhnzJd/Ez8McUr37JrhPbYkfAgVzxRWQ9mPH5v+xxbG3x8F41E00mKzUomMKPEnAiuXZy/fJKBc894MVIkKArQqxg8A16SkTwCdp8d/AJAzYZyUsPAmg3igSbB4tYyomuKXeBjxQq0fOIHj1kbqzzaNuBvvnqcZ3PP0ugFMhxd0YBIy5hWUk4oieukQviq5wDuBPzGS5kxTZDJdQ5oeL+RCkdpGEDbDyYp0ns9ZhP9TeNfdUBgb5vWi73OzOZHlH7sH1aMUonqJ6KKMhpvIgGO60jSc7cM8r2z9ORA2ixCjG3v+p6c1WBrLQmKkIYU3GKLE2TF9aBC8TG1c1+HVHGy/C8tqWOVpS1FQuawqhhre4vPAiHWAaETJElGQ18QiCcbZ9vQ20M+0zKHfR1ksceleVK6Z+1x2vl8W075Hup+INdVII12SSidufl0/yGxh55dP8AoVBM3/Erpt08dIqYFE7ogABIJgkpC5FK9+C8Crvhz34qHL8TenMdg1xYVSjyRB6qZXTwmyIkcxpWEv5+eoiExcWYqSgNb73p+rKWImkw03+zR1UJzRyHDCNq5r82TPzYhkE5ljywSMp7Ldve6BfPFx9Dtz6X3hSYrP54poNZdCoVAoFAqFV45XTVZEyHc/fLxjpkUQ1BhOlTSyR7S5wWsv3DTU2I/ICvPoZs98eyL0B8E4s6ZvnCWIiujDIKDPF83xOFYju0kQFNRNEd9e1HmfVSPkxJCTeyDnNWQb6G8JQhwh7iO9WAsD46xe8fBecmqjaem/iELorHOQ86bNy45kXGeI/y+/F6fKIQBATdMkiGQ1/NwAdYPoJBAe2B+s/N7O4CclkjzthZ819caJJ89JH2eZRjkwCZdO4I6I8DhGh/jvHmhAAHCdA2VLrwmSwC+QhqGRABrNoxzY6o2/SQsYYRKqUehlQcUMvESgAOm6TqTcMeaWKjRYjwkyxAy7IGgiRcLG79FHL1Vw8BKTcmjXr5f39rEN/0wA8B3PvhmWkrQc1rQknfQgQXwNfLuTqGHJ6bwcbu+aJiygTRYSBLDt4hshG5Pp/vC2iTDTeFimZ54SUZH0cW7SDHw4mZgEAOElFWf5CZ23IBv9njzqaYyD8W3zvkTzHFIDMlkRuiVGVpCvSz7+aDRTnk7yoYCsKk5ONYu/0zyQUFSFuVe9Zbn2jtlXwkpYeLu+JkZgiXVajjzQMSoj74n87POPsraR0JJ5CCDKMzu5Q0iRdcBcW+h63GiLsAA819/vkXztJQpMEnF7wkpWWDgLPRdhUSgUCoVC4XXjVZMV0oDrx4gyog5/19SXZvsvvRCHUYT5culkxvCXQz9vJCFMJxOegPbeyoYyRXv9nIx6UcKCzFgeDykChOx6ySjJKQqaK25/d8LYARBhuKHh/SWolkD24nkbmwDbUFIie7OTh8/FJRfjw1942ciUJmp82cvzTU49Qa/jbYQRZt6+niJEsoexk5X4s+lq2l9+O2vO9reM/tQwWsPGakANpLU6qe5Ff1DCKOYgkQV8VQM0CJsxDQG+YnpffWkOhnQ37RDabd6TMR7LZ5EIGmkht+uQI0uylLi3DgAAIABJREFUhkAnjMtMZbEJXuaR3Mj3/XxI5wHmPvfffe8C00MehMKRbBBZNE10YpPRa6kQ1KHaGG7H34mkCa2LTFz4TzfAdusTEpFj9xnICIlOxiLO76NfLoi6jDkNuNMkJ9I8xxzu98kDoTluLycZqVHA1HCIdVwNaYH2jT06IKKa1mdCzEW6TszBPbLCx+GlWRkQcNyfOfULWXTS18TTEgAla05jnZtEIo7OQaAOS3+KdDZrL2v0zHs+TYTfG5soeZF1IzJJlomntHflQA5px+xeaLdkUhBsvpdSBFh0q8mqzRFRKjK1HvJYGIA/j4X0WebkAZOmAx0I8ptAoj777aS6fk7aRiIgSPy+nc+AJVIq5m9OPI2ZGiWmi5GJMSewC4VCoVAoFF4zXjdZsQmevzFmeD/MYLggEQZYwsfdoBxbehdu6p0HAK/c4VU4wsjzF3WYlsLlYOwKMM7Ts+hebCdKxr6q2kuzvrRpfIed/wCMLVUgifD/SQS4ERHe1jZTTSRX9nBS4srLCyxfaBpxcMMqGa2bwEP623tWA9ONSiBeoLUaiYqcei7/XKBpXB6jMkIE0YQ+pQHjgTFsjPy4oz10DBb0TTDeaqSFRsmYx/MskG0AJ0F7nOVSpoCmGWAkoCZobUCEVP8DwPW5TePWDb9khNBOQYJQt75uMg0iNiMmRb0QC9jWgXmAWb3xzPPaLp7ZO2NcmkawH9MNjpU/7hiEd78/CESO9y32bhg7DI3kAWZ0yaBpoB+1SWgamsOMougOHY47Guf5GIs04p0meeWHCIArpoc6Vdo5ilySOEFhH7U5J+TrmbVFzAut5903thfNFidM0jws97STmalbM2Tf5wFGZMrNtSINjA+kyiHyIIuRxr0EYJhOxEIc+PlLuo8avZIz0AZN8tJ0VEIY9mTpJQLgERD/3W1oEq1ok4zwGBNwIBJkrkOGa4JkAjVFqnjVnmhfMAnPK09CJmm9ZEKET133kLXBJOA20HdnMwCy+xKwMabnpQer7FcN6whh2yCSTER2TzfhkbXiW+IpIvuumJoiHvFx7x5H2mv+b1mKzlgI+HhmHZ4hh/YKhUKhUCgUXhsqTrRQKBQKhUKhUCgUCoXCB4VXHVkBFshXr+hWjcBBzxxRA+qVnWHAng5wk8ftApgEoBO6e7uHebQI0QZfrXzolUwI8tYr6qkjQohZdo8/D4Ddc9xMsPM0+9TfaLQAnbt6/SwyIsbngoYeos1iqRgpbHpvWvL0mTUH3QQrvVxkaDkQNNJjmwKQAKKUqjRNeeHr6s2DTWscf9BN8GOjIkjSYfBzaWg0SgSMnAj9vUUdvGnob7rOw0MHvdkxPrIoCfOKchOw5fm0prnqYxBkzNQTal2PAdC2od5h91Bvs1yJmGBh1tUYV1Zdgp1VX2MTbOcONk+0txWlS0lLjLbmkRXzWv59S/uOSDB4LDoIMT2ynuf9I6hnOI4bHNdwbRDiuRn3s45ZOoPamCH/z7pB6coa8TBoKa8b7W8ahSSnYREktriH/HkPkw+PekaIqNq83qnQEKVxrYlFBHHROsGMqEgpB6Ev0FJUiJ+WSju+qCci6VjrG2F+HtEU7u0ehz4mZ39ooCRveHyW9UnyYyv20Iwa8WoaMW4LIKCrHrikqXiQBcnsu0c15dSmpySuQILBmEKnEQUi4IduUQ7zVJCgsQ6AMPelR0IMU/kV65fv4RwpQTzPHaLqLZImMZbCoy7SmtC56z5OQpLRrkdGcLoHgIhs8r/jHiF7Vlg/fZh+XGsDQwjNNoxHzQEarTE8IiM6ab+64Oaxak9oA9FtRR8/JGuddJkRGr5vshjvsGgNj2ZLz2axtK7bKxQKhUKhUCi8LrxqsoIIOD3u64sjgLE1Ddv19I8cP7JpxQDyPHcCWpuhx2TG8LZ1iBD2nXF9f5ov/gD6IPS3qrnAV0SeetadWPQl3MAx8IWCrOiPgsvXO/CQdR8AOg2cHnaIWOlOTzkAgN7CCKGhP+MlGFrRhC8qTNme9SWWTZxuFYoz4+5YYQNKPoyTvhBTX428GIeVnOQ9hbSnNBHqEvnYrsGx6HZYeguR/mwXYPvUjOkTYX9L6G8E/eMBvNnB50k8AGpk9M4YV8bVSZ2eBBxFCaBu/bqSjc/bOAn4rEYZu+Flv+teMILJlf6bGf7OCfUWhMWw8PqxMyIraRDkwkvofw5Z977wZiRCDm1PRmiuUEFNFkPHw9jJzhuDQGnDb+fdxtEXQmcYOdefmxJhV9MrQBJyBDTVpglwHpou4H1biCfTJ1h0AbD+3sTKoAL98dbIi5Qa1zFIbYinO/j+CnFQO/BhgLYRBqLcKc0pG63Co0ILYblc023gLD6ZU8lM12EBrWlb3uY0QG0PXWcVD+ppmrxvDE0fIcxqMn5N74c3n/ayiqQCuNJ6Kx/IJ75ypK75c8lTFPpZ4nnVP9L0oXGsCsNpD3uqhRO9gpmy4adJGq+nDxmjE+kXeb18/Y8aEzY3C8mUdB5uSpBmkiffT0ifH/ebj8/FiAUYpwFqK2kjUMJC2IiWA6EThJMg0pr83xcnRGXQTZ+WKjI2N0F6DCixmKZEWCAnib0ZxJiVG36x0kmhUCgUCoXCK8GrJitcxZ9JVHQO0PxkMxrF8tdpm2/svAnYvIM3ecvQ9s7nHW/OV5xax9N1w3eHaguEEUTQcp480E+Ynu1LMiRSNRIX0gsH4oNo3j4B4+0Af/UKZkG/2Fv4uw1yZVyuZujuvAhD0lVfkMmMS7pq1Qt+tu870K4wAmW+60ubER821Kkz4cdaCcC2q2E0BFG6FcAixMgdWnnFiYr0cq4GhRMP8zrhDadDW6Tkh+uPtAuwvSf0R8L1PaG/YfSPRhAPgL6c8zPj/GyEkWh/MjHkOhxOuGSRv/4o6A8bxlk9zG7YdDfQcrlWAkRYL+3rvavBS0aQDHJSSL9nE5N0YgikWiRZa0HMsx1GaColO8cwjbX9bISB76VOQNd2RlMdg2yjdBMsJbsviPW+cQKknQeGCUFKs/3WZBpS1l9ijc7Q/Uh3PcOzw7N/Ohabc9NV2U7ap5HayPakWESB91H61PkII27MPvC5qyd8o/B65/4N12ZJFR4wKCJkQhvBogTcO54xsmF7HB+gGhI+b06YDJqVQXwrXXTzUceiOcGmrUPdDV3T3vH5yVox6X6WjebHA0EETA2O1IbYPZ7IR77O+3BsSSz4fbPPJBGVut/HlvrBJkJrF6CdECVW7e9F98PmTp85UyPFx0B9kiiuvSH5meKkkLV3HKcTSZT+Fm88wecgjk+Cl3mM46SEwA1pE8+GW9IKhEm+pfsnvndSElj0OhZxXddkIdH5aYDkiCrrA531XpLOU5uE9Z6QrKtRKBQKhUKh8ArxqskKDML13VmNhOS5IiCE2kJQzm2GAfUCC6Hbi2x/boCFxaMJ9ucNz+cTzg87xiD0pw3w1BIgRN6WKgM5zSH/dCMpQZoZxCQaXt8J+1MDvdPlOH/XPWQWqm/imtlgGdt8od6e1LA5GjfSgP6IeOkfDwiRRSEz8KOPTlZMQ1tYiYZxnkZ2LhFKls7iRExOEfGqGB6SHH06zI0bG+2iVU+crODdhEy/p2ko40ToDxzt+M/m4xaoSn9+52dEuH2O+IjokU3bzcaXJMHT/iCztKcJGkYkDZyoWY2/tbKIkReLgZnIBHbyKO0P0j0VaUKk/3Ojbdgc+DrGHrRqFWEc2vWuH7MGJNj6Oek0rEQszhbWv421qounTZnBLYM1TcTK7Ia4n10qUglwuBfsoEi/OhH6scwvIapAkM2Lk5A6ZwB5OP5gi3aZoofjqsQksSxkXMAIDmojIlOEJ1kZ/RYjJQCNsGqpDxst6TfMsqTqsD9vhNA7YfSmKQYR5aN9GI9mYe+8RAHJEylh6GlaVwE4pW3ZT4/eCMM/pc6wi/DStId17/oA/Z5Yjfww7AUqMEvA9j1bt1S5QiOwaL2fm95HACYh2ew/FqvG4wudJ3u24Yg0szRleS+JRZ1EGdx0/kwZSmOyOcnPoLiWH5fHZn3wqDJA111J3rlXyJ8TRqosZVH9d3vmOMGSx5TvhwgUy2kbMu/v/Ly6i4jGEpD/a25E/L3CN4VCoVAoFAqvCa+erNh+4aR59ZZ20c+yGl3AEnJLV8beBFEGEcDpE0Z78rddQHjD2ICnByUENtNsWA0wCsNQX6qTx+9Q3SNs0zCiZUYeXBl433D6lLC90yO394j+xuWSYTLOlibxkekJsGCcViPYrx+kQZs6FI4o+erGzZ2wdPf8H0Pc/R25Dx3YMdVFdQTSAA6l/paxDZrpJmGcqcHAF6A9Ae1ZcP7k5TZGIyuVCIxjtQyaBlQmDvgKbHtaJ4FVN9G/mxMZRnpopMQtMRGVJnxOzunyrPshkzSLgTbWv0HT+w1Mg8gJseHVBJhmH2INyXQSphHUnvSzMCgtmsNz8PePWPeF3zdNZoUU6P1CVl7Vo0Q4a1u4cZr2fC4N7GNSpgQAM/q7Bo848j7FfCUdgqXIQU6PEYsmirQra+8kSsoc4Jozw6u2CIAmN9IaORLCCaqIsmEBb9oOs2DI1BEBgNERWgvD0rK8ugWAIE/JNE88msujSzxyq19YIyyedc7bk43wmaZhLHfmeMy9MLa554QlCKqYa2d05bD/wsgnva4TeHY/MWESKnau7IkkdRKwwYx9imPnOs57Icp23uyVw+++JZIGjvfbCVA5kFROVkS625E887448ZIIGJCRHASQVZZiOm6WQz+DjJwExiRS6OZ42RIBwrM/Pt6oPmMpKes94oSJqAYJWRcSobKfBujTe8xdoVAoFAqFwuvB6yYrBHj4tr7lRZrCdscjnnLPtyclLnK50+0dsD2ZUWFpBGp0EvY30zDrD/bzTXrpFw3hBtEkTLZ5be8nCIuuhr9E8ztGe0/Y3icDlQ8v4hZ9HykW/nJrL7X721TCFPbSOlSzIsgJy7GennNEuLmfI1v2DKYX6Sz451PPqn9AMktiikwCKDz/olZnhIf7+ZZ6gE3z2OXEwD61PMau8827Rla0C6G9x2JgRNRHQ4g3ZtKI9hTenUK7fZ63T2kxvLKR4GsUYoqwtJecyuKGiZd8NTLH94l7ZIOsMG/r0ZvLnRZvN2+Yxk8yeu7B96pjMFQDJBEyi+FmhppYypCWj9V7ZmwNsomWqfU52N04tWiK5NXWA+w/C70nACKyGl+2V5sZ4FujxQj2qIylzO3hGiN0EuwzmfvRxxXlftMeB+b445kAJTaWKB/4fKV0rYOHe5y0D3tLezvYCiQr0jREGjA8Be2UIjug5EXbBjYXp3SNnGtTEuONCqL29/pg4yfWNC9fx8NewgaNAiAX7LW5YFmiXlQbR0KPZRF3HYiSnO2d32S0ps50J+xmX5xgjHsjkQc9rYffA8KzXPFxTYMwsTFmEjRSNjyFIxGgC0F8OE/YN4EN42R9nKcvaSMA1mi2O8TxIhy8fHlox3lzmedClNwiLzPL67kRAROpIBrdcaThCLT0I9oRfZ5u36s0kEKhUCgUCq8br5usMKMmRyJQ16iDGYqcDBpYasFFwN08kI3AVwny4vSpgHf9T4jQH5QI6ScKwbL9rUZwuBHqRm2E2L8ZVqXD+hkvrskqGmpMt2c1BkcD+sdmuJx0HHwlM+qxvNQuxlQTjI866GGE0B2ZMFv/9lm94d36vmNBfrk9Cg7q/MkU1ANCFwRAGDAS4dRmvHiKhBlyqudgbW6JHPH52dT7LJt69Ifn9F8QBAx9TOCd0N4rMeKGydg0nUFOk6jJ8LSFGJMbbfbZOE2S4m5YfFb7d+P6PA0xny/XAokom9P8XDx9JhM+3r7pS7hOCKW5zFEHOXoiqtz4Urnxar/LhqkRAWi0jhnqEXGRrB7dZ1PvRBpFCpCuE+L8IAQy6WUEzdIPpHMx5wuQKS6bjpfYK5bKAKzCkGawAUZMWaSQj5G6/s0XimogamzOfuToFJCSYTm9ILztZsB7Ko7vARqkmiA+F0EAUfQBQBjjEenE2slhQqXCwNXERtGUwAAwRRxFiYztzRWPby7YP9LJ269NhX53FfYNDRufQ9vbsgnwpoNPQ9NcnDiBPhfaNrBtHUSC3hlbG7EdVFBY01euH21wgpVTlZDeCddLspw7gd97ZRmKKKsgnNzoTnOzVJLJRIKk++1AbvqeoK7iwcf0uqzrslwD1pckdCrnfJNjZQGszXiOua5IJjOEYp/ekBVxjP2eIkH0b4p5cQLuhpD07qVnWhCF6ftc3ekYaSMX+3exUCgUCoVC4RWjXC+FQqFQKBQKhUKhUCgUPii87siKbeDpzxk4/xLj9Kl+1J4FzQQXwxt8WiMaeNfv+pk0jeAr8/vT90gjK65T1G5sdqzPFquntH80gNOAl8wMz9ZpTG9dKm0Xnn2CHs+AXFR7Yn8jwI9oeAefO/rOkPcNXkaPmmB71O9JNNd9/8RKqlpZybZZ5QcSDCZczxph4akMS241qTdyRoaQqfN7H2l6KBlreUFAPcMsev1UUm/1QE4P4TiLeRCTBxgALgw5DdBpqOJ9iCryLHcJYAxgd3X7pCMAlhnK7h7ZXLrUvcsWybKEzlPS8Ujh27P0qSxRC57ykT3ykXqwyRoN4KdZJRjXTaArLREtIbbne0J0bsJTbhUmPPJlirzOa+RKM1560T3N+1vbf2ye77GOka+anoGhFWRkzAgRnyPPr49opVx+1TzcIZ7qmRBAUKG69qI6GcPSTJCa8IgCAGQirzm1RSN2fHy6ZuOUIjaGzhl1CsFTzpETTY8JYUS/eKyjebhbigIAIM8czwDaPfpFOxvlIpMHPbJCiEBDozCi6saFlqo5XpXGr+f3h3jUxZsdIoQ3DyoI0d4I3j+ecLls2K8N47lF1RQAoIcO3gaYB87njmZioHvnEAIVoSizK6Kfd8IaeWHleU+nbmV5OdpqrFobrrPhlVeenjUHq3eea+qRVk3QkoIn2/2qfYiAGevfFC7te9OqTXaeY/SGftE8j6gWk9uxXCRuEueLELo9o7Qs9fz8CJ+LYcI3rj8SN4Tdoz1XhqLb++Emmi5FVizpKdYGdUydGMHN8zoHgBDS/ZkirJaIqfxMKBQKhUKhUHileNVkxbYNPPy6T/DpRx/h+h21XB6+Q9g+1VQPABCmKJ0JKPFwBaE/Av2NkgTypocBTE9NCY2Lhhu39zQrLXj6gBtrVooRgJZ1zNVCdhPgTHn4YYCeBrANCBH2t4A8DJy/9owf/ZHvAQDOraMPxrc/fRMv1KfW8aMfvwMAMAQDhP/78at4/+6M8e6E8b7hsqXl3ATYBsYjICcz4F1cELBSeM5ceEg5LXngNAjSoUbZlaMKip7CwEMHbUONtF3PD60KK/8oNNRofVBrThLpQc+M9iljnBjjow44aeH9T0aKVvUYM5wbAK5sBjhpqVahKBcKwIQl3djHbQoFVF8ijHC3P7IYqB2/EBopVYHMMPVxaYUUiuOEMausmNEtaZnGJgDTDAW3a3n6UuTfixq0oemQCJdIXbBwd9lE5w+AfDSC0BlOKgmSgCahP+teHRfMi0X/Zh/iWpm4MoM8R/K77RZd7Lp+ctJUiPGIQ5j+DOHHmH2LrweBn5xMw9QfiVSZFPbvmhE7LekHi6XXDsRSImaCgCNBfxhhlJKVa2UvsZBJrzSE0JNhURHQpOfg4JRa4KllTpqNk5ExnfCuM65vlax4OO84ta5lj1lwpWncA8DpvKO1aYTvnbHvDX1vUeEEgyDPjIuLqOb5AGKfgAWnhx3OW4qVxiASM/Z7XLfxwMdvVQWUlYuCCOHamx6PFYJJmlyvLa6hX1L8zTxAVrHFSQclMjr2E8ffgJIk0YSdv21jmY+rVVbSFBg9zgkJSvs9V4jx6i5X0xH5/9h7l17bkuQ87IvMXI+993nce6uri91mk5QgEvLIHhjmxAM/AE8NTTTVwID+gvUT/BesmScC7AlhjwQZBjy04akHFGQIMshW1+veuuex916PzAwPIiIz16liVzdZj77kCqD6nHv2Xq9cuVZnfPHF9wEoQI3Z6ZJOdgNLzAHGrHXZLrq466BBtdC8U3Tw9D4VVxQDK3L9rt271LMIfxJvgAm7nOxejv4ee+yxxx577LHHhxUfNFhBYPzi9Xt80a94f3MCAFxueoRHh3ClkhimviYzqQfymMHHBNcnHI4LTuNSqofMhJQdpjVgWQKmc18SKJpUfO7q5N+za6wJNaGHJMvhqj3F1tvsq6ZFGh3i6wh0DO4j+uOKn9w/436QRf/oVwSX0fmEKQZkJnhinIIkLsElBJfhwPgl7jF9OqpuR12cpoER30QRsDRRvUZQUxREXUmamAXQ4KYiSKsCLqtoa0j1XBftA5AOVIGeAFls1+FQB4VcLCDbCidfA8KzQ/cgFec4kwBHR2WH9ApuLDrW2p/vr65Wq2dxLWjF9lzjKCLJX036i5ZDU4203vTcoYJSRXcDtWJp21E9lokEUpP4mZ0roOdB8nOjxeDa7anaIGrivxHLIxTdiNxTBU2a5LgEKaBGQOrlg3ibBbxoEyD7T8cl9wx0EJFS1mT/BbNB5oReS2wqx1yvc/N9QsNEEr2H3BPyoOKKXW620Yt3CrIYY6BJABM5mYtamaYkQAsAAT9yBQMR8gYQskNYMk4+l2SyDTYQIcs5o+Oq1zLIdSUDH1+Wrds5ZcloAFi1cMhAJBK9jM1YQvfHXMRMXfTgi8Osjg7zmNEdFxBhk8D7UFGQZQlIyYnVcpLneaPbksUi2OZpAeKaucD6cz12RTTUm54Ji4DrOuTK3vGMcCMX2Q9rSeBj9OKakqkm+tltmQo23mUMFVBth9YzXKfvBM8qX2IbKdNjwx7R+xS9AnjtAVC+w9mVQ7sXlrSAgB12Ld5nZH3vOQgwkT1tgRbbd3ZCrIhQ5gvX65KrkME3QE7BVLPTBirIWd8TXEFvvY8AQD2QW8bPi2e21SDaY4899thjjz32+BDjgwYrYvK4rD1+dvuEXlsgHsYDrqcB6+RBiwP3mhQZ1XnI6A4rxmGFdxnHfsWhq9Ygh7AiM+EaO0wx4GkYsK4BKRHiIMOVXAc3aRJ/dWWRaCKdflJ2R8QGrLDEMh4I8Y7gTxFdH3EcZ2QmPMwjAODqO5y6BY4qhXpJHl9eBZAJLmMMK4Yg1dZ4BbpHKsenDKSBcBk88iGrkB/rIl+TL2tdsUSmcT0AoMAANW4cTXUd2hpz9Yg3moCGpuoPiA2mJiqcCSk6+WypbgPhTAgTlLYvLJZVWRH54MpYGmOCYmMjq+MtzgRcmQgvckinC//kAQQg92UIqk2qikxKO0fdgVm7tm0jmwomADehOMgUi8eWecH8tSqqtXAQAMQKAFnrAFD3JYwSzWWXCpC0J8EKoJA5JSSU8VjPHhzqtZg4pSX2uWv2ac9InSYbIMhaaYQpUo9f3CAKW0CPZefJAEjaqHJQcCZUmrqJZRYHBLt+qttb64pYpyqYpsdNgyRzBoSU+dgAb+wYUGHM7LZJnDnauNnVdhEn4q0l2tYXBT42YWOWCQwWwMJztdHtFKBR0MzaSIr9qrW+2OtqBlxy6J5kB2lkxFsP7jLcIcq4OwavMsgpOrC+8/zVyRy0lp/mMgpWmetcacVaLfKzL64bQe1TbR7kzpf7lj2wvJL34vU0wNqYKGnbkjLMALnWUObnN+AI7fvD/uSB7IXZwYGF1WbtYibOmWkrXpx0fLU1B67O96Tzwhgt7BjppegngKWTviYKedOqQsSFYVLOsQE+7H3H0SlDB6Xlr1yjzW3Sh4Ug/0+c6lfgoXNOPucWILPny4A9QJh8jSAwrYR8/Ia+tD2+t8ivTrj8l3+K45/9Xz/2qfwgcflHfwoAf2eud4899thjjx8nPmiwgheHv/z0Nf7kF58VVkLnMt6HhHnukKJH6CJyJnitMnmfMXQrOp+RmbAkj5dr5qzsipRdqeSFwDA7jciEFKRdhC6u9iE3CR57Xau2TgNtOMbhOOPQr+h8wuN1xDx15eOuj+hDKrTjeQnIqfZdO8e4P10xLZ24moRyeiAW15P+wSHOhDw4cM/FuQPQpDOpqj03VXA7vRXongSkMHAgN9amYRIwxk9OdD/UkaO229SMmpLoIuSuakA4dcJIA4o+CJ1rCwcHLwXJqKwHu+dNsp68bE9M1Y3D1WsxjYLsGXlQer1WhQFZ0NNKwoBh23eThFy0kh/rPRWGTP1OuBAo1Eo13EvpCqrn7V4kaQ0QIhaj1cK0ddIorQW2WZvIK8vCEUAKuLgVcCuXcQZJW1S5xoZxkgaoJoUwN0obxYs8pzifBC5VZjsXF1H1GwqwULd1GUACfJQcrLh7NCCOAWFMcr6bthyuY2f7d7HOS2NR5U6SwtSJHWsLsjDJdVYr2+baSJLW0p7RjpHlf3Z8Sx6tjcqq4fZcaYK8OWdgyxhwAHs5qWS2rcoacask2S4RqNHNcYu4gOTeIa0OHOo8BiAtVbNs72bagGf1S1xsO4WFUwEouwZSkMMvqMBQC1Ylhp9t4OQYxrxIQwUxWhZSGZtig4uNnW8FUOr9NQJCO4as7VIF+HVNS17zEndR5oNf670r1tYtM0mf9+Iug/qZPaeij9LYvxbwqQHB27YO+xldAenkhdCcYAt2tSBLaVuSa6IgrTDWZsLNMZgJoY9gRcPS6qQV0Q518fB3C/bYY4899thjjz0+5PigwQoXgfDLAV+8OeFulBV05xOOCgCsySM4EYwz5oXRhdfkEJNHTA7OMYJSgZcYSkvImgSwYCY4l4swW383IZ0cmIHl0oMXB1qqZsWmeg/gJRqSO4Y7RdyMMzwxYna4XnukJ+0Njw6rZ1zHVFop0uxlAWzBYmfITEivE9h7uMXGRS1RZ6CfZaUr7Qr1XFySBNUSy5I8NGPrNGHJnSS1eajf8bNsFy4APUPaGZrTM1tQY5e4lZFvi1ieAAAgAElEQVR6KvT8eATWW0bUym+4EtyCkgi5c93XeiP7iwcW3QNbk6sQZaniJqugN4mAJRSd0Mm7kOGaXvaUlJquFVHyXBKIefICVqyiX5IG2XcVSmVEtW0sVpKN2N4mWbTkqOPN30xHw89UkqRSfYWwBorWiAlZthoNChCYxgqxtsdoAhmuNfkr2h0R1S41VZvfvAq9XT6w40ubiNj86rVvEn1We1qqgEWmjUCtJOJyHpYEwxJjbM+vVNcb4IuygSlbUM0STGuzMRaNm8vtKdu3mhvl3jTMDpu/3BzbvWxTAORZMrBCxU/LeBG0HcXG5sUxolXz8TVmRmE6NEwIDjVBZZI5IvogThgqzTW4mSrYQFwBzOZeMpR14rARhK2Csnqvcn0OTbS0jKfeW7HOlPMyJtHGKlPHwu6JXUMBwdSKkysepOMg86a0RTV2y3J9jByoAFwcaAOiAvrOyfr+aphtFtvf9Z3dAJwFaGMBrERHRP9kzJ/OI3sA7oWVr32Hq75K2zbEjuXaba6rrTRvwCx9z5iujOeNiCiX/4FqZjBchy3QOnmMY2UM7vHDhTEO2vg29sHLbX4stsI3nftvus3OsNhjjz322OP7iA8arKAE9A+E9+9PWG9lBXo7zgWg8LqwHEPEsZNMfs0ecwyY1yDsieSQEhCU8p0ZCCQaC60avinhA8BxWDCGiEAZj7cDpqXDde4QVxnOuDg47XkGQypexGWxSSHjdCNMkOsasMSwUZynmUAgZF3Yp157nNsqHANxCvBjxPiTK6axr64cDLizR/fk0J0r6FBcQSDJau4riMFrTe4ArUYOkiTGEyMeGXnMhWpMC8FPhB6iz7FJPPXeSGVWK9FaMbekIJ4Y8T6WqmTuHfykgqZQMESLlrlTGvyrBDpEdKNkMF2XCigFVKE962mPUbKhjRsC13EchghXBEOBGGU/2cTwDtrCkhxSdHC9XGQr+BdvApy5JDRiqoCweKSnnsBJwC3X5Y2IH6uo4LqITQWZ0KsCKqFL1e0E0vfviAvgIvsAcvJYZ1/EXZ3qq3RPVNxIDCSw6jbw4p5rcm0ij4AASjwmoGOQz3BdRghpoxHA2ZVrZ72mNHvQ7MpcMfaRn2tCvGE+uHq/yzyy6a7nbAl4DihahIDMb7AmxlrNd6kpdvvm+tp4wZrInc5Pa4dpnjdrmbDNOGmi2wJTJnRorIIWeLBWANUsYWocVOwYmsAyKWPAKfBp16ktI25BYUPUm6fgRqh5cAFzGsDDAMvCamjBt+aa00gFJNsAbITiqmPn4y/2zFIBKUACZiQbG6ACog2Ik9v2GlZGScNOaR1XTDy3gCcGRLzQeEkKHlBfgYt2vm/cePTnxvWnaY+x9rACkhkjyN5lJI5S2/YlaubcFvxjxxsQjiJ9TcC3OAwpgJEdK3vMbqR8d508ChhrrB/oOyTjGzU19thjjz322GOPPT6k+KDBCkCTn09HPF3lUvJPHO6PV0lCXUZMHt7lkpRmVWiPClQA+JqFnWlFjCFW2z0AycCKbsUpLOh9xKmbsRwCzmuP8yKZxRo9hi4iZYcleqzRw/t6DgAQfMKTtn4k628uYn62OpfFLEcnC1cT07PFrhOLwTe3Zzx1sSTbzjHm2w7zsUd+50s7x7ZKTCU5sySpaC1AKurrDSMPjHxKcGNEF7KcKwSAWa++gAwuymLamXtC6Zmn0iaSO6nSA0C+SfDHWBwqEgHpiCpSGjUpssSgZ6CTZHkc1nKfgjeQiQqIYONgFok5EfLixekloegIxJsI36cCbqToCsAEAKFPcF6o2BwSvGesqy8uApwB5+Xv5jyQkiv78z4jZ1JXBkkeyFWgwcAMZiDr7+1n7dxMDHjPcCw99Pa59dADK+LgxdYyu6L5MR90LIz5oMmgiVM6S8zU6pCDOORYv7u/XRG6WIA2s7Zsnx12qZAtiOScYuexFq0BV47PoTIISpQEHsUWtWg5QOamgVimt5FCrSSXNgZrH4jb7dlahOxPJlhon5MmkZ2AcxbUCMYa0FdbFWhj4dqyB2SD7TUaMEHaEkN6jaW9xtX9WOLathxZSxMU5Ckip83xsyWtOmYbDRFY8m3jSJUtU1pVDMRgEea15Ngcehgb96OkH6eTglKzK2PLTi1qKwYroIeDsHHsOaOa6Bdx0pZp09wntwhjzJxznNoA5443bRyltcNp+0wDGhUxWK5zYgOSMbQdpxnHF8DZhrWh+zKGhp2bMTG+pgeiYIdZ7ALNfuyrjkqbUTnHXOcKSFlxtlunAIeCStzJPbg+D9jjdyNaxsJLBsI3sRl+E4bDd8Vk+OuwKf6q/ezsij322GOPPb7reNnVvMcee+yxxx577LHHHnvssccee+zxo8YHz6xwETh8RlgvUso6d+Ko0fmELiSkTJhiwKKtAk/TAGbCdO2RopOqeSacrb3BMaaQ4L04hQSXpbU4V1znunaYY6jHcQmHsKJvqAneZVxjhzP1YKBoYgCii7FGj+vDCCwOtJJUxaxqOSRQl6WXedZy5wshNtenYpuassNpWDA7Oac+JNweJkynDg/HI+JXPcJV2y1C3YdV0o3a3dLS8zGDDhG+yxj6iBASCCL0CSjVv8vAHbAq+4M8VyvXtcHBhgTfZ3R93DAC1tUj6hTMjuFDrqyEkIXFsHjgrNz22SHNPR4n6yWhTe8/aRWa7dimpRAJYSIRYmzYI/HZSVUWWk21CqwxL44Z3HPpIV8B0MWXHn9mgHvG6uXeWdW5nI/OrdIKpKea9XvRWlDUIhGQdg4AWFVwgC+huBesnrcifIBYO4YMcsLyiKsXtoWN86lWwqVlQyvaq7VouOp6wsqAOSb4Wykth5CQk0NcfBXwa11kANlQ2xbIMShsxwHQajqUws8tzQFi8akV/dxzqca3LSpSzabCAhHRQ913a9GorQ65bdcvWhTN9xrGQank23fIWB7YaoNYRb5hhrxs49joFRCqvkfz7zIHX3ZyGKvCoVgIt+drpwCH4hzRtjdY24T1gbSCp3ayfq7XItX7ppWFTCiXCuNK3hE6VyLJ/QlctVOMiQFIu5AxKYyF5bnOFV8HndvBs/YFOw9unx0qzhspqvWxtpqUVhgTO7UxVfFRcqzf56L3U7Ypg4mGotD83tiOyvNR2TuUhelj45p73jJqVEPHNEDkuO3nyvbJ2ubTtM4BVbPDWBilPahxPEpDe18h7VEqmivtUgx81fQQ7fE7E98lk6GN34TV8F0d+9v2vzMs9thjjz32+K7iRwEriOjfAXiCLLkjM/8nRPQGwP8E4I8A/DsA/5iZv/rWnWWgf6q07vWhw5kJrk8YD6JTcZ370r6wPusCbiW4xRWLw+hNQQ1YgqwQn9XmlFm0AnyjE5CiR04EHzIOxxljF3Hq5XiDb7QQVDNhTb5Q56dLD14d/PugbhSyeI1Kp+YxAT1K4kST21CFOTDyKgnjtDrE1ePV3WXTzvJ6vKI/PuPLYcFn4Q7rJEmvG2RF7C2h1AV5StJqYuKixyEiuFxaK0z/w64rMYH7iLGTVXjKVDQ+AGBNvojCdT5h6CLeHC4FtHlaBrw7HxFCLhoRh2EpQqg3/YKHacTjecQ8i4Bp9+Dhp0qHdjPBLC9BQOqthaBODwMWzG5zo9sRJUMtzhKW6OpTkQYv1p5ekjgRFKXSLkMJSL1+3rM4a7QODQZOBAaZmCDX5JqbBImNHm+Wlqr30D/Qtjf/hWhi9tq+EBjrMUtC2UlCCQA0KKhlIIWJO5roQ9CkkLiAL3BA1udlfu7gnj26qzpVMIo7ioWNEWuvfepZ9tNoIbx0bLAEzD6Tf+t5NuNQxqcFCiwpb8LcWqC7oW/ijCmI0AIP7bkIWCeACpmeQ3OcKowpbQ4vHVM2gCJkrDcODuaSoX9L/QurWgNMTKchAd5A1GTXTzUxR51jUMedAvI0x30xBPV6Y8Vn7LuW8LJDEdI03QUR5aUinps7AQnSqJt3mrSHZtwIFazI9ofmM0ZBKVpAqWj48PaZQuACgBSQ6mtiJAAiyXknEsCo1ZQoIrYV8Cv3jrABVcgx0sYWVfYZVwGZW0HYclmJS8tPNpHZ5nVr1q1F90IxHzJLZm2ZgraKMG/3z07b4rjurz2Gn4G1l3fHHt8e3+l6ZI899thjjz32+E7jx2RW/BfM/GXz738G4H9n5v+eiP6Z/vu/+7V7IBGBTCpyBgB+IlAKyJ3H5Var/ZMXtw4A3ZNTATVxn7B+9qIGn2QhaMlPPHTap05YVW+BWPunWb77dOjxPCZc70UO3+xIxf5UFozLEpBUS4GfA9zsEM4ifChWk5IIA0C8IcTFwXr3uydJFDf2lcGJnkVgrDcBz11C0ETfJYfMhOASXo1XvOuPojMQHbpBVs+HYUUXEmJywvRYPYZhxbHXirrLiFn2w0zovOzbABkTtWRA7VVVL8Ob+GQuwEXMDiE7rI0gQO8SbscZjrhogXQuF6tWB0ZMTvQnsjAk/CxinjYQfkKT8KCIeJYx8lYtln8nbeFuHQbMmrUkEKjJW7gC9Kz7clQFQ236ZSB4iDuBaoC0TgOWRKSR5NgKDFmy6TTxfGnT6WZCmPQczpL8lCq3VldJs2BWMb80ENZbqSLHA5DHXO4PilMHieWlJc3lRG28dH5PDu5BLqR/79A/An5mUOKSTLfjLG4MJI4aAYgjIY3Aeqd6IocMdBlMDkgQcUq31ZUg1OQYWV1j7ByNbRCETWHJ2kazQgEM0mp2m7DbvS0Co6kRKwREgNQYCdRoIDDVy1Sgh6nu+6U4KbGAkyAGGwJjO3gJZEDBp2YAWnZEq40h54jykz1VQKP5ezuvN24nLzQpuD0Ob8/BrtWtYk+8udcMOAIwoYAI7An5opc4kOqSMHJwxRK0Had2vLZCInYO20HdgLRfsxmtoFbR5UgEJi+Aoo6jiY7a7u15M2YI+wp2MwHcZQEs7HlxKEylAiz2Eaz/n1Jch3SMCgMnyy8b1xidn/V6m/FtGDBVz4Kai6ubGQMJAJBoI5rrIoG7DDd/8MTJHzL+5uuRHzm+b9bEbxN/1bn8TRgXfx2XlT322GOPPT78+F1azfw3AP5z/f1/BPB/4FsWB9kD158y0sgl8aEkFF0/EWLuwJ4Rrq7YeoZzY71nNvQkFXJAFrdpqKJs3aMyHwK2to6si2BPSDMhB4dnXfBehwTnk1iiKoMhRS/2owD8VZ0vJkmy/AyEKxewIlwJ8dmXxai/qoODHV6p8G4VGz9/JVxuR3S34jfITHg/HfC0DPDEWJdQRCaNdEAEdApuLItHXAKcY6RQ+yTM3tURwzsRjrzt5RhZnTW+ON9gWgNi9HAuFyq3cxlJwYa4eCx9wGXuyueHfsUQIg5hlZaQ7DHFgOsiyMLz3ON8HWTMnDiRrDdyDyyxMKq6MSm8JviWzJiAaDqIlWM24dKmWp1WWdznwNVK0QquV0L3BPilVjGtsmzHMSV/P+vxzfEBgFdgI64qDEjWHmCAgyT5rcgpgAJeAZZ0EjgLQOESb9ghtpF8n8BBJqexeJJ9xWjscOCGlsCOa6uDnnv34DC8k+/3DyzirHouOcj3DHey58QlBkeAVwEh0lyBlTUQuAdoTMLiyBCrWGvXUeFPdlQS5dw4UGyq0CT3sXWwkO/o2DZJfMnxTFi0ods73gJbhXUig1RBMLT7aMf9RXRo5ojusN2/Y2VHUFPZb3J1x9LWxPJsVjaBglI23iZgq6yNwvKxCrsl0+XE63UJG0Su26r1L7ECA3VorVX/l6BHmav2nBTLZDuHxt61BU2alhgDF7/GTjH2iw1hcx+z2cv6ZjzsvtgwatIudsn1+cwNI8nmkc1PbsE7J+AjBwW5vEcOXJ55qDgoD7lp8WjYLkzgPhdL4tLe14KokeReAyIorK4fxXXF59KuVWyZM4GN7cEm7lrnWFJAFwB4xpbVtMdfJ37r9cgee+yxxx577PHdx48FVjCAf0XSqP8/MPM/B/AJM/9KP/8UwCfftCER/VMA/xQA/JtXyH94Rd8lTF+pVsU7uSSKhPAMgAhuqeruRvm3xT3lWqEGdOGqC23KshAPZ0Y8ECyPF/ozEHVBSyvgI4EfVX8heKTAgGPEU4QPCWlxgFbi3Exws/aPo1ZNwyTn4hdUJX2lAr9kf7hVkkhKjHAl+PcBawOMLHMAMyF0CXH20rMdCVkXyVN01T1jCeDJYwaKdoJz4jIR1clkTaKFMAYp3/YuwbuMJXpxNEkOXR83fediC+uQV4/kGessxwGAy5gwHFac+744pzxfByxaDSRNqFzI8GOEDxn8BliunbijAKIfEYVxARZgCUBplUiDAFl5bJLbJsmSnch/NFp/AYo2A109cudF60Kr1rkH4oHL74V6faVSiXax7pyYi2WnbNQcu3GAKACEAgHGFFpP2FRq3dokhajzw+YRLUAAVSAOrtggstNEydGGAm+tCsQEWgjhTOif6iDFIxAP6h7TafXZ5qK2hJhFpFX4/QL0jyUTx8oAn5LoWVi7hrEbkjj05FHBitkVZoiemCSxOnf5kDZMBQZksigQkqFOEba5B5C5ujroOL907jAAps6VmjgzAUiAa9pU2FWtAtb92P4KENBUxdlRYclYW0i13JRWoZdaCuU+W6KfuTpcNAelpACGggPmHGKuEmUoGzCnvOvaFgk9sDiFtH/TdiaugB5l1RDR+e6MMZPq7/YOs2vILdNCwYiXoBBs2Lj5DgBn98faoWy+NVOlMAxYADT7vrO5Q7ofqt97ORfMjcPOjT1VlxiTbRmogotpy3ozhgkgDIjcueL0JBvXd5a0n8nvhQE2cpnzLqIyaexecwO0WGvSBhh78XOPb4vvZD3SH179EOf6Qcdv4xjymzBFfp3Lynd1jL/J/vfYY4899vibx48FVvxnzPxLIvopgP+NiP68/ZCZmYj4mzbUhcQ/B4DTn/yM/94nb/E0D5jeCVhBDQNBWkKaqiM0qWoWqYUqrovLNGpFvhO2htkg5h4gTTjZqpdNBZ/QLNoXp3RiRmJNwiYPN8nq0StQ4da68GffVM4S4CJL5REoyU/RKVBGB3sulnndMyEfDCzR2rljpFFFRENWDrdGIsxzB0csnytVORoQQAJYiBUmYVUQwWxBvc+4GWdp2yBG6BKGPm7sWaNj2Q8xuj5i5h7ZqNOzw3QJmLpBxEKZBMgoonkMd4zox4g3t2ccuxX3/RVfzUe8v8q9fv9wQl4dsjJW0uCqxSOA3GfgIOKeRIx4DSIsaYmDZ/gxwbmM8bAgq52taWikwWP2HeKzK2043HGxX+Uhw1gL8USIV6esCKu4orQYWPW5FWfMBnZFAc1EB0BaKNJBvrPc54bGz6JxsjRJtEOxeXWrtK74qbrcUiLkoepKgEWPwlgmJrAKBhCpJMLrUa4hHoF4ZKRjFrBM50lpwUgk56QCrW5RZtMsLSw237uzx/zaIZ2ybKtClDIQqFoarC0srZgoADCDswOtDjR50FKZERxYgANjiTggv0zUWg0F+1X/Vp7BhvEgiTB/LYm1F4aL/AJsQAWVrO1kw5xABYxsN80zzcQiFEm8TTJLxV/vUyZwR9V61hgG9vX2+prWJvsOM4vwrF5n9vV4GzYHCbDQtjUZ0NBajRp4BgBstqirzumXYESGXqOeD29/bsIOYcAC9D3bCIs61fH4JvaHtH9RZQE1fy9AteGTrXWoAYexAQJXwFPdHpA5bde0YYeQvctRmFgcXtwXAzS5AfharZyRynVvWpi+CegkFK2YVrOFqblxe3xbfCfrkZvXv9jpLHvssccee+zxHcePAlYw8y/15+dE9GcA/lMAnxHRz5j5V0T0MwCff9t+Opcw+Ih/f72DV1aDvxK4Q6lwF/aCCZ3ZwtQqal6qnS5Wii1llL74fJSEk0NdTLYCi9mSV1cX8X7SfWSAFicMhydfqt2lZQAold5NtXqFMD2spYAlcXK64s0BiAcAh1pVdQvgrrKCZU1A8pDBidAdVoSQNwKhxobIRBjGFakTBxRbk62rR0qEnAk5e/A5gBJhutQpM9116PuIvk84Dou2i6hOARNSdmAV4CRiOMd41u3ds4dbCEwOHFTvIjYJaMeF4eCJ4SnjtpObeVmlBGluAZbY5lveZD4UGGFcMQxR3Euik20s0fYiIOp8lnaV1SPOoSTJ5Bj+dkXyAcnOTSvKcpFUjsdjRuwZtFQGgOkvUBKwougPWHLnassHZUkgUw+sN4ys+hp5sLIygD6DiUX80m6lZyA60WC5CorWPQNukfPqkwBtqaeS9AggIp/HE5e/u0XEQ+MRWF7pPLhPQJ+r04o5gbT3qVfdkiygiR8cwrWymbpnRv8gmizrySON2LZxlCq0PndjBk1u26IQBKiR9imHcKlMiHjSNp9etSZC1bOo2+vzaQCBnqtcBAEGImVl0VhxWzdnZUoZ4wWOtmwKDTJtENvGW0W/OTZ9w3ZM3wxUtCiEiloUPQ8PYXDZebh6HMrCknkp+Jr1fWeaPMJ+4HoMCMhijhUbcUsnzjkF1CFx/yltUSTj7x2BDIh7kcjnrl7TS52Nr4+jArNlrqG29ygo+TVtzcJokX/mnkvrUhnGBPhF2UcKemwAFP0v96iAQjudGoDEPnv5TBuzxRubh7++/3YuulSBsTDVdpf2u645RnGOofr/FZUZhHJte3x7fFfrkT1+s/hN2BV/Hf2N39aJ5Lc9xu50sscee+zx48QPThQlohMR3drvAP5rAP8PgP8VwD/Rr/0TAP/LD31ue+yxxx577LHH343Y1yN77LHHHnvs8bsdPwaz4hMAf0YibhAA/Atm/pdE9H8D+J+J6L8F8P8B+MfftqPEDp893+Ly/oBBxSn9DGBFET10yqww5gQgAmqUuTorNFReN0PZDOruMGbEG/mb0yopTVJJCxdxPnCjqrM3vfhGzwUL4yGc1clCj+FWofqnvtUgMKZEUxKj+u9wlZ9paKjCTeWv7W83EU5OhBAyTuOCU78U5sRjGPDVVzdgBrpTQhfEXnReZUqsqxd71lW0NtykrRDGOkiEtA6Y7h0OpxljiHh/HeGL5kUtqTpCEc4ktU7lSTQ0KKNaG1JTyU0EPAdMq8OnUVpZHu5GdC7jSdtAePIb7QJ4bWnJbZlU2lC8T1gHjxTyxlYWEI2PNRHyFKSir0J37Bjd7YLcZZkrmUCLg5usbOsqi2XMUnnuUYXygFLdTCq4B9dW9I0CrkwcQAX+aoWdVmttkZYfGl426XOxH03eYVVNinyVY4Spzmmj5WdfaeyUSNxvlFLPHoh3GfmkzjLHKH9PTlgVs7A4yrh7rowFJ9eefEbu25tJGB4Y/QOjewbWk4pp2m3SCrzpJCz3XltbyuaIRzmGuOMA/fs6RvFMSL08E7mTnn+zdiy3oRnTEoW1UO+BVfuLDkHRMREdh9xpi8SLsjU189DaCxhULDy51RjhWhUvbh6M4mZTnEASbYRGrW3NmDDsGi0WQm0VIYC5vt8qPYRkbr6s7Jc2jcqwcAt9nZ1iLSytZofqKQAAVMsnKSXFaRtUbaVhdY6px21dLFpzEGMaZXWAAfR3Z0wG1XNo23B0fOs+UFxjWFu3xLaXkCYnukAZ1YUGst/SltFv91VvMMoYFi0kUy5WVkoOxlbavpvlOrnoYljrRqLGAaR5xE0DpDFS2uiSFG0NVMZfBtDPVEVF9/h18Z2tR/bYY4899thjj+8+fvDlDDP/WwD/0Tf8/S2A/+q32dcaPd6+uwFmh6Q9+P0jwU2yyHStgKYtCgdJaFykKl7Z0JX9IloVboX2+lNZeFqC4pxoSfhJtyPVGRjtYkgWxx5AksRQevmbk3dAHIH1jotdZRVFFKcPsNL39ZzHt5ZMNItfTTg2fdk9g64OtALIHvMgQEHmrWwFzx5gYJo7OAUhplm+G+cAnj1odXAX1bGIKLR2ykA4O0TucFGw4fLVoZyDHxKIWLQoGMjPnajit0lu2wrwgs7tLg7h2YHJIT8I3/vT+wHd3VxEPIuugSUUTsUiX9DaGQAR4zjKAAdth5mjxzx34ETScmKU7WI/QMjZFQFKrFQ0I2wM2JHMPUa9tiYJLsljLzeMAiMbOJMJCBkuZGQFZDiKJgNMY2V2xWEhJ6rbloHSY3sGPCO+Skg3IuAKiLNHmCr93YAto5SHC5BXAVLSKIlhHnJplcmLBxZXdClM+LUKfNLWUlJ1I/IhYzqogOvRY70h9I/SEuIX3lD1i+ittkOROiGY4CxlATjioWpv5A6bNpNAklzmjuS7x2ZqZUn6pQWhJrytO4XR88XZRFpBiOocAgGk7RdF58PusX6N7H5nuwZupjWJZWpgcXgIvHGeqWKStX2GsrZ72ZyyFgdNyEWXZgvWyXea8wNAjQqo2XVu+hrsu6W9iZHNsaYBG4qlJyrQlylvwDcAIG0fETFfLsdi1RMp58DqYmFAgI2fHKDcE9OIgQd40J6NlSqQ17wDWqCQFpJtHMu7Byj6ODk4aadigBZXNY0Wm49qb0sVYALq+Nv5mshu7ptn38aZqdgit+CDnWxxNgksoHU5Byp6RrC2kkZfqbTdBGlLMpcal+p9CI9AMgeTPf7K+C7XI3v85vF92qx+3xau33c7yN9UNHSPPfbY429bfNC1F04EvgRJ0j6S7Cs+d+hU3C17gHupYBmQsNznkuhQInHSmKozR2t7B2if/R0jHbhU1NcgivxwAlj0j0C+ENIon8eDJmykFdtVFrXJdAh0ERlvGfE+wZ1WrIsHZlkFx5PH4TOpLK63wPxRRj5kpIOVGKFVZkveGGlAqYSyZwVhCARC5B7Xc4erO5Rkww0JdBFb0OW9nNjUZ0DFKmkl+EXH5kpIB9mnykggB9Fh6J4c0tzjcvUIT9VuNR29nIcmAsMzgb2v4pdeElrudRXfaaZqeg9ZtD8AIKuAJHFA7DNuX10AAMMgNylnhxid6mu4wpzITOg6TU6yuMuJUWQAACAASURBVJ94x+h9LXOm5JBInE9y58AZ8DpGaZUshUpyJYm56RZYzzzfRLg+IV9NIa8mejanTAiVmworALguox8iciYRAV2DHNeApyhWiSiAlt/26ZtooxdwjI8R7q424U+nDm5yknwru0PcSZR5cW2AB70eWl29D4lEaLYBx9LAX9MaKEKFZo1JDH+/6lyIuLzyWL4KOHzRVNqNPaJghZ9ZxWWFcWRVcz8LIAEirAFY7hnxSEX3pXsUkNFPsg+3iCbBhlnhtdKstppw1dXFKvfsjSRgzhxUQB2KgE8CcqYe4E5z61a4094dkYpN6YYEk5TVlRTkaZJg6L4ssTW7TWpYQuz08UoAPLZMp0zKzJBx4heggH0H2RL6ZhKZGqtnBSyoAK1MqIK0gUH2vBoA0bsirGmOLQwUu028OJR8jWvCT7kAH9W+pRmTSBXUdAw3JAFng7KaotuAFQZKgABuqTStIwqxsJGc0Jm4YWNxIPDsNu5BX7N3bYA206aog6zH9iyAi70LDGgwdkgieXZJQAc+5MqyiQIOko6/a8RkgQqeMKm+iz3XrSvLM5V5vccee+yxxx577PGhxoe9nLH14ZARRkmM5o882EmCzEESkjwy8q2s5O4+OmPoItboMa8B14cRdPXwxh7IgL+Q2oKiLGbz/Yrca6atLhDL1WP8zKN/BPonBqv7wfxKq88rlcXrepex3srnQlEncJdBY8LPPn4AAES1MHg4HzDxDSgSlp8kfPSHX+HV4Ypf/uQeALAuAXEKoKcgjhRdloT6QVstVhFKtGobJVdYJJYA5iHAr8LYoLMph2IjRCpghVbhNckzocXcM7InhIngr0C+BEmoyqJakgg/S4JkgFAxgOiBdXVIR3XZYKigaF2Vp0EE+6xNIQ0MChmvj0JRueln9C5iSh2elwFTDFijLy0oOTuM/YrL3GNaOjCLi8mkzApP4lbSdQnUx5IPBAUz1uRFhLQTIGTVDCFr5hH6BHKM17cXeJfxxfsbsYFVwIdZWA+0OnFgSFTZGzp/MwJmBSrIQZgTriaFWZkY5rph4q21UqvggCa/qXdwhxVdl3ScI1J0iIsASaQCnetVznH4or4CWEURw4U2bQ1F0C/Is0R3S3FM4aTARhEPlYSVVictRJBqtn8VEY8R52EorA8DrgqraZbEjJK0faw3CqhcqlAukwiQ8pCKI0Z3cuieCeGCaumrY2/nLwdCsSiWirWBi0AehOnDAciOwD1AqbYg+Ku6ukxadVeGxwa0URJB6/CwaR/g+l+plr9wqRAwCLWdpX1DW8KdqbQzlF2THctAEH3HNCyQwlqJug9jDtgxfMMQ4gqUFGZRJnAkUJB2K/LCWODe5gKBF2FrwQmrCms9x5K4ZycgpX1UEBu7N1zPzRHQ2g4D4jDUZeRM8ozl5ly1labsw8l/ZPasmQSYyFSFYhtAhgnILiMX4EBAYwMzWiaMMYpalk5pHSrsiy3LhbsM9FlciVg38AzSd4lsomy0RdCzPFIBmuwgZX6l5u/NNeag7WR77LHH9xK/jQ1ru8139f2ddbHHHnv8XYkPG6wAZNGYCTkpI+CTCdPYS0VuzOhuFtwcFrw5STX+Pzg94BQk83lYD/js/haP04DrLOXyuHpMTz1odQhP0hqQjhk3r66YBvnOq7sLhhAxx4Avu1fgTwPoS0K4yOKwO8si2xIODkC6jxjv5bhdFxGjx7IEcCYc1JbzGLQP5DXwf8a/h2UKON1P+OPXX+BVf8Uf330BAFizx2fTLX71dIc+RIwh4svnE54eBMzwl+oWkb20tVirgtcWBr8oJZ1IAAVNxK2C7hcUoCJrUsZdTb4oUanEuyRV53jCllpfqpWSfIZrrVQbsyUtAlakUbUTmkp1OjByx+BTKn/zjguoE7NDIIfMBEfCmOh8qi6ExHDEOANYpiCL/yZx8V0GuYyuS3AO8C4jM5VK79hFJE9wJJ+tR4ecHbog53M7yP18PVyQmbAmj/PUYwmVf80A0hTkfkRfASxAW4tEEyRrGwc8g8aEw0n2nbMyRpJDWh3iQ6dtSrUaXe5JBvLVYXU9kraddEOE18q5D7m05ixBHv1ldVUzogPCtWFRQBPnURkwHaM/Lbi/uRb2ypoc5rVDzoSUHNY5IM8eNDlAHXpS75EPEaFPiK9XxEUTNavYO8B1SWxzE8E9BXDPG32U8OzgrzJnEWSMcJTPl94j3nixTJ2k3UTaShRY66iwHmScBPzqng1gAdJKwk4ylkWnz61R7s25JaqdMQCO27FvOh42f6t/QNVrUFei4uDg5PlKg7C45Pi5QffQJPd6rNhkwRDwgvR71vYDQtMaweJQZBoptvlqCCbX57YFVqy9YHKAc+qwweIS0+Uyv5iAzOJAJO0wDOL6LnJLbb3LBiqgPg/shEXECnaIHgtACi5yJuRV/FOp6KQwyAAzQH43dkOXQb0AuZwacE0dbShW5kexY7XfTbemM6DgBWCSqLCZ8ou2m3KPNiIcuqFn+D4jewavTueBtH/V54HhuoyUdLI4BmcGma20tpa4SMCCzTtTzk3fwz+4fPYee+yxxx577LHHdxsfNljBBH924KtDVvvLn/6Dt7gcO+Ts8Op4xZvDBTdhxsEL8yK4BE+MQAknv+Dv377FfAqIWn5dksdfPr0CM+Htl7fg2cEdI+4OEw697OP3bp5w101wlPHnxPiiv0caOxw+k9WiW1ULYDB2B0B9xh+8+QoAMISIzITPnm/x8HTAl88nrAePV4MwBv7g9A7/8S/+Ep9fbuFdxtvphHfzEf/w/jMAwMf9Ez7un/D7x/dYs0dmwrvzsSxaw6T91kWsEsWq0nQxwlXZH22FMFXOtonDmT4AZQjzwhKPFWX7EoRNJdisB/Mg1c943vbjexWfAwvzIo11h0mFKilK8kLaJhKvAZ++FVDG+QTvRaeCteWj80lsNAEc+hXzGjBde+RLAM2uiKACQAySgKeuVb+rvw7jihAShpAwhohXo80hGYRAGefY46v5iDV59D7BHWYsatfqNQGajx7rGjD5XmxaLeFRkcLCYvAMPySMhwW3B+lxKBR5CDDw0B2RZo/UtOsYw4ASwc1iz8pOPp/HUJLb2OeS6JktLN9ESeS00p4UbTIaPJ8i+qOMg/cZt6OIqVpkJiR9LtbsMC0dluhxeTjAv5PJQFcHfvJYb5OADF2WhNHaPMYo+z9mOMc4dyN48UWMdTwuAiI+9nAXAXz4HEoLEQ1ZGCg3hDUS4tELO8R0FTpJGk0Xg7JqyDRWwi7KHGS1ykwHbd3S5yYduAguegXYig0yUEUv0QASLwrbBkhkX+95LmKucgw+yBg5z0iLK0ACJVeZIr22L7QJsjEtGjAMJupqMSQgVgVKUltdmnQuKABgbTIiNkqlHUvabqqtZjow0siIN6rH0mUBGoyd0WpRND+5eQapYV4gyLhJS4srAAEXOpgI3AIQIMeYHbkRp2xsgzNcbYdp9Bzk/GR8jAVTGDckc58b4KAAGYAMTsgCLhh7o73GpAPomotmqjcrEnJs6DaMcm6FweLEMrowlvRdXgGVOredtne1ACNYQMav2bruscce32n8On2JH0o749tiZ2DsscceH3p80GAFJWD8UhavVqEMf5Lx+/cPCC7j4+G5gBTvV1Gq+2y6xaosjDkFvBnPGH3Em16YFw6M4KpjxHXpQMToXC45wBwDLtSh9wlvDhf0P094d3fE83gDABg/d+ieIa4fHcH1jDR5PK/CzMggnMKCQ7fi3PV4eH/E03nEp0H6RN7dH/Efvv4UgTLezUf8m3//U3AivL2cAACfKFgy+IindcDDcsDj46FU290MkKdagU1S1Uwjl+Qo2eI2N9VkoIAF7Ah5UNDAA22CDRgdHkXpnr1oKmRdvBsIYa0llkTkRvTNBOWE8VHuqny2QBLMBCxTJ0mjMx0LFQwNLIx1FalbxgQ3JBHOA/AcMvIUgEiq26DChpr7uNWDF0b2ThK3lZCHDKcJ0fUQgI5x7hPmG49XB8Kb8VKArffzAZ8/3mC69EAm3L8+4zgsOPS1zcS7jLuRkbLD4zBgWrrSQpGTg/MZKXr4kND3Ecd+xalfMEV1ZckOnU/ofcLtkDF2Edelw6TuKssSBLjIBKzW6y6gBSCJaGkZIQWmAgq1nm5XG06ELiEOHj5keGOPHGccurUAQMFlnJceSdktRIzOJ3Quy3+jMEKcYzyvMl/9k5f2leQR7yDskVjLvolFg8B3Gf2wwncZ6bkryZu7Yfzem0ecbzq8f38CfTYgnKU9BwDSKQMhi1NKz4hDRrr6TbUcUMq8uki4lRCejWUk883PAGZlUUQRzbW5kgZxlMgdI2oi7toWB0uAycYZm9hoFnigUBsMFFJXF0uE00xwT74woWithfp4opq8WkbqBdhzM1Wx0ATR6liUYaItA7RWHRI/E7pnmx+y3zhWgNGtwlQBVFPEWpBYxIrjgbDcyznEW1YtCGVIEFfdDGh3SXhxT+I3ZNSZVBzYVZcNOcHqeNER0iEDBwG/ij6FQwEi3Eyg5+3/xWW9j2y6FerCVMCORIKRsLwTxIWnAX6JQYHBPm3RgNxeJBpGhV6jYSXKoqDSoqLfDw0tJxN4cRXwcdvPBQsiYZR5cbaqjlJy7PU+FyHgPfbYY4899thjjw81dqLoHnvssccee+yxxx577LHHHnvs8TsVHzSzwq1i58mEYh339umEN4cLXvUXBJdwTR0e1hGfX4S18HAdsUav7hEOXx0PGLqINwdhVtx0M2L2GMOKm0GqylMMmJMv1eSn6RbenbSqnPHT0zN+fvOAf9N9DAB439/h+BcB3RnwK5AXwD96/OqzV3LeXcbxOIuum8/ga0B+JixaGfuLd8ICuelnvJ8OwJcD/Ex4qwKaX57uEA4Rw7himQPWKcA9dLVFIwJObVIpSUHOL4Q4Elir6DlwocSXVoQXbOf1pKKWaqdo1V3bb//IRdAuB2sNqW0k1g9OUSqi1lYCCGOjtIywsCzcWl1Y3CrWlS4C/XtgvXPIHptKaxrk2OnAYE9IK4FnX2jrlAhhRhHAYy9tFWlUG8PJyflq0dItQIzSQgAA/GTX1uHpvsP8poN/k7EoM+ft4wnz24O4oAA4D7E4jgDAlAl9SLjpF5yGCcduQcoOxtGJ2WHw0lLhiDH6FcFlxOzw+ZPMpaiCoadxwRgijt2K3ifcDEJFWZLHEn0RA42rR1w8soqmdg+i5WDVdrEalTEDpErvB2F1nMYFpNofxiyytppl7ZCZcFk6nK8DsraRkMvoe9UKIcbQiYbKT2+f0Wu7yPv3J6wPPdzVqdiosACKC8kawEEYEXH2IIK0d5Ec44wjbsYZf3j/FfqQ8NlXP0E4O6j0DNzikHuHdMzgIcONEVAdEkDo9aajwItDSoTEhDTKF/xEIqo7V7aQiwCvwlKyByJ3QD5msaHNKAwem8MyIPqzy9tWkNi0Apg+SSu+mQg0O9CTLw5F3XNt2yoWzESIJxVQ7OrzyKSOKos+IzrncwDM3jR1KHoOpM9jmIDhK22nYWGSiPWrPhcZCOfKlmICwizPJZ+FpdQ/yf6nN05dj4RNkTsRZC1D9NLylZrxQn0XuQhQdPCziL0WS2dCEXvNPSHOhMgkLSHGOlB7WTjAPzocvqDNe2c9qbvTqAyLnsHg2j6yyHHcqs4mvj4zdu+kTUYHI8vgm3go2nYMbafZtOJ4+7vo6TDJ3HRdAtl8zUCefXVn8QzXp2LZnPtcNDiStZ2osG2J24h4eUHv2WOPPb63+L7bPv668fK8/ja0hfy2Y/234Zr32OPvcnzQYEVJiI36C2D+/IhPj7eIR3XWmEdc5h7PZ/EuTdoDbj37T9HhCcA7Ly0c/bCiCwmHfsV16TB0EcyEaemKsOP1MiBr8uHUKu8fvPoSf/yRCGD+v8R4H18Bn9bFop8I+KIv/z6HEXnM8HeL0pYrrTy8d/gLfIz+9SSJlmOARGQQAHBxYNfh2o/i3pBo0xbNDvATgx2BWECJ/lESnniQL833VBbSlKhavdopkzippFGsIInFbYEagUw/ASChtVMGOFIBIOzeuAR0j1wSjSL4P4igYfk7hIYfrrKyDxOLNWsQfQ1LQtp95E51BXo5D+m/F6tVG/NwlXNZTyg2r2Yj6iKKY4rsUMRRw0XPcZGe9hyAZQ6YacQXXcK6yiAtT70AHlkSsZwcLtNQ3EiWuYMPCcvB4250OIQVQ4gITYP56CMGH7FmD0cZSw54WA44P8p8ZU2Ip6FH10ecDjP6kDD4RuRzEOCjV478ee3x9klaMJblVFt4uF5v6clnQggJ96crbvoFDozIDrO2oby/jpjXDssiIEKKHjlSdUdwDjl5zMQglzF3AThO+OhwwSdvngAAb48XfH53g4eHo9DbE4G9OIaU+wACDwBmLxolqbYJubcBX97c4I/u3+EXt+/x+es7rHFAOFvLkGovRIc8EFIi0JiKTSybloNnaZVRen9W0Eq0KRzcIomtaTUQ17nhLpK0xgykW026NwKK+m9te6BOxUxNG4QgGgzRVZ2CJsF0k0P/KM9Y98RwUea/S1y315vYP1ewor23IgDKpaUqDrR1E9F3hFnvOhVqDLOCEarB4RcgneX9kUMjtzBSeebcItv5BfBfsd4H+SyO1k4irWTWGpYVLDGRXdPGKJEBEMEtpO8CeXeEqWlN0+tOA6F7JKxXj3hw1Y0jcNmvnwndE6N/5jJO8UCYr4T1hrDeMNIpg0+pgLjck7ShRAK12hr2uCSZt0w2Zvp9+9xxea+WFhiPqmERxNGInDgRMcuczFMAqSiu7zLokOBDkmeBpN3K++pyBIgYNAPV4aQBzEIfBVTbY4899thjjz32+IDjgwYr2APTR1SU+gGge+fw5ekOT6cRKRHW5x6ITsQVIYlIPuRiF4dM4NUhaxVqoh7XwHjstGf7GNGPUWzyNEFLiwMm+X66eHy+3mOJHn9w/x4A8Pdfv8W/Th5nd1tsGskqYJBkoP+MkDuH6adOeqADI/Wy2vSTx/irgPh4EvG6Y0IcM0jtJv1MxWIVTrUbDlyqeuudAzEp+CA9+pQZx88zurOdjxM9Ckfws4gHoqfaeh20MmtOAiT2jSWBjFzsG63ySLkmInIMFS5ctQ++J1C0axQQxEVlSHBlUgBA9oTlViqhLgmQ4ueqrwGguIykmyTAyOw21UWz93Mr4AbdJlGzA62gmtMCA91TrWa7VYVHjVkyO5zfH6rzwOKQDxn5RpJUx8B86YrbSJ49oguI0WNaOoz9ij4kHDsV6qSM8zqg8wlzCojZ4bp2ePd4BCszwiwL+eoxdwHL1CH0EaOKfZ4GYVwEl3HbT7jvJmQQ7gYR6PzXU8B6CiJQSgyePNzFI6vDiusShj7ipl+QmXCNHR6nAcuibiGXHqzPDoIkVgiVEsAAcgQ4OxB5xCWACBhDxMejiCH80e1bfHx4xi+P93h7PmKeO7GEVSAmXcVWprtZEOcAnh3ikQvo5CKQHnp8er7Dz08P+OlPHvGFv8XyXrLu7tEXjQa3ECh6pFiFR6HvB7EaNXCBRZcBklTmQ0IejU0g27q5sj/CRW18F4cYqeh/FMcbV9kEItLohDxgdp32GK2a2OqcMk2KAlKo8Cd7IB5RhR9tHyzPgV/E5afqcfAGOwEE6DBrW0A0JwAgxSqSGQ+ENChotCrDSQGP1DNyICx3ygQ6iuYDKbDqr4TuzOgfK8DIi4AM7IAuKAsi6Bgpq8At9VzXG6qABaOeRwQoctW1gZyX2bNSYvhVwMXc1WPkXgCNNCpT6qDAxyRztn+ScevOhOVKWCePGQArcEUqQsuZ5HSMPdGyaIDqzuLkxhSgOGTVlwAoCAOCPIOM+ZFEi6KwchhwZ2GDJbXGjncR3WnFMKxFzHdNvuzDQIsUHXJyAmiSWskC5f3z+v6MPfbYY482vm8GyHfNYvguzvfXCaHusccev/vxQYMVqQMuP89CVVaxvHAl5M97zEMHtxAOj7LYpJpfYb0lxJsMVnotHIOeFQi4mpWjLn6HgOmjuK2iqkidtTjQ1eHh/Wv8+c8lefqHn3yOP3z9FX7pMp7OI9I1SCXVEpuLw/FXBHoS2735I0Y6ZuAkCejyBjj8MuD4K8JyHzD9bAUdEjDKyj3OXqqyqwhGos8iLDlpgnkbsfzEibhln8DRYX7TIXVBqowaLgEpSDV0uaNK4QYq44FJ6NxB2zicJR7V6pGVzt09b5MlOGE9XD8mrLeyb0tAu2dgfMcYHjLiWJkd8WiUcsL19zLyIUnV+Z0rDJpqn6o/DWHh+jcAJSHjxl6SfWWwWGLZJkndI5cqbQ6EeJLEZ72VJA1PoQpWeoa/XTGMC5gJ1/cjEF1lLaiwXuKAy+xx9QOcZ4wHQUOcy4jRI2fCugRwIhHfu/rqskJ6fRGg6MGrwxoC4kHu9bUbMIwrOp+wHjyWHPDRcMYnB2E1vPvoiCV6vD5eMfiILy8nfPVwQtCExzlG8AnXtcN57nG+9ljPfXGhcFcHl1WYMCtdPjXilaxihVktJ5lwdYy37lhaST4azxj9io8PAl5c+w7zGso5nKce87XDzWlCOjjMS8C6BMRnKXebxeQvv3yFzIRPjs8YfMKXo7BHzuEoyf9VxEQNaDCgMI2sVH0uTCC3UtPi4cSe1bOILgZh37CrFrFJGUguAt1jTYxLK4c+F9mq6TqvnIIjTNAWB6ogWiOKaM/WegfMmmyvN1UMtjCVIhDOVCxmba4KW0RAxlYE09omAHEJkmeEkTvCegOsd80xDISYCP2DnEM8AstrreiPjUtGFuDGT4ThrewgXBRQiJXp4RduaAl6rQmlrWVe3EZ0l6m+atNBztGYUH7WZ9jJeLnE6C7KqMpqb6qW0euJ5B3yCSOeCJ22anXPDL8yurMAQ92zgMPLnbIVbsWml4YMp+wYIlYbURQGA2cSV51M0p5hQEJgOJfhfEYIuTCSrG0qPQbQKnbR4g7D6B4cuifAUKXl3GH5WG2Se3HKmafa8+MV/MhMyKs6xih4BghY4caM3u/Mij322GOPPfbY48OODxqsQMegNzPyNSBrJZjOwPilJhqL6CpseoYJ6J4Jy73HshLSa6A7rFiPWmlegiQkz7J4Z0dgCsWaENCKqlHpWSp33RNhnqSV5N/2ER+dLvjkVhLGJz6UhBkAsgtYbh36B8L4Tqqfa3RYtbLm7xcszx7HT6EOGwHrKwfcCu2AhgT0WqVTK0oA0qsP4GcfP+Cmn7Fmj8FHOGK8/fkRn370GuFLyQzCWZIms1aN91EcFcydIBMwObjFCSDhM5ZXBLqpLRbxJoN7TUYSgV1Qe0MdapZ9Lx8l4G5FP66y6AYwf9XDLw7DgyU0wHLjcPlE9n/9xYo3P38AALx/OAHvD8hBXUiUfUFXuaExip4FsbJOCvtDfibrvtHkrarm636ybavnPMg5rDfAcs9INxkcGOHRg5YKyKQTw2lCsC4B7inIMfpmwhHEqUB725NjnJWVA8fA4kCT35w3GIj3lnzpvsxmcVGQKsk4RmLErgNCxlN/gPMJH796xscHqar+3ukJGYTfP77Hyc/4i/41PlUdFgCY14CcHb54uMF67YR5MbnN2Jj9IyWhx1uiBQjgUxJYgropdHhaPC7aevXZ4RYHdTmxVirnMu5GmSyHbsWn6x3W5HF3mPDmdMHjNODRHeW7PmOdAtK7AX8ZX+P4+wteDVeMQcC9v2BgXT3WoRMW1bStgmdl3gglXwDKOIXCtvLPhHAWJkQeGdywiYx9kTtG7sSdwtpOrD2rDWd2oQostEwjY0mYgw5QwYZ4EpbQeicsDzokjMcFTu+/aOwIqLXMHu7Jw0Uq88MSXzt3SgRa9FzMKEOdQvyVkAZGvM+gu6VU5BlAXAQQmx88ci/ADR3tgZLn3JJzPgGRCfEkc8mfZXz8LKCOvwrL4mvPo7WCKChRW1xkjHIQ0GV+zYgnRj5WjRkb+3AhxBtxdAkTI6hGj1+5WpF+JO+e9V7cgACgf5T2ke4sbTb9EyNcgEFcpbHcecSTx3qTdS6oy4wxIxScIM9wnsHMcI6rO2sisUxlKgBFjA5Zn/nxcw8/ybsmd8IsCVdl2ylDpX8E5nPAeuuxHBjTIYslsb6b1z4XW99yX4ACnHEmeM/46nzAHnvssccPGb+OCfGbsBp+SMvX74JlcflHf7qzNfbY43uODxusIEZePOgqonQAEM6yAE299mNPL+jRJH8TWrfDFDusHwHQRX68Tci9g5sJ3UUqeN1TrewBUumTSikhHRlukQWnncPj4R7Prw549fqM5/OI9NRhE8SYfpqRe4fxC6liyrZyO9IhIr9ekbsO3Zlx+iWwPnjMH2l1blCdiMBqVanX91M5wZ/8/+y9Wa8k23Xn99tTTJl55qrivZe8pCSqBXcDhhsNGIYf/JX9Bfxg+Ml+6QfDkNQtNi/JO9Vw6gw5xLgHP6wdEafYrSYhqdUsKBdA8NzKk5kx7IgT67/+Q33iTb3nMFVEFDs78PXmkf3lR353uAbg/rDBT4apt7x6vV9MFedp+BAMj4eG8X2z6LPTzqOyBGCaDK4ZKUsBQ/rBMVBRfDTzLopZ33Vk8+WB66ZjWwwMQfbx8armOVwTncF0MsnuXsPwlbAO3nz5xE82B+67DY85ynCeGK/T6CQNUavw+bl8prLPP8uEdv35pSmh7QQAWAz8EEbHeJXXwlYaR4oIvRGpUYRUzOsgMR0LpqhQvaY4yLqImdqfXGI2tVyGy0aRZnlCksbfdKu5Y7QZQNrkxjZ7dSwN7swEyd+hoyINAOL1EIPih33J4400+v/6zVusjuynipAvgquyozPZrDU0HNuK8bFCjUqabcXi57AYIKqE6vN1sV/BiqSF3bMYPkbQoyGddN54aG3JqYw8bCeMDdi8hoocj1qaQEqK48cGbuH17kjlPGNmoGzKTG+SDwAAIABJREFUkbZ0tPcFvCv5XXPNm8sDu0LW+6vdicNQcNKJ4A2h1MvkGaDZDIvuv3J+AU3mZq59v8E9mSyXUkSXIyvtC+DCJPzOCxgyamHN6P8cCJ2jYyGDFXP0aFilajEfmuhWo9NQJuKlp9wOFIXIeupi+iQuOSYYCgdb6KqCMDOKAO0i1kSMyV4ZSS0mwovcJrNfpl7MG83GU1br4jcmkmqFUonhwmFVYuzWe1fyGkYBddAifVIuwoXsmG9y3KZXqMy6ENPc9RgIAyDLY2YGyEtlllnBsekqkuqAbQTlCI3Gjxq8xjfCojGD3Bts+4LdMZHlcfkz60DYymd0l5rxYHB7LX8r9lAcE1UGK4pDwleKaaOzN4bJEc4rgDX7+cR8/oNhAQpEhpSvjwihjhL/mk1/qw/gWnmfr4W9FR2MOxbAxbWJcp8IhWJqFL6xqCD3sfk4+40hbCJ6M6HKLFsJ631FqUT7YcO5znWuc/2p1J+aCekfC6z8oe3+Y/brDGic61z/8Pq8wQqvKX502JOi2Ms/uWOi3EeGCy3GiLtPJ58o0TkXR9E860nRBYe/yJNsF4k7z4CFB3GkV0EmX+6FhGJuIPsbtUgm3EFe33yr8Q8VT3ciRakOeqE3g9DSpxtPvw3EQuQe5VNampxDVaJuR/o7Rf1ODOKKQ8IdZ/q57Fso5QFXe5FQ7BtpLL7dX3KYSqZg6L1lVw68qQ/85fY9f7ERE9DH24aTL/nbp9f829vv+aG7YIwWm+kjWkW2xcivjqU0ZlHMRJuNNIhz41dZj9GRZ10RrzSTF3MIFRTJJfT1wEXdL74MLiMJv7h64Lu/itzf7WDvSDpR3Pb85EIYAZdlz7t2y/2jvA4CNvgaVPafMIPCtgnbrsdOT2mVUCDeFyoII0NFmYQvhp79yqoIhVDe+7tIvJbmy5QBA4TeYlq9TudnnwCvsM9OKOmTWujvC/OiUiszQSeSg5gSOq7+KaZX+f0say0mFvPIZFagQ4VsEqrTCylMZmTkxs30kPaO4SSg0febSxo38djWmCz5aNy0JNuM3jJ1Dt2KzCYZSUhQ9UoJSJNGDUYMKLMp40tZVfIK5ciICqgRVNSfyHVCqQgnzdREpiqgbeQpH6iqmAheYz86jnFDSorSTQtwZnTkoho41JHi0dDdN/wQNHeXx2Wt+CiNuXcCupXOLzR494IOvysGKjNxV5542Amg89fmDYe0w5zMAnZFBbFIkuoBoBOuntA64b14bLxkS6HA2EAMhhhEIjBNYiYKgNeYo/7E4yIWafFK0LVnsxkprMdHzTBZhskuxA3vZVqfosK6gM6JInFe9zqSkvye1glrI2Xp8d4wzmaoGSSKWUaQInSncjFx1S5gbMS6QFONjFl2pAaznEcxnRVGx8LmKPOBcFFYGg5SEfEbMnViXij5/zOIRxBQgxe+HsmkxWskmSTARDu79rJK6aogXiMXoFxkHPM11RqR8mVQ0z4bYqWJGazQtSeWgX5j0Z1mvFSUT5oym4TaLsn/+rRc70lDKOZtkv2YtnoFDl/sm/bCFJuZeMOlHLsZqDJjBjANAopZGC8EFEtG9qF6UGx+DBSHQLlXhEL+fkxZIucb8REZvSJUAWM9UfOJH093KCnffd5/3s91rnOd61znOte59B/+lXOd61znOte5znWuc53rXOc617nOda5/vvqsRy96hN1vxeVe+1mrPU/QRQ883KZlGgYy7XIHRfkg0XvlM6ikGHPSxnipCJeBdDPS6wK3F5M9M65GdSqC7fMETeUJmWaBfur7hD+CbY3EiA6fyhdCpfCNpv7qyLBxtLFm16+a5eZHzbGy9K8CYIiPiuI5vTDHzLGCJstccrzm8F724XG65qGKKBdJXnFfRN7vtvTB8mUtFJTajAza0riJH7oLvnm8FdPDTM2/3bTsioFyO+AnSxg1SrG40y/xnMFAMBKdWU50l3IQYtBoF6iqiUNf8uibJcoT4MubZ/7N7VuKV9/x7emamBQXRc9+FJ+D758vOX7YYPYGExXTRfYOKFfnfTMo3LOiOCCshiisCl/NxoorA0BFMdSzJ5ZEEeXlnIVSok2ni0i6G7m6bJc1NgXDaV+Ig3/B4kcAotEvHl+yJ1gSTuBFRKPOkqE6kVAvtilLUF58pkqyXcV+NWZcaPJJpCli/sj6Orww+VO4PehskPl2d41tPH5fCBuo8RT1tO7fYEmdQWmIRU4yyGkGAGmUybo5ih+BigrfzN+1bsCShmFEIaCmle1iRnmfypGQPkEsFX2e6E+FmIuqBPbJclIN6aZdDDinYChMwFxOTJNG95rxseJ99gRo3khqQlNKosll2VMZv3haHKeSIVgGL4kr+1CxswOvS/GUGW8M/yFoukNF3Fv0qIi/zy6JijAZoknLBHs2WQRQOuFcwOaUlsIGYoKUj0+ImuOxInot8a1ZRqEz28HYSAia01SKd0hQn5jyvoylnMqs+1JpMUKNg1kTK1RirALaCvMi5XsbL8w/UwIGOZZzIkmyFl8nJp0YL0dCb1GdMBXmc6yiWhgWwi6TyT9kvw8lrIrZHwSTUPk8rsdK7h8xKeJoSLMJad4/eiNRzqPOsprZ4yUzlCwi0UkJikhRTahFImHEgHgw2GeDOypSqwjZK8fvDKkOEm1bBcZLxXRlGK5mTwstJqatGIW6Tkw8i+PMSpHrt3qSDRZmVlr8SGZGxZySpLzch2Zj4uF6ZWiECqZNTjMqIn2Wfk0XhlAaqo9aDEtDTjuZ73tjojgoVFL01jHVmfkypxRFJd4cB851rnOd61z/gPqnlqz8Y708znWuf8n1eYMVE1QPkWjVQtONleiMQym0/uFV9hyYH/qDYtpp/EZTvVe4NuGOL2UEmh7gywnuBnwssSeJ+5vj8ebkCttnnXEnMZ6zq71QfRXaJ4nmY32InbfbHTTeG+6ujnz4heIUara/lYf58jExbS3DFxPdV4nxUlN+1IsMZKYS60noySINgOJZXnd7w7Qz+K3sd7KJ48ny/3YFv2ruAGiKiWGyTMHw69Md6bnAHDVDNhQ8vap4fbunqUZC4emtIwbN6HPjQI7Z84YYNDEotEnYMtOtdcKYSIyKdt+QRo0+Gub277eDZeNG/pebb3hVHDmGkrf9Bd89XQHQfrel+mhQHvo3AS4nlE4UlV+i+7zXdPuS9F2BO2RzxwL6u9V/RA8ae1Ako7AZg1i07JX8PO0Svkmki4m6GXEZUJm8yQkX2cNBA15h29kfRUCsaCRRJamcdLBEn+Y18UKJ9DKKEdaeU83U8LxtL41KX36GHsVPIc6+GW41WAxKXpNEB3m9/L7AN44igyK+14zOfeK1YEZJfJG0FSV+GHNMbq+WmMroxMfBb1bASMX0iSQkFtLMS4JDysdBGludAT/TaYJJzBqH4LV4Qmwi5iQGmcPgSEX2KoiaXkfKamS4g3BwqEkJAAO8b7aUzi+RsI0dsbkZBpZI2NNQcOhFHvXU13y5FQPXynh+fvPIh3LLg9kQM7CToloBgKCIvREZR5DGUL/wW4hFoi0cuvEokxiyN8csQSmdJ217UlL0nWx3DEpMcoFpNEw5pla3Bj2nmsyAV479DEUijeqTNSW/kAEElU1Pg5IkY81iJLp4bGSPBdNmmdsc56oVIRtUjqmQfzdZDoOAWSrlWNde5bWuFq+FNMhijs7IWi7EfDW5FeyY78PB5XvyC6Duk31SAtq99LyQz8hSKDN/l2ZIIgWBFRDBRmKpSaccEfuU7529YdopQh2hjJgqQBkYmwxWvxIzXHsQDxN7FInI7FeEyoksfcqIDyIJmuU6laz9aAQETSYnCuW/DcNtlGs2Ksi+KLNBsbvIX7KDw1VBd++wvcb0OeUl3zvMyAK8NpMmab2kwIB8XPmYmM6WFec617nOda5zneszr88arFCArxXDhcZLEAe+kgdcm5s1FeVBcn6YTWjiNtDXETVZUOoTZobEEmpOm4LNmxOn2pE6I83sPM02YLby2c2PifI5oQNMOWJvmYKpbKJX5fjEGazI5nLTfc3BBn7y6pl3Gvps+Fc+JcoHmHYG80UHl9BeOcyznC49ianc3GSGEkhQPcjnF0+JZBWhnB/yFeakSV3F0Qhz4ahkShrLiH024kb/pJYJYD9UvE+w3fXURY5UnSRSEiBGTYp5ojtpVFD4MuK20qnPDI2+c3Cy0pBHlkY/jCV/zRcMwXJbndiPFd/c3zJ+J0/Y1b3GTAI46duRVzfCCKmsX9gdMSn2TcXH041sd1KMV5H6pzJSvK5G2qHg9FAz9QbT6qVhBmFpxDLBVoxDnZNtPvXSTA6DI45GGtciinFdWpsnM7AkF4QqAySsqR5JScMSs59DLEBl8795Accy5YSN3IhZ+fdP/R7yYs/NcXqplWdlX0QXZYqr9MLMmM1bZbqbML1CHdXqnWDyuo4KJmmidOCFienaGCebtzE3rQAp5qZeZ8BPQ7SRZNe1ZFu1eIMkr6CHWGnS7AfhpfFMm0DI2xRay/CCVZCyZ0pZjQwqiWnt7PHy1NAWgbBrxVgyakrrGXLiiY+adnR4b+hPBWnSnEzFc77eXl0ced0cuKlbRm/Ye41+cuiHF6BOWoGgOY1ChRV8ShqSMsTCCVigoKsTp9motA64ymNdQOko3hajQfUrKDSDBuILkr1MZjZWOZ+r7JWR5JiGTVzOY0QYB1QRRmFNxOIlkiTgR/K/B5DNJqJp3g9QkyaVEXsxojKLpnQB7zV+tPjOoAaNHvTixyCglEL5DGBFhWJt5BfANkEyJpvwfnpfReUEEqUy+JsWJpTsQ74+AigEiIijW5Iykk1iJZGTUaadAA0mG1zqAVwU889QakKjoYzL3we7GTEmMV0ZfFCMnWW4NZ8AJsqDO64KytlMGMT7JtmUrxMgyv7MgE/aebSLEjmazWwZNKbTTFruO7u7E5fbnv2uYugtaTBLchCAPRrcsxhA1+8lijW+OEbRKHRI/7lf07n+m9a0gx/+NznmX/5f6Q/89rnOda5zSf0+6+LMtDjXuT6tzxqsCA4OX2vGq7Q+tJcB1Rvq7w22hepHg2/0wnoQV/VEagKhSUxBGBPzBNOMCfUM4a1luJToiFniEe0qRQhbSDbmMZ9Mu2Ywwtcs0gyh+2Zne7M+wKhBYw+a1jZsqpGfv/nINz/7Ir9qsCeoPmi6G0u1HXDXniEbaIaoiJPIMkwRsrGfZpzEMNC2WSaQp6wqNze2W7dRT5pkwG+zT1x24Z/ZB9VHRWcqDkkRd4qUHeb9JM1VmrLzf1BLpGXSankdlUhRIvsU8gAfNgkV5+YM1A8lvzp+wa93E7G3uI+W6nke2Yvx3HgXeHN94LLs6b2j93Yxh9QqCdW+jIyXmuQC5V3Hz66f5DjoyKkoKKyXifboGE7FMhDV2UywqiamyRK8pmvLZZquBi1RoUqaQeVlX/WQGx+/povoSb0ADfJpnJu/F9GC2qtlHcxTYqUSoRSQ4JP3588IpZhzfgJWvHhdzUaAVpo0iT3Vy3nUE0zbtExfTf8inUQpkssNcvoUaJkrWpFSJfL7T+YT5geKhXWUJpFQoF80aGptupUHjUzmQ2bxEITBoGpPqrK8YDCLeSRRjBijTfSXStZ7GUmZMZA6gx80zzTE0fBsosgq5vOohcVgbISjWwCH4SS3vx8mI6acxrOtBqadYXhy2KNe42S1xNjGItPyfW7O833DnsCdUo40lf31lSKU8h3T1jLcOromijRikhjOuYmeQbz5+1R8wcxhXkesIMB8/vI9SW/F/NMVnm098Pi8wcdCAKQX952oMoALBJXP6wxoKGQxZfCiuez46dXzwg7RKnGaCg5DSTsUdKeC0FtijgXVLyJjF7ZNAD3/nNepfDlZRqGWfZyTcFIZZf2ZJIyEBdxLKC/XpJ4EtNExpzTl4yggrchdkkmEXSBWilDn43wUwFKMbTNjqDDCtAD8hSK6KDIVA2bjiUWU5I95H4BxBhDm4/aSHZKPo9JJ7pNev4gXFYYOXi/x18or3EGjBzkQB7UhXHUUmVmUCjn+3mU2mwaSsHzMlARsn1bAhAKGa/0pyHOuc53rXOc617nO9RnWZ/04Ewto/2zCbCbKPBVXKjGNlvi+wXaJ+qNEwPl6ndJOO8VwLQ12KIXG7yRYYIkYrO7h+aZC2TzpG9dIQklliKgyMryRqFN3WGUGKcdNak+m8Cp8o2Cm7teBVETS4DAHw4cfrqi+vqf8Qj5gOG3RXhJCph8KuhuDavxCcdY6oYrIZtNjTWCYHFNaH/rHHTkeEChyg6xzQ61fAC6V6KWDEe17NIb6Q36o76D+oOhTyXEwKBdRJq7695nCraXZSTZPx/MxmtpCmtBpbc4pIz7/HFuDaRXVW0v8aDB9llTkfehfRdL1xMVVS2ECT33Nvq1kMr5o81lGxGnnMWVgUw8cRokLCVFkK9ZEduUAGzg0JcMky36cbJaqaEJQTCdhr8xpBNrP0hJhocxNrsmT5KSyy38C08l5D8Xa6Oswy4TUAmzIVHlm4OTISieNsBmEYYFikXm8rCXW0aZP9OlkjbzyrE1ajsT0tVo+L1qJ2U0W4qwM8C/2I18fsVyn3bPEaF4TphPwo3xKyz5EJ9fRwrxwSqIdy1k+kL9ryhKqvBaX6E0lxzllFsscf6mnWYufJ+NK4ceSaeelAc+NOpOAZuHg0DkFxRvxNJF9EImLb8LCYNCe5TwHVfLW7rjetZQm8PriyG+vSnx0ayqLgtgEzE4O1BQkmSN1spaKj4b0QbxlRCYQF3mEnAeNbTWhFuB0BjUWBotn8beZPXbmdQECsKosP1GzNOlFWsnV5YmbpsPpwNYNFCbwY7xEvSvl88gT/zpKXz031y6uvhkuoHXiatsxesPr7ZGfbZ7QGXkYo6UyE7WdGCrLO73lREWcI5fn85HlHmqSa2ZhJeR1Cnnf0xopvGxfkcGSKR90BeTtUzbJvcbIhaCCmvG7BYRNCuJFXtRBGD+pzOyzXHrIkcV+9SMymeHiB9kwn695kbF86l9iiwBFQGc5mjFREmIAPxlhTYBIfIKSvx1zAlCW9ui8f6HK10lafWrMWNAfLHETln0gX+cgoGIsEqGG7ib7WsRVQhYqxemrtERun+ufv2aGxVxnpsW5znWuP7ZmpsWZYXGuc0l91mAFNlJd9YvZI4g8QZtIyLINd0oUh0go8wOkAtfKRNM34JsEbm1sVFKomDB9on6rGa+F0q09y0ReXOQ0MSJ6558Epp1dPCNUbuRNNs3Uo8IMemkQpp1hvAnSPHpF+aPlO3fD9krAirGWSXvxBNvfwfhs8Y1dpAbJypT84KVhUkcxBSyOL8fhc4Mn3grJCmtiNp8cbwJp46l3A8ZEhsEyuYJxkM6yfAC3l1HoOMp3R8dixrfGsEZSmVCVdF3zMVKdyQ3n3JQpwtWL36sCU2nRHwSo0OGFxwjgXnVcbju0StwfNnT7Cr23cp7mIaeT45B2HteMWBs5dSWPj1kTFBVp0uja0zYFdTFRWo8P6wR4HM0aN9mbhTUB0szELJNQeRIMrI1PISCXGRLuKM1l+0YtE82UQYxinzBjkqa+WBu2UMkYdroMpG3Aj5ri40xhWE+lnhRMGUQweTo+H+d58h/m90hzNL/fN2kxJlyWRpHWafaL9yU7G8ayTIKTyQ1WEJmQbYVWX+7lmguF7NM8FY9GpCDTVjHl0zBeRsyosEeVgb7snTAzJ0wCr1ZvhZiZBfNamw0dFRSTYkyW2MSliaWIa7yuzwamUeQvgJjR2kTw5hNZy3yIdacYDiUHG6l2RzZu5OdffeThuubw2OSTqdhet/zs6okiAwJXruMU5Hr5m4c3vP9wgf5QYE8a2xrMIEaNcg4Rf5xWwNHZyyAuayF7TWiV/W+EjaVvxcfAmsjYOhgMupcoy7T1uEbAk7um5bY6EfNe3dQtp13B/r5c2BvRKOJuotqMeX1msCafB+cC1kSuqo6QNJXxPI41fTZc6LzDKvHh2NiRq7qna8uVdWAAm7CVR5vI1DnCpIltju/MGAIKUpFIKqHH9VqMVRTwpAgiIxpeAKMAOlKUnlR5JkqRoUx57c2ARxG5ebNnmBztvhL2TVrZJfM1BIhcxc/gQT4GJwGl52s+GQGKQr/+/ZiqsJiHkpSAuBmgIBvAzmBickn8PZboUjHd1F58J2Kp6G/lGimessnns/i6jFdZwvjCnHleK8klfA3HX6SFuTSvpVgH3KuO4V3Nuc51rnOd61znOtfnXJ83WAFMoyRVLBWlGVHbyHBtUFHj2vQJTVcFKJ+TNIuZPh+qF69HhZ4SxfMLWjGsEgpA9QozGPxWwW6Cy4mhzJPgUbj6ZhBQpP4YqT+Ca7PXglPsf2bo76RptCcFvys4zMwNteAhVI+J4iCNrs/bmLRMZ31TyfS+lQdvPc1T2NwoBnkIn6f19pSWp/reJpRJaJ2IUVGWnhgM00WeFvYa2yOT4kFo1C9NQufjGa3BbzTTTVimvPMxmGnuppeJcasMITve692E2k2MUaE7JU34NrC9OwGwrQZGb7h/3MGTo7o32E4kNr7JjYeTSWx90VOXo8g8nmoBSljlGTwbOltyqgP1bbc2aV7jByueC0om9X6T1sam08QqkqooE9J5QjwvtwTp3mBPCj3Ke8brtHgA6FHhrKSVANkX5UWShwYa4MJzfXNEqcRDcYHe23WteQUpyXfk74+LF0nehgyoRJWZH/N0nryO8uckXngsmBWMmCtmz4DlTQjgZVqZPhfP63uGi9y8WQhONPIqgPEpT/5nPjyEP+vxkyZ+dJQf9cKumI9nykaDuhdzw5k5oLNRqYoCgESX8nqSSXXMprqpzikmMRGazJwY1DJpFkmFgm5esyyGlSBsBSZN1xb0tUWrxL+7/R32VeTbV9cAtL6gshOXrqM2E1+XD/x5+Z5KSRf6dN3w65++5q+PX/C7wzXPXUXXFUzPgmwVH2WdmF72L5Qic5oByNhEKIPIA1zE1RN/effAZSkb7aPmx9MFw2Q5HGu0iWybHpOBAmcCrS84TCW9txiVqIqJ55sRvpNt0FFAq9cXR0wGeGdJB0AIGg88dA3OBJ77iv2pYuxnLQrY0rNtBi6qAWcCrvCEmUmQNKYMNM3Azablqa0ZJkuXfXJiZz4B0rCJWPoFjFBFEECq9BTO0/WOGAwhgxbGRi42PbWbuHcbuueKtLcCsmVWh7kY+WInF9zv9BWHjxth3szX7ny9mATlev7nxBPiml6TrPy3msD69ZpLnZXPUevlMr+aIN/35dro7/K2zQkyCmKdoBdZnntIRKNofzkx2HzfiuIhpCe51+luXvN5E10iVolYRMq7br2HZ3bhphSD2ffnYf6fTP0+0+K/VGf2xbnOda6X9S+JYXH27TjXf630H/6Vc53rXOc617nOda5znetc5zrXuc51rn+++ryZFUGR3pXYaR1tLVPmJtC/UkyXCnvUnxgCSlpINpczMu3y21likSf/RuGOieJZ/C6m7YupR8rUdC/0B58cqZYIPBDmA0gyST9abKdo7uMyxa7f9piuYD9axkvRoBdPimhlgikpFUKlt0PCjGCHiO1nmUl64YsgSSZJrZPkQWlCKWwFnX0MVFz3DcDuDbHVtAdHMgmzmyTdYyfa7Glw2FaOQfmUiKd13yFbRQT53GkLrTdMOy2ac2SSPWuzi8M8ldf4Ro7NcKuJlx4aT7oUendVTEts6NOxoX+oKN5b3FFRf5DzOl6Bv5LfMbsJV3judie0SvSjQ02rmd2cmmF6hW4hdjLlNXVYdiLlqa622QC1BpMZIsFrbOGpqwmtElMwaB2XNJIQNcfLiv7k6O+E9m9+cWQ+zMFrupMjVA49CmXdjCtlPTrwu0S1Hbise940B751nnflJSFr6KNX6MwUad4K4yc6tVC+yR4R0cy+Bgp7YrkeJB1kXboq5HOXJ8XixyEsEG2E+aEH8YcAYWnYE5jM2hmuFaGWCE2YmRnit+GOInvRPqHCGgd8cyWGMPd6RzxUmFaRNmlhigSrIEtQdFplJ7PpJEomzNGt/hXmhVxnvFQiK3BRpuwuSjLHzHDxv2ccquU+sXiTBDGHjDge3IZj4ZmuDF+5J95cSgrNIVR8GHf80F1gdWSMlj467pxM8f9N+R1/Wbzjf938HR/vtjyFhnu/41ftawD++vENHx53tCcnTAKbKLcDm1KYGa+3RzZOJB+V8dyVR35RfeT7QaJ8977G6khlJu53WwodKIxnPwprYQiWH04Nx7bEDxZlEhe7jtubI/et3BjUqLA2YnSkthOF9kzBrCyLriIEzceHLcokwslhng1umOU0EKqCx03JcTey3fRcb1uesiZpGi11PXKzaXnTHLguWw5Txdvs7XCyFWmfU1y8AqNwu3HxfohREYOhLkdeb4+EneYwlBy6zAzRidpNfLl95qLs+dZesZ92spbzNW9M5DQVvKqPfHmx5zejZWwLUr6eEtkod9JiLJpZFrMyLKmUmSLihbPG7s73XvG80J6FBTSbn4Jcg9Gt70tFlH/L7BM1KmId0a0mOs32d1B/SHRfGbiStdDZhD0Y2a4bT+wM6aAXOU+yIu2iFKaJ0RGnI9tC1k+hA79+vKEsV5+Nc53rXOc617nOda7PsT5rsEIFRfGkcyMu/zZHP46lIl55kolMg1mM91SWRtijElBgNiUrZsPARFICbrhWpBPKZxnEDIjotDSDphM5yLRVi6GgPEyKnGD8ycQxOUJpGC/lPVe/0lT3E9sfAtOzpn2tSRrKpyzRuIHxIorGvNC44/rwC7m58uICL3RkiSkNmRYfSrXISLACmojJYlrkBcVjNr8LMDUwvNLErcc0Gay40pjOyrHqoxgSrioSkVh4sJ2AKCpo+lu9gD68MAH0lcId5KE8S/xxJ033xuG3keJNS1VMTMHw/CweAeZtyeajmIzqMeGSUCy0AAAgAElEQVS6RHejGW89xbUgT2U5YVRiihofjMSp1gGVvQy0SaQI08mhW0lfUJ0hZjBCacS0VKXcqCeUXiNMbSO/V9hAYT0KqN3Exoo+IaKwN4E+ODrv+Hhq+PPrB/ogiyolhU+ax9c1KSnavqB9rD5JBsBFKhuYgqzPN82BEPUSqwkwtI5xY2jeOolN7FfgbG6UYgF+FG28GdbX/bQ2/ClLoMyYFpPPUGazwYGsP5L3z9eTHtffP32paL+IpK3HZO8RrSMhaKbBMD1aikeNyUakPts9aJWorOfq6sR+W9D8qJfvn3cibGb9RwYqTgqXAbJkwG9YFp4ZRJaipTfDDDoDjjnhZhPlnpCv6VDk63VJJBFw0/h5HxUWiKNhCjVjFfi/i1/wm+0tt6VsxBAsH/sN33y4IWWzROsChZMP+Z+/+B3/avOWn7kHXtk9r+yer9wjvyzfAfDvdr/hmzeveJpqnIpEFFezIy9w7U40WffiVCAmRRtLftveACJDKXTg0vV81TwDcPAlU0at7o8b9o8NqrXo3NQ+R8XXP3nAfC2Zxu3oMCrx4bihtIFtOTBFzbYYl/O07yr6g6w992SW2Nn5POhRkVrF1Bket45Xv3hLdSnHYPCW0nq0SrS+4FV5pLETOt+4PpjAExvSYFCjGFlWtXhfADy2NX2nCVFTmMCr8plDWbKvBJB5f9zy2NY0buSuOlHdTvx/o2V4nDV84EfD797dsL8s+WJ34PbixN4FhtLl68GQjhZ71KRJEwsx0Fw8XUwi6eyHohGJYYRkMtiQDZjFwFgt5jGrueUcVSqAmvIKtZ0gX84pKap6Yrow9NcGdyzZfhfZfmM4/FW+L209voiozmArj2pGprIgHPN9RSFgz6R5eN6gdaQoAv0c1Rs0x28vSJsXRhfn+pOvP0Yq8sfWfwtJyX9t+84SlnOd679d/b5E4r9Uvy+b+KeWkPwx2/CHqvnf/58/+nP+Kb7v5fee6/OuzxqsgGw2qT/V6KuITNibgHERVQR8IbsavRLTt0KiN2MZZWqWNfzBzYwLxdSLu78ZxbdhddUXw75oBUTQA7ioF7AiVBkEsQl72zF9HZluHO5O9Ofvbhp235Rc/92EO04k5eju9Iv4VPBvJsbGMF1o3FH8Hxa/CICosD3Msam+enEMcmqEiqKN900iNJHUhMXPwZ0MxSGJn0QHKml6LGTWgd5MDK8UKEMyGjMKMBLd2vwqnzCjwbWJ8lmO2+KrUICvBZwIr4UNUN+nxYegfBQ2yDgY+qKi1yWqNZT3sn3VPRRHmdADDBeK9stEedPNQ3+O+5rUzsYEYt5pK0+Rp9XWRAobGHeG06nCH5yY5w3zlFX2A5MISUmSiU6EnEKhTcL3FqUTKsdhltXEppSdsDry55f3fL15xBB52jU8TxVtL126VomrsuMXrx+4ci3HUPLru7uleTtOhURADo6Pk8VHzWXZU1nPMU+btY7QgNkNdK8vcAcBcGw3u33KeY6jwvTCkkhmTVWZAa3ZB0JlLwgVZ98PFgNZHeR3tX+xlrRcC/21ov1ZoHjVUpcTZW7SjY70k2UKhqOr6QuH6QVA9Bns+fBxR1F5mmogXHvCxwLbszAvYmZGRcsSgys+JytDRg/i3aGCTLVtmxa2lO1km0Ml4Mu4M8SC1Q+iyNHGOWWDJKDOfD3NBovJgG0Nvtbc9zd8MNerIWwU3wP7aFFRWB8qwJzU+398ueP/vPpLvrh95uvdI1eu48q1fFFIjO6Vafm3zW+p9ESjBiIaTeStFwTzIWyZ8kFvY8Hb4ZLftjf8zfs3sg9R45znvtpwUfZ03rHvS/pRTnT7XKOf7WIgqkdIDwWH64L/4fa9fEZS/HC65Lff33KIiv12lLjT7FNTWc/oPMfsJ6Iy2PjS1wQE7LEniKPjx9sdv7h+BGDnBg5TyfePl6Sk+LDdcFu3i++G0ZHSeVnzXUFMiqaYFkbAcSgYdOSwr/lWJfyFRqu0MJmObYm/r/mPzzUfbg/81c0HfnH3wG+4YTgK+2KOQH58KOi/cvzk8oBpOrq8Xn2QiNvUCXhJUqSYVg+XJGswRS1eKWG9381rabm/zm7CL0l3RlgPsZS/DeW9YaBYgANbeXaNXOMA3x1fUz5qdt9GQgZU+leGVEbsUeNVCRcTtvFLkpI6WuxBo46G9FSTFHRFos2+HSoodr8R8Phc5zrXuc51rnOd63OuzxqsSAaGu/Cp80ZkcZhPg8Z7hS7DMgFTFhKR0OSHUptgWs0TU52Im4BXhiEKa0FP0tDMpvR40IjxZlQScacCnyRJKAugCBeGspmwFz0XeYKoLo/8cHdFcShp3nuadxO+LpbpnOkUtgyoeiJGzdg4maTNpcUMkkni+3SrZT/m4eAoE3J7VEtEYMqGmvNDuW8StlUkJQ1usZeYy7GSB2ZVe/TNQF9Zpo1bjAGjXZvcOZGifqcpH5IkHpzy50dFdIrkEtO1x1/zSWKKO8mU0p1AfyuMAdtCcch07kGm4aGQWMzj1+B/OtDYyOm9jNmrHyz1+yTskI2i/dISXo/kgTuTSqR6pCmmPKkWKYg5fJoykGxaTBijWyU2KSiqgzR/c2RoWydOWV6QXGL/E5ngGh3ZuoG//fCG05OMUbUL7C9LuITSeGoz8Yvtw3Iah2j49njN81NDGg39seB5W+Ocp+/W7FLrvIACf9EzPDumD5bycaWlm17iMuf4S98oxl1eC0Omro/Cxok2T4FfNFjRqQw+SRLObEIL0vz7BrovIpsvD2zKkfhCVzIFg8+skKoZGU3C5+tP5bWmf6gY6sh0Z3CbkeHWUr/TS7Rn9KCixjdpkRbN517Ws6SthHKVI8xrEGTNmCmhn2YjWsW4Vcvv+42iT8K+mk0R9fSCKTWzLDwwCHsgnkwGNOxynBeD2ZRTfua4V6B4tviN5cermm8vX4GLlBcDX90IC+JVfeTnzQM39sTW9FRqYkqG70ZhTvzt4Q0+syQ67/hw2nBsS6ZDdoFM0Cc42A0/loHY2U/uCeYoMqSkwe8iILKBp6cNTztZjzeZJUJnsEfDOGpwkft8IV80PTGJSWVoLeMcOTxHkub4TD1Ko68nOPyw430hB6Kyno+nhuFbiYF5ZxseXnd8fSdgRmk8P9s94Tea1hc8D5WwOQZhRoQ53vOh4HnveN5usKWnrHJc7FNF/dagouH5xvE3f6n5n958T7pVfBPlOE6HErdXlA+G1u/48OeRi2rAGlnQpfWEC8Vh1KSDXSUVuVRQWeahMTlNJrq0xO+GKjMxbGZjREkPmdksehTQOWYWSvM24Y5mAQ6mG03YtdzVwjo5/rzg8HjD9d/AxTeZyfWkGa80xR6SNgw3hv7LCZ1Zb9GklXkkZCRhKs3XtBIwOL6Q/Z3rX1b9U7I0/qHfd2ZbnOtc/3z19zER/ikZCv/Y+u+1LX/f9/59bJR/rjozPv74+qzBCnTCXo2kpJbIzBQU0RlJb/AaekXqzeIWTyH03lkjD6zRlIimOFWJWAdGq/G1SEJmxgBk5oYH5XJzUMsUbZ7UKq/QSRrg8FTQbwzlduB+lIf4n94+8a9++o7/9K+/ZrxwXP7a406JqZHvsD20ncU1E9YG2ED0a/SptgltAilqUlKEwq7IAcBeJqwqkR94FXqwhMqs2xglcjJUwgzRE7ijQsbPMF1oeNWzvW3pmoKxtzCu20AVMEVE68hxU+FrQ/VxBUz0JF4HyiumK8Xm9Ylh4zh1suTMk5V0hE5RPIM7pCWpAiRi1TeSauKbBH/WUrnA6b5h8418xsVvIs07YTlMO8nO7HyxSBgIcKpqDpmmziST1OJBr+cxgprTL3KDPDfDehIARU9AXiPTRq+u/AW0p0v+424rzelmZHzb4NrsWbIxPCVFPzrebbZU1nNdtoy5AW7sSDc50mhQnUZFQz8Y+iKu6QVRERtFDJov757oLh2PlxvGx9w9RbCtxrTCtIkWpl1imn09TgJomV7YOcmBryRNAED3iuRilgjJmkkmYfI+xDISm8DmruVue+I0FnSjI2SRv5/M8rPWiboZUJuEArqcImG6CtsZBl2iboRd4Q8FhfTx2FbYEcorYjEDLpCy9MN2M8NJQBhJ0njBNMpAS/UcUSFRPkfMqBewwx9FphQqkYQkI4kNM3AXywQ5SWWOiTRBwB+TARWVpPmLTkDSUK3vB/HAMaMAjfGjJVrwjeOba2HZ/LoK/PvtTylLj1KJJjf4o5fr7XCs11SN0QiA6hXqRfKMziBk0paik9dn+cKaFCPNtLcB3WnSY8F/MMLOuNi1jN4KSyCC7jQMmk4JWOC9xtqItQHVAA0oHdGzB0y+zwZv8EeHOWns3vD+vbBDlI3EzlI9aWGdBc0QG36bQcHdtuPV5sRF0XNTtmiV+PbpimGYs34VKcl5KD8YkjX4uuC0zSyjzBpxR3BHzYErvtueuCw6LrcCBD94g68t1YOi+qA5NDuGa0dRyD1Aq4RRic1NR1cV677NLJ/REKxGP5k1LtStIC0qr7dR0kLUpAV0PawyEXuS1KLkBCSsP6TF86ILhueLhue651V15Jc39/z7X9bs+4aL38h+NvcirdMTuC7SvFc8eUf303xPsC9kWmGVd0nakNzDxku1yLDOda5znetc5zrXuT7X+rzBCpVQKhHDC1RfgSoCyWsIMh3VnV4eNsMWidg0SQCNkNkHeUppOiWSgCqQmkC6HBl7i2rNYnBm8vRVhewX4LJh3wx45MkwSeH2mnTSjAe7PLB+0zlu7w6Uv9zzfNkQnaO6Xyd8KoC9d0yXGsqA0kn2J29jAEJysg9K9gPFOgGFheZePqSF1h8dizFjdGtTSJnBigNLAzkeNS0V0xcBYyKq8vjgFu+PqGTCpzWY64E+lNjWfDpt3ktjGAtL25Q024EvbuUL7i83dPsK89GhosT4TZWwOyBHlG4S03VAbydeXx15+/01zTeO3beZtv7oBWAojcS8PkjDOPsh2DaRtGK4KghlPgZlonyej1HKHg2zXCY3LGY9j7GQRlRNORJUrWaToVBEowmdsD/G1mJP65g2uQRe0z3U9G2B0omHbcOUG1RngzT0Ss59QuI7U/ZUmE92ai3TpOEGfnbxyKvNkfurzfI97VAw9I5hNGgX0Sbwk0uZoh+6ihgV42iJk0bpRLMdeL3LppfHjRgu5ubZqITRkYeTdDrWBGrnsTrSTY79qWIaLHHMBymze5JORJMIheei6VEqLWDFtElyzXQa31ts7RluLDYDIuVJXldBMdwIODVdSjQjgDkZij2YLsFOEarEeMkimxov5GCNjwZ3SrhTWl4DcF2Ce5GJ+EaMaJNeJU2hSYTZaNSzMKUE3MgfolZAK2kxlV1PkjTQM9ghLBawvcK2WXKkDckWTFpeeyyk4ZxNQFVUWD+DJWoxCg7NKjVQITMbyIDomIE2WIxDibmRriNxFzBPlvSDgBGPVSmgrc2ymJjlZXkbp7FisuK1o4uAUmB0WmRXznkBLFygB4K2Iivbrx4tOktHooPkE+6g8EGYHQ/bgsOuZtMMNOUo0qHHBpXvKcklVO1Jm4i615hWQKUpgzjTZaR/E4hOUz4oNt9pfnXzhjc/eVqYE0Xp6e8CKImKLe4N01gzZuNggsI0npvLE3UxodQKxAB0oyNGRWdq/E6LiXMZUZnVkLL/kWnVYlQr7KZ8muZ7eIKpTvSvRDIzA2vuqJg+lnyrrrmtTuzswF+8uec/ccdjKddc/U6AB18rktY0957dbxXJynHuv5gYryKhVst2EGUNAcRSAMmweakbPNe5znWuc53rXHP992ad/EuKpv3H1ucNVkTFdHJrsw7SULiUJ5BiDKgG0DPlv9fiMZkboVnzPxMT5gbAY0jFhCs90QW8c/giN+qdxrbyXdKMywP+bF45TzlVTsMwrVom3wD+UPLxYHnzy3uuft7xQ3mN/7tyabBUBLdXqGAIlUg8dFQLLXkGSkIlTQFaHOhTfMGu0NLAJCNNnnuKkiKi5TP6K0MoZF+TEtmFHWQqDdCfDHrStOOW2ESSStijoXiaqfiaUBtClQjbIP6FJct5WPb9JHT5vqs4/dzSfC1MiG09oHXiBPQ4YiESjNlnwG8DlJHmquN60/HU1tS/Kdh9Gxcvg/a1ZbjSjDuZMBZP4mOgs8+FayO2jdQfNVMjBqS+VsvrM7AiBpVKkjNeUMKjy4CFUdgpZf8G8JlePW1yo1vl468T/iJKygDgtiN+sDAZORdBcQRiP9P9M1PF5ffYRJrZK4tUXqGi+Gk8dxWNG7kqu0XHr0kUlx6buyGtEsep5E0tKRaPY4PNho69d4zRcFV0fFWLl8L39RU+6eX9pfFM0XBbnZbP248VH04bnp42xJNDTWsah4p52m9FYtLtK0rnlyYQINxOhEGLqWJQlNVEe6XxD9JEl09Z6nGCaSfrgLuBIhudTltL91wJoJfXdHw9LIkpZJ+A8cZgWo07aornlw2iABG2A5LcE8LqyUhIGRRQKXtmCPCQDC9YNEnYKZOkkYQqCSNjNp/Umb0S1vfMZowgvjICRGT5yCJxWj1eVGBhJglzg0/9IhJyXUcIpazH+V4jv5vTYFqNN8DGEwuRDIDIC0KZGF8FUhXlvjmaRb5m9uva8xtLVDBUVu6ngDIRpbMZbQLdeFIRSRnsUBkUnS7Tui0HvbAOUqfwJ8Ozq3iagaiDwR7l9VAnPHI9DDdpAYVnEDeVEXcxMl4Y/Kag/lFT/bbgXbimuBDxV4oKtfGMNpE+WkwvgHHI3h4qKsJgOBaeTTUuh7bMHhJKJbRKy2s+aErnKTIY8v55S/ywxZ7UJ6bDM4shGQHSYpGIu8DxQiSGMzBnWoV70oSx5m+qN/zFzUeuq5b/8aff86vmDoCntzvs3hDKiOkV/jeW6iGx+U6+w9cWf+VJt4GpteIv4lcZSDJyTaTyDFac61znOte5znWuz7vODlznOte5znWuc53rXOc617nOda5znetPqj57ZoU6Zb+GeRIdFDHELMOQSVe0q75djNAyuyKtlO+Xpbzo5KfCMGqHnqNIczJANInJakw/R99FmT5m/XPIRnREhe40RgudeYkN3YMZDB9ud/z89QM//eKBb8MtKkfT2ZPCtnlSO2VtfJIEAmBlaEwqJyAsDHAgTze38p5WK0IlrALbJ5E+5N+RiMjMQuhSNjLMdGcNZtQUz4rxygjLIED5lFkJ4+zXIWZw0zbJtDqfB61E0mHGRPUY2bxLmMFyP97KL1yNaJtQOhF2gb7ORpZlpnPvRpwLbKuBBLTfbbn9XlIwhkvB2J5/Cf7LgXIz0veW6ZsKt1eLcaOvNJUC2yWKUyIOCjsourvst9DIPoRCqNPR5rSJdeCKCjL1jkF8NKYLkTUAhG0UingpsiNlI9pGyhdpJPsuy3UyjT+2FvMs57l4Et37cBvE5LOIspaiWtJpUkQm4CbRtiU/ckHfuMXkMibFl9tn7sojt+7EEC3fqSu64JZ9uCo6aj2iVeLdsMNHw4+9+Ay0vmCMhs47QtQYHZmCYePkIBgd+dhueHrekB5K7JC9Hfy61pJZjSvV3vJsGjm3mV3hmhHqzIIyIjOpNiP9K9lG2xlZvCqvKwvNZqDKCQ5jadj/3GQTAPnespkIhawVV3i0TsRLxTQZurZgfLAL68DtFe64pouoIHHEeiZm9CqnPGS/EptQ2TthXs/JJaJXixdLtEokHZk5odLqgTInEkm85bqOljhfxzIFn1+fryf5j/yzWqUus8wjGvHXQCeiUgslbGZ4mT7HzR403hgo42JcWeyFpeNbTZxjLVX6RAJnRsQH5STMjlCKxGmuWEa8yyaslcQEpzmW86UcD0hKE+r0Yh8VRoE+KJLSC0tm2cdJPFbCNpG+6BkHIxKVmaSTo4avrk509USrNzQ/aoq3lmmW25QRXMRuJiYg7o2YgY4vPIcGRfdYM24sWkdiMBgrG6lUwtrIn99+pLEjGmFazNdb7y3PcSsSnpnlYxPjV3khRCgvBioncccpJ548d0LlOb7dUn9vcSdFly74u6j4i7uP/LR54idfHQB4eNXw7eGKwgT2fcnDxRUXf2vZvJU7/OY7zdEa2HrSZiIFLVLIeRe9Qp/Mp/HA5zrXuc51rnOd60+u/pAc5SwT+dzBCsWq33jx8K9HnR/u5aE6VGmhrYubvTz5zyAAsJptusyEzgZ0aXKEQoAIlRtpykByEe/MQs1GJ0kdQR6o06wfLgyx1sRWL+7sthUKMT9UfKuuubpo2d2e6GrpCqbGkR5tllKoNeIx6+c95HQK0bwn92m8YCojqgqMlWhBxhtx77etXgwDoxNqum2lmRD/BYW6kgYyWvFm2LyLuFbja5FQzIiI7SWBIR7B9prj1zkdJW+jbVVODBGgyJ0i2+/CQgnvXlX4bUIZCJso/g6Ayr4bzgW0jpyGguPHhu3vDLaPTBtN+yZT33/R8mevH7Aq8tA13PeW8dJgu1XH3z8byseEbddjNl7INo7X2VhyXk5TBjpyY6O9+FSMFzBeKaZNIlx43EVu5JOS5ZMUodWkoAhBMeU1OamEao1Q4jsx7DMHS/NjpuXvZ+BHwCDfGGl+6gjbOcc2kbRGmUjwmuOhom1fdI9AOzr224qLomcMloe+4d3TbjmOV03Hxo1clh0fui3HoeTUy4nSOjGOBj9aUlBizZCg3OR9NJFpMsTOorO0CtRy3KRJXj1gdK8x70qRKcw09DLgKo9y0sn3g6MsJ7Y/FanKwezwjcV02RdEf+oi35Qj9ifPPExXFI/iTxKCwticnmADSiXKSprDtpo4uYoZg5xOjuKDoTisEo+X6J4ZQD3o7Osi94y5cZ+vKz9ouSY6lT0xFGZcga3oZi8bMsiQZRqzd4YEdBCcYtqtwMQCUGTjxmQEkEjZV+Nloy6yNjH7VJPKCTD53qDS4n+xgA57Q6gjKZ8rX8/7q1DJZGNhtdxC53SVOfFEhRf3KuTfZjPVpEUOF8u4yEQkLkjlLp4lbclv88v5mDDIeSCthrDz5+tJEQdN/WoklJqxsMRh9UcZn0pSgk09/v/svUmvLdmV3/dbu4nmnHObd1+XmWQlq4VlSxOPPLSnNgxo5rFH+grWR/DUsAEDGhiyZgYMfQMDhkaGBUj2RHappCqymExm5mtud5poduPB2jviPEqqYlWRrEr5LIB8N+9pImLHjrix/vvfED8/Mp62+Gc582sQYmuINqsMq7GkvWeZDFmBPxkMMTREk5HBrgbNPjN2kY+bDVNj6WzASOJx0sE7TZ75WmNws0tIm3Bt4PffaMrPafbcdided3vG6HiYeu7aI9NOj+EPJXO6v6V/J+x+YjjEa95tBm6bI29bBSt+5+Ydf+fqa2I2/POH38J+mXkf7nAnHfvNO51Mx7ljvo3IJuDbgClxx9PJw/4X8mYvdalLXepSl7rUpb6H9T0HK7Jqr9Oq102i/ycJMroCmZtEWvIO9QFfH5ZlMcyrT+zRrqt9kkBGQw6qiV8sIZqkMaBt0oQMUCO28sBsrTZvYjLRZHIjRG+J26LN3hvMrFrqEDe8u2vwu2l52HTbmVBW1O2pGBiKrmqCskGW1VvDsnK/iHp8wnUzZpNJScjXBuMic7CchnLKZ0FGg3/UfTGhPMfP+pBrZvW66O7z0syEfo2PjK2UBAR9MblcVmLrfxfvz06Yt8LwwrD9LnL1lXY+7aNh2mlCw/Ezq82tZTFLPZpWmTEHx+7HjvY+M2+FeSdMt7qNfjNyf+w5nFrmUzGqvJkJShrANpHhC+H00NB+sMisYEUozIj8ZsT5yHxsYKpGkQpYgbIaktd4XHkx0XSBm25d8f/2ww1xr1Gom49Gmz2B2DXLmDRHTbhwe51r7gib74q/RNDxk2QXo9bQC9MLy1Abdld8GsrJzVFIsyzGigD3Tw2P283KJooG97Xuw3GbObRbZTRsZ3ISNdosLJ7sdWVdJqnEILKoWShAbpQ5gsmk67A03aZZ6Ui+zNswW5I42q8cTRJSW+Z7a5lv7DI/p9Jg/87rDwA8thPfXV2TP7S0HxVgO50aUrmeugauu5Hh7YFT2mEGITy3hAIO5qygi/oNgDOJ7dWwnKdwY7g318TOYafV8+XcELf7mBd/kmwLGDecgRUdjHeymNXaKSMPLKvZMelrdlKTVzfUwSxzzaixpxQj1+p/UgECpMz/6lNh8hJlfF5q4ilq3rk/89TwavaaGvVQsSdN+bGDWUxCU1u8JkrsaD22OpVimwm7ArKMlcX1KfOsjlu2ykjh2S7Mi9TklY1S78ddJlXgrYAvaTYaFZ1RIKWATnJwCo6MhmHwq31p2UF7NJhRiCfL0yvLbjfw9GbGzM0nnkMkIThHaiJNPzOfgV9pVoBZBquMEqP+K2Y8G8dZ+ObDDcZGbGE4jUc9UfnkcK9GdtuBu+2RF+2RN90eVwbp47RlP+uAbN3EPrQcg188Lz67euZf3l3hjo7Nt5ndT4XvvrjGm8RxU+4bWXjpD3gz8zh1bJuJ0w+feTroje3uXwjbbyJ2NOy/tMy/FRZ2EUCKhvnFjG3PTtylLvU3pH7dsaqXyNRLXepS/z7VLzIv/qpMi7+IsejfFFbH9xusMBnpavJHNZrT1T0ZTFmVVvPL2uiTIc9mlY4k0YbwzL0jN4mYCqMh5cU135SYiJR0xbBS9ckoWFEM/0JWdoWYpVdBmrSYekZniLPBTI7mQbCDJ+ws8Sou75UuIjYTW6v7KCoT0DfoPqXZQDCruVppAglCnMuKvmSsi/TtTLs7LZTwEA1ztBxvW6VvJ1EApn7HbHCPlvGFwZ1KXOTNKrfJUgCNwWDnAlZUNgsQr0oDe5OZXpmyim25Lk2cPybsnAtgISQvxaiujPGTwczQPAqbbxPzVpiuRQGTYsK5f9hgv2nwe6EZYbrOzC8ScqPL3b4JvNgd2V+1PF9tkHuvjX9NTYmGORhlPTlebBcAACAASURBVFSquJSoVMD4YrL5YuKz1480NjJFy8NBV1nzdy39e0P3MdM8JWKjDWttMsPGaFNqtHlNVs095119WCur9DEjE5h9JgymmJ7ql6QmE/tMdjqXJQtmUNM+/QaVZKQnWwA6bSSrZEgSmHsHCbJzzNcZcRlbUktSpyaRGlnKIlWwi+khZNHP+H6maQLeRlwBKOZgmaOuTDdt4JiEbB3bn69sn9gKw8npcRhIrWEKhg9bTTT5wdUjGz/z8+6a065DDpa495xqCkSrX/T66sDXkyP+vMc8W3JZaR43BvGJGA3WJryLdD6wbXQeGMnEN4b9pmOaDZwsZjRrQ34SeFQDTjMnvbwKIFFLoiH2yrJJXht4O61pIdmorCo5QZ4ykhQYOKfimwjuMWFHWeQVNZEkeSF0OudAt2/O0kIospQqOzGjMnMWIEGUMTFdC+OrqIBshOZDYVkA462+T4KCJ9mWa+FMfhKdMrNSX+aCrEylBYAorA+TwB9EzYZRYFKiRjmrZAWdWOU+aZtETspIoFu/U+p9dFADYIkQ71ud70kWOY8ZBXfSa2wyDScXsX1kehEXmYdEvReawRCfGlIfELPet5ByX3ZFr+MSQTKmWcFsMqSHhpSUxWZGwZXvt6PQfPbMH7x8x6v2wOvmmY2Z+Mmg8ranueOrxxv+cHzDzfaELUa4tqApRjLNi4Eh9kiytB8z9quOr+IdD7cqFfn54ZqbdqCzMw9Hvddc9wPz7+p18HS8YvszU1iEoscHhJIyZF3i9ef3bPzMn3CpS/311K8blPh1bPfXDXT8Mvt2AVsudalL/Vn1m0wx+bdt668DwPh+gxVlxU1sIpd8PTEZspDKCvACStQGNUO2cX0tnAEXpaR4U5BEwYbZkKNddc+iEpJKr14kKNO6+p1hkaEgKAW+PFSKT9qY7xISDWYC/2hWKUprYBuwNiL9v/sPV7YZAsgiWyiAzWxJoyHWxtxmps7T9jOuaLOdSbR+xl4nrEnLqnQoNO+YhcNVx/GFw5yKHtydyWb6qADHbHDPRoevxC4C5E3UBqpVOQeSOT5uF+aGHZVZMG90ldfM+vna+Jigq6TNXv1HpishbLR5r7R0/+OW6z/O+EPExMxwYzm9sZze6EP/aeeJ0dC1M+12Ytw7zLjKamTf4A+yRE5mqfGWlTavbAJrMw+HnpyF02OH/agd5eZboX+XaZ8VJXDj2coyME/qFVKb3tBp4378sqyAevBPBjtqo6xRq9UbpExBJ4StqDTAszBqqmeEZG2mzMxZlK4CHHVuuqNGK2okZvE4WXwCFHiLXVquEZnNIoWxJ6NRvm0i+oTvRxoXmcs8mYLldGgxNtN2E8YnptuE/eN1G3EEkhB7bdDnKzCj5b3VlWKRzIvuxOe3Twy7I1//6UtkNosHwjxZHmbL9s3M7fWRd48t9tliyz7GKOQmM0WD8ZHUCtYkDlORukjmbnukb2ZiMkv8aijgYjpYzKTMGHfU/Q6bcj1VXKsVYgPDZ0Gv4+JH84lFcXlvc2+YH6yCAuUOK0Hne/8x032MCog4OQN0DNOusC+KT0oWHTNYpRmgc1SCft6UX7qTSq38QYh9uQ+J+rX0H+pcKACVq/KdwmSqxzCUbQ9W/VhsVrDs3HejzNtc5qOd1A+kvp6czv/KtCIBRcYRq0QksQK9wSyyHzPLIp1xj2ZhaFT2SE1fkiS4vWFuW5WgXAViufdKSdgxo2CfLflo1vs0aveRXfE08gnjI5wxENJoFQAeVTpnJ/DP6x+HLBCTIWXhu2HHPjQ8TT1f71Vb9rjvmR5buq8973db0usJsVnZdujfJ2My7m7g6Bti62k/gMSG5zIfn+2Or13C+IRvAikJIpmX15rQ8/Pf94Rdi9sL020iR8Pp0CoIj7Kerl6OGPl3/+241KUudalLXepSl/o+1PccrGAxNqyrcwpWqPSCdLYiuC5mq0TDZoxPpKiN+mLQKejq29nHkkvFwKw8EEdWCYldVyZzaRzMaLSZlLIyahTAWOjOhdoft2k1Jgyy+DaAsj+CWNW/WzX3y8vxyGLgKWE1D63/1ihAqJ4WkKxj2LoVtCmME7GZppuXh+lcASDJOB/hNhKvDGmy5JNdGv1zJkuuRoOGBdBJhb0SserVkYGrzOHzdQztqAaTtdGWtBp+NntlHJBhuDXEtjTidqWob3+aaZ8ibh/JVtjMGX+ytB9Lk3vlmK537O8SqUu4o9Ho1TIEfl8MQyshpVMZRl1Jnne6qp9GS/zQI1G4uVdQAcDMWRkTThivNLYytiutPhtBkq4OaxykGqJOb7SL724HTs8tTEYjHE96bJIVzIHSuB31XGuTWIa/yG3qtqQYVCLazNXXa4SsBJBU5AHzSns3szatsxedv6JMijomagKqJq3hZLgfrZoqhnIeDxoXml3muHNIm8i3gdPLBr+vc02bZlMkDBodmnH3evt5l285vmr4wc0jnQv4q4n5oV2MH5mFPDTc73q27YS/GZlTi+yrZEl0QVwMaTJM0RBmh7HVNBG4OtK5QMoKAIwuMjkFnYLLnJxlmNXUFgrIkNbbRi6eENu3B3bdyBQsMRn2ewXGxGT6fsLbyPO+Z3hqkNGsxr6z+luEraV7b2ifI+6YMJO+ITnBH9ZrQ02BM7FIacycia0wXRmmK71ujp/nBaBsnqB7D81zZvO1YbpmMT8NRaZRDUZN9Yvw8okURQL4vWCHzOkzS9hoBGkFXBafDynMCZOZr84MOkdIBVBMXVajx1EW4Cv5lSVR71tmZpGR1BhQcrk3LL4eZa5biD5jJh1P++RIfSJ3cb0H28KkS0bjoo+fBl7FXo2AczFEztEoQ6809uIyuAhBx5hRlghogPk6kwfPv/74itPoVXLx1CyArgTwQejeweZr4bjviF0mbOtEgOwTZhPYvT5w7Fvs/9PTfhTM5JZ9rBNv+nyk6WeGyS/7+Pb1I8ON5+m5xzeB8X2PPZn1751x/CvzhlSvn0td6lK/VP0i8+FXwXL4izI9zt9/YVlc6lKX+ptWv2pZyi9T32+wIol6RpjM4opfVu/EJ/Jo1we4uspUG8msv3NeV/2lPueZTCyNWArKdshZoEmkyowYTaFlV8CirErXbU3alJFLo1Qe8nN5+s657K9PpFtd3UtppfOm2cJkyCcHJhN9gvnsYbRWoeyf694BXEk4kayrwynoQqYd7dIA2Kk8hJtM2DRMjqUJgfWh3/RBMRrRpIwKUrjjOnW0GRZ96K+mhWeACVkb8NRkprsCiriMOSmVuerildZctw/Bq6nnvNXGITuVQ1Qwxg2J8coSWjU/9KeMf450H4rR3I0jGzjdGaZrR7bQnnlwmBmaQ1qMDCWqLKNS96crITxqE3Lzx7EYhcbFKDVsDNPWEDYw3iqYMF+ndTV9Xg0Kl1VnWOQ6jQ/YW2W2HK469XIoZoDNfWGgDLqf1UMlWwVtYlfGyUGeWYCzZTW79kaB1dyxEQVBBm1qy6khNYIZLbHXcZTEAtg0j/rZ5ITUCOG+UUCkLEZXJky2UpJhErya2P9Owj2vjJ/2XhvumiSS7coOcR8dB9lw307MwdJ2M7P3mhgBZZUb9h83jFtH30/EYCiXKWYw679G2SLRZaIpA27gfTRc704LGGdtUjAOBeZkO9G2MzkLriSiwGooGWaLLQyNu+7IEB2NjfzR/BqArpn50e09Oz9yvPV8HLY8jw2nsVk+H6Lh6a3j8GTxT47+24w/lOtlLv4lUe9FkvVaap50kCRlJFuGF8o4iA3MvzUhBQAdnj3TjaP/Frr7hCRDamC8gfFFZclUYBT8MSvodtaIS9D9aJ4Tkq36w1yfmalWvDaLyuNEiNvEVG4q7iCETS7MjeKFcg6mBlkACXcoCS3DOpfnnUq8lHWystKq1AajUrOa2OOOQoqGvDcKjlDuEcsFXrYb19t/KqbFOckqeRNWg02boSssOKcyqNSwjsHdBLPh4d0OOTjsSdg8ntPy1DB0uobNN9B/m4mdML4of1M8IIZwskw+cn194vGLhv5n9oyhovc7CRC2HrOdMCYzznqjv9uc+FsvvuPwsmE/t/zh11/i9mfA2CTk+36ZW5e61KUudalLXepS39cyf/5bLnWpS13qUpe61KUudalLXepSl7rUpX5z9f1mVmSQWcjG8EmUQUbNKItMAlbtuC7tAcFoiIhLRUec1u8VpRGn2a6mlS4vEX3ZRvW6CLKs/mHyIkVJjVLdl20Xan5d7c4lOlGahG0ivgm6uluJGZNlDm3ZdpGzmPzpMRbaf/ZAkXDE8v1TUkkCqazGl8jFmoIAqnGXCCYKoZOiM2elXpcYx7ixKiVxutJbVyjt6Xw1cWW2LDTvyCIZqWyJ6QbCi2o4oYwRmQ3ZKUtFVwaLD0Gn+v3Y6XmuKQeVJg4wXas8IVldiXQH6N8L7SNlXzJ2TNghEx4MwwtlFiwRsJ1KB841P80+Q6Hm20kw3+o2+29HED2vydYYBZh3GkUZO5heRczttPiCxGAZB5XyMBmadxY7CP1XOhmf4zVyM9H2M9ZFnM+UoWJe5q1gT0KuHhPnsg/KlKr7n0VNCVn9IuxpPbexLTGdZ94gdlKqv98rgyV2OgeqKaPkDBFMUnlA87CyPEB9GEJf2TZKu99vLf7zI/FNMcicDfO7hube4M4SNs7nmpwMD08bYjCr4WJTpUmQk8G994ST5XhnsC6RN3qQSSxEwT2rqawZzSK/qmOWJsNjlMU/oMqeykGy7Ue+vHlg4yaciQxRV7FrssP745YQDUPQaFjdr3lZkU9ZmJJlShp3+dn2iVe94bujRsjOxdi2tRFbmBs/e3dLftDJ6J4N7mAWiYadKH4jeR0rgeFVlShldrdHPr/SuMvTa8/Htxse//SK6z8yuFMmiDC8SXBbTuZDA5Kxg6G5F9qHvBiFQrkfBAi9wR1122aSxRA2doUxYcp9NyuLItyU+d7rdWzmwiI7k8DoXM0kv/rOyKPgj5m2RPgejNFrzKxMBjKkbpVQUGKW7aD3FTsK/hmmm8KGusnkaWVzhI1GO1dDWmUaySJJWhJh4joXqzGuZJXq0UZsmYttOzMcGsxeWRU1Env5+5JgvonMn0XCtqH/Tu/V6/1SGWpmFCbpeXph8a9PnHJP961d9iWLfqd/MowvPDfXR5pyX2lt4E37zN32wD99+BGpS8RRFumYJKG9V3+US13qUn/5+usyCf1lt3+RiVzqUpf6665f2vDzH/+vf+ltfL/BClTjn/KZ0V3R3GdKPF5J0qgPzp+kfsyGXKQauVD7U1KecooCQVbTTFYDQttHUkhqaFb8CEiyaKtp1Ysih7L9XGjGtQkT/blSuGM0mDPAxNrMXGUlBQQRm6plhjZv1STSZKw7a7yA0MXVzG4ukZwY7MQSeZmrER6aUqA+HKwifYCTkI4qbwgbNdur41djCqsuHoq2vDwwk8q4ytpofcLjKSZ7OWfiJi8u/rFbNe1ILo2zEBttOswkStUGhpcKUoRe902CMN0Y/LNuyO8zzcEgUU9AcoZwu9LOa9RoPW4zVXmC7oM/ZuygdPT52pO8EIvkBCBZTSdJ1XdjFuLeLZGdSMb2mgkbJ0PzrMkJTWnO2gfL6U3PtG1JfUmuEaBJGskLBGPUpyMU+nuRdVTfjixrIkSVidSfgSWdpB5rNWocXlaAgcU7wA25eEqsPgLhVqn7CnAI9lgazVSbaFk8S/wh445weuswdwPXW0VEnI08X3Uc3m/w713xzBDCrsz/TcIeDfN9i8zq92IMpE05sDIu7UchHi3T3GmMap2rTSpGjTrXJBTsrI5LVjAk5obkE6mLRHfmUyCZOVpCNhjJWMnc+IF0Fg+7dy3PoeXjww5jI8ZkNt20vD7Njp8+3GIks2mnkuagySnn9arf88PNA1+2H3n/ZscfH18B8NPnW55OHTEaUhKG2eF84Hnwyz6m0eo9ZLDINuhtpNwzXvV7Pts+8eN24kN6ydWPNRaYFxNvXyt693zV4W1kmDzH2475o8Mehea57FzxsQg9dB+h+5DoP+TVw2Wrfh/VgDZlvcfEWwWNcp+RvdUkmlyu07yClYjeN7JPzDeCmSwmyGIA6vc6/1KjpqlLcomrxqB6D0hdIj9bbJFH9e/TAhokX7wuylyImww38+LfIB/9Arqqf8t67LXMJJpcI5D6jO/CEis9TY48G0wxs52vEukF5AJmmMFgrma+/Owj313vODTXuOMKiiSv92wzC907y3zscH/whPtiz8nsdDq/V9AtOU1bmR8axn7iswJMbdzEmBzPsePDsMVezQSXiIeaIFSuS/fX22hd6lKXutSlLnWpS/1V6/sPVsyrdwQUrbOHGjeaLYvOGUrjXFkKJbIzs/pJLFUYGktDFGVlNkjGen3ozlFWBkbxlBCfME3UVJAsagJ65qsBKABhMikKKVpylrV5KkBGLmklYhPWpeWBGdRHI0WD8xFr1SS06vGbtjQPGebJMR+9OvqLWaIQQ4+CB8X3YkmxWPwcZEnk0CZY1LugrHjON1FNQZu8gjAmkxajOf2yql0HSG1awB9tJvIC3NTzssSK1jGPQm5k9bgIsoIFTS4JHpncR6RJnF7BqexD951jPMoSszreZW3Cq7+JLfsUBCyYQQgbs6xQ+mfBTrJ4LKRGVn+QUvW/TYDdT7RDqmBIbGC+SZhR6B6F/tuMLaacAN19pv9gmLbCfK0r0fMO5ptM2Gl3k7pE9kIaNZlAomDSypwwxQegpOouq9gV0KnJD2GbsWMx6RTY/37xQhh1Nd/tDXYoc8vA8LZ0VxY1Sgy6yuv6ekGcXU+o94A/6Pm0AwwfO+K1vnh3c+Dzmyf2/cC3zS3u5w3+SQi78lU+YU8W/+yQYiiq/h+lSb5JGsP5lImjYCbDNPllLqYrTehIm6RzyiyOt8uY2Ak4GJIXNdl0aY3UbCPHQ8tP5ZaHrqf3M1s3kc6Qu5SFOVrSQ6MeMklwbxJto+M4To79xw1EYb8JjNeOTTPjCpiQsjBFy4dhizOJnR35ve47XhRjgd/dvOcp9MzZMCXHnCw/6B8WwGTOlse5548eXvN46ti2E8+nlh9/uAOg9YG77ZEvdk8Mv+t4lhuygbaf6Zzu4+b6md5pd/503fH4puP5sWco/jOSBHM74ZvA80+2kA39+7QwH9yQCZ0yLWIHMYCxkJo6+fRa93spyTTlflhuW7Ey3bIQryLjm0hyhorCuoP6dsRWPVKyAzusYElsRa/hbdRzX2JMs4HuQfdxvNNY2Bqtmg2Ezpyx4rKaqBbvjnOQopaZUMDaZ8zBMlu/UJpkNNiSKJQaTQtqdhNNmQfDqSHOhpgMX9w+8ZPfcgz3LbYYfdZ44BrL3N4Lh/ueu7dPpDcK7k3zFjuo4XIWwT0Zjm7LfqevNybyx/tXPAw9H/YbmnYmN4G512s2zIb5LQwuwX//bx7fpS51qX8/6t/GvLiwLS51qUv9+1bfe7Cipkgs/500trSu1GfJ2nBVnCEoy6Amc1TDyOWhNX968881cjSY5bU0WYxPyoRwChLkZKiOf9nqA7nYYhZHwtizPyCSMedSk4SuDNfXm7QknOSo3x+jWZM6TFITvtJ062uQ4rqi78rxVYAj+0zMK7CQXQZ/FndQne/qWJb4Pv8sxfAtL4acALILkME3cWF2GJMYO6W1pygYm7Em6f5PVh/SP67dfl2Vz2115cyLyajYRDZG3+P1/JjR6nieJWFUJgFZP9O0gbzRgxpMp3R1r8yN3EY1Xp0rPUT0/EVBmkhsDfF6ZdOMR2WjmEmWSNBzTEs4a3iycP11onmOy5iGzjDtDP6UkaTxptkKodU3dMfE5puJzglhqwcyvLCMR+HwA91QvIpL0wJG00Kmdc77ZzUETY3KWmrUY20Uw1ZNN8NVIltlaWQLb7/8uBxHTIbHfcf40Kl5rGTaz7UxEsnEaIjRMOw8Mpoyd1aavkwGuzc0T4V+bzTpI5W0jm9PntPdgRebE1e3R56fHP7J4g7leuqsAkODsnzcQa+bea+v751ZxtTM4AR4lAUUmpwldzUVIitgOZ/JsCJIMrgTpAlSsMTNytBJNpMHy9O05dC1OBfxPuJtZNNocz8Vw02CaNzwLKQk7Dp9PUSzsJjSaDkODTEZdp0iX94kxuB4PHU8DS3vTju+vbrGmxIlXJbevSQCGVNO8KviuuglQguH0LBrRnIWnk8tp/segBOw33b88PU9P7x55PE/HIlJm+ZTMWa8akdCMmzcxA93D/yduxN/evuCp1EHck6G37n+iJHM/+2+4JlrsjULE8iOmWavTJ4wC7ZRcK1KHARZGnES2MI+qLdUF8F9zEwnYYiW8HpmepOgGKFufia4U9ZY47kwC44sqTIaayoMry3zdcKOFolwem3o3xVW2qjvS07lLe4oxL1nfFk1eJVFpmBIrHG99e9DkaNJUomcfxbS4BdwsMpcUgPzFoiygMUAvgnE+y0//ckrNi+PbDcjj6Oj3prr/TV1IMnSvRf8d56Hdku/VQpK2kYk2MI0ywqYvrd81Skw9XCrDJzjQw8m024nrE1st4o2Ni7ydvfM71+9u2AVl7rUpS51qUtd6ntd32+wIhfa9xlYYUalmEvUBz0zC9GyPI3KLMgk5FaUQl/lG279zirrIAMuIy6TpSRyAIyO2EWN4qt0cpfJhRpPUB+FokkByjNqfSC2EMuDfWUPmMGsEX0Y3bf6FZNRBkd94C3MECnJJeeAR625ABFiy35ntJmrdGWfMDWyTzLeR5xNy0quSGYcPfONJx9tkWWwxKsayVivbI9z/b/zZcW+0cSFKm05pO6TdBYpMhGAlI025BhNtqDIcirTw+iYSlkpXcAKo7IUGQ0yWFISxsFhet0Hs50RA7ZovVMBfFLpGPJkF9+RnJQ54rqZVNgDaSekR782vbYAK+bTRp2kgEroheYJ/JMeRHOfKYx5Dj/smHcawZr8OgYtYKZE8xiInaXZJ8ysyRoAgy2afZtJfSINQh7WWEc36DiELOQtn8p40Gsged3vCMSNAlXjXBrEdtKV9x08ZiGMbpFHAXgfaX2J/OxmUtLm7LofyrkTxtmxP3QcnxuNI82lUbwv4N2HhsMLz/Obns3VCLczcwE3dFzNJz4Y2YEZC1MDbTinm8R8pewPbVohl0hM/2QIUUh9VFaVS5DMGq/rlHFlT4IbhTRDFrOCFUY9L2QyxFmI4hkBswkcy4q5tQlnE3kXMM8OkjCNnqmAGcZkaKNOV59ISRawAGDjZ7bNxGFqOE2ebybPu/2WrlwvjY20JVp1jpYpWv5f3rLx+v2fbZ+48Sem6Ohs4GHscTat95TJEJLnG3/Nl3f3/Kdv/xVztvzT9z/i/qiAhoie98ZF7vojr9o9v739yKEAjIfQ4EzES+L37j7wR0l46nc07xWoaT8Wn4usjAczawrOUoWtUCVEqSbgVLDioFHBZlYAcN858u3MfKsn/hQVHHWnNTlHUsYVxk/3GHGDspf2fysy3YpKuzZZfYvQeWFH3Rc7QP8uKXBXzkPY5iWpKXbqg0NmiYA1Y17uOyaqD0TzuHpgVDBQGVWCmR2n3J+Bvuo/0t5bxo/XHN9OKtupoLgUAL1JhJ0Q9pb2XhhMx+FNRV71++1JmVCSChvkp3qe9o9e408HQ+oSk/GIzWy2Coy1LnDXHtiYVaZ0qUtd6v8f9Wf5XHzxT/Iv5cNxYWdc6lKX+ptU32uw4szX8Sy2TVdnVR6gq2hmZtXv5rJSXqQN6rsgpHPEI6Pa+Qi5yYs/hDnpA68dhDipBCL6pEwIcyaHCKKr91nKTlKiR8vXO31Apmi6a9SklEY++wyzWUkeQZZ4Rj3YgoHYXCQOv8AOEchzYZY0ZpXANBHbauNuJJOKlKQyA5JbwYWuCfhNhM3AadcwDp5U2BEAaXDkNpIHx5jPtl8aeddGYijgQBLS0SkwUAEZ0f+TVACkM9q4jsG68q3a9RJfavPqDWKVDSJZQSo56v6FXWmSd3q8ucTCprLqvXhnRF19zzZDWckM+eySMFllFtULop7jM4+QGnsarOHpdw3TtacvzZ0/JvxzZL6yPP6eVemNrPGuyRnmjeCGjD8lpatnpdv37+rJNEzXspgYns9zWBvDJS61GnEuDBrUa2Vej1Wi8PxHtwA8Fi8QqlQqgh0N82ELwLiJmE3AmEzXT3RNUMlS2bvOBVqrgMa+azmMVzT3BnuSM2+OTPhGOH7sOPzI4W9G5tcBU1b8mwdhumYBo6Q0ie7EcszZZca7TPthBWqq34A7Cv5JmK+E2ELcFnZLnZY2EzaUVX9ZPluv62ztQizCiBovzkIKwmiKZ0QX2d0e6W8GTvMGEQP3DQ/lO5puxvczOZlllT0Ew6FEl4pkNn5m12pDeZo8x0PHYGt8KjRNIJV7wDxbYrC8m3Qufb25pu9mGqegxtPQKtPKF+At6U1heG55127hTq/xw9QwTsXLIAvDqSEDT8eOx7HjRXdi43QyHUPD1/sbnEls/MQfvHrP++2JD290Ljx/uyF8bXEnBZMQPS+LZ41kRFSqsXg0NHkBHtoPhvZRr/XuPhN7w6GxUIxSR2eYbtVoVMo90e5kMYN1g6F5imy/gcOPLPE6ErcJs505ouyQ3Z8K9rQCFt29zoXpppzHCh47FmA6G6AyJ8otOTtIJmOK/KreuyUrk8nMCoa4o2BHv5rFOpXCNE8Z/yyMz+3i1QF6vaZW2T/ZZsIu478VuvfCiO5juCqAclCMRQ2B88JEstMqdQFDQsGzQ1znzmFs+D/HHwH/mEtd6lKXutSlLnWp72t9r8EKYG3Qf0G+oSZjhc47CYmqWUZX40sTWlfKFvdK0IfkqUgiJlkYHNXLwEyFCuyF1IhSyt0ZcgLKckjoSm9lUNQeNwqSZTFlq/tVqcbL++tiXHWtPztGYQU39Hiz+kfAKtWo5p7FG8L4VYWvEhSjS6hN1AAAIABJREFUJqBRSMCcZAFcjj7RbGZ2m4HrzcDoA4dTQxh1yuSjI0e3aLgpipLFE+NKGQcStfEzScd9wTW8Nu0ZBStMWSWvD/0pgxhZnPERiK2Oscyr6Wk9RkkULTmYwjCZkxA7Qzz3ChnsIjuRcuxqMKn7iayrm9U4sv6cBBjM4omQbWZJZXGZ6Ucj4xvH8UG/w+0ddnBMt5np80llPYNFyrGGzhA2op4Ko4JTbsj4PbSPFRXSfRtQkMUdSqM06evDy3KOLYtR5nmqi5T0A38wiwmhRNh9tSJb2aq/RvK6klvnNmgyxHzlyA72140ycxI81LnpFajr+ol5tpjB0L3X5JBlRfyjTmx/cIDj+LsZfzUyvtUvaR8c021aWSvFT8YWGYl/NEgS5hcBMzkFphLL9/sn9QEJG022GV45wjYtDWL2mbyJTJ3BTApCyrwCLnZUX5LUZHJWhpMEwU12abpjZzg2LdvNyHQ9EZ8a+q8coZi5ji8t/moizipZyUHlNNVv9tS0bLYD192Is5HGCbOzC4MlJyEES4yCiJrsQiTtSwN77HnyLTQJ49MCwC0XVJ2Ls3A4tfyz+9/iODd8vN8u8rEQLPHkYDKcaDh96PlmG9jthnI9wfHQkTNstiN/+803/O7Ne377WiVDf3J1x9fdHe6jX0wjkzsDxtrCQOsScrJ632kTrit+Dr5BsqN5hOYx0zxkxluLvCij1KlsLQRLfFafCAmygMQmGHoL7pTZ/Mxy+A8Cm7uBxkUeZp0r+WcNbsiYSQ1BU1Ma/LHINJ61yQ+b4vdSk5kqsyLoNZPa4n+EHuNiYluYI8mt9yX/zJIwlEXlSfOV0L3LtI8KbsyK9zDdaHpQyAqOKphTAJmP9R5oPjEnjd0KtoGC5X4vpHLN50mZdakAY7NtiKMCIJe61KUuVeuXTTf5q6agXJgZl7rUpX6VZf78t1zqUpe61KUudalLXepSl7rUpS51qUv95up7zayoRmrCyiZQjW9JiCgxlGZe6d+xGPBVI7VFn1xX9qVEec4ro8HM6/tqmWk1YiMb1TDXJIusK7cAycYzOUj1KiiodZSFjZF9+jSRpFCUMZksmdQblmXY8tlln31WU7Yq8TC50MJZPSKKeWVdZY3Fa4EoywqmPa5SFYB563i4tfTbicYF2jYsK7nzrIaiNdayshvqerUU2YvMggnqC5LsOkbZ60pwthmDWT0pytBUDXn9XY08lCBrTGE0C5lF2QEaq1nPtXtGZR+Cbquc98WItJRkHUszyWLQV89B9XzQDYKdzCK1qHMiG0hdRq4Trj8x7/SyClHI0dDuRposxGCJQVbzS59JbVnpL8Z9qlMX+vd6IprDpwwIW6QR85V+x/hSY3LramstO6yUcYnQvc/YUcfHTpnNt0r9N0MEI8TWEjtD6M2aoFDGdS7eErFzazTqWRRjFhhftpB1NXfzXcJOqxmpZCBl+ncz2XpS4xm+zEjxFgm9w56EsE1Ik2j7Geci021Jqfi4wx4FXgXG14Ir5pyx+mqIroh3HxOhFyQbTiKV2U9sE36jXiQ5qSyJMy8SM4kmTpx01T32uqptB1nYVPIgHKXj8EZou4kjDe29rtQD2JNnfF0kVQaaB6OypXpCDOy3DfvdrH4xxbh0kaJEwwyLh4rpg17D9eOjkGeLuXeaylMiPFd6SGFZ+EQMlh+/v2N8bpFhlW5Fl1cmVNa5y4Pj6bpG2mRkUtnYfrJ8s73mVb/nRaOT7vdu3jMFx8d2yzBa/e64srGqga1rInP5fo1WLp4xVzMnn0lfe7JRyU62We8rqFSmb2asSXy0W2IozJM7/f5n3xF6w+6rxPbrzOFHFvsi0fqA7+dyXTSYWfCHxOiFpy8dfp+X+4rfK8vMTpo4UseiyqkA/AHykU+iPz9RCVqYr9Z7sX/+NCUou8x4qyyo7jFhprQkmphJE43MWNJEGr2G3CkvzLrkhPEF5Opb4SA3mbDVbZoo+Ec93bEk/CCQxroNlVC195fVzUtd6lKXutSlLvX9ru81WIHJxCulpdcH8moAickqQ0gCgz4cgjayyReq+bmsomqWhYUanMkrKBHXmMpsWBpgU7whzExJbCibj7XpteqzcP4wW80+gzZHktVLoNKpJcvyHgQ172wCZ19PjqKUcwCX8LtpeV0kk6LBukQMa8eYoiUtJqFWmwwDSFroy65E7JkJzGyIU8th5xiuZpom4Lw2Hnkj6gHhLWlTJCslgQVYtfTerPhHjSqFVZ5iioEknwIli9milLFp8qfynXIuddwKZbvV8+QO6+vaMSn4UeURZkFEyj7kSr/Omj5yljCSWoh9iTctx1YbD5XmqL49zjCblnEXMWWMmn4ulP7E4butxoQmSP2afpKuw2IEKYPKQrI3VNJT85ixE3QfVPsfO41iPb3V74h9AgN2sDoOZb+b5zp+Kl/q7jNmzgVcyoReD9KKkBqj5qFGxzZ0qy+ERGieFeiwc0ai0tqlmMkmq+8dv9Pm3MwaPykxL9fDeGNJTujfzfTvA7HxSGyWhIawyVz/CeyDJzUw3FpMH2i6Kh9R0GX/1mKuZoLVQQ/bdY6Mt6Jj5JXybybBlmOIOyEVLwnfBoxJHKsECpCkxq/tI0jMTNdFDhPWudTdJ8xoOE4d85d6zzEhL3Gvfg92tMROG/DtVxq1uVigJPRa8ZbUlJSHs8vFlThNMxXgrXeQWBpUKRG13QeN6wydqMFkjW/tis9BE8kJxqcW997r7aMAKrnM/2y0SdZ5A3JfJBRFNicJZmf5+uM1xyvPh0YH+qoZ2bUj6RZCtOz3HenkFsBVfMKUdCTTRNJoyYNlTIoEiMn0L06cZiH2lnATkE5TV0DvW6Z8124zcCqyhputgiWHzchze4Ukx+bbjHu07Dc97cu43JeGN5FsVOs3vYDxdWTzp3bxP6mxwSagPilRf65zqV4zfq/SonlXdWvleqj3+pKqo7eO1ZdDyuezgfEWmidDk9Mi2+oeEyYY5p0wvtD7U/IKINriQ5ON7ltyGUsBMUejMrhyTbqj/mxHnWexW/fRBAXxx7uLDORSl7rUb77+qjKSWn8ZOclfZNsXucqlLvX9qF8bWCEi/xPwXwLf5Zz/TvndHfC/AL8N/Bj4r3LO9yIiwH8H/BfAEfivc87/7M/diAGu5pWFAFineu4ctBnJWYhW0GV9fZCrjARJddV3XcmvTVpsM2L0AT8VK4NzsGJZaaufrcu4lCa29N9mUj+L5Fc/B0QbGgmCiDafn9xeg+rWJZXIUJ+gWT0xxGTEKhuDJBrzmQwxrt+SkyzJHpyzKUZb9lH02bZGu7oM3czc6JQwJzVJtCfBTI4wGk5bhynsDesizoLbaGyet5E5WuaSMpELkaRq8KWkm1RyyDw6TTHJgEuktjBQ6t+OCqrU8mlp6lNd3a+u/GdxrFkywRTPiMP68WQLUHIO+JjKmshLU5na9TxJhNTr+Ger7JNoV3PL6nWRrf7bvreEoyHudPtjV1gds6F5Z7GTAiahzJ24SUgbycUvJRezy8no6qrut9A8lwSFAOMLPYaaiCKzKWwJnUcKGKxeGyRt3rPRtJLYFI+Kdm3CYlcYRbOCbMmvx6jgg/5sx4SZyyp1ASJM0dY3T5HYGcZry+mlWRo2QH1dOkA87WOkfx8xs+G0L94iW7j+yUzzZAm9MF17Qu+Zbkvj+qiA1Pwzz/jGQJOIV2nxfAk3uro/fHCYGdoHBV0WdsmzJZUI29gHbI0jrufcZ1KrppB2UKZG7AuwNes+uCFz9VXETob7azVNnK5XE1E7KLAUZgVI/THhTiBlwksqSRVTIjZqrKp+D7oP7aMauNg5Exsher0pHL4oEbaNfr6912jP0AMI8005jV4KO0vIJ4vdW9oH9ULYflOALQ+nV4bhtR6zArtmYcmYWGJIMyRvmH3LQxYe2Og+djN9M3PVThxnz9zNjFlZIedlbMK6zJT1PkwxB84u0/oAb46EO8Pba3XOrIkpIRpOUzFddRHnIs4kdo1OwB/sHvmJi7y3N8TGY6dM+tBw3Pklkaj97MiwbRnvHNzM3N7teRxf0DyYch6ksKxgeIl64ARhKokkEoXklA1WvS90DpdxLteJfxZCnwvguZ7HXEHyDOEqM7zUZJ+mxK/6Y6L/EAtgYYidssZiKwug0T5mhoNhusmkRhOt3H5l8dRre/GmqQB7+fsUegUAl3vApX4zzyOXutSlLnWpS13qV16/TmbFPwT+B+Afnf3u7wP/W875vxWRv1/++78B/nPgD8r//hPgfyz//rllbF4SLGpFShpHlVm4tMRRmr3BVIlHYUxUGQOgjWCh1WajK6BLnTW6lYlRJQ41lQIoyR76rztjXMSurnDmEm26bofAJ6vZkszC1kheI+oWI7vCtqhpIjla0mRWeckZ7VsZJOVDZ4kjS8KGQcfJgNtO0FTKtmHeO+yzxZ4E/2RIJ0MsK/LzLmKaiPe6Mtq4iBFw5VxMweFtJGbBmYQ1GWfjIkN5ti3DqSFNFrGZtp8xJq0ylWiIQbeVoihdPmrDmUujThCNYzVATUTxmWT15MyizesnqSwFAAJl4eQyRhmU6RCFJZo0ycrAKckrmMRs1ubLjGYxZHX7IicorIg8aONqT7oaTplvleWTGlFzvErJR89P6iNTBROwZCe4Q8ZEXWnPBpXslHloB517yo7IEITQlU9PEI0QNkLsFRjQ5qgyXzK500ZZJqPRv5Oma9T5bQelyc8bt5rA1hJlGHQfNXVhuBOGN0XyUw9BsgIC1tC/E/oPSSNay3dNV4KZE9d/MhJ7R7I658fbAjAWI8erHwvu6Di9ScokKJe9u55ou5lwYxgfOrJ1uIMsTXh7L2RrCb3KbmKXP2X5GJh3meGVwR3Xazs2QJHbHMWw/Sax+S5x+sYxfB4YXucFVHKnYs5YVuqHO4M7ZXxl+QTw+4h/msne4DeW5FdJkH8qUogEsTcFRDKEEv8a+jKHsgIgZhb8fr2nSCfkLpMPjuajymT8XqUx/XfacWcjzNtG5ReNzv1IWuQwdpQlQjYflDU1+5USFmZL3gneRsbZYW3C+kioYEUWkETbznir94ajtKSh/JmxGe8ib6+eMYVFcQqeh5NO1pAMoVzzMZnlvjKGErO7mfiDF++464/86+1r8nctdhROh5ara6VOfHb7BLcwRcvOT5qc8nbD0Bej0o1+f7iJmN28pKiYwgSLk4K+sfWMLwx+X469X6e8G6B5XMGw5M/iWluWa1nZDZnhFTSPJcL2G2HzLuCPCjpNt3rfH14JvrCh2kc12Z23ReJRznc13c2Cmsn29ZpfjXUB4kbPrb+/WFKd1T/kN/A8cqlLXepXV78qhsZf9PsvjItLXepvVv3awIqc8z8Rkd/+hV//XeA/Kz//z8D/jj4c/F3gH+WcM/B/iMitiHyec/75n7mRJMSjI7ozs4NZkKBN19LA+VVikXzGniVzVNrzogipWuusNNzk+DfkApJA7DnwoB9cVihLAogJgpTVagVIyiaM0sOT1wY4W6AwLM73yQ6ysAfSZD55Pfni41BiUO3RfGKXmgt7I1tWX4ciuwDWJAt0dR7JhMnhi3582w/MreXkO/Kjwz+rfl/KKmiMQm4sh8FydJ2yO7IssY05Ca6JiMkEm5R9kdbXoUhuCpgikrX5Ka9HJ0xFzgKGeFQDgOwz0hUpShCkpLDk6NbzV0Gdq0Acz0Cc8oYlEtWWE13lN7astldZSKWBTIX6YjPSrCv6tknkpL4YKRiStUvMZt2X2viea9wXYCwJcjILiILNChy16zFOWeUD7niWWGFY5pot+xaKR4uu8makgB1pKNeAg3mbiVcRXNJxA2wTMTYhktVTYzakJ09Nx5EAZrNSUuocrtsn6z7MGyF2wvEHmfBak0+WuVCa2VPjiL0heUPznHGndSV5/0XDtlztdoj4/YwvSRuHH3RkA81erzEJhvlq9SsZJ8Oh9bjdrACWo4Ao+npt8s0sJCvEPi9+CfoGSNvIaRuRyeAfDCYI001aJDsyC2Fj2X6d6D7A+EoIr2ay0XkXD2aRhGhUpq7Kdx+LrGqEptV0mizKbElWFubFcNss96HsdNxDfyYjEQVPFNiRs2Si8u8gZGfxD4btzyjSKH1/+lI7aX9ICpBY9bqQ4tOSf7GnzQq+ZGuYGquAIGBcImc4jg1zsAtbaokjPmN2eZvovA7IWCQa3ke2zcRte8JJ4t1px/PYMhQ2Rc6i7LAshNmSGmWHjaKD8L7Z8bZ/5g+u37H1I3/YveH0zU6Tia51u72buS0eG0YyIRturg9Mm+Ij88riXOQHN4+6b9GRs3CYlDoxzI7OB56vW2IWTh87ZbjVueISzTee/lvB7/MC7lTW3bxR4Dd0CiyOryLmamaqqS4bh2SLP2b8XoGM5DOnN5noK4tG2S0mQvSaBBJ7IRXgRJLKVsJGGWM1XrmCb2YUNj8Tbv/4Qq2o9Rt5HrnUpS51qUtd6lK/8vpNe1a8PfuD/w3wtvz8A+CnZ+/7qvzu33g4EJG/B/w9AHf7AvtoVaNR+88SXymx+BkUSn3catNRNd61ibSjMgw+8VKwK602+1U+kCuDI8kSE5qqv4VR80aAmHQlX2I1blyBByhgxAQghfBQ9qmCEVEW48oKcshZI2AKCyN5bbwyRUddZSplfyUDtaksdOHsV7+Eaq6pqI6QD46pbMfahHOR/npgMC0h+cWsE4r8IQjmyfyCtGL9OWySbs9AjaSUylqo0hCbSMEwDp5gLabQuVM0qwFhFsxgVOaxDbhOH8JzMqsnh09qTni2j347ERtLjoVpk+XTFXUofieFGl7HoxJ1CkBxzszImeUYcl5lR9JGYm1oqgdFLDGJW43OrNuq0aVkUWZGmROpKecomMUslU0kdIlQqPT2qAaYNWE1i8pJ6nwESGHdRmxlmZ+pzxo9Knkxb4zBEEarxyT6hdllwq5qVbShXgC/zGLECNoYmSAMLyG1iXg3q9nhmR9MqmN4OzFaT/KW5tHQfVjlNs+/DcOrFnvKNHsFMpZ41jshi+jv5szmO4gPLPKU6TtLtpbxpac7FtPddCaFKY0fAyV1Vs9nZTVkC+kq47f6hZPvNNr0duKL14/LVPj5ixuS62meMvZoSC8m4nX5DgOISjviRr1IxCXmW7+Mkx0FM9rFUwBZmU7DW50fucnFmNaQfcTW2M5ZSC4Tb9Xk1n10NI8rm8sdBTMb/F6b6HmXmW+E02cw3xYPlfcWOxQvlxLb6Y5CrBGvThkmdlRWRvMEYJlelm1cK0IyTJ4YDPNQ8jur98dsiLPheXBMu5Hr7UDfzHReb7a3/YnezeznlpSFp6nlcd8TC5hlTCLFlU2VkmcybpF4/CzfMEbHl7t7XrZH/vbbb/i/5h8Q3vUMJwUb7lulQMRkCNnQ2sBVO7G7UtqCk8TL9sDb9omfjzc8Th1TcoQCwlqTuO1OfH71xMv2wM9fXzMnu4AZrzYH/kXzOdm0bH6u3i2uzFkAf1AZyfBSCBtwtxNv7p4Yilns4/WWB9Oz+6kyK9xBZVnzm5nB63umW1kiVrNA2iTGO1nihN1RwZAlorr+LTvzT3n9zw/4bx651J9Zv9LnEfvixa9vTy91qUv9xurPYnRcWBeXutRvvv7aDDZzzlnOl9l/+c/9A+AfAHQ//K1shzVtAj4FBXQVESCTC/U+bpPKIzIq+RgMEtfdkJKiUQGPRdIhrKMlkF1t+kXfZzgzxSyr/UDqi9fBKEtKhT1JkXigppF1878wGtmcNf/nC/Oi/22m0pjVhe8zKYumiLACLa40m3XV/6ypz4WdIEH17gCn1GGaSLeZcG1gflEb7PKh2SCTYEezgimZT9gdkgyx+XTVb7HR8Jl8FTBNhABpsCTs+vnwC38sjLJIXBuxlTlhImEsLneCHltagSfnEk0TidEwT46cWfxMPhnMlDXZZDSrbAbAQs5pYduQgcGuxwCkRuUI2YAUL4RcGvmMgRZlYiyslqy/R1fDq2fK+dHKJCRXvEV8wnWR5AWxmZTbwh4oY3ATFnPVMCnDI1tDLsyf1BXmDGV+BoFklrQasLiTWf075MwPBcAVb5M2LiCN8Wkxao2DJZYUCikpF2Hwal55BlggGdNFpIvMd3lJH9HzmJl+e2R8azFHqyye08qcmK8yZtRG0J6E9jHRPoE76j727zNmSszXDkmZ4dYyb2W5rrPR67MaopoIMZ+xRALIwTLPnR5nF6FTIMoXgPK6Hdh+MfEvD1+Qf+IwIRMO6+0zdYk0WQURm4S4xPZqIG6mdQhKAsjppBEqObMAW3dvn+h84EV34jA3i4/DY5FIHI8tzkXeXh8Q4LurHeNXG9xB3+dOxbfEwOmNygdSm5lvI//xf/QnAPz06QUf/9UdzYOyQJJTsLbeC6bbTHw9Y54ddjTYE/Tv+P/Ye5dfW5Iszeu3zMzd997nce+NV0bko94F1WqpqhoGMOEhMYIBs0ZihBBDQAhmjFrMGPWAPwIQSEi0BOOeoZIKQYNK1KsrO7MyIyMyIu49957H3tvdzRaDtczc982syuiszKyOKl/SjRvn+N7uZuZmfn199q3vI3hpxrHfIbtMiEp+SvSfJHum1R39ETS6Xsu7ibsPA1eHMzc7AzmGOHOcO754PDDlSCnCdE7t+rkqVlZGjq+j4nNxOiUeHnac30vcDid2caLvM7PA9MbAhM/1hof9YOeeIl2Xef/mkVtnecwa2MeJhzxwPw18frxmKqGNt6pwzonnw5F3ukfeefbIsfR8fjYFzm/tXzF/M/Dt4V0ewxXdo9C/XrRLwmzPj9ybq8xuN9HHzHVn8+B2d+bP5QX3cc/1d+2+IUL+Zia8b6hDngJj17dyMfrC/E5hfu7/ftxHZBLSkxAnmnho54DJ7q7QffKa/GKlGrrFXxo/i/eR4Ze+tWUxW2yxxRZbbPEzjl90UeunIvIRgP/9Q//994FvrT73Tf/dFltsscUWW2yxxc86tveRLbbYYosttvgXPH7RzIp/BPxHwH/rf/+vq9//ZyLyP2JCVq+/bH1oFcmsIpalW+rpi+tC2K7qajs8YLvwQc3ycr0fUowJEZ+CO30sZSK6Oi9VILNYPTteDlKvodF/jor2hbyjlSzkPjRxNvCNRGd6gO2AS8F29pMxFkpayhc0XLar/m4RacTp+7RSmHb+2tds/aRdpzSnDgB5iugxclRBgtLtJ2IqTcy0shXG1CGTsVOaQwosdd5VwFOto9V1xXTtTGRPu0Ip8cIBpJbx4Hokep2RVOj6mdnHUUuw71QhzM5r8Z31Mk+R1GViLGg3U4own9NS5rG+59nZLnFdw+9MDYXqVBKOoSn/SwYdV/eg8/Z6PySvyovO4WJ+2D1zC8mqm+KMkTAJ5cntJHdCiYpEG6s8ZERjYxINt2e6LttO8ilBZY2sdTkqTd/LZMJZmqiiqFHRwSx9NYjpa7SapDpvXORUlLJmpiQr5ZFavvPYEU7Ba/lX7J2g5EOAztg95SZzdtcV3RWurs/kfWC6iZyvOmQMTSsBFcJjhBBsfauVVuXe2rF7lYlTIX4xmh5MMMpSvY8a1noego7uCOTHw9nYA2GEMiTO72V0KExPA9+5fx+A7mbk3ecPpNuR87uB9CT0nycT4cSYFTI7wec+kovwFJS9Myuud2duBmMY/PD+mmk2ZkEVkZ1zJPYTh2SfT1I45dRsPe9iZpwT45zo08wwzDzc1Bovs7ZEYXyhzC9m5MmEceUceNYbq+DZez/gH392g3w+0D2YtWbuFxHhfJO5fe+Rx8PA6bynfyPsPld2n/tUyB3TbWK+LnQPges/N3eL+uwN2cvuohDHwGO/437Frnk49xzPPeenzoRlfa1U5k/wtdQckPyZvWZChYfAD3jGZ901fT9zOvZoXxBnhM0qPByT3YgxMAGvYmFw0d2HqeeTx1vToxg7Hp4GUKHr7biqcPZ7cpo7XgxPnHNidE2cN/Oeb13dsYsz/x9f4/jUcbpPzW0kPZq7yvmFkl/MiCgP54Frv/dX3chvfPgZfybv8TRf0d1b+YYW4eaZuaOcxo5jEeSLau8hhP1McvHjaZfQx0Q6meBt1WTp3M5UChx/4z2++Ls9/D5b/MXxM38f2WKLLf5mx08S/dzKRLbY4mcfP0/r0v8BE696T0S+B/wD7KXgfxKR/wT4DvAf+Mf/d8wm7E8xq7D/+J/nWpZk2f+PzxfLNsnSLB6bB/0YLIEHS9h3lgQ3Bfc5oJ2QC83iFGe0V8AjqFLEXqwlm/ChaGhCcxVEKEnNtUAxmnxyp41U0MGovDIvGhUtuaouH1424JISizim0KwsW8mKlxMAhLw4lGiwWvSShRJKK0GQWawMIVtJQfb+NCeMWov+pqN0BR0KqpnO69uHfqbvZ8Z+Js+xuXjUyLOYLkKRBayICmPNEN32EkhdZvZ+a7OQFUhKHKxzwzAxe4I3n90eNQevvRcI0spZakxPHZN0xJ3V+YuoaVesHFFqqChl52Nff+82hHX+LE4qFRXypL+CQUXMlaR+LqpbGS7lRbpqn3aKYqCZVAAm2BhE16jIKnZbu0KpuhKytGWeI+OpM52LY7RSnq4sYMWqREhm10KZZOm6LhoPtY1h1iaKqRUkm+MFmNYigBYf11lI96aLcCFq6mBPPAvTtaLDqn0ABR5f75ojA50533QHW8jzFClZmPe2EE/vibfZrnH+LJCOic4FO+edmOjtChyyEgVg1uYGVPsjGeQe0pOLl06R0ke6h8X+drrp+OFHe8pQCMGeDd3jIqw47yPpiQYK5ofI9Ljj8Zl94HTb8brbE0Lh+DBYAi4YiAXcvxp4M1zx8eGZaaAE1xWpz6UpIveJ4yhNd4ehtOdb3rk97bOZD7/5klf3B8ZPDwyfRX7ve78MwDdevCYNmbxTc5O4UvKwuOuEq4kgyjvPHnn5S/B036Gxo3tjl9s5rSzDAAAgAElEQVR/qvRvhPHGhGTTyQQm43lB/0on5CsDgNLrwJwH7lxckmLgiWSIs1TzENKTlzeccAcmmK/rc2B5LhKV9BiYPhuYojL29pyUfW7inuEUDECMWJI/w5svrjiP9swoOTAduwW8LdauyfUixJ+b53PH5/0V17trwmq+3533PB+OHNLIr7z3ksep582LgdP7hlo9PnZWUrbLHG5OqAoPx6E5puhe+OBwzy9/8JJ/OkXy9wa6B3MFGt31JMZCt5/IsbPn9FOkKFTTlWE/MQZlPAdUDMCLZzi7fsrpeeD4YSD+3dfwD9mCX+z7yBZbbPG3N/6qDiYb2LHFFj8aP083kP/wLzj07/yYzyrwn/601yrd8kIr757NflJdf6EJSdrxeAyW0GUTmMsA+4oGQBVh1KFQQmhggAZtmhMU29l9m+lQd7DjuTIDII+RMihlt+gMSJ8dRBATKnxrZx0HJjR5UleT/nWf3VEknKVZr9bkzcATB0DEPmfuD+EtpwohZBxUiGjn4ArWFlEXdIyCPkVyl5gHSzzGw0zXzy7E6e4YobQX+6zCPEcTyfRL9n3mfLLvi4vplRIIkkldRlNuAntggnsxuZNIzJaYHzv0FNt9qAk2FayJtLGSkwFCuWpRdLqwMOCyCCoaMyPUJNFDx9DYFSqgB13cD7wNFU1qOhBVU2OXV3PEhC2bqClQRTtlyOgZt5BVE1n0uRRGoUgwrY1ayy+KVIvXNz3haO4Vki0hLwS0Wb2s/rAAbqVqXgTfWVdzr6n9XDNgqpNGjTVWUTpzzGkgyIytPWERAPT1Es5CisbK0U6b3oK8cYeMvbvbRPtTdQjsQiaOm7N9rlznNs7js+QWq9LWQDyu3EAmSI+1j7ae40lXfah9NHbA4VP7TP9Q2L10HYEh8PjDxPlFIg8mcjjc6QpgrICPjV/poH8tTHfuQnHlCWeE3VFM5yQuIqFV76Gk/oKhVPugYtfrHtWdRODNr8eVpo0xkWQKPJwGxBkKwys4/YFZZfzp1/ekq4npWSEPgXIo6M4siMFYBa9fXXH17MiH77zhfJv4YnfN+Jndh5t/FohnZTfB+Ex4/LqxWHoHM0qEMphLhWTT0IgvI+pqsKL2bKpuNtVJY3hl9yKOdmy8EcYxUHrT3ahztbg17+EHgZJgPtjcm4dirCpAnmy92hqxCRzeJM6VSTQU5GF5xmhSwhiwB8cy1uPZdCOOw0DqZkIT1RW+SAc+uHngWX/kqjtzO5xIL+z6VW/k7M+xN087pimaywlQVBjizFUa+foHd3ysz5nvO8jC04O5tuwOIyEoc2/uN/EoyJRMGwYIzwqpy0zvjJx3yfQrHgMYCYjcF97/rc/5tz76U/6QLeAX+z6yxRZbbLHFFlv87OKvTWDzZxGKJQUlLS4fu91ElzJFhVPfk8fYhNrAPhsme2kOk4AGytS1xFXUd5Nd0NFsMW33vFRSwGrnuCRzSiiylJ+oCKH4TvZsjiFljK2NZS/2cp0ULX6NutNXOyZAX8xOs4j1oeZXnpjU0pTiwETNIoszPtSJBhqsvemoTXix0uILljDEETgLeXI1/sGyLinu+OCAT3HqfR4ip0Nn5TTJXD5Ct5SJ1J3hyrgI0Y71g+2Wx1h4eJPQKVBiIPYZCcv3cVvEUgQRsxacx4Q+LVO2lZp09kdiQVfgiGVH0ko3VC2pkWZZqYRkAIsEpesyIsq42oXNfj/qbndYsTdCKJYU+ummpx4J2koi+sFZJ347JRTrQwVbvExEs4MpFWRLSnbQaO0C00COqO2acg5+f/x+Ri/H8fmpzr6xcQJ1QKfuyGtUcwgJSjnHds2yAnI0gJxWDJ4638DLoJbPls5Lr2RJ5KUowe0YJZtjD6M0AUFjYkA5CnlQAyN6Jbl1aRnsXNorc1R0Vwj7VQL5oZX4VNtLzWIikcd6fnNSCKOzAaA5jVgD3cVjsPnWPSrppMRTIZxtUcfjTDwV5s8j47UJx3ZPpTmKhKyUJMSzmrBvgPKo9K/rc8KTc4UwFQcnVyCr3z87ris3oKWd8ZgJs11Ho0DozOYSKL1ZXnZvIuPrZ+QeusnYIjffse+np47H31Q4ZPJQGghafM2HN4n0KDzddaRfLQzdzEdfu+PV1QGAN+ma3Q8D6QnGG2X+lRP7q5E3jwZmSMDtTJX5h3v6u0D3uLiy5H5FSpqx55HYeFPviUCJAVG7J+MzoVzX+2Rz4+oPlXknjM+EeQ95nxqAGCYb+4p5NqZUXTvB5g/FHIbCkyX66/lcOiVPBjLnSch9IKQFQTrNPaUI+3cn+jBz3Z150VsJR9kF9nHkh+cbvnf/nFIMaJwne6YcVfiEG14cjlx1Ix997Y6n5z13n96gvv7G5MyxfaZoJEziDlLWh+lobLHUZeTFbOe/ja1MZOgy/8r73+OdSgvaYostttjiKxFflpmxMTC2+NsUX2mwgugWiwFzXMB2vvqUKQrhoDwykH2HDUAPmTzbLrjt+JpLx3qzu3RK2TmFP7LaHa9v2quHSVgzIjxxGLBEeHQb1VlIExTfJS1jJO8X+9Oqy9DAiOqEMQdUdSkxaGDFcnlzA9EfcdyoO7JSbEcyZIhPsgAqUW1nclgSyHh0xghQRtdmKLZDauAO4AlgiYLe2WeqnoYBR9raxapsYe4KU981qn9M2QCYKaDAPAtEvUgKwECCSbAX+YJpGQx+r1OBTgl9ZthNpqNx7BawYmflKxxjY19IKs3VQgJ0XSalTBczMSi5CDn6jr9nMCJKUSFGY3nUJLkySYq6Y4YKqct0Xu6TYuY0dsYeCfbdEzBVa1MHRZSlhIjic2pY5jNg4JZT1CsYYt9NFA0GWnXGypBpmSvSJjYGqimmUVJLLoKNX0yFuZYOTaHZ4FZApUzBwL0MqL5tXGOf6dVArsAl/QKz85VipUdhNGZBBUxEnZHhDCGKINNSHjAbpsh8yLAzILFMoUmPdPsJJSApt3szDZnp6KyGc2C+CqRHIT9aaUD3wLKOAsx7WRgipT4HItO1axGclPSU6d9MpKfA8b0OjcI8+L30spIwq623WUkzyLEm0YrMSqjOQ0XRFJgP1amFBiTG0dazzIqUOkZQukDeO+hTlOvvze07420wsGRUwqRMBysPCBOks58jw/i8Y3qWkSLEx9CeEwDDSzH7SxFex2foVebFe/dc701vQb6lPO2uSHeRfFX45Q9f8hu3nzP4SVLI3E87vvv4gj99+BB9MPCoPnMQA3zDCtSdr+Cpr44mVqIzHyw5718bzDfvV/PoqpDOYqBTDoy3gqZFnyRMdh3JXhrTOejsVsASFDnMlDEiD4HhCwNUqg6NOSgJ8yjknTLd2hosFTQMwCScnnreXO84dCOHNDKXFSNMlCSFp7EjZwM5q3vOPEYe5h25BN65euJrh3vClfL/PA2mOQOtpKU7jMwxMesCptvkFrJE6Av73ZkUCuG6cOuuK33IBJQ/ePiILbbYYosttthii69yfKXBCklKeGdECwR/mZvGxAO05DCEgnaLXaX4S6vOgXkIxIdl5wpsZ05mo8GXpKiPkL3EeqKxzsPUE0JZQIt8peRZiGer3YbKsvCX0BOEKdhO7q6gcmnKYpapArNSLVF/RANgVcetyXbMG2iSlFI/78lvGQWZQ6P3gyWAusuWEAezII21fvwoXl7jdfywaDNgYEg4+fnFWRcdECqYAVXrQztPtAal7H2Mohr4UEt0solGlrTe0mdhm1Qx0coEwICHmIwSvesnphyZp9i+kpwpkfeR+ZTsXlUABbufZTWuuQhzCS3hFWd32EFpv6/sj6LCnIOXugjFgYHox+dsZTC26++710GJ3v58svsqs7R5JllgBvVxCjvblq4Cm7Xdrc1RKX30EhhnGki4sCuV2onkYohh+SzKhWCmeimIVPDP50u+hjJVkG91iwI+Rx0wrMwToQkk0hW311VyF9FHA4/K3i0pb63cSNyStfSKZGnCjxp8HgS75zq7Von3carrxa8TvJwnXhvylneBeRfJ+0jem2ZCHpYkWtR2/UsVy4xmh1wBOLCykv4+MrwuhNlKMaYrGF0nQIOxk85jNKDi6An17HPJgQcV0CSUFCidMO/8+87u0ABhH72kRBtzQ1SZDkLphTBZUn/98Uw8+vPMc+UwKcPLMyUFNAUev963RL5/LFx/J3B+YZOte7RymDoOxhSxtV26yHSI3D0+R5676GefCVcTc1SYhC8eD+zTM373+ffs2qLcTzvujnvkHByAWdpmHYHcAb0xWU5fK5RnPsefEulNAPXxO1rpy+iWnfkAHDL33xjYf15IRysDAVmuodan+Uo4fg2zqYYGVpen1LQzuvvA4VPrfwVE8s6YHvEkVm7TBXLRFVvLJowW4fVxx1QC55w4ZZsoRYUgtzxNPQ+PO+YpElcAqeaAnoWjwLg7UzTQx4l3nj3yOlkjJme6DIMJqZ4cWJzPK0aWAyilmKXxVT9x5faofZi5m/b8wWcfssUWW2yxxd+8+KtqY/x1xcYI2eKnia80WBFCYX84M46J4upj831HDqntGK8TU7C8OsSMdJnSB+aklGNsFFuC0drDiAsm+gu3sAAXkUsRTGcFNAHMfQaFeQhGNT6LOUVUQGQ2scEwwySCTg52rCjhlhB6IlMdKsL6xbuiBs74WB9PlUagK70FYT4szIkGPnSKxEzpAlMXG3ASx+VBmAdzU8iwgCZeOiJ5aW8V/KynrsOeByjJNUJWZQ1l0AUEclCFt0GZ6tbi/dOkhM6p+Z4EqJqCvqpckl6CklLm9urEfWe142vRQs3CnO13U4yt7CTGS1ZDTVTybKBE/f00RcoULHn2z5zGyNR7GUkR21HVyrjJhJiJVWj14CBadUGpU3AUcvIfdgtQIUEbgNLKQLpMdkYEYju42nl5UR3GtLopcSktAkwYcwpWAhIUOQVjXoTVPygq0Be0x+bq2uVkvb7UjyE0UVGMHaNdQZIifbFdagFxtw+JhZzSyjXG/kw3fuo1OOKiqhRpa1YmA6I0RiT7vOqsVKS2UZKi17PdZxU0hgWsmK2cyNhAkPe0tV0Bk3gUTmehu4/Esz0L5h2c3ve5Miji+jHdQyA9wPCaBlCWVP82AVBLhFkYMN5Hczvx3zuAUTsxH4w9E2ZjnqCJ4U1p5w9ZyUNAcm9MjrICe/wa1z/I7F+G5mJiQr0LeyP3VuJz+LQwD0L3EBlfWBI9HxQ9GMMnvYkc75/zh1e3/Pk3ntv3RTk+DZQf7th9Hqj6JzXCZP3Le5ivjE0WP3zi733jYzt/iXz71Tu8eXnFNAYgcfhE6e7t+/MuMN/A/d87c/7OwO23DWhIj0quoE+E7slAm+naqByaFJ78ufYkdI/m/BOPzsAYjM0BBlrEswEe3QOUXhifQfZB1KjGOlLh4X7HOCVSyiQHKKccmedgQp73PRRhHnJjjOmMld6MkePY8Xm4oouZfTcxDl6CV4ZWTnPoJ66GkVwC90+maTEeK+3J9F9yDIw58jjZTZ1CZCyJuy+u2WKLLbbYYosttvgqR/jJH9liiy222GKLLbbYYosttthiiy22+MXFV5pZUUpgHBPTOTURzfgQTVwyGD1ee21K8QB5wsT2ukJKmXBdmLpE8e+XORCeQrMtFbwUorIpYLFzrLuiU7iwvAxdJkQld4HSBbQz2ntzi3D2RtXMaLalflj9M9Yg151IilLr42lblopR5tcuGKrqehF+QneZKHsribHzSmNiSFRCN6N9ZopOZ34MTcujDEqubfROS6msCmnsiupIAf6z/16j7ahWx4TayTDWHW1ZSgjWdGtAVveulRfULpSAzl7GMIWm6dCERmcTx+vTTNfNqMI8pVb6oV4+UM7Rd999l9qvua5CaXXrRci1xGKMi3VqbfMJsqxEQGcbZ41qrJIDdL3t+O+uRqYxkWM0hgPGmNBJCG5pWWKCTs22tJahrAQ2q/in2TAG69NqjIALm1CJanMzL3OjsYrErxuU4jaKVCHXzh0XqoZLGxiWdTAbs8KEIZcGaHTRyS5A1CaM2mxqS1xKfOq9WdvQ1tKlyjby/lbqfzw7I2O2sis9mY5BdvvXqg0ju4z2BVVhvsGtgKrQrpdFpWUda7c8O+YhMIsyvhuQyZxHwiiUK2eH7DM8c5bPVUc6RPIgTZ/h7MKPpXMXi87menMDcaZVc/eQ2h5vS1DyYGM5R2MUlD4wvrx02ig9xHe7JoZqrhy1PMj0K7r7ifkQOb4byR2tNKzahIqafkQclf0XSvfgrJAephv7Tjq5poQGzt81ZkUJsJ+guzfdjLzD2Q3ex6pdcbBSH02FblXe9DvPv8eL4Yn/Y/wVUiqcbjvysOfwA/ta/0bIN4l//Xf/mD96933u83sMd9bWWsYxH4xFlk5K/9rFVXvX4gD6N8rwurJR4OGbgelGyf3ybC+D0N8J+8+U3ecmAlqjJKFoQYnIqeO060wIuTInzrExhMLJ/h3Ju0C5dZXRoKZRVOB47JnmaCVLoSyPPlFzJfLSskM3MZXA0Nu9ns7VZnV5xB/HjuyquLkI+35Cjuv6my222GKLLbb4642fVL6ylYls8ePiKw1W6CyMr3bI6G4VuIhkscQgTEKZ3Da0JuaTgERyp5T9TBpmt81ckuK5ghdqyXhLYGry1BTmcUV9TyRXPBUJhRAFlWyJpnq5B1AGq7cP0yLCeUGbrzoULMl9GOUyQXT9CE0Ljb0mzRp1AT+8ndqpU5hrDYQDA7NQiITeABa59Tr/0FGOVhqgfXHNgFUip06tV+zlvLiGQF7aW/U/8BIaXWle1OTK9DlMM6CVEtRYuXaI2Mt7Axewtsts14knMW2Ot0ClaYjc5UCMhaJCmcVKCfB2r2xtdbJMbfbjsgIIgMXGdFUKI+uSCOxeLKCUJ9/BywBUyTE2zYvdfmy3s3jfNflXPNEOx4ie9aI0I8cVqAMLQFPHLhX7U/u4Fi1VLyGpGi7ZtVR8rmi0MoPkWisE126ZogkZrkC5ej5xwKbdT4zy33Q4io2JxgUM0KAwLm2o164aJm2e4p8V72dNeN1eGCATViVINh+YcEtKo/lrcsAnGghSS7UAA5/q+luvMViAElFkl4k3EwpM9x3dq4T4OOqkpMOEBCXfwtwX8iEucwFrw+K+AozBbHWxkoR08vIZxfUrVmO9+ls7K0U4X82Uodr40hxGpND0ZnafBYZXdZoIJVkZRB6E6VqaJkeNUIVOoelXVEAlnZTu0fVogN3rQjwWrj9Zzl8j74R5suu08jj/TzwJ3b3Zj57nA79//GUAXn3jwFWyiw3dxDeeveY76QXlC6sH6u5hfhkoCP/21/+Uf/RbB8ZPdvR3BjgA5BcT51Nk+CwyvLTvaDKQAnC9DwNI50F4/JWZ7vmJ4GtnHiPn28B8lci7wPDK+tzEk3ubLxqhuxfyTih9NPAJA86EZQzj2UrvJnf4kNvRyvqyMD12zJ0DdUWa+LCIQspAcBDCytXqc6k+00M0rZ6iQi6Bk8+147Hnnv3lM2KLLbbYYostttjiKxhfabBCstC9isi8JEZhlmV3ElpC7q+QZh/oyVkehWkfCbuZqnGZuhn2VquM2K51cEG1luRW14JVgteuBZQxNgtNEU96UbQ2rDMAIM8BfQowijMvFtYCTbfC/j+ukmqz91NCtmQjjIKGter+knSV6KyQScwtwl+qa5tlDHCGMgZKV4i7qrZXfMfeE7z6+Zq7xUJ07YjiO3qaFz2HPAfyaLvQFKsRvxAJrfeiiIkPaiQPa7TH9CnUd9xVsF37LMRaf141M2Zp9oiaVvoAnVImoZwDU+eJ38o1pf1cQZQKPrGcq/0k2oTtWOf+dWwczNEYkDqZxEEFWV37FJhWzIsQ3Tq1s53VIEruCsU/E54i8VhBAnHB0hVotErkm5Wri03aB6SJW2qR9oeaRM/GhNDkyXRSOAWS24qGUZAOdASCkHubT03rMxsAGJxB43gBmmi71eKgTsjOUKkuOHVo13O7rADC9oHVPVutu+rwoKmQp9AcfiQvopG1jWBgmgbTsYm7xQZDe/E1a8KL5rbhwEEFI5Lbp3aFrsvolTCfYrNXzZMwZTFL1aik/Yzu5yZeKqKkuvvuuifZ2w4wdwHtvA+sGBarcV4nnxoUkjI9z23cCD6WGfRmJnSFp26gdO628WT9ClNAsjIf/Kvdcq26piZZ7oloTfRNNLhajObOxjs9OLtEbe1N19FcUtTu/XS13MfqxDK8BI2ClEBJOwC+8/1vMn04IgJ5jnSx8MvvvuKfvWvaC4ePheGV8Ht/9Gv8G3/nj/mdb32P7z57wedf3JCcrfTe7RMiyif7F0juTUT0uMyH6Vo4vWvuIfMe+hcnvvb8ntdHa8OTDsT9xLzLnD9Szt/fc/1dd4/BQKSUhBKhv3emRUdzZanPbJkX0c40GtMHYL4RQjLLW33skYdkc10XMVA5zKgKIWamMfFUBnQ2C9RlUqtp9ogyjuaAlGdDkfKbnnQXkWFDK7bYYosttvjqxI9jXmxsiy2+0mAFxXbpUGzXFJpQngbI+1Ui2Sw7l7KF+BTQSchVIBNnPURFgu1cAZQc0cLKUlRMpK+KdhZZjoGxDGoSXHdT1zvfNXmNXpqRxEtXlhdeKTS2CMV2qmuCWDoISBMBbXZ9l66fAITgLgPRRENbIt8bE0QqeBOgpGBjcTHGnqBn3/mv41wFIAUTifQ+NVvQPlPEkndmgSkYy8JDfCdbMkRPglJazo8uu/h1l1hmaRasYLTy9ZDavVkILposkY7uYFETwJoIl953+WNZJcPLTnBj09RjAWfRXN5HGSwBDlHRkikr0EUc8KIAc7D77MyQqQihzzbXggsOpkKIhak2ZzYwqyX0yUAHajLf+RwvLCUgtR9wwQxpApi6MCAMFDIBWO1NlLKkSJ4sg5WjJVKNwo+Bga3cp9DYALnHWUE2t9aAg8wLyKO9swvqfE5qcySpgWc+NxoIuOpHK7kqC3AWD7MBf+fI3Nk5JPOjoFTGS6JgXS4kKxZSGxc8sa/rYTJmVM5CORggUfaZcOfjlEFPifkmMO8z0cVUQ78syhDMfabkWq6jSGWQdIXcWzmTFFkcgCpj7OxAVbA+CKCZBZQSKz8r5whTIHTmhpTfPXPsrI3xMTbxzniyNZGeuIjS2XjUe6yR9lwKLkYZ5tX0miP9rjJkzCVlvBE0CmHU1jawcxWF/qRc/TAjM8RTbs+tm+8n7n5t4PS+lcl8eteTf+mO8V2bbP3rRDrC9R/2/N7Vr/A73/g+37y5I5cFJI2h8P7+kfGDyKunF+Q3geGlkE2bkvEZjC9KY6qVMfLFw4HjvYEVeorMfaE7jHzj3dd8vh+ZXj6nf2PfT1MFIO152reFusw1K62B8Zkw3jhA88baN71IlP1MSAqTsP/EGCbzlTbQKAvkVAwwLwIPifSwCMLmnQGG0xS4myL65MCms7H6e+HmO/D4jU2Saosttthiiy22+GrHVxuscFbD2h6vAhUEp3oL+JY1AKpmBxgmL1M4i/GaPRkq52CU3qi2Iylq2gR11xVaPXilPVfdhhqhWp9GTwRX7iFLu1lKM3bGYlh256Ql9ua0IRclFHnwMo9W32/HWuK+3vn3nExmZ4n4y3XxRKRqRwBEgexaBSXZuIb58jz1GkZpj5ZUzQ5oRLXEE6z0wJNjqYyIUS7ABRXAqea1XTUDrW4otc6/gUyFNjb1vpsmhi7nrQ5/XlpSy0SqNkhZlSeUXluiQzKnA6luIF4WIFIBGu/TqpZFghK6YrluME2HblhQmRDUrE2LMI/RgIq6QzoFitLsZwFyMCZEtUmdh2y2s64PohEDLDwJDvu5lbhoFsoUl/IWb6MlxrIwgnTFBgqgKHSF0Ge6fqakvIAlXVrKO2q3CxfzvXQ2l/OhmCZFb+hFs4ANmLVuTSijmqWw/yyhkOdoJRSPnSXHDua1m1rn3drlxIGEdGUOK3NUSjZtjEvgBgdBvFRJIJMWhEtoDiOtJMh3u2sCGUYhPglhjMyjkPfFnxEL6yCcQTHAL4+B3MfLuVRZOdUq1t1tALPh3Wdbd1VDYg4NkClvr28Vv48LKKW69LmcIiUG4pCJbg2a97ldO4+BcDJmQ2MKBdOdEAdHQ53q9ZwJpgONIVWfK9VJQ4o5pOSdfS8dHYx0vYi2loMBIGnMBlZkG4Or+xGVA+kYmfdCmANfTO/AwSbbdG3PiuGVMv7xFf9X/hapy5yPXVtTr4rw8M7A0M3I85FxSJSU2jNRI5TbGX1IVqLx8Y5JdwxPSx9KB+M7kU+i8v7tAx+/e2vjhPUpntSmjhjLJJ3KxXo43wb7N2Y0YFPDwszoP4uM70PZZQS4+tjm9tPXhMnNO0QjeS8NBE4Pgd1ni+5G3hkAnXcB6Gx9FrOitbmqXH2amfdf7X/et9hiiy222OIv0rn4WTAu3j73xuL4FzO+2m8zUndzl19d7MZq/bNKXLBEpLp/hlGI4yXwoGcHQJy6W3dX3wYB6u+stGQ5FiZ7SQ9FLEkL/tK6TtQjVqKwK+hOXRuiNtt3ylNw5gMX7c/74oyDsDAKan/faqf1yY6JrhKNWVqSsLZKbUBAsuSk7aLKcg4bZ0+owpK8apCWdJcULiwT5a3130RLfTCbhkXdKHb2S6WONwvHVQKZdw7cJKVUynMtXQHbQc/SABeq9EbTEaiZWHCdD4G3WBY6rxg0ApIWO9xaXgGWiGuplP+l9ty0NjBQIylFcmNW2G6/JclN9LOIsVPqNZKxb2QSUC/j6UsTHg3BbkoQRQmU2bPaCoiI9aPpVlQRUv9ZuuKWqDRdkL7PpOeWGZ2HjuxCoqjpW8hKSFUD1r6d2TPGWAzwKIFcSyCCAReLJayVDlWR0K7LaG8nPBahpGAgV/fWRAYDdlbioGAlR6kzwCJ1yhyTWbiuWBKa1TVUnMV0kgvgTRyoqFajjdWz9zYnS57To12IHWIAACAASURBVCBzYJ6Fsi/MV9aJ9AhR7VkSshhY0IUGRoS3AM1FQLMCYYHcLQDfui21jes+19KWqvuhQdFzQFzXQ1yEMe8i4hauEtXEdwGuZvJOOMdk7DSwZ46X+MSTUGaz8Wwga1h29REHNMaFtSC+NnMrPxC6R+gel8U/PjOtjPsu0j0E+odIOtrx/vVEf58JWRmvg5ekBB6/tYxDSTZE138O4/2BEuFq/XwAHt9NPH50NsbSYWYOijzaB9KTEF4nugchHoXuAdKTEr0Er4KZ45vI9PkN3//NRHk+c/J/KtNjIB0XC2h1Rtr6mXt+Hhhvl/bmXhsz4/CJMF8ZSKn7gsbI4bMZlURd9JK9PKbTpkESJmsnOPDhGHuccH0mJZ0dcE/C+Vloz+Ittthiiy222GKLr2p8pV9nVBYqf3tZXIMIx9g0ChZdCfGEbdmVJ6+ScHXdixmr62/JubZa+0pzr/X1b2sxFBQ6b4ssYErTjtQV42ESK6lYn0IUet+1dbo4cbW16olm3ntSus694UJ3AUC8jGC9Ix4ma39YJXToiuEwCcGZF0tdxTLOjWkRfUhd4LDV2nv5CeKgRDD2w5IgVhBEKe4WErxco96/BqKsrqMR5mvraB4skScqpGIlF3mVqM8Ljb90ikRpDgngIJRoK8HJxcGJtXRGFS0VZzMEaYyBGmUOzc2DIuR6vIIZc2iJt8SylLo4mNLumYKWaCUvK3BBdtlcXPx8acgN3ChF0Ckhlbmjl/feSpKctROXSR4cHEh9RkTJczSXjNHcCQ47U9jc9RNTjuQcKEXMTWVeaPcEpetnhmGmFCHFQgzFvlP1HmpZgy+y7MeKLn3oukwIamK3sZBzWEqKWOXLoVCy1+afvJzm2FFUCKGQUqGUbM4t55Ugod/LkEEmuNB1SZdryOZggKDkqvFylZkUwhhNgPQIWQP5ytdiCY2J1FgWcdHeaAyp9bMIbwd1bkcDTlx0VlgYQ03nIzirarLPhAY0uMio1OQVAx+OkdnHQYfStC10yMSdgUTT0cUfx7CIuPZWstQ9rhLz+jxr4p/L83Hpj7e3gzKosYIeV8cVxufKfFDiKHSvI8OdfWd/CHSPmfRU2mevPgXRRShVst2v3avC9Q9MJDXMyvlZbNft3wQeyo75NluZTIB4tHHqX0sTvQyT0j0p6aTLcy1YP7tHe1a8Hvc8/dpEfscAn3wjjO4YJQqnGeL5EqyYbpT5/RGmQLpLzLeZ4c7GeP9DJT0JMwE+OPP6NyNhinRPhflhEZwNs4mTVq2U8ZZWjtM9qoEX3u4qhJp7Oz4dhDe/DmVVgrTFFltsscUWf5PiJzmL/DzOuTEv/npiK2rdYosttthiiy222GKLLbbYYost/oWKL82sEJEIfG39HVX97s+jUV86VqwFfgwYFo/LTn1ptd00W08NdZdTGiVdq4Dh6rRNtNOtEklqlGov88iB5qoANAvBxspw/clG5a6OFL4T3ixLWw29tOsQtDl06LoMReVCvM9+t3y9uOOJiFL6KhC6En48B9OqGF0pv1LPV32obbf/4YLGLquSEMLCtGhCjNnKOjQIZTB2Re5ZtEHS6kQ7pczmClKZAaIrdoULadbSlHxVHUsU6fPlvVcas6LaiCqgHc6iWHQ4gMUS1tkhGmPbLG673H7vipqTRm5aClbioCe3qBQbx7bLWvtQhLwzPYvSl4VNkZwVUkszlOVPna7OGgmihFgoORBiIZ+sFkYnE2X8sSKusJy3MnS8r00DQm0u5SmY280snIu0ko3r3Zk+udCsCnMOxiKot2/V3Kx2LBdjYeTqnqNWqjG7W4Fmc9ap2gxZMBvhZCwPAsR6070v4n0JQRExFkZdD+W+I+eOMqzrLN6aE6zWtmu8rIVUL2xCpypaK5TO59JVhheZcR6I1b1nVaZRdiaUq6cAqotGyoWDg98WnxNVmLQ1U1zgcv27sLpRUlldCmLOIVVvRrIxOjTYs8pEeQWmZd3OObQpUgrkLIRdJhxsQZRkdVmSCqW3UpyyC4uNrlssV6HP0ul6qrZ2VCZbdkcQVpam8wGm2wLPJ2aF6VmiDF6y0gvDndA/ljYekpXDD+3n8UqY99Ke+2EsxLEgs18LmAn098ruM2E8R8oQKUnpX9vxeIThtS7jHYXpcMkQkaLE0UpDbr8D021i+sAoZ93tSOrMpUNdZDmvtU6yMXLeefFAEPhcnnHz/gOPT88AY3Z099LYKde//QVfpHe5+fZSrmbMCRMdNncV5fxO4fS+HU9Pdt/TcbmuKM3dZT4o+3/prq23LbbYYosttthii69qfCmwQkT+c+AfAJ+yyLEp8Ns/p3Z9+fC37wv2u/+uCTIWMYU67CVXgiWw1VFBVyUWBmRoS8ABq5N3q0BgcVgQRRIoGZJcJibVQtJDy3K9i3O4Haas7FWrloGqJaCqnuBVsMFfjsuw0k9YlyZUJUkxUCVIdgFPbXmDDpk8BoqDFmYtSqtzruUPJVmSJFkIcpkLa6WUO+gjq0S9ARs1XxVMULS2cw3mVMvMtzMfpTmtVLBHky4OCJ7k60r7wUQU7SThbLT/UgVOA3Zs1aZUXS1miLNcJLElLZ2tJQs6xgXAEYVo16l2m1WvBJyKX8EXT/DLsJoTvaCHmdgvWg5a+yDLHAtuU5hS5lzESjZcq4A5IKOYGmItMQi6Eqdk0QStIEkwFwRgsUSsLhwqMAWqSUQpQkqZLmZiUFIs9CmTXZ9jzoHT2BkYMdV6J5ColFVpDNXZYgWoNOcNhRwMSAhBm4VrHfM63EWFMiXXAskG5ICBHtnmwZSFUOdHddpwQcqyL1YOI96m5jzjF5FlHsfH4OKQ3s8pcnh25PQ+TI+dWYy6fgf43OyUIuXCxrI50ARa6Zi58HjpQS3n6XRx36j3TFgEa2sku7dKMTXMBnS6VWxdj8n7ssKvqNN7EsIc0Rgo54hWu2JfTyEpGjKaA3KYTbMEE+WMj6GBvaVT6JfnXNWdQU0ItgQl7wvTrSzXjzZOohC7QrmZOPtcLlGYD8L5FM1utEA6WbkGrMR+YxWx7Ag+jtO+3gcb4+7eAKPS2zqOXspSBiuTqM93E+/logwojELn+hDDXeHqe4FHDBycPrSyp/3hjKrQxcxcQiuLOp8T02PP3d0Vt7dH4tXE8/0J/VU7/vjmOcMXVsJ3euj47V/7Ad/9V098++ojrr9t49y/VroHdeFgA6ymdwr9+7Yqq/7L+WTrrtpkd3sHVFLhZnfm/jSwxRZbbLHFFlv8bOKnKT35q5aOfPxvyt/68pMvy6z4L4B/WVW/+Hk25p871rubKwZA3Y3UZAlgmHTRlRBtm30yyyIeWaNqHNRkL2mzEWy6BCwbcW1H9u3zrMEDd8NYJw0ENR2DzhPGdUVOAZmsZhxRiiRL2Ju4nrdx1XddJ6gAU4CoZAdDWtbiO+ZdP6NDoByEPEbyOZhTQm1i0ZbomrYGl1ahVf/CE+G8V9fA8OMrRkRLBNdjtN4EryCFrMatWqQKnoh6clkTTe+jVkDD3TJkzXDxXWyt7imi0EGuOXVcdrHD2XY2pdB27MPsO8g9zrwQ5LxqdljpW4j3qTIj/D614wVCBc4qJpVN2V9EzUnEOzxPsYEJJqRXTLAyC/M5oVNATtXyRFbuNNh8SqtxTGqLoDq2gAF3znpo4zfXG+3tfmPJ2fEckd6cQmIsiFiiVJOzaUzkMdra8PG3JLtcrEmymKWos4pKXxbYE2AOri3pANTqGu1jijskQE6LpoUl/wa4aBaKChILcVgYSSWaa0sZzaFDc1hu/jm2uSV9JiRljh3hKTQxVjlGpkPk9vbIYyrM50Q5B2TFVkIsEdckzHJpn6oOItTfNT1Fp0OVQRdxWAf2Gli3HgRd/b+w6OEkbXbE7ZnkY92YW+5mIw+huSHFk5B3djzvC6JC1e7EgZ/g41iCktUBLbc71rQI0moVKfUx0wB6PS/6K2B9PCZ46JiHTNhl9NpQiClFpltbh/FobUtPS/ulmG5F8bEbnwnIisnk4xPPlvD3DwUNwrzzzwLn58r4zD5nuhDVTcrug2QDbqcn718IDHelad88nQdOzzvKzWzPcAE9L8h2fAjs3gTCBG++NlCuZ74YDnzz+WsA/uRXd/R3O9IT9J8m/vDVB/xrH3yH9HcKfyLfAOD2jyK7L4o7kxjwM94Hyjs21967vefDK1PsvB93vBkHztPyT/nx3PPxd98lPG7Mii222GKLLbbY4qsdXxas+HPg9c+zIT9NiGJ2mHChvlGFG2uiWMR2MtffU+zlt37v7RdesJfg0oOI75afl13MtRWephVgAVbOMYeWuIbRKNtru8jS+3miiSnWpLweV8oCWBxjS0hrhwxA8N3xWJMkf+H2hJFsgpFt59hdScDAmn7nloY72xXPtc1giaILkZYcYAyWxFQgoIInRv4w20qlCT/KLITzcryVkeQl8ahAh9akXtT8U+sY1jav74snvWBgwroNlAVgsIvQdoGba0c0NkT9/zmKfS/JBSuiXS+sLFBHWTmJWFKowRIogiWba5ZO6VeOJyuQp0ZQt7hMgdhhJTsq6BgID6m1QYMl2zkoMgXivALpvO9NiDQJpVOKJ6C6zza/gixzcn0fx8VRppVHRW3lBZwjpYvkIZpwqGKMhXqeacXIKLYrXRKIhqVsat3OeQGdtFvdW1jsPRVUF9ZATYwBZ9BYEqkrQBFnKYQsRjwagM5dMIKQhowEcz4x1kZp9ynPoc0zicpuP3ISJYcOnmyyhJMwvRmQw5l+mK10Jih6Ws2VldVqEW0MKDtB7eOqw2vwrquKmva70DsbqjrHzLJiGYV2zlzdSnp1MUldhEkraNGQVYEiJj6K3Yt4FlIVwCyG3pU5WV+ykJWFEeb3Xos2K08Ci9NMA1asLErFQeHad7U2hJPZppazA0su9sqVQlDD0s4ROQXSY2iATJ3jy3PW1pjM0kDSMBqIONzhJV9WIjLvfRhvizFJ5kA4hmYfTS2py2ZvmxsDKhDPcPjMjqenwHQljM97m+cF0pG2ntLRBDu7x8LxZeDpw57Tm8TnvT1r3333gfsXO/afmePK5//vB/zfv5v59dvPefNrxoR4efcB3b2QTkqYlXiE/WfCMVidxw9y4DwnrvqR05y4Pw6cTz35yZ4Z8U3k5uNAerxcX1tsscUWW2yxxS82fhZCoOtz/G1kWfylYIWI/Ff+v38G/GMR+d+Atresqv/w59i2nxzFdsSr44Q1Ct/Fl0a9LklbiUV1g2gJdP17RWW28xibIsztHX556V+DFYKp/selDSJyQWYIowEfzUpRADXqsKay0NXXlptigAXTojTfrEDN3PSCfk1eEjrJcrkzmz0xLNLaME/R6fSY3WQ3k/NCZy5FbNfQwYrch0ULA9vhVgcKUGkv+037Y7LBl0kQr5uPay2QSpFfjSlyWc5TlfBbUipWq93q9OfKVNCFAZIXq9E2H2oyDa0cpB3uCxQhJ9fNWCeYtS9xAbaKW1rWNld3B40QT0bbrmBITtpcT8ypwe7FulQmPgVKsV1mXPskPEX6O3cGmFhKklbjdVFuo8vUMWq7kN3GcBJF90rsi7VdMVZBuxHa1olWRxFZ5qoBNEoJnlkXgWk1BsX7LwrJSBva+3kq0FR8l7pbzk/QZd77eZreSE30nelTwbe2+10EZhrwVsuDZKaBWSpQQl2QSuzUrGXVEmRdr+fWBqGMkamLxFgou8V1Jd5HwkPkzcPerFabM88qka/aI6LI4BarDYCsDwOWz6/BCjFwoZ63ATdLzmwMr9EZGwHKPoPbhErS5YEj1hdVmh1tvaYq0BWyVKZWaA5AZlMsxAyafC1NsdkCG9C3gHEGEMHFoq3rp0DIQn6KZF8Q1VY1nsyNI47CJJFy5d/tTJtFImiYKSkyJV3GWGgMs8reKLti97ta6YoxI/JA0xuZDtJcVYw9U0yHqN0DXWxyO+tnFmOajc8Kx1MguevK7i6zewXzy9B0d+J5AcbVy7HiqOw/L4QxMN0EXnUvANh//YHzBzOHTyLxBM/+GL4nH/HqN/f86ouXAHzy9eec7gb615h9a4HhlZK8Nut8t+fN1Y6Xe22lbfEEvbcxjmZzGtbA6xZbbLHFFltsscVXMH4Ss+LG//6u/+n9D/zoq/4vPipo0IryF9ABDKTQ6LTh+rJadRVWVGr721/IXQVPStW8EKvLBi4Sa092gm/qNiFDsN1N/3zViCirkgNwwOQklBTRXC6TgGpVmhywWIMceMIR6v+wAmj850ijfLfv+Ut5bWM5RcbZyihiXxh248XQqtoObN2lNWFDRaMnkNF2fmXVKV0lLQoLmDGLAQvrGePMjQowrIXiapTkgoFOhUbU6ev+2ZoYiZ1rXXJix/12uPil6WUsYqoCpgtStTd6tZ/X96klX2L3sNLs2wdobUON8dLsG3u1eyGKqtlCSqSV8YSpJm5C6cUtJ832siYmYVV2Uq+1Fm+tWiMXO84RSrW0JDKrUOJkGg+ijgt4GUiny9zw3XSSLlUHnfW/aoo0AcW19kizWQXtnRWQF7aCelKsqbT5Q1RCV2uGoJwXYKGur8oeaboQVUsmellXXRcOQC02oQKToCznnOtzYS1k2sZ1tY7GwFh6S16FxpbQTgknIX8xcL6dDEyYw4U2CDjgUMRrflZgQ71OBVXrnK8Dnd021MG/pkly8Xzy8ahLu1uNIa5DsrbC9c+tn4kCaFesvC2qVS41kU7xNizlcRdMorhih9W5PgdbM95GqeVA2YDe8DoaiMSqHb5WrNwDKsqrQ7DyobVOxwoclM4YQvZcCSvAR5tOSEHJOzg/l1Z604BsIDwFcki08hj1NeXlPJLKAqSp2Sqf3lOCHz98At1ToXvM1hY/99Tb8fFWyL0Qb83etjuaUKf+mX3wXq/hZqZ0CSnK8Fp55w+E+8cX/NPfcUDkeuT4tY7cB7oHsyiNZ9i9snHZvVSkOGNkZ2U8Mlu/wYCap4+4sGDeYosttthiiy2++vEXMTX+JjMu/lKwQlX/GwAR+fuq+j+vj4nI3/95NuzLhO0iL17z9ktWZQcCqkit44emP7GuJYd1TqFeW67tXBesjfrhmvCU5Xo18dC3VCJL8mTqovGWhIURmIK5AKwAlNKXJQkTXcQI/bhWTYC6G71qX2MPSKWWr46XJZnRycYon5XjFJC4qj+fg7Ejmpgoy84tlpRJLC7gqUsJSU0soqJD9uQ0NLHLZQxtfFu+4WyXCyeNGU9CtEJRoLQdTSm6JG7+Z82qsORIW2mIuHtLTbhs99oERjWy7PzXNlThvVpi0WljJ6zvxdtU/0vmhS7JvW+Ws9oFTkdjB8VTTRhXyaifYz0mP5KAyGX/6zmiX8NcEAIzHXmXLeHL4UfBugryRBNlJc3L8eKgVZGmS9Culay0ooFNAVJnZRI1hy05oql4OUA2IdE5tPKAEAzEqGUCP8JskbeArujz4S2wrgJOjPa7uvuOeIJb14v6XKiJd1/aOSQLjBFNwX5fQcxO0UlIj4EpLmUSa4ASVues7Wrj7M+cRompfa3r0X/puibVXaaWyrRnUFIK4qU0K+ciF0YVL/uS2UuWVmuqki7mG39+YABPriBPdbRRf+6oWulbnSNqTJsKNLaONLxkVSYlCiLE4wIutpKmwdagOiCcjg40THIBTL69nrSWjom1u4EfsQ62PednWZ4R9d+Hur7SEcIczd2pAnAizZlGe58nUZ11BfMz5clRphID/b3QPdT7YmVPTRPjhT3vwwzdG6G/Nw2N4aV/PkQefsnKUlTs+b97XQh/IrwczDFk/mhEbzPnAGUQ9M6uUefa7i7TPWQ0CtNV5PTCRE+Pt3Z8ulHyu9OlVsgWW2yxxRZbbPE3Nn5SucnPEsz4qUpb/pef/npfdu/lv/6Sv9tiiy222GKLLbbYYosttthiiy22+CvFT9Ks+HeBfw/4hoj8d6tDt1jV+F9vuLjhWhRRoroThaE+IQs6Y4J3+Gbmik1Rd/Ea3uS7chcaFXV3srIK6u9Xu9ONiu+fbzuAvgt6UToQMKX9FQNE5tWGfDZafN5po0TLJLytk7Eu61hT20XFarjrjqPUXeBlJ1jysouL0+arTWntW3Aqemv7akdVk6K9GIMi2W792rpVgiK9UbpLULQPlPESiavOHRqVEOz/F8r4MobrnV2z89P2mSaw6W2WOha4vkSoY7yMfXNHCMDk7BZZatwbQ8bLiJbyCm3WltaAOlZurRr10kJXacKTbT6ExT62BDUxzcmEAet3SoJ8ZZ3Iw+L20Sjrq1KXxvxguYZMEH03O47Q3QtSAvNB0F7bWLUILKVFvqMefYxDXCxgdPV3JTVEd5cpLrgYQnFrxUXvoWqBxmjHAE7HWk1m55NUXO9Am32rNj0IZ/RUaPXHOd/U8pGuoCFcsB5E3aVkxaxoZQyA9rS5rVGR2QVxfYcdjIlQBpCnQBgDZefMphVTqZ2wsibWUY9naSyQ1ub2JS7nOyzru5YqpUK1qa1uOPY572Mtc9MfPUe1/mQWhLC4HFUL57Vwqrcvp8WFCAU5uwitl4oIumjnXDDDfO2UhRUis10r+61vtsjr52yuIrdvjSegMTYxYUJl13DJZgmmE1P2xk6pDIzah+owUjol97J6Xji7oxNr+8FZSMUe7vNzu8YxRcZnQvewcraJMD2z4/ONs3SyMUTyXugezEoVjGGRexMNLb0xMkQD/UPh9s+sDfelZ3w3Uw6FMQmSI3m3PJ+kREqy+TvvhNybq9D4wtfi3thOa/eqLbbYYosttthii69i/CTNio+B3wf+feD/XP3+Hvgvf16N+tLhL8Xrl3MpRuuV4KCFu1LUZLW6M9SXVMmrxJ+aCKrRhyt1vGZbLQuu11ol57MsOXVwGnVzosAU/Os1Et42+7wJ2y3diqNYPpJBk1HLwyRvgSNWOnLBj1+DKbNYbbpnxjLLklitoglxvn1c1JL/LEbXr5+poobBBCk1qlHm36Lwa1JT+Q+K7HIDN1p7S02sLKkos433WjhSPNFp9oxiSYZWwT/XHqmiqWGShSqOa0ZQKfF2v6QsYngVNKgCqGu6OhhoULoVFd/HoDlxrBPLdTlKHcIqcrgqPZGarGK/Kx2mSlmkiXjmgzLdLGiCvJ2AyjImpV+VA/kxmcxxAaC/s6QmPbo2RtWoaPepjrEaNb63mqpm3YsJsbY1U4fI50oRS5irfkkhkLM2UMLG2X5Oya1EqwuHg2EiWEmRAzklcwFIqJfv2A82fhKtDMnasPQn9sWAhbmqkmIlIJMYcNSAxFWZgdr9q/ob6vfTGldPDNorWXXRJelLEwGlCmLOstLw0LZeTKlWlpKJzsttah+DNHFKFWyOr+9rbWdkcS4ZQztfAx1E7VxJmQ/WjpaoP6yS1wISZBHJhCaAWUvPQKAvF0mvT1H/wltz3tcnAjoU6/Ig7TkZzrII5qosehTrPkIrZWngxeqZoCKUUJrWimQvvVkDG1UsM6dW7pMPi0qx+L0KeXmeV2vSMNqzaI6gZCuLCgrubJOjkg+B+dpLbhxwqq4sdU4IprlTbtT+Tnb+3RdK/xrG5zQnoeP7wrwLdK5Ts//EtIzmd2Z0r4zPzbWl6nJoEMZzRMqi5zPvlnGMD4H4WaS//9Hn/RZbbLHFFlts8bcv/rLSjS9TIvKzcDX5aeMnaVb8E+CfiMh/j70G/hb2SvRHqjr+Zd/9hYQnDWsBtbaDOVexQ09CfVdf07Izj0IoJiqn/jKpBTT7ZvhsL4Jhpu1qL9eWRWOhXqMljPWl3IGLub5we4LnzI1W361L4lzDEmcDS0rnifiK4SCzfaaqz1e7VvDERUDzYkvZdv/rddx2s6x3JSvTAhYNh3VCV/uLvdy363QLo6CBQp0u7idpESusgp1a7GdVFtHF1fVl9rr9sAJT6s7/KoGk0yW51MAa9cnXtssZzqHpV1xE7VPt17z6PeZeIsWwBA02Vy4sZH3cKw61vgftPtUDPhcv9FV8Lqos/URgvlL0xm03BWMorDVLlMa4SbuZ1FUQwD6f58A82mQ9dz3hJHSPQhyxpGfBzbx23/4OyUQaNWljRWRRu9a6b1lMXLIerwyEYLaWWqLZ3a6aK1g/Sg6UEsjHiJxi+0DpCzIUpCsEnxex6maokOdgAETVD9ElRw5dbu4ZIZRFd8OjzME0McawCFy+DfLV6RHVxDF19fNypsWWOCmhKwtBpbatVNBPCam0NdsEORsrpzSwpd7oxaWlJt6rBVvPvVpDjCvwQVk53RiQEvf5ghkzhaF9VrQm+SvQ1cEBHOStTjdr+2AdlCIrfRqlPSO00Oxqxd2BQlca+yOfoiOOPuYV1KlDUp8H0//P3tuF2rZt6UFfa32MOdfa55z7Y2KVFZNgiEbRICIo+CDGB9/8/wFFFKKYPER8UHzQFyOx3rR8EYIVlCIPUfIiCkkQBCEvShkiiOYpMQErhNStVNW99+y91pxj9NZ8aD+99bn3vbWLOufe2lRvcM5aa84x+uh/Y+zRvva1r3ECN3QUHYwA9E6yEsfOVKFOxo4BDGwhGs8t/3dAvT+dATAnUGzsGYB0gIl8B/iFTGvlYsyx2Ad87dBdrDRwvScriYFK2wD6dQCkfNr4ayWkfjVmhP6KH3MotneE828D0BTyxQndGuDsJdlMnDTAZas0BFy+a99vL8DlVxWf/eKPn/y4bNmyZcuWLVv2G7Ffi1kR9k8B+K8B/BXYq9jvIaI/rKp/7mvr2cdapCbkC6I5fwyjk0tTtHNE59ABLqVF+QaAyCLccPoye8Rf5nfyEY6mkSbijm5NRQHsb8GIDNbv2an8U0lSGn6J7gpWP+9AvpSGSChULb1FLRqO8LUK4kFx7ej2IyAWjk+Uhdx0KvuZgpDbcKJJCOSgTzA9WN0R8/5HuTwNsT4BdGOL4HsFgpzKoMLH+jWMEoRNLfIb8yBICpjERgAAIABJREFUxy8ixelIueileFWYBDOu5qgJwaj9zn4pWIOt0wZQYW/UNB/28qjBuJmYEYTBoomP6xpIAZP8vOp/aofTwQ1wi+/kIqMZr8ICpgQjVJEOK5GiNfEytIreGe0qCWDcmuJ8t0Hbhst3KauHZCA6SqkKAXf35Ylw3rwDHP0bwBXdC7sE/v1zz/2nglEhp9jpwB3fGVsVFVXgfGL0LyRFQINlAQDcBL3zAAI6AaeBEADAT6fPh0KELRWFdAxys3XKUpU6HOOxkL4xovRoCEAWgVnNny5OeXL2Mapo2ISYECk1gCPFQi0VTA/fLA+pNaCxpihrWy0qrLDfQ/0iw2H2Ki3p+O+CtnVcr8cg/wjb2rxuUNHB6jjndAE6bH25A72xMbTiu+sAYHxRZ+Bnl2ndmQsgc7Ux2BzoYK0F66jJ0B11cFI3gkQFogPOkIGlWTiYyjcapDCFlajdbb5ZCXQjdH++67XjJAW/MqiTV+xRqO93Ou2eYGdQ6Q2QCycIo2T9ZJ+TeJ4EMKte1UU3QK6C5gwncTDl/g24yKhaSWcxcEQuwM30NdHi3lMApGjPgg6g+zXkNKZd/rtCtjmjgtD24lvistJAli1btmzZsmU/3H6crImPsY8FK34GwD+pqn8ZAIjo9wL4MwB+vGCFlkh1nWc26m2UWezQjM7ppgMcUFe3Rzk/AIhI0XCHkgjvR7cxR9nzK374Xuf3eTo9oh0RyWBghK+zubPe7UXcOoIEVEhhGhyzLzNy8CNtw+cnWSEPe5HEymQqwQrS6vxd9olgL/SbDoeU2SJ6pQQjOTgR00FJrzbQSDcqzA5kaVm4FoSt57welGs8opOZI8+AYsybXjsmNCKQh6aQJ7WIsdPcrR0DXSiqSxAm1gGdXlr0iHmNsfv3BItQC4aD+TiHDmhUXzYdXDFtEbnaPGiUP+0EfYlE/3nNcmj+y/HacOx7VmeRk61CizuAbeugz4CuwHlsaK+EBx/Y031sr/Pdp+w1ACHTzZDCsuE7JrCiPwEncc493ykjyDGG2Id82p7jc067aU+E4zRdDXkW6KY4SjlJuTWj14fWykHZH2HNqiT95vn8pFltJECeOEaVwE0hqcVg8643NueZ1FMq6oy7T0hIJ1tvPOtoSLCN1DRg0LIiA3m6AG1q+0UdbHnY72U7Q3SAJcQ6MRwAAyQyNeCgZCNEn/rZcLAmYLDtJ0SMwRFlhWlTqH9PrFaq9rWB3pGDuYS+lU3ICrp4GeOoDpNVOTSfkcESOalNY8tqMvHMONiqtwCQk6Z7E1AbYw67ON/OrtCLALc29qM/Q8QrIWlT8J2TxaOfncCuEIjdZ7sCu6Bvvtc6gV8ptTNsrxJ6sBpgAApdnAHk8xDAGWJNN4FuQFdP4fDunc+K/pmlsQTo9/QdRruN5/u5eQpbmVu+9gHIHAy5sWv8EOiirkcynk/9txFefvI398vHsmXLli1btmzZr2UfC1Z8P4AKt/8Xplvx47cPsQYAAyx2//6CIrCo6cNWsc3URQhH1L9nwDQmSjS6XjKAgXjRtLbc+eFRXk8jgg2kFkNoKqQDFOfvir7Zi7N6eoA2DCE8IXeUZ7yC6i/p1Y7PatnLiATT4WyD7k5aOYbCOQ9nvkTX9SqWh91DwC7aHdFyVbtOgEbpnPp859wQ5ZzlHEeJUP8sc+ErJd0dRBw+iU71zrf6V0r2hm6azlRlaKg6u9odm0lz4j7aCaq1bHMfppQQFxScABsXe405AAroJAPB0BaMHhgLJOahgDt1TeteirXV5hkMm+Y1+pue6yYXzXYSOFGL5JKnOhmTBCn4qQ1or4BcKPUJqOzlmEeSAVa0V0K7jXtO29y2nV92roMY1K3UZX8m9GvZ7y6SGNT31BfxMRxbg+6u2XCYI65Fk4KqwC3BRCFR5pPg+idRMpSMecAK9UaoySQgi266IGO/jn1BykBXaFcT+4QDUVWjQcj0Lh7BzpImlHoMcb4f0/lxc3jf74wQ89XO6KcDAIElFC0NAqCvDUqSIAhfOghe+jMi9nWeSolaVTKGRB/ilSo29pGuBui5jbFtzrqoD61uaVoAgFdnMFVpjc969k+bAY7szyxpBHxxQm48QF2fQ2LyFA6bj+blUc+dR0loL/+aY4ul8X8v6DQWHvXBdqCzQXeGXGWsQ6cUIw6Qu38G0GcnFCc6thQ3lQboRUDPHXgiyEmQ712wvS3ddxZGe8uQJzLSzC5oT57ude2Qqwsad8p9e37mfeyWAte+KA/cZcuWLVu2bNmyT9A+Fqz4C0T0ZwH8adj73L8C4P8gon8RAFT1N1A99Tdm4RS/ZwzopbzkPpojDnqlyREYjAj7XA57uTWRzvmFuL5Yy+REI1+CFQppw0kDkI7rBHCUpnVynkskMh2bUNp/ePF/cGYnRxqPjr4dGKkimQ7TxjkpMqoWwdNLSbFoAQBYOgpJRNLj5b/0KQT+bnNFEx/KcOiLAxwR4+hLAhWVRUMwx+R0Snd87CwafmUHrdRSTNKZDNSABmBFBQxJ5wqQq8/FCWeDlOsDQ/iSgO5irwOgMicpWAs5H+EDkjngpEPjJJkJweaoOikx3gpWhBBpAbykUZ53vvHqBn6nh6xHAAGpQeKO6elttpt/fqhpo9y9jzyDXiA7th0jetzutp/aYdeQbcyVzTdZ1YX4XM3Jazc7t98IckFWPzAgZ8wfu2BqLmNjyJVGKlM4oyleWYECHek0Ho2nTYFIE5oEUMcwVQsLAsh9mE5yjCsqmMicKjPtX/g6looleZg/V+y+oAlkyb7w+2se96hcHVA4CMkWiaD/5UEnAzDAxOdJWLFdOvjSIc9k1TKOMR8gWMpIgC2eKpFsKIaBi36vkZL1I4GAGBjGPb0p4HstAIHYozYXnM/xnKN4JnlX9CrAa/mnjIxJJzSAu83vJ3619uI+p4Md7CwXUN9zzVhPfBtryZ6Kwq+DMcInpu9j3/QLgy4CPcX2T1gnSyHaBPwkuH9LjP0R9/QBsJggbr8p+p0gTwJ5tocI74L9ydAPq8xDkINzCHISaBc8Pf/4ZaWWLVu2bNmyZct+I/axYMUTgL8J4J/wv78D4BnAPwN7zfuxgBWZYtFLekHhuCdIsVcvGMPpYIVePPIap7mz4vIHXh2BIMSWMw0MR4N1NFUdk+KQyjaqV6C80IaA3QfHVdK+08E+C1hSz/sAWPGokfGhFJBxMTstUsvTl3KNiRApDQAAlYESehMRqa+OtetJ1Nz/87XQtd3xhhhN+r0Ic4k059hiUWr3O4FvDO6w6L4Wx+EEQJ577mkotWQndRffU0tvMTbNcPRDz0JJgdAm3Mr1dRa6010hbbBpLPcfYHipT52250g3OYCsJUPu/AQrQd05dYDLykaWNlzPg7y/uo3oLmAOYL+E8++OIg+ALyqsgDyi60BIRKK3FxPmDJDERBcHAAHYfuYbBpMhtDhSk8XuqRDztFKLsNK8vrbbW8L2am01TxMJMM9AAMzOYZkHbQS9m/CggXwOGgTgFcCa69kgCAKxbqojfSEYPMHMyFSRcgNtYqkEpKBzyz4NBoL/rIChEFgKEOJr+YNuyzw/q4kUYAwF+Cn3ozYMMC8q7dzHflaB3QOh9WL5ZMbIgIE3R2fTjrhY5/S1zbecs0EiHScBhzRNhpJCJwAyc62ajDna1crAwu7fPN73PN8YKWMa92WkFB2E82Wz53uIC3cyhpITMgSwNDDfS3y6vgUTQIr2yg/P2wFU6C7AVdB3zudfe2Hw3So21bUIi33ZXgiyb9DPT0sJ8b0YzytE+ds3J/SbB+73C7a3fowDdyTGUuqvBLkQ+rNNWn9jwAVfTJNHFQZCxRh6g75sePtaH8jLli1btmzZsmWfnn0UWKGqf/Dr7siyZcuWLVu2bNmyZcuWLVu2bBnwkWAFEf0+AH8cwE+q6u8non8QwD+rqv/Z19q7jzASAs5ROUBbMBZG1BYnjdBZidzZT2dV1MgpYVLm19MU8Wt0TLsOKr0SeqtR1PG77moROlbAheqkMfi1RPaBZDAAgHYKRnWyPAgwxf/Sd8VDVC+uXyL4UzVPHdoS5O3UnPsaQVUOpsAYU843AJzBrhj9nsQ8nDWR5TxJLcLIZZ79en1jT+coqSARtQyqfkTpQSOKqqNqgUVSnR5eK7OoU7NdzLK2NXjTBHaGQmpPxFdeljWjx1U7glxnIkP1sCh+zbm/OgPngtSuiNK5pAA2yjSIKIsa/Yh1kL2k8Zd1fTQ+7NhgWwDIXHlLC6IhVJpaGRZJll0nEc3zjX3f31rVjhYiiLsxIjJaT8D2jib2TqTWcLAVYh83S5WRi0WHo5ykzVODxLVC7yKmvJSZzLkq67y9GiMmtDtkN5ZI7F3d7G+NtKuH1Iuci11HOc1gx7hQpKpO59EuoIuix7mvbM1sRR9DMVKOuov8llSS6Z7ze6fq6WAbeyn2Th33lBqyqaf7zEwsPi1Ny8bAxiKSYFZgMJwA8N31PnYCPXXwJqZVcowHTqauKCy1qpU5m56hfk3wzI6K40KzhBXqzIrOzkbqcd+xr3fso7EAwYZobxn9c825sEohyDlgZw6Js3j4tTBUyJlP8SyDs3lyXvz5de0mPOp9jIeAtWMsntxbhdW2vRBOapamUqaGDzIdmJeGQwD+xoHz2ye02YZtL2QCp+IpVSeg7wj6pffhiXG+aXYP1YpIMTe3ZuWKv/eBh8SyZcuWLVu2bNknZB+bBvInAPyHsPKlUNX/i4j+FIAfL1gRDm3VkoDaS3S85Ebp0OL0aogNNoWew/HNFqI6RdNRPrApEIJ/J9nMhZCmKKK0ZLSl8f5eqPfRYVVLSbB+ASY+OZzwzEl3ajd59kPVeyAB0DAJN6a5Y5g57eHwhDAhHHSI65E5dFNaShW1I7VGHIAAfD6peAjZx9KIC4maAx+Tg/lnU0+1MYp6ppu418bxO4aTEWtVc/c1UsgLLpVihvowNszfEyK1wBxekdFXhms1RDsPoE6mIkUJwqiKEVNw0aJH4ZoK4VB2b0CNnt5erY0KMOnuDv6GSch06FMM4KM/kVeRGceNSibeX9c54dyLQPe0Dr0MlEtd5O+4MM47Je1dG9CfivOlgG48+qUD6JqcUwdJdPd7axeQgxVEis6A7Ay+MbYXr0byQLNPX9XXIz8/kWkwYEAOQI6R/iA70IXQaw7QVKN2ABXkpTf1tGM+BDxoJwMUr0PEUJ543P+5/4YTrSejHx8ATePQqL4TYFiAfOGEdjs3yormXsz18mNJTX9DxnH5fFS1tA8XtLXSn0PPBFGaWAx4eejipJGj8TzcNCueQGFVQKTMby2D4/eJnmWS2hijPnVLU+lkqRsN1p/puWSpdext8kGQO7+Hk6S2igT4YX2UnWYwsPkjzceVqVLkc35nA2Oijxex0qc3T4cRSzGJ0qaA9Zlvdk+0F7ZMogeAjE7CdgdADcdFwE8nevThwpDdKoS0F9NnaXeAvCRpe7G0qfNNg1wU/QLIdeSyhGBulDJdtmzZsmXLli37VO1jwYo3qvrzRPWtEecPOvhHZsUZzZ55JQA6TaCMBCZYdoy+62ZlC62ywyzsGDnvuit0E6i4sN5D2cCqWF8F4wBAm2Q+d1px9LMPUYEgfMRBgXAHQyf2QkbTH17eEaeW36toZDgJBlZExNgaCjCkNwd56kv/nZ0V4pVNUN66ndFg1xsnhXZHrRghjRCCoTmGUONvo+IBpjmAR27jC7z3wh8ghAYjoEapY558bqvGwsRGyXd8MmdOMa+tH5MR5VLAIdfFo9PslRHSN/Pcd2yw/PR0tosD6uskJ6NfCNs7msapOxJcC6f8YQqyjxLOf2FWSMfEJppYJ94AiY9LbFAkA1DRXaA7IE9kIJwDDanlIIRz7x+OuBdHfwKpOgEnQ0slFGwCfQb6rtCNjYniQEu702BaEBy4KQBMFa7VWfMDcKFOhaNbZeKiDC8BQmKldcPZPp3tMynf+nw5A0FYsV3tMZhlQcVhICUwSzK0dCfoEyX4KcIJaALm5BOQGjDEVnqVKqACQA6GeB+paPGwX0dOtkokbGyzmclEOTdK9ozrz0M/Qi8C3A3owKtXm0iaR7kHYh0BK+FaaT7HmLdJ2DSs+5wShrBxYbkhGDqsUMTGHNfUppCnAbqp9yuBqWZitrpp7hOggKgtwEN7ZujmIqHxLLuqgy3WLr2y7YtkVqnpbGx97OWmYy3I92+zueEDk1CqepWq/uR6MC+E8+1mWjfXnsecF0G/MfiZsb0jyCuS3RSaRO3VQA86gd5HZZrQnKkCzsuWLVu2bNmyZZ+ifSxY8UtE9Hvhr41E9C8D+BtfW69+HTYi2/Z3vLzSYdFZ6h6VKqwEicoTlwIARHsAtAdLmaER7uLyoh7OV+ZnfKBTOzLKaV5GDfnbyzTInDOOEpnFSWW4yJszI5R1ri5QKzNoHOcvxFH5wr+jTsAxBZLnNBH+0Bc2f3ryeLl/SJWJ+ayRyvw6XvgFoAajZiuVqPMAOXTTHHtGgVkhMb5wamSep6zcEf17jFZ7RRcAk9M0YR4pjvn+l8lkkHA6MDvewYAgRVLKS5UK3SnTSKZoec3pILgApFHNj42ndJhMt9h1pAFhpEYk00VHCkiWfcW4P4bD+f6WDSe0dUawPCJVRXZNMEhDyVPHda0BjOoPKPupglDwPkTJz8Ikysh0VGS5KE4GOCqYbDDg0Z3QrCTjfeC7rYVc7BLc7bNMgcn0iXK/1k1AwCmMXsYUTJtRUWScxy6O2yk1c7FdzmxWOzkQwyCnWxALtk3QmoBIoUo4z/Hw6WezcxwcIFIQyyAvxVx5noMqoW09v++dIMIg8bSli4n/TuCUwOY/QK9OUOYUuDTBYTEn+07GICssn9xLsQcOB+cSRJ1TXXRTyHNFBsu5CvvXR2nA3g3jmRptPFZycrHKrrYnoqRzHCdM0MPABfV7rzLvsjzx5swesUoaKM8dY2NQpnRQJ0h8v3la36ajnK1SVpYJsEU3ATag72QVR2pq2SZW5YSNBdFuhPOlZToMGKBLhzYDk+SJbf8H0CxlTzvYHWWB4VO4vQNef+Jh7pYtW7Zs2bJlyz4x+1iw4o8A+FkAfx8R/XUAfxXAv/619erXYXybab1ZmvC0yFM4LmGRWy8AmkeQp6i7wqLkmcpB6fw/liKsUfha8lKZBv05KNHArJcRqRFN0RtDthEZgyJLRYIsEmeaFu74dC/L55F70vIS7n3ToJB7NJzVo8FRztXH3C+YWQk5Uc5KuYfT4IyBiOBGdYYKlihKZYAxZrmWqgDFcY0XbrhjUIGjcDLmcqXuzMY8V0emFRQhfORakrZGb3OefI3Uxme5FMWxSaclwAiaHH2NNXAQQp29MnQ9jI1CDGiUgKyR7rIfBrtAoFFpIfotPr5tnCs8wIfoq0WjaRrjKOurA7jSgUlVLQR4hJkPypKcpmVhYElWppHSv0cHFLA0A8aosBFsAbU0A0t3oQQ3ZFP0g10vIMBBtbKxsL0aFVFST4QA9RI2fCkVL5o5zNuXQ2eDD3sGbA9gVprvte2l9jfAkWi3HH4G0NIgvtbHpaWjG/d8r8DUJui7MS1aEzAbYBEgBJFaW1kxhADhsQ0c4FD/PYCMcT7ladgMJTSmSN1vUWbUquekTkxUA7lYukNo+HCPe6PcZ0q+RP7clbFk0mg8wwiQeNAWMENbAQ9otAnAwLjYT1OZ2LEn0BwkACDMI00tQKUd0D0oN2rgwgVWQrXMD22C3VkxfW8jbU8coAUnQy+0hex7OCNKAaYsgUs3n8P6HN4c3BOd55AV2rpp9bCdxzeG1HlyDRbaO3QT00bJifZ9cnolpFeaNY/IgIvzzQ/a8MuWLVu2bNmyZZ+G/VCwgoj+/fLnnwXwv8JePd8C+JcA/MzX17WPMLEIEslwJmjz1IbTGRW9RKHh2Rnk78+nO5FP4z1Q3QHQjnzBld3ajxSHjJQD+XJdSysaL0Lc4RkOUEae01sk0GbOqbIMFoCayF2USI1oZzARButCp0jzexY6GQyoGqlaXIsg+iUXBRyIsYj++4BMlMIkIGnlxOplP72P5H+nToAmG0OvMij1k8glOS184AY1l3xiMcj4e+hf6HCU03GiCXiJeVcHESZnIhgI1WmqToGDEWhA30IL4n1HXb1PdjplNL0ycfigHF/qp2iJhrJrTjx7fnw6uWrh+6YjAk1IQb9M1yBz3Cgp7sWxycmAR68xKAGRciQRsfU95ikYfEZZUFv7SeARnpIRa4f5PtKt3C++VsEaqe20AFBcy0AZGRkHLL1FG026M7IhNWTk4se6jkI/Cf3SrCQuTAC0vWKAMh+wmhokG6WYp5b0sZoFpUzYoOguwAnmmaEUE5Jsp2Zjgu0lXAS0jTQRIrgWhpUOtecQLC0FzqhQ08WRpuBmQpmZJhKgBRvoRaygq0K7gWXAAK76c0O/Gbtl0kE5KNMjZAf4dIC2jCXYRbF+WoBOez4g75uRwhZgg/2uwFT6dJrfHAvNG8vPJ/IxOvPA0Bsd88jGfNCTsww1XzqUC4BBADcFO3BUU22kM3rT1LHQjZw9UfroaUIqvufOorVTGBpalZCrxXPsIuiKwZgIhl3za/plbZ8ImqNtMdbeGf1lg27N1zIGQTi+wKSjsWzZsmXLli1b9inar8Ws+MJ//r0A/hEA/yPsde3fAPDzX2O/PspIgf1LePTPPtPQOdBCjS3vbPFZhztkD46+CU16hYl4V1aPogVYUdIWgg3AGFoFJIB4KYJkfZRqCdQpX2T18DSRGoUlr5oRjoIFQ/OlX0AjraHQ0wf13/spmqwEdfr0VNVDrF/JCtHywowBHlC5RlaZONzBhbFXMoc83s8312vgMlekoDbAm1ivUNXXUv0AkdbBAPrs7Gf/gHQeSG2+qMwDFBZxFgDCBmqVa2S0M5wqhTkbyQ7BiCw3B4ZkAFC+GDlekIlVThooFQwB0lGPc6kbnd0ADcYpZNoTMQ+RehJisTW1JfrOmo6JQk1TIkGecmw3Z5fKXkXZYulkNoAjvyDy43v5uziZtI92cowdKXpqH1jVFN0cV9rsOqFJwR65jnYNBCPTUQGAzcEgFw8NnQANqsQGYFfw3m2vXoG+C+RmHehXdsCipNcUTGp6VgRwBtM/iHENYdpyz9+HExsaMJE2kQK5RWdG2VIwZLOKDvIsllIApNgoFOaoAqYFkXuJgHKP9c1TRPw+YmdEqAtxKmAMjmvHFk4uKdgH/Xrbcd4b5GVLVkBcH832k2w0KxOR6WAESIl9pB4BPt7YewEkbAUcJM0xoN5HNNbEBkiDHaQP58Pmh5qCuJtGxzgZIAVvahokvc3pHXAww9M6jqP5PBeGCyta0pXsO0hhqChM16M7804xpQtN+h7y8DzKsY5/RxJw8XbgQ2b158Bhwq1agCtqYqCkGJChVzJgsDx3Xr4A+M2PX1Zq2bJly5YtW7bsN2I/FKxQ1f8UAIjozwP4h1X1+/73HwXwZ7723i1btmzZsmXLli1btmzZsmXLfsvZx2pW/CSAovyAu3/2YzUSoypbRNw/1Iholqhd1WTwyDB79JPEpAAiAtphVGM+kJUiKPLIvQ3ZacgdBBXdz7NrUpYkDUuxzOiTC04iopqhzQCUcqF++GOOt+tdUGhiEGal/qRl86SlUXUdFJrsilTnb+PQ0AeICHmeGW2fEY2mjKRHyUDA0k3o9BKJ5+j/KAU59Cg02mWd2A4xbhIv2ejMiqws6lVKUv/CGRoT1b9Q1iNSml+HtkI9QUcJ2oy+KyztwvNVJv2CIvpp49HRfuhNACM95SxRWPFIuKgJ7Xm5zv7MgylUSj9GOlOtvqKXUnoyFmlK5fHzY24i5aLqZWwW8c7Uh7Kvk/1xUN4vjxbnxfFQY9tktQsiLxFpfZWrR7FDD+JEBtSr/kVqxEQfGZa6FcOLX0KvQCzazZuiXTsk9VUY9yc2YU+/X1IY1tcwKyu4ToXy6Cdg1RVMbBEjvSH2Ffx58TAGY3mN/UGKFGBtd8LRCfJEYygxdq9EMvQr4lqUbCM9g2lkfewhgFueA8cT4fLmSNbA3jouW8fGgut+4nZseNuu6JsvxEEjBan5Pn5Iewo9C/hep12yqgsAsLM4mE2DgwizHoQ6+4QVfPC0jOR7KdgdI/WiPDOclYHmDA7f28lGEXLmgafHdTKGTT4DBRqMG2qQi7MVknnhDJTsv9/z+WCjTGcjULK79BJ91LzPKYScFaCzaFr4kMQZQqnTESlyMe6Yi2DfZUphCK3CmCus9jOel1fB8xev07osW7Zs2bJly5Z9ivaxYMWfBPDzRPQ/+N//PICf+1p69OsxBc6nQiEHwIeCOyBQaPPypFVA84y0BXdKHpwvdmckHBMijHJ/meLgL9hc8tsLOBEOcDj7FPTsie4cjnj5O96HN01nkRSuyl/AjJIbH+kXk/hn2AlEuVltLjQXDlr3tJbiWD1qD9AJTzOIi83zSKfR2qFDImF8T+DN2g0BU6tYMpwzSxMZTv57ud1AalHYSe7wFe2QrJYQTlqdhsBpgopND5fIfTOfG3sifcGgrrc5BYN0UL2zZGldJ9cLMUc8EaSiueHfbQzdfE4FBswEg1spfbLoYwWNBOPzHCcPB1P30B1Agjp57ViHHSBHgOo+B1AEQ9UEF4EJeAsNEPW5HGUpxz2phKGVQUgnOyqwKOuUSpCgUzhfrwYQmmZCgG0PoocnQT1fqW8C2jRBJ/LqDXKR4Rjm5Nn69hsPsCJSpy5jT8rumi4FlGn32ZGve+hRbsE7nc8DvgE7CN2d2FPIHE7XDaFu+yB0O2yv0g/UeUhgLfRAxNJf7p3QnyMd5sDRG572E0SK637ivDIOH9S5DVSSXAdiVDaBO+7+TPSyrOSpIYDvgRi/C4aKEAQtv8/JIZqAOOugrzlparJM4FukygksxSP2u4wUPRwwfYq+/v/TAAAgAElEQVTnksd351xHZbZUt7juKwOEBLbAAHbTE4ljNAAk7yM87c32rZc1vVYhFzKQ6mBP5bP1tiGMFCw+XDQ1QNttrDUYwEUMQPKUGX4tNyYAsA69lvpc2kzAlT6ELC5btmzZsmXLln1C9lFghar+NBH9OQD/uH/0B1X1//z6uvVxpgzcv4Us5QaYI8t3uBNo1S4SXIBFNtmBiH4dYEENrke+eUTF2l0tIhrRboJX/PD2o1zdBFZQilkqAVycjBBjBFl/pekkbMkMK3vZAJCaH7QRNMsgRkMYznJ5Lw1HMaPoAJQIIqXsYzARyB1tcacznVCkOKnswwkZkzTGk053BTMEoDtcZFHHvNT186izbJRoh1S0pDImqkhg8fWzdGdEzB/7GP3aYt3q936NB4cznWS/FnmkVNm0Fx6j3eEkhi5BOh2PjAfGXKHEtTCkKfRigod8dyeo9CGAtbo+sd+nSgU8fw4Acno5yTIn2hQS7BAu6x77unra7KCLVxtBm++ndM5l/Iy2EnBh5H4PfQhNldtx/ySY4cKwzfVTyMttaqmkYvfsADsSIFFjUkTlCMCcQLpYqUpzsL2NmLfueimCFGXMsfkxPTQBCMn0AWNUTXHAUjcU0OgBsGD7njpGtSIfY7sRxLVwOERIT0Au415P/QOf20cGUV0LY0YRbseO8zNb7JfnDWiKd5eObe+4XE4rdxq6F5ukmCSx6VsYq8q/JwU3K79q80cuMKr5tySw6MKgOsp+ajBGAGMgXbzjcT+7HgkfZM+BKqBrHRh6QR0DoFAaIspqC9erDs9Z9DHeu78Howjwe+NC0NhDjImtMvaoDr2fTUBtnkPdaPp3gqLUh477Y3pm6gArQgjYxEJtrIoCZkQ/ql6RlPtJCG+/94TL84Fly5YtW7Zs2bJP2T6WWQFV/YsA/uLX2Jdft2kD7t+WIfoHe9HjuzsBHCyF8a4ZjAE+CLJr+mURNQ6gA1txQMKBizYEIC+R2m40p3egVAbxF1NyJzIdTYoovAMCoExTsIER9ADoosOhVwxV/+pwhxNTwA4iAz8y0hp6izpKPFrKRhnTOeYLMKFIvrsTEEBMcYgQtH6fG6pOnvcrHbY2/o55BgDcrW+ye5USGs52Ig+KUTo2UhXk4RrAVGZy6ocMoCVTPibgZQwqwKl0fKT47cXxqHupXj8AHE2goFyIXdizpJBk9JgV+tQNoHoyp5icIs8nLFK7DYccVMCtCqgUQCALMNwLGOPOs2xIR7465trckVdkrk0CcBsgwdIpFWhm5okxmlIw1S8hu05ik7HX8gAufa6gYR/HG3OpsJXKXkkAKfp0wCj6mZLkZUE3yT1QwTervGDO5+QA1vXzqjCRHqGtDKj0V0rGQS3rmaKbm33eXgnthiyr3F5tHPkc83tX75jN15m90hHJWEdlGoK1Yt/RSTjf2qY7P2dIA+Sy4X4VHM+n3XNFuDKZDCT2/IvOZ6cMlDhPhvSGfn9AIMtzgJoJ0uY8BuAVa3WpNxicIcMQX4RgjQXYFmBOFd2tz0w7xwGr1wa9iLdBM4hXHP0QU63pWqyAHuQpVjqEfoH3UtUAgPZRUYT8GCKgi51I147u/9RG5aSJiaOj7VhikA5Rz80eYjKVkIWLENPMPAGgB4NeG27nw9osW7Zs2bJly5Z9YvbRYMVvRtMNkDf9/Rz9TmgvPKj5pJNfQRdAzvGiqgRjFsD+4Ih+Nc9aIE/3CEe/lN8M3YyqI8DFqZfdXsKnKKh//eADDEelw0t60nDORGcdDmQQcTjMMS/kL+TlutTJSp6mp41sG03BQTGPNq4KYZieAA1na3ivDkJEjrs8ggCzoxbjq2k33EeKDakzTEqkMfopABCOHD/MZUlrSTp1cUQSRID7xETzXog5knD+aKr8APgacgx6tDelVFTnI1KSYigET1sInRB3zk53vj1NIXPPZYyhXzBdc6x9TK71WeHgk/pc+J1NAXYk6GDXeC+dJZqLMrbRx8DpCHO1lmhPMelAZEBcFN37MFgcMSHzz5zHH5RjH+BMIGY+3xNoFUBR+MMVHBQCdbZUkgYoPZT7Def1QbukplZlyVtynYZGkKtkSczYa7Ih2SXsqVIxXyoYaR3key2m5nDdi277LcdYtHhqJUyTUhj91dAF2X2KxJz57d1g1rQ7oV8MGOwXK1+KTWcn2LUiIqWDmgw9iMYQT0s6bw042EC1mm0Qz+JWUtkKq0E3DL2LTVLjIk8/GMrN9qz4/JUKRcZGUJDye3sTsH7z4VVX4mFB5dnpoJiUNDuJVKmyBfgkSLCNSK3CDgyAmXLefLxRlUQ/9BxkBbwyh0Y1m/qcC02Zeo+rjztAnabgSDWJ24ANENV7m9fANS7opSCjy5YtW7Zs2bJln6B90mDF8MRHdA4Ez0+30ojU58haRIX1olPUOZ25NtgM6u+AwZRIsMIjl3wC7VZext2CVkwdLtg2/KzsupizHtHuSkUOB5thYIEyPA3hcfyYBCvjfKlRcy19EYJmNFpNz6I6aTLGIiTAjhH9VwddgnXQKWnSVZjx0amukX+jmA8nQaP8X1ybaHZAfW6Y1PQ3xNYimQ/wMdGY68drRw68TbLOYIc7dXE+HzQAjod5rsKaUwpEOEvBhh+s+Pd0HeLzlKw4yXLsTzZtFXdKwpmbTMo4Inc/1iWAjgIghIOJg9Ixzn6RZgpFXaMUWy3H1hQj0xnRqa26p1O/Ij6P/pSUqmhIi4M80nDK+IBxH1YWBsb6vXc/xBRE36PdA2C11A0DXGgATMAUOa/TwkXMk8jO0Z3G+BilZKytZ+gwKCuk0cTgqOVMI3UlmBVR1jSYRnFcAqNa9hMB/WnubJRsll0T5OAb4fJ9Ah8+IS+EALdCH0XbSFuSXZGaMgrTU7iUfSMGJqoCuDVP28CcZpRT6uKapEM40pkbijH3TGNduQmEFN0ZGdrtv+nxSr5WXmJXH8AKANALge4uggzbT/m99z9AI41z495WWCoKdOjlRAlm2M9IJVIBRNgFUX3eYi8Fe0IIcjSQp87wJskcyQ6Iz3tJkcFZSmXDU1KiBG1oiUi5z/1agD3z9E0HfbnAimXLli1btmzZp22fNlgBgFw8bYqCRlTzQckewBAe5OF4cYnCqufk04HhkDQDALIKhEd19f6QlhDOGTkQEdoXj84TIvKJdG4zdaQe59HN7FthDMR1QrCNSxqJNpoBkAALqn/KFunPigURjY2KEzc2x24fTsA0n+V6yQjB4zXCcfKUANYU67OAopoehtB7czSxHzw/fYAuY34iwhipPFJYNFOqS9C0HXTJdn2Nk0nAGACND6pfbRwRjZ3WIebctQq0lYmuzneAFUqp5B8AEnUCi6dZyOzI17k3HRF6D/hKQUBgvh4AbaZJoKcjZiFiWCOxOR4abJw6bfLQ5sAT8iBlB9O4zvmYQ+1DjNWo9KWNKao8WA9S9l6d0uh/brUPMJfqfUkKYyqROa+P2iWTDuEjqJgN+o+DIB3J0Bjis1rABB0OdAVz6u8X4GwKutoV+hOS0WPPG9tLnLoMozPKOnRDYuChn+Cf0WkaKNoY+9twYpH3DJ8AvTwIEIfjT8gJ0gndK3uvphbV1KaYsAq2ab0+GQhKao68lPZ9n217z/lSYXRnNejJCZKm5klU8Sj7X8XEVifR4WSlBBAWc2XzNVX4UX8mBrNDAY3KJYfPc6Rg3Bl05ynNRNtgTJES8NJcuwfAVcYkBVMtfsaUpd5GMEQAEEGifM4mBpDEvYlx7Wh3fz5wPKboLFu2bNmyZcuWfWK23maWLVu2bNmyZcuWLVu2bNmyZb+p7NNmVgiZGr+zIwBjRCTrfxsMisqciAivXOxAPuaIvmyK1ueUBIv+a7ZhJTmHwGQqvLs9sgGCuTAOKMfHd5VZQciIc1Loy/HRRlQFoJIaEakZoTMRaSVV5FA3QDr5eG0uKlW/vRBkJ4iKpxlgjsZjsBGmlIDKPN587mmwLoI1oGzfWxTf8upJjD5uc1wYHVV7o5SIrSkYWlJfKmtcd+9esAeqkGk9zgUwp0oX8LXe1Cnz/l0VwvO9kTnzrHOUX3XMW7O/gzFAXvVhzvlHslFiPJnO4gwiKiNUVih4RO1DJHGrtIKHsZK+10bMJXkKQ3xvNPsyJo3Idv3bDyxMiml/f8BmZsaH2Boz+yH1SIJd05EUeJZyfvz3QPMxzRcMwdXKnnhgmWTKWBlD3BsqAHdO5kNG7INFIeQinD4fD2yLKD9qOghI5pI8jzXX0C5hzUoaYyK8j5fu5UW9f0X8EgD6naFCuGPLqD7fHp5pJ9A6RrWO0GgAkn2kJ80pFk0njZMce04mUg+FXPcj2Fp0mB6QdNOTUAa6IlNlhBncFMwCbgJmgLYTl6u1d7/tVupVAWwEamKsqtKGioKaZVYo84dZRI/7s2oeBYPGb6MoZytOvUiGWpSZPWhKgyG1NVBViKfU0EHAGeVbaaR5ZYri3Meo/BPiR/GckGC7bDSXTg0mRqSvMayayoMeyLJly5YtW7Zs2admnzZY4Y4JycygjUoffQPkWUzAsCi491cX37wK+q7QV84XTmUFNfNm+D7KglZxSW0OdJBR1XUbueLZL5gzwF6mkPoQbrTrALQPB2ui3gdAgeGEhQgkgNRZAFlZ1XDe0uJF3PvNpwES4YwDJhhKp82HNKCrp7SEc3YHeleQMORi6SDvvfhH3/3XrDriJo4chPBijNvmUCeHVDefp4cdGRR+MWW9URrTvxN3GLPiSPWjyNcpUn46ob1WRGnMo/IAJmQb7etuTmgq85d0iCxXSuPatQRtCm0e5OklDw7srlAd+hFTWdOwWhq1u/ZKSZsBCNJdZ6WAHLpxzrOtTQGBKvBUHbcqPFmFHRUlN14Hhd4PUDXHFCWtJcCFalqvhbEn4GunMb2x9ys1XyidYA0nbrphkGVtA1iY9B6IgIasnpH7BeNnrVoT5z2uNQnsudAUuo/FkublJl1HgGh28q06iaaDmfoN8VzaZaRjACBPfYCnJBFrlhWFEnhTq/rDs0Oqwjmx1BTyWcedbCLbK7C9o3xecF1j72XqV5SSwLKNcVbtB8Dv7QpQklppZde/yYpCQAIX2ghnV3S134doq6JvCmkMamLARRNcLtbAtncHUmyxybUhCKPyjHbTd6BdLI3EU6um+6reZ5EW9fDsNl2Ysae5VGUhMs2ITEl6bz6KEG0FSQHgIEszCzDN7+33dI82HfpJMvdBe3lWx38VPGqK/u7T/qd92bJly5YtW7YM+BrBCiL6bwH80wB+UVV/v3/2RwH8OwC+44f9x6r6Z/27/wjAvw1zk/49Vf2ff82LMCBPilqJQq72UicXhXze0d6c2C8nuLzUv+4XyL2BL/YGKXtLNXd7aVTITmjhQAfLojo3Ovqgrnw/aQ2wvZxLH85BdZ4yH9/FP7kI1dE5i0hGNJkrIOH9UCJwRLVnpGBqI66bDvSp6RSyO3tStBos4j8qWIj/PbUFDCcxc8Ln66nAop8cIclihRGhzRX5Q4CzD4eHnIUB1Ulo0MAKjPz48KNSf0JTPG+Uaf3BYIVc3MkIlkRHOpemA+AHFwdEm89plFUta0SeX08HWZv0wFqoznd0K8UlyxyCprK1dS+SWsQcTINdIEjUqWqmAMD5RZ+d5LpYjEQKavUE7+zY90UrwELlZEwCJhNCPR/WGTC2QUS1H5ZCONbe1pyz+kMJcwMJGARrCGVOHrVjUo8Efg/71lJy4cUHcC8YM1OJ1cJ6yX3n88tCBqB511qHVRspYIsUsCP0JULANNlTweIIEUXA5jPmOz7bSiWfg9EPnp3umM+4TyPizpoioJ0JJAy+E1rRbYlHI4mDFIoU4KzVdAAf48PyShGrzPsjnln3UjZUhrYOHVGhBYWV5ONkhVKDbAYy924LwuTCm95HVQBi85ZgYjdQijextqqGTEwTl7mUVp4NBirFHlVnEE3aNBXs+4C+zCNIp03nf2WjrQpyMOaKUUL+bAlgkIwx0geA1K8x5zruqXyGAPTaPqh981vVfiTvI8uWLVu2bNmyr9y+zvDLzwH4rwD8yYfP/0tV/c/rB0T09wP4VwH8AwB+B4D/hYh+n6o+uuezNQF9826RzKDmK+E8GXzpePN8x3U/scWbOYCzM/SZcNQXuacOFXfSDjbn5QropdlL9TG/sJJX8YhoMCLSV5xkOxBFtb6GvgC9KKrqJd0Y7eaniVO2I+rmzkMtnRoRaCtzSMPhwjjGWAIORLig4XActDBGLIL3WAWDNiBKhlZGQ4xNgaRVW7pHvNnHOD3KXj+rfkPMacxTVQFl2GsiZzB5lMgMUb1KgcZoK1JiwrGwdsd6JWW8+cfOqAgWRXZV6D0h0RoBnVJDAIt01+8VScsf4zWHHqUfE9sg2BcB4kQ7MfbmP9PZLNR+HzNhfDbtXUIKAL4XSY5qKVlmtEyE+IEu0Jl9BubKBxP4Mztt79nkXAWjQ639w53RBKV8Xr3vJoBYhDgvZU58nxh13sbAAQ4U5g+h3C8FUFDAwS1nRQWYIAUQ+UBKVLCYFANMonI/GFgSgChSKDXTcQ5n9JCOCjdTaoBmJJ0OmkCU6HtUzxnCsmT7KZ6N+xCLJQfXogIJMMCJ2G8GoKLsEWSKWQXLpFRPClAnGWF1DH4en/DUO4JcvTwvom1KZoI2Az76zTZlv8jcF8D2SxUyLSwK8lSS98yfMxrgTKcEYrU+AABjvFSA7SIGdhxsgp/Rl/iex9+D5VTAkKks8AAcqI17upq2qDxSns/x3D7In/0zIEMnrBrKezSt39L2c/i630eWLVu2bNmyZV+5fW1ghar+eSL6uz7y8H8OwH+vqjcAf5WI/jKAfxTA//bDTmpN8PzZfcpZZhaoEpgUTEaVPs6GU+zF8jgaiGBl8jrjvNnfUVpOvSxdROX0ZOAke+EPYOEkjwwiXzyz4gUwqg/04kwHu+C9QYzIqXzgxT7ZD8d4+Y2KGFFaUdyRqmCCVTgh9Mt4WSYhtFf//m6/s7MV2l1HtQaYo5Dtuv6GTFUyrJJHluoDZi0DdkfZncSI1D/OwQTwhMMKd3wwRy5VMYETAQCFzkKmMrQCeEQE251j3Yo74s5/RjCLo2FfU/4ezngFfGpFhDwjrlcsNE7SiaoAgTse5FFaIXYwQbPt1D8gn+Nt9Mty6sc8mb6G7U8AY58GOCAAdU72SESQST2pQsq1xnKMOXQmRYIWBWyyKgY+/6GDERZOpPrv0zagbENZ0a/OfoiIO+B0fN/j/D6LKdfDwS3sCnVPtTXTRJlpP2MvJuODy37YLFofLA/qAxAQ1zAJlkeuqWCkcKnfg8FwaepsBetHsj6iP6eBouCytuGU+pEVBHhMHbNrDJ81dA9EydLgYsy7VvzNgANnwvAdqeuR90HZ76RIoC07L0BLLZ+4l0dfogwrMJhb7Q5s7+y/HvuhjqFZ2lqUUQ3rT2RAUqz9I0gATCCblY22fwNE3kctiGHpPASnwWBMYGiMxL3oH2+XE8yKc9vQ7/5vwjHADntmUz7/B/untC/GJCLy6iq+74nm8QYYa9VHMMDq03QyUr+HKEHXnIsCliz70byPLFu2bNmyZcu+evtxJLb+u0T0bwL4CwD+A1X9FQB/J4D/vRzzC/7Ze0ZEfwjAHwKA/W//xnvfizCYBV0Yb18uJh4nNNGE6dqxXTpUCfRug5JCeSReW3R1OF9oAF0GkqBC0LvpXlCJPGaU019YjX5OKQBJGQrGeCn2F+8UW4TRqnvNdSbXmOh+rH9u9H+7/pQnDXiuuafDXJz1IYrN88/bq/Wr3b2fMmtfqPiLOLnjoKMvQIAPwRqxXilhMFx8Pgy4IRMAfABUxqLBAaH544k1Ecc9sDsGYKA2l1FSMCxLPxrDQypo5OBGpjVEewGgqE40a7mYA/DIQBkdxtTfzF935z9SAao4JVFpUwl0hwmTBjAUUdNgMGw+qbHX2Gnx7pCSpwtk1PekBDTC+BxTREKpGQGClUSM68YgYn7jpAKqUXdwJYAMANjEosEV0KgpRHdKp93GgHTYwIBeJUvd2gnw1AiMv+u0B6Ml96YCGyAOQMqFjLUkAy+xMUUDGOBFAFguKhvd5rs7lgFSTEACZnAxfvZxjSg7a3OOAQglcGXpGKqoQfKStkW5h5K1oqUPSkNElGAij2RgZDA6Yky6mTYEGOidwKd1gp2xQZ6SFsBB7ncHYOI+lGbXT72fUto5NHL680hZkN36x3eCNML+Nq7lp2mMj0BXTXAjv+8M2RXyhCw7DcBTemLveVfFn08+0XK0sdbBzCI/LvQpgAFa0DhuUn/Vsg9jfaTcX5G65kC2RFtVBydAIIlHjj0HtCJYivHvBRvIlI86GoBFGg2tHRN9xrKPs6/sfaR9+9tfc1eXLVu2bNmy33r2owYr/jiAPwZ7ZfxjAP4LAP/Wr6cBVf1ZAD8LANff8zv17XefAABTUMppuu2Xd7RIJShVKEQIPRwsBfjOQHkBtRd1f1kOB6q8vFJT6CYmDtci8lqdL0W8paZeRXXWTirvvsWhCweyVBqwSLI7GqUNPjEiwoiX/DGGfPHfkNF50sGOULYIuOwjusoHwEeJTIpaOopHteV0yr1fD0qj4sqm7pyXl/biuMWcal2oEiGlg+3vGE/8Xp3mdPLLOKVMAAUrxlkynYeT7+iJMpJFYw2UuVdYBDNp2n5uqZbQA8QBRqSfRlta+pfaClSAHq5sEbWoqMsPKCP1HvIYFyu0axBmTwnD6XW6OFh9KA5e7IB6ZQjAgALouE7dtxP4kyF6/zvWNT6LzyPFggu7RRyQqQ5cXE+MrfBYBUV2Z/JE5ZkYFzCApLh2BaOyASAEWME09Ar8/O5ChblPIoUo5qCMPwC2+tyQCwZYFqBDXYqcj/ERBSMLMNFe32ckrrVSph4Ngz3iIIGiPtc05y/TgSo4JjoEH8mAR2VzhCf2RYCJMc+7oPtay0lANxFMcYAgwQmfH47qGC4qHIKjOX9+H6mnv53PCg19BdeRkE6QawMpv5+G4kAQQKOCis93O+HpbWVKFODOg4Wjas9/aVOFnKqjEuCaJgCI2RKUo5Ga4R8dBw/g7oi0HhppOQWosL9j3sv8NwMm6rOiXiNTqkKLyEHYqOrSL+T3MQ9dEcztkA6QaNkPtK/2feR3/6414cuWLVu2bNlXbD8sq/wrN1X9m6raVVUA/AkYtRIA/jqA31UO/Z3+2bJly5YtW7Zs2Vdq631k2bJly5Yt+81vP1JmBRH9lKr+Df/zXwDwf/vv/xOAP0VEPwMTtPp7APz8r9ngSWh/K2r72Y/IK+eDcP0ViyrKBTif/furR9WcNq7PHaCWOf58c8o4nAbNZFHEk4boYZRC3QTYYIJrnUfKQXMqvhDoxqYbUFI/pmoOH2JG1PgMj2hcfiEmapfU8w/Ec9Qj3nQj6EGpwp/fM9CfLPopkbJSKpLw4eJ7XYGTwF1d/X70iztcR8LmUxssfeRxPdTH6VoiYeJrBSDTIEY03NuYKNiY0gcyyu5pAAqGnjoYGxkxp4xWUhNwRGEFRpev7Iw6/5kC4n1oVlmhU8yR96WmSjysX2gZ8EHJspi1P+w8Nc6+7ZVS6iJFHcn3QW+W9lBSiqgTCuFj0Grge7X2p5OtQ1Drxe8F9vmg8XnOCekQ9nyo6pIshA7rEw1GymBTjDlKEccafVbbZ9wZsmmWBR4lMWmk6oTmwyO7wlkV5Poq2pFlP+Meogg5A5OQYq2Ykvdm7PVSsSWKpSoZo0DoYR4AS/2IPR+6BeWAqB5DmT41hpi6F64boU2TyRQPCj51CIHW+8evTbEfJU4ZI1WlZAKJ+vOMZr0TNEXfAdop00Ty9vMUK3IGTJxHR0kdImeUkLMrds0UJbBalQ4CZBfc7xfs35/TqkKUsx1Igc3UzIjUiovp9ASThA+kJk6/6tDo8Wd8lPuNPsiO1IgBEbTWXI69GvvhsVxzsB68SpCW9CUAxpqIZ63fh6RjnYzRoiPNUMn+3ajbeRIaVtAmaPvohAhBT8Z5acbSuxP4HEwhbZ62dFmB/h9mX/n7yLJly5YtW7bsK7evs3TpfwfgDwD47UT0CwD+EwB/gIj+Idir3F8D8IcBQFX/HyL60wD+EkzC7Y98jPI2CdBuNPuzQ3oiX/T7VdE/89SA5w7aBq388vkd56VBbpHTvIHUK4CcrrXgL8UpIHcS5GoJ5nTp5vzuJyRowUFbV0C4AdSg3cqAAoA8gBKRRoFHsGJyZqrHWZz6KPEp9TwDJ7LsqsK0NYqjrJulRFhuuVHI+QDk6k7RHWg3m98Q3KNaixEBMCAdM1WnnfvapGilzwUICQpFm6PCCQEP7dfSlLEGj5RrGxxBT4DDQY89UKneTunXkyxfP+ZOAaqq/tFXwB1vd7obGY2dFOr7ShgOROlUSrOuX1QQ4WNgHiFoGABVpNBAHFRyhzvnwB2zKRe9OtcC6EsrlSDGFMqVx/lkGiU2Zj/fndsQlHwEzGw6ip7KNvtSI7++OObuSVOZh6nfBEsfCoHUyPO/+Z51/ZIqpKgN5nzVfU4PP/33cGoDDDARwwfx00j3wIyHJQhCAKkWYVg/Nmj8vrZ52dMayqn3lI6Y59CIiQMktVLKvIjtDas8ogDToPITJiAp1mCa4yp2kYAJJSpi5W+9Cshh6woyXRsAWYlmlPxVYKPxGNrEQBwH2OBOuBY9H7A9H/Mkv3cBACdD1Co38SY4v9lBvdkzBmMvRxUWPsY4ctwCbG8p9Rn4ILT7GHq70qh4co51G+llBAodjgDXHu6rBERlrsgUpgQDwlxrJcVqAWRFo9jzAQZG+5uCmoEPbesgAs6j2fIVkc5p3Ay0rYMdjBAh8JPivu+QgyE3tuokVbw4hHqXAfjRvI8sW7Zs2bJly756+zqrgfxrH/j4v/khx/80gJ/+dV2jAcc3BFnmEjAGwknQi4ZV1AYAACAASURBVOD4bZrR7v2NhZIv1xPMgvt9w3ls2LaObes4L+bh3gHIGwK/NPAdRS+gRPiEAGHoRa383VMHlWoPIKR+AF+7lcerAoGpl+EvtSEMGee7unxGnuPcZCFQRviyAocO5zCrBUSk2V/yawQ55ivFHz2yLru/EG9IfQm+exT50UGTIbIHWPAynTUBQB6F9QjiY/CQwgkNxwo09O36cDQoru1OfOa4d0yVMfiwOZDqPLD1Y0T8ixMc7JNHR6Q6w/63bgq5eG58+BS7TeoQ6YtGykCdVRNChXRSKa3qjmE2WEQOHxzpqEYxOadlHSpAMWmZvB2OkkaUmjH1gcRF/gDINfbmaI8VmbsfgFAygh5FUGPvPbQR0glEtj6PYrB8ILUs+LA+SmFePOJYVUOm3neyAQy1zRjnAyYgW9kYtW9x7yk+XLEHZd4DRNFwTP1718TQAPa8Qgu7I84nspqGbhhsmarX4HPGDnxM4pUOIrADARNgVOagavOE4z3pJ+jDc0BNiwZwtlaU8Y01eqRt1XkXmp6PtlAx4TAw8AQo0FkZ86c7ARfB+RkPwNKfV3oS+D6etxVwoA5sb2EaKeT75hjHiIPMudcB6D6eCUoYZaG97QQrY3xxPyt9YKIBImfONQV2Ae8yVaMiVhfRJKgSpBf2VozD9XVak6xgpQ/XEX/uqzD62SAspQ9lzpsa8FfAPWl9vi9/i9uP4n1k2bJly5YtW/bV24+jGshXZu164pu/+7sAgKMPSsX9tuH5+Y6f+OJLMBRfHhdcmr25XtuJjQXfuz3hl9++gWq8MNqLXvuWvRDeXnacr5s5m0qjFB3MqeLDI6Bns2AvYwg7nuzsCneCNve2OPptfZHTPGja7AU33i17Y+i9jYocESGNd9VgFDS4+KcfF+/cXdNJpwBGaHZiJ4e0/vTvZQPwZB80Ho5EWIIgFaCpUW/AABEZYMPEWoA5vlnFxB0RSseERhUSINkjtRoHdXNE8u9gCxyzYxBjm9IKgIya5twAk4pLrd6hzsIQhafkwMCqXTFq1j7MJytAjL4rZLc9w4fOIpoB1MTc6ZjbbNKP4V6OCwDNUzeyPCkKAAQXYnUxxqwWUO96QqY7kEfNqY4h5h7DF01n1BvIiicFBHovGu3nCTsgUtrQsjfCYZ8Al5hLLXMW8+ftUoeBPpuaeGUFJwp4V8edfQtBW0/FyTF8AIAaJXLndhQKYmdMwPapCGXKEXsJWT5pAgQn0GAzJ5a7AVqRipX9dAd9GkJcn8q6O2hDANDHfkMBRNIXFwxAxQVhdTMRRxGdWQO9onhw0BZjjuL+JiQjieT96jnaHJS8CvSp49y9Pa9cwyfAO9De0QTEqV9qezuuZ8+EUq44rrGN+6gLBnDVfU59LmwNxrmZCRYsuvbw3CRAiZxJpi7CSWMhGdg2qzR1nmzYThuPbnFg5NSG82jgzcAKjpLZsH+PojFV4DjJWHvlxj0UU6Uj26tjrfjSRwWUZcuWLVu2bNmyT9Q+abDi8/2Of+x3/DW89B2HODNCGn7l9gbfvr7D3/3Zd9DB+MXbF3nORoLndsfL8wX/3/4t/OKXn0OVkpjxjTevaCy4P214d9txv+0W4eqUL5r62kC34gB4+kcqx+tQskdTAwxKFQpuRv8FWVTeXlQ1X3jbPuQWoAZ+6I3fz38PZ4oUSjIIAd3ylwMMAWBUbcJIoUjHYDh0yuN7+Ltxf9L3ophARH/jgsVhxvhMgEkro6boKMEItkogd5L4jtkJvA7HLwEJLaCJAnRz7Yyi3zFpARRHuEaxcxp1OHnqDJ2M9JL/jzXBKjpL+xsKS6b0O6zZuoQmhXSytJFS6pEKCGPAiYJ4aEoAxUcJpszD+JKBEg5YmeeMLrvTpc3ZMzx/bnPnEXWh4ffUcRUntwJc6SBnSo/v/VLqNE7J1CBFlmokuAMfoBy8T5GDH4AKDz2Caa7J9iii1CzDUjVy45lj3eu55fzsUx2g+Bhq+cpHK/0NBz3vRwbAgu5pVd2fE3wMVonumnshU5rgegwdABnDABj7JPVO3FEPAIoqaFCmRskAEPsw9ljsBQUe2CZ8wlLWulftaMURD9Axx+v9rmwU349ENGuWAIOpoQAdnlYXrDIYQKCbOf/dU5LorIMB9CC0VySIoQ3ATvM9IDBGh/rzqgBVpEB7ieMMRJxKMueegIM+BprWsqzagN4BEoYcBN05z5NdkiHRj7Egyaw4eGLZdQL6JqBNwa5LIVu3lDoxsEM72Xmlog65Xo5e/J64DHYHAOzX0ypeLVu2bNmyZcuWfcL2SYMVG3X85OV7uMkYxpf9is+2O57bgW9uLzi04dwZN3/L37jj29s7YH+Lb2wvYPo78L3bE97d7fvPLnfs3PH0/BZvny/48n7B2Ru6ULI3brcN5+sOvfPkkI03ViBFHRVOMx8RYenNtTDshVPEHPaI8BEp2iYgj7j1s+Gkza4XFu1muUgMEKNZhFmjT2TpKMSAhDN4eJ5zpJyYzmiCF+mU7kBnF+I8kPnlcJq2bMNZDp0MwD5jIKnnU1QeBTdhxxO6ierl8AipFxCCjO/pKbgzEv0IJ3iK5OpwPOKcaQ6LfYDxPVJsUEpDxokHWyrIfPDcwGYCeYCtq3bONQCbTgSEgG5R6C4GJEW0m2DgwkgHGQ5njiHTgzCYFgkQkTtdCt18vXad0h1002QPWfqBjlKnSd1HMiAU4/oZdS7z9qHMgYjAkwIawI97V+kgun5KAinRpu9xjf0eX1bH2QUPSclAiocUhtw7AS6VMeTnMa+9fJmMG1/nIzasOY2oAGI8hij+riiWzXvvDxPFDnQGwBPnKtCvhPZqxwfDol/GY8WELr2pE5O+SWjUVJvFWgPoGP2z+8n2V6TkGNA6AxqhvSGh/fAgdpoMlpjPnO9yrZOAGw9tjDD2PbApTqYEVaNd3RSHDIAh7vvUrLg54OIPGPGUmxagzwlsr2raKGL3BX/g2WIHOzBxGcLCAZDwhSCvBrLpPs6XjXFGGeYoC1zBHL/PK/tLN2Mn9c0a6Xu5AZICM/59CJZZpOzQU8f2dEBk/PuwbYJtq1SiZcuWLVu2bNmyT88+abDiJjt+4fXbYBJc/W3+JpvlrAP4xfsXBlZoS+bFoYyGJ1z5xE9cvo/b5zt++fIG33n9HADwxf6Kz/cbvrW/4KVf8P3zClGCKOH0Nl77hi/vV3z/9Yr7saGfjN7ZHE/AKoMU6nFYOIByMqpnTGwRYfKXWm7m2DJrvnQSC87NlovInUkav2svQAZ7W2yOYaXbZ5S1KRRiuhRqTpcCwKs3cR+OZL4U8xD2lJ3SKcgX+QNo9f04hlgrH1TNC4IBKjwAiQQiMBwA8shoHSKApMpn5DYj+xiN+CVDJ2ASWfRjghESTlhtL/tE7nXtyMipKiDKD+0VJ0TU88nZ1sTF9Wh779CsTAIYoDQEXzGiz4qhxTAJrvrP7o6dDF0P3n2NdktZ0ahiE+cI+V6wiaKXBkURcAWNKPykRBkdH/NF3l5S92sXU6PBWCN00Ax4sJpjGddCOd8dTyKafck+9mgCFmrOoELnCQ4QIIQhqynN96s7lFZ9ZQBLlU1iKTM6pUGlxfrUNhNYdDYD2XWSXdIoQZAAZfRZIZ+NPgEAdh33kK8dAGN3eUpHCLrSg6+qrJCtrCE/AE9+DHUHK06AawWgnHc/zk99LwtKfwAoWEAHEgB3gjSkwGeCUvHs8v3KNJhrJMD9m+Met2PK1L+Q9b0wsfhe0mkcfKACkExgpt/vwQiztKtxrOyAOvNJNgMa+mU8q3QD5N4guzpbiAagnI0g13pi50j8+0ED5Anwrin0OlDY3gnYFHzp2C4dl8uJw9M+iJDMjGXLli1btmzZsk/Z1tvMsmXLli1btmzZsmXLli1btuw3lX3SzIrXc8Nf+pWfxJv9wBe7UQKYFBsLbrLhl+SzZESwhxlPbdio48124KeevotvbC94bpY2EvbF/orP2w2ftxu+tb8Dk6JB0Au289J3/NLtc/zy7Q1ufUMXxpc3q2l5Pzfc7w3nfRsRNKGhaXESsn5pc+X4CIcBEIiJsnWGsDEsrtcTzaOBzAIR9ugZQYQtWFeoB8HygOtWSIlk2wH+0yPOkRYQ0bikKWc02SLNUbZTN8utrwJ7JuRY0ge8fSlidxnh1BJhdR0GLayGyJ9HidLSNv+dgpOlvTxvdAUsHsyOdJLHSLGMdvrFo6oYFPJYQzkIckEKJCpbFLdWVKmUbyUatHY2ynnN0Tf2i4Kb/QcouImtq0dRg43D7OwH9X0UY3M9DKsaQNCTgWNUH9CdRmrFLk7rL2FvxWBMRAlHGoKFGeUmeCnPuapGlhqNLIYyd/USqKyXTjnHdg0kc0Ur3T0sSrmSlxKNbV7TDaoIojy0IR7dZjK2i3fqvev4vE5lY3MMPNoNttJukf0xGQ9zKjS1NVVO4SJ8med4M0rGBLp20HX0F8AQoEUwpbzrJ0EPRm+mbaM306OJKjLRP4VO90qVoYiUHnX9jiwpGl0I9oEzfPj08RS9iGhzquRRmBd8FtaAAo2QJaaleQndRu+lKuWY27g3SMir81SWBE+pIyAF32mMJdYulGTLvR9jy4o08P2tMB0WGCsj9EL4gDHXBIVdRuAbIBfC+UbzXhoVTcacpWZIfJaaOpQpPRqpOg3gJxt3a5brU/7JwHm2rJ4DAMf9gv7uk/7nfdmyZcuWLVu27NMGK7owvvOrn+Ny6Xj3xjQnPt/veN7sTfNXb884hfHuvuOy9TxHFPjiescv397gp56/h8+2W4IdL32HKOF7p6WKvGl37NTxJpTuADAJRBnf3t/hb10+t9QSUvzy/Q0A4Hv3J3z39oTvvnvGeXrZuZLfrpsCkVMeL6pFYFPFqfcA7vcNrUmWWAWAjQWiJqhpL6kKF7i3888hxhapAST8/st/vDCHE6WYgIfqxNM5nG8Amf5RafC6A+GZUC9lRN154YOyj1mVoIhC9itm+nZN8aBsOn8qOy2bAihBajbE9zGtfA6wAtVRdseDSqWDWm2EHJAhVSgFWEE5RyHMGakyshdQKJzCSKVg158IsKSp56orsBmQQJ4OxKGRwIAK2+c+dCpgS9usNK4Ke186hBgIMdhavSKd+ZIKoOTA1gAchsPqfXSAIlIGlHRQ11Gc1HC4Y/4r1V5pzGd16jEcwkyPeMDJCDANBlBS9rUeV0UjN6feVwyhpGOwPDQeF5iO96ooFRgoZTpT2FIL+FAFTwIICZAlv6YsCwqM9rI/Xq400gSUW2qiVMeUIs2rfJagwa7Qi6DvDDmHEGPtooGGlIBBrmQAczAnWQuwmNMUZYgduMnqMZjXnxxozDKt3k7qSdTpjzm6GAAaZZTNydd5rWJ/NdfkefherwJ9wqQlIiejF+BFNv+FkUDb6IzPiT8D+O7VSRy8jQoufAO2Vx/TgQFE+960UrUE2WddH/UKTnmtMvdT9ZqSEmeCnAW8aSam2TtDDjaQuoBdAICTcfmlVQ1k2bJly5YtW/Zp2ycNVgDAed8gpWwp3lhFEFXCd1+ecPSGl7eX/FpOBjXFy5s7VAm/8vkzvnG54ZvXF2tPGKc23M4NX1xe8RNPX+LCJ377/mW2ceUDoowrnfjW/g4A8MQHvrXZ79/ZvsBTO9BI8XpueD22KfKlSujdwtUZHaVRulSV0AJcOZvpYShSMC0cV/LfmY1tgc1a6FWz4dEZqi/2NMpVPubx666mqC+UL+9UHUQuDm1xmCJKCtHUgQhnRaCWmx+HHwOs0B3o1+F4UAf2t5SOsJYX/Ijam9OB1JoY5U3HOKJ6SApP8vw94A6Eh5hDYyM+12bt8kHgruAXQG9+/f+fvTfbtSVJrsSWuXsMe+8z3zHnLlaxqlkUiiIECeoX6UEQNH2B3vQX+i29CHpsgA21CFECCbIL7CKLlZVZzJt55zPtISLc3fRg5kPsmyW2AIrs0wwDEvecs2Pw8PDYGbZs2VoNFU0PI9VUgGfzUrut5LHnZJ50PwY7A24YU0cwlaYEGdUnqTI8MiU5IjLCslHNCzLiPsO1kEDaNygrI1ARNVS9EtTJVmWjyI1OXAIEiEF1llX9yKlirCBYEn9MCWcSbZS/lXWR3FCsPzpeBRbk/VjZF4RKXJLyFHPSy5hPWcW6KclpEfDU9ZHnS9e6sokAiItLzeIhFkBwxioCsvCmJp+ZFaTgHbOSqipgsLpipImnQKCdBZuyEenzy1b1EOYPs14Li6hr4wvTRhN3UlZDmiPjdZz5+CjijcoEmVmP1msJyPvW9zGBSPmZOwIasmDr99xbCkLuYQvRTQn4wN4VLOBr/s6KNAPOuI2gJgozSO8HuwB21bgTAyoJ3FZBbRqMMJbCKMBfzTSjSDAHWYTGA/aAGQCaMYUR8L3MeXJxiU35vuAMZh2hNxWAxFNhZfBefph6K3M8iraKUVel2naXGOjeHx13iSWWWGKJJZZY4oHFgwYrmAH2BsEDO98BAKbJomkCjIk47FsEbxDvGxh90WsGQugY+1PJSF/uG7xtAlYrYU4wpGI1Dg26fsKbkxP0bsJVdwqn3PUTN2IfGqzsBEcBhhixeok3FLF2Ex6v7nEIDXZTiymavE1jIqZoEKLB5G3+u9G38sCETsGK+30nNn7RICqFPQQDYxiGUguA7q/V+OhIaOvEgCWwKdvk3IFRXm5T8hRLcsYdg0g+yElXTZmuEj5CSUpyNd0gnzOBFmyQBQVBIv5I+sIeOhbatEaqwOckTxMgdpzBimxdWL2k533Tv1rNNXWyETDbLrMyUlG+Pr4msnaQZNuOyCwM8gBsSe7kWDRrgcjn1LHY5BCgcxQbYSmwFWeByEC0Zn5vgBmYAMOZORFdBSbo/Ne6krCaVEfkTJI8ifsEUBgAahUpbSooVVzHQKrOk25fgSXS3lKdT51jmHRfJLCChf2h7IlU4QcEfDEsQoj1XKK6D7n1pgIF8iOXpipIkhsdq31mdQvSOqUqEcx2lDz7IFtsUtmGPKvgKmm7S0KT0jxXP3MB+eoWjMweCSjCiXVQeY5IAbK8ptN1MgBTrvE4uFXB3VZbBRoV0QXkOwFyX9gReKK5LXB6zrk8L5mNlIZYJd4y6ZiJ5qaxz8ZUgULBori+KPiSHITKDuWY5rhVBvIcxtTaQwIkFTCT8vVmhs0xHpDuw2zQaXdpySIT5TvXMDhEcHLo8PJdyoYwwcBtaTZHxYqYQYfEzODMxooNIXTCJGNHc2vWej1X42YFWNNcRGsyIMSuAETpOTT6HfPBvC6xxBJLLLHEEks8sHjQYAUigfZWXz4lqxh3DqNlUBckMfLSw5x6ltsbedH0e8J0wuCJMBmH6b6tjgvQaLC1LXabDq7zeNGfobHyRrpqPPaTw6adsG5GOBPR2wmtghk738JQhDMRJ2ZAbyd4ttmlpHeTMDiixda3H4AVALByEwwYb9wGu7HBMDmY5BZCDGuiVNIVsIBW1QHAuoiYKsJ1AkQlyUxVeI6ULTW5fok3QjsODeVkmo4q8IAkx1wBNbOkQNsFcsU66TZAfuakQaEghCRYelwAkzGgWNgWZqJ5xd6y6imo9abS8rPDwmSE9u3nOhbHYEV08zwhJp0A0upyIGFWjIDbUXZKMdrKQ6q5YTzAUzl+Ns9Ih2PMEjnp55fEIoJhQIAxcl05cdaf6wo1oerJl8QlU/gtZu0XaGKxUNQkmjwVdgSnhFTmNpIONN+n+Vzk86drYGjrBarkqmyXj8EEbnnm6JKXe92rnzRFqKwVdkBoOes+0HGiqfPKyYI3CTGktipl3kSCPCMJ7Kh0BGZsG0K+B5lZYQWkIbXszcltBu/ow+uv5iDfU22HEVZVVUI/em7YkDpuUJmjtA5YwRKe7SJDGCDVeK/PSr0W0oZOwSMr2iZ5LTGDJoIhgh3SdRQgIHcOKZiTW8EU0MnAQv6OKXMPAFGf3dKyJffCJDZJAnfq56ZqTckxCQsngSbGF5AVk7S/JHbUrNUmzXM6ZqVzkr4zohfQkBLrIp07t/go8OMYfi00EdGuSWPR//R71Y4Mty9fAtES/EqAiqTnkdrVvo+plteZhx6nfMYGmDaibyL/+9N5jML2CD2WWGKJJZZYYoklHnQ8eLDCbo/6dfUlLq4M6HSSF/ZVyBIOdm/RvxXQYvcxMJ1ppXMsL6GkOg/mYMAHg6l3mJpOLB8BfZE1uG4CbBPQNAGtC3C2tGk0NmDdTNi4Ea310kqgL8SOImBEeyKFoVg+NxEbO8IQw1DEtukwhHKrkv3owbsMVhzQ5L9bG+G18m6TDkJK3LLtZvpXfuaorIyKDRGbCA5iixrW0Ap9neSSsjJY7R4xe7nO1e+6gl0XPR2kkl+JHs7aADrR5cgsjqQHUQMmDEnoNAGU+dGPK/p6TgysijSiJERsOWtPgIDYxdnxYYHQSHU0OsAdFKwYhOqd70tQSjjKeRGrROQYrLCAsUDQeYsA7EEqw7OqueHCREjXHMp9SKciX5LD3PmxoVxJrnVC6iQsUfaJVVujKegIKfjDDkXbhLhUr3MCfQQgVGNLtAB2LN84uR2q+tyYzGLJbRyaaLMF4jrk82dQre7xV8Ayia7KcQoEFR3PALIaUKCq3aO0DjCSrgGgvxsGW1P0BVJLTT4QSiuMWpRSdY0MSdAF9GPpeThmR7BMOhutxicv4BqkSL9X9znNW9KG4dQa0ySNjAQq6QQagFwUFka6l3rvuSW5TiCvvXQOMJQZUSXXChpwYNTMpfx5mkMqmi0UITarwHzuUbalY6AirfUMaBRwhHL7jIAXMTGujoCO3H6Uvv8sZz2RfJ8avQ4F99hw+Q4LyKAZtwyPiNAS7KGc344E28i9MqOM22rrmJ0YIEJM4FX13ZSilj9Jv8tx52s0tFWLji3HYALcHjg8whJLLLHEEkssscSDjocNVpAkIXT8QqtVzn4zomukpO7V5eLenaB/59C/jfBrg9gYhHUsFcg+golh2oiwc6Ch0H+RtDG8vmSTxWQYUxsx9CEDAmQirGXcNR6bbsS6mY7aRKRtJAmBOhN/q4dsawN6u837pYhMuJ16DK3cwvuxRYjlKD4Y+GjgTIQ19X76r7alsP6cQYuc8FcuE0ywNiJGQvAyB8wk4m4paRu1dJ0SiqB95XZ+f7KYHmm7gIO8aAeAJlPKt1WyCmhioolxYjZIclao5MWBZF6NTgmK9NRTVe3W402U+8gFYCjzmDQOEntARDT1QxUaNEAGAUQUVNdBpaGRWw4qYC0xCMwkWa5x1ZgrGZZcZVdhzMzYAJQpUc6dE2a9xmk0oiOScKIECNX3JIn5Ra1yk6lwgKj6FiW50huS75NsyKp9MQdR8s9pXxelhaX+nIBoQqbaMx2dCwBVz5ecTvUYADkvMWIgYCRJgmcMFl2XiXmT2geOgIbZv+nndE4LASlNFJeGQHre+X7ZeUXXA6dkkkm0RqDrIzEs4nwesxaD6iokgc2kMcI6x6Tnzm1VyRljUh0cBQ85Uh5DxgTZqLgr5mEkQ2dLIvoaAW5MBjtm7RPp2azn0FfPvQITx/OZ5p5BuYWEaxZEHWkOM1hCs0SeAud2m3ofeIBccbTJz0R1zPwzl20ABQoSSJI0QlQYNZ+iEkXmXtgrsTe6PyEcFLyIAlyM54Rmq9O2L+cBhGlBXL4vZE4LoSmNVwBP/e6MjOgo698kEeEMXBjADjxv11liiSWWWGKJJZZ4gPHbcuQlllhiiSWWWGKJJZZYYoklllhiiX+UeNDMCnIMPBmEIeAr3MUT0ERs+hFPNveYosV+kjLT4azFcOGwfgVsvo0w3mD33CCcaAtHG9D1Ez6/eo+7scPNboUYCd6b7DqS/eu96jlMRmi9uRxm4AEM2xb7tkPbTcJEqNxAjGGcrAa0zqMxEbZqCUmsi8TAuGj3WNkJGydc4iE6+Gjz5531GHqHUXU7fLTwbDAEBwNG1LKhjyYzJ5LgJzNhCgY+2Pw7gFnbCBGjdR7WMLyKDgyTQ2gM/OQQg8x/chcBKuHIJMCXCtRTvnsiuKdV90L5LqVY0cnQPyWhQWVRpDBazbVDOcesSo1Suf1eF5Dq76WXvmLB6GehEfZFbCrNBGVtJMeCpPmX21GScwWQGRWzCmgAjGe4vbAiYkO5kpzGlsUqTWKTJIp7GbuZlNFRa3EkZv9E8Ovvv+40R2l7JmHvyPWpgCdDNA7S2g4VrSNPUqJO1NdJs1aL2QmNTlbNzrAA26A6AiISmp0aqDB9MnuJOLdIcDTawsCIkYRhUlk/ilWnVqYJABXB2Hz+ozVDQec8ryGWinqi7B9V5IutZEXvD6g0J1iGY6DMiIqtAJT5yAwAXQtpDirRz7xNrI6f2TqM2Oq6GNVaVNlMJp3HqJtIYhKlfWNhDKAVrZNoouie5PuXxprOb2bsirrtix3P2hOO/5U5rbxlMoOiWmI1jShtln5V9sgHOjARMFBGSWJw2fm+bDnrfsxarmr2SH3O1I2T5ixt4hi2C/L9rycIziI2BhQJITBizxhUMylp3phJvluiLVofSVdHnmdd5wEftHZRJIROv4+SI04EbP09hvm8LLHEEkssscQSSzzEeNBghTERFxdbTMFiHOVSmKVVIQwWb16d4bpfw9+05YV9JPg1w3eEs68P2HxrcHPfYv9Y3vCGXY/9ucO71YDTbsDl1TucNAN8NBj1zfD6sMJ2bLDddxgPTXnZTi/CQV/gPcEfLHyr2WxNW28iQiA4F+HU+SMBBd5bWE3EWucxbSxWbsKkb9U73+IQGvR2yvoWV80WQRNMr6BFBCEwYYwOgQm7SsxzjA6GGGOwmKLNFquTAjK2ciapdS5SsuhszGMMZOE1ueG6h3pCcY6oaNU6OGlfUEDjWPKAALBXh4eUr6ScuBJkTEr8FDFr+8gHQbXfUaSXeaOJLakbRRLLywklAc4QYgOEngoIomKBWYDQzhOf3BpWXQAAIABJREFUdG1iLViSsDoRMRPg9hEUqIAe1XGS2j+rvkF0ADVVklLNSwFbyhjsIYEdyOszumrM9byYAjKkHn6KBrHnIvQZ5W85Oa+T6EgFyDlq88h/U/vMGTClPHZyACWNBkoH1zF7WSScW0qqEzABFYDFAKgST8xaD7HM10wPwRwtkQRGVO0D0rLDs3muP6f0e+4Jkj/mz2tdCKtuJamFCpDWkJQEc1obBJr0We5imY4oAEStvQAAkRVUUk2F3GEylO++NO80URlLNUbStozIsUyKL2OXBaxgE+uc1m0Vsayt1NOSRUqz3gZ9MKdyAVRakjLwyOJugdmuOt7qHtT3KV130PUckdtl8n1OlszVqfR0WZcin494dkxQtTaMiHKWA0BabFqxD6ZIwMkEnMkBxskCBwNzMDPHlfpfMxGs2vym77ZYu83oOqu1RIxHAXEJmNa0uIEsscQSSyyxxBIPPh40WBEmi9u7NcJos6AcvAjgdW8sVt8R3L7Bxa8OOSkYHjV4+1OH2DDc+z3o62/x9Ksr+KdnAID98x77K4e7j5/g9cceV59c4wcXEy7bPS6anRzkFHg5nOJ2XOHdYY37ocX9tkdQMCJXKKE2kfrmS5VaPsPAHxp4w9mVIwsPjimTBA5rj2FysDZi023kGiaHYXJYdxM65+E3BpftHitVezQU0RmPtf6+C+J08hKniJpJ9OzRWY8pWozR4hAa3NkOB19AHyLGYWwwBoNhahAjzUQ8LTHa1us1RARvZ9VwTjoWgLzsB0JoJfkyE8R5YJRtk9tDilxYrZMGhmyTKrdRElxEIKw0GSKegxW5klr+NiNgcKpkysu9qdgXZqB5ghgSAyKdX8YTGyA5n8QG2WazHkMGVrisAzOV6xNhyLJukmtL1tS00qeeqq+50ksCaHgdEzEQK7cKM6iWRQIrdDzf18+eEk1uCCYBNUHZQ4ZzhZemUu3OYE2qLFdZfwIuZgyLipUyU59M2ylzh2wsApyR5noYKerjerlXlO7J7MJkXMmNJSeBISXNJZnOwNcRGJEYGTLfnKvaxyKSMZAkq6pxQHV2rWNjwyBDYHBGiwqTANni1ExlVxPMBwnqjB1iADqI01ECNtiy7JC+TgbM1jMAYTYkMGEiZaAA5E0BZioGigiPIouMfsBWSsyYKAwCTs+snGzmjpMZKJUAL6lGjAmFmZOSbk7PfgKBSMG7ynJ5fq8LmGJ1jcUkqFmBGjPzFo8ZTibXU+4hBYAnIE4kDBUvjlKzhkp9aNP52RvYlVyEbUZ45xB7g5i+5xNglZExuRfkSQVDE7CjmzuW7ytdq/ZAyuLR40RguMIH92aJJZZYYokllljiocWDBivsjrD+k7W81KcXz0leptevI05+eQN68Rrhzdu8z8mzpxjOfggAGB9v4P78Bri+Af1SP1+vcfbkEfxHl7j9wRo3P3yMP310hXjqcfn0DgDw8dktWuNx1u5x0gx4167xkhj7QUCBGAkxGPjJgg82J2aM6uWU1BrPK3OhsskjryCHYwSy2I09iICtEy86DgQeLPZdAFnG7aHDWT/grBMritYEtNbjst3BEmd2xav96cxxZKxaSdZuxNqNGJVZsfMtAkvbyOgtgl5Piq6fQCaisQEEoHEBk4+zVhduAT8WxxKeTLau5L3RxFd/N5i3JyRbUhWWTO0KKRk6Dm65ZPYadeWaLUtCXe+vlWEEghlMrlYnIMDtCotChDPn48xASrK51Cpx6KqBKXhCzMUdIpZjxobgV4Rmyxm8mCUmFsVNJAk3hjJGNkBw1T5KG69BHnvALPlOldh8DVSOF5MVZW4pkZYFEfkUYCRXlwFwOBozlfnISdho8v2kSMqu4NLaku4Lis0t2wqcUMBkVnFHtV5MlbDm8xdAJU1EFmfUVoHchaLHTokhVcl5HTlBjqSMF56JujKEih8rodSZkKmn7ISRk1vNlKOrWp4qgC0fP1bDqfC/fJyUpAcFw/I8cGbhmElFGRVAkLn48FmiCNiBcjtSBk1IBCHrqeEEiuD42ZSEmrlytkkUqZlCLOZsiQq0AVNmocxPKkBBGh9n9dhqvgNEFFWft8xaSO1kicFBmLFyErhRnzNZiwLCukrrPTrAuDJPsoGwMGIr10kRwM7mU5g2yH1ysSyNNI7MVmIwA3FSsFeBuDyIhhF3FjTqc6EHSACkgBUB7ftjBdUlllhiiSWWWGKJhxUPGqxorkd8+r98A8RYql8hAobAuwPi9Q3iNM72oVWP1VuPw4XFm591+Oj974P/9Of587jbIX61A33zLS6/vMLln52DW4fpaoX3P74CAPz1Z1cYn3isH+/w5HQLHw1aFwDIuayR5NwHg13XCmuCWHrrAYTJlKSESStzhWrMnWQT3GqGEiUR4uRGkv62d0Ag3B0sdqser+yJnN8yjIlYdxOs6mHshhb7oYHRF2JjeKZFcdnv8fHmBueNyNXfTT3ufSftL95iChahomOTghzMJDoHJPaWdcuIMREHyDVG1opzK1lDdBbcygs3UqWzooSLzaUADInBwKmFoNY6IIAMw2pfPVcJLQd1K4FsY5oIY+qGeJ1/JoSp6Hkkdsg0muw2YAZS1475GjQjlXuZxlNXNKnahwo7AZA2nemM4dfSXjKzPa1ZJim34wKaZGArnTOm/nfMrBBLoojsHmDGMsbUZpJYBylZi01V5UVKfoEYOCdwgC5bA5lnTYKFaVLGYA+E2Bb3CZpI6PnpFJazXWQeTF2pTra4QAFc6luYWg0MI/ZRq9J1wimgQmZqMECOKkYFitZK2eUDS8l0f4VRcbQQEkkiigYB1wlv2j0vveTkUF1nZREKku+D0FRrqWIjzFqmKlCqBrGyA02VSIdOHCKI9flSdkS+aMcIhuaaEXWkTpRqbSWWifxegQZEgK4zqthWaQPO1J8a+GABywiICkDERpgEgKzt7M5Tt/nUoNBs3TOCKfc2zUvq6sjaLrXmReWwkfVXUIEVXuffy7MfGwVMss1uudYEDJI3xUq2tQU4re5pDRpRWhckrTjsTGmPSsfuCCADjgwPI89Xdav4JCBujxbgEv/o8V/8i5//3RtV8Uf/x+///zSSJZZYYokllngY8aDBCp4m+C+/+q2fk3Nwn36C6bPH4FZe3F7/oAcbwPeE29+bMFye4/Kf/+c4/6Xag97sQHdbxJtbhJevgJevAAj7+PlfPgMAjD/6CPef99g9Occ3H58hrCNw6qVqBqBtPfp2Qt9OaFwAM8HZkFkNu6EVgcpgYG2E9wYcTWmxcBHBGxirrRXAzLbUOtG78IMT5oY3CDtCYLmdkyZ9u1r0c/qeF9cIoJFtbk5WAIBH/VY/EoDBEGPdTgjsxWJVhUCtYezHBqN3OQmsrU8BwKvOQLJGJcOwiR5vGbGNUj3MVfMqC3UMamKhqKeqbp2pKeBCKWFjzNppZEdG0jqQ3vVa0E9acKyJMNp2YBRoAoAYjDBFWFqOwmhKhV/nz5KRZMaXvyWt1GQ1KoOjD5NXBmILxA6YiBHbQu2uQQY20iZiRkmMyCPbHYIlaSIPGItcna/ZH5lRkPKoij3C1WfGaxU9yrjS/pnl0UjFPbpy/LpKL3oHQuWvr9XuCXZPiA3nBHrWT29IaPU56aR5lT6xIo7tNhNgEyizOWjlwc6A6/VOyPoBGWRgbdkAYA5G7UT1I4t5hR/zOfw+JsBMzDC1SFRznoec7gHz7DhseN7epKyCeQb64fFmEUr7EALl46VBxqRRAWiyrIBCbtfBzKIzXXO511SAMkrbl0Rd1kH1s6MZg6VOzkl/53rejDISUpIfCKGP+TriQHC7CmQAsiDtBy0Pum4zKJT+PCmYRMhiuXW7Teh4pmuSRTjrY/PRfa9AmISEUMWGoICsu8FTYYtkBhIwY5AwbPneTp+zKYNIxyIGHBDXEcHwbB/TBkS3eJcuscQSSyyxxBIPOx40WEHOwTTSGhFHeeM0qx48DDCXl/C/+zFe/WyN93/o4U7lc+YD4nULe29w9uwev/vT1/jVHz7CL16cAwBW35xi/R3j5IXH5i9fw//q1/l8/ruXAAB3fYPLf7vBVd/j8OPn2D9tsHvaYbiQ7aY1Y38WQOuAZjWhaQJO+oi+kTE0JuKWOkzBonUe3okTh9Mst3EB1kSM3uJ+J9dnK0HLk35A7zz2U4Pd2GC/60QSYtC3+skAAaBJWy20+h/7WKqc2g+NQV64p4PFl3yFNxuxjjjtRkRlRDQ2oNFxp1aTITiESNgPbQYljsGKMBoYF4XhwJCfdYjpejxZ+TyQUrerl3QGOEhPfVRBP67YHYgkVHyCJKd1uwA08SLO4EUki+gqPj0JgEI2Cnjijfye2CfEMKnf33pEZ+auM1HNMSaCobkgHqAJTSh/B+ZtJmyAOAKhl/OFjmE8SWtMfRoHQN0+IglwUzuSlOOXqnFKwPLtqMCPxBAAqmRLK8wUGe4A8KGMMTmSxEbWTGwrMKROyFkqy5zYFYl9MQFuD9FTaAG/5sxYSfuRLwlbYQXUqIAATTBc8nW9uJRAswGMY7BVd4Z6rUDAtJyBJ3cPAMHarBfANcA3q8hTbvNJv2dmAoQ9ktte0j6JAZDGny6JJEknVLoMkYRJBQBOW8Tq4deCprnqX86ZWiaSkGd0cX7PUf1MwBxxqf41nCv/ab5CR3nfrPNRi2bOwJsk0MoIfSxCoJif8wPtCijw0XBhvEw6J4m14AiBbZn76rLKPJRnILeJVACM3ADOa9QEnh0ogSXyC8H4GUyBqOyj5OAhrTFlG9VlzUKdiamRQa9qzURLmVHHlfNMAmBSG1y+51ULS2KisWNwE0FtBLmYLzFOBpXB1BL/AHG62f9/Zk78XfHbjrcwLpZYYokllvinEgtPdIklllhiiSWWWGKJJZZYYokllvj3Kh40s2K66vDuv/tDUODs4uA7gp0Y+0cGtz8OePajV/ifv/hjXFhx8vij25/gFzdP8dXLR3A24L+8+mv8j8/+T7z44SUA4M/uPsNfvP0IX789Rfvrj/DoL55h9XpCc32AeX0t5/jmBXCQ0rP7229w+cnHOHt+ieGJsBKmjcG0tgi9xXDZYTxlvLqIwLmUu9cnA0IwCN5g5ztwJHAkGFdKs10/4bBtgbsGiMBkAdbPxwuHx+f3eL65gzmJeL9ZYwoW+0nbQILFMDQiiMlAHC1MF3CyGTCpSKb3FnFSTYfByjluOrzbCf9/ezLAORHQ7BppAWlswEUrmhY73yCupNI3mAbea8uE6nIwAEoV0aoqmIvpUVgY0WsbSDjm10M0OlJLRKoIR9RcetEn0b5w+h5NCanUV38PVR+BHjMaqSTDExBJfgcQkmZI7djCyBVMKOOAJ2GGJK2EVH2myFkHwo6lxSPTzhmwowofNspGqCrEaR5ADLZFQA8t6g4J7eWvKPtUjpOcBHI7B80FQGv6POlcxYmhRjLCrEjihEFEQOOIWZtJ3Z4QnVSIQy8aGinsIBXk6Cnbv+aCfoQwgZgQHatjCuZtM8qq56R1YbhqL5CWi8jiNmPaANvG3OaR117qeYlSfs/rsy1ih+S+n1nBkym6MsoeEHti5G0zuyUJqaIwEGqtEhjVOqnmVhgsyvywek2eqrYKYYNkKgwBpYcHFdtCz1m5dtTClDONCpS1Wh8jsSOANBc82yTdDwDA3s4YMqktSOZVWAFZKyRTnlCYNVTmCEn7QZ8vjknbR0/mGGGj7DA/dxahZOWrji61gGgWLoXoXyTBXnEVEbbPsSYIO2U81CKn+jkFKhor1fDqqyQuzCqgWstpvHorWQUyauFdilQuW8VaKeADZgU7nTM9aNLn4Ujgvc0WzEssscQSSyyxxBIPNR40WMGnAW/+64MkvBpWqdSfPnmP/+mTP8NnzTs8svf4m/EpAOBvdxfYji1iILx/f4J/9f5H+G8e/Rw/7f8WAPCfrn6F68dr3MYeP/+DT/G//Sc/xYu/vcDqNxc4+1LsTS9/fgH61d8i3N4CEPCCXr1B30o2uWqcZH6GgMdXGD8+w/Z5i91TaenYP+sQOwZNhP6mCNql5I4YCP0KJ3eAU5cIAGAn17l/3OC7J2vc/7MbPD+9w6YZYVpGu5HMqbceh+Cw8y2maOGjwaN+i0fdFqNaA+xDgzFYjNHhfuzwdrvG9rYXDQwAw7bF1ESMLmA/NHAuYt2NuG8l0/Vs5LzEGPyEMViEWO6DDwbORgxegJMYDFwTcuISIyFMVlpWUgJ1zPOJUMtK/b1Sy89BDFARS5wJHyqln+uWAyBnF+Q16QHlBNkk7QQAoTXIYpKuJMfclOSJLIMRwY3+vUoG61aHMIqVox0IZtDhaQuOHavkJbcZ6LkMkPQu/Ioz4DDDdYKMm1haSdiUBMseqLgZJHCmKXoJZjqi5kcgdOJOUuar7B+T5WO6Fb4cV1wyJQE0I/K3iwgQiqtIAm/gKs0G1nmKZQ5M3T6Acr7iwFL+JnR5SVIDHOIGMH3I7TwibkvVeeRnzohOdZp0z45VPFNvQQp1FMlridM9pJJcqsNJnsNYdDPSv0mQUa7HCK3fiQ2uGUlaX/Qw3Gh7iK0HUkXG8CgnsDPxRm1FSuPOa6KW94hqN5qEIuvnknh+7jSfNViBMge5peMomU/3GkAWRp0dT/VGSMHDWtcBLkrLhFXnmASUpXN7EsthlLVix3KM6DgDbKmVYwY2RIL10u4xA8tSm5ACm3n9ZXXa+TFEWFR/N/Olk5/fak3PBGMrgCu1sDCVlhSu1hQixFEq2vK94wnNzkhr0hL/Qcb/W7vJ0iKyxBJLLLHEf0jxoMGK1gZ89ux9FoIEgMYGnDYH/GDzFhd2h39192P8/PojfPnyEQDA/moFMxJWXrQA/uT+h/jy80f4yZUIaf7B2W/wcXONJ/YW/9Xpz/F7P3qBP//4M/zRFz/CN78jbiC3v3OBzTfnOP9yQPvyHnj9HuHNG/D0PaWst+/QfNXj6qNnOHsiYMfwpMe0NjCB0V57mKCVRFMq8tEZuO0Ec/Cg0YvLiVNWxMUKw1WLN//RJX757Bz0ZMBqPeDxiYhjfrK5wdP+HgaMCIIB43l3g/VRqa3Rt+mJLX61f4J/8/4jvLw5lTHuGnAgTFMDMGG0EdNkEVKPvmFs2hErN6HrfNbcWDs5xxgs1m7CITjcTx0OXoCcQZkNPljc7ToRljw4qSZbhmlkTGSkCs+jIjhGgAGycSaoSUaqy8ZEWQcmZqkDIs6uJymYCUH9U/1kEdVRBYPJveF1op+1M0yVNDr5OTqAu6QzoL316rIAoPTeG0YYxU3FTwRzkPO7nST1JlQF8lQhVoAqgRixZRW25HkSpdsETZBjN/88tigihywJUOzKfGShS8OlchsBf1KquCLoWbY1UzVOtfr8QICymkcQ4Felip/1Oz5IckXYlVCBIFWYgCrBoxlwwU60AygQPJwQCprCVOJIyoSoJyeJalTjOBZnzT9Xn2W3hrJNbAhkAfJc2Aqm3FdxJElJrPzLVCfiyiZxVEDLI1cOVjvj2Ne6K1WpXjUwiGk+1loAU78REBXA0ecuX38CXMb8EBWmhOG5zScgmiw1w4C46OEwlH1Uzl+DG1kDJF1LRLZtpirxrsGK2NEcAEnnrJ6fGI/WVnXO2gY5acOwQ75GivLcmwlZSJa4Ag9tEedMKqF57et41MRDpi65hxy5iCaAgljXfD0vBvI8prlRwCkzlZiLzfJIQDSzMZIHmi3NQZglllhiiSWWWGKJBxgPGqw4DA2+/vYKJgkpQt7rTjYH/PLdY/yv4+/D/2aD9QuDJ9/K2+/5X98DzAirBocnLbavLe5eP8a/fiRtIH989c9wfrrDWT/gs5P3+MH6LZ62t/gXT7/Ey/M3AIDffHaJb96e4/rrDTa/6XH29QXWXz+F+VLYGeH6ZjbOeDgg/vpr0G/kbXPlHFZNA8QI9l5o6YAwMQAgsrzbckSMjLlkPmCsxcpafP7VD7D/9BQ3P1hhuFzhKwVDvn50hauLLS5We6zchNZ4XE8rdMbDab/Myk543NxjbUac2x1+b/MCZ26PX20eAwB+8eYphslhf9cBowHDYPQG7wenY2DcNh4XJ3ucdgM669HbCVettNsYMDozwZBYnO5ji9uph9cM00eD9/0a27HFzXaVr61tJBuyJmKYnLStsIhsGsNoGo/GyjUwkwASBGxaEQS1lapcArA669GaAEMRhhiHIMDJ3jd4c7/BODkM+wYcCdPBSoIEwBwk86ZQireS5CRmBhCDkSprcqSoLQhbcTORFoUI6iM4EsJK5iD2FmZUS9QEcDg5kVX2RRIwzEBFYivUiQgD3MbiwlElc8GwWoXKdrHRbSsHCLlYFvRHXQrCmvL5aapEQg2yswegDIiAubsHMHNYAICg7iIUBXSgETPmRGZ61K4qtW0n6THrSr6OPbXQhIbAapvq4cC9HiAl49oqxKlFIp20Ok9tsylF88R8KCCUPKdaYdfr8k0QodWq3WcWBGlXU2YD4cNtbCTEqIKxtlxfHpuHrrMKQEjf4CnzNQRWZxPyOh9pExflc6vAVA1oVDeCI4MmU0CIBBzonM1sTes2k3S4DMhouwRV56lFNpWdktYgedknPw+mSvYhLCAzEUJlg5uueT7XXNgHhgVIStdiuYBqlUhtskdNz5uZFFBKgGN6XnxiPtDss/r4CVRjI9dkPOZrLGLmapPspGvAIwnVcrUtN1w+VyHaJMhrUvuLnqO5B/ZPj9C+Jf5JxN8l8rkwL5ZYYokllnhI8aDBCrslnP5pj9CXF142wG61gtsSNm8Zm5cBq+92cK+kZSN+9wqIEc1mjeb5E5jxHG5nMVzLG+H07Rr3qxVuW+DXZ8/wx+cDnlze4bw74KSRDPKzk/d4srrHr0+v8PajE9x/3qN/e47+tYAF/XWE20W4Q4C7HWDf3CK8eJmZF+x91rz4dwlyDtS24KBJ+jgC3oP//N+i/0WH9Refwj8+wf65tJnsHvfYPl3h/UVEOIlAo7YVrjAXbBOwWY3YdCOeru/wpL/HF/07/N7JdwAEzHh9OMGXdIX9fQcepGWDR5lokZlweDM5bNct+sajcx73k7SJ9HbC2k3YuAErO2FlRgzG4cxKNhqYcNnusQ8N3q3W2E0tfDTZEQUAhsbDB2FzJFCicx6bRuYxsUY653HiBkQQWuMRj6gHGzfgxA6iu0EBUbOCXWzx3eoMY7DY+RaBDe7HFttBMtD725W6lJBQ00krvYkhkKqZEcBEonlxZEcItYqUxEh0D6hNTBog9ISQEjtKzAxCVPaFJCKYOxrEkv8dU+ylol3+KAAK1H0ibVPAEQEASnlbqPbleJnKzyWRY5QEKnScq9FJToENgw3NwYZUVQdgRsAe1IkhbWKUaWBKIlb6+/V4nj9wXMn7WmA8Jb0FMhavpeh0DTTREeuhJH/HrQy1w0me2wRcpJNXLQxkGdwSorWAMlFqbYh0DmMVhDE0A3hYtQnABBOBqI4Vs3YfvXa7N6Wyn5NkzFgKCSyQ9hFdry6CGgYUwIi1lksah7KOODFJZpoXZQzJrQJHY5wxPhToK2v0iIWR2j4yoyxdiwIcRoDOPEY9t5koa31ki1eU4yY2hIB7jGhiNU9qCwt1A5lke24VMAiMicRql7RdSFxdysCldYqFYUVlXmR//Zy4sk2tgARtdSKPCgH9kALBJjEsSNxGSHRg5BrSOSkDFrldTe8TG2A8X8CKJZZYYokllljiYceDBivcPuLRvxngNxah02q1E5G/Zhuw/m6A3Y6g7QFI1qZPHwPGACGAxwnt9Qh2LWIriY0ZCXwromvhvUPoHV6u13hx4eE2KpC5HnC53uO8P2DzfMT+kViI3g2SBbw7OOC+gbtz6F/3WH93irMvr9D8SoCAZIGawvQ9YC2STyY5Bx5HmMsLgAh8tkFcNUiCD2YIMPc7+K+/AQ8Dwl/9DcxXHU7PBCw5vTiFf3KKw9MO+0sLv3JwB4bvVUwOQsvf98B9x3hx+gTmcsDPPv0GP9i8BQD87voVPupu0BqPb9dnuN31GA4twl6zVBXGDHcN7g8W20ZYBC+NjME1AX07oWs8Nu2ITTNi8A6nbQFpThsBMp6t7vDerDFGi722izATOhvQ2QIuRCasmgmdm5fxT9wAZwIsMVZ2QsjbG9nHTnAmojcTGgowmrl0xqNb+/x3ANiFFq9HaYX5+vQSQ3CITLg7dIhM8MHAe5mDEAzCtkFqH6gp6wAE3AmM3Nue2BcpubIsrQp6a40TC1UwZfHSOFrEUZveNdFOOo85ctKDzDwowpnzRNBMAKqk3Uya7KSklJL1pGwf1lzkG1KbSE2LrxJkQIEKq60mqZWgGlNKdo3nubgnyYZs5fmlWK4w6TaUCnw6Tqpso7IvFSHSWlQ1NiTj0bYKGpWBklgH35NspxaFkqRW2yRdi4iqxUJFWD0Je4EV3Ei7qwUlt4ToRbekbnVhhywqOk/wNaprtwep1mdRznQfDM8SVrnnJOKgOlzTiZaHSVov8ehEFEEkah61MG46ZhKSnbUo5HmBtlWI7WkSmK0BimNGEHBE7LAsbJAkbtOUD7N4ZqUTcty6Q3ov8vNgWOY+bWLUklXbnuJoCkgIAA0QNozYWmFXKKsorz0wzEAwgcDM2dY3RRLSZSMAa9K7yWM0EDHZ79FjSWHqZwuARXpW9TvBcWHMsP5e/Z+cAjCeEXhVPWBLLKHx922vWsfC2lhiiSWWWOLvOx40WEGR0b49wO0bTCdyKbERWr7bBtjtCDAjnqwQn0gCun/Ww68IdmD07ya4+wn2wFk5PdpSCTMeoB3g9oTp4BBWkqTe9R3uzlZYnww46QecdgMu+j16ZQ0YiribetwNHd7dbHB32+L9T9c4+ep3AACbV1/A7iMoMEJvMW3MjBYMAM02wvdCxfYdCY0+tdhPgDtc4OSTK9ibA8z7W/B2C95JCwa2W7iXb3D/w5IrAAAgAElEQVR6dYH15SnCWQvyDL9xGdQZTwx8T2AH+N5iPFvjz8bP8e0zARt+9ugFPuvf46dn3+HT9TXeDCd4N6zx6v5ETrHvsuYDbS1grOTTOn5vGQfbC5ujDWhajxgJjTI7jGFsuhGrZsLKTZiilXaRSd7CIxNaG9A5n9u9QzTw0WDw82UbmRCZ0NsJg3W51cUSYwiy7R2kLaQxIYMZgAAWp/aAS7eFRQQc8Li5BwA8ae+wi62AFb5HYIKPFod0zKnH2604sXhvMQ5OErqUmPgkHipggNlLdlfTurmJkpBVGZsxEbbXSm8bELxBHG0WHox0lPVZOQ5XwqHpHBSTc4EkzkmIM1X1U5U3JfukwEoazpQEIdPfdbsyWHlmzIQCdhAXtwKggBVJ28ICsf2wp564sCTqLJgT7T1Vki1gJ87tOnZiGM9otlpRPlOHmOTEYAHPKg5qdD9TTlFX5ksCPa/YZzYKE0wCKwwyNZ+ZRLekSuJ5ppjKQCNJJQcCGwOu3GvYAGR13pMgY8VG4VTEJxVkVT2VWLNgLBVwRRkGHJAzbZ4EKAFJe1A+/hEbIgEvZHVNVe0i7A04i8bMblOl46HHClQAK6C030Qq58IcrBBNmKPsXTdgIIOCFPQep8PUIqmpzUW3T0K7ZQP9V58buZcJdAJs7xFdRJgMaLDCmKrGYo3JLUlsMXO9ATOIqOhR2LSe59ckz9z8eUoARqT53ygoyLbXUxiCXxVgUJ616vtjIhyexDnQtMQS/wCxCH8uscQSSyzx9x3H/gtLLLHEEkssscQSSyyxxBJLLLHEEv+o8aCZFWwI3Nmjvm4t9xlCWDdgQwhrh/1judTbLwymc4bdG2y+Mbj6ywg7BLT3iedr4HuGIdKeYwAs6uqZhntghEOL7c5h1/e4Xo+wNqJTcciTbsS6GfF0c4+Lfg/3UUT8MeHLH4sjybvrHrRz4n6wiqDVKD3vqRLGAG4agNRZgHlWxbQHgt0b7B+v0d6ssHp3iuZmgt1qq8vdDnS/A3yAvdmCQgC3DtZRFvNsLGCCARPQ3jH694DxHV5dPwEA/MvnG3zx5D1+dvENnre3eNreYTqx+O5EmBcv92e4Hlb47voU47CWanWiZQNIdqBS+bMYbAOKlIuUbBn32gbh+gnORTgXMI5yn4iAVTciMKFRpw8GMAWLm9RXrwKbiVHQtxN6J2KagOhbRCbc+w5jsBiCAzNhUhcIS4x1M+J6tcKT9h6GGJ2RthAAOLEDOlV8PHMHWIoI2loCANd+jSf9CmO0OIQG92OHKRpM6nhyv+8wjg5xtODJ5B75ZK2IiRAbArdCVY+k7SVNhO1zUz6sk5KrsvqlLSIterUBNSuf5yNOxcaQvRG7R2tAgaTbI1B2VAGERk7KRDAeRYcDSfCSZo4ix2FI7n0SBzWBEImVpVQiiQaiAfwaM+p6bl1Ju9SsgsqmlILQ5MM0d4xwO4Y7MJodI3TSCjLTnWCCP+H891mrRf0FUpFcaqZCsqElL1oc4twBsDIHgidwe9QGVFXI89+Tjgmr60fN1Dfa/VA969laNlnwOoAnFWDlSpBU92FDmYmSqu7Z+tiQtJql9hinx01Mn8S2IIDb0r+ROjJI2yrgRAOHvJm3ANXuHtpCQ1SxR5RZke/z8fEzC6k6Tj13htXyVp17ihRFERzVMZBaemZyS9ow6cPYo3NU8xytgXEMNkEeg4ZyC5awUiJ4kOPElmesBnaiRZLWSBJqrQVB2QDUYKa98gFjiQvzIjGfUntIskWlSAiOEdvEWEn3EeATD/e2UlddYoklllhiiSWWeIDxsMEKRxgvOpAvioOkGgHREsaLFrElTCuD3TN5m91/GoDTCf6uAQWL1bsO/dsR/Tt5o3cHi/HEgi1j2hB8LwkIecCowFnoRPXfDoTQG4y3DhQJe6WEv+8DmvUE1wR0jcfVZoefnL/CZ5v3AAD/mcWg/O3OeGzcMBOF7MyEbw/nGKOFjxZb3+J6v8Jhkn22dz38TYPYGkwnhPGsgds7uH2v13CC7v0k2hY+IjYGYeUQepMtOKMlUODsStBdR7g9o38n4zh8d4IvH6/x6kcn+MnjV7hsd/i4u8EX/TsAwPPuFi8OF7Am4stDg7i3amOoL/VBwYQIIEqPN3nkxJBdofH73mFqGOhCSQosI3iDvYtoGwEdmAneG4wHfQlPSZ3+u1fQo1VNi1U7gQA4E7GbGtztekyTzaCQdQHORbzuNrhYHWCIYcA474Rv/bgTK1hHAQ0FBBhYxCwC2hqPx919djzZ+g5DdBnMeN2f4ObQYze0CMFgWjuEvQXtVB9lMDATSW5GxdaSrUHsbJ6n0MQ5fb5yv0EQmjsZaT9iAIZCSRDVkYS9JLuSTJnS4pB49FGTI9WvsGN6nlCACpTWipnwZIBYVCZxzBpwQPk7mBE7xQ4anrUwJEFK4ws1fmYHmZJ4ToDFvE3EDoTmjtDeSUuIaG/o7hFotgCIMJ3o2mPK6y/rbxhkkVCKEOcHW52EOIM5CUTMoE8ksDclec3rsrRlIFTtJ5Y1sU6tEJSFTDNoAGRNChBmAJOMv0p20z2plgZFABNQCzimlgJEAVuio+IwYxixlUsNK4i4bCWQwglASq0YI80MPsio0wqXOZ6thzwInYva0hQCWkgSbuRc2SKW8vigdrRMDKpcQPgYGCPWdh2dcx2kGQhGXXM4tZzo93saWxwMQn9k1VKDMo7FZSce2ahCQZ5IiIMRpx8FxYqVqv6Szq3aKvU8Fd0Rngm1zp7JDAghf8fOdDAmg+ZmaQNZ4t+f+HfRylhaRZZYYoklljiOBw1WREc4XFq4wcBM8qZnJnnpCz1hPDGY1oTphLKN2+r5PZyNuA2E6cxg98TA7RyaGxGt6PYe9tAgNgbNluBXRvvEGV5FD6cNwa8IoRfQAoDa3Mm4wsogOoeRgEPHuDnZYDc1+OJMwIrn/S0+dXs0FDCxRWCDfWiy1eal2+F5d4MpOjTGY4gNXgwXeHUQvYhvV2d45c4wmU4SW0vwmzIWMwHDmUGzi3AHOWZojxIHlmuSCh7D3U9obhj9W7mI8bXDcGpxe3+JP/n4BP3lAV88eocvTgSseNLeY2VHfLS+xfax2I8mzQYAAggwIU7igJFcNbIFYBNBo5GER4XsuBJFZMOYJoPJMsZWGBFZD2LQiVZBP7kBjBHAZBhTo84uwaJ1Ho2NOEwO4+AQ92WMsTHwhjEODbb7DkbF9/pW5vn1+gStCWhsgKMIzwaGGL0VYKu3ExqK6KyHAWPjBnTs4TT77azHedtlhxRmwvvDCjf3YtU6bltgMFpRLpVU4wUIk3kA2Nrs/sCGxbnAVZnJRAjeSSLnTRF9BGDaIHOmOgQwsg5Mn0QrynrI942ASeeYBpOTela9h1psE8R53emvUimvqsms50kVYCZIhbzSEUgJL2sSbKa5ZgQq1hGriGO+jy3DbwC/ouxCUosiMqnOy04ATr/CTCS0BitYwQTyijdUYAETFRcHkkQy2XpKTk9ZPyGLOtb5ompeZE0HWy6ca/QnaV8AGVwEKXhADHYGoSPYkTJLJ4mM1kY4qcJfH9pkcVKAJ9H/SGBFdIKwyBwYxMDFLhPza6nXQf4zcwUQFdCRq0MICMWgvBBQvpRUayILpiZ9l2ydqsygpBlSR9qmAktTEo+pgDrZKlgZDqz6LgmsSJ+F1qi2Cs8VbVnAQdb/8rnTJCTmDDGiM8BBxpqBsSMtFcRqWqvvZnEC4SJKanQsSKCibmqQbVZrJlLzzsIe2QcvscQSSyyxxBJLPLR42GBFA+yfGFilfwMiPmc8YzgzOFxJJXV4FtA+EfHJnzx5he3UwQeD3WgwXDQ43FmQijY2tyPsIYAtobmPcHt5y7U7n6vd04nF4cJiPCexvtMEpwitERqlqUcn4MXL7WO8vhSRz/V6wKYbYUlU+adoMHqbHPzQOY/nm1sYYmzciPNmD0cBl61U/HvrcdoO+Lq9xL5dIbYWdlcSXDsAIEJ0BqHjTBWvHRTMyLCHoKKHDIoMu59g7wa9hjXMyHCDxe51g93HDr943uHFI2kD+fziGifNAEcRzzd32LQj7oZudn8mbzFMDtZGhGAQI+WWjXU/iljmocF0cAWAyDcXoMmAI4soYErCbJWopyplLa4Xi8NBCAbRGMAKG8LaCO6CJH0AyDKMJlPjoYF1IigYdf8EdlCmq8vfWyfZ7aqZ0BoRATVgnDQDDEWcOAG+WuNx2UacNwexb7Ujbn2P9+drAMDbwwbX+x57ZV740WLaOdBkYLdyUWZK/6VqMCFGzq4ugCRCktzb0p6g1PQ4UU6wsoggI1tUZhCDpIBtrLhBsM5x6AziZMSlhBgxiAtMbcsZJ5PbgHISm1gv1f1M+SmxJIkmIxSQVphUpdYKfm6BqI6VaP0Uy+fsxEKVDWP0BLcDzFixIvT0dgB4BwAqGpocUUYUgklbVasZQN1moePKoF/6W74RLEwZAqAMhVIux/zfVG63R5/rR3nulElAhOwaw06S2DgYmENaJ9r+YVNCK6CXGaqKOwGhQ3FTqQUqId9VbMv1SwW/EkKtABjW9oNj4CC3RBg98HGin8JxaVvIYIX+rq4fiVVQj98czAyoSgwW0vWcx40K1JgJZMoc1ewdVhAPQGbNmFFteQMVwAooArENcisKhcLi4XTtjsEmIhCBJpMdiASIk7HFxMDJk5fGnYCz5B4EBUfKIJJLCaKMNdnUAgBHRnOvosxLLPGA4rexLxbGxRJLLLHEP9142GBFy7j/jOH2QKOaE2YA7MAYzwnjOcOfRnz8w9f4ycUrAMDGDXg1nCJeEN64gJvDhb7olv5eioBfCVtDLBYZIIIZlf5/w4gNwa+FS8xOgQp9mXR7qeIiyottOBCaW4vwUpLUoVlhMKlqri/LlQMDIvDi9EmmPTebCavViNNegISr1Q5PV3c4eTbgu5MzvLndYLjtQFu5nW5LCC3BtYS4l/FIclde8u2s3AlwYxB7B3OQ7MxvLPzKoLv2oGDBRt7Q7/cCVvzVvsX56R5n/QGbZsR5u8d5u8/tLIYidr7FzdBnxoit+OBX/Q69mzAGi3eHDW72PYbJIWjS4SeLONic9Bh1EelVHwQo4EFqw4iREIKBcynBYzgbsWpUy8IFjN5mTQlmgjURPhhMo4N1ETFSBie8N2B2iNHkvzMTdvr5tmnhjGiVWBOx9w0aG7DzkiU4E9Ean4GKc7fHudvnVprh1OHed3g9nmDnW9xPHe7HDtuhxV1mXziYnZ1ZhbKrAJqU4GibjVhfcrb7NFNpJ4HRqjaTWGwC8rfaflOr+kbn0LqIaFjmnBgxGgGEfMnSuWFxtgBAyWb1yOrDTLKujSb5VK33vGlTrgeE7NCTLWGV8cBWkkqrn7NR1o1lhE7Wi52BCAAcYG8ZbieJsLAIytjS/Ab+YOhl/FAgiKpEX88TLRBBMCzzGxiAJXGbSGOoS+iR5Pvi2Bo16TakcAkoE8CCCNL24+JMF4QHIyyI6nghkrCXki5GmkNNyJMeQq7INylJTlo50ipW9CB0rWjVn9qoGhvfM2F6c6sOlAwSQh1marZM3sYpgDORtopU41NbV64YKfk8eo2pjSi1eWT70sR6S7oquv5Dp+fT70NimS8zaXvMEflDWqIINuleqENNBhImI/8/WAWZp0ZAi1gxdEAE8jwH9BL4Bb03ifkRCBygLTu6reXckmN9eT4SUymxMabTY/rJEkssscQSSyyxxMOKBw1WdP2Eze9e4/6+x3ArYIO7t1IVW0WETYQ9nfCzRy/wrL0FAPz87iPcDCus3IT/+Nk3+OtuxDfNY5SpaOD2Ckb0gAmcX36NL6yEXBRW8T9hdOi4rkXsjw0QGhH7ywkDMH8Bt1K9TgkYANiDUL0BgI2FX7WYVht8dyE7fXM14fLJHf7g6Qs8ffQCt2c93hw22VZ0d2ixv+lhthbttYG7L5XWRB9utgZ2KomNGxh2iLCqB7G/cgidVpsbgtsz2ttU9ga8X+HtaHHb9zjbHHDWH/BsdYeob9+OIgwxboYeo3donYchhjWlJL+yE573t/h88x430wo3Y4+7SXQ37scWu6GVd3imrEPxydktWpMsYgWkSP/ejT2G4OBVQNNHg955bJoRrfFYuwljtBlM8FHaOobgsJsaMJPYo4ayfwJEOGrlWoELmQML58QK1RILE8MGAAI0OBPRuwmtDbhxK1y7NS6aXbZGfWzv8YPuNbarDu/9Bnehh48G+9jiVufh5f4Ur+5OcBgaxGBE7sAFGE1M/GSFlbF30mbTS9sHpcRlWz3immiBCZm8bzi3MoAIPAltIDRaqbYCXkQjeiQchB1Tgx0gBtqSKM1EMjVCoMwWSdXt8gxp4sqQRNYyQl/o8jaJFRp5xjhZOebnRSvVijdKuwnNnzmWdWxGRjOpVkOlaUExZYqUGSB1yDkYnEAOZSSl8GtCiKkaDtCkCabOI1sFddQaOaulpkgMBMZMzBGxIAVcs2OIlO0S0+UJiKFMIUogRl/YTJgogx+o27JyO0zFkkiHrsRYozJJij2qaHZQeqYZwthJIpfEua1IhixIUJ61xL4IZU6LpofiB1QyejkvFcZEuk8zIEi/s5wCFMq8yowPHVtM16ptG2n/yQpbhFIbUlrPOod2SOsUueWEgDKHkUTrxJgCKlJppwmOEDttffN0BILoz17GkOxRYQSoMFlbhHL7SHouQlszL4CxYYSTI92NJZZ4oHHMuFiYFkssscQS/3TiQYMVl80O/+0Xf4mvdlf4zd0FAODVuzOMewdyEd1qwqOzLfahwb9+8zsAgL/57gmiJ6xOBjz6ZIv//uOf4182P8YvzUcAgNg49G+FrQECgr6QxqZUUZOKO6f+fw+4PaO7lpfD7r0HW0JstMpW0YgB0Y+oAQszyctoojvbkWFHrnq3JaaNHGc4b7F9foU//uctvnj0Dh+tb/Gj0zf4ZH0DAHAm4OX+DG/2G3z37gyHu0Z7wSlToq2q2QPywtveGjT3Bu2dvlS32mbzyMAEoL3VBCSU/cfQYuobvD043PY9wqXBupFydwIQRm9xGBvsxwbGxJxTXNse79o1Pj65wdPuDlfNFq3x6K0AERvXYuwtJu2tSTTqi3aHTmXxV7acqzMe19MKY3R4uZd2myHI8o5McCbiWSeA1aRv9V7/HYLDPjTY+RaeDbaTgBkH7+CDRYiE0TplbgBBhU45EAIkB4w2IjBhPzbwXvUeSBgJRAxnIpyNmRUDAM+6O3zevUNDHgfNtDvjceYO+NFKmEC7kxbfnZ3jelrhoNeT5ggArseVOJ6MHQITzrtDvm4AePH+XK55sgiDFU2LpJ2QwvLsbzSZ3P7AVsELq84PXsGO1KKRmB5JyLOJuWJOrrAKyDCibwtVvYm5BcK0QVg0Xqra7CKoi5hadUcZDOxe192BsuhnorkTKzvCE7gBYsMIPVftALJuQy/JpDkwbCz6Cqm9BqYCFU05thxDn0dlJ+Vns2aHsH5PxNK2E1v9/ugYMernrrRPZLwjJelMBTQwOnmQ62VGaZWAATVRwCQASJR/FX40iS3jYr6vARZkGGSjgG9Bn6oEFoxG7muUCycmUKV7QIEQA2VRSp5EqDWzRwBgMrnliEmPXWmTSFuG6Njk1qSaVZJACMs6HeVzNgxY5LVXWjjK4TNZw3EBd5QJApQ1yZGK4Gn199RiFpM4pwJoqWUlRlu0MCpx2hz6mJjBgAPrOFDafdooDJZOvotplOerBoqMIWnN86KxQQHC5Ej3Iq01vQ9+zSIImtYGMVCzbJZYYoklllhiiSUeaJi/e5MlllhiiSWWWGKJJZZYYoklllhiiX+4eNDMCkcR/9nmV/hh/wpfbx4BAP6v9nO83m5gCHi03uKq2+H//u5T7P9KmBfdtVSw/EmH/336AX7409f4H57/Bf6oOQAA/vzsE4xfr7B6TWjuGWZEdhdIldy4Irgdww6cBd2aLePkKxHxZGcwnUql3B4YzgfE1iD0gg3FpoifzaqzejemlhAPpFR3hp2A9jagvZUS3uZb4OTb/4e9N4+1Lcvv+j5r2NOZ7vDuG+q9Grur3HZs3Lbp2BDFEMjkkAFClCgSCjGJRCIlUhCJhMMfyT+RIP9EskSEQIqQSBAJCihGRArKRLCJ7cY27sbucg+urqpXw7tvuNMZ97DWyh+/tfbe53W1wdDd6XKfn3R17ztnnz2ufd7+fdd3sFxfzvnK/Slfub/j7HjFvanM2L86e8b3HX0AR/D24hZXTcW2y3Be97GaPtKxm86ybTJWVxXm0lI+ln2cnAf0Btq5QtWB2cMtzVEuPh1AfaQxjaaZB9w2p80z3t1ZJnPx1Uhsgl2TiVSh1XtsEoLiynqutiWPJgtOyzU+aLo43e1HM/+a0Ms73ro+o8pkivGk2KBVwCrP1Nb98i5S51tnWDci77Bmig+KiW1YxGVz3WHwzO0OHxRbl+NRPK2nAKzaAud170exbnLq1orJJDL7qrTo9kMA7zW7XSYMBoRNQFB7uv7racWTiaz/w8kRH1ZHTG3Nh9sjunh9jostD8orAGa25la+4la+wgWNUZ5JMnMAlq7EKM/OZ2TKUeq2Z2kAfKW6jQ+Km6bkalexaTKRjUT2h3caYx3eK7zXkX1hB1p7p3sqfWJWKM+QABJkFttXSkw5beint5OvhbLCuvAT11MVTO7RRjZirafLHO0mF3PFzFPNd7jJcJ7bTQ6totsZYVf4wUciGW32ia0xljPR4sWbIdAxmJSOJSLCdqCXYu2lasRZc1eMKPs9s2LM3gh9JGZvvsnAjOqlC8PpERlAkjhEL4iQmAZeyaAaEWBUlCIxYhv0ap7M93KegCJ4kU+p6HuQjiUERXBmYDSMY1KtHHiKr01vpyQLOtB62CZBolp9HqUuSqRmPgv7JpjpXMaY3XR9UoTrOL1mbFraR7n29JYoK0F8UXpWxSiZI1i1f27i++Pvnf5+VPSMohA3qjPXM4FClD0pE3rZU8gCdEoiSuM4CGNmhRqx4eK5U4mNAoQQxDTVRs2LjxG2I++YkMV9c0rkR47eR0PGQSR9GGHsuLnbk7IQwJROpC6H+pbVq/mKv/DyT/NH3v3Rj3z/L7z80/9Q6/l6nz/UoQ51qEMd6juxPtZgxVVX8cXdC9zNrvm+6j0A2hPD+6UAE1Nbc75dsP3SMbc+Hx8Eo7Gly2G7mfFXqx/g3/nkZ/m3730WgDfmr/F3Tj/B+x+ekH+QU1wo8puA2YEVPINQS/qI3XmCkQbC7jwhRmbubufUC4PLINsEihtpkFJaiMuk+UGJ6aWp5WG0l6draGfyvrcK3YmBX76Sh89s1VGd1xSXmvqrGav7E67uTDi/cwrAr926w0u3rnh5einmjlXLusvZdDk2SiiOsh2F6Vh1OY23PFnMeHI6ZVWIgWZ+rSmvJJ4xX3rs0xX2ydBUdHcWbK8KVvcM3UzAF39p2E0FhQmGIeGhcNDovcYLpwgYlhvLuip5MplKGkXUvxsVUCrgoq9EAj26ne0p21nRofUgtSizjs5pdo00621jcTdZbzL4ZH6MLjsWC0lVOZ5sOSk2nORb5tmOhd3iRmSjua3xKKamYesyll3BdV1xUwhY0MSGX0d9fuc0NVlvKhic7o0CE8276Qoua7nt1tuC83xGYR2rbYHrJBHEZJ43J3cBOJutmWc1i3xLoR2ZdlS66U1LfVAUuqPULRPd4IKm1G0vm3llcoHBU1eWq8mE67Zk0+VsOzlHu87262o6y3qXs3VKrhcjMK2Phty/B5VHEiecwmcKX/leKpQqWI1rPcoGobybEP0MIqjUaEkhGflnhKCoCkEjrPF0VSPmqI2l3WTQaMxmMNVNUo8wTpnoDQdVjCEVSr43ck+lY/EGfCHL6ySDAJIZZfp7SMWAFDWbAJNk9qkCQ2wmw+dVbJDT68ESUznSYIn3e56QS9noYH4qryk3pHOEJLkAVDbIG0TiYeQe00FkF/3FGI5Hdnx0QU2QZjrtDwHPYDirnZy3FC+qHbhGSXMNeBPQTuGtALIJU0mNuuoG00qv6OM59yQaESj4mv1N/3R6P51ED+OpP/7G7PtgMFzTPh551NiPpRw+aSlU6GUp4zjYBMKELP6Ohqo9oNJLBeN24jnr40o7hfcMxqkRNEpmyrIN1ct3uiKCRxG4SLsWlPgyhcJjpp3Ic/r7TuEajR7Lcw51qEMd6lCHOtShPoalwnOpEB+nKl5+KXziJ/99PnX7MZ8+eh8QR/6dz/BBcV7P+bl3XqX6uRmLd6RrqI+0xPdFRsT6gWL2O57wn7z+fwFwbNY862b86vZFfub8E3zw/inZeUb5VJFfy7kqrzz50pFf7CAEfGEJmWb5kkR3bm9r2onEIJodFJeBfDXM7vlMHuiDljhFuxNQYIg+FX+KYKCZy+9sNRh82g0UN47qfIfetnTzgnaRsTuJHgzHmu0dqO92mEVDljmcU7jO9EkaVdUwKRp2Tca0aChsh1We96+jx8GbC6bvQfUskK0dxbMa+85j3JOnAOijBWoxZ/vGbZqFoV5oTDOkULgcXKnoJlCfSsyhL8Je40IgGtlBKOKDdWpU9hoWhao1eqewm6Fr7I3+NJiNEp+ClDSA+BsUlwIYZZuAy6GdKLZ35PP1qccfdUyOt9yabbg/u6YybW/gWZkWg5ffyuOC5qqd0IZkwGlYuxyrPF3QXO4mXNcl2wiWbHcZXW0JtRFGQojsg1GDmcwwQ2OgVdgbIxO+yZBv7lClo5w0WOsk7lZ7TDpG7cm0Z5I1lKajC5pcd9gI+sxtTWVaCt2Kmai3XDTTnsGSgItMO5zXrNqcy9WE3SaCTjEVguebvDjTrHc6zh7Ley6CFQk8gEFb78t47BG06JtopwTQcrGLjTPutpTrkBctZd72oNCmzoXBspbzrFdmiDHVYUgPGTfmic2QQISRF/IZAC8AACAASURBVANq8KTR0Q+jfyutww9MjRATJVQQoEbGmtzLCdzRHXv7IA28fCd0VcCn76DoDaITm8FKLG0PSKSm1kQ8wxFTXRBfiDhOiDP96RwqJ54Ie9mkyZskno/wnJ8Dyeg0GWvGa56YFbqRxjvt614KBvSAShgnzKjhe02N3vN5ZBNkXvwVAGU8JvNDfG6IwEu632EvmliPQIr02zmN3428WdJ17Oks8byOI4/H/wXqYb/3jE6Tp0Xy/dGj5dX+55UJgydGG6NL03iLRqnByk8PdCh6sAINphAWku+0sDsUhHoUy6LifRSjh0Ore4BR2B0KZi3v/OE/+YshhM9wqG96febTZfjs33zpG77eA9Pi69fBaPNQhzrUoT4e9fYf+8/+kZ9HPtbMCrsB/bNHfO7+nF+5LwaZD25dc1Js2LmMt5+dwttTdAur+7GRP5VmQXeQXysmHwSWf/c2P8nvBeD33P8yv3P2Ff7lo1/mByfv8Pm7L/F3L17hncen7N6W6NHwtjwU5hegVzV629KeTtidyOvNIjYkeaA5DbRHmuJCk91EsGEnhpyJLq6dRKSm53tTB7KNIhiFbgVccaUStgXQHEF9bPFZRfnUYnYd5eMtpXgy4jNDN7XszjK2tya4AvJEb49XvCunXGXSYD0+CrSnjumdNbMYj3r5+pqr2QT/FY1pFMsHlpPyPoWN5pLXN7iH71Ntd5SLGd3ZDHNTo1rpvkKZ4auMbpKxu2VZPTA0C3CTfXDM7GLKhlF7TYMKA+1dd7KfupbzpmOKiWlDTDmByYc7XGXAD7R05QL5dYNeNeinl2AMYVLS3hNAZnc7Z3ecsb2T8+Hpgodnt5gcbTmeCvPiVrWhNC1WeV4or5nZmuNsQxk7j0y5PRnGab6h9Yatkyb6fDtnWUsUadNYXGeEnh9lJMEriToEaQ5jU2M3Shz/kdhbby27KusN9GAE1MTGXuWup65r68lzuQ6LyY7SdszymuN8S6Ydm26QiYDIZTqvqWzLPK/JFp7rTNJIXJBmMSWgKEUf9wrCXulaQ9iZvokLPsix9OyCCGYEidcMfgRY9cczGDPiFGpjcVs54E2WUc9a8rxjWgrIZrRnF4G3JhSEJjanBpErdANtnuQxOSIxkO33qOMI0nGkaN/nN6OGeRwRmVYQG0SzE8BDd2LgOchAIhMjNvtdGz/TgxXRJNQrukpADWFWxf0zERiIYIOwFgZ5QTLyTABRzxBRqge+AmrALvaSLkYnwgQ5fhP6mfoQ1x0yRWgU3iVGQJKqpZM4Wo8a/VbD24ltIWCKnPg0HoLSdN6hjN5LEulZE0FYHj3zQgXZ5Gh8hk73QEXP8EnsifgZYW8oAqN7KFUYPqNidGrPmkjnB4ZYVTWAjn0lACFmzQYNPoK4KrIsRD4i328qxVe3cRDagI8AhVJBGEkA1bAfIaa5UBtoFKYdmd4Cvgz47cf6v/dDHepQhzrUoQ51qI83WKHbwK03Wybnlvptie1899UJbx91UGuyK0NxqWiOYPtCfKI+arF5h/Oa5cYy+2LO9GFgXd8G4K988og337jHD5+8zWcmX+UPnfw8P3b0eb58/x4/df8HAPjVF19g+V7J7njO7MOKbN3RzLO+ic6WCt0ILb458XRT0Scn1gEqkHmF6gLtTNF6hWklBUTKky1FO60bg88Vq/umj2Z0GXQVuMqwOdMUNzn5jSdfShNt1i3Fo5riqWZ6VOCtRrkgkhWTZqDlITpbdjTHGes7huUnFjx5RbjGr957xu5oxQfVLWlCbWDzQsn8VZk5OvnCCvPVR3Tnj+H8MfbJMX65xKdG1Bi00eTGUJQFk+95mfosp57HhIdCmi/VhSGar2NPbqB86MEcuxNgwtR+ACs2Hbrz6E1D+OJXsRFIEWE96OlEaO11TXd13Y8b+1Aa8XlVsZhUuBdO2d2tWN3L2N7NOT8RKcwHRx2qlHFzcrJiUdYc5TtOCvEmqUyLVY5tNCCZ2prTbE0Wu7ezYsXWZay7gk2Xcd1UXG3LfZmK1dK0dJqgA50zvWQgDgVJA9hqej382FOB1MjK51QQQGpbyT7s1jnaBvKiZVbV5Mb10a4gzVDdWprOcjrdMMkaZnlNFv0kfFC9f4jVHk2gsF3/Wop3vWkKNnVO2xmR6zQGF5ulfpY/eQsY2dFQyHXUmUdrYYwQFK7ThE6jImtBNQbfaraFpZ1YqklNYV0fg+udpquNNHsxjcS1GtZJJqLQ8ZwpF3vMEVNCBmRiK8TZf4Z/9+cqWm6kxtRnw/uJNaE80ELqgHvJQvytmwGY8CYyOUhNrACVBIlBRYGO3xnBgCul0U7RqGo0+5/YJL3sJDJdRFoSXxuNmxBn5Rk32WlXI+MipP0OCUxQeBV9FoqBaaDTjP9zUqEEEA1ghor+CxKhrIDQqj2wIBg9pC7F/fR2ABV6ZtLo/thL5PCqZ20J0jkAG/1KnwcgxuyTUYUxuyudpzh+VQQWBLxSoxgSRN6UVqeRBJAIPtJqaOh9KIThkgC9eK29yKH25C5B7Ut2Go3eaRnbbWS7pEP0sJt7zOrgn/1bocZeFweWxX49H2kKB7bFoQ51qEP9VquPNVghRpWaYul6PwflDO0sx+zSAz5s73omD1aA+BQkyUMXNG/pO/gv50zOI+thm/OFq1f5lbMH/PRLr/OvvfA5vrd4n99RfZXvfukDAN6+d5uf/e7X+Vufep3Lt+dUjzPMVuQcANWTYdZyd61pjuJDdwQbmkUy2BQgJTWhybQwW2uqJ3qIS/QCzNhNPMYc2rnIK5oj2G002UaTLaX7ypcF+Y3D1D7qwiVG1dvhgVxFNkcwimzZsag92dZyvakAeC875uWzS1559QkAi2LH4xdnnL8aZSKzOUe3SqZfnBKeXhDqGlUUKCedSQgBX9fS6KzX2F/YkFUV84WASqHICWVGMIaQaXxlUc2It61Vz3aW/fUCuvjhgV1vO5RzqE1NcE62NyqVZ6jZFCYVtpLjCtst7loiTNnt4PIS3v+AyWLB5M4t2ntHNCfS3W1vWbrKojxsb5csp4G3jxzERk1lnnLSUO8ytA6cnSw5Kbe8UMn659mO2/mKl8pLNIFn7ZTzyYKLWhg6N3VJ4wxtZ2idwTlNOzfUq6yXWahW7/VSyQByr8FLTXWrehmCymIztZV41W1t+v2EYbZa2BjQNfJV4CaKSSayFwCNwiuFUR6r/Z7EBKA0LTPb0HrDusu5qCds2px1k7Gt5Tx2nSZ4jdIepSDPOzLjWEQWj9Ge1pneO6Pzmotqwm4lsqqwM6hWobaGrtOsnaKrWjIrY20yqWlzOb4kc3JO40z8ersZTDmJjB3dDucwRPlCahyDDgSr9sGKJIdQYfCd0OxJQ7qp3OO9LIYRGBG3nfxpdC1eJ8NFjL9GkokkNZGTAiBSp3hhhv1Oh+IjyBf38yP6749uzHs2gYAXIYAaMy9SGWHFKKV6zwVCIN2SSdKg/IiRoJB4TuQ7J3k4pH0d+3ik6NZ03MGAy0PPBvNZEHaHidtIPiUJxEuHk65Zkno8x/gISbqRzsVHnSgV5AL1+LHqPw9EXxIlRrTOfO020ratj78TuyUaa7ZqD/zaZ6goSPHHYQSajMaiblT/k8bKcNwQSod9ts+gOtShDnWoQx3qUIf6uNXH3rPitR//42QrSeMAcFnMqHewvaOozzzhpOH4ZA3AUSXGkqnhqp3lncenmDcloaG4pH8g3d4OtC/XvHL/GZ8+fZ8/cPxLAPxgsSbD8Cut4v9e/RP8P0/f4Nce3sN+IM3V9F1Ftg5kW6GCtxORcPRpIrkwCVwZcJUsI7N1kcJdK/Ib1Tc3vXmfSw0CdBNFfSsITTzQyyHS58ULg5423lUMWnci5dzJvth19OG48XSVLHD5XYbtaw33HlwyyVruVksq0/IsJmV87s1XKD+wzN8NzD5oya5qulmObuITt1Ho2qG3rYAJj57gl8u966esReW5/J5UYlaX2CeTilDmQqNWilAYXJURMo2LMg/dhSgTCejGYVa1MEhs9JRYlLQzi6s03kAwiuLKUT6SsaDeO8c9fba/T1mOKuU66rNTQp5BCHS3ZnTzjO2ZxUVavLfQHEkyjLeK3Rl0U4+/LRfs5HTFS4trXpxcca+4ptAttc+47gQ4uelKti5j5zIaZ9k5SwiK67pkHRv97abAd0ND9fztqhSD70Wth6bKjhYMDLOzavQbGROo2EQWjqzoKPKOIpPBpFXAB4XziswIYKFV6IGFSdZwWmyoTItWnmU08Nw5S+2iZCgoMuN6wGOWiY/GabaO56Fi68RnRqvA1NZctxXPdjLWPrhasF0XhK1BRQYKucdE1stkukMBbWfw0eNAqdCntrTLHHNt+/tD7iu11+CmHrIHLdIM/IiJoDo1NLoIQNG/b+ReVIG+ERePiXhPx/vN7CKLavcc00HRSz2Sn40KQxOuOtmeyyUBInlYPG+YqYKwLsIIhk6mnUGPjs0G8ffwIxPPNFYU+7P4qaJUQbn4mQRGJJZBMstM4ys1z+l7rYlMgMgiSUDFGPxJXkJyTiNAEQEcbxPTgr7RV13cl/6LLQzHa8LeNe1rDGIkj47+GEfLjCQhe58HVKN6GdMYPAxmWD6Y6CmS5E1pXZ2cw/R939+Oe6ANg1xk7AFCfH00NnyULe0ZwN6qyd6q+PJ/8ccPnhXfovpmeVb8g+rAtvhHr38cFsZHsTq+UXVghxzqUIf6rVb/OJ4VB57ooQ51qEMd6lCHOtShDnWoQx3qUIf6tqqPtQwkr1r0D12zXBWEtRyKWWvsWmZfm7MOs5AUhERJbzpLlbcY7dk0GbemG16/94SvxHXePK4onmnKpzB7CO5xyfnxA/762Qv8wqdeBuD3v/g5/uD8c/xwMeOHiy/zh47+Hv/zne/j//3EJwH4+x/e5+pZRXYpnhnZUkwh00ycb8UkMxiZ4RUPgkG7HWygvhVn0rxCO8iulSyLMC2ydQAVI0MzmT1N2nSXB9qFzOYS4oxs0ruPddgamLeETmOfZczesSzelenn019zrC9zHi9v46eOh/MTjhcbTkoxn6zONuwmOfWdjOWTnGyZ087oj8FVoacrmx0cvXWH+TsbzDJKNbyHTqZQVdsJRSDPCDZ6WswLXGXxmfhttHNDV2hcoejKuP9KpEAS9QqmnspM5IjeHyzRoDTgs4BdZ+TXEm07OV8wffQq+ZM1+npNuFnhrq4IS2FGjJkgpiyxsynl6TGYOJNsDG5eoHcdaCSVZWZZ3xNmxvpByefuHvPF23e4f3LN64sn3M5XzIycgxO7oQ4WFzS1t7TBYPDcdBWXjbAvHq0XbFtL5ww+MgbMSG+vVOh9I9rW4J383S8xYmT4FOnoGab0lYwjAL+11I2hzS3blH4QZ5hdSu5QoPWQwpDlHU/LKZOsZZI1PRNDq8A0k/OoVWCR7ci0I9cdlWmFrRGx0nWX0wXdf9YHzVG2ZW7lPGXacVFNWG4LdpucsJboUhdn9DeAiZKQrjVisqgDJh6DnbV0XhE2g5eAK0NvwKmCeEmMZ/rTzH0ql1wtR2wL/DDhHpQC7fEp6SWM2AeAbyU1xefgGjCZGpgeDIwKn0e2QvJ2SHIS5DW7AeeUsCv0aF/iQsKaGCQiuhvkZT0bwwZo1MBiGrMzumjCaYMkdTyvkIjyEmGZ8LVwtx6tsGfvxO3rEFNXkiQnmgyPCQzp34E9mUu/+U6JREXLmA6ReaF6Xw2GhJTxd934OBI7xjOkz+zJOEYyEfhIX4+QpXUkr4nnthWlGX36ypj6ogNBDclJKiiJ7B15qCinxBvEQO9TksgZKvp4aAiFQ9mAMh4f71EVvUi658yMD3WoQx3qUIc61KE+bvWxloHc+97T8Af/4u/jpiu4asQH4PF6xtWqQimw1hGCYrfL8Imq3Bh02aFNoFtnqMxz62xJYaVzaJzh4mpGOC8on2rKZwHdyMPi5q48XG5f6nj9jQ/5N+//Iv/q9Eu8YGe0wVEHMbhc+o7PNbf428tP8TOPP8nDt8+wl5b8Wj5fXAXaqcLHNA6zk4dtn8n7XQXdZKCWB0MvCwEBIfIb2S+0SF9cORjpeQMhE108RNlJHvYSEXwW8GWAWUc5q/Fe0TyecPQFeWJePOxQLrC5Y+lKcJVidxpobkv3pGctWd5hradpDF1jsXlHVco5OJlsewPEZV3w9MkC+2GOTd4ifpCp2G3A1LKfqflyhRITTi3H203kb1+EIeJVx+PKAvZ0J6aPQeGTUV0dm3MdULnH5E5SA9JD/01O/kxTXCiqJ57qmaM432CuxN8krDbQNpDl8ttaVFEI0DKuEAhtC20HeQanAobsXj5mczdj/UCzvePh/o57pzfcm4qnxZ1yRa47MuXQKmDwTExDGwyrTgCP83rBqi3YOcuuyzDaY5XvwQKtAq031J3FRTNM53Wf1tF5jVEBl+4DryUOsfsIUlWnpDGzoyY1NV61HgwA7SCFwHpUJpGTWUzrMNqTm8EA02rfm5Imr5ibpmTVyDFumkykIlbOg1KBWd4wz3b9MXbexHjYiscXC1yrJYEEpEO1HmUDodHikeAhlLL9/KgWD4vVoOFXpZMEEYBOzApV8hHwkqbRyxVI0aPPyQrG3iFKJBrSPAeRSuhAyEffr16abYkATWaTw/pFViLgkCSDROPE+Fm7A7uK4GQWwYeRT0Hax9TgKk8frQrD/vsxRK0YDCxJcgQBWvqo2V5eMGreUzzs8338CKD5GiAgxffqQPIOSfsglzEuODLUVLtB2qRjkoab+n3piglDzGjaz95Tgz3gQT23vyFJrMbJIUm2oejTafYkIemceETS0Q7fKT1449RgKDuON03nI5mbKvb3PS2WTGmT18XeOgJkAZ1JpLHWIsva1cP4bm4K8PDuH/0TBxnIt6j+/5KBPF8HWch3Vh0kI4c61KE+DvVtGV2qlHoJ+IvAXeTx7M+HEH5SKXUK/E/Aq8DbwL8VQrhUMs33k8DvQyZLfzyE8Ev/oO0cZxvuFddcFzIT/UJ1zfVRxa7LuKorLqJhZKilsTFLQ1gbutKDB3OR83RzjD2WWeBbxyvu3rpmM8u5uVNRPyoonmmydTTOBIpry8Pzl/hTL9/hf3n1B/gD936Zf37yJV7LxDxypuEFW/Njk8/z+ZPP8jde+DS/srzP58/vA7B+c0F2E6NXnRhzijO8rN9uwMfwCp+pPmUgMQq8hXaqKNogjf4O/O65ExPA1rLeYMCb/Qdilyt8rnBFwfZeTnvaoRYty0/GWfbMMn/PMTnv0C5IcsnCsLon53H9kqE+cbjjhsmkxk5lB8rodaBU6E0T50XN/MUn3JyVNNE4rnOaDiiyjrqz1DtpWEOa0E9a/sgI0FlyKmS/0dYBbT1F2fY+JK2TbTSZpd1ZiQdtNZ1TvSkmQCg72hNDvbWsri35dUZxeUR+I2kg5aVDN2JCajdODE87j+6iwaYL6E00FGlaeHpJ2GwJSwE7yusl5dtT5g+O2dzJWd2f8OROxfu3zgDIjndMq4Yyb5nlDUf5lpN8y8JuR+N7S2Vati5j1Rbo6PtgkwGmCnRBx/cCVjm6YGjiOaidxWpP5zW7Qjwx6tb25ygE5LwHRdcaXGNk5rcdnWMvTWbfYI4ZOq0mOCXpH414RuR5R62spHsgE+t1Z9EqsGkztk3Gdpvj4j3ZRzZmXmbNveJJ7qiqeE9ONyyKHcf5lkW2Y5K1bNuMy7Xc25vLCpySOMdkXVDr3iuhyTJmx1tqFXARxKkmTR932bWGrrT4WqM6DY4BUGjjsIsMpeQP01fqq72AjqGT2FlhZox8apIpq49RljaAUcNsefK8iIyD4CJzYTRj75zCKkkMMbUwFPpEk9hP+wjyyUbZB1xyWV4lUCQyMUyKpXXR/0DF2f4umYzus7GU9XtMsP53NPZMyz3vp0Hu5aUINIyv13g5U3bDKkPWgxXeKEltybykvqSPjcfr82aZiiHqlOeOBVCZgEOhR0lHAEqMIEVBSF8/EXzQRoxpfStmrM8DDkrp3mB0Pxp19PfY6NOrka9FBHT06LXEiInHq4xHm9CznPzouINXqEajjhoOJfWteh451KEOdahDHepQ39j6ZspAOuA/DSH8klJqDvyiUup/B34c+D9DCH9aKfUTwE8AfwL4l4A34s+PAH82/v66VTvLu9sTTvMN667oX5+apn94C0GhtScZrSsns5ZBK8LUETaa/NLgb6TxOV9m5Mc1i+mOB7evWM4Lbu5VbJYZ5bmcrvwaqsdQXBW89fAV/tRLL/DXX/s0/9ztNwH4g/Nf4WUrwMX35yXfe+tN/K0v8IsvyD78l0e/ny995QWyC4vPxVhTeTCRrq06yG+kKclW4obvDf2DuCvkp51JQ6M7enACJALVbj1m6zG1k45UCa04NdrjZ/r6rGT5omX5qqU9ljO1eg1caZg80hQ3nvKioXzisCvpjuw2pz61bO8Ybm5l6ImAFInBQqtRW4PuwFUevRiYGCBpDcezLa8fP+WsWOGD4mk96xkFjTPsuozaGTo38KO3TcZuJzOIvpNZV1cbVqvp3sO+LABqZ7Bb1UeAhgy2izjsS48pHPmswVcd9S3NbmvRGzmGbCmmjEGB3VmJCGzoo1OVg3ydElsC5dNj7HWNuRDmRNjs4PEz8vWW7IMJs3em1Gclm9uy/fpkxu4I1lXgfO5h3lJOGs7ma46j3GZiG3TscGyMlkjmliDRornuKI3u0zp80HR2YFYM8gqRZzSjqfXO6/5e2XUZmzaj7QybnXTZSgVcZwgTYayk2E41nmVXQeJXA7jO0OmAU4HEa1cKms4QgPWqxK+tpJwkYCquMygjDZkDbyyrdYx4bQ2baYafKo7zLW8snqAJXMyFTfWmvcu2zmi2GXnVyj5c55gYXaqWlm5mKMqWOo0dr/rkEB0lI20uEamhE7mR84MJ4jgaVHWqb/T7S5GAgahoSkBHYje5Vu8BC2mYjmM55UV6GQYK/LjTrQKNV2RrhW4D2oUe3AMBPl2rhHE1+lgfZxqlFVpDMEoYFpre2FF3US6Vy1eGbpQohvaYGHHs5/tggQwFPUSp6iCjNskhRp9Jywclsb39++P7N53YEbMjqABW2Ab9OrwiuJFzcLoeblhHGJ0MNTqGVGG0j73pZmRUfA3vMDJ/UAGFGsCM0fVDIZHJIZ109pI8+mNMrI9U/TJheM8/97l0zN7gW9gktpPTe+a7ulFfwyL5Dq9v+vPIt0uNo06frwPr4rde/WaMPg8sjEMd6lAfx/qWyUCUUj8F/Jn488+EED5USr0A/K0QwqeUUn8u/v2X4/JfTMt9vXVO3nghfPq//cPMsoYmagOs9mgCtbNs2oxNneO9YreOU5zLDNUoQiHSAec04SInv4yNlYduEuiOO8rTHbNKuo3OaW6WkaXxrKC41OTXYHYi36hPoY4Siexsy2u3n/Hbjj/gR+df5HdXzzjSVb/fv9ps+ctXP8wXbu5xrxJfBBcUy1aoEzdtyVcen1EvC+zTTICMrRLZRCw/9O/SRHdgNwmsEGmF3XmJSWy9SKVbj65jw9t6VN2i6g5CwE9L1q/OuPwuWfHmgSxnNppsrSguoLzwlJcxmjTO4tYLw/ZM0U3BFYMPgG7AbmU/vJH3w4iCrjtojgPdCw3HpytuT9es25xtGxv5NqPrNN5pbObkOgXodplQt0Eo2J00lHYt2nHdMcRJxuayj4uM76WZZ1fItfalJ+QestBHeQIy4x9kRapLP/RyCJlNH3wFsjVky0B1IR1GdV5jn65Qmx1hV4N3qLIkzKTJ9vOS5rigXVh2x5r6WM5TfeLxJzKlX81ryrylyDqyyFTwQe3Hh9qWEFQvu4AB0JhldQ9U5NpJEo7yVKbZW67QHbW3bF3G+XbBsi3691tnKExHFzTOa1o/sC6azuK8ouksTW1jMycgR2+JEMAYj3OaelmgdhL12FP5I5sinVdiUkRPgS8dpuqYTmpOJlteWzzjTrGk1HKOnjRzHu9mvLc85t50SRc0714ds3wqaSLmyuJOO4pZTdda6QW9QkfgzBiPtTLGvBPwxndaxkICUpwa5A9OwApMGJpIr9Bb3ceWqiD37OAzIADEXtypYgRgJGlBBH9icsfz9g/KyVg3u33WBCF+D7jh+9zlimD3l0mNdUoeSQkcEOVWlcQ9++jJkBI4IO5/FgaZhQpxln8EHDQROLB+aMjTmCycgDFG4AMXGU+9J8RY2pFSRUagg7zOIKNwCjrN1yIK6Zx+1GthfzterqsayW1Q7EudUsrJaB+U9SKX6cfsaGNJljJmjiQmSDrWBJCMUePkEzN6TTU6pp2wD8Z6erZTSg4ZR5yataY7a3n33/uJgwzkI+qb8Tzy7SID+c3WAcD4zq4DgHGoQx3qW1HfljKQcSmlXgV+EPh54O7oP/xHCC0T4AHwcPSx9+JrX/fhwDnD+cWCp2bQ8KdGScem0zmNUmDiw6I/bnoZQZZ33JtteFZN2OTS2JSPLPmVIltmNDeWp0cletKR5R2TqQAXOx3YTS3NwlI+1RSXgelDmHwg6/XZjIdHM75070X+5svfzW+/9x7/yunn+NFKDuV78yn/1Z2/z/XZZ/dAjHF99bUVn2vu8X9cfS+PdnO+/Ow2N0+ErWGuDdlKo5zMhCYzvm4yPAhrpyTm0Q6xidK4y3nSrTAxdBsoL1qyZxvmv3ZBthQJxLOmYP3A091p8ZljWxvMtWX6nsxMTx55imvH7P2G8sLgc0Uz1Xta8qCF5aF8gEsoblzfWOjG4wtNfZSxO73Fu7du9Q0S0MdEKg31JLIXOkXWjTT3Shot1YLdSFxr7F/77aeZ4z7qrxO6fjpPPlMEa3CFoZtIlGwffZglBoVo+IMJkLM3C94tVE9/rzuFaRSbpXSg5d2K8qKketqSP92gL5aEpoHHEpeqzz1lUVAuZkxnFd1RQVcZ6mPDbGNKBQAAIABJREFU5k5kmJwU3EwDfhIBlRgzOm6kVObRmRcGUWtQZogWvXW8wnlNZhy5cUyzhllWo2OHepxtyZTjyG7wQeOCeFy0EQ3TymNU4DRb96aYtbc9G2PdFdTectVUPF7P2DYZTRPNbiNzIYEVIShM1RFi05pYNkXZ4pymaawwG1otTWiqVuN8xrLVbLc5tTNcT0tenl4C8F2TR7xYFNwq1tzOV7TBsMh2fCUTuc3T+hS1NjQ67/eH2kT2B7jc4wrdnzOFzPJnedcDLipKSFxnhKikA1r7PUCmKzN8o1F1MmAdGmm7HYwYwwho7IEEpQZWhY6ABQNQgI8eGBPZV7MVYK43CfXC6rC76HfhQu9rkYwbTROXA2jlQMcGncpHDMKDKyOTpIsyByIbowt9Mx+MxJ/ugU7pugW9d5+AyLkE3HCRdREBoDEbIv1OrAI97H+/k62OTDEVfUZGjbqKPyOQI7Fi0m5hQy/REHBJ9QyGHlxKBqIqgFGEOFZVJ99xIYEREdwY2EEjJkUCm9I+QQRh4o4qZL4/LfO8j4wKAtyl9Segw8V/R6BCd3E/opGpt0GArJ3hUF9b36znkUMd6lCHOtShDvWNr286WKGUmgF/FfhjIYQbNeKmhhCCUuNps3+o9f1R4I8CmJNj/GUhVOXxA7MJKCsARghC1U2N02TaEILCe9XPRr94fM3NRDrYR9kJ2ZMMu1IUlwq/znClpc0D9XzgUysbCLcaNpWlXZiY1iFv222guIBsZWjOT/iZ+TF/6+xTPHjxAoA//MrP8UcWD78uUAHwWjbjtWzFv1D9NB+6hl++e583d+J58dNPXudL79wj/zATnbsGsoCLnhaSLBAb7iL0rvcqjJojn5gIivy6ZPphzuKtLeW7VwCc+WOyVc716xndncDs1ob8bsfVHQF1tu+VTB9mzN/vyG86zOOWMjf4PKZ5lJr62NAV0XPDgWl0D5YEo8gvGiZfWoI1dMcT2qOc+ig9YIfeZLOdRTp/Bygx25Rt0AMRsoD8JJkGRDAiNmVpwtI0A2Bjmsi40GKQ2E117zPgqqHZ8bnIcdIMOQx/Kw8oRbCBzgZp9IB2rtje1mzuFJQXGeXFjPy6wV6JxENtdoTtDrXaYFYbzEVG4QOTMmd+ew5Ac5LTzA3NVOMqgzeD7EcOQvbbFXLdi91gvghwflcGRcg9qhCT0bJqmBbCrLgzXVGalqNsRxHjKbYuY9ozLzwzU3Nq1x85TttgaIPhuquYZSecb+Zcb0taZ8hMTHtRAaMDwTruLFa9n0k6hHleS2JPl3GzK9jWOfU269MN0ux52FrareW8tlzPKi4WMhazM8fENNzOV9TeUnvLWbHCnsr2P7suqZ9V0GhULgBDSDPzIMBFp3EmeRREEKMz6Pi9Ya0XAFQPUgYdAQtICSngMoNTwjDpRlKJYA1mExva2MgGHQY2CfRJJD2bZ+ihZZeCAgOh8DircJ1Cd8NYV53C7SJo10W/jOdkBikhSDcCaCgv96KsQKQkaiPb8wa0EiPgtN8qNs8BFdkZA8AY8iCsNRMETNAMppmAajXBenySZyS2wFjCMfpRXsnneymEkvVGZoty0rCPjYP7Q23TiZZjSnsRDCKdSX4Qgf3vEB+ZMRDNUuUi9MkdjVwD4uu96Wzcvg5KPpf5wURzNKbkGBixJNg39xxXUPugx/OyKSMHH5z8H7Bn15HO46H26pv5PPLyg49nuNpvJBt5vg4sjN969RvJSA6si0Md6lDfDvURkQDfuFJKZciDwV8KIfy1+PJ5pFsSfz+Or78PjDmUL8bX9iqE8OdDCJ8JIXzGzGbfvJ0/1KEOdahDHepQvyXqm/08cvvWgclyqEMd6lCHOtQ3ur6ZaSAK+O+AN0MI/83orb8O/LvAn46/f2r0+n+slPofESOr699IHwqRtnwtRn3JBM7lgVAEgom2hEoiO0Mhs6xF2aK1R2vYbXPOr+fcWax4MJP4jfLljvcnx2yvC7JLg6kVZqcwW4XfDrNorgz4mUNVjm7S0d7REKMQVa2xW4Vdio9BcaVQ72RcvnUPgP/6e/5Ffv6NL/NDi3d4vz7hw/qIZVvwykSYF29U5/z+2RcplWaiM161Ez6Z3cBMjBt/dfH3+HNHv4v/Nf8+/CpDbyRWMmnPhZrNEJ1n4wycGmZB+5k9p2jOFLvbhvpoyu1flgWKR0tOtxX5smL1oGD1qiU8WPHSXaHe3xwVXJ4uaI4zJh8apueG/LrFrmRG3mw1kBNODM1C4yqoj8VwM1278sJy3Hn05YrsvWfYmykgMpRgwDUavwVTa4lj1cIaMHo0hRgGKrvL4/vxOqkQJSNjOjYM5qsKVAjR3DRgGmFapJnkxMrQnfiSpJnqFDEbNH1CRPq3z0L/eVdJPGw3UdSnms1Kk60seTRzzVee/KbFrFtJFWla1GoDqzX2SrxMsiJnmmeEqsBXWUya8KjoTRCsxmcGXxi8Vditw5UGn8lJWb1gCQa6ytBNhI2ym1RsCvn8+eIYUziyvKMqGowOWOM4iQafVnsmtuHcLLDakSkff8v9NDENmXKcZSuRk2RbzvMFV3XF85OUuXZ8cvEUqxxbl/NkJ2DjxEpMqQ+a5aTgqq54tpmyjiafbWPF32BrhdJ/Y9nuDO9GH5rOa47LLXfLJe+tj+mC5qTYcBSjTz9x+xlvcYu2seRFh9aetjW4VpoL3xiRFiRDxXifdI3p/Q1asx9XmwweU2qNHvmFJO8Blft+OWcCBItuVM+qCHbECAsxlSOyn5RD2AN+f9Y91EoYP8nUMn2D6wBZHKeV6llIe+M+E/aF7kBbkYrhh/GOFvZSilPW0WOm/16JcoQkjxBGwyCx8I18B3k7zPwrPcg8lAOfa0JIbIO43+PYED+sr5d2jM9Bp3vGhRhuRunMmMERVM+6UCAMiLHBpY/fGSFKvTRDClEnn0/LBB1QYz+VENkUbtjtkZesnHeUMDIiy2/P76K/JsLECVkYDIh6+sdwHD1zRY3eZ8RoUUGUKCOCSrrWIdsfs9/J9a14HjnUoQ51qEMd6lDf+PqmGWwqpf5p4KeBv89A5P2TiE70rwAvA+8gUWEX8WHizwA/hkSF/ZEQwi/8RtsoH7wUXvkP/rg8BMeHdl8EXB4fQHUQ2vTIVE1Pu97XottZcAo7bblzKkDA7WrNtstYtTlPLue0qxw6ha41upanQVMDQeEK8TgI846sarExXjOzDuc123VOuCjIrxXFM9VLINqZojkKdLNAdqMxO1lnknF000D4xIbbJ0t++9lDPjP7Kr938jYv2oFJ8tit+bMX/yR/+8nrvPfsmGaZo1dyElSr0PFhOgzP/n2MKYwebAO4iScUHr02HH1Z3jj5YkP+bIPqPG5esHp5wuUbmt2npAF8MYIWl5uK1fmM4twy/SBQPY0+BFcdygW6yrC5a8WEczbIU3z00picK8pnnuqZLL+9nfXvJxDBW4XLFc1cJCVjnX1qxrylTzfofStSozZiYfevQ39idC3xselz/foTO97J+fKZvJZo8+G5JkIFWSZFzLbz6BtghW6uo0GnGY0ju4H8JpBtAtnSCXhxvUNfS/xpWC4JTUtwDqUUaC2+F6mMkdezDGU0oWlRRYGaCCDiby0IxuArS7vI6SaadqLoikGq0lXgC2inAV94ofPHdBdtJP1AaU+WOYzxZMbho8nm8WTLSbHhtekzKtMy0Q2X3YT3t8e96e2qLQhBUdmWV2fPyJTDB8XD7YnsY1DcKtZUpsXgqX3G02bK5U70PhfbCdsmY7MqCDuD2u0TwsK8Iys7jhcbrm4mhKCYRDNOgE8unnK+m/NsO6G0HYXpqJ1l18n9styWbJYFoTHiPxLHSGp4+7GgR+BC6g5750sG48b0khU5GkT1wjLDrGKzrePYSFKRIGa2emziOupAdUcvV3BFNMAcjVXxkNgfs0n6NW5ikxRMdcnXZvSdEO8du4n+Fk7uvQQEJ7DQ26+9R2AkS0hGovGnv/eCLJPkVETfi30X0pH8I20jAQnJyyLwdZt4udeVJMH44e0wSl0JZvh3iJGyfYqJU9CpvUQW/GgbQQCZwWtkODcgYFAwETBPUpCxoSfsATKAROwqBlAm+WA41X93jH019sZd+nsshUkmobOOd3/8Pz8YbPKteR75uBpsfiPqIA/5zquDRORQhzrUb6a+LQ02Qwg/w9coifv6Zz9i+QD8R7+pbWhJoBiDFf1DaWxUn5+d86s4jRiz7VWjcTvNB7U0Ts1tywvzG+5ObrhVbbjaVTTOsG0y1hsxPayXGWZl0LUiWypca2lnmq6KDd68ZlI0zMqa5aRhdyunOSoon8gTbXEJi3cDygXspiUZRfYxFEqx/ULF5mTK/3b/Ln/j/vfzl1465586ewuAf/3ol/hUlvMTZ5/jR6a/zs/efp3PXb3IW5enAKyuJoTLDLNVmFZM+JST2dK+t4pJAroLdBPD7kzTzQLLV+Lpygpm71vmv77EfnjJ8YeXTB+ecvFYGsj3vv8ORy9f8z23z/Fnj3n2ySkPn5xw9Ug69cmHBZPzQHHtyJce5TXNRvV+Dl0F3TywfCWwvq8ornLyq9DPyqIEqNBdZDYYmbFNQEC6hMDgSRFnqnsNfhoHo/GQ1p0+hwqoUmajJZZ0tP5kUZJ8KeJ6EqtBMfKPCFEbr8DEtBHdiudGN5V98zZADt0sNrBeGhCzU5haY7cau87I1hXVU2GYVI826JsterkmNA2haWWaOE4Fh7aTQ6xrGT/awHaL7iKF5WaJVgoNZIs5ocwJZUEoorfIJKebGFylqeeGZm7wObQzuaHGjWybBxoNGxPE1A+4nh7xzrzjgztH3JmueGP2GIfGasemE+bD1a6i6Qy5dVw3JVoFKttyU8vFbL3m8WbOothxWqw5zrbcK284ygRsyIxj1RRcas82z2htLv4TKep3aWlrw9PaEqKp4M3Osl7L+s+qFXfLJbOsxsYL2QVNFwGXZV7zSM/Z7jK62ooBb6f2UiKEMRDv0z62dNQhpgYxzaQHRXChT67RpUNPW1zIMJsR2NI30RIvG9oENoYYhxrHWmROqU78JnrTy7gqb6OvihnACRXHaz/eo5eNMkAGLo7ZcYUYkawc2Fb8X/pElJgconwkA4zWK3/EcxVUz+4ae8X0hpZdOldyHMP7A7KiULHZH7aDV3v3YQKk+1QMhvd634xADwz1FZv73gxzlOoiAPcostQDhhHDRRglqhuhMGEEXhDHSKvEKHUEcgCDT0ZKlxnvNB/xzzFQ0Z9f+XwyHfaR8dUDJh781O0nlHyH17fieeRQhzrUoQ51qEN94+vj6QiVSgfao5QbOXrY65TMzlk/NAPpwbBNFGGZzVIBmalt5Gn2qV/gA+hFoDQtL87qPiVg5+R0XTcVj27mrC4n6GuLqRXqysB1jP28zliXHl11KCPmnu5WzTaLrIHMShzoTaCbauq5FvlCMujcBcoLx+S8ZfGOYXec8+Gdl/kfbsuszV/77k/zB177PH/i1t/jxyY1P1R8lrcXv8wX7j4A4LPLT/BLT17k6dWMdpWhtiJnsauRCWgEAfJloLz05Nea3Zlmd1uOdfWKpz42tNMjZu9XlF99hv3SQ+4+EuPH6uk9nvzAKV/4bYbvv/sBv+vOV2jPDB9+4giAX7u8w6MPTqi+mlM9CRRXgcWFQ0VApqs021PN9o6inQfqUwGPslVkwLiYYuIDduul8c8VKqg+erSbRtlPGsWxYeQ5ttDerPOYke2G374QcEGMB+Pw6oamVDn2GyXou8FkihhiIzdEzAZMIyam3qZISNkWIEZ8NuAnEs6AB9VqdAvZjRxUebEgW83Jlx679ditQzce3cQI2kZACdU6aFoocvltYxTv5Q2h6/DLFeoiaXDUkKiYWbKiQFnLbDGlO5ngC8PuVMaqy1WMnFUxxhKCHsaRzzTNouDpk9uc3zrm4a1jJkUjzKJ4T908m/ZxpanpCqXbuzdVrQmlo1jUvHByw93Jsjf5LE2LLgKF6ViVOTdFyW6bCzMKYCez6GFrhmSIncZFWdZbl7d48eiaRbbDB8Wmy9HK42N3N81qzmaaTZGxazLq1vbSE78bUZES8Bln9UMaczA0n2MWQ6fjhRX2iJm26HmLU5mAIGOWvoZQOoLRqEzhYyMa2oECpVsBnXQDqtnr7TEKXFo2gXfPORIJOMAeiDDG8HqWUh5/ImiiuoHFEbyADTqBg4qBeTFmOigVwZEBQE7msIOsREXAYfTvEbDhne5lGiDHq5sEnIiRpUL1EcXpc2lflAq9EShj0DoocFGi0qgB4E4bUaNxmYCXJPtJ/4/05pxqMEXttx8ZIkoPcpeegRH6c6QiICZyvWEde5GkXvXAxHAhI2gUX9ed6o+3P/gAevnx/u/9UB+fGpt0HlgW3xn1Gxlz/mbqwNA41KEO9Q+qj/fTjAmYoxalQi/BCAHa2uKjHr13908+Bc4MLvyJ6uuHCEB9bbk0c5rOMitrjoods6ymNF3vKWFnnovZlIdHx7x3dcz6skKtDXYtT9X5lYZg8JnFF4GuDEL3joBKfafjqjDYtaGrAt2Rl+atje71G83kw4zqcaBYeqqLjuoCuneiD8EHJ/z3n/xRHv3OBf/G6S/wRhb4VNbxg7lIan939RafO77HF7YP+PXNbc53c27qkqttySbONrtlhqo15VNN+SSQrwKTR4PefHvfs3uho5sYVi8WLO7c4/jNCTx8BMDi73Zky3s8vTniZ9+Y8u6rJ/zQ2UN+aP4uAD+yeIuv3rvN33nwCd5/75TJr+fM34XyInqHXHZkK02+NOxONe0c7BbKywhWdKFvqFQXyLYes/N0U0MT00HW9wW4cFMv+vhO9Q1EXyoQ8gDRPyD4UZPY6shuCHinUF3A1KofCz6yJlLsqVDn2WtMOjvMvOs4E50aC3ktkHmFN2DaCFpECYYvAq4Q6QVZgDzAtCPoQH0mx7h7IFGYZqfRjUY3WZ9iAhHg8mDagG4EEDG7YUZ++miBrh3ZxQa1a2CzJWy2vZTErUcpH++DKUtsVZHfFpZOmBT43OAmmURXZppg6BvYnpnzyLC5m7M+PeF6GgSIiKyC6okmW0pKjgBQ0E0s7XS4TsVVwBWG+jjnnRcmvH/rmOPFBkCkG1G+cctumGQty6JgE8GQ9aokdJqQPCdig5fq8umcurXcmm3QKrCsc4we4l1L25EZxyKvWeR1LxGpW/kBJFVIBbxXfeTq2JOjbQ1tjGwNnSYECKMYU7UVb5F80tAtAm5nxeOmj/sgxnSGvqENThHyOFZyGZ+6URKD2sWeeiBm7KeJJBBg1HsDeKP2WEhjjUj/NalCZGog4K5PYIX83culDDImRjKRvhH3kcmRDR4vyo7kLKPjHqQsw/YhJpHkCl8kdslwfMqBTwiA2U9V2fOISJtJ7/fbFhBDeWJMaQKiBm+f9Bm1x4CgBx56X4xRmkgYAStEf5Ik+0nnMKSIZi8xsyE8t/7RdcXtX+d+keQbohPiTv+9JwC8wa4PzIpDHepQhzrUoQ718a5vmmfFt6LKTz4IL/6p/xCtA1kPViic0zIzmmZzxzOMnZJGytNHIkqU3xCPGSz40qOqjmLSkmcdVd7y8kJ8Gu4UKwrd4tBctxUfrI94vJpxE40Tw3WOWWl5ME+znIr+odvPO7JZ0+/3UbVjmjV08Um37ixPb6bUlyXZhaW4UBRXoWcdmCbQlYrL71GE19d83/0P+ZGTt/k9sy8A8JKpKZVGK8W581z5nI0vuPElj7pjAB63C3Y+41eu7/OFD+7BVydM3x8aj+3dQDcT/wKsR99YFm9pTt+UKfXinQtU53BnC9YvT7l+zbD6hOPsVQF0PnPnIa9VTwB4Z3vGz5+/wtP3jikfyQbyS6ieerKtgBL1XBMslJexEXTS1DdzRX4TyJeO/LJBdx5XyjpWLxZszzTbO4F2EQGhUZPRz2Jaj44Gq8ErgkuOnCPgIgjlXzUDWJGMDpO2X0W2x5g635X0TY9upRkbS0mkmYnX3cZGMU7W+xg56oqA///Ye7efS9b8vuvznKpqnd5T9+7eu/ee7dlj7ImPSRwlMQEr4oaTQHABSLnjJhdISCAigZC4yR8BEiCRO+AGgSUEUjhGDiYOcaLYTpyxxzOzz927u9/TOlbVc+Di9zxV9bbt2InMMJ2pn7S1u/tda9XpqfXW7/v7HqrsYVBHcBHTyEasy8wJRY7bhRQ1sciaOiNNbwLVqyE+0mRfh8ULYYtU20S1i7htwG17zE6uo/7qhnBzS2pbpqWcSDj0eoVqatJmBUaTnCFVI8apTh6MItaW43sLuo2mPdfS6OavluY6sXjlqW46MQeNibBw+FX2WImJ6vWRWFu6i4rde5bjU8XpsVyceNlTrzpWi5aLcq9ETZuZTvenhtYbYhSQoO8sIQh4AZD2FlwSZoOOhN6g9Ag2GBtYNh1nTcvSdegsyeijGcxYS8Wk0EqADk0a4lmP3rHvKjpvOHWOGBV9ZwfZmdlrwjLiLlrqpqdtLf3RQfv7JAiYNDKFYIz49Ap90rJGJ9P2wjpQiWzyOAICUwZR+fdkx++l8UMYgAzlMwviDSNJ3YFp0yB/ipUAIENpBPwNIpeI1QTMyOyB0nyrxMP7wYz7UfYzOvCLkdlRQBnS6CETH8i0hAEjZqAUatR4iIWloKbbSxPmw+T/xTdj8ity8J9ptXjPJB6wrqaMlqksY3p+opX7HSXXbHjPPyw1M6mHsrcikYFhHZTfYQD6JMf5O//xX5o9K75P9cPsWfGHqZlxMdc/bs3si7nmevvrB9Kz4vtRKSn8yYFK9G1ufEwanfl7/bBxBXnwdBFVSZNczM1ifo/qFOao0SdDPGjaneNkI/dV5P4grITz1ZFHiwMX1ZHH9Y7Hlzv2ZzWvHq0A+GJ3zvVuyelQCTU9T8aL/p1eU1WBzWLQC3Df1Zj8NGp15BvvvCY80tycFtzcrdjdVVSv5el6+aVm8Try+NcSx89X/ObTH+PvPPmI//b9PwXAzz39jD97/h1+fvEdrnTiynYYepw6AAImBBLnesHNo1/h/3n/nP/qw1/gb/72R1Qfi0bBnBTaa/wqEc4SvNNyV1f0a/n5xeUTNt/eYl7ecfbils23Vpze33D7o48B+KvfeIz9kR0/++wLfmrzJf/W13+VV+9v+J2d/PzjuytefHFO/aWjvgYUdBdwfJwZMdmNv71KuL3G7jRnH2ua1x53L4312XcDi9cVh2vL/pnm9EgRlnFw5h+o1K0hDmDUKIHA5HWR/5MGLpHKJDiDGdpL86aimGOmiYFnLGaHEYxRxJy0IO8vIEaeSPtEUgrV5eark8mqOUl6gBgYamKVCAv5kHZlUS5ia4+xEee8eDlO0ieObYXWkRg11gZC0PjMLNqe1aig0CeFOWjs0WBOFW4n3iPLl+dUdx637dD3R9ThRDq14oEBoBXJB9SxBSNMgbSohbIPqN5DGzH7E8uYqDYV7uBG7xGgug/Ur46YV/cUiY6uHMVGQJ06UtdhnMN+VVG/WtM+b9g/kXNweLemu6i4Pl+wvThxdXZgVXWD/8Sj5Z4+GlxGkXZdzbG3nDLz4uAlqSfcV5LKAQ8aVJ8svrf0wdAvNEvX43RAk6itdP5lW1YHumixKqBVYuPkPB2DY+UqMed1FSFq2spyzCkivmvQraY/OOqmx7lAihpfpvetkYSJiYwCNUooVBXRLhCDIjaK6DMroxxP9t+Ra5JlAOX/GbzQmXlk2gkLY4IzFCYGmRUR3gAPyGBFMgKAmT49BCrKy3PCjkpynqeGt8lONzjxAWECjGTQRJhKI/gYnQB7aYLvFLBlkIKlNIIFJjOM0uSFjNvIOyvbmxzjAAq4wj6ZHJyLkBQpJZHoZE+LBzUFOKbSGMjMDvmHZBOxvDa98Z7Je1NOTEnTn1cRVdI+oiJFRZyA8roz9GdvGJLMNddcc80111xzvWWl/+CXzDXXXHPNNddcc80111xzzTXXXHN9/+qtloHUH34tvfcf/XvZ2C5Pq634E4Dodn+XORkQlwFVy+gvFSnI8EOFubOYEwyO9YbstJ+3sQjYpWexbHm62fHh6obH9Y4mjxD7aNiFmutuxYvjhtf7Jbt9Q3/Is+ROo1YebWVKF3stKQa28HlhdXHkanXA6EhKij5qrrfC3Di9XlB/Zdh8D9y+SAygvRDs6fBuonvW87UPXvPTV1/yQX3DxpxY6pZ3rES0PjI7vumOPDbymZ/5Hf/j7pv8p9/68wDsPzmjvtYix2gS/XkkVRF9kDFi/Uqz+TSx/ryj/nILr2/heEJdicykf/+Kux9dcPvjoH58x5//+rf52fVnPDISyRnQfNZd8at3H/KbL59yPFY8vtgN1PzOG9recbU6cOwdx85x/HhD85Vm/bm8ZvNJizn0JKvZv9+we9/QXUC/yUkZeWppWoU5jWkKZQobmpQ19UnMLnWmfU/p2MWB32f9eS+fJSc9075dGibZIhXJ/iddloX4MiHOU/I4fnTMBpbDGiySoSrvYyVrTyQjSWQiJg1TVe0isdfoSqaoCtAmDucxZkPBlBQxyP+JakjNsLcG04E5qiFGtdpFmtc52aYL6C5k6n7+TPdwlKw7jz72oBRxWdFd1jkJITOFDgFzyMwNH0ApkjXC1gBS16GaWlwbY752dUU8F/bH4f0FxytDe6k4PUr0VwF33tIsxHfj6WaHJnHZHAYGxME7XhzEDPb+2LC/b+SYFb87SrIXI0TVBJplR+08zgpzosoyD2fCkGJy9G6QgKwzs6ILhpg0p2AH+UiImj6Pu59/eYk6GpJKmIuOqvKkpOiySWjcuwdrrEg2hrSPZUAvvUTJ6kgMhhTB5O+MlBTBa/Fl8fKm1Ofvtje8F8xe57U5ei7IGxim+sUY802jTkkVUqPcSY3yErKsQw8SEknmKete92LYOkDkRQYy9XkozAqKwzS3AAAgAElEQVSVDW6nMhEH3VmWS5ksIzEpm3wWxgwPYk1VNya6jMeQpSIT79SpxKLsTKrSaJBZoq+rIDG0nYajEenFxPNo9P3In5eZGVPfi2REBpKcSOyIck9OfTeKb8aw7akPhwJlIjqzPACin8jbADqNOev47l/4T2YZyPepZhnIH03NcpG5/qjq95OP/KOYg84SlLnm+qOpH1oZCAn0SRrqQu6PBlKbXeTJ2t6s9R4qKpJXKIs8BIbxAVq5QFxqVNKjWV2UBqIYlsWDJm4t26Ziv214fb7k2dk97y0ECHin2vJudc/Xm9dcL1fcnze8atd8tpNG/rMXl6STIfZOHmQ7hekUkikozcPxzvLp2QKz8NR1z6rpOF9JlON60bJ7VHO9XrH4StO8SjS3keZ7Apasv9S0v+O4efIef/XJU8JafBDQCbeQ16wWHe+f3/Gz55/zz5/9Bn+yNvzF80/hm38NgP96+Wf49HuPab50uJ3CtAa/1IP+vD9L3H+kOL5Ts3i/Yv35huY7r0i3cg7s9S2Pv7hk8+kjbj5d87/85B/nlz/8iJ945wUAP3v2OT+9+Iw/vviE54/P+aK75MrueMduAdjGho/bx0QUTgWcCnz7/Sd8ur/gd56/A8D9P1iy/rRmcR1QEaq7lNMg8nWy0jzYncLtyP4TaTD8840aDAAH4KKOpKJP0GloFFKdUCZlT4Tc5QQ1pk8kxKQzG3WC6NFNK34XJss9pjGHKj1sQGSnQcfRQNMqxJQvxzxGowfwDATUUIpBA59STkjJYIZqAkoltEm42lNXnsoGfG5s+ic5wjRoTlFxv69Qe0P9OsuBjmJ8ato0SGHUG8dgj4n61mO3HaqPmDb/sIAzCcK6IjYO1UviBQp0ji5FKbqLGpXAHDz2eo86nDD7vN7vlywul3QXNcfHhuMjy+mxYb8Rj5hP3nNUlaeLhkoHHjV7LqsjPnejjfVc68hu3xCjGo1Wx1MOXpGOhlOsaa1D64Q2cfDCcUbOY+38YLpZ2cC9yYa1SQ0yLq0SRke0CawrATMOVxW7XUPaOWJniDbIZ+dT0IGYAmephOp0Xkv5FJ0M0STUIqAVY9JFMQ4GtM2Wk1VA6UiqMmhRALe8f2Gt8Qcr352RwZhRh5w0MjWrnDbeJY40L/9iljk06nEEK0qspryuSPEEcCjeE5S1VHAGL+sp5bQalaTz130aPic6hScJPh2R1+YYasjggGaUdiX9QOqChgcA/SQSFBiTXgpAYOS+NwUM1BFjEsYqepMkLSaoQd6ivBoAixJ3SzmWXFMwQ1VRvFXSJPllyJ4t3z8CTgzvUbJ+YzaVVYohIrccoznrmGuuueaaa6655nrb661nVjz7S//+gwfjog+OdSJWEbUUO/XUD/l4Ak6oJEabBawok7M8qaY1Y7oEQFTYQ3mYzA1YnnqnRcBtOtYr8aA4X5x41Oz5YHmLU4Gl6QhJc9vLpPj/fv4j3N6tiPtsf5+146VJMAedteEy9Y+NTNTNSoCG5bJlVXd03nC/XRJe1TQvDYsXsrP1XcKe5OG2W2mikzQKFRliP2Ol6FfQnSfMN3b8q//Ub/AXH/11TD7gv3H6EX7x5Z/gV7/3IXxV47Z5n3IjH11OsnDkqE3N+tPE5nPZx/r5DnWUB+Z4seL0pGH7vuX4RI7x+Mzz7kev+XNPvss3Fi9xKnDjV3xQiadGnwyfdVf8zuEd3qvveK+645v1F5zpE6+jsEH+h9d/il/6+Bv4T1Y0LzVuD+aUaC8yWFHJOTQnabjLtRtKjY1DaCA4CMs0NBjR5ebMRahFI65tHBrdFJSsqxJZWZqmofnSMiXvFaYDu8/Mi9KAesRwr0RKliUcx9eMMYUPCR+xXAczHufwM5t9NICwyNPxfAx24XGVHxrXpupZOEnbOK+OnIKjDZaXOznHbevoW0tqzRAJPDUh1fm4muvE6kVg8cVRfC7U2DyFhaW7sDJVf1OXD3QrRb+Wab3bJZZfeZqXR8xrAa7SdgfGoBYN8XxFf7Xg+E7FKV/n/Qfgl4lw4UHB+urAs7P74Rj7aAhRc+glljQmhfdm/Hln8W0xKlXjeDz7RIAwGFJSaCPXPyaFmTBYAKyNGB2pbKCyHqcjjZX7QavEzWnBq5sNKSmaRTcAIACdt8SosomqwneG2BtUZjKpTpHqBOsebSW9Jk1BBQS0kj8ktIuSdjLZvwK8KAWnYyUGxDAmp3iFvTfCAkrjmhqYFWrC9HBpADqKJ4UKDAabKqrBhPOBJ0X5jMQQTzwYRHoGM9qRYaEwxePFCMAYFvLdqwowZ6fsi5yuoxHWQmbOqSmYUSoJCK378TzGSsABAbllH3ARW4/+D9rEwRumOzpSZ0b2ShiBcUlEmTAkYDRNVQL+qGUYgbNJYsnwniH6NEmyVdn1Xj+Iy33AyjCJ6vJE97rhk3/nP5yZFd+nmpkVf/Q1syzm+kGrmWkx11z/ePXDy6zQibgMlCQHENBCdyNwoWzEmDga6wExN16SDII80JYnZgumDlBFUsxTyQTJa7zJjUN+wE3ZhV7tDL2vudkKd//GbvikuuJ3No/ZNC2PFzvWrmVtpXl/vDxgdKJdj6e/soHWy+cfDjXdbYXZG1RUmL2CvSbus2niwnHc9Dy5uufq6Wt2lxV3zxZcvxIwpHppaF5Z6tuEbRO2jQ/p3rmSloSKw+cb/ruXf4bf/ukn/NvP/i8Afqb+nHfeveej1U/x9z54j9/68gn9iwXmWM5z/owmkK48/Xvw6pnj/qV00csXVzSvI4uXPe6+Y/m9e5ovLDEbR3YXFXdff8ovfvQE3j+yXLbsbpcD88OYiO8N/U2NvehYLFv+pR/5Tf7p9bf5uVriU7/+9H/l588+4q997cf5m5/+CKdPVqw/1lTbIleQaNNooT2Xxr3IQoAhAlR3Oe6zlXjRsSETRkO0ilAbMb6spzmFhSkhDUOq4jCJBfl7AghC0fcLje7G5s4eVZ4OC1gRLQKsKUkgKZsoKQwlnWSSxDuAcyonz+TbYrgHTCfMj6QMsTbEynIqkhfg0ARMHajrntPKsnQ9C9vz7EwYMiFpScYIhpCkSZ9Gep6OFd3O0Z0bQm2xhxq790SnCbWcyO7ccnyk6DdqTGCAQY7TnQmwqDsBBNtLx+KRYflckLXFdxTp5o6426Oub6lfLKi+2LA+F2bF8tWSbqVpL+X+Oz2q+K13V7hzYTVYG1kvWpaux+iIDwY/MSi1NnDSDq8c6WBQSUOAlDSxxNhWcqGUzkaHQfGAgGASvY1onfB1jw8aZ8OQJvLu6p5KB4xKtN5iTcCHETCxmUrjg8bohG80PmqOtZyDcOckAtNrQjGinMbwMsHhNISoiLnRHZrcfP2qymNsINU8aICj1wSvxPjVj/f5VOah8ufLwszrcCLrKNtPCJD5IBo1r99k5bXJ5I8J4/uGONDpLTaRa9hTQnuFqTKLo1eERhI2IIMSSouEpbAq0mTfYLiBVMz/TcAKVWQpRg2gSwpqMEItLIdUPwSHyNtPaDlH6cGhP6y8PyoqUvsGQPbmxZyCENPPiKB6PZqkTr+WjKLbVlTXv0/SzFxzzTXXXHPNNddbUm83s+KjD9J7f/nfBRiZE71E+wEytW6yBGJaeSKlCuCQteKQJ2pVxFQx0+cjKUGMWhz4EeCipIhI2sTDDEChRyviIpLqiF31NIuOy6WM942O1Mazdi1WRyrtpYnJUYw7X/N8t+H6bkU4WWg1+jgmm6iQY1Aft1xc7LlYnHA6cOilWbveL9nfLjCvHdWdwh7HqeYwxezFKd8dEipAe67Yfh2e/gmRafwbX/vb/MLyt3Aq0ifNLx9+jP/5q5/i2y8kzcN/tUC3Ws7vWU+z6ljUHW2fExjuG9SNo/lKs3yRWD33VHc95k7YJ3p3IC0bTs82HJ46+pVi+SoSskQj1NK0VPvI8UoTGsXdNwPNe3v+7AcfA/Bzm084N3vuwoq/vf2Qv/Xl12i/dc7mO+N1CBW0l4ruIuGfdHKdWrmO5qAxB4UrjIfu4XQ3ujFWsFDYY80kWUaNPiY6sxhcjlAFiSDN6yghzWDqdAbHineAGlIQkmFoAMtqKvGSJUZymFKXaWz5exop5+X6ygcwTLuHps+MjU10wiSJVSIuJK7X1p66zp4VKlHZgNERq+MQ69nlqIhj52hPDt8ZzPOai29BcxvxjaJfyT52Z4ruPNFvZBvDfpfbNDOGSgyrPmncVlG/lvdffMez+GyPeXlLvLsXXwutUbWsd7VakpYN/nJJ0kriT983HJ7K+/uzSHjUs7k8YDNIYXSiDPErG2h7y+FU0e5qATELQ6ZciJIKMZ2Qx8mFKqkyCszCY0ykqjxVThN5vDywsP2QJuKj5uV+PeyD0RKH2geNy4wNrRKnDArd3q2Ih5IBWrY5Xn+JOp3szyTt5IEMwitUHdBOGCIqb7dUv61E3pQbeHPU45rzo89FWUclyhMYY0FzFbnHA8ZQn9+nofhdPABDSiNfQDk9YRnFLEcqXjFGYlH9cmQaJZO9XQorSj1kJCWTMsgsxzgcU9mEkXMXqyJBye+zD99f0jhSYeOU74SohkhkNWHNvbluBmChRNJOr1eCaVTpm2DLIDMpQIaaADX5M5UX4O+3/vJ/MDMrvk81Myv+v6+ZaTHXD1rNTIu55vrD1Q8vsyJlkEJPHpiTGuIidQ/q1g5aa5CH2WgQQ71eJudTin0ymmgTsU4kG8GlQS+ss5mdrjwkRci6/6itNAHloTc/5GqvSZkRsasq9msRqK82Jx6tDpxVJ6yKOBWJKGojT+Ur2/Go3vNFc86ur7g7LIS2fZSD0FuLaRXhZc31ybI7azhbnVhVMp19Z7PjfHlke1Wz3zUcD1Yeou2kkwgK5TVmq1m8VFS3ic334Kv0FID/YvfP8r2PHvMvn/9dfq6+5Zvn3+JPL77Df38h8aj/U/2T7D/boDtF3DpOSeFs4PF6L+dkdWB7WXP/eMHpSc3+fUd942heyzlYf7HA3hypXx6oXmvRWb/egs+dS+UgRpKzrFcNyRk2ny04Xm34lWc/A8D/+ewnWD7d885mT2N73j3b8t0PK073mWFyKx4VZfroGon/TGvZRL+2dEeLPxjMSWG34m1hSmpnlwZAqpj9CYAxmXZaaZiShtAqafqdrIuwjIQQ0HVAm0S96EnNeAn6laVvxwZ9MBhMSmIsQZqeSdMp7AsGXwylhU2U1AREyftV3k/phbKcJE2kKOZUmCRK1r4zhKrm0BQQJjdnmsH3RE1YSkWKoF3EnwfuPzKc7g2hgX6dQZ8qkrJcSldB2AlhbKyVEoPAQrmPTaBdabpsGNuvLcunZ6yer1h8eYG+O6B2B1Ir6z18/iUYg100qKahWjQ0X51xfCbMi927hsO+Ztsa1NJjK8+i6Yf+r7KBRdVjTeRgI31viEETsifAgyqdd4k9LpVNEgGCssQsF/FW7tnnwbCsO87rE1oljt5x6u3gJ1GiaH3QdDphdcSaiMvRp4tlxxEGj4TiZVBiM7ULWaYizI/BB6GsJ2R/ldcCvhavCzUaByvF4KGQalmL3iZ0Bvd0L0257hWqz8DZVCoHo88PE0BiOHeTn2dQAfNGk10a9QlYUT5P97KuTcsAHJUI1rKeUwRQ4tM69U4eVIByj6pUWCPqAWNBB3mNbuWmKR4UlM/X8v6UGRcDGPgmaNVrUqGiTJeQkvOcYjnO8XfGFJxQQU2+D94AXArbRI2Xdow4lXJbRX/29g4i5pprrrnmmmuuueBtByuCQu/Nw+meFwpzSTcorILBdNGJxj9UoxFaeeiF8vCnMi1fiyFcnj6HRjrItPSDCz+AqgIJMzw4BisNq+5kG7pV6M4Q8oPprtd0neX+JBTv2kkyQJn6rquWi/rIZXPgvD6ydh3HleOYWQu3yyX9fYXZGsydoT8ueNXUXC/kibpe9DRVz7LqqS8Cp5VcZmcC1pTJciREzam3bC82LD+x1DcCWAAcd2f84vWf5Ne/8Yx/8d2/x7+2+TV+ttIsL/+G7EO/4H87/DHiqxqz16TWcR9Wg3HjZtGyrDvsZWTXeNp3LMe9RWfA5e71gsVXDW6XqO8ibueJjcNsT+N5bT3+aoUKEZVg+fGe5adw+Q9GKcn+3TNePj3n+F5g/eE971xuef51OU/9K0t1L428PShOdzV+6amz1KRqPLEK+KWhb0XGECuNy0aq5sS4rqI0OLofJ6BJMzQsZYocu1FGoltNrDVhYfB1JK0UxgaqSq5TVXnCUtP3Bn9yxC7r6zstzRIMjeEw0S4Nfpk2l+l0nExaJ6EApWFMhmE/0UNfPX6GL3IYhdU8AGSGzyqAnx0bo6QgOfAbkcB0Tz3dIyV+D1nnn4IWA8CUm+3CCHjQRIupYrIJmijGoHk9H1eG0xPL/n3L4qszFq/WNK89rrB0PvmKdDoRjye00tB2mN2B9d05AG57jj1VHI6W9sLQn1n82qILKyYp1k1LYz2rqiNEkb4ce0vnZa15b6TJD5qUxNTR92ZI3iAiIEL2xElR42EANLvWcjw5tpWgVX1v8J0ZgB+tkiS2RDFMVCo9WCvOBFh2HKlIXg8AapFxWCspFbqAHl4Al3J8AFFrWc5TsMlr0pTZkI0lbR2EDeS0gLFAqhSxV+gWtGNIvnlTXlaAsd+LtFcMWjOmkNdjGtZXzjYZG/UJaU13EBoBK8qaH/xfJsdgQr4XJu8dvCqSSKwSPNxG2cGYAcAAiYeAAZPX4DPqUHyPSpU/F3PMAsqVlwxART5Hxag2qgEQGfw+4gjkPFCJmOxDU/wweAjqqJTvyfPRZ2Ouuf5JqL/y4S8Nf55ZFnP9INQ/LFlkZl3MNdcfTb3VYIVKErmYjHqoD548tKmQ5OF2oPZDbMHaMlmU5mvQ0qvxwXdIYch041jLe/zS0DdxeL1QsHkQPZqqSPAqU6r1IA0BUAdD1zd0d5lyXqak+f22DmzWR86admBbLF3HRSMykrPmxKvlinu9Rh0NutWokybdy0GcmopjE9BNwFWeGCTSsFcGnR+maycmi84E3MWJo1+QzGjSuf48Ud85Pn/+Pv/ZB0/4lR/7iL/w9G/yk5X4RfyZzXf53ruP+O3+KamrJBr0peOQp7DHZYOtxczROU/lPGkNtZWT237dcHO7JB0M7tbgdpZQg9sKK0L38p9fgz0Ik2D5MlJtA9WNUB+W37tj8YWle7Tg/sOKm/6c9I17Lt8Vv4XdWc3+pqG6NmIC+dzil4bTRWaoLD1aS1OodCIAba0Jtfzc7cc0hlArdJFrlCltBgBiNTK5CwtDrrOCA8S9ISw0/mgIjYAjIGCJczmdo/ZDQ9y3lnDMDeLOYJIixUmD1vOgeZKUE/HfSLkBnDKJyjpHCzMjmQTu4c8LIFISP3QYP7tEvpYEB2kyC2oi91TXGvwqEi49eumliS5Ncq9QrUGfsplhAV0GgDE3kDoRagi9JtVxkNOYhUcve7orzempY3drqG9q6huRgZxdNrhdj3m9A6VQ+yPpdIKcTFOnBOkCe3Ic3tF0O0u/NuJhAuwWlu7cslq0nC9OrKsWrRIrZ/D5Yh96R4h6AOOMTnTe0GWZhu+NmF4WKUaJjJ1IDLzX9LsKvBY2jE6D10HQaUSQcoRuMFqiZ4GqCqybFq0jfW9RKg3ABojHi9aSBqJVQutECPqBAWisNH1lBtlHjJoQzZiUFBWJiMrxqESNrTx98U+JiuQUsVLCYOsV+jQm16iohhQMierlAaugMAL0RM40rE0YgIKBDJL/G6RWTnw4YsUg3yjGuQOzQo/rNxUmEgz0JO3H7/xyTySdRoDRK2JJ4Cj3+nStMgE5ohL2RP6zXIj8grL/E+BwuLZZUpSCEm8MIMWEUvJiFWQtKA2qn4CC5XvHjudmev6mzIz2Mj5k0s0111xzzTXXXHO9hfXmo9Rcc80111xzzTXXXHPNNddcc8011/+v9VYzKwYjtvhw2h0tpGUiGUWohDY8TN4MmV4rBpMKiMgUC3KMpB6nVIW2rD2kkrBwUMRKZ7O1MeIyFraFjSgrE7QUxMQzTCasqtfoo+iidY9M98zoaB8qy83ecb/0WOdxLtA4z8VCxojn1ZHNZct3gP2uIewc+qjHBIiDIh01yVraJqcIqCQ66DwlPblEmsa3NoH2SqE7ORHNq0RzHWleQ/e549de/Bj/4JtP+de/8WsA/MTiC37+8XfZ9xWfhyu4tehW4W5lVJ+2hlA5+kVEZdmMqzybRlgRT9db+s0WnyRSsvOGp+sdrw4Smdl7Q+cN1kS2h5rQGrZfVbh7w+KV0AJWLxZUdx4VE8tXAf9dy121YfM1mai/c7HjsOi4NRuSstS3Shgg2cjUHw2+iuM5SMAiMDD7Kz0wAXSmvIsR5sjSQY2mfCrI9TSnIuGYJo4o7E4RGkVYZAbM0nFqAvWqw7nAou4wOhGajnYp+3isGvqjEWmIZzDOmxr2qQB04yS7rFkQmnmZEA9MEJQYtDJJUVBK4ijzPVXWUpmAF4PPEkfJ5P4wLbiteLR0yhJWWc5STBo7hWkVdq/ExDSM9yuMFPZkhNlhWojOEKvs/bHSpHWPqz2q8fgLw+6p5bDNiSSPa9yuZvligdtH6usWc71HZU8L9kfqLzXmuMTta9pzjV8ouo18vl9o2seGm03F8bxivWipszFmkWZplWDy58Z61nUapFmdN/RhTF/w3tD3ZvS8yNP51FlUq7PRpCJl7xE0gxdFylKMhCbmz/Q+UZlAtQicnB3ukTChlJlsflpK6zQYdQJoGzAm4kzARy1Mngix0MqyqajOhsQxKDR69EwxOUXGKrCR6DWx0cTjhBFQtt2rITlo/Md8DoqpZvn+LmspqMn3eGYfGMSoEoY1F20mSuhEatUDk87BhLaTz+1XE0YQYE+yn6FSIvGzkNS4XRVBo4iVbK98tw/3UyJ7xIifRTJKWBD5+z2V75LBAPd3y6kGdoXK+5WZRimvrxQBrYd7gvhQSVIMQokiwVFZfaSmqo93WvR19bu3Pddc/4TUVBICsyxkrh+8+r0kIrM0ZK65/tHr7QYrcpUHX5CH2+QSYR3xyAOwmkTxlYc67cXHoGiDBzd5LUkbA/0364anD8Ta56YtP+RGp4g2EfrcqDud3ehLI5LApeGBmZSgE1mBDoqU5MFcp9IgKqJXxJOmNY7WJfZ1YL+Uh8+rtePxYs97my33dcd22XDcV/hTvpy9gA4qIMaNEYhamub2dzcQvkn4s0Cyie4y0461It5Cc1PkF4rd9Tn/zV6MXP/Cz/wtfnrxGbwHv+y+wSevLuluGswum/Hl5jydDHGvCXXCO0mOANiuazZNy9p1PF3ueFTv+WBxQ58lGn0yHEPFPlS8bldsu5rbqwXH1nG9Fa+PuxtLfd3gthJp6HaJ9fcMW84AOL5z5Hx9YnF15KgbVHBUW2maQSJMo1FDOkGsEnEpaTAA0UZir1G9IoZRIhFdoaVL6oBcNGlQdKsHaYU5qoESb9osW2oVaZflEU4kJ925pV0G7EJkM03Vs6zFV0PrRL80BG+IURG9xh/GplhlY1dzUhP9/ihfMR2DPp4EpphsDoE5o36/0PSjzU1Xvq2SS2PSQ3zY/GmvhhQVewAVNPFeD42orOdy7IxRmJNlGKvxtkABbUlhyedpr+gPmnbp0CsBLZqrjnCeJRpXFeloOLxrqa8Ny68Mi1c11bV4Wpi7I+p+j9sesNcLFpuGWBm6c9lwvzEc7iT6tDu3vFovoBZpUGnci6eE0hFjEl3dUTs/HENlxQ/G6IjTkZOXeNciE4lRiQSr8bS1I7Qmm9w+bMQpzbCVE5JKeErQhKSoTBh8NbRKhJgBndIUJ0VIavSpiIpU0AYdB08LpRJaRzGcLWabOZbTVV7eFxUhqsF7QumUgYskfh8mEE0k2ryYimcHEKKsb/Vms17AsH70YijnYFi/EWKRR9jJudEQM0A8nLMJYD18fhJvGblXR+AQwJxSBhElWjU047GXbRMh+gJaK0KVhvWq+/HfE7Lep8kcQ1qIAkEgeIg0mJwgpOII+k3lP/ltsZGfJ6se3M/l54jX7XgvTQBEFESdqF/PxMm5fnjqr3z4SzNgMdcPfE0BjBm4mGuuP1y99WCFsAV4kHkfLeAiugmD8/qIE2RzPK/pTmaMj5w87MZaJs7loVPlKLrygG2Pami6ymRZd2PMHVrAiwJ4hDqRJg+8Mp3MIEfxq5hEOSYtD9oqR7Amk0gnzfGUkwU6S3tmebTc82hx4Kxq2S2rYbJ7aCtOp9wQ9Xqc/KnR20MiOtUA3OhOHrr9Ohv0LRT9maLfaOqbRHWf2HwaSVoSFv760x/lgw+u+ZPLj3n87o7fPHuP37x9yvNbAQpO2xq1NxIPelI5dULDtSy5Q1WzXUlUpms8l5sDn9SXnFfCHrE64qPm9WlFFwzHzuUmvsOc7wDon2m2x4b7+wZ17Vh9pqnuEpvv5FjN7YpXTyuas5bl5ZFDUKjohutkD1AaimSkcfG9wj/KDWojAE5SBkKZ9qrBZyCZBHUWx+duP+okxwkko0EpzInB90F7UKeydmWt9DuDXxj80nFqIqeNx2UTUGsDzgXqyks/khTdwg7miaGXSN24zNG2GUgozAi30/L3DLSU623yz1PZ/ZjXmcm+LoOZZpLGqUyBS/8zMCsU+iCmpHYvpqRuK6d19AtgiH6lTKlLEwcDuCOfx7C/Za2aVqb0sdb4haE9D/izjsVCmBNPHt8Tk+L2fMH2pqG7MJwuHc21fHBz07D8roKX17Dbob/SaKNxK/FHiZsV9fWKw1NHd6bwS4tfWol1LUBK6cddwtvEqW5QTRiMdq0T1oLRkWXdoVUa/g/Q9pam6rlcHjltLIe2Yn+s6I8ZJY0qJ2vke7V4IgyJQ3B/bKiseJyU77GSIjJeGDHOLV4VMY6+FwV08BMww7owmHRGq9Em4pz4p/SK0UC0XLGY6CcAACAASURBVPRCGOgz/UyBKtHQjmz+mRli5X6ZrJXCuCkpTImxR5/6spiY3zoBvZIeWQ9JP2RlDL4N+c/lliz3uu7LC8d1NgDQafz9IeBiInXy/ezJwG1hIOXfNwNjqRu3JfuhSFaNazo9ZBGhBSRJ5XpNTDSTffg6OWZJHtHo4ZeYCgJ0K68esimG+ykRt25INZprrrnmmmuuueZ6W+vtBiuSOMRPae86Jwt0xhI1aBdQJg0TRZKSB3SViGtNt7b0R6FmgzAPpInPD5wmEU0cJ2wINVcFlZv8HJEaefBwaJQAIElBrOS9KfcloUpZ+pHAjA/ew7Q5Tf4Nhhi7lMGI4BWve2lIrhYHFrZn5VqabMZ5CpZ9X7PrKw6tgBh9L9N5XyaABcTotIAnpRmt8+TVRfwF9JeW49bQvBpBC4CPP3nM/778Y/wLj/4eP9l8xjfqF/zU6h0+f3wJwCfHKz7fn/PZy0v66xq7FdDC7vN1CkqYBZUswevlitcW/CYfdC0XVe3l58or4tqj68B6I93+1erA+cUtp7Xj5WbFzmzYfFfjtpn23inaY8XxmaZ+dESvPH5tsJn9MU0VSFEadG2g6ECUCmCiNB/oHBOZxjQFlbKRpPw5RcBCyshXMLnRcWIsOQAFw1pNmJOs4SqDJaEy+JWmX2d5wTIK8FbJeVFathXzPqagh44vlX3RknYjb8j3SV8kFg8bnLLutEfMIXs5xoFplKSZGtbIlMIuHy1NqdYkpQdgJmkIdX5NjlmMTprXaUQljKyPoYHM4N3QgGbzUNMJG8rvLf1es7vMKTePJXq0yH7u1wu6y4rDVs5Rdec4X11x9tsO/fKWtN8TtwfUThajurtnsb/A7c7pLmr8UtNuNH45OY9y2gmVfC+E2hBrK/GbQFtlQNJF9k3zIMlDzmMGj3Rgtegk4adxbBdykjpv8N6IUWeJJ035miCSjMO+5qhqrBNjWGvDg1RMXdgSOqKTIiZFzEacIMAFQMhpJUpH6jpg7cPUCIUwerRJxJjGVA/FCIJ4XSgGA06i8hpJUcmfXUalSiNfvnusNPQ6J+fE/L2qvMiFYuABY2AgHPTjdYhmwAeH+FYQUKcYwKYOVEgjswSGNKik1QCoTUGPsh7lOz0N5spajQu+pJ3In9VD56cooEMyacQwzQhYJKVEJZLUkBYy3TYwMvKGiyHAeWGgFJaFepO1MYDhUF0b/IK55vqhqjktZK63qX6/JJGZcTHXXA/rrQYrVGJIaBi09YVyHzS+dYSFIbokDVWu3kZMFaWhaDzBBXwrpyIcLfbOCFuiz0kjuTkrk2KZPieSzw2uldeWKd50gk3MTZZWQwMYGvEHSDlaT6k3Hpb9ZCqd3eqntHp90qTguK2WpKRYVR3rquW8kiZ+ZTse1QfaaDgFxyk4umCGaSxAH02OZ3T0QXPqHG3rhubH2IC1ETYt/kqzv6ppXzmq29x0feX41eZDfNT8c49+i59sPuNnmk/5U833ALjfNNzGJb9y9aP88lcf8fzLS9IrN0z87QnMMVElcPvcUCjoNtlPYgmhGuUTykNoKqKD/bnEP95frKnOWpq6Rykw75w4Hhekl1nmcYT6BpK2nGyDqgOpScQ2N1wue5XkyX9oMsU8r6XQSYoKGnCSkjAAFEyasqEbyw1ISS+wiWAVsREpicqMB5MbYFsp0g5MlzLwlbAG3FZRZXp6qHN8bvbFEImGTJ5hgm+pscmJExZPrNIIfuXPedCc5Y4q6lHqpHs1RJPqPPEvYEWagGxyL+RtNoleRUItvhxJTwAPl/X9Vs7VkEASxia1XOMiN1FhZFxM00h0D9UWTKfps6ToJm64X3o2q9MQl3tYdnifmUYnS3dZ056fs/5yRXXdYV/vUAe5X9LxSLrfYmPEbJfEZYU7r2ivHH1u+HQo7ANhJ4Va1k/ICUHRlUY4EbNXS9dEVI5vlcZf8WXUwg5SiYXrcaucjuMtXTC0vaWtLH1rB6CiVGwluSNoCzaibRy8dgC0jgPDQ9JCJGa1ACWQcrxpIqk4pAFN44zb3tIHSaUxNghQUVKM8poY7gkPhNHgRwAAacATuYl/sxNXiPcFAiwkG8dFHBWh1XkdlthnIMuBSpSwCtn+oqxHPbyEUNZ4BcZl/xgFsUiKLAMzo9wXwlJ4uJOmTQMYbizEcouXiFDFCA5MLpNOQCgA33DaBzZW8SbRiVHi8vAyj+8p2zJJpIJle0F8ZwZwUk1ej/xOqu4Vp8dzGshcc80111xzzfV211sNVkAxN5w08tnQsLoTqnuojUgySuOTSsMF/SKS1h5TB2yeXAedSHuJAjWZrp+MGifD5PdPou9ilSdpWWddmr5kGHwOVAAzMYErcauDN8akzImhQY1VIlo1NIrygoTqFP2u4jYpDrXj0Dh8npyuXcvGtdQ6sDA9PhpszqJ0GdWJeaN9NOx9xXW7ZNvVbE8y6fXeoJQY9G2WPYu6Z7+sOS4FKKhfGtLnDX/n9CO8+tqaP/34Ed9cPufr7iUAZ/rEj7kbftx9xTcWL/k/1t/k11fPOK6k+/O3GrtT2MwscMf0IM5Qe+hXcty6l0a+vpNzWSb2/dLRnzkOm0RoIpx50kUQ2QbgdgpzlPOpt4YYERp2nob3eTul4Yl1blAyyyalDFRlw1TtRs0/5El1NjBUOhFCZg6USTCgamEnJK+FhdBpfDYx7VuFW2jZz5bciCVMl0aWTm5EVIkMLZ4Sgw8Bk/hdlVkVDJRyv0xjM6XSsPZHsKKwMZI0dkWukj8/6ZRjRTOuUSjrBaSwskZT/i9UibDiwWvQoLrJ/uZmd5gU56ZMGYVK6cH9Ix+Udz8WSUhmo9yUSXNFaBy3l47DuYBXy7rHLrNnxXlie17z+mLD/bWlvnYsXi5ZvJZ7on7dYl/ew6lD3e8xx5a6W6DiEnOWfWiKCaOS8xWdIrg3AEwrLKpoITSG0OghpjYsI8FYutRwXwlTZrlqWWfD2ZTUICFZVD2HytH3dpRwRIVPCLvKKwhGwIvhOgpu0GcWjsrmvsMaYgQbnAuY/GU0jTYtkhUBOgw2gxUpjvfD8Lo6iN+L15PoUwTEKlIWhZj4Tr/fFCO4YRJUcZCNKJVIS/FlUVr2Xx3MYOCZtBqkVOWzyndwKI16nYZrEquJcWY+zGgZjHEHjDGCmrAzQg2mUtkYN2GPipi3GV0BBhkiiwfWkZwcBv+I3wuEyPuiMuBAjk1NRXYIgngUBlPIoE8Vh+sZ8jbEZDOf8wkzL5kk5qlr/3tsfa65fjjqTQNOmNkWc70dNftazDXXw3qrwYqkZBrORDJRAAN7Ao7FEE0ND6bTB10xN9T4dSQs88O7i6Q6Ebw4rheZCYyeFeVhNWUQJBkEtMhghsrU4hSUTLN1mVjnTZcpct6PB0AEYI+y70KlnxhADsyM3HAeNT46QmfoWsehFYH9oupZ1y218axdi1aJlRV9v+dhc6JVZGF6ruoDVsfBsG8bJC0gBIW30rjUTU+4zBr6+wZ7VJjPKz47PeHF7Ya/ffU13l/dAfC0vufn1h/zM/Xn/DOLb/P+uzf80vLH+dbTpwB8fnfO3e0Stg671didfgBWJA39mQAL5iCeF4uvEm4PVZZ5NDeR+FyJv8bScHxX011FwioDMpW8zxwz5bzWJDv5eaOE8VDWTm7GxcsC0lHlabkmLQIhKgGOSkWFdhFlIjYnLYSgh4l4Sgpjg0yyk0Jr+flAyw+G08rhbw32lJkNXgAcc8rMhh5ML+yLMgkemmZkbU8nzGhpyIp8QfVqpKXnZIEHYAWyzVglaQQnppjl80XWIY1fSQUpnVG04vHil8WTYmI6Oh2qp/I5b0zaGdd+ScGQa/JGp6dTllQJu8G0+T4B6mtIVtEfHN3OcH/mMUtP08gNt25anmx2bJqW/XsV292C/W1F9Tp7WrxcsfmsYfFVi9meUKcedeyoXoNpBRkLtZmYNaYsX9DD9020E7Ndw5A24RdycH6lCY0YlYY6ERrLbmU5rOTztU64yuOsSFqcEYmHDyNQoHQi2DSYcz4w6IyZ1dAbYqcZZF3wUKYApFWPcbIuO28HwMIHTYgaoxJeJbROGJNQORnFe0knGda1gWiN+FcAyWdmEZGEzuyI8bs36ZT3aSKdiGrUeWjxaIk2ZqBE4SuDb7JJaWXQJzXcKwXoFYZBAdFGMFrkRmrwZClrTL6Px+0mPaZBJZONNzuF20PdgmkTOgfLRCfXOSxGMDnZN9ZwYcEV9kWafL+PJBd0YUi4OICi5TwonVAmDiazIgfJx5jTUFJC1oCLIuubXOf+9wFK5pprrrnmmmuuud6m0n/wS+aaa6655pprrrnmmmuuueaaa665vn/1VjMrUKMcYzoZK9OoIa6xHd3gh8SOBE6LYV+/MvSbMgFNpCrizwKx0pP4yTExRAWhoafsai+TtXFiPNDXM/PDr4q8QA37oHs1RkDm4xjo7jkGUvcppyKMuniQqZ9fAEkTg8Rq+lYP0aUHW3PnFkMMZmUDtQmDTKSU1ZHaeIkxVIk+miFdwJgohn+dxZ9kaqvy1LCcJ7sHc1A0Lwx+t+Ljlwu+Vz2RY2gCv/ToR/lz736XP7f5bf5Y9YKvX73m8/NzAD5/esXfPzzjO7vHXB+XbE81MSm6To4hJThbndjUHa92K07HCr9ucFuF28r5qG8S9X2k2gWiU9ijYd8b+lWWKNSJsBB5iQ4QO2EZ6JUsghQVsTXogxFvkRz/aU6FT53lFVYRWjX6QpQfawhNIDaKVIO14gNQZBcxaJI3g4dASoqq8rhMwU9Jsbc1na0IRyOT6F7hO4k9hWzImf1TyloRY78Je2EiFSnT/7KPphO9fYoKupGBNC3TyXTZnCZ0+byGo0PYGmViXWj1RXYVgFZhtKzb5PKEPY3TbKHFy/0TYZgQDwayKU/CCytDQwppjL0st1aWYiWdhOKf74tqCzrLiexR0x0cYWHZL4RptK8X3J+fOF8duVod2DQt3aXh7olIku6e1RyfOpbPlyxeNdQ3nvr1CX3osDdCszGVJTkDKaG3WR9mjfwbkJzJzA8DRhGtzvKcLPlZadpzTXSJfi1soHBUhF2WibiEryqOdeBQB4wNOT40m73qiLWRqvLERmXWkyZ2eft5yo7XebIva2mIms2le4XvFH02A+2ceF+A3PNaJ6ps4KmUmHhWmVnRGUvbCiNIXp8wxhNy+o3HokwEB6RA7LNMpVzHHO/L1Iuj18IKoFz/KMeSFNqIPC85WSi+tviTIe4Npp3cD4rBVGK412FguyXNA9bWwHzIjLvCppB9KNK7zLLaK5FlZWaF9mmILhWWkiKU5CcYt1UYc/mwp4a0w9FHYUioWOKry4tGHZnKaSBTllLxKklJtm9sFBVI8coJitSM5sRzzTWX1GzAOdfbVr+XCecsDZnrh63e7qeZJIaF06dxoaUnQlKDX4Se6JwLPTcpaa7cVhIZ3E4eFPu14vQOxFUgnnniQkMUU0udKde6y6kK5XMLoDBohhl0y6lGoh91Ii6Kxj43Emps2KIdD8OvFO5eYXLSiO7Fy8DmHknnyDrdKUIPwRuRpBQsQid6Y+lNxaEajfjSG35rSiMNgY04GzA6jmZ7mQauTSL0GnrzgNWvXBrc5s1B9tfuiiYGknHcfl7ziy/O+fUPn/FvPvtV/pX1t/jI3ch5rl/z84vv8NubJ9yGJYdYE1AcsiFFnwwbc6LWPZ+0j7juVvz65XvcHxq2O3nN/rVj8aWhvtG4Q6LaJ/xrSRQA8EtJYTGnvBa8wkcIdmwGKJKcoLAHNaSDlLWScsqLORWwatTBJysRr6Ex9EuLX/jBFwCQRjIqQokt0AldBxZL6XyckcSItFL0Jg0JLdEr/GoqNyk691EPX9ZKMZ5UXvYvLLKZpSngnMopHOLhUlJrBrDBpyHBwxR5gxmjF8X/IksbFvL3WKVBplESb1RQ6JQg7wdpTDmQpj03eV5JH5eBxLGyyWpKowfANLXEj8eerHjLDD+L4i9gTmKqWiVF3EvaDEjD2t5bnl/UNOuORd2xrjueXYpk6bB23J4vuX3SsLuxNK8rls8dyxc99bV4SqjOo9peTEFv76FtSSGiTAYTrAWlsZUDrcEKsEEosZ4W/3hDf1FzfGRpL8SI1Gcj1VhLSlB0htBEfDWJQwEwibDwNIuOZd0Tqx6tIOTG3+iED5pj6/BepBmx1+DF3BUYwAtz0HDKMhabCHVJrxHpQag0ddOLF6aOLJzP61VedzwYAfoQ+UqJb41RFqpzYZA8paSGmN1SwWvxX/AaejWCUr2CTg+gVnAJtfTYnKpSNT3BRryLxKMRb5miIOlHcE95hbLZu+JN1VG+l4pXjfx8vF9A1rIAYvJdHK3CHjMYkg2dzSmJxNBk0KzIssi/BvIf1IN/ZHzNBGBUHnTSxOGLR5FSJKXi3aEFiBp+z2lGjQ+DrGz4/CgSIX16+O9zzTXXXHPNNddcb1u91WCFClDdZ6AgPw+LS38iuoTSKntWTDTDmYmRDENEnfYj88J0kIymBeKZh+zmH/XoCK86TazEqd4ec4RpmPQWZaqWwQiVJ/pDSkQTSXXWd+fGDp1GrzyT8GdGAJKTNN+mVYPhZ2nMdJdNRE+jj8WwA8U0spiLThrHUkklooHeJE5NwDY9i6zzN1p041pHWiqCQhqJIoZOkKqEV7Idk8/DMC3vxZG+uq34+PoZ/+WxgR+FX1h+G4CNTjwzAVc955DGZTiFRE7JolXkx6rnnJLjJ1ZfcogVdxkl+fR4yW+8eI/Xr5a415bFC+kOCnvG7sAqhT2C8uLZ4LcyeYfRaM+05LhahthNuQ4MPg3CbiiT1VEfbw+KUCkxU1zprGHPjU1SqG70olBAdJb9Sravlh5jI9pEtIsSZxjl81Nd1qv8TBv5eYoKrdPQqsRei8lhBjLspid0ZoybPBppmnpFPGiMy839JD1Hmrc0NHAqStINkAG67Glwgm6j6DeKaCfNXZq8No2fOQyKLQQ7eW0cvTHKWopaYUJmoxhGHwPkGpWJtQA2Ar4Uj5h+A7HNEZNePs+0I0CZOoU9Kvy+ols6TquG7abn4kxYE9YE3jnbcVqeOD2x7LcNh2c1i+cCWgA0twG7C5g+4mJE7Y6w3ZJ62Ug6nlBKkUy+CbWGEEidAFPJe/SLFYvLC6p3Ljg9W9KeGfrlCJLGOpt21ppQF1PJfA50InaaoxcAwNnAummpMktn5WQ7bbC03tJHTdtnptVJGCbdydEfrKzFLns5eDX4bpAjTfuTQelEXffCCCpLUUdWtaSsCOtjBCxAgE8Q3wmtElWORPUTg06TfXG81/jeEvaWNHxnjB4y5pS9IU6aPpuUmpWXxJM64JHvZAEf1GjP0St0JBu6TtbQdI3mvw+A2RtRviMIIV4sqhL/G2DwrnAHASvMqazNCaMpAyLRMtwIaZoGUr6Di7GsVzmNZGSYEAxpiCPOUdoFEImjmTM6yf2f5N9lMWnU0Ux+H8w111xv1u9lwPlmzeyLuX4Q6/eLPP2DamZkzPW21tsNVkSo7mQKWxzgVSjNI8O/h4YHRmVDUkfMD59KTNRAplz1LaioaYMjrMLwUDkwJ6pIMDJxU1Eeak2cTORTptEbeTiubiQ+taRQxCaNk7xCk44MYIauArrpicEQeo1vNarTmL08zLq9ABf2IMaLViv8Uo1xpzCazJXo1cQAYEAGUopJooEYFMFEfKZca5WwWlgZWidCrWlPjpipxarV8iCdzy8Koh/PQVJQbcWscv2JZtc+4j+Pv8Dff/8ZAF9rrvmZ5jOWuiUmjVaRQ6xZaplkx6S5jw2VCjSq55HZ88Rs2ZRuAdAk/u7j9/mN4wf8xt0zfuOzZ4Stw97n5uaoBjmNOYHpI+kO6hy/GhpFqPN5UiOoFTNQEI2Y56k4gjGFtSEvyNP8FuIRzEliRkOTr7P7f9l7k19Psuy+73OHmH7Dm3OqysyuntjsbnY3J4OkZEmGIWhhy14Y8MreeuGVDcLwXyB4Z/8FBmQvuDC8tSBIhmEYJEGZEukWze5mj1XVNeTwXr7hN0fEHbw490b8XqkpywJEd5K/A2Rl1vsNEXHj3vvinPMdcoItRSU5dUVYJmrATMRdzVE3CuqpvaQHgXyXlaMs3EDRacoekxIXHxWds4Qone+zZsP1dsKmkyS7nRTiJNEbXGNwTuGmeqAkFctU4HHJejfNY7sb14PpIsVWxE1NQhhly848dgNKaL8BvIc0GhKnkLvJ4zhKUSHRdbS6P2/hXtKloiSiKuxRVcqECLEj6uge3SWdW7ESwVW3U/iN4SrRpkztmE5aSus5m2w5m2zZnBXcPpqweSbuN+WVpVhaTAvFqqZcBeprh9lKscIsWpT30jLvHcp56B04eT0sloTNhrDdoe8WTJYXVGcz+hOZbLtTSz8VpIWbKHQzIlJkDCSRD13Btte0taPrLWVCPewqS2U8hfE0tmemA7qJTGzHopNr2PQlO2fZdgW7bUlINKghnEIF2Wv6LGpZeDqXxkkHmrKnLntc0PS9Qe0tB5v2CoAQFVoJwi27iOQCWlX0VAXsTGAXkWQbiFYPVqnKG6H1LBUhUcN8p/FlgCLDeSIYKbrmdeODTgWzcX7CuC/JD1OBYX9PV+P7dJfFUsXdhkpQRSDfK8VuNdgq6/7+9+diXSgEpSTXNp5PMFH24uxklRBD2dY5apWKddmBhkQZSa/7VBBVjFXB/YvxUtx285/B+TrEIQ5xiEMc4hCHeIvi7S5WREEZEEFlznFqqIWSIUHvpxGSnSJ6fJBVDkxKpEalf9BdpFwotFP0G0u0gkDID775QTEa8FVKaKMaO7lBHlRj4vEXW0F/5ATP16OC/KBbkVAQAL7XxFmPMhFdeigCMYCbJW741mCXhupa9BtMFwc7VBiTw3zO0ai94sVeAjiKzxPcSGMBSYIVYJUkxwCF9WyNdGn7RfLt8yOCg2Kvm15EQiX2pNUNTD5VrPUp/8vNNwE4Pl3zlYvP8aXpJcfJfuN1d0SfBnnZ1wQUU9PxrL7mc+UVz4o31MqTmtE8sTO+VFzxtycfc30K/+uDX+R763f4k2spiFwtp6yvJvSXhupWYdfiEpMT8YwEcY0Ck+6ZHTv2Ykcp98rXcUiCVLrPGe0iNJ099EWaX24iUPLMnVe9EmePZEuqnKb3CldaTO3QRYLTd/dboiEoQlRYFWkKx1G1Y5LcXbSKuKCpjaMyjj4YWi8JKcCk7nBBU1YOZgzWqyF1u3fbEm08MWiB57cGejU4Zdit8PbjTaRcBapFSJbAqdBVZD0LldbDiFgZefyjHSlpneQxBFnDQ/FCjZ//rC1oroII/UoNHH2fkEShlHmnErpin4YyaM6kf2sHvpcx8rVh0RSo0rOa9MyallnVcvpwS3ch43D1zpTtpkpIFoXaGuyqxG6k2FAsppg2DjQb20ZxkUiIrfKup7haoRZrwmIJr99gVxvMzTR9fkZ/VNLNDe2xppvLNe2PszYQO/CdJZSGXVWwS2OwsKIpY0o/aNVMyh5qBvRFbTZoFdAqsuorNn3JclcNCIyuLfCdJm4N7Ax9lCLXUNDQkX7eUhVu0LHI8xMEYVEYT+/N4HrTd1YQQyl6HagqRWkdpfW4whMzzKcCbQJF4dlMK9yyoFiY0UVpoYlWdD+ilfUYbQQbIa1ZPwtEozHJTSmUIx0KBEFEQGxys2NH+j1B+mcubmmtCDoKqi4VIH0dx2LFbrQcHmiGQRB32kehU9lxLx7mt80uQyPqTe0hPIRapYb6Qz6nQS8mILoUOg66G1kHJi0IeW2P2nKIQxzi/3v8q6Av9uOAxDjEz3P8yxAZB9TFIX6e460uVkQDrt7jH5MeEv2YUAaTRTP3Htz8qPkQtTwUDg+CJRRRoVykvEtwZKOGpAwEEhxsJKSufNDpITZTJAIiNJgpBk4aZRn6bnfqXqc5U0hywSTcabojQ6iCCA7aiCo9JlFSYhFwdSBaSyh16hhHeRBHiiXRKfRABUmJeGAvcZCOXCiiJORlwOzrLQSN1oHOGcqkZ9GUPTYlPouoEt3AoJIFX+50AkQr5+inmlBo6itF80rhFpLcLc8L/vBmyo8vLng0W1Jqx/VuyoubIwD6bQEqoovAo7MFz+a3fHP+CU/LN0wTuuIXy1c8MoELM+XCwBePP+T17Hv889NzAH7YPubby2f88O4BL94c4xYlZqUp75LOgGNAQqj9AtMeQibrP2Q0jI9jYu1bSVh00hbJaJ19yHmo8vgm5MZODZabWczT7TSxUJjCUxQeZ8LA81cqDuKcWkFlHaX22FR0mJoOrSJHdotWke8vH7FsK7qUgBap814VPSfNjtNqw+N6SZUywJWvOEpiKH00LPuau77mzy7FYna7KQlry/bGUl8a6jcRu4sU6zCMUQ5XKaGJzGR+D5SgoAYUU6ba7I+T6SO+GIsdGalhfO40gyoi0UpXOyOEMvRe92PxLypSt517McDw873p1WAPq7wW8VVtaBcFu6bibtZxMt9yMVkD8OzklnAsX1oaz9YVtM6yTkWh5brG7QrRYmg1utWYnR72Ibux1Nc19c05k1ctxdUG1lvUTuayfXWHWVSU04rivKaba4IVIU4AV6c9qFCY9HcoxgJjLnxGA10R2TaBmyrwsnGUSfNhWndY4zmudpTGE2OPbiJtKYt2Yz1mHlhvK7plSUzil2pj0n2Ebafp5n2iiEjxS+e9V0WsCbigUSoSgsZlkc0UAblJsVLps3Gg/CgVaaqOi8mGbma4nk9YVFPUOiGlNjKeNu3bQ8G4CcQqa4MEgo6gRPA0TjyoONirql7W66A3lFALA7IiFxazSKxT6BAJg8ixoCLcVAqRcZ0KFvv2x0b+3/SRmIq5On1ehUgoBNEVeiXFTJUKeux9x/78zeiKOL4howMz4m9f84iE6lD9wezrEIc4xCEOcYhDvN3xVhcr+OYruQAAIABJREFUgoXdubovGOgR9EQqAKggiUos08NsUIJUiIpYKlAau1Fjkm3ANalr3sWBHiI0gfTAWkpnrZ+CnyQIejmiFvYdGkAoKcqPD5PKJV2FmGHE4zHkDQJZ95WRZLqKuIkRSgqgqoCpHeEisJ1Y+jtDuVBjx96PSaHuk6uCSjDsdA4BSQ7FMSNANTpWwH3RNucjnTNUhRtE9mbTHV1l2KpKBOCc4p7Kf1RgPZSezhqitdSXinKRzjFo3KbkzeKU67MpVd3jnIYPpNNcbXPHEF6cV7w8OebDi1OO91AFp+WWp80N32w+4mvlS75UVDyxM55YGYh/u/4+t/PvcPfY8J3uMT/YPeHHmwd871oS8dWuAq8pTaBrLTGIKGFMyZldGEwH2iu8JxWn4tCFleJGRNUiOOlr0X3IcO48J6OWOegGpwF53eykyGFXBmcE/VOWnqrqB+SD1iEVKuJAA9m6gi5Xzkqhw4AUoj66PWGzK/Gp2BGCShoComvwsF7xrL7m1EoSbggYFahVj1YBQ2QdSj4/fQbAdTdl7Uve7KZ89OaE9adTqktNdSuHL9aRYhMxbcBoRbFh0PoYQglaSbsRIr8/31UUGpYgKXJSfr8Qop0iREHvhKT/kot/yinwcVh3yjNo08iBGCkVewcd0B05KQXUVhFXmnhdcDmvuT6W+TiZtEzKnmkpxaHG9pxWmwGxtTsq2LqC3htab2h7S+8Nzsl96nrDZmNRa0v1pmHyqmbyOlBfpWLFYofa9ei7DXXnqN5YQqEJVXILKTW+0fQTTT8RZIsUTdMlmLSveKEehFKncSzoEq1pNwlEE3mV9GmUgqLwVIUUrkrrOWs2nE62fBROca0V0de0r6leYRYW7xWb2qJLcS3J1A9jAloZ0aZIziK9sYOwbxb5DUHTtZaidKO2CuC9prcGFzXn9ZrTasOnZc/NciJjuKhQO6HDCfUiFwA0Lv071h5sJKT9XteiC+Mz1cRr/M4IjS2v0z2dmiHRV3Essjk1zDWf0Dm+El0kX8ucHeZSQgaFjLrygirScQ8V1CdxTiv3T4rJo3BvquekdRRHpkc+xTye7KFDwv11FauAXR6KFYc4xF9k/MuQGAfUxSF+nuPPQ10cEBeH+HmIw9PMIQ5xiEMc4hCHOMQhDnGIQxziEIf4uYq3GlkRi8j2ccBsR7tKcXaQ1zPcXHdK4MdINy53x6OO+ElAdxqdOq5ZGI3ES87uAtpFVKJZSCcu9bl0olIkQc0cg7OEgtDId4x2kXsw+AjapM5dOm/TR8yd8Jl9gnv7RtEnO0vfRPxcoycOc9zhaoOfWcxKrtHulPCm97r8IN+v9njPoRxRFbrwCba9dw3JvtRHhfcarcYLtEZsTpPbJH1n8U4TUgeTZJsYTUTVnv4cdG+pbtJ96hIUf2voNzW7SUksI5O7sdspWgagW4O7q3l1V/Ky9qik7RC9YN9nJ9/i2cktf+vih/xS8xFfK64AeMdWPLU1T4Gvl3f46Q2fnGz49vlDAF66E1a+xqO4cxNetUd8tD7ho5sTADZ6iro2A9UjbEe6gZxA6n6mTm6cO4ICv0uw+VYPCBpBicRBvFPmQUZWKIgW32s2XmErh8liq1oQLyEqNl3BtitwyRISREcEGFwWFldTESjMSKJCoZQgY5ZdxZURKMC1lb/nZsfS1xgClXZcFEtq1fNeLWP4vLqmUI5a91w+mvPPnz7je28ecnM9k2t8U4p17EKjPWLtmRhB+2KuoRTIu+7jwO/P4JCoBcWkk5hn3Cj6GWSTGN/EwTJS9Ghk/Ya8XnaJshNHpLxKCKtxMjMgf7KY6rBeE+0ku/eYVtZPcWfxV4kiMalZZgRSIcKzVdUPjhdFcsDIURWOpuyHbrjVQo9wQbN6VnF907C4LGguRbmxvqpp3njKux6z7dGbTrrxyfpUtR2xsIRZgzup6KcWN9F0eU+o85zaG28DaIUv03gXJu1rllCURAObJrI+Spo0tcMaz6zsOJ5vWdmKrrWQxCX9xqLXBrNNCCRr6IsgmhGIMHBvAkpHrPUYEzB2FHksCk8IShBUaf4aE4a57HrLdqN5FTTtxHLRrLmYrAfNjWXdsd5U9JMCtTYi9pr2CLtN36E0McYBdRGcFu2fjHozHq8i0YrjivJKhEUzKiEJWWoFuIiKSqh6g/X1HtUvoXd8FUfNpETN8LWisCLMK78L0hgl2p8KQJd1N8Cn9QFpXWjwRUJfFHsTm715HcffdXpP4FMp+Y/ZfIYLdYhDHOIQhzjEIQ7xlsVbXaxAR9RZi+sMrk2w+U5EHXWywItK4PZmtw/5FeGzYEWLQB4Ss+iiUEK8AV+OvOasSwCfoXO0CYZex3sCaFnQT0XwWbk9fc4PGZX8LJ9rfvA0rcJuBOaunfxtOnnwBUlE3ErTnRj8WSdaFmXANQkyvjWYtRZ4/VbdEzMcOO42iZCaCNkVICh8hnwbSZCrSi46c6h7L8cIyYawKnqMjrQm4LymT8r9zhjixqI60WNQZaA7CWSxjuyiUazl+m1l6I/ioOURKkkoTZeKFkFhdgbfCLwdxDbUbiCYih8dHfNn7zzm4mLJL569AuCX5x/zt2ff5ZulZHJGaZ7bGU/MUq4l3hIIeCIhRj71ij/aPeOfzL8EwD+tnnNVHBFuLHatMa1CbUfhx6wREApJNnyjUbVDTZOIZWkIGyMaKTlRNuM8icllxm5l3vmdwW01blLQp2JDVztJMIMidlIA0ls96GZ0CSOeHQXKXs7HHflhrsVg8c7QdpbXixlax1HzoupYtyU+KqwOHNUtR9WOR7WM0dS2HNstn68u+Ur1gq88eMHL0xNedFLQ+eH6IT+6veD6bkq/KjELg9nI/crihoMQbRGEbtEp0QLJThc6YteK6laoTHYbQSncJM25udA/hkRRJzpOmgeetG4H/0rG4kaOOK7bXJzYfy2LNXolwoWqV8n5I983JeKOdixm9mWkTdcYqij6MiYOdrO28INeRFP2Qh8pes6aDdv5irsHNXcLucjVbUl1ZSlvLM1VRXPlMG3A3gmlySxWcLdEX91Q1RXltCHWFe5cKgndvMDXSkQdDbhK1ncwahiHUUMkjaUSAc9+IVmymxe83BZUs5Zp04oDjQnMajmH7axgva7xy2KwGcWZYWBDLYKUmIgvDTbppdhc0DEeZSOtslL83JuHAK43eKfZrCraXUF3bJhXLVUS8yymnknV0R8ZFuuablPitgaz0aNosleQ9gvlIQQrxcO0nlQRZD+vvFADgxJtjr0ihPKK2CsMiqAiOipUMiEaNEi2ahBXzuLFkCiHmuT8pEWvoh/nbihIwqtyP7SL0Mt7fPr9It+n0EUqQiXr0/19I5o4roc9PaRxTh8KFYc4xM9T/CyKyIEacoif9zjQQw7x8xBvd7EiiiOAbRwhWykGRVdadCsPsMND4Z74ZXYFCIWij6IsH/a57Ht2jD7x4HWfrSfTg2FMyAUHODmX3PH6rHWi7pNmRLb1S+KbsZBCQSzivjMqzkkSofvRscJ0UZI4gC0US7Brza6tcMceNXGYWg4abMCV0kV1Oy1JBRlpksZJS6EGr1Lym4Y0zYhQBnyvaYGydFjrsSYM4+SDpncGreOgq6CUqPkDWBXpd+IsoXZJVaEK9Ef5oV2ES+02oywAFG6WxyiiGkk47Havu7lVEDKHXpKHahmpbqF/U7M4qvj9mQhs/u7RL/A7j36dr56/5jeO3+fL1Us+Z294ZuVGzXR9bzqdGnhqP+K9UlAFT6sb/tnRc96/Oef21RyzNFIw2hPQ1C3oVmGMdIb9xBCytkgRhEPvFdGr0fEid3FtFAcXN4pPmlYRloZQyTn6pFmgnZLCzU5cTcZOL8O9lfdLkq+TVkI/04MwaPAK16uBAw9wVwVJ7JJI7W0RiVXg+8eSoJalOD88mK753OyaL01e88Au+Y3pjwH49elP+OT0jKt+zoe7Mz5YnvNiOcd7zTwVumZVy2m14bQUZdG7vmbZ1aiU5GoVuVzPuHp9RPGqYPJCinX5JN1EiaBhFaQgkZEQWRC2lOJiRiupKN3xe8UKGAo6KmmhDMWLtCcElcRUi2RVuadyaDpQ3YjKkDFUoyhukd1k4lDQ6OtA18gN2lQepaEoHbOmpSl6TidbTicyJuuzkrtHDct1yfqqoHpTUqyhvpZCwuS8prrcoO/WxNs74mJJdI7iEyl2lEcz4rTBHTeEyuBrQz/T9I0eij5uou7lsMqTrH3lh3arcKuCfmq5OS2xlaOsHE3StJhXLWG25lUzZ7ct8VsDTqMyeiukMXfiLOOtoBpi2nO81xgTcM4Qg8LrSGndYGRRNx1tW6Tis+F22dD2lioVPUrjMSpyPFkzr1rWs5LVrmKzrPCbXEGU+4kH1Wux5gV8EhENpczvWIlgsQJiEQhZzDWt1Wg1KD0I7GYk3aA9FFNRQ0fR4tjTR8nCn6EUVFqw938fRKsGx5CYCtXE5GxFmos2ErvketMkB5GEvMjnsy+oOdqXpkJNiIOF8iEOcYhDHOIQhzjE2xpvd7HCK/yihDKgrGRrxgbCvCdONC4p2etODbB6laD3ugfJqxPkPHf09xAQ8tApHS3tQKfR0i6hFQZkREZs5Nfl5wNdIOeoanzgDUUkaDU+xO5Zl8ZSuqLKJUh6L4JtuVhi2jhYcNqtol1YulONn2ULQI8qA6b0+FITvEbpKDl+pml4ebg1S0Ox0qg+nUO2AKyMCMh5xa7RFLXDqDjA3redoCiCV8SgUDlxzM1vHUXI1Ct0q6HVyc40UThyp7oEu5ZrND10uVNtgTpIUaXSqdOdEop0H9w80h/J5+0GilUckBoSBb6+4I9PL/j9d3+B+eMlX3vwil85+giAr9af8K69ZaIc71jFsW441g1/PZ3je/aP+I3pj/jhxWP+8enX+PDulLvFVJI0QLWG4lYnGpJ04YNVdCdy/H4eoRHBvwENENRAR/AZ0aNG9IsKgrTI4n92JRQh5XLRDcpVFLoFY0c1Fyv6yUixgeRmk+kNQ1FojGCNzLU9R4RoDe5aBnlrYKsiN+Up358+4fdOvsA7Jwu+fvICgC/WlxybNd+afMgvNR9xezThlTumDQXHRhLxM7tirrecmA0nescuGm79hD7xMAyRl+6Ybz95zu+/+AJvJmfMP9CDW0d5m8bzJLn67NFqIJ23jcQoiVrMdKe9XE2sIZN9af4T914LImIawyh6GGdRihZpHFWmb6mMThgLHsMe0CtMmqO+1fg2o6EMEdjZgl1ZoWtPM+mYN+LEUltHc7KAE7g7q1msGjbrgtVCxqi4q2guS+qbY+YfnGCulvDiNf7mRk7g5ga0wZ6doOoarCEcT3Hziu5EstzdqQj2ukYNNr2EcU5kRJjZKfq2pD82+Jnm1sggTKqOJ9MFzVnPqqtYdyW73rJZCbdJa4gRsd6NQK+JPjIAu9o9y4pELYMReWF1gKrHmEDXFgRvaLtInwpvWseE5AqUWlxNauu41oFdKdeYqWgxKIIC3etE48oTQQnlotL4uUbVHm0DOrvFGJk/0UaCAtUrQlDD69olFFwqYsjN3UPbeSX7bEab2IhSaqDfoaE3YAqZcyoVYZUT+h8w2qH2QIjp94XCZRCKUig/Iity8XFfoFlcUg7FikMc4uc5/lWsUQ/oi0P8PMbPQlwc0BaH+DcVb3WxQjmoXtmkvSAPZq4OqMahrBQwgor4ShMyuiBZ15k2P3Ryz/p08LPXUqQQbntE2A85E49o0kNpRlh03KOB5AdGcRpgoIQABCedsphcTMQtgoFbHm0qGiSIsaoS0mIoVqTkfB2pbkWzo1hruhM5ge5YE5qAV0I/0YXHFMKJyar4oTeorREqxZrBCm+EU6eErTWEtaabWNzMMJlJxz1GcZoI20T1ALJ7B4ArgkDijeg5qF6nY6QEswq4IuIbcS4wm6QPMij8J9izjfip0AdCKfcuH8M3gTj1uAug1VSXFruF6lqOUS0i5QtH/EDR/VizPT/l2xen/OHFlwEoH2w4nW+YFD3vza751aMP+c3mx3wzdWGf2xnPredXyx/yhfI1P7x4zJ+snvJiewzA9XbCi8tj+tuS8kbTvFaUizjw5/uVFC7cJEiSbaStn51ppFsqmgK6ZyyK7VOOAqkLK1z2UEA/HREygwNGmmMxdXHLZU588v2MQ+c2ZitEJMFxk+QMkzvAjhEFwj4ax+Cbgp/M5/z4VHQ/ZicbnsyXfPnoks81V7xT3PKV6sWgEQOgCVKciJbSeia657FdDiidQgU+Z2/4RvUxvzz9Kf/49Ov8QfMlmp8Kz6NYQXWjCFbjTvwwr3KBMno9UjmCQvVaKCZ7KIKY1qHulfx4j+Mv6zbeKzwCg60tCI2FqO7b2vpxnIZueypiZDqBHugDGbkl4xitZV0XbCaS6NvK0dQ9s7rluNkxqzrW84L2XMbAe83NokKtDctnM5rLKbOPz6hfrORrb5eEm1ti20HXE9oW9aaksJZylhxNzuaEpqA9r9idGLnve7oeUcl/okloJm3wXnHby3pY1g4fNCf1lpNqy8OJUIVujgS6sekLfNBsumJw/AhRDbfBuz26hdPETrPtNTrdR23i4C5SlG7Yq3JRw6XCQecsVocBcaEAWyRUWdDoIlCUDtcY+qIgrAw6rQO7UZQ7RVQKt1X4xuAno+4GVjQ3lAnEQg0FZp+Qe6FLVsV71tP7RSupGuzh5PapR0AwaV/P9JyEElLJxljOMf3MxwG9p0IczgVA71luRyXFueH3hxY63VBMOcQhDnGIQxziEId4S0PF+PY+0DSPnsUv/Se/TdTgk3BhKKE7DoRJgCJI9zXu8dk7LTDZXh4W7VYezu91pRJFI5rUxc1+9ulB2ySUQ+6ymVYSyv2Imdse9xKZfV5xOs4+IiNrMQgMPek3ZNg7e927lNCWd5KU6l7e083kjd2xdPV9HYmVJMfC1Y5iRQjQa+zCUCxHy9P9CAXD8VWURNZNI+5csmjTeLEh3FjUzqTu4PgwHZMeSMyJZadFlC4/QNs4WCLSJzvCVo3c74RoCc14D5VX6J0aOoq+DsSZp5x2FIVnuykJa4u9kxpcsVBMXklBp1gFtBfbUNfIGGwvNP1MigXdScQ/bvml9z7lP3r0xwD83en7XCRBSoAbv+G7fc0H/QMAXvXH/LO7z/H+4oxXl8cUP62YvFTobkSZuEYsbt00is1tEYlDOz7d1JA0HIrULfVgNnKOmfqhgszxkJPwPdi58OJlTqogyY7dZSTROHe0SwWoz8x3X8kY5Pm3H0ORLSfnaS5k3Y5QQT8P+FPH0dmad4/veDa9oVCBhZNFueorPl0dY3XgolnzoF5xUmyoEpflzK55Wr7hi8UlZ7rjLhT8D9d/nX/4o6/JJb4/pXkp9Jbdg0BoAmbeUyQ9iL6XCwoJws8udfb3k7W05nUrRbNM5ZK5Oo5H1hcQod2Im4z3Mt+7LLqY9WaGsUrIDd2mz5u9gknufH+GLjCOYyBWAdV4qqanLntCVAOSaVa19N7go+LN3ZR+UVFcW6or+cLmKjJ96bCrHrPuMXdr2GzxN7fEPiX1WqGsRZ+eEB6e0p1P8LUITYJ077upojsStJlvRM/HV+OajRNHPeuYNS2PZkse1CvKdB9vugkuaLp0US5oWm/pks5N7w3briAERbsr8a3QxPbvESYOqDDgnkBnCAqtI663BKfQRRA0TFSD1XJo5Vjz8zWV9YQIq01Nt5HJba4LyhtNsUrfWYhVdUjX6Mt0L5q9apbem0e90F50J/tQpvzpTCNRewVZnQsN4+8foX7tzb1cJMxIClLRIlGT7C4jLBiKtMGMaIpQMhTVc8E+Jr0ldOT93/6v/ijG+Osc4t94/Pq36viH/+jZ/9+ncYi/5HFAWhzibY4D+uKvbnzwX/7rP4+81ciK/OC/D29XEUyr8bXGNeL2sd9lVUF4xrGOYCNdLYni0KnOD5Up0dBdQlDosSPutcD9s6ZEVAqjx44sYeSw50Qv7r0+6AxEBki67kdIf2wlCcoFg6iFRz8UNRpBkvha4SaKYiWihKZNsPmFwnQCd/Y1+EYPD9BDJzhrQSAibjGJEuZzk+PKOekO7FpoDv02QcofOqgFPRGrQECj4x402Sn0Lj1Mqyjc7jgWGobHfxuh8ngkWbrXqc73S6XxB6IeUQd2owmtpus06mzL2cmacAy8I6/33vDmeoq9LGheGZrLSH3jqa/lC8qlJliVigqa3WnF9z/6PH/v808A+OOv/Cn/7vH3+Erxmq+WE07NhK+x4bH5EIDbsuRRcceH8wu+e/yEP5485e5oSvVGEqbyVu6L7pQI8tUK18RR9b8S1AkamVs2EktQJuBKWZq+SzQaEt++DINYIMi4KB2JTqg2uhWB2SwMWaykK5udS4LlXof2s4WwIalOkVEYef7m+ZFRGsUKyluNf12ymxX8oDnme9OnYFOBCtBbRbHQRAOvKkHKxMZjkp5DVXc8PFrxjdNP+VtH3+eXq0/5z85/d3Ak+Z+Ofo1L85DqWmE3ShwfplCkzrpOIo19ZwlGDx35e8WKTv25hYPIWByLRua76VSiRKT3qCSVknfMsKcZQEo4VRzGKO45Eg2xVyRSA9Ijfb7XxK0mrA1tWbCrAtiArZJQ6kxRFY5J0TN7cIN+ENl+ruB2K7or15uKq8ua8ragWEJ1d0R1G5h+2mKv13KQ12+gd7hXl+jFkvrVDKwlTuQ7YlPijmp2D0pWTwy9V/TTESUQPKiuoF1ZdmXNzXzCp7Mjnh7fyX0g0gVDqT217XlYLXHR0CfbF60Ca1fRBcOb3ZSbTcNy1eBTATWuLWqnYadFc6cMhEJhEvJC6ShoLqeIO4PvDN6k+T8UYRUqKnbbksnxmmezO9yRYd3LonsxP2I7nRBeG8q7pJmzY9CkkOKM6H2EMhLqIMXqwU0kglb3Cg5oBmHie2gek+bBfs0qKClW7utcqFHnBBKaJwmEukkSiN5zuRrcpBLVKSP58hdGlcSZt3snc4hDHOIvRfx51JFDEeMQb0McBDsP8a8T+v/9LYc4xCEOcYhDHOIQhzjEIQ5xiEMc4hB/cfFWIyuige6IQTATBDZbrMBshZ/ctzp199OHVFJat4mi0HhRf89aCb0eIbceEXZ0SVMiw3sLgfOHIO83BXi/pw+Q3h8TGgC4dw4q7HVcI0lD477Dg91B7FI33KjBaQAEth+qiJsHfKNwMyUik6JnKGKVbe4Qg9+NbeTB4i4hPtxEOvzR/Axkhcp/C7zd7PY6wdbQnypxu6g80Qa8Gy01VbKSFCcSLZ1EHWV8Edh09JHoYxKgTJSVIiM/VEKk3KcpRROJjHScooWwtHS7KXePFbNJyztHCwCeNAvCY8Unm2N+en3K66sJ1StL81qmfXUXEionYteB+dLTXBm270un+R+8/jX+9/e+zG++8yH/wdn/xXN7Q6XAJGTCie74xfIFz4o3fKl+yfPmmj998A4/fC00keXLCfVrI+4dWdS1VQO3PJSCtvBVuvYg44BhQE9Eo/BVGLntRcDWo0qm1hGTHFj63uA6S9dY/CS7iSRXnCzSmekcex1/5eWwyqX7nf7ACDHPArB5XoQsALqReVEuIuVdgrwrQzDiSAAyr+0uiEaHlc61rw0umbG4uuan0zkfnp/z7cdP+a2H7/PvH3+bf28mFfizz6/47/Xf4OMfPMQuRdC0XxXskk5BUSSnGhvAQWycrOe9qRO1ko64jQk9tYe0iGTBBqFvMZ63TtepAoSeYX4O350b6vtuDyoxB+J99EV+cxZD3O97Z5SVaRXsFFFLZ9/VciNuWoOpPEXhmU92vDNb8GiyoDmVuVBpx+vPzfl0dczttuZuU+EWJeVlQ30tmhKTl+cUm8D0/QV6uSWuNsTbN0SfW/YKay1HZ6fYrz9he27YnY9uIr5SaQ7Imo63ltu6ZnEhb6ibDu81k7qlSaiXxvRMEwznpNjwuFqgiawmFbfzCZ/Mj7lrZSK8WUzpNiVxY1Ct6OAEpwhFEk+u9vbqjBRLGhN5oCOG2EO/LVgUNWfNhpNyO1jxntdrXh/N+eTohPXrmvJWY9cMCJpsqWxaJbauM9lrszAwGWlkx3v52cjuHoKUEDHMYR550FENyD2V5smgU4QgqEj0zODFBSTsRkRZFuCEvXW6D5YKspbt5oCsOMQhDnGIQxziEG93vNWaFdXzZ/HJf/1foJzQHgDMRpL2nPj7Sji+g9tHEQeKQyij2Ewq7ukniE6DQnUKsxXhx2hG7nZoIjELSAI4yT70NukMtMndYR8mXI4PuFnHIqY8XiWL1SywlosN+wWNaMdr8KXwk900iVcmPr7Z5gRydDzJmhjZ1lHvJVW+HPna9wo6ZPh/Pl+Bw9uNCHuCFDm6kzjYpiqdqAZJDC86NdgaisUfksGlotAgpqmj3BObihb5HPz+0/eeToNXqESLsBtFsRSthlBAdxpxR57JA4G9f+7shvdm10xNSxssr9s5Hy9PeH0zl2t43chYdYrqBprLQPPGY1eSFeweVqyeGBZfDJx/5Q1fP3/JlyeveacUB4ZnxRvmesdUOfqoWcSKl+6Y72yfAvBHN8/50esLutcTiluN3ah7VIp8X4U3n+ZWAWESiHY/ORLYeC6i6XK8iVqLKGFReFTi77etxSdRxLAs0LvkiNCnoliaX5ALVCk5z4UqNQoKDsULlQp2VVoH2fmmlblRrIXyYjdxWHsZ1h6VGrUycvFDMdh+Rp10WirRWnEPO77xhU/4T5/8AQDvFVd8t32X3/nkN/jRTx5jk1OJO5IT1rOequnxXuO9FivdoPGdjEGMpHWtBk2QgfZFKuI4EYLMBRlJ+NQe7UUNYrg/K/ZpJNlxRH2GKhJ1/ExSqYYENX8WxnUbbEpckb0j09lU45mfbDhudlw0Ir7wdHJLpR1tsLj0Rbddw4vNEXeJKnJ7OyWXdl35AAAgAElEQVRuLJMPLc1VZPLaM/lkg7mS4l68vcPfCqXDPn5EPD2iP5/iZnIj2xPD5oGmPdujjQUGXQ+X3IhioocVk56ychwne9bzZsODSvRKGtNjVGAXCrZevv+6m3K1m/JqOef2zYzsKDIMYhFlrwVUGVAmUFQOawNNKWu2c4btrsDtCtCRyazlZLLl8VSucV60NKbnqp3y0fJE9D9uaoq75Eyzk/lstnnPJ1HpxkKCaBql9Zn39HxfAyinwSliEdIcUsPePvxu2KNWRZV+Lw2aRXGYZ3mc9d5c1J0aC95xnCtjgTFiN7LWv/Pf/vZBs+IvKA6aFYf4eYoDLeQQf1niQBH5yxF/dTUrTITjnhgVfXqI7XuNWZlBmDBrUOwnDflBTztFcFYSlCwit6eZMIjnRTWKnCFJhq8RQbwqoFLyGGxKjgqDzXag0rBFewb0xj3uvI5SNNkTOBTBTDVY2g2uJdkNZCtoCd2LZoWvxNLRZeRFLcUbs1ODk0TM2hlmHI9oRgE45YUrfS8ZU+nhvAiEicLNFcUiPXRv5fuj1fhCoyov3PLs0FAq0SRdFbDTCSmhBqREVAqdHBOUl6Qtxjhqa3i1h6pILUgbUAXDe/pCEY0ZUCX1a0W4tXR3RwB873TCTy9OeXK84Mnkjsf1gnfrW7bnAm348cMLFl2FD5qbuymr1zXTnxac/CSJFr7c0byE6auam8sH/B9Pz/g/H32Od08lofvmySd8a/pT3i1umOsdj82ax2bNc3strzcf8Senz/ijh8/54PqMzaoibiw22VHalcLuoLwT+1UVBIHjGjOIgIpOxaj5EVUk7MabFGzEWYuvHXXTYXXANh2+lmtoS4/rDP26SAUtLRokm5w85XsAII4hOjCgIob5muaPbxSulvk/zOEk6KcSmiFz6vukTRoKERmNZvyufSSCToieYgnlncJfVXz3+j3+u6/OAPgPn/7f/PrkJ/zH7/wR/zO/xo/MI+zrYkgwnVdseyPaBUERE+IibpLFrEswhj0703uIHZtcPlJS6LVoCCgTGYURUxHBqdHtJ4v3yoQeuuHRpGOFyH4tOOa8O4mWRj0WfQZrW6mpjAW+/HKvRIwyKOJWs9jNWVQTXlTiTPP+9JyTyZbG9syKltNyw8N6ycN6SZEGe/2kYuVKfvzeBZc3c64va5qXRzSXUrybvnjM5IMFfPgJ7uUrePkKDVSVCKVO3nlM+bWHLJyln6khkR4KxZ1J+1kqWOqCnYmsJzIRXjSeatpxPN0OjiJz2zK1Ujl73lzz3uQNt/MJ367eZbGr2GwqsUIl7Z9pjsUIWjPYmb47T7oZKrDuKz6+PWa7rljfNuy2JYudXMNJs+PxdMFpueH0fMP2pODFg2NeLWWubTcVu0VJ9coOSDJx5JBrzEK03kgxgiIjO9I9dppIQEU9IDBiEVFBxkQFMBl1luaBirJ2YtZdKsZ9OmtbRBPHgnsS5MxOVsonpEbMKCA1uAsd4hCHOMQhDnGIQ7zN8VYjK+ovvhuf/jf/+f0fqohrLbEzqK0WmzmnRkR4piOkBzrl1GD1BiMSYV/NI5KLG/kHklyHAvzUQ+PRRRgSodAZzK0V6O9eYqb30QIp+Qtl+p4mDN1e5ZMDhEvn6EehS2AQ0hwFNMHVcVC0j1ZQDvnas20fKu5RUUYnk5x8uXoP3pzEGP0kEItET9ARldT2q0sjQqJlpD8OxDqgGzdQErQJWBvYbUv82goaYg++LeMiqAYROxQI/gCrz/dBIbQbHaEMg13lMIy9RrUGu9BU14KyGB7qS+hnkf40oE9bzo7XPJyueFBLN9pHhVGRUjtC1FzuZvzg8gH9jyR5O/sOzD9qMTtHe1axfmTZPlLsHiTBvyc7fuX5R3zz6BOel1f8YvWCWnnMHv9gGUo+6C/4sLvgqp/xqp3zw1uhiVxeH8GnNdOPFcUmDufuK3ERgdTVrcbOPSlBHMRWrdyz0ATMUYcxgaoaaSJWBzpncU7jeovvtCBeUsHDrnUSeJXimN2kBC05mmQkiPZgumznq3BNOn4hFKUMfR8tHBnpA6W4oUQzzi/Vj6gDlShUps3Hj/hSsXknIT6+uOLvfOHP+GvzH/GT9iH/2+uv8JMPHlJcFcOxpOAX2beDtIt0jduM7IhDR3sowJAKQnn992qkiOm9okZGQHVJBDbmdTau2UE0UeWx26OixPH1gYbzWdtQNVIBBuHEFMN37aOy1Fi4iyY551QeW3maScu8bpmXLbNCigHHxQ6tIloFumB50055uZpzu5Ab5d7U1C8MRx8Gzn/vU9yHH8Fnfj/YZ0/xD4/ZPJ2yPTNpfqa5Wu2tWTOiAmIqiUcdcQ34uUfVHls5qrpnWsnGdtZseHdyx4Nyyav2iJuu4Xo3Zd1JcXG9K2l3BWFnBHlVRHTpqZuOZ6e3AExsR2k8n66OebOasLlr7jlp6NJTVo7z+ZqLZs07kzs0kTY5mLTB8GpzxI9fPiBcVdRXGrvhHiImlNAdJfHmKgjiY3gRWV/9SPVAkehwoHd6QL7t73X3ItOE4jiOvon3UDiQ9/c0xp8BopW3ska///cOyIq/qDggKw7xtsUBfXGItz0OqIu3J/7qIityqIhOSYW1kiT7WuMnBt9pSdDy09zeg6vqFHal73PTUyIYyij0kWTBSWBwZTC7mJAL0k10nRbV+AwN1sm2M3VINSM6Ih8jd5PjTuELUEGP0F8TBWpfxUG7QfdjBzNqJciRNgq0P3XK3URed+nBNhrR2Yi5O2f3kAtBEi90smFN1IDYj/B/VUai1kQnn4vl+HlfRmxGVxiN71XiVyd+ee0GigJT8MoO7hByDcnGM7lx4FTqFqaH+kxJAKKHaJQ0VUMWBABVBLFQrT19YUFZisXo4GDXMi7lwtBfTXgzr7k8OqaYSXI0m7RMyp6jasejZsnXjl7w3uwN3zkVN5AfHT1h+6Dm+H1PddNz9GFgcmnYnUlmu3kx4Z8uv8CP3znnK2eXbE4rzuyKcyPFkBO9Za47vlV9wq/WHwOwi4Y/O38EwJ88ec4/Ov4qt+Gc6SeKqo2YXRTdlWTg4FIyGLNuyWcSQHGMUfhW4foKbyP91KJSwtxMO0JQlKXA5WlAqYhPdJ12VwgaIa0R1Wr0VlPe7XHsEy2hWOd5GAdkhui/5Hua55nc06G4B6DUQCPJlr3DXLDgo2ghBDtaA09eyDF23Zx/0P4St19q+Mb8E3717CNaZ/nEXQBQXBuha3V5rUiml8/RrjNkXglyoRiTQYB+osS69sTdpx/BWHioArFCnCucQvV6cAfK6ynbGw/uDG4fViEHzKiLuE+HSocRSo4aEv5MJ5Hv514SqwcqS/qeDoLVRGMJZWRT1KzqyKfluC/ZymOs53S+4bzZcFTs+Mb5Cs7lGDfvNlx9ecanVye0x085fv8R0z/+KXEnCypsd7iPPoaPPmb+6WPqzz+iPa9oj0br08FGs1K4KTImXR4CSdI7pYk7jbOWXles0rbwqjjhR9MLHpysOG82TGzHO9M7SAid265h1VVShLhpwClCb9k6zQf+TOZC4TlqdoSomFQ9/UR0XGLae8KqYLco+OSu4uXkiE+Pj7iYrLlIBcyzcsODcsW83PH+0RnX9THVK4tdJ/TILt2zqPCtwddaNH9yUSsjh/J8yIievLfbKDbbiqE4LVZIe2siJnSTl71PCh7jvAwG2QNjKlykItZwjJjW1GRv/h3iEIc4xCEOcYhDvIXxVhcrYoQY1VCoAPBeo1LxQpcOYxWh0oSUhASXkjIboFb01qA7jc4c/sG6NHPzo4hIKknQAWKhMZuk4bAS5IKvFb5OD8RTL8l9EwW5YVSCEcvnTSeJt9KS/NlExcg0ELHPS4WShGoITuH3tADMVuwwc2fN7omM6lYRqkQPyXaJKhUcBmE4EbMMpULvFMV6LFoAxIGiIcURVExFnDROfuwQ25XCtArXKUIaA98rtqVB24A2EVWKON5gbRoV0Yg2g7JiPRi9QiULQbXTgmZJ9JmogSD2lxmdEb0iVB5lIqry9Ofga4NdpiR1myz/HJQLKNYaf1MSChnoRTPltg58UgV+NO945/yO5/Nrvnr8EoDqa44fnD6kPZ1w8sOK5spRrOQPQHNpKJeW9atz/uDpEYsv1Lw3veZLk1cAPLBLHpgFD82KR7pnri2NKvlKIZacf63+hKflNf+j+U1elw/wn2rKBdgtAw3DdArtZOx9mccuDnoP2d7W7BRma4ga3GwsfK07IyiD2qF0oKocpXXUjUtzVeGDpncGrQNGR5abmu0bgU4op4ausPD5Rcw1a17kNaN8HNFHLt4TkRVOflpTpRp0HfI55uQtlKJ/4CZScCgkf6S6AfXDht/tf4Grz8/46tFLvnr6imWC9i/dEWYt9BYVpFil4h6qQSU9jW2aw0UuFMgbykKhvWI9V6g6oaQQcceswaJMkDxUpwKeImms5PWEFDEyjyPmH45jJNUGxgLIXgEz2ySrlIBmyojaK/hkZM1QaMl/GPcA+X4FSkmxVZvx86n4ejmd8nLuKY5azo42XEykMnZU7Pjq6Uu+eHzFd04e8+HLY2Zf/yLNpRzk6MMW+3t/Suw73IuX2O2W4viI6TTBbJQiFoZQGty8ZPVuST9JxSuk8CbXnPbCqNBhb09wBlTBy3rCi7OO45MND2crHiRdjncnd4RGcVTNeZ8zdtuSsDPEVtNu5By6oFjVE1QZmM53VEnTwhWJMtRa4sagFxbuLFdXFZezY5ojKchczNc8ni54XC85f7TmB3XLh8057pXMtfJuRACZFsJGCboo/W4IxXibxeI0DoXiHKGMoBWEuGdhq+6j+xJDaEDCOQaEnDJJ5DQVQ/Jc2ad9uGakFR7iEIc4xM+Kv//8dw/oikO81fE3f+s7B3TFX4F4q4sV+8lATP/2Tg9Cg0qHsbtPFtuLxF6hVEQVkTgPBKcJfW5L7X1vpizkw6ROatCRaDRonTq6kthn5EOnEBSCDfJ3ocQ5pErFjL3CiElCaHrPoUEcFSIh0U1y8hLTA3GfqAKhTMeOpHOQz9stxC4XLaT7DVIwCVmELTuaNJ5YaFAaux4V57Ub/8j1y2vZySLqhH4IkiTFPh0vdbP9RI4VqoBrkhipjqMmiFNSBKmDJIdFIAYIOamwEd2JMKTqc2EkC3WmRL1XxF6LC4QCVQbCLNIn7RDfJIHUfhQctZsRRSOihoZoDL4q+Pik4acPznj0UPjvnz+65pfe/ZQf1g+4ro6ZfFoweW2obpILxbLn9PueyWXB6lXJdzfP+fGjCz54IF3e59MbnlY3PCrueLe4oVCOx2bFO2lePTINf3f2fSafb/md8jf5wckj7GVJeasGZ5mBWpG7tGl+DmJ6e3B7u0n/DnpAwDgnQoO+NkQbcI2lLTxdchRpyh4fZD2U1nPebCiN50bLZPReE7xG6YjrDX2vUWuLToKBKohgoElCm3YrFJLsspLneRyYKePPcqLtSyXUl6minyr6WcRNAiEVZ4qlolyC+knJ97qn3L1X88XjK54k15fdg4KuqjB3JjmeKAiiwQLQz+UYdqOk0GOlAJSRF9UyYDpFVJbtuwrOWuq6JwRF38kWGbwa9hhlk4tNFj6FVFlQw14xFGs+A8/XLfK+RL0ZNGzNWJzIH8kOPfLhscCj1F5iqsb3ohh0DNjT/x32FR+JOifXGjcpuJpPeHV0CoCd9hzNNzyarfg77/4Ztw8n/OmzJ3x6LRow1x9NeXz+Kxz/k48JdwsR40yCnPdCKaqTE+z6Ge1ZhUvONP1EEywUq6S1U0MwcUyqVV6jmrCsuLsruDuZcHki0IoH0zXn9ZoH9YpwprjZNSw2NbtNSVjJl+hWwVbm/MppikmHtYGilKqPLTxdafGLErPWlLeauNB0N3KfP5pOeHl8xBceXfGF+Ru+dfYJx+WOH0yEurW9rTF3lupKi2tIcg/Jc3UQkt0TpY0GERxG5nwo4yDWrDxovQfzSe8BCOn3wIA2y2uoU4miNArCBrP3FUrm/r9ALznEIQ5xiEMc4hCHeMviIMF1iEMc4hCHOMQhDnGIQxziEIc4xCF+ruLtRlYgiIoYFCF1PaNXqfUIBEPQEa3inuQh0GvpWtmAKQOm8lAlG8TUUXad8P5jUMStHYQ45U0Qq0BvI77Kgml7Noc7QVKEWieLU+n8Z467q9XgTOAak2zm1CCgCanr6kWzIuaObWrOBRvxTSBqPTgvKDdqNWQFe7uD4CHY+5BhSMKIlUDuMdLtcxFMPoetOFXcc23oGOgyoWTk1idKiAZI1qZhJ8KZwRrcVOMnkVDtcfw9mLXGq6S7WQr8XmdNDKuTq4DFJDFQ3at7DgrKKWKnBpi5b1LHMUGuQ5WvNaFEenUPgaI70Ns4jGF4pehe1Fw9kA9ef37KO2d3PD5a8uJLisXxhO1DS3MpXdzJS8Psox3Tn66pLwvspmbzZM53ngok/f1HZzw9ueO42nJc7GiD4VG15Iv1awC+XL3kPbvl35l8wNHzHf9w/g3+9PoJr6+PcFs7zFWSlkd2osidVpA5FA0JyaCk07pvg+llTeggdKTYanpr6U0SLaw9MSiUEvHBXVfggh41YIwjAkZFQqXQKrKdFng30gtCVPSdhq3BbDR2rbFbBnSI7qXzK7ooEdMLBSMjL4ptwBcKv0noB6/oTkdLzGiguBPb3OkHlhftQ+6eNVzMhL5wcbxiWfWsigmxTRo1QJxJN10XgW5rRf8hyFpSraZIdKHJp4ajjxzldwN3G8v6ec3uCdR1T5nESttdKfoBIBSyQR107z7omCxnNdGD0uN6y0KlJol06j7dm7weg3ThsQxipaEA9qD8KusX7HXQBymeEkFraCBRw0jvH85BCTJJJ2chu4byVg/0NV8WLJuG6+MT+q8Ynkzu+LcuPiScy0FePT/ijz//lLv3PsfZ9x3T710SPvyE2O9tXAAx4m9uMH8WmE6nUMlci9ktaVLRPpywPbfsTjVbkXDBTQOhFLSCXSnq3uCXmtsb+fzNfE49b3l8suC02vBk2jMrO+7qmvVE3rNdi+OO3mjUytD3Fa4KA7Wnqjsmk5a+8LSTgl6XyTUpz1VDWDf8oH/E3YOab55/ypfnrzkqxX716nzGy+Wcm9kR9s5SJFrIoC2ShWYTfSfa0Z5WfpD2qjpRDNP+N/zO2o9MF0q0qrye8n4blXxfSBS9fRRONCNt5BCHOMQh/rz4+89/91/42YEacoi3Kf7mb33nz33tQBH5yxFvfbEiOEXAjLagUaHMmBSL3sFnILYBlNPEXgkEPtvPAcqA1hGfbBCJarB8zLDaUIgSPEUgFIGu1uidUBbyMbLtaYhakmcTB1z2frITbaSvNH7v85nXLc4De3zkrMdWgp8IlSNkgbU6Dqr8GZKf9Rp0ohHcQxp3EDcKszH4Jg7QZb+nSRH2RUGjJETDA3E+p88kY9lFQjuFCpGoRA+jnym6EwbHknyeRENIFqy6FP0JAFMEYhHw4tuHbhWmi8Oxh2MGScZVBNUbobdk7ncWIUxaG8FGtEX43uTkWVGsI7qHoo0U60iZ7FlX3ZQP3i2ZnW04nmxp3u1ZnlYsHkkxY/ugINiG+cctxfWWs+95pq8q1i9lWa2eHfODJ1P0tMfYgPeauuk4mnwBgGfzW/7a6Y/5G5Mf8LXyFSfnG74ze5fvXzzmuhPo+6IXj9DOG3auIETFti9wSUuhcwaloO8NbWuxpYOoBleWdlnJeLm9osf/096Zx0qWX/X9c353qVvLW3p909Mz41ns2B4ba2zMYhscY5YAQTEoRnFEYiRIHCWgBCUkwRBFjpClgILZhEBsMRATVodYBERMTIAYsI3t8SyeGWb3TE9P72+r7W4nf/x+91a9nu6exd39+lWfj1R69e69VfU793dv1e93fud8T+GQRnNiGDV6pahLWO+mvupL5js6imtcVBNFPqXKRZUPqw/lQZ0onaTEiVLVjmkZMRpmTEehAgx4zYugyRAFHZFoKsSjoBmxpcG5pl40Nne4Upj6bBrqRCmWgS0h3YClsWM8Xuapo/5iPXxwk+VsSrzfVz7J8wjnlOW+997105xuXHCku8m4SohdxWbe5ezEV8F46tgBIOXQ3UPiYUW6lbI56TJci8lW/Ilyrgbxzpok9eKxdT1LDakqX8IXwndOJd4hOrtdoHRoHBGNQkqTzmkS1CElIp7db211jbBBGhHauRQaZO79Fa+T4ObujTknJ4Syl6EKUDQNDrvga0hD5Y+qE/PY5CiP7j/MLTee4ZYlX4r3lu5Z3nTno3x49fU89oo1Bq88wtLTa6ze4/fLJPdiC5MpOplSnTt34TQRIFtdoXPTESY3LRFNvdH5iqPszzQ7wE+40+Cc082EKkl44kDG6YNDVroTOnHJ/u6I/V3vJdVVYTPvcHazT77Z8aK+paMKF/m4cMSdkm43J0sLhknldSwmszK3birImZRn833kZcSrD5xkreNTjm7K1hktpzw4WOP45jKbJwdEG3Gr4SLNd1FIn2v7t9XlCJ0WHAp1HNI5klnaRlvppelHCaKtoX8inf0uSCi3i+qs1Hbz2jlHs2EYhmEYxl7kijkrRORm4FeBNfww+udV9SdF5H3APwVOhUN/UFX/ILzmvcB349cE/6Wq/tHzfY6GSRg7JgZecUwi9ZPqapZL3qqzB1FLxaGltIPKPHG4uKbOo1YfoH1pyBmOSv8mVQ+vY5FV1Endrhy6ifMaFKV40fZYdopbNiXtwDsw0po6UjSeTe7clDb3vlGKb3PPS2/PrMyhX71rJvqaeAFGL+w5d650bgBbQxyE4uqh+KoTXZ2LvFBKnWlYSLPSO1edoMmTR/1qrZT4KBBm7XVls5IuqHMUK/4NGhX7ZCjo2FGNHVUnnkVfpDUuqZC0ou7jdTVGbucAXHeel0aMc361W8KKdRNtUIfz0+x3uRcZbZw7zQOgdxykTBkOI/K1mP3LQw4sD2HZr+ifXe5zJu2Tr2QsPxHTPT6k+8yQzml/HWTnemyfislXYqrMd3WeZZzo+tKozy7t49iNK5xcW+YrB49wS3yOL+s+xh3pSYYhLGS96pG1yeoQUTOsO0zUL7lvVxmFRhQasVF2OZJuUKkjCR1/79ZR8jpmM8/YzjtsjDPG45SyCKvcYy/ASRUiN3JftUPHQTskFVziJ9ZJUlGrtJVGwinEibLSmXAgG9KNCs7lXTbyLueC6OFoklLXQl1FTHMHhUNyX3UE/Op+su2dFum2km3UxFPBhQiJfJ9QZkrZ9xVosjNKNIXR1L//SYH+YMJyNmXQyal6/p5d7nhnReoqbumf5Q2DJ/25kZJ6LgPu8RsO8aHsy0hGPVYen7Dy2JR4nLK5nTK6xR/nBgUSeUdFP8vJYu+gKerGaRRTB93EqvaipQpt1RXx80mm/YRiFFOOIqKJa52BrpxFK80Ecc9zVsT+u6FxVuxYN290DlCI/HebK3dO/L1gr39eBYdRE4UFIfKoUOKRsvS4ozzZ4alzN/Dkiq+60lsZ8y2338+33/hpnj64n3tedZTHz+7n9F2+nEgSKsh01pXsnLL6mVPo08epRyPOp1rfwOUFvTPLdB8NNW6TmPzwgNFayvCIo+iDpswiDGohGgIaM8yXGC5ldLoFvWxKN/FG3DjY4JbBObaWOzx67iDnNvpUo7lKRNsxxSiiLCK6/Sndbo7rTZkWQZukFvJJApsJbjvmbLnKZ6Yptx88A8BatsVaZ5M37v8Cm8sZDy2t8fT6CuNtf79q7suqSh4c3VWIcmjLTofzPAUQJIG2nHYTMRXRas4gvt+Cz9Z/RuSFd6Wc9W3zf/Ma1JcLNjxXazxiGIvAhaItwCIujL3HpaIuwCIv9gpXMrKiBP6Nqn5GRJaAT4vIR8O+H1fV/zJ/sIjcCbwLeA1wI/DHIvK3VPXi60OiPkpBBdVmtD5LeWjQyrUDXpfUaKdCNfKrYCF6ol2pbAQbG++FU7RTUyaKm4SJS+5jdyX3yv+a1P64RoBT8CKezcp+ESbyc2VB/bKYH4g2VQU0hCpr5Cf2GgXhzWYyMVdetYnCaJuZC80SeVO2tAn5bss2ytzqW6i04cPBdVbNoymJGaqRVFEQAax3OjNEd55mKWmrh0BIRalmwpZSKcm2tJUYqq5Sp7590dhXfqhjoQoipFWmVL0IzWokrdFYvejmyM0i8CvvzGnKzbarmLrz79zl4qMsXBPV4iu+1J2QHlIK8dg7cMCX6Oys+9DwSdXjZBGRdXNW+z4kfG1li3MvL1lfHjBdTVgdLNE9VZCcDSv6Tw1JNzrkqzFFV6hS8RUvukFwcJByfHSI/zXpcOKGZd688gg3J2dYdSNWnW/EajQkk4K+FOyPCnoiRAiJzCbbhfrrpkA54LpE4qjCtmPLn6NS2NKYU1Wfp4oDPDk9yGbpIzaOT1boRgUbRcaZSZ/NSQdVYZL7Wa2IkqUFS52c5c6EWComVcK08hdKUUVUKiRRxc29cxxMttgoe2yWGQ+Jj+8/2b5fhXT8faI9qJd8h437MeVmRDEQyg2hs16TjJTeyVBycyLkK0LR91UOqg4kI+j6oitUnYytfTF6WOh3ctKoIomqNjVsUsWczfs8OT1IHa7YlWjMjck5AL5h6V6Ovv4cP558LeNPLbH6SEX3TElUREi4IcY3OKqlksLVrYBrFpft9R9H/sJXFSIHVa1Uc5EXIkrsanqdgrLvyMuIfJpQzKX7SCmtM9CnV83uJwn5AjvKwfLcax5oJ79tsFmY5NbxTJARvIOzcarCbJXe5ZAMlWRTcIWjfjYN5znld6d38TV3PMyXLj3Ba294mslawmdufhkATwwPkLqSZ4fLnN3uceY1h+kdX2PlSe9s65yZ4rZz3MY29foG9dbWcxwZ0edh39EbyV59hMmBhMk+YXIo9ONBH23lHc4KlTAdpkw2Om3J2ZMrA1526Ei/zo0AABvRSURBVBxr3S1ese8UZ3tDzoz6bI28M2G63UFGEXI2ZTSMkW5FnBVt2lMnLel2CrbijGo7wY0iJif6PDD298Pjvf0cGIx4xcop1jqbvHLlBCudMRur3nE2rWKmVUReRqgKeenTCadN9MgwxW3GPvVvMquqo27Wt3US+irx6XlNBaT5csVVpq1Aq1QQVTIrIRz6vVgyZ8UcV348YhiGYRjGZeeKOStU9ThwPDzfEpEHgKOXeMk7gN9Q1SnwuIg8Anw58JcXe4EIxJ1yR+nSsox8VMQ8dTg4EKU1FSHiogrOijbnOPzfvGfsHSJxWlJXYbV5EkHu/LGlIE2qR9w4Gyo09e8tRVhhmwvdlgofri3BiRHCt1uCg6TqQFXPQojbvOp8TiMjDqvbxczGZhWu6tXU5c7Pdmm7BOsjL4pQ4nOircMAoKxpS/Jpk1PdOFeAupkrBweAP3+zfmja11TiiCb+bzKchSpXXaXMIM29HkGbWgJUmVB2I8qBo1wpIfERKFoK2vSVC2kN4f2aiJUmJaixu3EOST2LoGnOExGUXcUlgCp1Km2J0HTLO7HiEWQnHXmeMe6mjJf9RL+3PGG1Nya9seJst8/pXkb3ZIfBMX9bdU8XROOSLK9IejFVx1HH4jVLgGIoSBWxVa3w55OEEzcs8cqlE9zePUUWwnhGdYeOK+i7KUfjcyy5Casu52BIZRlIh4HzzolozoHRcEs8mPuvosiOc7r3GE2sxrB2rLqa9drxTLXEU8UBzpYDnpj41fJpHdNxJYfTLQ4nm9QqTDThXOnTVLbLDscmq6SuZC3ZZC3ZoOdyMtfnmXQFgHNxN0RXuFA6GCTSWYnQQUnhQjWQJUfRd2RnlCSkiXTWlWgCbp9Q9mCyX0g3FVf5/dlpX7VjWweMlwo6WU4nKYka553C2XGPJzb3U9aOqnZ0k4IbBz5F4av3PczX9x/khrvW+cl9X8eTh29k9cGE7pma3onmpnVMpwnlNGK9kyKxb38TZSJB60ZDREWTCtKUPkX88WmnJEsLep2CTlIySf0kuKocZR5RTyOvUwJQS4jiYqYLE1JqUO94aLu8qTwSPIjzZSzbFXnXfJeoPz5UWW3LhuC3SemjwZrUrtj75ki2gAd7/PHwTh659SCvXj3By7IzfMXSYwC8tn+MZTfGhVCnT99xG49uH+SBk95pNTrZJ3t2wOCpfQyO3UDvoZOUT3zhOddseewZOusbZFmH+pYjnHm9r0aSr4IcyomT2XxxOk6I1hPSc6HSUprwyHrG8cPLvPzAaY72NjiYDdle8s6KZ/rLnN3oU5/uEG9F6NBRduI22qroF/QGU5b6EyZJxcR1kGEMZ0Lq13rKU0mf0wf73LxvnX3ZiFhq1no+TSSRGjfnOSpDR4wr38+nxgOOry8zOdGHc6ECUznr36YP6sg7NvPlmcNC536TFK9zIrX/jWlLVDeXQ6KtZotxdcYjhrHoXCzi4nJjERzG1cIiL/YGV0WzQkRuBV4PfAJ4C/C9IvJu4K/xqx3n8AOHv5p72dNcejABQBQpaciZByiTiqkk1LXzOeHzaQHgQ/+jGumEnPPS+WOCQ0AdPrVDfEJ5q2UhXpwN8GX/ioiqcGjh2pW+KERWSBD01Ob9K5k5RkIbWsHMMGnYsTQaeZEJadJGwq4m6qAuHG7sS5EqzHQjGoHP3Ast1on4EGL1+hGw08nQTGKqTEi2fSpE04xo4h0gVSWtNkVTphL86l7rHGjeay69pYq9OGidNn8JZUT9/mRbQq62F1J0uRDlsygSXwLTh6pLGVN3vKhou/Lc9FX4zCZKpgmh9/t9NIm6tjv9eaobp09Ib4m19bNoPNPVKKuZIGc89qvOdRxRn/UnbrySMD6YkvVyOt2C6c2wuZow3e/3d09kdM/UxOMwKU+888O1QqzQOadI7ciHfe7f6PDo/oMcWt6ml/hrrQqTnUhqenFOFpWspmOWYh+9sT8ekkhFoRE9l3O27DOtY0a1Xw3/kt7TZK5gNRqy5CakQM6gdYYcCIqpfVdzI1sccCOGacIrs2cAfMpJnZBKRSIVkdQ46iB+6tv3ZOcgozplLdkInzNmyY1xK/48Hs62eaK3n41pxsaoS36eM9GlNZUodRFRdiuqbkwxEDrrTZqId1x0zvm+m+6HSSytGKwrIR4KejqiHDlGvYRRUvuoK5ilR1X+fiSUhHym550pT27uY/umjK/oPcp33fxx/rD3JfzFodvJHs5Ig+SCyyHdEHQYUcdRENOcXVPzpSNFva9Tmqim5nqNoEhh2q3RTo0kM+2aKKmJkgpx4WuiEjStqcMN68KNpbWGa91HLjURZU07NMJHVsw5LOYdF35SHFJEmjSupn34e1ojP0mGkF7QpL9NIRmCeyzhC1tHeGL1ENnSlNtCikQvzoldzQ3ZJkc753h19xle3X2GN+/zDrNjt67y0NYaj5/Zz+kzPdITR+k/c5TsnLchHtdkp6YkXzhN+fQxGA6R7SEH3MvD5y+xeVuPyeEKHVTEWelT9lIlZEX5e+pEzHiyxOfziAMrQwZpTj/2F8vh/jZZXHIqGTDZ7MDUO50bfZW6TtguIjqDKXFc01meMnWgkyZFzxFtC5O8z8Mjf+93OznLmb+PunHBIJmSRQWJ1HSjgliq2f2ajjiQDfm8u4Fh2qPaiEi2ndcOmSsb7dNF/HdY1fXfw3XapPmFurQxqKhPMwzf9d4IkH45c3oZO7iS4xHDMAzDMC4vV9xZISID4HeB71PVTRH5WeCH8WPjHwZ+DPiuF/F+7wHeAxAfXPETZWhXOLOQu1wUEWVTsSDSdsLfhGXHSeUHgAJ1JW1kg8Y+8qJ1dNSC5o6CuP2MJKlIk5Kq9lVFRLxDwLWODW3F9VSFsnR+VTlM0lRDDnuTsgKzVVFoozokqplfLK+j2WC1iiJ05IIWhBfmi5o0kRKcl4r3A9k5DYf2eYhE0ESpMl9RI96eTQCjCchU/fsH4U1RqMNn+BDlINjnQnh544CBNtqiWRFs0lIa8cp4BCpCvqJUHSiWoJ7MqpFI6dMwoqm3q8yEYkm8k2ReXT9IlvhV52aSNoveUCeIzmaRUs+iaKKKILY6O75N3SFMQBvBw5o2+qMh3nZMJxmjfQmuX5B0SjStyPuNYGDK9KSjs+GIxtquWjfOChVfsUVqJZoI8Sgh34p4ailD5iqnqDKL/hFftaO5jqO4RlyN1o4orphud6AQpPQXzh8cuJMkqeh1Cg71t9uJ1CBM3m7r+lTtnsvJpOBAvO3/Oq/LseQmnCqXWa96bFQ9nNQcirdYjfz+Q9GIJTdmve6x7Cb03JS+5ByItnld5xgAw0HCw0s38MhkjQe31jg5WmJcJEzmdAK0dmjlU6LUleRdoeyHSKbM0XsW0m1FFKb7hHKgVOE8NqvT8dCL0lYTQeOIqtOkRdU+MqeSmdBhIdRBl+PZjZQPTd/IU0f383Wr9/OPDv8lt/bO8JGV1zJ8aBWAdD0Ig241KUc7naAa+Wuk0WJpohJaH6TSOinrNKLqRD6lJQuOseWKaCUniis0hbqIvIBvk7IU0kTq2AvNNulczbWqyZyuQXNvz9+PzKV7NNFaqjv3Cz5NxhF0XmYaF+Dvr2RbiIfQO+6oT6XUacpDqz7Kpu7UiArarciWptxx6DS39M+xlvqogzt7z/AVS4/CjTCpE85WA47nqzy8dQiA+48fQR7qs/bJowzKkvLZE+h0itzzMAD7/qbDvjtu5uzrVti6OWJyY4RbzWG1YBIm8vFGRDQRkg1HUfd4dj3D9QsGS95ZcHhpm4PdbW4arHN8dZkzwx7D7Yx6O6Q9TR0ydUxLoRwU9HpT4tW6dbAV44R6GBMNHXWZMs5ixp2Mja7/3UmSKkTO5CSuZpD6+60X7rfVZMzLemeZHEh4Oi7Z6PaYrqdeQHjcfDf6c+wK/50jta8MVTXfSyGST2OFJgUxoXXOaenI+jnjkz2MnVzJ8cgtR/e8XrlhXBO82AgOi8QwrhTzkRcWZbF7XNGlFxFJ8AODD6nqhwFU9YSqVqpaA7+AD60EOAbcPPfym8K2Hajqz6vqG1X1jdFy/0o23zAMwzCMBeBKj0cOHYjO320YhmEYxhfJlawGIsAvAQ+o6gfmth8J+aMA3wbcF55/BPh1EfkAXtDqFcAnL/UZqrNIiZmQnc8Zdk5nwQqiyI5ckKYtYZ/MpQhIE6Uhs0X0kMZRN8r+aUnk/OfUTohcTVW7NhWlVr862URc+LbVbclMmWuA1tLKabRaC89tqt8+p82hTttw76bCyXyQhoR8ZlV/HuYjAmZv6N9HxGtVNOVLCcdLPUvLaKt7NBEqTVk9BW3+qlz4g0JI+nx1AwkCn8JsBdeVQiNf1gp1lqA5XrSwCFVV5g2dJ2jv7SjneCk0rEy38fvMVsCbdstcfyi4Stu2aeRXQ6uuo04j6JTEcYX69HaKXkTV88KljdrhfD57ex5CFEmde9E9jaNWHHJHO0vf1jp37Sp5GYecAxVKp7jtaEfofu4yiqRmkqWUlaPX8ZUs+iHNJHUltQqDaMogqP0diLfbj45CGMpEEzaqLg5lyU2onO/IRCqW3YSCCEdNSkUvXDQHI9+I/RQUepph3eFEusyoTKlU2vKrhUYz7QdRiGpwQh20PeqOFw8UBVd4W1WYCQqGsr7NudRCqFFcK/0gs+oKDTXtd4IUwvZml6f3rTJZTrg5PcvLsxOsLd3Ko12fD6FbUShJ2XxfnHfxzUUSedFD3alTo+EzFeqCHWUmAarcp6O5xOtQiFOfwtREUzUhZLWPFtK5yxbOi5hyc1EV880M32s7BGjno63UR0npXJpa8z3j2zAT4G2Ec+sCNJzoOvHvXU6FSSkcz5aIpabb1kaFw9EWh6IhmVRUCFt1wr3dmwAoNeK+0y8jX3bQ64KLoK7Qwr9ei5zo6RN0j/aZ7E+YTn2VmjipKLuhWydNSoX4SDNx1BIz7cwEYeNOzf50RF5HFFVEnsdMg2gqU9eW961DdFISVRBKOldl1KaNuVIgd/gsQv/+bQltII5qIldT1o44aJrU6kikohfn9NKCcadkksbUzO7pJjpGVFuRVZ/6M0vzmE+HayPxmigc8eW3mygi4+qMRwzDMAzDuPxcybjFtwD/GLhXRO4O234Q+Icichd+6P0E8M8AVPV+Efkt4PN45e7veSHK23Le5E/VOxl0x2RP2gmu7DgWdqiSNcee/yFu5tiY/5xKxWtjzH3u/H4IIe7atOG8WbRK+LzzbXhuW3e+bvZoqnKc32htHDDPM15tyqNKcE7MVyNonSBhMjifB8/8+1/oM3ac6NnEqNWGaBwYzf7ms+vZa2DOydEIzF3KHrnI30shs/dsHROXeF0jygmhTSEcu50oqLRCrJTSCoy6yk8gGwfPjvd0fgJaRyH8PtGZ6KGoT0VyOhNhTWaOL8Jnq4JESl0KOIc0+7MKl9R0Mh+aPkhyuvEsLL1xUPRcTi+akrmCRMpWJLEK3qtEKjpSEkndPsBPvnIiCo1JwgS0wFEhjML1Xqhjve6xXWWMq4SijnwKVetofO5J1lBKFXZO1NqJHOx0BMDMKdY48pprNVy/O+6ROQclsRKnJYNkSiIVQ01Zr3ps5+ksRai9HmXHNd/e0uE7QiNvj6NxKDTXRdiPn9RXaVOdI7Qh0ra8afOQ+UY25Urn7vuLoj716Tm+w+Z8y+wS3/H1d4FrXxRfuDG0odHgaEqi1slM46VO/JdRndVIVrHUyRkkUzpzJUyGmuJq/yajusPJcokHxjcCcGrUx40cUV5DUUL93K9/WV6i6Dlf5jXyt0RdO69FQnCABmeLunAvJUoU+c+MXE2twrhKGJUp0zL2Tug5JwPOO4matL5KpS1Bq3NpZBqOw2mbghFFNXFckcQVkatJXEUaVcThxDmpqXCUtX9UQUNFGiFkeO51OvcdfMF+Os9h758/ty+vc67KeMQwjKvP+WkjlhZiXAne+qb7LRVkl7iS1UD+HxceLv3BJV7zfuD9V6pNhmEYhmFcX9h4xDAMwzD2JqLPWdbcO4jIKWAInN7ttlxhDmI2LgKLbuOi2wdm46JwvdjYV9VDu92Q6wEbjywMi24fmI2Lgtm4GFwvNr7k8ciedlYAiMhfq+obd7sdVxKzcTFYdBsX3T4wGxcFs9G4ElwP53zRbVx0+8BsXBTMxsXAbHx+rBC7YRiGYRiGYRiGYRjXFOasMAzDMAzDMAzDMAzjmmIRnBU/v9sNuAqYjYvBotu46PaB2bgomI3GleB6OOeLbuOi2wdm46JgNi4GZuPzsOc1KwzDMAzDMAzDMAzDWCwWIbLCMAzDMAzDMAzDMIwFwpwVhmEYhmEYhmEYhmFcU+xZZ4WIfKOIPCQij4jID+x2ey4XIvKEiNwrIneLyF+HbftF5KMi8nD4u2+32/liEJFfFpGTInLf3LYL2iSenwr9eo+IvGH3Wv7CuYiN7xORY6Ev7xaRb57b995g40Mi8nd2p9UvDhG5WUT+REQ+LyL3i8i/CtsXpi8vYePC9KWIZCLySRH5XLDxP4Xtt4nIJ4ItvykiadjeCf8/EvbfupvtfyFcwsYPisjjc/14V9i+565VABGJROSzIvL74f+F6cO9hI1H9g42Htn7v2Fg45FF6ctFH49cL2MRuMLjEVXdcw8gAh4FbgdS4HPAnbvdrstk2xPAwfO2/SjwA+H5DwA/stvtfJE2vRV4A3Df89kEfDPwh4AAXwl8Yrfb/0XY+D7g+y9w7J3hmu0At4VrOdptG16AjUeAN4TnS8DfBFsWpi8vYePC9GXoj0F4ngCfCP3zW8C7wvafA/55eP4vgJ8Lz98F/OZu2/BF2PhB4J0XOH7PXauh3f8a+HXg98P/C9OHe+WBjUdsPHKNPS5i48L8hoV223hkAfryEr/VC/Fbdgn7PsgCjUVC26/YeGSvRlZ8OfCIqj6mqjnwG8A7drlNV5J3AL8Snv8K8K272JYXjar+GXD2vM0Xs+kdwK+q56+AVRE5cnVa+tK5iI0X4x3Ab6jqVFUfBx7BX9PXNKp6XFU/E55vAQ8AR1mgvryEjRdjz/Vl6I/t8G8SHgq8HfidsP38fmz693eArxURuUrNfUlcwsaLseeuVRG5Cfi7wC+G/4UF6sM9hI1H9hA2HnkOe+43DGw8chH2XF8u+njkehiLwJUfj+xVZ8VR4Km5/5/m0jfwXkKB/y0inxaR94Rta6p6PDx/FljbnaZdVi5m06L17feGUK5fllm47J63MYRtvR7vJV7IvjzPRligvgzhencDJ4GP4ldg1lW1DIfM29HaGPZvAAeubotfPOfbqKpNP74/9OOPi0gnbNuL/fgTwL8D6vD/ARasD/cIe/HaeaHYeGSx+nZhfsPmsfHI3u7LRR+PXAdjEbjC45G96qxYZL5KVd8AfBPwPSLy1vmd6uNmFqre7CLaFPhZ4A7gLuA48GO725zLg4gMgN8Fvk9VN+f3LUpfXsDGhepLVa1U9S7gJvzKy6t2uUmXnfNtFJHXAu/F2/plwH7g3+9iE18yIvItwElV/fRut8VYaGw8sjgs1G9Yg41H9n5fLvp4ZJHHInB1xiN71VlxDLh57v+bwrY9j6oeC39PAv8Df+OeaMKAwt+Tu9fCy8bFbFqYvlXVE+FLqgZ+gVk43p61UUQS/I/mh1T1w2HzQvXlhWxcxL4EUNV14E+AN+HDDeOwa96O1sawfwU4c5Wb+pKZs/EbQ1itquoU+K/s3X58C/D3ROQJfNrB24GfZEH78Bpnr107LxgbjyxO3y7ib5iNRxanL2HxxyMLOhaBqzAe2avOik8BrwhKoyleoOMju9ymLxoR6YvIUvMc+AbgPrxt3xkO+07gf+5OCy8rF7PpI8C7gyLuVwIbcyF9e4rz8sy+Dd+X4G18V1DEvQ14BfDJq92+F0vIKfsl4AFV/cDcroXpy4vZuEh9KSKHRGQ1PO8CX4/Phf0T4J3hsPP7senfdwIfCytW1ywXsfHBuUGs4PMn5/txz1yrqvpeVb1JVW/F//59TFW/gwXqwz2EjUf2PgvzG3YxFuk3DGw8MnfYnu7LRR+PLPpYBK7SeESvAQXRl/LAK6b+DT636Yd2uz2Xyabb8Uq+nwPub+zC5/L8H+Bh4I+B/bvd1hdp13/Hh6oV+Lyl776YTXgF3J8J/Xov8Mbdbv8XYeOvBRvuCTfnkbnjfyjY+BDwTbvd/hdo41fhQyrvAe4Oj29epL68hI0L05fA64DPBlvuA/5j2H47fmDzCPDbQCdsz8L/j4T9t++2DV+EjR8L/Xgf8N+YqXTvuWt1zta3MVPfXpg+3EsPbDyy6+19EXbZeGSP/4aFNtt4ZAH68hK/1QvxW3YJ+xZuLBLa/zauwHhEwgsNwzAMwzAMwzAMwzCuCfZqGohhGIZhGIZhGIZhGAuKOSsMwzAMwzAMwzAMw7imMGeFYRiGYRiGYRiGYRjXFOasMAzDMAzDMAzDMAzjmsKcFYZhGIZhGIZhGIZhXFOYs8IwjCuGiDwhIgfD87/Y7fYYhmEYhnH9YeMRw9ibmLPCMIznICLx5X5PVX3z5X5PwzAMwzAWFxuPGMb1jTkrDOM6RUTeLSL3iMjnROTXROSDIvJzIvIJ4EdFZL+I/F445q9E5HXhdX9bRO4Oj8+KyJKIHBGRPwvb7hORr77A522Hv28Tkf8rIr8jIg+KyIdERMK+LxWRPxWRT4vIH4nIkat6UgzDMAzDuKrYeMQwjItx2b2VhmFc+4jIa4D/ALxZVU+LyH7gA8BNYVslIj8NfFZVv1VE3g78KnAX8P3A96jqx0VkAEyA9wB/pKrvF5EI6D1PE14PvAZ4Bvg48JYwKPlp4B2qekpE/gHwfuC7LrP5hmEYhmFcA9h4xDCMS2HOCsO4Pnk78NuqehpAVc+GxYTfVtUqHPNVwN8P+z8mIgdEZBn/Y/4BEfkQ8GFVfVpEPgX8sogkwO+p6t3P8/mfVNWnAUTkbuBWYB14LfDR0JYIOH7ZLDYMwzAM41rDxiOGYVwUSwMxDGOe4fMdoKr/GfgnQBf4uIi8SlX/DHgrcAz4oIi8+3neZjr3vMI7TgW4X1XvCo8vUdVveElWGIZhGIaxl7HxiGEY5qwwjOuUjwHfLiIHAELY5fn8OfAdYf/bgNOquikid6jqvar6I8CngFeJyMuAE6r6C8AvAm94CW16CDgkIm8Kn5mE8FDDMAzDMBYTG48YhnFRLA3EMK5DVPV+EXk/8KciUgGfvcBh78OHUt4DjIDvDNu/T0S+BqiB+4E/BN4F/FsRKYBt4PlWMi7UplxE3gn8lIis4L+ffiJ8hmEYhmEYC4aNRwzDuBSiqrvdBsMwDMMwDMMwDMMwjBZLAzEMwzAMwzAMwzAM45rCnBWGYRiGYRiGYRiGYVxTmLPCMAzDMAzDMAzDMIxrCnNWGIZhGIZhGIZhGIZxTWHOCsMwDMMwDMMwDMMwrinMWWEYhmEYhmEYhmEYxjWFOSsMwzAMwzAMwzAMw7im+P+G0yO1aogAhQAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "x_cr = train_seismic[:, idx, :].swapaxes(0, 1)\n", - "x_crl = train_labels[:, idx, :].swapaxes(0, 1)\n", - "\n", - "plot_aline(x_cr, x_crl, xlabel=\"crossline\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Model training" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "# Set up logging\n", - "logging.config.fileConfig(config.LOG_CONFIG)\n", - "logger = logging.getLogger(__name__)\n", - "logger.debug(config.WORKERS)\n", - "torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up data augmentation\n", - "\n", - "Let's define our data augmentation pipeline, which includes basic transformations, such as data normalization, resizing, and padding if necessary. " - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "# Setup Augmentations\n", - "basic_aug = Compose(\n", - " [\n", - " Normalize(\n", - " mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1\n", - " ),\n", - " Resize(\n", - " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", - " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", - " always_apply=True,\n", - " ),\n", - " PadIfNeeded(\n", - " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", - " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", - " border_mode=cv2.BORDER_CONSTANT,\n", - " always_apply=True,\n", - " mask_value=255,\n", - " ),\n", - " ]\n", - ")\n", - "\n", - "if config.TRAIN.AUGMENTATION:\n", - " train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)])\n", - "else:\n", - " train_aug = basic_aug" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load the data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For training the model, we will use a patch-based approach. Rather than using entire sections (crosslines or inlines) of the data, we extract a large number of small patches from the sections, and use the patches as our data. This allows us to generate larger set of images for training, but is also a more feasible approach for large seismic volumes. \n", - "\n", - "We created a customer patch data loader for generating and loading patches from our seismic data." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/data/home/vapaunic/repos/DeepSeismic/interpretation/deepseismic_interpretation/dutchf3/data.py:460: UserWarning: This no longer pads the volume\n", - " warnings.warn(\"This no longer pads the volume\")\n" - ] - } - ], - "source": [ - "TrainPatchLoader = get_patch_loader(config)\n", - "\n", - "train_set = TrainPatchLoader(\n", - " config.DATASET.ROOT,\n", - " split=\"train\",\n", - " is_transform=True,\n", - " stride=config.TRAIN.STRIDE,\n", - " patch_size=config.TRAIN.PATCH_SIZE,\n", - " augmentations=train_aug,\n", - ")\n", - "\n", - "\n", - "train_loader = data.DataLoader(\n", - " train_set,\n", - " batch_size=config.TRAIN.BATCH_SIZE_PER_GPU,\n", - " num_workers=config.WORKERS,\n", - " shuffle=True,\n", - ")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up model training" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's define a model to train, an optimization algorithm, and a loss function. \n", - "\n", - "Note that the model is loaded from our `cv_lib` library, using the name of the model as specified in the configuration file. To load a different model, either change the MODEL.NAME field in the configuration file, or create a new one corresponding to the model you wish to train." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "# load a model\n", - "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", - "\n", - "# Send to GPU if available\n", - "model = model.to(device)\n", - "\n", - "# SGD optimizer\n", - "optimizer = torch.optim.SGD(\n", - " model.parameters(),\n", - " lr=config.TRAIN.MAX_LR,\n", - " momentum=config.TRAIN.MOMENTUM,\n", - " weight_decay=config.TRAIN.WEIGHT_DECAY,\n", - ")\n", - "\n", - "# learning rate scheduler\n", - "scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS\n", - "snapshot_duration = scheduler_step * len(train_loader)\n", - "scheduler = CosineAnnealingScheduler(\n", - " optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration\n", - ")\n", - "\n", - "# weights are inversely proportional to the frequency of the classes in the training set\n", - "class_weights = torch.tensor(\n", - " config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False\n", - ")\n", - "\n", - "# loss function\n", - "criterion = torch.nn.CrossEntropyLoss(\n", - " weight=class_weights, ignore_index=255, reduction=\"mean\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Training the model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We use [ignite](https://pytorch.org/ignite/index.html) framework to create training and validation loops in our codebase. Ignite provides an easy way to create compact training/validation loops without too much boilerplate code.\n", - "\n", - "In this notebook, we demonstrate the use of ignite on the training loop only. We create a training engine `trainer`, that loops multiple times over the training dataset and updates model parameters. In addition, we add various events to the trainer, using an event system, that allows us to interact with the engine on each step of the run, such as, when the trainer is started/completed, when the epoch is started/completed and so on. \n", - "\n", - "In the cell below, we use event handlers to log training output, log and schedule learning rate, and periodically save model to disk." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "# create training engine\n", - "trainer = create_supervised_trainer(\n", - " model, optimizer, criterion, prepare_batch, device=device\n", - ")\n", - "\n", - "# add learning rate scheduler\n", - "trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)\n", - "\n", - "# add logging of traininig output\n", - "trainer.add_event_handler(\n", - " Events.ITERATION_COMPLETED,\n", - " logging_handlers.log_training_output(log_interval=config.PRINT_FREQ),\n", - ")\n", - "\n", - "# add logging of learning rate\n", - "trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer))\n", - "\n", - "# add model checkpointing\n", - "output_dir = path.join(config.OUTPUT_DIR, config.TRAIN.MODEL_DIR)\n", - "checkpoint_handler = ModelCheckpoint(\n", - " output_dir, \"model\", save_interval=2, n_saved=3, create_dir=True, require_empty=False\n", - ")\n", - "trainer.add_event_handler(\n", - " Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model}\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Start the training engine run." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-09-25 18:19:05,213 - ignite.engine.engine.Engine - INFO - Engine run starting with max_epochs=10.\n", - "2019-09-25 18:19:05,214 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.02]\n", - "2019-09-25 18:19:27,468 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 50 loss 0.7012420892715454\n", - "2019-09-25 18:19:46,513 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 100 loss 0.28899359703063965\n", - "2019-09-25 18:20:05,579 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 150 loss 0.173098623752594\n", - "2019-09-25 18:20:24,618 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 200 loss 0.17099666595458984\n", - "2019-09-25 18:20:43,668 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 250 loss 0.08583173900842667\n", - "2019-09-25 18:21:02,722 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 300 loss 0.0811498835682869\n", - "2019-09-25 18:21:21,772 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 350 loss 0.14504070580005646\n", - "2019-09-25 18:21:40,824 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 400 loss 0.12912097573280334\n", - "2019-09-25 18:21:59,878 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 450 loss 0.10280831903219223\n", - "2019-09-25 18:22:18,935 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 500 loss 0.06620465964078903\n", - "2019-09-25 18:22:37,987 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 550 loss 0.08374416828155518\n", - "2019-09-25 18:22:57,039 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 600 loss 0.0626806691288948\n", - "2019-09-25 18:23:16,098 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 650 loss 0.061261530965566635\n", - "2019-09-25 18:23:35,156 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 1 Iter: 700 loss 0.06074289232492447\n", - "2019-09-25 18:23:38,481 - ignite.engine.engine.Engine - INFO - Epoch[1] Complete. Time taken: 00:04:33\n", - "2019-09-25 18:23:38,482 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.01052125719751]\n", - "2019-09-25 18:23:56,938 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 750 loss 0.06132698804140091\n", - "2019-09-25 18:24:16,004 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 800 loss 0.05830317735671997\n", - "2019-09-25 18:24:35,077 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 850 loss 0.06894852221012115\n", - "2019-09-25 18:24:54,142 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 900 loss 0.05653904750943184\n", - "2019-09-25 18:25:13,209 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 950 loss 0.06860452890396118\n", - "2019-09-25 18:25:32,281 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1000 loss 0.05137062445282936\n", - "2019-09-25 18:25:51,346 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1050 loss 0.058020830154418945\n", - "2019-09-25 18:26:10,416 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1100 loss 0.048042453825473785\n", - "2019-09-25 18:26:29,489 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1150 loss 0.05230626463890076\n", - "2019-09-25 18:26:48,575 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1200 loss 0.06947802007198334\n", - "2019-09-25 18:27:07,645 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1250 loss 0.0545332171022892\n", - "2019-09-25 18:27:26,710 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1300 loss 0.051540959626436234\n", - "2019-09-25 18:27:45,788 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1350 loss 0.06202984228730202\n", - "2019-09-25 18:28:04,866 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 2 Iter: 1400 loss 0.044138021767139435\n", - "2019-09-25 18:28:06,337 - ignite.engine.engine.Engine - INFO - Epoch[2] Complete. Time taken: 00:04:27\n", - "2019-09-25 18:28:06,656 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.0010000237825795594]\n", - "2019-09-25 18:28:24,347 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1450 loss 0.051229942589998245\n", - "2019-09-25 18:28:43,423 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1500 loss 0.06309280544519424\n", - "2019-09-25 18:29:02,499 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1550 loss 0.05887899920344353\n", - "2019-09-25 18:29:21,575 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1600 loss 0.09512709826231003\n", - "2019-09-25 18:29:40,655 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1650 loss 0.05940958112478256\n", - "2019-09-25 18:29:59,732 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1700 loss 0.07722700387239456\n", - "2019-09-25 18:30:18,804 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1750 loss 0.05359434708952904\n", - "2019-09-25 18:30:37,883 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1800 loss 0.07005228847265244\n", - "2019-09-25 18:30:56,956 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1850 loss 0.0506112277507782\n", - "2019-09-25 18:31:16,030 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1900 loss 0.04246119037270546\n", - "2019-09-25 18:31:35,136 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 1950 loss 0.04035837575793266\n", - "2019-09-25 18:31:54,217 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 2000 loss 0.05233351141214371\n", - "2019-09-25 18:32:13,293 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 2050 loss 0.04997049272060394\n", - "2019-09-25 18:32:32,368 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 3 Iter: 2100 loss 0.04571027681231499\n", - "2019-09-25 18:32:34,603 - ignite.engine.engine.Engine - INFO - Epoch[3] Complete. Time taken: 00:04:27\n", - "2019-09-25 18:32:34,604 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.01052125719751]\n", - "2019-09-25 18:32:51,553 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2150 loss 0.04112791642546654\n", - "2019-09-25 18:33:10,627 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2200 loss 0.058870673179626465\n", - "2019-09-25 18:33:29,718 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2250 loss 0.05149131268262863\n", - "2019-09-25 18:33:48,793 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2300 loss 0.04544535651803017\n", - "2019-09-25 18:34:07,870 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2350 loss 0.049925826489925385\n", - "2019-09-25 18:34:26,948 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2400 loss 0.0426165871322155\n", - "2019-09-25 18:36:40,503 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2750 loss 0.039714518934488297\n", - "2019-09-25 18:36:59,582 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 4 Iter: 2800 loss 0.06449242681264877\n", - "2019-09-25 18:37:02,578 - ignite.engine.engine.Engine - INFO - Epoch[4] Complete. Time taken: 00:04:27\n", - "2019-09-25 18:37:02,892 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.0010000237825795594]\n", - "2019-09-25 18:37:19,078 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 2850 loss 0.04923075810074806\n", - "2019-09-25 18:37:38,158 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 2900 loss 0.04656514525413513\n", - "2019-09-25 18:37:57,228 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 2950 loss 0.04173054173588753\n", - "2019-09-25 18:38:16,295 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3000 loss 0.045953549444675446\n", - "2019-09-25 18:38:35,370 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3050 loss 0.03728904575109482\n", - "2019-09-25 18:38:54,446 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3100 loss 0.04939805343747139\n", - "2019-09-25 18:39:13,525 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3150 loss 0.045213744044303894\n", - "2019-09-25 18:39:32,600 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3200 loss 0.09391524642705917\n", - "2019-09-25 18:39:51,690 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3250 loss 0.04950859397649765\n", - "2019-09-25 18:40:10,766 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3300 loss 0.04418589174747467\n", - "2019-09-25 18:40:29,841 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3350 loss 0.04449845105409622\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-09-25 18:40:48,921 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3400 loss 0.043800223618745804\n", - "2019-09-25 18:41:08,003 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3450 loss 0.039736416190862656\n", - "2019-09-25 18:41:27,081 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 5 Iter: 3500 loss 0.038158830255270004\n", - "2019-09-25 18:41:30,841 - ignite.engine.engine.Engine - INFO - Epoch[5] Complete. Time taken: 00:04:27\n", - "2019-09-25 18:41:30,842 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.01052125719751]\n", - "2019-09-25 18:41:46,253 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3550 loss 0.04899909719824791\n", - "2019-09-25 18:42:05,331 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3600 loss 0.04364394396543503\n", - "2019-09-25 18:42:24,410 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3650 loss 0.04946207255125046\n", - "2019-09-25 18:42:43,516 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3700 loss 0.04041216894984245\n", - "2019-09-25 18:43:02,598 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3750 loss 0.0504840649664402\n", - "2019-09-25 18:43:21,684 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3800 loss 0.041116222739219666\n", - "2019-09-25 18:43:40,764 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3850 loss 0.04205234348773956\n", - "2019-09-25 18:43:59,841 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3900 loss 0.0413297601044178\n", - "2019-09-25 18:44:18,920 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 3950 loss 0.0376584492623806\n", - "2019-09-25 18:44:38,000 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4000 loss 0.03894781693816185\n", - "2019-09-25 18:44:57,080 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4050 loss 0.03995727002620697\n", - "2019-09-25 18:45:16,158 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4100 loss 0.0402936115860939\n", - "2019-09-25 18:45:35,237 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4150 loss 0.03892284631729126\n", - "2019-09-25 18:45:54,319 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 6 Iter: 4200 loss 0.03852567449212074\n", - "2019-09-25 18:45:58,843 - ignite.engine.engine.Engine - INFO - Epoch[6] Complete. Time taken: 00:04:28\n", - "2019-09-25 18:45:59,922 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.0010000237825795594]\n", - "2019-09-25 18:46:14,592 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4250 loss 0.04775504767894745\n", - "2019-09-25 18:46:33,692 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4300 loss 0.035263791680336\n", - "2019-09-25 18:46:52,785 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4350 loss 0.03407309949398041\n", - "2019-09-25 18:47:11,875 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4400 loss 0.03757726028561592\n", - "2019-09-25 18:47:30,973 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4450 loss 0.04868190735578537\n", - "2019-09-25 18:47:50,070 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4500 loss 0.04181281104683876\n", - "2019-09-25 18:48:09,169 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4550 loss 0.056338150054216385\n", - "2019-09-25 18:48:28,273 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4600 loss 0.0411737821996212\n", - "2019-09-25 18:48:47,373 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4650 loss 0.051507413387298584\n", - "2019-09-25 18:49:06,472 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 7 Iter: 4700 loss 0.04746434837579727\n", - "2019-09-25 18:51:01,204 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5000 loss 0.03539836034178734\n", - "2019-09-25 18:51:20,303 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5050 loss 0.03780356049537659\n", - "2019-09-25 18:51:39,411 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5100 loss 0.03860008716583252\n", - "2019-09-25 18:51:58,510 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5150 loss 0.031131915748119354\n", - "2019-09-25 18:52:17,611 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5200 loss 0.03526508808135986\n", - "2019-09-25 18:52:36,711 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5250 loss 0.04343230277299881\n", - "2019-09-25 18:52:55,815 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5300 loss 0.034633275121450424\n", - "2019-09-25 18:53:14,917 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5350 loss 0.03684268891811371\n", - "2019-09-25 18:53:34,024 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5400 loss 0.038611602038145065\n", - "2019-09-25 18:53:53,123 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5450 loss 0.043228939175605774\n", - "2019-09-25 18:54:12,227 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5500 loss 0.03565002605319023\n", - "2019-09-25 18:54:31,330 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5550 loss 0.0400361567735672\n", - "2019-09-25 18:54:50,435 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 8 Iter: 5600 loss 0.04023653641343117\n", - "2019-09-25 18:54:56,493 - ignite.engine.engine.Engine - INFO - Epoch[8] Complete. Time taken: 00:04:28\n", - "2019-09-25 18:54:57,614 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.0010000237825795594]\n", - "2019-09-25 18:55:10,763 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5650 loss 0.029134199023246765\n", - "2019-09-25 18:55:29,876 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5700 loss 0.0546630434691906\n", - "2019-09-25 18:55:48,970 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5750 loss 0.035139769315719604\n", - "2019-09-25 18:56:08,062 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5800 loss 0.03422367200255394\n", - "2019-09-25 18:56:27,160 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5850 loss 0.042337819933891296\n", - "2019-09-25 18:56:46,254 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5900 loss 0.037118345499038696\n", - "2019-09-25 18:57:05,353 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 5950 loss 0.03742925077676773\n", - "2019-09-25 18:57:24,441 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6000 loss 0.036784060299396515\n", - "2019-09-25 18:57:43,533 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6050 loss 0.032838501036167145\n", - "2019-09-25 18:58:02,623 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6100 loss 0.036310210824012756\n", - "2019-09-25 18:58:21,722 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6150 loss 0.03435125946998596\n", - "2019-09-25 18:58:40,814 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6200 loss 0.035941898822784424\n", - "2019-09-25 18:58:59,908 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6250 loss 0.032288145273923874\n", - "2019-09-25 18:59:18,996 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 9 Iter: 6300 loss 0.03844328597187996\n", - "2019-09-25 18:59:25,812 - ignite.engine.engine.Engine - INFO - Epoch[9] Complete. Time taken: 00:04:28\n", - "2019-09-25 18:59:25,814 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.01052125719751]\n", - "2019-09-25 18:59:38,194 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6350 loss 0.036316316574811935\n", - "2019-09-25 18:59:57,292 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6400 loss 0.03244319185614586\n", - "2019-09-25 19:00:16,389 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6450 loss 0.02924899198114872\n", - "2019-09-25 19:00:35,479 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6500 loss 0.03716830164194107\n", - "2019-09-25 19:00:54,575 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6550 loss 0.029776113107800484\n", - "2019-09-25 19:01:13,666 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6600 loss 0.03399008512496948\n", - "2019-09-25 19:01:32,757 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6650 loss 0.036496154963970184\n", - "2019-09-25 19:01:51,850 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6700 loss 0.0327572338283062\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-09-25 19:02:10,947 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6750 loss 0.03194150701165199\n", - "2019-09-25 19:02:30,038 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6800 loss 0.029924677684903145\n", - "2019-09-25 19:02:49,132 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6850 loss 0.03403583541512489\n", - "2019-09-25 19:03:08,225 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6900 loss 0.032861556857824326\n", - "2019-09-25 19:03:27,314 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 6950 loss 0.0374293178319931\n", - "2019-09-25 19:03:46,406 - cv_lib.event_handlers.logging_handlers - INFO - Epoch: 10 Iter: 7000 loss 0.031759023666381836\n", - "2019-09-25 19:03:53,986 - ignite.engine.engine.Engine - INFO - Epoch[10] Complete. Time taken: 00:04:28\n", - "2019-09-25 19:03:55,106 - ignite.engine.engine.Engine - INFO - Engine run complete. Time taken 00:44:49\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Evaluation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will next evaluate the performance of the model by looking at how well it predicts facies labels on samples from the test set.\n", - "\n", - "We will use the following evaluation metrics:\n", - "\n", - "- Pixel Accuracy (PA)\n", - "- Class Accuracy (CA)\n", - "- Mean Class Accuracy (MCA)\n", - "- Frequency Weighted intersection-over-union (FW IoU)\n", - "- Mean IoU (MIoU)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's first load the model saved previously." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", - "model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False)\n", - "model = model.to(device)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we load the test data and define the augmentations on it. " - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "# Augmentation\n", - "section_aug = Compose(\n", - " [Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1)]\n", - ")\n", - "\n", - "patch_aug = Compose(\n", - " [\n", - " Resize(\n", - " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", - " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", - " always_apply=True,\n", - " ),\n", - " PadIfNeeded(\n", - " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", - " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", - " border_mode=cv2.BORDER_CONSTANT,\n", - " always_apply=True,\n", - " mask_value=255,\n", - " ),\n", - " ]\n", - ")\n", - "\n", - "# Process test data\n", - "pre_processing = _compose_processing_pipeline(config.TRAIN.DEPTH, aug=patch_aug)\n", - "output_processing = _output_processing_pipeline(config)\n", - "\n", - "# Select the test split\n", - "split = \"test1\" if \"both\" in config.TEST.SPLIT else config.TEST.SPLIT\n", - "\n", - "labels = np.load(path.join(config.DATASET.ROOT, \"test_once\", split + \"_labels.npy\"))\n", - "section_file = path.join(config.DATASET.ROOT, \"splits\", \"section_\" + split + \".txt\")\n", - "_write_section_file(labels, section_file, config)\n", - "\n", - "# Load test data\n", - "TestSectionLoader = get_test_loader(config)\n", - "test_set = TestSectionLoader(\n", - " config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug\n", - ")\n", - "\n", - "test_loader = data.DataLoader(\n", - " test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False\n", - ")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Predict segmentation mask on the test data" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-09-25 19:50:17,990 - __main__ - INFO - split: test1, section: 0\n", - "2019-09-25 19:50:25,205 - __main__ - INFO - split: test1, section: 1\n", - "2019-09-25 19:50:25,851 - __main__ - INFO - split: test1, section: 2\n", - "2019-09-25 19:50:26,497 - __main__ - INFO - split: test1, section: 3\n", - "2019-09-25 19:50:27,142 - __main__ - INFO - split: test1, section: 4\n", - "2019-09-25 19:50:27,789 - __main__ - INFO - split: test1, section: 5\n", - "2019-09-25 19:50:28,437 - __main__ - INFO - split: test1, section: 6\n", - "2019-09-25 19:50:29,093 - __main__ - INFO - split: test1, section: 7\n", - "2019-09-25 19:50:47,011 - __main__ - INFO - split: test1, section: 8\n", - "2019-09-25 19:50:49,830 - __main__ - INFO - split: test1, section: 9\n", - "2019-09-25 19:50:50,473 - __main__ - INFO - split: test1, section: 10\n", - "2019-09-25 19:50:51,115 - __main__ - INFO - split: test1, section: 11\n", - "2019-09-25 19:50:51,759 - __main__ - INFO - split: test1, section: 12\n", - "2019-09-25 19:50:52,410 - __main__ - INFO - split: test1, section: 13\n", - "2019-09-25 19:50:53,061 - __main__ - INFO - split: test1, section: 14\n", - "2019-09-25 19:50:53,705 - __main__ - INFO - split: test1, section: 15\n", - "2019-09-25 19:50:54,351 - __main__ - INFO - split: test1, section: 16\n", - "2019-09-25 19:50:55,005 - __main__ - INFO - split: test1, section: 17\n", - "2019-09-25 19:50:55,666 - __main__ - INFO - split: test1, section: 18\n", - "2019-09-25 19:50:56,312 - __main__ - INFO - split: test1, section: 19\n" - ] - } - ], - "source": [ - "_CLASS_NAMES = [\n", - " \"upper_ns\",\n", - " \"middle_ns\",\n", - " \"lower_ns\",\n", - " \"rijnland_chalk\",\n", - " \"scruff\",\n", - " \"zechstein\",\n", - "]\n", - "\n", - "n_classes = len(_CLASS_NAMES)\n", - "\n", - "# keep only N_EVALUATE sections to score\n", - "test_subset = random.sample(list(test_loader), N_EVALUATE)\n", - "\n", - "results = list()\n", - "running_metrics_split = runningScore(n_classes)\n", - "\n", - "# testing mode\n", - "with torch.no_grad():\n", - " model.eval()\n", - " # loop over testing data\n", - " for i, (images, labels) in enumerate(test_subset):\n", - " logger.info(f\"split: {split}, section: {i}\")\n", - " outputs = _patch_label_2d(\n", - " model,\n", - " images,\n", - " pre_processing,\n", - " output_processing,\n", - " config.TRAIN.PATCH_SIZE,\n", - " config.TEST.TEST_STRIDE,\n", - " config.VALIDATION.BATCH_SIZE_PER_GPU,\n", - " device,\n", - " n_classes,\n", - " )\n", - "\n", - " pred = outputs.detach().max(1)[1].numpy()\n", - " gt = labels.numpy()\n", - " \n", - " # update evaluation metrics\n", - " running_metrics_split.update(gt, pred)\n", - " \n", - " # keep ground truth and result for plotting\n", - " results.append((np.squeeze(gt), np.squeeze(pred)))\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's view the obtained metrics on this subset of test images." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Pixel Acc: 0.821\n", - " upper_ns_accuracy 0.979\n", - " middle_ns_accuracy 0.894\n", - " lower_ns_accuracy 0.969\n", - " rijnland_chalk_accuracy 0.794\n", - " scruff_accuracy 0.309\n", - " zechstein_accuracy 0.662\n", - "Mean Class Acc: 0.768\n", - "Freq Weighted IoU: 0.689\n", - "Mean IoU: 0.638\n" - ] - } - ], - "source": [ - "# get scores\n", - "score, _ = running_metrics_split.get_scores()\n", - "\n", - "# Log split results\n", - "print(f'Pixel Acc: {score[\"Pixel Acc: \"]:.3f}')\n", - "for cdx, class_name in enumerate(_CLASS_NAMES):\n", - " print(f' {class_name}_accuracy {score[\"Class Accuracy: \"][cdx]:.3f}')\n", - "\n", - "print(f'Mean Class Acc: {score[\"Mean Class Acc: \"]:.3f}')\n", - "print(f'Freq Weighted IoU: {score[\"Freq Weighted IoU: \"]:.3f}')\n", - "print(f'Mean IoU: {score[\"Mean IoU: \"]:0.3f}')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Visualize predictions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's visualize the predictions on entire test sections. Note that the crosslines and inlines have different dimensions, however we were able to use them jointly for our network training and evaluation, since we were using smaller patches from the sections, whose size we can control via hyperparameter in the experiment configuration file. " - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "image/png": "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AAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAACBzBDgAAAAACR7ADAAAAgMAR7AAAAAAgcAQ7AAAAAAgcwQ4AAAAAAkewAwAAAIDAEewAAAAAIHAEOwAAAAAIHMEOAAAAAAJHsAMAAACAwBHsAAAAACBwBDsAAAAA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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "fig = plt.figure(figsize=(15,50))\n", - "\n", - "nplot = min(N_EVALUATE, 10)\n", - "for idx in range(nplot):\n", - " # plot actual\n", - " plt.subplot(nplot, 2, 2*(idx+1)-1)\n", - " plt.imshow(results[idx][0])\n", - " # plot predicted\n", - " plt.subplot(nplot, 2, 2*(idx+1))\n", - " plt.imshow(results[idx][1])\n", - "\n", - "f_axes = fig.axes\n", - "_ = f_axes[0].set_title('Actual')\n", - "_ = f_axes[1].set_title('Predicted') " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "seismic-interpretation", - "language": "python", - "name": "seismic-interpretation" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From 75fcd9ff321ceaa3c2510c6971d22d8c05136b6e Mon Sep 17 00:00:00 2001 From: Mathew Salvaris Date: Tue, 8 Oct 2019 08:50:01 +0000 Subject: [PATCH 058/207] Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 --- .pylintrc | 9 + README.md | 28 +- .../segmentation/dutchf3/metrics/__init__.py | 56 -- .../cv_lib/segmentation/penobscot/engine.py | 125 ++++ cv_lib/cv_lib/segmentation/utils.py | 40 ++ .../dutchf3_patch/local/configs/hrnet.yaml | 2 +- .../dutchf3_patch/local/default.py | 1 + .../penobscot/local/configs/hrnet.yaml | 107 ++++ .../local/configs/patch_deconvnet.yaml | 64 ++ .../local/configs/patch_deconvnet_skip.yaml | 63 ++ .../local/configs/seresnet_unet.yaml | 64 ++ .../penobscot/local/configs/unet.yaml | 68 +++ .../interpretation/penobscot/local/default.py | 115 ++++ .../interpretation/penobscot/local/test.py | 386 ++++++++++++ .../interpretation/penobscot/local/test.sh | 2 + .../interpretation/penobscot/local/train.py | 366 ++++++++++++ .../interpretation/penobscot/local/train.sh | 2 + .../interpretation/penobscot/prepare_data.py | 106 ++++ images/penobscot_seresnet_best.png | Bin 0 -> 49429 bytes images/penobscot_seresnet_worst.png | Bin 0 -> 50846 bytes .../dutchf3/data.py | 5 +- .../penobscot/__init__.py | 0 .../penobscot/data.py | 556 ++++++++++++++++++ .../penobscot/metrics.py | 130 ++++ scripts/download_penobscot.sh | 56 ++ scripts/kill_windows.sh | 12 + scripts/parallel_training.sh | 30 + 27 files changed, 2332 insertions(+), 61 deletions(-) create mode 100644 .pylintrc create mode 100644 cv_lib/cv_lib/segmentation/penobscot/engine.py create mode 100644 cv_lib/cv_lib/segmentation/utils.py create mode 100644 experiments/interpretation/penobscot/local/configs/hrnet.yaml create mode 100644 experiments/interpretation/penobscot/local/configs/patch_deconvnet.yaml create mode 100644 experiments/interpretation/penobscot/local/configs/patch_deconvnet_skip.yaml create mode 100644 experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml create mode 100644 experiments/interpretation/penobscot/local/configs/unet.yaml create mode 100644 experiments/interpretation/penobscot/local/default.py create mode 100644 experiments/interpretation/penobscot/local/test.py create mode 100755 experiments/interpretation/penobscot/local/test.sh create mode 100644 experiments/interpretation/penobscot/local/train.py create mode 100755 experiments/interpretation/penobscot/local/train.sh create mode 100644 experiments/interpretation/penobscot/prepare_data.py create mode 100644 images/penobscot_seresnet_best.png create mode 100644 images/penobscot_seresnet_worst.png create mode 100644 interpretation/deepseismic_interpretation/penobscot/__init__.py create mode 100644 interpretation/deepseismic_interpretation/penobscot/data.py create mode 100644 interpretation/deepseismic_interpretation/penobscot/metrics.py create mode 100755 scripts/download_penobscot.sh create mode 100755 scripts/kill_windows.sh create mode 100755 scripts/parallel_training.sh diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..14131a70 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,9 @@ +[MASTER] +extension-pkg-whitelist=numpy,torch,cv2 + +[TYPECHECK] + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members=numpy.*,torch.*,cv2.* \ No newline at end of file diff --git a/README.md b/README.md index 7e75d5c1..8f1d7e8b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ This section contains benchmarks of different algorithms for seismic interpretat Below are the results from the models contained in this repo. To run them check the instructions in folder. Alternatively take a look in for how to run them on your own dataset +### Netherlands F3 + | Authorship | Experiment | PA | FW IoU | MCA | |------------------|-----------------------------------|-------------|--------------|------------| | Alaudah | Section-based | 0.905 | 0.817 | .832 | @@ -47,6 +49,18 @@ Below are the results from the models contained in this repo. To run them check | | HRNet(patch)+patch_depth | .908 | .843 | .837 | | | HRNet(patch)+section_depth | .928 | .871 | .871 | +### Penobscot + +Trained and tested on full dataset. Inlines with artefacts were left in for training, validation and testing. +The dataset was split 70% training, 10% validation and 20% test. The results below are from the test set + +| Authorship | Experiment | PA | IoU | MCA | +|------------------|-------------------------------------|-------------|--------------|------------| +| Ours | SEResNet UNet + section depth | 1.0 | .98 | .99 | +| | HRNet(patch) + section depth | 1.0 | .97 | .98 | + +![Best Penobscot SEResNet](images/penobscot_seresnet_best.png "Best performing inlines, Mask and Predictions from SEResNet") +![Worst Penobscot SEResNet](images/penobscot_seresnet_worst.png "Worst performing inlines Mask and Predictions from SEResNet") ### Sparse Labels @@ -70,7 +84,19 @@ We present results of algorithms which are based on scribble-level annotations, We present results of algorithms which are based on pixel-level annotations, where the annotator labels individual pixels and gaps are allowed between pixels; the annotator can also label a small neighborhood of pixels, e.g. large dot of ~100 pixels. ### Data -The scripts expect the data to be contained in /mnt/dutchf3 +#### Netherlands F3 + + +#### Penobscot +To download the Penobscot dataset run the [download_penobscot.sh](scripts/download_penobscot.sh) script + + + +### Scripts +- [parallel_training.sh](scripts/parallel_training.sh): Script to launch multiple jobs in parallel. Used mainly for local hyperparameter tuning. Look at the script for further instructions + +- [kill_windows.sh](scripts/kill_windows.sh): Script to kill multiple tmux windows. Used to kill jobs that parallel_training.sh might have started. + ## Contributing diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py index bd9ef9e9..2e50423e 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py @@ -6,62 +6,6 @@ from ignite.metrics import Metric -# class runningScore(object): -# def __init__(self, n_classes): -# self.n_classes = n_classes -# self.confusion_matrix = np.zeros((n_classes, n_classes)) - -# def _fast_hist(self, label_true, label_pred, n_class): -# mask = (label_true >= 0) & (label_true < n_class) -# hist = np.bincount( -# n_class * label_true[mask].astype(int) + label_pred[mask], -# minlength=n_class ** 2, -# ).reshape(n_class, n_class) -# return hist - -# def update(self, label_trues, label_preds): -# for lt, lp in zip(label_trues, label_preds): -# self.confusion_matrix += self._fast_hist( -# lt.flatten(), lp.flatten(), self.n_classes -# ) - -# def get_scores(self): -# """Returns accuracy score evaluation result. -# - overall accuracy -# - mean accuracy -# - mean IU -# - fwavacc -# """ -# hist = self.confusion_matrix -# acc = np.diag(hist).sum() / hist.sum() -# acc_cls = np.diag(hist) / hist.sum(axis=1) -# mean_acc_cls = np.nanmean(acc_cls) -# iu = np.diag(hist) / ( -# hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) -# ) -# mean_iu = np.nanmean(iu) -# freq = ( -# hist.sum(axis=1) / hist.sum() -# ) # fraction of the pixels that come from each class -# fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() -# cls_iu = dict(zip(range(self.n_classes), iu)) - -# return ( -# { -# "Pixel Acc: ": acc, -# "Class Accuracy: ": acc_cls, -# "Mean Class Acc: ": mean_acc_cls, -# "Freq Weighted IoU: ": fwavacc, -# "Mean IoU: ": mean_iu, -# "confusion_matrix": self.confusion_matrix, -# }, -# cls_iu, -# ) - -# def reset(self): -# self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) - - def _fast_hist(label_true, label_pred, n_class): mask = (label_true >= 0) & (label_true < n_class) hist = np.bincount( diff --git a/cv_lib/cv_lib/segmentation/penobscot/engine.py b/cv_lib/cv_lib/segmentation/penobscot/engine.py new file mode 100644 index 00000000..2d733a45 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/penobscot/engine.py @@ -0,0 +1,125 @@ +import torch + +from ignite.engine.engine import Engine +from toolz import curry +from torch.nn import functional as F + + +def _upscale_model_output(y_pred, y): + ph, pw = y_pred.size(2), y_pred.size(3) + h, w = y.size(2), y.size(3) + if ph != h or pw != w: + y_pred = F.upsample(input=y_pred, size=(h, w), mode="bilinear") + return y_pred + + +def create_supervised_trainer( + model, + optimizer, + loss_fn, + prepare_batch, + device=None, + non_blocking=False, + output_transform=lambda x, y, y_pred, loss: {"loss": loss.item()}, +): + """Factory function for creating a trainer for supervised segmentation models. + + Args: + model (`torch.nn.Module`): the model to train. + optimizer (`torch.optim.Optimizer`): the optimizer to use. + loss_fn (torch.nn loss function): the loss function to use. + prepare_batch (callable): function that receives `batch`, `device`, `non_blocking` and outputs + tuple of tensors `(batch_x, batch_y, patch_id, patch_locations)`. + device (str, optional): device type specification (default: None). + Applies to both model and batches. + non_blocking (bool, optional): if True and this copy is between CPU and GPU, the copy may occur asynchronously + with respect to the host. For other cases, this argument has no effect. + output_transform (callable, optional): function that receives 'x', 'y', 'y_pred', 'loss' and returns value + to be assigned to engine's state.output after each iteration. Default is returning `loss.item()`. + + Note: `engine.state.output` for this engine is defined by `output_transform` parameter and is the loss + of the processed batch by default. + + Returns: + Engine: a trainer engine with supervised update function. + """ + if device: + model.to(device) + + def _update(engine, batch): + model.train() + optimizer.zero_grad() + x, y, ids, patch_locations = prepare_batch( + batch, device=device, non_blocking=non_blocking + ) + y_pred = model(x) + y_pred = _upscale_model_output(y_pred, y) + loss = loss_fn(y_pred.squeeze(1), y.squeeze(1)) + loss.backward() + optimizer.step() + return output_transform(x, y, y_pred, loss) + + return Engine(_update) + + +@curry +def val_transform(x, y, y_pred, ids, patch_locations): + return { + "image": x, + "y_pred": y_pred.detach(), + "mask": y.detach(), + "ids": ids, + "patch_locations": patch_locations, + } + + +def create_supervised_evaluator( + model, + prepare_batch, + metrics=None, + device=None, + non_blocking=False, + output_transform=val_transform, +): + """Factory function for creating an evaluator for supervised segmentation models. + + Args: + model (`torch.nn.Module`): the model to train. + prepare_batch (callable): function that receives `batch`, `device`, `non_blocking` and outputs + tuple of tensors `(batch_x, batch_y, patch_id, patch_locations)`. + metrics (dict of str - :class:`~ignite.metrics.Metric`): a map of metric names to Metrics. + device (str, optional): device type specification (default: None). + Applies to both model and batches. + non_blocking (bool, optional): if True and this copy is between CPU and GPU, the copy may occur asynchronously + with respect to the host. For other cases, this argument has no effect. + output_transform (callable, optional): function that receives 'x', 'y', 'y_pred' and returns value + to be assigned to engine's state.output after each iteration. Default is returning `(y_pred, y,)` which fits + output expected by metrics. If you change it you should use `output_transform` in metrics. + + Note: `engine.state.output` for this engine is defind by `output_transform` parameter and is + a tuple of `(batch_pred, batch_y)` by default. + + Returns: + Engine: an evaluator engine with supervised inference function. + """ + metrics = metrics or {} + + if device: + model.to(device) + + def _inference(engine, batch): + model.eval() + with torch.no_grad(): + x, y, ids, patch_locations = prepare_batch( + batch, device=device, non_blocking=non_blocking + ) + y_pred = model(x) + y_pred = _upscale_model_output(y_pred, x) + return output_transform(x, y, y_pred, ids, patch_locations) + + engine = Engine(_inference) + + for name, metric in metrics.items(): + metric.attach(engine, name) + + return engine diff --git a/cv_lib/cv_lib/segmentation/utils.py b/cv_lib/cv_lib/segmentation/utils.py new file mode 100644 index 00000000..2c31143a --- /dev/null +++ b/cv_lib/cv_lib/segmentation/utils.py @@ -0,0 +1,40 @@ +import numpy as np +from deepseismic_interpretation.dutchf3.data import decode_segmap +from os import path +from PIL import Image +from toolz import pipe + + +def _chw_to_hwc(image_array_numpy): + return np.moveaxis(image_array_numpy, 0, -1) + + +def save_images( + pred_dict, output_dir, num_classes, colours, extra_identifier="" +): + for id in pred_dict: + save_image(pred_dict[id].unsqueeze(0).cpu().numpy(), output_dir, num_classes, colours, extra_identifier=extra_identifier) + + +def save_image(image_numpy_array, output_dir, num_classes, colours, extra_identifier=""): + """Save segmentation map as image + + Args: + image_numpy_array (numpy.Array): numpy array that represents an image + output_dir ([type]): + num_classes ([type]): [description] + colours ([type]): [description] + extra_identifier (str, optional): [description]. Defaults to "". + """ + im_array = decode_segmap( + image_numpy_array, + n_classes=num_classes, + label_colours=colours, + ) + im = pipe( + (im_array * 255).astype(np.uint8).squeeze(), + _chw_to_hwc, + Image.fromarray, + ) + filename = path.join(output_dir, f"{id}_{extra_identifier}.png") + im.save(filename) \ No newline at end of file diff --git a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml index b34eae58..01f37893 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf +LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf SEED: 2019 diff --git a/experiments/interpretation/dutchf3_patch/local/default.py b/experiments/interpretation/dutchf3_patch/local/default.py index cf5c87f8..15d355d6 100644 --- a/experiments/interpretation/dutchf3_patch/local/default.py +++ b/experiments/interpretation/dutchf3_patch/local/default.py @@ -22,6 +22,7 @@ _C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" _C.SEED = 42 + # Cudnn related params _C.CUDNN = CN() _C.CUDNN.BENCHMARK = True diff --git a/experiments/interpretation/penobscot/local/configs/hrnet.yaml b/experiments/interpretation/penobscot/local/configs/hrnet.yaml new file mode 100644 index 00000000..9389a39a --- /dev/null +++ b/experiments/interpretation/penobscot/local/configs/hrnet.yaml @@ -0,0 +1,107 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 7 + ROOT: /data/penobscot + CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] + INLINE_HEIGHT: 1501 + INLINE_WIDTH: 481 + +MODEL: + NAME: seg_hrnet + IN_CHANNELS: 3 + PRETRAINED: '/data/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + COMPLETE_PATCHES_ONLY: True + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.0001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "none" #"patch" # Options are none, patch and section + STRIDE: 64 + PATCH_SIZE: 128 + AUGMENTATIONS: + RESIZE: + HEIGHT: 256 + WIDTH: 256 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: [-0.0001777, 0.49, -0.0000688] # First value is for images, second for depth and then combination of both + STD: [0.14076 , 0.2717, 0.06286] + MAX: 1 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 128 + COMPLETE_PATCHES_ONLY: True + +TEST: + COMPLETE_PATCHES_ONLY: False + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + AUGMENTATIONS: + RESIZE: + HEIGHT: 256 + WIDTH: 256 + PAD: + HEIGHT: 256 + WIDTH: 256 diff --git a/experiments/interpretation/penobscot/local/configs/patch_deconvnet.yaml b/experiments/interpretation/penobscot/local/configs/patch_deconvnet.yaml new file mode 100644 index 00000000..86315a39 --- /dev/null +++ b/experiments/interpretation/penobscot/local/configs/patch_deconvnet.yaml @@ -0,0 +1,64 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 7 + ROOT: /data/penobscot + CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] + INLINE_HEIGHT: 1501 + INLINE_WIDTH: 481 + +MODEL: + NAME: patch_deconvnet + IN_CHANNELS: 1 + + +TRAIN: + COMPLETE_PATCHES_ONLY: False + BATCH_SIZE_PER_GPU: 64 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "none" # Options are non, patch and section + STRIDE: 50 + PATCH_SIZE: 99 + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 + MEAN: -6.0668 # -6.0668283 # The statistics are estimated from a filtered value that are less than 10000 as dataset contains outliers + STD: 1367.8212 # 1367.8212 + MAX: 10000 # The possible max is actually 32767 but the dataset contains outliers so limiting to 10000 + MODEL_DIR: "models" +VALIDATION: + BATCH_SIZE_PER_GPU: 512 + COMPLETE_PATCHES_ONLY: False + +TEST: + COMPLETE_PATCHES_ONLY: False + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 diff --git a/experiments/interpretation/penobscot/local/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/penobscot/local/configs/patch_deconvnet_skip.yaml new file mode 100644 index 00000000..16f680a5 --- /dev/null +++ b/experiments/interpretation/penobscot/local/configs/patch_deconvnet_skip.yaml @@ -0,0 +1,63 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + +DATASET: + NUM_CLASSES: 7 + ROOT: /data/penobscot + CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] + INLINE_HEIGHT: 1501 + INLINE_WIDTH: 481 + +MODEL: + NAME: patch_deconvnet_skip + IN_CHANNELS: 1 + +TRAIN: + COMPLETE_PATCHES_ONLY: False + BATCH_SIZE_PER_GPU: 64 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "none" #"patch" # Options are none, patch and section + STRIDE: 50 + PATCH_SIZE: 99 + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 + MEAN: -6.0668 # -6.0668283 # The statistics are estimated from a filtered value that are less than 10000 as dataset contains outliers + STD: 1367.8212 # 1367.8212 + MAX: 10000 # The possible max is actually 32767 but the dataset contains outliers so limiting to 10000 + MODEL_DIR: "models" + +VALIDATION: + BATCH_SIZE_PER_GPU: 512 + COMPLETE_PATCHES_ONLY: False + +TEST: + COMPLETE_PATCHES_ONLY: False + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 diff --git a/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml b/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml new file mode 100644 index 00000000..e0457122 --- /dev/null +++ b/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml @@ -0,0 +1,64 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 7 + ROOT: /data/penobscot + CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] + INLINE_HEIGHT: 1501 + INLINE_WIDTH: 481 +MODEL: + NAME: resnet_unet + IN_CHANNELS: 3 + +TRAIN: + COMPLETE_PATCHES_ONLY: True + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.0001 + MAX_LR: 0.006 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "none" #"patch" # Options are none, patch and section + STRIDE: 64 + PATCH_SIZE: 128 + AUGMENTATIONS: + RESIZE: + HEIGHT: 256 + WIDTH: 256 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: [-0.0001777, 0.49, -0.0000688] # First value is for images, second for depth and then combination of both + STD: [0.14076 , 0.2717, 0.06286] + MAX: 1 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + COMPLETE_PATCHES_ONLY: True + +TEST: + COMPLETE_PATCHES_ONLY: False + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 diff --git a/experiments/interpretation/penobscot/local/configs/unet.yaml b/experiments/interpretation/penobscot/local/configs/unet.yaml new file mode 100644 index 00000000..7ee89169 --- /dev/null +++ b/experiments/interpretation/penobscot/local/configs/unet.yaml @@ -0,0 +1,68 @@ +# UNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 7 + ROOT: /data/penobscot + CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] + INLINE_HEIGHT: 1501 + INLINE_WIDTH: 481 + +MODEL: + NAME: resnet_unet + IN_CHANNELS: 3 + + +TRAIN: + COMPLETE_PATCHES_ONLY: False + BATCH_SIZE_PER_GPU: 16 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "none" #"patch" # Options are none, patch and section + STRIDE: 50 + PATCH_SIZE: 100 + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: -6.0668 # -6.0668283 # The statistics are estimated from a filtered value that are less than 10000 as dataset contains outliers + STD: 1367.8212 # 1367.8212 + MAX: 10000 # The possible max is actually 32767 but the dataset contains outliers so limiting to 10000 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + COMPLETE_PATCHES_ONLY: False + +TEST: + COMPLETE_PATCHES_ONLY: False + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + AUGMENTATIONS: + RESIZE: + HEIGHT: 200 + WIDTH: 200 + PAD: + HEIGHT: 256 + WIDTH: 256 diff --git a/experiments/interpretation/penobscot/local/default.py b/experiments/interpretation/penobscot/local/default.py new file mode 100644 index 00000000..f4999678 --- /dev/null +++ b/experiments/interpretation/penobscot/local/default.py @@ -0,0 +1,115 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from yacs.config import CfgNode as CN + +_C = CN() + +_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models +_C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR +_C.GPUS = (0,) +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +# TODO: this should be loaded by automatically figuring out the file path location +_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" +_C.SEED = 42 + +# size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only +_C.WINDOW_SIZE = 65 + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "" +_C.DATASET.NUM_CLASSES = 7 +_C.DATASET.CLASS_WEIGHTS = [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] +_C.DATASET.INLINE_HEIGHT = 1501 +_C.DATASET.INLINE_WIDTH = 481 + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "patch_deconvnet" +_C.MODEL.IN_CHANNELS = 1 +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + + +# training +_C.TRAIN = CN() +_C.TRAIN.COMPLETE_PATCHES_ONLY = False +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR +_C.TRAIN.AUGMENTATION = True +_C.TRAIN.STRIDE = 50 +_C.TRAIN.PATCH_SIZE = 99 +_C.TRAIN.MEAN = [0.0009997] # 0.0009996710808862074 +_C.TRAIN.STD = [0.20977] # 0.20976548783479299 +_C.TRAIN.MAX = 255 +_C.TRAIN.DEPTH = 'no' # Options are None, Patch and Section +# None adds no depth information and the num of channels remains at 1 +# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 +# Section adds depth per section so contains depth information for the whole section, channels=3 +_C.TRAIN.AUGMENTATIONS = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 +_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TRAIN.AUGMENTATIONS.PAD = CN() +_C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 +_C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 + +# validation +_C.VALIDATION = CN() +_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 +_C.VALIDATION.COMPLETE_PATCHES_ONLY = False + +# TEST +_C.TEST = CN() +_C.TEST.MODEL_PATH = "" +_C.TEST.COMPLETE_PATCHES_ONLY = True +_C.TEST.AUGMENTATIONS = CN() +_C.TEST.AUGMENTATIONS.RESIZE = CN() +_C.TEST.AUGMENTATIONS.RESIZE.HEIGHT = 200 +_C.TEST.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TEST.AUGMENTATIONS.PAD = CN() +_C.TEST.AUGMENTATIONS.PAD.HEIGHT = 256 +_C.TEST.AUGMENTATIONS.PAD.WIDTH = 256 + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/experiments/interpretation/penobscot/local/test.py b/experiments/interpretation/penobscot/local/test.py new file mode 100644 index 00000000..8178809d --- /dev/null +++ b/experiments/interpretation/penobscot/local/test.py @@ -0,0 +1,386 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# /* spell-checker: disable */ + +import logging +import logging.config +from itertools import chain +from os import path + +import cv2 +import fire +import numpy as np +import torch +import torch.nn.functional as F +import torchvision +from albumentations import ( + CenterCrop, + Compose, + HorizontalFlip, + Normalize, + PadIfNeeded, + Resize, +) +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss, Metric +from ignite.utils import convert_tensor +from PIL import Image +from toolz import compose, pipe, tail, take +from toolz.sandbox.core import unzip +from torch.utils import data + +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) +from cv_lib.segmentation.dutchf3.metrics import ( + FrequencyWeightedIoU, + MeanClassAccuracy, + MeanIoU, + PixelwiseAccuracy, + _torch_hist, +) +from deepseismic_interpretation.penobscot.metrics import InlineMeanIoU +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) +from cv_lib.segmentation.penobscot.engine import ( + create_supervised_evaluator, + create_supervised_trainer, +) +from deepseismic_interpretation.dutchf3.data import decode_segmap +from deepseismic_interpretation.penobscot.data import ( + get_patch_dataset, +) +from default import _C as config +from default import update_config + + +def _prepare_batch(batch, device=None, non_blocking=False): + x, y, ids, patch_locations = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ids, + patch_locations, + ) + + +def _padding_from(config): + padding_height = ( + config.TEST.AUGMENTATIONS.PAD.HEIGHT + - config.TEST.AUGMENTATIONS.RESIZE.HEIGHT + ) + padding_width = ( + config.TEST.AUGMENTATIONS.PAD.WIDTH + - config.TEST.AUGMENTATIONS.RESIZE.WIDTH + ) + assert ( + padding_height == padding_width + ), "The padding for the height and width need to be the same" + return int(padding_height) + + +def _scale_from(config): + scale_height = ( + config.TEST.AUGMENTATIONS.PAD.HEIGHT / config.TRAIN.PATCH_SIZE + ) + scale_width = config.TEST.AUGMENTATIONS.PAD.WIDTH / config.TRAIN.PATCH_SIZE + assert ( + config.TEST.AUGMENTATIONS.PAD.HEIGHT % config.TRAIN.PATCH_SIZE != 0 + ), "The scaling between the patch height and resized height needs to be whole number" + assert ( + config.TEST.AUGMENTATIONS.PAD.WIDTH % config.TRAIN.PATCH_SIZE != 0 + ), "The scaling between the patch width and resized height needs to be whole number" + assert ( + scale_height == scale_width + ), "The scaling for the height and width need to be the same" + return int(scale_height) + + +_SEG_COLOURS = np.asarray( + [ + [241, 238, 246], + [208, 209, 230], + [166, 189, 219], + [116, 169, 207], + [54, 144, 192], + [5, 112, 176], + [3, 78, 123], + ] +) + + +def _log_tensor_to_tensorboard( + images_tensor, identifier, summary_writer, evaluator +): + image_grid = torchvision.utils.make_grid( + images_tensor, normalize=False, scale_each=False, nrow=2 + ) + summary_writer.add_image(identifier, image_grid, evaluator.state.epoch) + + +_TOP_K = 2 # Number of best performing inlines to log to tensorboard +_BOTTOM_K = 2 # Number of worst performing inlines to log to tensorboard +mask_value = 255 + + +def run(*options, cfg=None): + """Run testing of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + logger.debug(config.WORKERS) + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + + torch.manual_seed(config.SEED) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(config.SEED) + np.random.seed(seed=config.SEED) + + # Setup Augmentations + test_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=config.TRAIN.MAX, + ), + PadIfNeeded( + min_height=config.TRAIN.PATCH_SIZE, + min_width=config.TRAIN.PATCH_SIZE, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=mask_value, + value=0, + ), + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=mask_value, + value=0, + ), + ] + ) + + PenobscotDataset = get_patch_dataset(config) + + test_set = PenobscotDataset( + config.DATASET.ROOT, + config.TRAIN.PATCH_SIZE, + config.TRAIN.STRIDE, + split="test", + transforms=test_aug, + n_channels=config.MODEL.IN_CHANNELS, + complete_patches_only=config.TEST.COMPLETE_PATCHES_ONLY, + ) + + logger.info(str(test_set)) + n_classes = test_set.n_classes + + test_loader = data.DataLoader( + test_set, + batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + logger.info(f"Loading model {config.TEST.MODEL_PATH}") + model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model = model.to(device) # Send to GPU + + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + + # weights are inversely proportional to the frequency of the classes in the training set + class_weights = torch.tensor( + config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False + ) + + criterion = torch.nn.CrossEntropyLoss( + weight=class_weights, ignore_index=mask_value, reduction="mean" + ) + + def _select_pred_and_mask(model_out_dict): + return ( + model_out_dict["y_pred"].squeeze(), + model_out_dict["mask"].squeeze(), + ) + + def _select_all(model_out_dict): + return ( + model_out_dict["y_pred"].squeeze(), + model_out_dict["mask"].squeeze(), + model_out_dict["ids"], + model_out_dict["patch_locations"], + ) + + inline_mean_iou = InlineMeanIoU( + config.DATASET.INLINE_HEIGHT, + config.DATASET.INLINE_WIDTH, + config.TRAIN.PATCH_SIZE, + n_classes, + padding=_padding_from(config), + scale=_scale_from(config), + output_transform=_select_all, + ) + + evaluator = create_supervised_evaluator( + model, + _prepare_batch, + metrics={ + "IoU": MeanIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "nll": Loss(criterion, output_transform=_select_pred_and_mask), + "mca": MeanClassAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + "fiou": FrequencyWeightedIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "pixa": PixelwiseAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + "inIoU": inline_mean_iou, + }, + device=device, + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Test results", + metrics_dict={ + "IoU": "IoU :", + "nll": "Avg loss :", + "pixa": "Pixelwise Accuracy :", + "mca": "Mean Class Accuracy :", + "fiou": "Freq Weighted IoU :", + "inIoU": "Mean Inline IoU :", + }, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + evaluator, + "epoch", + metrics_dict={ + "IoU": "Test/IoU", + "nll": "Test/Loss", + "mca": "Test/MCA", + "fiou": "Test/FIoU", + "inIoU": "Test/MeanInlineIoU", + }, + ), + ) + + def _select_max(pred_tensor): + return pred_tensor.max(1)[1] + + def _tensor_to_numpy(pred_tensor): + return pred_tensor.squeeze().cpu().numpy() + + transform_func = compose( + np_to_tb, + decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), + _tensor_to_numpy, + ) + + transform_pred = compose(transform_func, _select_max) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Test/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, "Test/Mask", "mask", transform_func=transform_func + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Test/Pred", + "y_pred", + transform_func=transform_pred, + ), + ) + + logger.info("Starting training") + evaluator.run(take(10, test_loader), max_epochs=1) + + # Log top N and bottom N inlines in terms of IoU to tensorboard + inline_ious = inline_mean_iou.iou_per_inline() + sorted_ious = sorted(inline_ious.items(), key=lambda x: x[1], reverse=True) + topk = ( + (inline_mean_iou.predictions[key], inline_mean_iou.masks[key]) + for key, iou in take(_TOP_K, sorted_ious) + ) + bottomk = ( + (inline_mean_iou.predictions[key], inline_mean_iou.masks[key]) + for key, iou in tail(_BOTTOM_K, sorted_ious) + ) + stack_and_decode = compose(transform_func, torch.stack) + predictions, masks = unzip(chain(topk, bottomk)) + predictions_tensor = stack_and_decode(list(predictions)) + masks_tensor = stack_and_decode(list(masks)) + _log_tensor_to_tensorboard( + predictions_tensor, "Test/InlinePredictions", summary_writer, evaluator + ) + _log_tensor_to_tensorboard( + masks_tensor, "Test/InlineMasks", summary_writer, evaluator + ) + + summary_writer.close() + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/experiments/interpretation/penobscot/local/test.sh b/experiments/interpretation/penobscot/local/test.sh new file mode 100755 index 00000000..a497e127 --- /dev/null +++ b/experiments/interpretation/penobscot/local/test.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python test.py --cfg "configs/hrnet.yaml" \ No newline at end of file diff --git a/experiments/interpretation/penobscot/local/train.py b/experiments/interpretation/penobscot/local/train.py new file mode 100644 index 00000000..bf2eea22 --- /dev/null +++ b/experiments/interpretation/penobscot/local/train.py @@ -0,0 +1,366 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# /* spell-checker: disable */ + +import logging +import logging.config +from os import path + +import cv2 +import fire +import numpy as np +import torch +from albumentations import ( + Compose, + HorizontalFlip, + Normalize, + PadIfNeeded, + Resize, +) +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from toolz import compose +from torch.utils import data + +from deepseismic_interpretation.dutchf3.data import decode_segmap +from deepseismic_interpretation.penobscot.data import ( + get_patch_dataset, +) +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) +from cv_lib.segmentation.penobscot.engine import ( + create_supervised_evaluator, + create_supervised_trainer, +) +from cv_lib.segmentation.dutchf3.metrics import ( + FrequencyWeightedIoU, + MeanClassAccuracy, + MeanIoU, + PixelwiseAccuracy, +) +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) + +from default import _C as config +from default import update_config + + +mask_value = 255 +_SEG_COLOURS = np.asarray( + [ + [241, 238, 246], + [208, 209, 230], + [166, 189, 219], + [116, 169, 207], + [54, 144, 192], + [5, 112, 176], + [3, 78, 123], + ] +) + + +def _prepare_batch(batch, device=None, non_blocking=False): + x, y, ids, patch_locations = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ids, + patch_locations, + ) + + +def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + logger.debug(config.WORKERS) + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + + torch.manual_seed(config.SEED) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(config.SEED) + np.random.seed(seed=config.SEED) + + # Setup Augmentations + basic_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=config.TRAIN.MAX, + ), + PadIfNeeded( + min_height=config.TRAIN.PATCH_SIZE, + min_width=config.TRAIN.PATCH_SIZE, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=mask_value, + value=0, + ), + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=mask_value, + value=0, + ), + ] + ) + if config.TRAIN.AUGMENTATION: + train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)]) + val_aug = basic_aug + else: + train_aug = val_aug = basic_aug + + PenobscotDataset = get_patch_dataset(config) + + train_set = PenobscotDataset( + config.DATASET.ROOT, + config.TRAIN.PATCH_SIZE, + config.TRAIN.STRIDE, + split="train", + transforms=train_aug, + n_channels=config.MODEL.IN_CHANNELS, + complete_patches_only=config.TRAIN.COMPLETE_PATCHES_ONLY, + ) + + val_set = PenobscotDataset( + config.DATASET.ROOT, + config.TRAIN.PATCH_SIZE, + config.TRAIN.STRIDE, + split="val", + transforms=val_aug, + n_channels=config.MODEL.IN_CHANNELS, + complete_patches_only=config.VALIDATION.COMPLETE_PATCHES_ONLY, + ) + logger.info(train_set) + logger.info(val_set) + n_classes = train_set.n_classes + + train_loader = data.DataLoader( + train_set, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + shuffle=True, + ) + + val_loader = data.DataLoader( + val_set, + batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model = model.to(device) # Send to GPU + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + snapshot_duration = scheduler_step * len(train_loader) + scheduler = CosineAnnealingScheduler( + optimizer, + "lr", + config.TRAIN.MAX_LR, + config.TRAIN.MIN_LR, + snapshot_duration, + ) + + # weights are inversely proportional to the frequency of the classes in the training set + class_weights = torch.tensor( + config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False + ) + + criterion = torch.nn.CrossEntropyLoss( + weight=class_weights, ignore_index=mask_value, reduction="mean" + ) + + trainer = create_supervised_trainer( + model, optimizer, criterion, _prepare_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + ) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + def _select_pred_and_mask(model_out_dict): + return ( + model_out_dict["y_pred"].squeeze(), + model_out_dict["mask"].squeeze(), + ) + + evaluator = create_supervised_evaluator( + model, + _prepare_batch, + metrics={ + "IoU": MeanIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "nll": Loss(criterion, output_transform=_select_pred_and_mask), + "mca": MeanClassAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + "fiou": FrequencyWeightedIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "pixa": PixelwiseAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + }, + device=device, + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler( + Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={ + "IoU": "IoU :", + "nll": "Avg loss :", + "pixa": "Pixelwise Accuracy :", + "mca": "Mean Class Accuracy :", + "fiou": "Freq Weighted IoU :", + }, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={ + "IoU": "Validation/IoU", + "nll": "Validation/Loss", + "mca": "Validation/MCA", + "fiou": "Validation/FIoU", + }, + ), + ) + + def _select_max(pred_tensor): + return pred_tensor.max(1)[1] + + def _tensor_to_numpy(pred_tensor): + return pred_tensor.squeeze().cpu().numpy() + + transform_func = compose( + np_to_tb, + decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), + _tensor_to_numpy, + ) + + transform_pred = compose(transform_func, _select_max) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Mask", + "mask", + transform_func=transform_func, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, + "Validation/Pred", + "y_pred", + transform_func=transform_pred, + ), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + path.join(output_dir, config.TRAIN.MODEL_DIR), + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger.info("Starting training") + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/experiments/interpretation/penobscot/local/train.sh b/experiments/interpretation/penobscot/local/train.sh new file mode 100755 index 00000000..e826c10c --- /dev/null +++ b/experiments/interpretation/penobscot/local/train.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python train.py --cfg "configs/patch_deconvnet_skip.yaml" \ No newline at end of file diff --git a/experiments/interpretation/penobscot/prepare_data.py b/experiments/interpretation/penobscot/prepare_data.py new file mode 100644 index 00000000..103bbdca --- /dev/null +++ b/experiments/interpretation/penobscot/prepare_data.py @@ -0,0 +1,106 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 +# url: https://github.com/olivesgatech/facies_classification_benchmark +"""Script to generate train and validation sets for Netherlands F3 dataset +""" +import itertools +import logging +import logging.config +import math +import warnings +from os import path + +import fire +import numpy as np +from sklearn.model_selection import train_test_split +import os +import shutil +from toolz import partition_all +import glob + + +def _create_directory(dir_path, overwrite=False): + logger = logging.getLogger("__name__") + if overwrite: + logger.info(f"Set to overwrite. Removing {dir_path}") + shutil.rmtree(dir_path) + + try: + logger.info(f"Creating {dir_path}") + os.mkdir(dir_path) + return dir_path + except FileExistsError as e: + logger.warn( + f"Can't write to {dir_path} as it already exists. Please specify overwrite=true or delete folder" + ) + raise + + +def _copy_files(files_iter, new_dir): + logger = logging.getLogger("__name__") + for f in files_iter: + logger.debug(f"Copying {f} to {new_dir}") + shutil.copy(f, new_dir) + + +def _split_train_val_test(partition, val_ratio, test_ratio): + total_samples = len(partition) + val_samples = math.floor(val_ratio * total_samples) + test_samples = math.floor(test_ratio * total_samples) + train_samples = total_samples - (val_samples + test_samples) + train_list = partition[:train_samples] + val_list = partition[train_samples : train_samples + val_samples] + test_list = partition[ + train_samples + + val_samples : train_samples + + val_samples + + test_samples + ] + return train_list, val_list, test_list + + +def split_inline(data_dir, val_ratio, test_ratio, overwrite=False, exclude_files=None): + """Splits the inline data into train, val and test + + Args: + data_dir (str): path to directory that holds the data + val_ratio (float): the ratio of the partition that they should use for validation + test_ratio (float): the ratio of the partition that they should use for testing + exclude_files (list[str]): filenames to exclude from dataset, such as ones that contain artifacts. Example:['image1.tiff'] + """ + num_partitions = 5 + image_dir = os.path.join(data_dir, "inlines") + dir_paths = ( + os.path.join(image_dir, ddir) for ddir in ("train", "val", "test") + ) + locations_list = [ + _create_directory(d, overwrite=overwrite) for d in dir_paths + ] # train, val, test + + images_iter = glob.iglob(os.path.join(image_dir, "*.tiff")) + + if exclude_files is not None: + images_list = list(filterfalse( + lambda x: x in exclude_files, images_iter + )) + else: + images_list = list(images_iter) + + num_elements = math.ceil(len(images_list) / num_partitions) + for partition in partition_all( + num_elements, images_list + ): # Partition files into N partitions + for files_list, dest_dir in zip( + _split_train_val_test(partition, val_ratio, test_ratio), + locations_list, + ): + _copy_files(files_list, dest_dir) + + +if __name__ == "__main__": + """Example: + python prepare_data.py split_inline --data-dir=/mnt/penobscot --val-ratio=.1 --test-ratio=.2 + + """ + fire.Fire({"split_inline": split_inline}) diff --git a/images/penobscot_seresnet_best.png b/images/penobscot_seresnet_best.png new file mode 100644 index 0000000000000000000000000000000000000000..4f8f3beb7aaca24e7b117d1642b4a297671928fb GIT binary patch literal 49429 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

sTD1xA!7>I}$7l;@W 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 TTKN%L;BUuP?=4*Spya;;-18bF literal 0 HcmV?d00001 diff --git a/images/penobscot_seresnet_worst.png b/images/penobscot_seresnet_worst.png new file mode 100644 index 0000000000000000000000000000000000000000..369ec4e3c67a70e425004a72b7ba95d03b391d02 GIT binary patch literal 50846 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�%09K(Cu7i}@K{eQ#ryc5w2Qb_uO9vT#}Lw=ERo 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 <=0sJYfUMK__%#O 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 3, np.unique(mask_array)): + mask_array[mask_array == i] = i - 1 + return mask_array + + +def _combine_classes(mask_array_list): + """Combine classes + + Segmentation implementations using this dataset seem to combine + classes 2 and 3 so we are doing the same here and then relabeling the rest + + Args: + mask_array_list (list): list of mask (numpy.Array) + """ + return [_combine(mask_array.copy()) for mask_array in mask_array_list] + + +def _replicate_channels(image_array, n_channels): + new_image_array = np.zeros( + (n_channels, image_array.shape[0], image_array.shape[1]) + ) + for i in range(n_channels): + new_image_array[i] = image_array + return new_image_array + + +def _extract_filename(filepath): + return os.path.splitext(os.path.split(filepath)[-1].strip())[ + 0 + ] # extract filename without extension + + +def _generate_images_and_masks(images_iter, mask_dir): + for image_file in images_iter: + file_part = _extract_filename(image_file) + mask_file = os.path.join(mask_dir, file_part + "_mask.png") + if os.path.exists(mask_file): + yield image_file, mask_file + else: + raise FileNotFoundError( + f"Could not find mask {mask_file} corresponding to {image_file}" + ) + + +def _number_patches_in( + height_or_width, patch_size, stride, complete_patches_only=True +): + strides_in_hw = (height_or_width - patch_size) / stride + if complete_patches_only: + return int(np.floor(strides_in_hw)) + else: + return int(np.ceil(strides_in_hw)) + + +def _is_2D(numpy_array): + return len(numpy_array.shape) == 2 + + +def _is_3D(numpy_array): + return len(numpy_array.shape) == 3 + + +@curry +def _extract_patches( + patch_size, stride, complete_patches_only, img_array, mask_array +): + height, width = img_array.shape[-2], img_array.shape[-1] + num_h_patches = _number_patches_in( + height, patch_size, stride, complete_patches_only=complete_patches_only + ) + num_w_patches = _number_patches_in( + width, patch_size, stride, complete_patches_only=complete_patches_only + ) + height_iter = range(0, stride * (num_h_patches + 1), stride) + width_iter = range(0, stride * (num_w_patches + 1), stride) + patch_locations = list(itertools.product(height_iter, width_iter)) + + image_patch_generator = _generate_patches_for( + img_array, patch_locations, patch_size + ) + mask_patch_generator = _generate_patches_for( + mask_array, patch_locations, patch_size + ) + return image_patch_generator, mask_patch_generator, patch_locations + + +def _generate_patches_for(numpy_array, patch_locations, patch_size): + if _is_2D(numpy_array): + generate = _generate_patches_from_2D + elif _is_3D(numpy_array): + generate = _generate_patches_from_3D + else: + raise ValueError("Array is not 2D or 3D") + return generate(numpy_array, patch_locations, patch_size) + + +def _generate_patches_from_2D(numpy_array, patch_locations, patch_size): + return ( + numpy_array[h : h + patch_size, w : w + patch_size].copy() + for h, w in patch_locations + ) + + +def _generate_patches_from_3D(numpy_array, patch_locations, patch_size): + return ( + numpy_array[:, h : h + patch_size, w : w + patch_size].copy() + for h, w in patch_locations + ) + + +@curry +def _filter_files(exclude_files, images_iter): + if exclude_files is not None: + images_iter = filterfalse(lambda x: x in exclude_files, images_iter) + + return images_iter + + +@curry +def _limit_inlines(max_inlines, images_iter): + if max_inlines is not None: + images_list = list(images_iter) + if max_inlines > len(images_list): + warn_msg = ( + f"The number of max inlines {max_inlines} is greater" + f"than the number of inlines found {len(images_list)}." + f"Setting max inlines to {len(images_list)}" + ) + warnings.warning(warn_msg) + max_inlines = len(images_list) + images_iter = images_list + else: + shuffled_list = random.shuffle(images_list) + images_iter = take(max_inlines, shuffled_list) + return images_iter, max_inlines + + +_STATS_FUNCS = {"mean": np.mean, "std": np.std, "max": np.max} + + +def _transform_CHW_to_HWC(numpy_array): + return np.moveaxis(numpy_array, 0, -1) + + +def _transform_HWC_to_CHW(numpy_array): + return np.moveaxis(numpy_array, -1, 0) + + +def _rescale(numpy_array): + """ Rescale the numpy array by 10000. The maximum value achievable is 32737 + This will bring the values between -n and n + """ + return numpy_array / 10000 + + +class PenobscotInlinePatchDataset(VisionDataset): + """Dataset that returns patches from Penobscot dataset + + Notes: + Loads inlines only and splits into patches + """ + + _open_image = compose(_rescale, _open_to_array) + _open_mask = _open_to_array + + def __init__( + self, + root, + patch_size, + stride, + split="train", + transforms=None, + exclude_files=None, + max_inlines=None, + n_channels=1, + complete_patches_only=True, + ): + """Initialise Penobscot Dataset + + Args: + root (str): root directory to load data from + patch_size (int): the size of the patch in pixels + stride (int): the stride applied when extracting patches + split (str, optional): what split to load, (train, val, test). Defaults to `train` + transforms (albumentations.augmentations.transforms, optional): albumentation transforms to apply to patches. Defaults to None + exclude_files (list[str], optional): list of files to exclude. Defaults to None + max_inlines (int, optional): maximum number of inlines to load. Defaults to None + n_channels (int, optional): number of channels that the output should contain. Defaults to 3 + complete_patches_only (bool, optional): whether to load incomplete patches that are padded to patch_size. Defaults to True + """ + + super(PenobscotInlinePatchDataset, self).__init__( + root, transforms=transforms + ) + self._image_dir = os.path.join(self.root, "inlines", split) + self._mask_dir = os.path.join(self.root, "masks") + self._split = split + self._exclude_files = exclude_files + self._max_inlines = max_inlines + self._n_channels = n_channels + self._complete_patches_only = complete_patches_only + self._patch_size = patch_size + self._stride = stride + self._image_array = [] + self._mask_array = [] + self._file_ids = [] + self._patch_locations = [] + + valid_modes = ("train", "test", "val") + msg = ( + "Unknown value '{}' for argument split. " + "Valid values are {{{}}}." + ) + msg = msg.format(split, iterable_to_str(valid_modes)) + verify_str_arg(split, "split", valid_modes, msg) + + if not os.path.exists(self._image_dir): + raise DataNotSplitException( + "The dataset has not been appropriately split into train, val and test" + ) + + # Get the number of inlines that make up dataset + images_iter, self._max_inlines = pipe( + os.path.join(self._image_dir, "*.tiff"), + glob.iglob, + _filter_files(self._exclude_files), + _limit_inlines(self._max_inlines), + ) + + # Set the patch and stride for the patch extractor + _extract_patches_from = _extract_patches( + patch_size, stride, self._complete_patches_only + ) + + # Extract patches + for image_path, mask_path in _generate_images_and_masks( + images_iter, self._mask_dir + ): + img_array = self._open_image(image_path) + mask_array = self._open_mask(mask_path) + self._file_ids.append(_extract_filename(image_path)) + image_generator, mask_generator, patch_locations = _extract_patches_from( + img_array, mask_array + ) + self._patch_locations.extend(patch_locations) + + self._image_array.extend(image_generator) + + self._mask_array.extend(mask_generator) + + assert len(self._image_array) == len( + self._patch_locations + ), "The shape is not the same" + + assert ( + len(self._patch_locations) % len(self._file_ids) == 0 + ), "Something is wrong with the patches" + + self._patches_per_image = int( + len(self._patch_locations) / len(self._file_ids) + ) + + # Combine classes 2 and 3 + self._mask_array = _combine_classes(self._mask_array) + + self._classes, self._class_counts = _get_classes_and_counts( + self._mask_array + ) + + def __len__(self): + return len(self._image_array) + + @property + def n_classes(self): + return len(self._classes) + + @property + def class_proportions(self): + total = np.sum(self._class_counts) + return [ + (i, w / total) for i, w in zip(self._classes, self._class_counts) + ] + + def _add_extra_channels(self, image): + if self._n_channels > 1: + image = _replicate_channels(image, self._n_channels) + return image + + def __getitem__(self, index): + image, target, file_ids, patch_locations = ( + self._image_array[index], + self._mask_array[index], + self._file_ids[index // self._patches_per_image], + self._patch_locations[index], + ) + + image = self._add_extra_channels(image) + if _is_2D(image): + image = np.expand_dims(image, 0) + + if self.transforms is not None: + image = _transform_CHW_to_HWC(image) + augmented_dict = self.transforms(image=image, mask=target) + image, target = augmented_dict["image"], augmented_dict["mask"] + image = _transform_HWC_to_CHW(image) + + target = np.expand_dims(target, 0) + + return ( + torch.from_numpy(image).float(), + torch.from_numpy(target).long(), + file_ids, + np.array(patch_locations), + ) + + @property + def statistics(self): + flat_image_array = np.concatenate( + [i.flatten() for i in self._image_array] + ) + stats = { + stat: statfunc(flat_image_array) + for stat, statfunc in _STATS_FUNCS.items() + } + return "Mean: {mean} Std: {std} Max: {max}".format(**stats) + + def extra_repr(self): + lines = [ + "Split: {_split}", + "Image Dir: {_image_dir}", + "Mask Dir: {_mask_dir}", + "Exclude files: {_exclude_files}", + "Patch size: {_patch_size}", + "Stride: {_stride}", + "Max inlines: {_max_inlines}", + "Num channels: {_n_channels}", + f"Num classes: {self.n_classes}", + f"Class proportions: {self.class_proportions}", + "Complete patches only: {_complete_patches_only}", + f"Dataset statistics: {self.statistics}", + ] + return "\n".join(lines).format(**self.__dict__) + + +def add_depth_channels(image_array): + """Add 2 extra channels to a 1 channel numpy array + One channel is a linear sequence from 0 to 1 starting from the top of the image to the bottom + The second channel is the product of the input channel and the 'depth' channel + + Args: + image_array (numpy.Array): 2D Numpy array + + Returns: + [np.array]: 3D numpy array + """ + h, w = image_array.shape + image = np.zeros([3, h, w]) + image[0] = image_array + for row, const in enumerate(np.linspace(0, 1, h)): + image[1, row, :] = const + image[2] = image[0] * image[1] + return image + + +class PenobscotInlinePatchSectionDepthDataset(PenobscotInlinePatchDataset): + """Dataset that returns patches from Penobscot dataset augmented with Section depth + + Notes: + Loads inlines only and splits into patches + The patches are augmented with section depth + """ + + _open_image = compose(add_depth_channels, _rescale, _open_to_array) + _open_mask = _open_to_array + + def __init__( + self, + root, + patch_size, + stride, + split="train", + transforms=None, + exclude_files=None, + max_inlines=None, + n_channels=3, + complete_patches_only=True, + ): + """Initialise Penobscot Dataset + + Args: + root (str): root directory to load data from + patch_size (int): the size of the patch in pixels + stride (int): the stride applied when extracting patches + split (str, optional): what split to load, (train, val, test). Defaults to `train` + transforms (albumentations.augmentations.transforms, optional): albumentation transforms to apply to patches. Defaults to None + exclude_files (list[str], optional): list of files to exclude. Defaults to None + max_inlines (int, optional): maximum number of inlines to load. Defaults to None + n_channels (int, optional): number of channels that the output should contain. Defaults to 3 + complete_patches_only (bool, optional): whether to load incomplete patches that are padded to patch_size. Defaults to True + """ + + assert ( + n_channels == 3 + ), f"For the Section Depth based dataset the number of channels can only be 3. Currently n_channels={n_channels}" + super(PenobscotInlinePatchSectionDepthDataset, self).__init__( + root, + patch_size, + stride, + split=split, + transforms=transforms, + exclude_files=exclude_files, + max_inlines=max_inlines, + n_channels=n_channels, + complete_patches_only=complete_patches_only, + ) + + def _add_extra_channels(self, image): + return image + + +class PenobscotInlinePatchDepthDataset(PenobscotInlinePatchDataset): + """Dataset that returns patches from Penobscot dataset augmented with patch depth + + Notes: + Loads inlines only and splits into patches + The patches are augmented with patch depth + """ + + _open_image = compose(_rescale, _open_to_array) + _open_mask = _open_to_array + + def __init__( + self, + root, + patch_size, + stride, + split="train", + transforms=None, + exclude_files=None, + max_inlines=None, + n_channels=3, + complete_patches_only=True, + ): + """Initialise Penobscot Dataset + + Args: + root (str): root directory to load data from + patch_size (int): the size of the patch in pixels + stride (int): the stride applied when extracting patches + split (str, optional): what split to load, (train, val, test). Defaults to `train` + transforms (albumentations.augmentations.transforms, optional): albumentation transforms to apply to patches. Defaults to None + exclude_files (list[str], optional): list of files to exclude. Defaults to None + max_inlines (int, optional): maximum number of inlines to load. Defaults to None + n_channels (int, optional): number of channels that the output should contain. Defaults to 3 + complete_patches_only (bool, optional): whether to load incomplete patches that are padded to patch_size. Defaults to True + """ + assert ( + n_channels == 3 + ), f"For the Patch Depth based dataset the number of channels can only be 3. Currently n_channels={n_channels}" + super(PenobscotInlinePatchDepthDataset, self).__init__( + root, + patch_size, + stride, + split=split, + transforms=transforms, + exclude_files=exclude_files, + max_inlines=max_inlines, + n_channels=n_channels, + complete_patches_only=complete_patches_only, + ) + + def _add_extra_channels(self, image): + return add_depth_channels(image) + + +_TRAIN_PATCH_DATASETS = { + "section": PenobscotInlinePatchSectionDepthDataset, + "patch": PenobscotInlinePatchDepthDataset, +} + + +def get_patch_dataset(cfg): + """ Return the Dataset class for Penobscot + + Args: + cfg: yacs config + + Returns: + PenobscotInlinePatchDataset + """ + assert str(cfg.TRAIN.DEPTH).lower() in [ + "section", + "patch", + "none", + ], f"Depth {cfg.TRAIN.DEPTH} not supported for patch data. \ + Valid values: section, patch, none." + return _TRAIN_PATCH_DATASETS.get( + cfg.TRAIN.DEPTH, PenobscotInlinePatchDataset + ) + + +if __name__ == "__main__": + dataset = PenobscotInlinePatchDataset( + "/mnt/penobscot", 100, 50, split="train" + ) + print(len(dataset)) diff --git a/interpretation/deepseismic_interpretation/penobscot/metrics.py b/interpretation/deepseismic_interpretation/penobscot/metrics.py new file mode 100644 index 00000000..28502d00 --- /dev/null +++ b/interpretation/deepseismic_interpretation/penobscot/metrics.py @@ -0,0 +1,130 @@ +from collections import defaultdict +from cv_lib.segmentation.dutchf3.metrics import _torch_hist +from ignite.metrics import Metric +import torch +import numpy as np + + +def _default_tensor(image_height, image_width, pad_value=255): + return torch.full((image_height, image_width), pad_value, dtype=torch.long) + + +# TODO: make output transform unpad and scale down mask +# scale up y_pred and remove padding +class InlineMeanIoU(Metric): + """Compute Mean IoU for Inline + + Notes: + This metric collects all the patches and recomposes the predictions and masks into inlines + These are then used to calculate the mean IoU + """ + + def __init__( + self, + image_height, + image_width, + patch_size, + num_classes, + padding=0, + scale=1, + pad_value=255, + output_transform=lambda x: x, + ): + """Create instance of InlineMeanIoU + + Args: + image_height (int): height of inline + image_width (int): width of inline + patch_size (int): patch size + num_classes (int): number of classes in dataset + padding (int, optional): the amount of padding to height and width, e.g 200 padded to 256 - padding=56. Defaults to 0 + scale (int, optional): the scale factor applied to the patch, e.g 100 scaled to 200 - scale=2. Defaults to 1 + pad_value (int): the constant value used for padding Defaults to 255 + output_transform (callable, optional): a callable that is used to transform the ignite.engine.Engine's `process_function`'s output into the + form expected by the metric. This can be useful if, for example, you have a multi-output model and you want to compute the metric with respect to one of the outputs. + """ + self._image_height = image_height + self._image_width = image_width + self._patch_size = patch_size + self._pad_value = pad_value + self._num_classes = num_classes + self._scale = scale + self._padding = padding + super(InlineMeanIoU, self).__init__(output_transform=output_transform) + + def reset(self): + self._pred_dict = defaultdict( + lambda: _default_tensor( + self._image_height * self._scale, + self._image_width * self._scale, + pad_value=self._pad_value, + ) + ) + self._mask_dict = defaultdict( + lambda: _default_tensor( + self._image_height * self._scale, + self._image_width * self._scale, + pad_value=self._pad_value, + ) + ) + + def update(self, output): + y_pred, y, ids, patch_locations = output + # TODO: Make assertion exception + max_prediction = y_pred.max(1)[1].squeeze() + assert y.shape == max_prediction.shape, "Shape not the same" + + for pred, mask, id, patch_loc in zip( + max_prediction, y, ids, patch_locations + ): + # ! With overlapping patches this does not aggregate the results it simply overwrites them + # If patch is padded ingore padding + pad = int(self._padding // 2) + pred = pred[pad : pred.shape[0] - pad, pad : pred.shape[1] - pad] + mask = mask[pad : mask.shape[0] - pad, pad : mask.shape[1] - pad] + + # Get the ares of the mask that is not padded + # Determine the left top edge and bottom right edge + # Use this to calculate the rectangular area that contains predictions + non_padded_mask = torch.nonzero((mask - self._pad_value).abs()) + y_start, x_start = non_padded_mask.min(0)[0] + y_end, x_end = non_padded_mask.max(0)[0] + height = (y_end + 1) - y_start + width = (x_end + 1) - x_start + + self._pred_dict[id][ + patch_loc[0] * 2 : patch_loc[0] * 2 + height, + patch_loc[1] * 2 : patch_loc[1] * 2 + width, + ] = pred[y_start : y_end + 1, x_start : x_end + 1] + + self._mask_dict[id][ + patch_loc[0] * 2 : patch_loc[0] * 2 + height, + patch_loc[1] * 2 : patch_loc[1] * 2 + width, + ] = mask[y_start : y_end + 1, x_start : x_end + 1] + + def iou_per_inline(self): + iou_per_inline = {} + for id in self._pred_dict: + confusion_matrix = _torch_hist( + torch.flatten(self._mask_dict[id]), + torch.flatten(self._pred_dict[id]), # Get the maximum index + self._num_classes, + ) + hist = confusion_matrix.cpu().numpy() + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + ) + iou_per_inline[id] = np.nanmean(iu) + return iou_per_inline + + @property + def predictions(self): + return self._pred_dict + + @property + def masks(self): + return self._mask_dict + + def compute(self): + iou_dict = self.iou_per_inline() + return np.mean(list(iou_dict.values())) diff --git a/scripts/download_penobscot.sh b/scripts/download_penobscot.sh new file mode 100755 index 00000000..290c7dad --- /dev/null +++ b/scripts/download_penobscot.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# commitHash: +# url: https://zenodo.org/record/1341774 +# +# Download the penobscot dataset and extract +# Files description +# File Format Num Files Total size (MB) +# H1-H7 XYZ 7 87.5 +# Seismic inlines TIF 601 1,700 +# Seismic crosslines TIF 481 1,700 +# Labeled inlines PNG 601 4.9 +# Labeled crosslines PNG 481 3.9 +# Seismic tiles (train) PNG 75,810 116 +# Seismic labels (train) JSON 2 1.5 +# Seismic tiles (test) PNG 28,000 116 +# Seismic labels (test) JSON 2 0.5 +# Args: directory to download and extract data to +# Example: ./download_penobscot.sh /mnt/penobscot + + +echo Extracting to $1 +cd $1 +# Download the files: +wget https://zenodo.org/record/1341774/files/crosslines.zip +wget https://zenodo.org/record/1341774/files/inlines.zip +wget https://zenodo.org/record/1341774/files/horizons.zip +wget https://zenodo.org/record/1341774/files/masks.zip +wget https://zenodo.org/record/1341774/files/tiles_crosslines.zip +wget https://zenodo.org/record/1341774/files/tiles_inlines.zip + +# Check that the md5 checksum matches to varify file integrity +# +# Expected output: +# MD5(crosslines.zip)= 7bbe432052fe41c6009d9437fd0929b8 +# MD5(horizons.zip)= 42c104fafbb8e79695ae23527a91ee78 +# MD5(inlines.zip)= 0553676ef48879f590378cafc12d165d +# MD5(masks.zip)= 12f142cb33af55c3b447401ebd81aba1 +# MD5(tiles_crosslines.zip)= 8dbd99da742ac9c6f9b63f8c6f925f6d +# MD5(tiles_inlines.zip)= 955e2f9afb01878df2f71f0074736e42 + +openssl dgst -md5 crosslines.zip +openssl dgst -md5 horizons.zip +openssl dgst -md5 inlines.zip +openssl dgst -md5 masks.zip +openssl dgst -md5 tiles_crosslines.zip +openssl dgst -md5 tiles_inlines.zip + +# Unzip the data +unzip crosslines.zip +unzip inlines.zip +unzip horizons.zip +unzip masks.zip +unzip tiles_crosslines.zip +unzip tiles_inlines.zip \ No newline at end of file diff --git a/scripts/kill_windows.sh b/scripts/kill_windows.sh new file mode 100755 index 00000000..4a5caaa6 --- /dev/null +++ b/scripts/kill_windows.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# Script to kill multiple tmux windows + +tmux killw -t hrnet +tmux killw -t hrnet_section_depth +tmux killw -t hrnet_patch_depth + +tmux killw -t seresnet_unet +tmux killw -t seresnet_unet_section_depth +tmux killw -t seresnet_unet_patch_depth \ No newline at end of file diff --git a/scripts/parallel_training.sh b/scripts/parallel_training.sh new file mode 100755 index 00000000..afc255ce --- /dev/null +++ b/scripts/parallel_training.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# Script to run multiple models in parallel on multi-gpu machine + +workspace=../experiments/segmentation/penobscot/local +tmux neww -d -n hrnet +tmux neww -d -n hrnet_section_depth +tmux neww -d -n hrnet_patch_depth +tmux neww -d -n seresnet_unet +tmux neww -d -n seresnet_unet_section_depth +tmux neww -d -n seresnet_unet_patch_depth + +tmux send -t hrnet "source activate seismic-interpretation && cd ${workspace}" ENTER +tmux send -t hrnet "CUDA_VISIBLE_DEVICES=0 python train.py OUTPUT_DIR /data/output/hrnet --cfg 'configs/hrnet.yaml'" ENTER + +tmux send -t hrnet_patch_depth "source activate seismic-interpretation && cd ${workspace}" ENTER +tmux send -t hrnet_patch_depth "CUDA_VISIBLE_DEVICES=1 python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch --cfg 'configs/hrnet.yaml'" ENTER + +tmux send -t hrnet_section_depth "source activate seismic-interpretation && cd ${workspace}" ENTER +tmux send -t hrnet_section_depth "CUDA_VISIBLE_DEVICES=2 python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section --cfg 'configs/hrnet.yaml'" ENTER + +tmux send -t seresnet_unet "source activate seismic-interpretation && cd ${workspace}" ENTER +tmux send -t seresnet_unet "CUDA_VISIBLE_DEVICES=3 python train.py OUTPUT_DIR /data/output/seresnet --cfg 'configs/seresnet_unet.yaml'" ENTER + +tmux send -t seresnet_unet_patch_depth "source activate seismic-interpretation && cd ${workspace}" ENTER +tmux send -t seresnet_unet_patch_depth "CUDA_VISIBLE_DEVICES=4 python train.py OUTPUT_DIR /data/output/seresnet_patch TRAIN.DEPTH patch --cfg 'configs/seresnet_unet.yaml'" ENTER + +tmux send -t seresnet_unet_section_depth "source activate seismic-interpretation && cd ${workspace}" ENTER +tmux send -t seresnet_unet_section_depth "CUDA_VISIBLE_DEVICES=5 python train.py OUTPUT_DIR /data/output/seresnet_section TRAIN.DEPTH section --cfg 'configs/seresnet_unet.yaml'" ENTER \ No newline at end of file From 4a87ccb50f74ae1717266bd9f302c09ffbf5a7d6 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Tue, 8 Oct 2019 13:44:24 +0000 Subject: [PATCH 059/207] Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 --- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 636 ++++++++++ ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1055 +++++++++++++++++ ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 914 ++++++++++++++ ...zureml_buildexperimentationdockerimage.log | 201 ++++ 4 files changed, 2806 insertions(+) create mode 100755 examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb create mode 100755 examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb create mode 100755 examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb create mode 100644 examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log diff --git a/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb new file mode 100755 index 00000000..3e4f397b --- /dev/null +++ b/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -0,0 +1,636 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# FWI in Azure project\n", + "\n", + "## Set-up AzureML resources\n", + "\n", + "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", + "\n", + "\n", + "\n", + "In this notebook we setup AzureML resources. This notebook should be run once and will enable all subsequent notebooks.\n", + "\n", + "\n", + "User input requiring steps:\n", + " - [Fill in and save sensitive information](#dot_env_description)\n", + " - [Azure login](#Azure_login) (may be required first time the notebook is run) \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Allow multiple displays per cell\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\" " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Azure Machine Learning and Pipeline SDK-specific imports" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "import shutil\n", + "import urllib\n", + "import azureml.core\n", + "from azureml.core import Workspace, Experiment\n", + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "import platform" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Azure ML SDK Version: 1.0.65\n" + ] + }, + { + "data": { + "text/plain": [ + "'Linux-4.15.0-1060-azure-x86_64-with-debian-10.0'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/workspace/examples/imaging/azureml_devito/notebooks'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", + "platform.platform()\n", + "os.getcwd()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 1. Create utilities file\n", + "\n", + "##### 1.1 Define utilities file (project_utils.py) path\n", + "Utilities file created here has code for Azure resources access authorization, project configuration settings like directories and file names in __project_consts__ class." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "utils_file_name = 'project_utils'\n", + "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", + "\n", + "\n", + "utils_path_name = os.path.join(os.getcwd(), auxiliary_files_dir)\n", + "utils_full_name = os.path.join(utils_path_name, os.path.join(*([utils_file_name+'.py'])))\n", + "os.makedirs(utils_path_name, exist_ok=True)\n", + " \n", + "def ls_l(a_dir):\n", + " return ([f for f in os.listdir(a_dir) if os.path.isfile(os.path.join(a_dir, f))]) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 1.2. Edit/create project_utils.py file" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./src/project_utils.py\n" + ] + } + ], + "source": [ + "%%writefile $utils_full_name\n", + "\n", + "from azureml.core.authentication import ServicePrincipalAuthentication\n", + "from azureml.core.authentication import AzureCliAuthentication\n", + "from azureml.core.authentication import InteractiveLoginAuthentication\n", + "from azureml.core.authentication import AuthenticationException\n", + "import dotenv, logging, pathlib, os\n", + "\n", + "\n", + "# credit Mathew Salvaris\n", + "def get_auth(env_path):\n", + " \"\"\"Tries to get authorization info by first trying to get Service Principal info, then CLI, then interactive. \n", + " \"\"\"\n", + " logger = logging.getLogger(__name__)\n", + " crt_sp_pwd = os.environ.get(\"SP_PASSWORD\", None)\n", + " if crt_sp_pwd:\n", + " logger.debug(\"Trying to create Workspace with Service Principal\")\n", + " aml_sp_password = crt_sp_pwd\n", + " aml_sp_tennant_id = dotenv.get_key(env_path, 'SP_TENANT_ID')\n", + " aml_sp_username = dotenv.get_key(env_path, 'SP_APPLICATION_ID')\n", + " auth = ServicePrincipalAuthentication(\n", + " tenant_id=aml_sp_tennant_id,\n", + " username=aml_sp_username,\n", + " password=aml_sp_password,\n", + " )\n", + " else:\n", + " logger.debug(\"Trying to create Workspace with CLI Authentication\")\n", + " try:\n", + " auth = AzureCliAuthentication()\n", + " auth.get_authentication_header()\n", + " except AuthenticationException:\n", + " logger.debug(\"Trying to create Workspace with Interactive login\")\n", + " auth = InteractiveLoginAuthentication()\n", + "\n", + " return auth \n", + "\n", + "\n", + "def set_dotenv_info(dotenv_file_path, env_dict):\n", + " \"\"\"Use dict loop to set multiple keys in dotenv file.\n", + " Minimal file error management.\n", + " \"\"\"\n", + " logger = logging.getLogger(__name__)\n", + " if bool(env_dict):\n", + " dotenv_file = pathlib.Path(dotenv_file_path)\n", + " if not dotenv_file.is_file():\n", + " logger.debug('dotenv file not found, will create \"{}\" using the sensitive info you provided.'.format(dotenv_file_path))\n", + " dotenv_file.touch()\n", + " else:\n", + " logger.debug('dotenv file \"{}\" found, will (over)write it with current sensitive info you provided.'.format(dotenv_file_path))\n", + " \n", + " for crt_key, crt_val in env_dict.items():\n", + " dotenv.set_key(dotenv_file_path, crt_key, crt_val)\n", + "\n", + " else:\n", + " logger.debug(\\\n", + " 'Trying to save empty env_dict variable into {}, please set your sensitive info in a dictionary.'\\\n", + " .format(dotenv_file_path)) \n", + " \n", + "\n", + "class project_consts(object):\n", + " \"\"\"Keep project's file names and directory structure in one place.\n", + " Minimal setattr error management.\n", + " \"\"\"\n", + " \n", + " AML_WORKSPACE_CONFIG_DIR = ['.', '..', 'not_shared']\n", + " AML_EXPERIMENT_DIR = ['.', '..', 'temp']\n", + " AML_WORKSPACE_CONFIG_FILE_NAME = 'aml_ws_config.json'\n", + " DOTENV_FILE_PATH = AML_WORKSPACE_CONFIG_DIR + ['general.env'] \n", + " DOCKER_DOTENV_FILE_PATH = AML_WORKSPACE_CONFIG_DIR + ['dockerhub.env'] \n", + "\n", + " def __setattr__(self, *_):\n", + " raise TypeError\n", + "\n", + " \n", + "if __name__==\"__main__\":\n", + " \"\"\"Basic function/class tests.\n", + " \"\"\"\n", + " import sys, os\n", + " prj_consts = project_consts()\n", + " logger = logging.getLogger(__name__)\n", + " logging.basicConfig(level=logging.DEBUG) # Logging Levels: DEBUG\t10, NOTSET\t0\n", + " logger.debug('AML ws file = {}'.format(os.path.join(*([os.path.join(*(prj_consts.AML_WORKSPACE_CONFIG_DIR)),\n", + " prj_consts.AML_WORKSPACE_CONFIG_FILE_NAME]))))\n", + "\n", + " crt_dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", + " set_dotenv_info(crt_dotenv_file_path, {})\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 1.3. Import utilities functions defined above" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_path_to_sys_path(path_to_append):\n", + " if not (any(path_to_append in paths for paths in sys.path)):\n", + " sys.path.append(path_to_append)\n", + " \n", + "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", + "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", + "\n", + "\n", + "import project_utils\n", + "prj_consts = project_utils.project_consts()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2. Set-up the AML SDK infrastructure\n", + "\n", + "* Create Azure resource group (rsg), workspaces, \n", + "* save sensitive info using [python-dotenv](https://github.com/theskumar/python-dotenv) \n", + " \n", + "Notebook repeateability notes:\n", + "* The notebook tries to find and use an existing Azure resource group (rsg) defined by __crt_resource_group__. It creates a new one if needed. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "sensitive_info = {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "##### 2.1. Input here sensitive and configuration information\n", + "[dotenv](https://github.com/theskumar/python-dotenv) is used to hide sensitive info, like Azure subscription name/ID. The serialized info needs to be manually input once. \n", + " \n", + "* REQUIRED ACTION for the 2 cells below: uncomment them, add the required info in first cell below, run both cells one. \n", + " The sensitive information will be packed in __sensitive_info__ dictionary variable, which that will then be saved in a following cell in an .env file (__dotenv_file_path__) that should likely be git ignored. \n", + "\n", + "* OPTIONAL STEP: After running once the two cells below to save __sensitive_info__ dictionary variable with your custom info, you can comment them and leave the __sensitive_info__ variable defined above as an empty python dictionary. \n", + " \n", + " \n", + "__Notes__:\n", + "* An empty __sensitive_info__ dictionary is ignored by the __set_dotenv_info__ function defined above in project_utils.py . \n", + "* The saved .env file will be used thereafter in each cell that starts with %dotenv. \n", + "* The saved .env file contains user specific information and it shoulld __not__ be version-controlled in git.\n", + "* If you would like to [use service principal authentication](https://github.com/Azure/MachineLearningNotebooks/blob/master/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azure-ml.ipynb) make sure you provide the optional values as well (see get_auth function definition in project_utils.py file created above for details).\n", + "\n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# subscription_id = \"\"\n", + "# resource_group = \"ghiordanfwirsg01\"\n", + "# workspace_name = \"ghiordanfwiws\"\n", + "# workspace_region = \"eastus2\"\n", + "# gpu_cluster_name = \"gpuclstfwi02\"\n", + "# gpucluster_admin_user_name = \"\"\n", + "# gpucluster_admin_user_password = \"\"\n", + "# docker_login = \"georgedockeraccount\"\n", + "# docker_pwd = \"\"\n", + "# experimentation_image_tag = \"fwi01_azureml\"\n", + "# experimentation_image_version = \"sdk.v1.0.60\"\n", + "# docker_container_mount_point = '/datadrive01/prj/DeepSeismic/fwi' # use project directory or a subdirectory" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "# sensitive_info = {\n", + "# 'SUBSCRIPTION_ID':subscription_id,\n", + "# 'RESOURCE_GROUP':resource_group, \n", + "# 'WORKSPACE_NAME':workspace_name, \n", + "# 'WORKSPACE_REGION':workspace_region,\n", + "# 'GPU_CLUSTER_NAME':gpu_cluster_name,\n", + "# 'GPU_CLUSTER_ADMIN_USER_NAME':gpucluster_admin_user_name,\n", + "# 'GPU_CLUSTER_ADMIN_USER_PASSWORD':gpucluster_admin_user_password,\n", + "# 'DOCKER_LOGIN':docker_login,\n", + "# 'DOCKER_PWD':docker_pwd,\n", + "# 'EXPERIMENTATION_IMAGE_TAG':experimentation_image_tag,\n", + "# 'EXPERIMENTATION_IMAGE_VERSION':experimentation_image_version,\n", + "# 'DOCKER_CONTAINER_MOUNT_POINT':docker_container_mount_point\n", + "# }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 2.2. Save sensitive info\n", + "An empty __sensitive_info__ variable will be ingored. \n", + "A non-empty __sensitive_info__ variable will overwrite info in an existing .env file." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../not_shared/general.env'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%load_ext dotenv\n", + "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH)) \n", + "os.makedirs(os.path.join(*(prj_consts.DOTENV_FILE_PATH[:-1])), exist_ok=True)\n", + "\n", + "# # show .env file path\n", + "# !pwd\n", + "dotenv_file_path\n", + "\n", + "#save your sensitive info\n", + "project_utils.set_dotenv_info(dotenv_file_path, sensitive_info)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 2.3. Use (load) saved sensitive info\n", + "THis is how sensitive info will be retrieved in other notebooks" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "%dotenv $dotenv_file_path\n", + "\n", + "subscription_id = os.getenv('SUBSCRIPTION_ID')\n", + "# # print a bit of subscription ID, to show dotenv file was found and loaded \n", + "# subscription_id[:2]\n", + "\n", + "crt_resource_group = os.getenv('RESOURCE_GROUP')\n", + "crt_workspace_name = os.getenv('WORKSPACE_NAME')\n", + "crt_workspace_region = os.getenv('WORKSPACE_REGION') " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 2.4. Access your workspace\n", + "\n", + "* In AML SDK we can get a ws in two ways: \n", + " - via Workspace(subscription_id = ...) \n", + " - via Workspace.from_config(path=some_file_path). \n", + " \n", + "For demo purposes, both ways are shown in this notebook.\n", + "\n", + "* At first notebook run:\n", + " - the AML workspace ws is typically not found, so a new ws object is created and persisted on disk.\n", + " - If the ws has been created other ways (e.g. via Azure portal), it may be persisted on disk by calling ws1.write_config(...)." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "workspace_config_dir = os.path.join(*(prj_consts.AML_WORKSPACE_CONFIG_DIR))\n", + "workspace_config_file = prj_consts.AML_WORKSPACE_CONFIG_FILE_NAME\n", + "\n", + "# # print debug info if needed \n", + "# workspace_config_dir \n", + "# ls_l(os.path.join(os.getcwd(), os.path.join(*([workspace_config_dir]))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "###### Login into Azure may be required here\n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Workspace configuration loading succeeded. \n" + ] + } + ], + "source": [ + "try:\n", + " ws1 = Workspace(\n", + " subscription_id = subscription_id, \n", + " resource_group = crt_resource_group, \n", + " workspace_name = crt_workspace_name,\n", + " auth=project_utils.get_auth(dotenv_file_path))\n", + " print(\"Workspace configuration loading succeeded. \")\n", + "# ws1.write_config(path=os.path.join(os.getcwd(), os.path.join(*([workspace_config_dir]))),\n", + "# file_name=workspace_config_file)\n", + " del ws1 # ws will be (re)created later using from_config() function\n", + "except Exception as e :\n", + " print('Exception msg: {}'.format(str(e )))\n", + " print(\"Workspace not accessible. Will create a new workspace below\")\n", + " \n", + " workspace_region = crt_workspace_region\n", + "\n", + " # Create the workspace using the specified parameters\n", + " ws2 = Workspace.create(name = crt_workspace_name,\n", + " subscription_id = subscription_id,\n", + " resource_group = crt_resource_group, \n", + " location = workspace_region,\n", + " create_resource_group = True,\n", + " exist_ok = False)\n", + " ws2.get_details()\n", + "\n", + " # persist the subscription id, resource group name, and workspace name in aml_config/config.json.\n", + " ws2.write_config(path=os.path.join(os.getcwd(), os.path.join(*([workspace_config_dir]))),\n", + " file_name=workspace_config_file)\n", + " \n", + " #Delete ws2 and use ws = Workspace.from_config() as shwon below to recover the ws, rather than rely on what we get from one time creation\n", + " del ws2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 2.5. Demo access to created workspace\n", + "\n", + "From now on, even in other notebooks, the provisioned AML workspace will be accesible using Workspace.from_config() as shown below:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "# path arg is:\n", + "# - a file path which explictly lists aml_config subdir for function from_config() \n", + "# - a dir path with a silently added <> subdir for function write_config(). \n", + "ws = Workspace.from_config(path=os.path.join(os.getcwd(), \n", + " os.path.join(*([workspace_config_dir, '.azureml', workspace_config_file]))))\n", + "# # print debug info if needed\n", + "# print(ws.name, ws.resource_group, ws.location, ws.subscription_id[0], sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 2.6. Create compute cluster used in following notebooks" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gpuclstfwi02'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", + "gpu_cluster_name" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found existing gpu cluster\n" + ] + } + ], + "source": [ + "max_nodes_value = 3\n", + "\n", + "try:\n", + " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", + " print(\"Found existing gpu cluster\")\n", + "except ComputeTargetException:\n", + " print(\"Could not find gpu cluster, please create one\")\n", + " \n", + "# # Specify the configuration for the new cluster, add admin_user_ssh_key='ssh-rsa ... ghiordan@microsoft.com' if needed\n", + "# compute_config = AmlCompute.provisioning_configuration(vm_size=\"Standard_NC12\",\n", + "# min_nodes=0,\n", + "# max_nodes=max_nodes_value,\n", + "# admin_username=os.getenv('GPU_CLUSTER_ADMIN_USER_NAME'), \n", + "# admin_user_password=os.getenv('GPU_CLUSTER_ADMIN_USER_NAME'))\n", + "# # Create the cluster with the specified name and configuration\n", + "# gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, compute_config)\n", + "\n", + "# # Wait for the cluster to complete, show the output log\n", + "# gpu_cluster.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finished running 000_Setup_GeophysicsTutorial_FWI_Azure_devito!\n" + ] + } + ], + "source": [ + "print('Finished running 000_Setup_GeophysicsTutorial_FWI_Azure_devito!')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb new file mode 100755 index 00000000..5504af80 --- /dev/null +++ b/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -0,0 +1,1055 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# FWI in Azure project\n", + "\n", + "## Create Experimentation Docker image\n", + "\n", + "FWI demo based on: \n", + "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", + "\n", + "\n", + "\n", + "In this notebook we create a custom docker image that will be used to run the devito demo notebooks in AzureML. \n", + "\n", + " - We transparently create a docker file, a conda environment .yml file, build the docker image and push it into dockerhub. Azure ACR could also be used for storing docker images. \n", + " - The conda environment .yml file lists conda and pip installs, and separates all python dependencies from the docker installs. \n", + " - The dockerfile is generic. The only AzureML depedency is azureml-sdk pip installable package in conda environment .yml file\n", + " - The created docer image will be run in following notebook in a container on the local AzureVM or on a remote AzureML compute cluster. This AzureML pattern decouples experimentation (or training) job definition (experimentation script, data location, dependencies and docker image) happening on the control plane machine that runs this notebook, from the elastically allocated and Azure managed VM/cluster that does the actual training/experimentation computation.\n", + " \n", + "\n", + "User input requiring steps:\n", + " - [Fill in and save docker image name settings, if needed. ](#docker_image_settings)\n", + " - [Update DOCKER_CONTAINER_MOUNT_POINT to match our local path](#docker_image_settings)\n", + " - [Set docker build and test flags](#docker_build_test_settings) \n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Allow multiple displays per cell\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\" " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "import shutil\n", + "import urllib\n", + "import azureml.core\n", + "from azureml.core import Workspace, Experiment\n", + "from azureml.core.datastore import Datastore\n", + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.runconfig import MpiConfiguration\n", + "from azureml.exceptions import ComputeTargetException\n", + "from azureml.data.data_reference import DataReference\n", + "from azureml.pipeline.steps import HyperDriveStep\n", + "from azureml.pipeline.core import Pipeline, PipelineData\n", + "from azureml.train.dnn import TensorFlow\n", + "\n", + "from azureml.train.estimator import Estimator\n", + "from azureml.widgets import RunDetails\n", + "\n", + "\n", + "import platform\n", + "import math\n", + "import docker" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Azure ML SDK Version: 1.0.65\n" + ] + }, + { + "data": { + "text/plain": [ + "'Linux-4.15.0-1060-azure-x86_64-with-debian-10.0'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/workspace/examples/imaging/azureml_devito/notebooks'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", + "platform.platform()\n", + "os.getcwd()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "#### Setup docker image build and test process. \n", + " - devito tests take abou 15 mins (981.41 seconds). When running this notebook for first time make:\n", + " > docker_build_no_cache = '--no-cache' \n", + " > docker_test_run_devito_tests = True\n", + " \n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "docker_build_no_cache = '' # '--no-cache' # or '' #\n", + "docker_test_run_devito_tests = False # True # False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Import utilities functions" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_path_to_sys_path(path_to_append):\n", + " if not (any(path_to_append in paths for paths in sys.path)):\n", + " sys.path.append(path_to_append)\n", + " \n", + "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", + "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", + "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", + "\n", + "import project_utils\n", + "prj_consts = project_utils.project_consts()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Create experimentation docker file" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../not_shared/general.env'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", + "dotenv_file_path" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "##### Input here docker image settings \n", + "in cell below we use [dotenv](https://github.com/theskumar/python-dotenv) to overwrite docker image properties already save in dotenv_file_path. Change as needed, e.g. update azureml_sdk version if using a different version.\n", + "\n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(True, 'EXPERIMENTATION_IMAGE_VERSION', 'sdk.v1.0.65')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "(True, 'EXPERIMENTATION_IMAGE_TAG', 'fwi01_azureml')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "(True,\n", + " 'DOCKER_CONTAINER_MOUNT_POINT',\n", + " '/datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# SDK changes often, so we'll keep its version transparent \n", + "import dotenv\n", + "\n", + "# EXPERIMENTATION_IMAGE_VERSION should:\n", + "# - match sdk version in fwi01_conda_env01 environmnet in conda_env_fwi01_azureml_sdk.v1.0.XX.yml file below\n", + "# - match the conda env yml file name, e.g. conda_env_fwi01_azureml_sdk.v1.0.62.yml referenced in \n", + "# Dockerfile_fwi01_azureml_sdk.v1.0.62\n", + "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_IMAGE_VERSION', 'sdk.v1.0.65')\n", + "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_IMAGE_TAG', 'fwi01_azureml')\n", + "dotenv.set_key(dotenv_file_path, 'DOCKER_CONTAINER_MOUNT_POINT', '/datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'georgedockeraccount/fwi01_azureml:sdk.v1.0.65'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'conda_env_fwi01_azureml_sdk.v1.0.65.yml'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.65.yml'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.65'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%load_ext dotenv\n", + "%dotenv $dotenv_file_path\n", + "\n", + "docker_file_location = os.path.join(*(prj_consts.AML_EXPERIMENT_DIR + ['docker_build']))\n", + "\n", + "docker_file_name = 'Dockerfile'+ '_' + os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", + "conda_dependency_file_name = 'conda_env'+ '_' + os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", + "devito_conda_dependency_file_name = 'devito_conda_env'+'.yml'\n", + "docker_image_name = os.getenv('DOCKER_LOGIN') + '/' + os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", + "image_version = os.getenv('EXPERIMENTATION_IMAGE_VERSION')\n", + "if image_version!=\"\":\n", + " docker_file_name = docker_file_name +'_'+ image_version\n", + " conda_dependency_file_name = conda_dependency_file_name+'_'+ image_version\n", + " docker_image_name = docker_image_name +':'+ image_version\n", + "conda_dependency_file_name=conda_dependency_file_name+'.yml'\n", + "\n", + "docker_file_dir = os.path.join(*([os.getcwd(), docker_file_location]))\n", + "os.makedirs(docker_file_dir, exist_ok=True)\n", + "docker_file_path = os.path.join(*([docker_file_dir]+[docker_file_name]))\n", + "conda_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_file_name]))\n", + "\n", + "docker_image_name\n", + "conda_dependency_file_name\n", + "conda_file_path\n", + "docker_file_dir\n", + "docker_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.65.yml\n" + ] + } + ], + "source": [ + "%%writefile $conda_file_path\n", + "name: fwi01_conda_env01\n", + " \n", + "#https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.13.1-cp37-cp37m-linux_x86_64.whl \n", + "# https://github.com/dask/dask-tutorial\n", + "\n", + "channels:\n", + " - anaconda\n", + " - conda-forge\n", + "dependencies:\n", + " - python=3.6 # 3.6 req by tf, not 3.7.2 \n", + " - dask\n", + " - distributed\n", + " - h5py\n", + " - matplotlib\n", + " - nb_conda\n", + " - notebook \n", + " - numpy \n", + " - pandas\n", + " - pip\n", + " - py-cpuinfo # all required by devito or dask-tutorial\n", + " - pytables\n", + " - python-graphviz\n", + " - requests>=2.19.1\n", + " - pillow\n", + " - scipy\n", + " - snakeviz\n", + " - scikit-image\n", + " - toolz\n", + " - pip:\n", + " - anytree # required by devito\n", + " - azureml-sdk[notebooks,automl]==1.0.65\n", + " - codepy # required by devito\n", + " - papermill[azure]\n", + " - pyrevolve # required by devito" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.65\n" + ] + } + ], + "source": [ + "%%writefile $docker_file_path \n", + "\n", + "FROM continuumio/miniconda3:4.7.10 \n", + "MAINTAINER George Iordanescu \n", + "\n", + "RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \\\n", + " gcc g++ \\\n", + " wget bzip2 \\\n", + " curl \\\n", + " git make \\\n", + " mpich \\ \n", + " libmpich-dev && \\\n", + " apt-get clean && \\\n", + " rm -rf /var/lib/apt/lists/*\n", + "\n", + "ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml_sdk.v1.0.65.yml\n", + "ADD $CONDA_ENV_FILE_NAME /tmp/$CONDA_ENV_FILE_NAME\n", + "ENV CONDA_DIR /opt/conda\n", + "ENV CONDA_ENV_NAME fwi01_conda_env\n", + "\n", + "RUN git clone https://github.com/opesci/devito.git && \\\n", + " cd devito && \\\n", + " /opt/conda/bin/conda env create -q --name $CONDA_ENV_NAME -f environment.yml && \\\n", + " pip install -e . \n", + " \n", + "ENV CONDA_AUTO_UPDATE_CONDA=false\n", + "ENV CONDA_DEFAULT_ENV=$CONDA_ENV_NAME\n", + "ENV CONDA_PREFIX=$CONDA_DIR/envs/$CONDA_DEFAULT_ENV\n", + "ENV PATH=$CONDA_PREFIX/bin:/opt/conda/bin:$PATH \n", + "\n", + "RUN /opt/conda/bin/conda env update --name $CONDA_ENV_NAME -f /tmp/$CONDA_ENV_FILE_NAME && \\\n", + " /opt/conda/bin/conda clean --yes --all\n", + "\n", + "ENV PYTHONPATH=$PYTHONPATH:devito/app\n", + "\n", + "# WORKDIR /devito \n", + " \n", + "CMD /bin/bash" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 24\r\n", + "-rw-r--r-- 1 root root 1098 Sep 25 00:39 Dockerfile_fwi01_azureml_sdk.v1.0.60\r\n", + "-rw-r--r-- 1 root root 1098 Sep 26 19:04 Dockerfile_fwi01_azureml_sdk.v1.0.62\r\n", + "-rw-r--r-- 1 root root 1085 Oct 7 22:25 Dockerfile_fwi01_azureml_sdk.v1.0.65\r\n", + "-rw-r--r-- 1 root root 713 Sep 25 00:39 conda_env_fwi01_azureml_sdk.v1.0.60.yml\r\n", + "-rw-r--r-- 1 root root 713 Sep 26 19:04 conda_env_fwi01_azureml_sdk.v1.0.62.yml\r\n", + "-rw-r--r-- 1 root root 733 Oct 7 22:25 conda_env_fwi01_azureml_sdk.v1.0.65.yml\r\n" + ] + } + ], + "source": [ + "! ls -l $docker_file_dir" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker build -t georgedockeraccount/fwi01_azureml:sdk.v1.0.65 -f /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.65 /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build '" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sending build context to Docker daemon 11.78kB\n", + "Step 1/15 : FROM continuumio/miniconda3:4.7.10\n", + " ---> 4a51de2367be\n", + "Step 2/15 : MAINTAINER George Iordanescu \n", + " ---> Using cache\n", + " ---> fd7cf6c96c9d\n", + "Step 3/15 : RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends gcc g++ wget bzip2 curl git make mpich libmpich-dev && apt-get clean && rm -rf /var/lib/apt/lists/*\n", + " ---> Using cache\n", + " ---> 8d9b9aae4809\n", + "Step 4/15 : ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml_sdk.v1.0.65.yml\n", + " ---> Using cache\n", + " ---> 98c1084d1571\n", + "Step 5/15 : ADD $CONDA_ENV_FILE_NAME /tmp/$CONDA_ENV_FILE_NAME\n", + " ---> Using cache\n", + " ---> 75c937721b70\n", + "Step 6/15 : ENV CONDA_DIR /opt/conda\n", + " ---> Using cache\n", + " ---> 3dc77d946814\n", + "Step 7/15 : ENV CONDA_ENV_NAME fwi01_conda_env\n", + " ---> Using cache\n", + " ---> 6c04ce507b84\n", + "Step 8/15 : RUN git clone https://github.com/opesci/devito.git && cd devito && /opt/conda/bin/conda env create -q --name $CONDA_ENV_NAME -f environment.yml && pip install -e .\n", + " ---> Using cache\n", + " ---> 29bd65e2093a\n", + "Step 9/15 : ENV CONDA_AUTO_UPDATE_CONDA=false\n", + " ---> Using cache\n", + " ---> 7b012fbffee0\n", + "Step 10/15 : ENV CONDA_DEFAULT_ENV=$CONDA_ENV_NAME\n", + " ---> Using cache\n", + " ---> d693e9ba185b\n", + "Step 11/15 : ENV CONDA_PREFIX=$CONDA_DIR/envs/$CONDA_DEFAULT_ENV\n", + " ---> Using cache\n", + " ---> 35f2022ed1b4\n", + "Step 12/15 : ENV PATH=$CONDA_PREFIX/bin:/opt/conda/bin:$PATH\n", + " ---> Using cache\n", + " ---> b917898481e7\n", + "Step 13/15 : RUN /opt/conda/bin/conda env update --name $CONDA_ENV_NAME -f /tmp/$CONDA_ENV_FILE_NAME && /opt/conda/bin/conda clean --yes --all\n", + " ---> Using cache\n", + " ---> dccfdefe03c4\n", + "Step 14/15 : ENV PYTHONPATH=$PYTHONPATH:devito/app\n", + " ---> Using cache\n", + " ---> ef2ab56fd9ae\n", + "Step 15/15 : CMD /bin/bash\n", + " ---> Using cache\n", + " ---> 5b225e465622\n", + "Successfully built 5b225e465622\n", + "Successfully tagged georgedockeraccount/fwi01_azureml:sdk.v1.0.65\n" + ] + } + ], + "source": [ + "cli_command='docker build -t '+ docker_image_name + \\\n", + "' -f ' + docker_file_path + \\\n", + "' ' + docker_file_dir + ' ' +\\\n", + "docker_build_no_cache #'' #' --no-cache'\n", + "\n", + "\n", + "cli_command\n", + "! $cli_command" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Docker containers can be run using python docker sdk" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'georgedockeraccount/fwi01_azureml:sdk.v1.0.65'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "b'/\\n1.0.65\\n'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docker_image_name\n", + "\n", + "sh_command='bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'\n", + "sh_command\n", + "client = docker.from_env()\n", + "client.containers.run(docker_image_name, \n", + " remove=True,\n", + " volumes={os.getenv('DOCKER_CONTAINER_MOUNT_POINT'): {'bind': '/workspace', 'mode': 'rw'}},\n", + " working_dir='/',\n", + " command=sh_command)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Docker containers can also be run in cli \n", + "\n", + "Here we also create a log file to capture commands execution in container. If flag docker_test_run_devito_tests is True, we run \n", + "and capture test commands output. Tests take abou 15 minutes to run. If flag docker_test_run_devito_tests is False, we show the results of a previous session. " + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./fwi01_azureml_buildexperimentationdockerimage.log'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fwi01_log_file = os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log']))\n", + "fwi01_log_file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create command for running devito tests, capture output in a log file, save log file outside container" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "if docker_test_run_devito_tests:\n", + " run_devito_tests_command = ' python -m pytest tests/ ' + \\\n", + "'> ' + fwi01_log_file +' 2>&1; ' + \\\n", + "' mv ' + fwi01_log_file + ' /workspace/' \n", + " \n", + " with open(os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log'])), \"w\") as crt_log_file:\n", + " print('Before running e13n container... ', file=crt_log_file)\n", + " print('\\ncontent of devito tests log file before testing:')\n", + " !cat $fwi01_log_file\n", + "else:\n", + " run_devito_tests_command = '' \n", + "\n", + "# run_devito_tests_command = 'ls -l > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'\n", + "run_devito_tests_command" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks:/workspace:rw georgedockeraccount/fwi01_azureml:sdk.v1.0.65 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; \"'" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# conda environments:\n", + "#\n", + "base /opt/conda\n", + "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", + "\n", + "total 504\n", + "-rw-r--r-- 1 root root 11521 Oct 7 21:43 conftest.py\n", + "-rw-r--r-- 1 root root 6425 Oct 7 21:43 test_adjoint.py\n", + "-rw-r--r-- 1 root root 13882 Oct 7 21:43 test_autotuner.py\n", + "-rw-r--r-- 1 root root 9727 Oct 7 21:43 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Oct 7 21:43 test_constant.py\n", + "-rw-r--r-- 1 root root 52392 Oct 7 21:43 test_data.py\n", + "-rw-r--r-- 1 root root 481 Oct 7 21:43 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16585 Oct 7 21:43 test_derivatives.py\n", + "-rw-r--r-- 1 root root 30846 Oct 7 21:43 test_dimension.py\n", + "-rw-r--r-- 1 root root 21233 Oct 7 21:43 test_dle.py\n", + "-rw-r--r-- 1 root root 1138 Oct 7 21:43 test_docstrings.py\n", + "-rw-r--r-- 1 root root 26251 Oct 7 21:43 test_dse.py\n", + "-rw-r--r-- 1 root root 8612 Oct 7 21:43 test_gradient.py\n", + "-rw-r--r-- 1 root root 15229 Oct 7 21:43 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31514 Oct 7 21:43 test_ir.py\n", + "-rw-r--r-- 1 root root 60563 Oct 7 21:43 test_mpi.py\n", + "-rw-r--r-- 1 root root 61542 Oct 7 21:43 test_operator.py\n", + "-rw-r--r-- 1 root root 11839 Oct 7 21:43 test_ops.py\n", + "-rw-r--r-- 1 root root 11252 Oct 7 21:43 test_pickle.py\n", + "-rw-r--r-- 1 root root 1815 Oct 7 21:43 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Oct 7 21:43 test_save.py\n", + "-rw-r--r-- 1 root root 5711 Oct 7 21:43 test_subdomains.py\n", + "-rw-r--r-- 1 root root 10526 Oct 7 21:43 test_symbol_caching.py\n", + "-rw-r--r-- 1 root root 1896 Oct 7 21:43 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 3186 Oct 7 21:43 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Oct 7 21:43 test_tools.py\n", + "-rw-r--r-- 1 root root 3302 Oct 7 21:43 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Oct 7 21:43 test_visitors.py\n", + "-rw-r--r-- 1 root root 21810 Oct 7 21:43 test_yask.py\n", + "1.0.65\n", + "\n", + "content of devito tests log file after testing:\n", + "============================= test session starts ==============================\n", + "platform linux -- Python 3.6.9, pytest-5.2.0, py-1.8.0, pluggy-0.13.0\n", + "rootdir: /devito, inifile: setup.cfg\n", + "plugins: nbval-0.9.3, cov-2.7.1\n", + "collected 879 items / 2 skipped / 877 selected\n", + "\n", + "tests/test_adjoint.py .......................... [ 2%]\n", + "tests/test_autotuner.py ..........s.... [ 4%]\n", + "tests/test_checkpointing.py ....... [ 5%]\n", + "tests/test_constant.py . [ 5%]\n", + "tests/test_data.py ..........................ssssssssssssss.ss. [ 10%]\n", + "tests/test_dependency_bugs.py . [ 10%]\n", + "tests/test_derivatives.py .............................................. [ 15%]\n", + "........................................................................ [ 24%]\n", + "........................................................FF...F.......... [ 32%]\n", + "...... [ 32%]\n", + "tests/test_dimension.py ............................... [ 36%]\n", + "tests/test_dle.py ...................................................... [ 42%]\n", + "......................................... [ 47%]\n", + "tests/test_docstrings.py .............. [ 48%]\n", + "tests/test_dse.py ......x............................................... [ 55%]\n", + "......................s.... [ 58%]\n", + "tests/test_gradient.py .... [ 58%]\n", + "tests/test_interpolation.py ........................ [ 61%]\n", + "tests/test_ir.py ....................................................... [ 67%]\n", + "................ [ 69%]\n", + "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 75%]\n", + "s [ 75%]\n", + "tests/test_operator.py ................................................. [ 81%]\n", + "........................................................................ [ 89%]\n", + ".................. [ 91%]\n", + "tests/test_pickle.py ..............ss. [ 93%]\n", + "tests/test_resample.py . [ 93%]\n", + "tests/test_save.py .. [ 93%]\n", + "tests/test_subdomains.py ... [ 94%]\n", + "tests/test_symbol_caching.py ...................... [ 96%]\n", + "tests/test_symbolic_coefficients.py ..... [ 97%]\n", + "tests/test_timestepping.py ....... [ 97%]\n", + "tests/test_tools.py ..... [ 98%]\n", + "tests/test_tti.py .... [ 98%]\n", + "tests/test_visitors.py ......... [100%]\n", + "\n", + "=================================== FAILURES ===================================\n", + "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-8] _____________________\n", + "\n", + "self = , so = 8, ndim = 1\n", + "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", + "\n", + " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", + " @pytest.mark.parametrize('ndim', [1, 2])\n", + " @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [\n", + " ('dx', 'dx', -1),\n", + " ('dx2', 'dx2', 1),\n", + " ('dxl', 'dxr', -1),\n", + " ('dxr', 'dxl', -1)])\n", + " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", + " clear_cache()\n", + " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", + " x = grid.dimensions[0]\n", + " f = Function(name='f', grid=grid, space_order=so)\n", + " f_deriv = Function(name='f_deriv', grid=grid, space_order=so)\n", + " g = Function(name='g', grid=grid, space_order=so)\n", + " g_deriv = Function(name='g_deriv', grid=grid, space_order=so)\n", + " \n", + " # Fill f and g with smooth cos/sin\n", + " Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply()\n", + " # Check symbolic expression are expected ones for the adjoint .T\n", + " deriv = getattr(f, derivative)\n", + " expected = adjoint_coeff * getattr(f, adjoint_name).evaluate\n", + " assert deriv.T.evaluate == expected\n", + " \n", + " # Compute numerical derivatives and verify dot test\n", + " # i.e = \n", + " \n", + " eq_f = Eq(f_deriv, deriv)\n", + " eq_g = Eq(g_deriv, getattr(g, derivative).T)\n", + " \n", + " op = Operator([eq_f, eq_g])\n", + " op()\n", + " \n", + " a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1))\n", + " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", + "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", + "E assert False\n", + "E + where False = ((1 - (0.3281994 / 0.3282032)), 0, atol=1e-05)\n", + "E + where = np.isclose\n", + "\n", + "tests/test_derivatives.py:397: AssertionError\n", + "----------------------------- Captured stderr call -----------------------------\n", + "Operator `Kernel` run in 0.01 s\n", + "Operator `Kernel` run in 0.01 s\n", + "------------------------------ Captured log call -------------------------------\n", + "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", + "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", + "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________\n", + "\n", + "self = , so = 12, ndim = 1\n", + "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", + "\n", + " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", + " @pytest.mark.parametrize('ndim', [1, 2])\n", + " @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [\n", + " ('dx', 'dx', -1),\n", + " ('dx2', 'dx2', 1),\n", + " ('dxl', 'dxr', -1),\n", + " ('dxr', 'dxl', -1)])\n", + " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", + " clear_cache()\n", + " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", + " x = grid.dimensions[0]\n", + " f = Function(name='f', grid=grid, space_order=so)\n", + " f_deriv = Function(name='f_deriv', grid=grid, space_order=so)\n", + " g = Function(name='g', grid=grid, space_order=so)\n", + " g_deriv = Function(name='g_deriv', grid=grid, space_order=so)\n", + " \n", + " # Fill f and g with smooth cos/sin\n", + " Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply()\n", + " # Check symbolic expression are expected ones for the adjoint .T\n", + " deriv = getattr(f, derivative)\n", + " expected = adjoint_coeff * getattr(f, adjoint_name).evaluate\n", + " assert deriv.T.evaluate == expected\n", + " \n", + " # Compute numerical derivatives and verify dot test\n", + " # i.e = \n", + " \n", + " eq_f = Eq(f_deriv, deriv)\n", + " eq_g = Eq(g_deriv, getattr(g, derivative).T)\n", + " \n", + " op = Operator([eq_f, eq_g])\n", + " op()\n", + " \n", + " a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1))\n", + " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", + "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", + "E assert False\n", + "E + where False = ((1 - (0.2223196 / 0.22231674)), 0, atol=1e-05)\n", + "E + where = np.isclose\n", + "\n", + "tests/test_derivatives.py:397: AssertionError\n", + "----------------------------- Captured stderr call -----------------------------\n", + "Operator `Kernel` run in 0.01 s\n", + "Operator `Kernel` run in 0.01 s\n", + "------------------------------ Captured log call -------------------------------\n", + "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", + "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", + "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________\n", + "\n", + "self = , so = 12, ndim = 2\n", + "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", + "\n", + " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", + " @pytest.mark.parametrize('ndim', [1, 2])\n", + " @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [\n", + " ('dx', 'dx', -1),\n", + " ('dx2', 'dx2', 1),\n", + " ('dxl', 'dxr', -1),\n", + " ('dxr', 'dxl', -1)])\n", + " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", + " clear_cache()\n", + " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", + " x = grid.dimensions[0]\n", + " f = Function(name='f', grid=grid, space_order=so)\n", + " f_deriv = Function(name='f_deriv', grid=grid, space_order=so)\n", + " g = Function(name='g', grid=grid, space_order=so)\n", + " g_deriv = Function(name='g_deriv', grid=grid, space_order=so)\n", + " \n", + " # Fill f and g with smooth cos/sin\n", + " Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply()\n", + " # Check symbolic expression are expected ones for the adjoint .T\n", + " deriv = getattr(f, derivative)\n", + " expected = adjoint_coeff * getattr(f, adjoint_name).evaluate\n", + " assert deriv.T.evaluate == expected\n", + " \n", + " # Compute numerical derivatives and verify dot test\n", + " # i.e = \n", + " \n", + " eq_f = Eq(f_deriv, deriv)\n", + " eq_g = Eq(g_deriv, getattr(g, derivative).T)\n", + " \n", + " op = Operator([eq_f, eq_g])\n", + " op()\n", + " \n", + " a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1))\n", + " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", + "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", + "E assert False\n", + "E + where False = ((1 - (11.338287 / 11.338157)), 0, atol=1e-05)\n", + "E + where = np.isclose\n", + "\n", + "tests/test_derivatives.py:397: AssertionError\n", + "----------------------------- Captured stderr call -----------------------------\n", + "Operator `Kernel` run in 0.01 s\n", + "/tmp/devito-jitcache-uid0/d9e038c50e679daec98f5e80704a19e4aaf8e62c.c: In function ‘Kernel’:\n", + "/tmp/devito-jitcache-uid0/d9e038c50e679daec98f5e80704a19e4aaf8e62c.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + " #pragma omp simd aligned(f,f_deriv,g,g_deriv:32)\n", + " \n", + "Operator `Kernel` run in 0.01 s\n", + "------------------------------ Captured log call -------------------------------\n", + "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", + "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", + "======= 3 failed, 800 passed, 77 skipped, 1 xfailed in 866.14s (0:14:26) =======\n" + ] + } + ], + "source": [ + "cli_command='docker run -it --rm --name fwi01_azureml_container ' +\\\n", + "' -v '+os.getenv('DOCKER_CONTAINER_MOUNT_POINT')+':/workspace:rw ' + \\\n", + "docker_image_name + \\\n", + "' /bin/bash -c \"conda env list ; ls -l /devito/tests; ' + \\\n", + "'python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; ' + \\\n", + "'cd /devito; ' + \\\n", + "run_devito_tests_command +\\\n", + "' \"'\n", + "\n", + "cli_command\n", + "! $cli_command\n", + "# # ============= 774 passed, 70 skipped, 1 xfailed in 1106.76 seconds =============\n", + "print('\\ncontent of devito tests log file after testing:')\n", + "!cat $fwi01_log_file" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING! Using --password via the CLI is insecure. Use --password-stdin.\n", + "WARNING! Your password will be stored unencrypted in /root/.docker/config.json.\n", + "Configure a credential helper to remove this warning. See\n", + "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\n", + "\n", + "Login Succeeded\n" + ] + } + ], + "source": [ + "docker_pwd = os.getenv('DOCKER_PWD')\n", + "docker_login = os.getenv('DOCKER_LOGIN')\n", + "!docker login -u=$docker_login -p=$docker_pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The push refers to repository [docker.io/georgedockeraccount/fwi01_azureml]\n", + "\n", + "\u001b[1B8da5e589: Preparing \n", + "\u001b[1Ba559fdba: Preparing \n", + "\u001b[1B772bb00d: Preparing \n", + "\u001b[1B54377100: Preparing \n", + "\u001b[1Bf8fc4c9a: Preparing \n", + "\u001b[1Bba47210e: Preparing \n", + "\u001b[4B54377100: Layer already exists \u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[Ksdk.v1.0.65: digest: sha256:f327c88a842c9e77df4df9ae1b980367ea053be4a0c778e1647e105d2cbf08a3 size: 1800\n" + ] + } + ], + "source": [ + "# %%bash\n", + "!docker push {docker_image_name}" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!\n" + ] + } + ], + "source": [ + "# !jupyter nbconvert 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito --to html\n", + "print('Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb new file mode 100755 index 00000000..5dcc9299 --- /dev/null +++ b/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -0,0 +1,914 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# FWI demo based on: \n", + "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", + "\n", + "\n", + "\n", + "In this notebook we run the devito demo [notebooks](https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/) mentioned above by using an [AzureML estimator](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator.estimator?view=azure-ml-py) with custom docker image. The docker image and associated docker file were created in previous notebook.\n", + "\n", + "\n", + "#### This notebook is used as a control plane to submit experimentation jobs running devito in Azure in two modes (see [remote run azureml python script file invoking devito](#devito_demo_mode)):\n", + " - [Mode 1](#devito_demo_mode_1):\n", + " - uses custom code (slightly modified graphing functions save images to files too) \n", + " - experimentation job is defined by the devito code that is packaged as a py file to be run on an Azure remote compute target\n", + " - experimentation job can be used to track metrics or other artifacts (images)\n", + " \n", + " - Mode 2:\n", + " - papermill is invoked via cli or via its Python API to run unedited devito demo notebooks (https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) on the remote compute target and get back the results as saved notebooks that are then Available in Azure portal. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Allow multiple displays per cell\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\" " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "import shutil\n", + "import urllib\n", + "import azureml.core\n", + "from azureml.core import Workspace, Experiment\n", + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "from azureml.core.runconfig import MpiConfiguration\n", + "\n", + "\n", + "# from azureml.core.datastore import Datastore\n", + "# from azureml.data.data_reference import DataReference\n", + "# from azureml.pipeline.steps import HyperDriveStep\n", + "# from azureml.pipeline.core import Pipeline, PipelineData\n", + "# from azureml.train.dnn import TensorFlow\n", + "\n", + "from azureml.train.estimator import Estimator\n", + "from azureml.widgets import RunDetails\n", + "\n", + "import platform" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Azure ML SDK Version: 1.0.65\n" + ] + }, + { + "data": { + "text/plain": [ + "'Linux-4.15.0-1060-azure-x86_64-with-debian-10.0'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/workspace/examples/imaging/azureml_devito/notebooks'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", + "platform.platform()\n", + "os.getcwd()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../not_shared/general.env'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_path_to_sys_path(path_to_append):\n", + " if not (any(path_to_append in paths for paths in sys.path)):\n", + " sys.path.append(path_to_append)\n", + " \n", + "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", + "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", + "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", + "\n", + "import project_utils\n", + "prj_consts = project_utils.project_consts()\n", + "\n", + "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", + "dotenv_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../not_shared'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "workspace_config_dir = os.path.join(*(prj_consts.AML_WORKSPACE_CONFIG_DIR))\n", + "workspace_config_file = prj_consts.AML_WORKSPACE_CONFIG_FILE_NAME\n", + "workspace_config_dir" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/azureml_01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%dotenv $dotenv_file_path\n", + "\n", + "script_folder = prj_consts.AML_EXPERIMENT_DIR + ['devito_tutorial']\n", + "\n", + "devito_training_script_file = '01_modelling.py' # hardcoded in file azureml_training_script_full_file_name below\n", + "azureml_training_script_file = 'azureml_'+devito_training_script_file\n", + "experimentName = '020_AzureMLEstimator'\n", + "\n", + "os.makedirs(os.path.join(*(script_folder)), exist_ok=True)\n", + "script_path = os.path.join(*(script_folder))\n", + "training_script_full_file_name = os.path.join(script_path, devito_training_script_file)\n", + "azureml_training_script_full_file_name = os.path.join(script_path, azureml_training_script_file)\n", + "\n", + "training_script_full_file_name\n", + "azureml_training_script_full_file_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " \n", + "##### devito in Azure ML demo mode 1\n", + "Create devito demo script based on \n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/01_modelling.ipynb\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to summary of modes od demoing devito in AzureML.\n", + "\n", + "Main purpose of this script is to extend _plot_velocity()_ and _plot_shotrecord()_ devito [plotting functions](https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py) to allow the mto work in batch mode, i.e. save output to a file." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $training_script_full_file_name\n", + "\n", + "import numpy as np\n", + "import os, argparse\n", + "\n", + "from examples.seismic import Model\n", + "from examples.seismic import TimeAxis\n", + "from examples.seismic import Receiver\n", + "from devito import TimeFunction\n", + "from devito import Eq, solve\n", + "from devito import Operator\n", + "\n", + "\n", + "# try:\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib import cm\n", + "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", + "\n", + "mpl.rc('font', size=16)\n", + "mpl.rc('figure', figsize=(8, 6))\n", + "# except:\n", + "# plt = None\n", + "# cm = None\n", + " \n", + "\n", + "\n", + "# \"all\" plotting utils in devito do not save to file, so we extend them here\n", + "# https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py\n", + "def plot_velocity(model, source=None, receiver=None, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a two-dimensional velocity field from a seismic `Model`\n", + " object. Optionally also includes point markers for sources and receivers.\n", + "\n", + " Parameters\n", + " ----------\n", + " model : Model\n", + " Object that holds the velocity model.\n", + " source : array_like or float\n", + " Coordinates of the source point.\n", + " receiver : array_like or float\n", + " Coordinates of the receiver points.\n", + " colorbar : bool\n", + " Option to plot the colorbar.\n", + " \"\"\"\n", + " domain_size = 1.e-3 * np.array(model.domain_size)\n", + " extent = [model.origin[0], model.origin[0] + domain_size[0],\n", + " model.origin[1] + domain_size[1], model.origin[1]]\n", + "\n", + " plot = plt.imshow(np.transpose(model.vp.data), animated=True, cmap=cm.jet,\n", + " vmin=np.min(model.vp.data), vmax=np.max(model.vp.data),\n", + " extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Depth (km)')\n", + "\n", + " # Plot source points, if provided\n", + " if receiver is not None:\n", + " plt.scatter(1e-3*receiver[:, 0], 1e-3*receiver[:, 1],\n", + " s=25, c='green', marker='D')\n", + "\n", + " # Plot receiver points, if provided\n", + " if source is not None:\n", + " plt.scatter(1e-3*source[:, 0], 1e-3*source[:, 1],\n", + " s=25, c='red', marker='o')\n", + "\n", + " # Ensure axis limits\n", + " plt.xlim(model.origin[0], model.origin[0] + domain_size[0])\n", + " plt.ylim(model.origin[1] + domain_size[1], model.origin[1])\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " cbar = plt.colorbar(plot, cax=cax)\n", + " cbar.set_label('Velocity (km/s)')\n", + " plt.show()\n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def plot_shotrecord(rec, model, t0, tn, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a shot record (receiver values over time).\n", + "\n", + " Parameters\n", + " ----------\n", + " rec :\n", + " Receiver data with shape (time, points).\n", + " model : Model\n", + " object that holds the velocity model.\n", + " t0 : int\n", + " Start of time dimension to plot.\n", + " tn : int\n", + " End of time dimension to plot.\n", + " \"\"\"\n", + " scale = np.max(rec) / 10.\n", + " extent = [model.origin[0], model.origin[0] + 1e-3*model.domain_size[0],\n", + " 1e-3*tn, t0]\n", + "\n", + " plot = plt.imshow(rec, vmin=-scale, vmax=scale, cmap=cm.gray, extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Time (s)')\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " plt.colorbar(plot, cax=cax)\n", + " plt.show() \n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def main(output_folder): \n", + " # 1. Define the physical problem\n", + " # The first step is to define the physical model:\n", + " # - physical dimensions of interest\n", + " # - velocity profile of this physical domain\n", + "\n", + " # Define a physical size\n", + " shape = (101, 101) # Number of grid point (nx, nz)\n", + " spacing = (10., 10.) # Grid spacing in m. The domain size is now 1km by 1km\n", + " origin = (0., 0.) # What is the location of the top left corner. This is necessary to define\n", + " # the absolute location of the source and receivers\n", + "\n", + " # Define a velocity profile. The velocity is in km/s\n", + " v = np.empty(shape, dtype=np.float32)\n", + " v[:, :51] = 1.5\n", + " v[:, 51:] = 2.5\n", + "\n", + " # With the velocity and model size defined, we can create the seismic model that\n", + " # encapsulates this properties. We also define the size of the absorbing layer as 10 grid points\n", + " model = Model(vp=v, origin=origin, shape=shape, spacing=spacing,\n", + " space_order=2, nbpml=10)\n", + "\n", + " plot_velocity(model, \n", + " file= os.path.join(*( [output_folder,'output000.png'])))\n", + " \n", + " # 2. Acquisition geometry\n", + " t0 = 0. # Simulation starts a t=0\n", + " tn = 1000. # Simulation last 1 second (1000 ms)\n", + " dt = model.critical_dt # Time step from model grid spacing\n", + "\n", + " time_range = TimeAxis(start=t0, stop=tn, step=dt)\n", + " from examples.seismic import RickerSource\n", + "\n", + " f0 = 0.010 # Source peak frequency is 10Hz (0.010 kHz)\n", + " src = RickerSource(name='src', grid=model.grid, f0=f0,\n", + " npoint=1, time_range=time_range)\n", + "\n", + " # First, position source centrally in all dimensions, then set depth\n", + " src.coordinates.data[0, :] = np.array(model.domain_size) * .5\n", + " src.coordinates.data[0, -1] = 20. # Depth is 20m\n", + "\n", + " # We can plot the time signature to see the wavelet\n", + "# src.show()\n", + "\n", + " # Create symbol for 101 receivers\n", + " rec = Receiver(name='rec', grid=model.grid, npoint=101, time_range=time_range)\n", + "\n", + " # Prescribe even spacing for receivers along the x-axis\n", + " rec.coordinates.data[:, 0] = np.linspace(0, model.domain_size[0], num=101)\n", + " rec.coordinates.data[:, 1] = 20. # Depth is 20m\n", + "\n", + " # We can now show the source and receivers within our domain:\n", + " # Red dot: Source location\n", + " # Green dots: Receiver locations (every 4th point)\n", + " plot_velocity(model, source=src.coordinates.data,\n", + " receiver=rec.coordinates.data[::4, :], \n", + " file= os.path.join(*( [output_folder,'output010.png'])))\n", + " \n", + " # Define the wavefield with the size of the model and the time dimension\n", + " u = TimeFunction(name=\"u\", grid=model.grid, time_order=2, space_order=2)\n", + "\n", + " # We can now write the PDE\n", + " pde = model.m * u.dt2 - u.laplace + model.damp * u.dt\n", + "\n", + " # The PDE representation is as on paper\n", + " pde\n", + " \n", + " # This discrete PDE can be solved in a time-marching way updating u(t+dt) from the previous time step\n", + " # Devito as a shortcut for u(t+dt) which is u.forward. We can then rewrite the PDE as \n", + " # a time marching updating equation known as a stencil using customized SymPy functions\n", + "\n", + " stencil = Eq(u.forward, solve(pde, u.forward))\n", + " # Finally we define the source injection and receiver read function to generate the corresponding code\n", + " src_term = src.inject(field=u.forward, expr=src * dt**2 / model.m)\n", + "\n", + " # Create interpolation expression for receivers\n", + " rec_term = rec.interpolate(expr=u.forward)\n", + "\n", + " op = Operator([stencil] + src_term + rec_term, subs=model.spacing_map)\n", + " \n", + " op(time=time_range.num-1, dt=model.critical_dt)\n", + " plot_shotrecord(rec.data, model, t0, tn, \n", + " file= os.path.join(*( [output_folder,'output020.png'])))\n", + "\n", + "if __name__ == \"__main__\":\n", + " parser = argparse.ArgumentParser()\n", + " parser.add_argument('--output_folder', type=str, nargs='?', \\\n", + " dest='output_folder', help='ouput artifacts location',\\\n", + " default='.')\n", + " args = parser.parse_args()\n", + " \n", + " main(args.output_folder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Get experimentation docker image for devito" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'georgedockeraccount/fwi01_azureml:sdk.v1.0.65'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docker_image_name = os.getenv('DOCKER_LOGIN') + '/' + os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", + "\n", + "image_version = os.getenv('EXPERIMENTATION_IMAGE_VERSION')\n", + "if image_version!=\"\":\n", + " docker_image_name = docker_image_name +':'+ image_version\n", + " \n", + "docker_image_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Extract/decide the python path in custom docker image that corresponds to desired conda environment. Without this, AzureML tries to create a separate environment." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker run -i --rm --name fwi01_azureml_container02 georgedockeraccount/fwi01_azureml:sdk.v1.0.65 /bin/bash -c \"which python\" '" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/opt/conda/envs/fwi01_conda_env/bin/python'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_Python_path_command='docker run -i --rm --name fwi01_azureml_container02 '+ \\\n", + "docker_image_name + \\\n", + "' /bin/bash -c \"which python\" '\n", + "get_Python_path_command\n", + "\n", + "\n", + "import subprocess\n", + "python_path_in_docker_image = subprocess.check_output(get_Python_path_command,shell=True,stderr=subprocess.STDOUT).\\\n", + "decode('utf-8').strip()\n", + "python_path_in_docker_image" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "#### Create azureml_script_file that invokes:\n", + " - devito exclusive custom edited training_script_file\n", + " - unedited devito notebooks via papermill (invoked via cli and via ppapermill python API)\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to notebook summary." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/azureml_01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $azureml_training_script_full_file_name\n", + "\n", + "import argparse\n", + "import os\n", + "os.system('conda env list')\n", + "\n", + "import azureml.core;\n", + "from azureml.core.run import Run\n", + "\n", + "print(azureml.core.VERSION)\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "parser.add_argument('--output_folder', type=str, dest='output_folder', help='ouput artifacts location')\n", + "\n", + "args = parser.parse_args()\n", + "print('args.output_folder is {} but it will be ignored since AzureML_tracked ./outputs will be used'.format(args.output_folder))\n", + "\n", + "# get the Azure ML run object\n", + "run = Run.get_context()\n", + "\n", + "# ./outputs/ folder is autotracked so should get uploaded at the end of the run\n", + "output_dir_AzureML_tracked = './outputs'\n", + "\n", + "crt_dir = os.getcwd()\n", + "\n", + "cli_command= \\\n", + "'cd /devito; /opt/conda/envs/fwi01_conda_env/bin/python '+ crt_dir +'/01_modelling.py' + \\\n", + "' --output_folder '+ crt_dir + output_dir_AzureML_tracked+ '/' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/01_modelling.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/devito_cli_py.log'\n", + "print('Running devito from cli on 01_modelling.py----BEGIN-----:') \n", + "print(cli_command); print('\\n');os.system(cli_command)\n", + "print('Running devito from cli on 01_modelling.py----END-----:\\n\\n')\n", + "\n", + "cli_command= \\\n", + "'cd /devito; papermill ' + \\\n", + "'./examples/seismic/tutorials/02_rtm.ipynb '+\\\n", + "crt_dir +'/outputs/02_rtm_output.ipynb ' + \\\n", + "'--log-output --no-progress-bar --kernel python3 ' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/02_rtm_output.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/papermill_cli.log'\n", + "print('Running papermill from cli on 02_rtm.ipynb----BEGIN-----:') \n", + "print(cli_command); print('\\n');os.system(cli_command)\n", + "print('Running papermill from cli on 02_rtm.ipynb----END-----:\\n\\n') \n", + "\n", + "\n", + "print('Running papermill from Python API on 03_fwi.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/03_fwi.ipynb',\n", + " crt_dir +'/outputs/03_fwi_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 03_fwi.ipynb----END-----:') \n", + "\n", + "print('Running papermill from Python API on 04_dask.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/04_dask.ipynb',\n", + " crt_dir +'/outputs/04_dask_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 04_dask.ipynb----END-----:') \n", + " \n", + "\n", + "os.system('pwd')\n", + "os.system('ls -l /')\n", + "os.system('ls -l ./')\n", + "os.system('ls -l ' +crt_dir + output_dir_AzureML_tracked)\n", + "run.log('training_message01: ', 'finished experiment')\n", + "print('\\n')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['azureml_01_modelling.py', '01_modelling.py']" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "script_path=os.path.join(*(script_folder))\n", + "os.listdir(script_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize workspace\n", + "\n", + "Initialize a workspace object from persisted configuration. If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, make sure the config file is present at .\\config.json" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Workspace name: ghiordanfwiws\n", + "Azure region: eastus2\n", + "Subscription id: 7899\n" + ] + } + ], + "source": [ + "ws = Workspace.from_config(\n", + " path=os.path.join(os.getcwd(),\n", + " os.path.join(*([workspace_config_dir, '.azureml', workspace_config_file]))))\n", + "print('Workspace name: ' + ws.name, \n", + " 'Azure region: ' + ws.location, \n", + " 'Subscription id: ' + ws.subscription_id[0:4], sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create an Azure ML experiment\n", + "Let's create an experiment named \"tf-mnist\" and a folder to hold the training scripts. The script runs will be recorded under the experiment in Azure." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "exp = Experiment(workspace=ws, name=experimentName)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve or create a Azure Machine Learning compute\n", + "Azure Machine Learning Compute is a service for provisioning and managing clusters of Azure virtual machines for running machine learning workloads. Let's create a new Azure Machine Learning Compute in the current workspace, if it doesn't already exist. We will then run the training script on this compute target.\n", + "\n", + "If we could not find the compute with the given name in the previous cell, then we will create a new compute here. This process is broken down into the following steps:\n", + "\n", + "1. Create the configuration\n", + "2. Create the Azure Machine Learning compute\n", + "\n", + "**This process will take a few minutes and is providing only sparse output in the process. Please make sure to wait until the call returns before moving to the next cell.**" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gpuclstfwi02'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", + "gpu_cluster_name" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found existing gpu cluster\n" + ] + } + ], + "source": [ + "# Verify that cluster does not exist already\n", + "max_nodes_value = 3\n", + "try:\n", + " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", + " print(\"Found existing gpu cluster\")\n", + "except ComputeTargetException:\n", + " print(\"Could not find ComputeTarget cluster!\")\n", + " \n", + "# # Create a new gpucluster using code below\n", + "# # Specify the configuration for the new cluster\n", + "# compute_config = AmlCompute.provisioning_configuration(vm_size=\"Standard_NC6\",\n", + "# min_nodes=0,\n", + "# max_nodes=max_nodes_value)\n", + "# # Create the cluster with the specified name and configuration\n", + "# gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, compute_config)\n", + "\n", + "# # Wait for the cluster to complete, show the output log\n", + "# gpu_cluster.wait_for_completion(show_output=True)\n", + " \n", + " \n", + "# for demo purposes, show how clsuter properties can be altered post-creation\n", + "gpu_cluster.update(min_nodes=0, max_nodes=max_nodes_value, idle_seconds_before_scaledown=1200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create an Azure ML SDK estimator with custom docker image " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1eaca5d2dfe54a7a8cec9c2a33c0352e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# use a custom Docker image\n", + "from azureml.core.container_registry import ContainerRegistry\n", + "\n", + "image_name = docker_image_name\n", + "\n", + "# you can also point to an image in a private ACR\n", + "image_registry_details = ContainerRegistry()\n", + "image_registry_details.address = \"myregistry.azurecr.io\"\n", + "image_registry_details.username = \"username\"\n", + "image_registry_details.password = \"password\"\n", + "\n", + "# don't let the system build a new conda environment\n", + "user_managed_dependencies = True\n", + "\n", + "# submit to a local Docker container. if you don't have Docker engine running locally, you can set compute_target to cpu_cluster.\n", + "script_params = {\n", + " '--output_folder': 'some_folder'\n", + "}\n", + "\n", + "\n", + "# distributed_training_conf = MpiConfiguration()\n", + "# distributed_training_conf.process_count_per_node = 2\n", + "\n", + "est = Estimator(source_directory=script_path, \n", + " compute_target=gpu_cluster,#'local', #gpu_cluster, \n", + " entry_script=azureml_training_script_file,\n", + " script_params=script_params,\n", + " use_docker=True,\n", + " custom_docker_image=image_name,\n", + " # uncomment below line to use your private ACR\n", + " #image_registry_details=image_registry_details, \n", + " user_managed=user_managed_dependencies,\n", + " distributed_training=None,\n", + " node_count=1\n", + " )\n", + "est.run_config.environment.python.interpreter_path = python_path_in_docker_image\n", + "\n", + "run = exp.submit(est)\n", + "RunDetails(run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One can use the above link to currrent experiment run in Azure Portal to see tracked metrics, and images and output notebooks saved by azureml_training_script_full_file_name in {run_dir}/outputs on the remote compute target that are automatically saved by AzureML in the run history Azure portal pages." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "# run.get_details()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!\n" + ] + } + ], + "source": [ + "print('Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log b/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log new file mode 100644 index 00000000..5dfe1288 --- /dev/null +++ b/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log @@ -0,0 +1,201 @@ +============================= test session starts ============================== +platform linux -- Python 3.6.9, pytest-5.2.0, py-1.8.0, pluggy-0.13.0 +rootdir: /devito, inifile: setup.cfg +plugins: nbval-0.9.3, cov-2.7.1 +collected 879 items / 2 skipped / 877 selected + +tests/test_adjoint.py .......................... [ 2%] +tests/test_autotuner.py ..........s.... [ 4%] +tests/test_checkpointing.py ....... [ 5%] +tests/test_constant.py . [ 5%] +tests/test_data.py ..........................ssssssssssssss.ss. [ 10%] +tests/test_dependency_bugs.py . [ 10%] +tests/test_derivatives.py .............................................. [ 15%] +........................................................................ [ 24%] +........................................................FF...F.......... [ 32%] +...... [ 32%] +tests/test_dimension.py ............................... [ 36%] +tests/test_dle.py ...................................................... [ 42%] +......................................... [ 47%] +tests/test_docstrings.py .............. [ 48%] +tests/test_dse.py ......x............................................... [ 55%] +......................s.... [ 58%] +tests/test_gradient.py .... [ 58%] +tests/test_interpolation.py ........................ [ 61%] +tests/test_ir.py ....................................................... [ 67%] +................ [ 69%] +tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 75%] +s [ 75%] +tests/test_operator.py ................................................. [ 81%] +........................................................................ [ 89%] +.................. [ 91%] +tests/test_pickle.py ..............ss. [ 93%] +tests/test_resample.py . [ 93%] +tests/test_save.py .. [ 93%] +tests/test_subdomains.py ... [ 94%] +tests/test_symbol_caching.py ...................... [ 96%] +tests/test_symbolic_coefficients.py ..... [ 97%] +tests/test_timestepping.py ....... [ 97%] +tests/test_tools.py ..... [ 98%] +tests/test_tti.py .... [ 98%] +tests/test_visitors.py ......... [100%] + +=================================== FAILURES =================================== +____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-8] _____________________ + +self = , so = 8, ndim = 1 +derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 + + @pytest.mark.parametrize('so', [2, 4, 8, 12]) + @pytest.mark.parametrize('ndim', [1, 2]) + @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [ + ('dx', 'dx', -1), + ('dx2', 'dx2', 1), + ('dxl', 'dxr', -1), + ('dxr', 'dxl', -1)]) + def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): + clear_cache() + grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) + x = grid.dimensions[0] + f = Function(name='f', grid=grid, space_order=so) + f_deriv = Function(name='f_deriv', grid=grid, space_order=so) + g = Function(name='g', grid=grid, space_order=so) + g_deriv = Function(name='g_deriv', grid=grid, space_order=so) + + # Fill f and g with smooth cos/sin + Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply() + # Check symbolic expression are expected ones for the adjoint .T + deriv = getattr(f, derivative) + expected = adjoint_coeff * getattr(f, adjoint_name).evaluate + assert deriv.T.evaluate == expected + + # Compute numerical derivatives and verify dot test + # i.e = + + eq_f = Eq(f_deriv, deriv) + eq_g = Eq(g_deriv, getattr(g, derivative).T) + + op = Operator([eq_f, eq_g]) + op() + + a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1)) + b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) +> assert np.isclose(1 - a/b, 0, atol=1e-5) +E assert False +E + where False = ((1 - (0.3281994 / 0.3282032)), 0, atol=1e-05) +E + where = np.isclose + +tests/test_derivatives.py:397: AssertionError +----------------------------- Captured stderr call ----------------------------- +Operator `Kernel` run in 0.01 s +Operator `Kernel` run in 0.01 s +------------------------------ Captured log call ------------------------------- +INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s +INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s +____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________ + +self = , so = 12, ndim = 1 +derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 + + @pytest.mark.parametrize('so', [2, 4, 8, 12]) + @pytest.mark.parametrize('ndim', [1, 2]) + @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [ + ('dx', 'dx', -1), + ('dx2', 'dx2', 1), + ('dxl', 'dxr', -1), + ('dxr', 'dxl', -1)]) + def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): + clear_cache() + grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) + x = grid.dimensions[0] + f = Function(name='f', grid=grid, space_order=so) + f_deriv = Function(name='f_deriv', grid=grid, space_order=so) + g = Function(name='g', grid=grid, space_order=so) + g_deriv = Function(name='g_deriv', grid=grid, space_order=so) + + # Fill f and g with smooth cos/sin + Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply() + # Check symbolic expression are expected ones for the adjoint .T + deriv = getattr(f, derivative) + expected = adjoint_coeff * getattr(f, adjoint_name).evaluate + assert deriv.T.evaluate == expected + + # Compute numerical derivatives and verify dot test + # i.e = + + eq_f = Eq(f_deriv, deriv) + eq_g = Eq(g_deriv, getattr(g, derivative).T) + + op = Operator([eq_f, eq_g]) + op() + + a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1)) + b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) +> assert np.isclose(1 - a/b, 0, atol=1e-5) +E assert False +E + where False = ((1 - (0.2223196 / 0.22231674)), 0, atol=1e-05) +E + where = np.isclose + +tests/test_derivatives.py:397: AssertionError +----------------------------- Captured stderr call ----------------------------- +Operator `Kernel` run in 0.01 s +Operator `Kernel` run in 0.01 s +------------------------------ Captured log call ------------------------------- +INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s +INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s +____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________ + +self = , so = 12, ndim = 2 +derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 + + @pytest.mark.parametrize('so', [2, 4, 8, 12]) + @pytest.mark.parametrize('ndim', [1, 2]) + @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [ + ('dx', 'dx', -1), + ('dx2', 'dx2', 1), + ('dxl', 'dxr', -1), + ('dxr', 'dxl', -1)]) + def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): + clear_cache() + grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) + x = grid.dimensions[0] + f = Function(name='f', grid=grid, space_order=so) + f_deriv = Function(name='f_deriv', grid=grid, space_order=so) + g = Function(name='g', grid=grid, space_order=so) + g_deriv = Function(name='g_deriv', grid=grid, space_order=so) + + # Fill f and g with smooth cos/sin + Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply() + # Check symbolic expression are expected ones for the adjoint .T + deriv = getattr(f, derivative) + expected = adjoint_coeff * getattr(f, adjoint_name).evaluate + assert deriv.T.evaluate == expected + + # Compute numerical derivatives and verify dot test + # i.e = + + eq_f = Eq(f_deriv, deriv) + eq_g = Eq(g_deriv, getattr(g, derivative).T) + + op = Operator([eq_f, eq_g]) + op() + + a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1)) + b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) +> assert np.isclose(1 - a/b, 0, atol=1e-5) +E assert False +E + where False = ((1 - (11.338287 / 11.338157)), 0, atol=1e-05) +E + where = np.isclose + +tests/test_derivatives.py:397: AssertionError +----------------------------- Captured stderr call ----------------------------- +Operator `Kernel` run in 0.01 s +/tmp/devito-jitcache-uid0/d9e038c50e679daec98f5e80704a19e4aaf8e62c.c: In function ‘Kernel’: +/tmp/devito-jitcache-uid0/d9e038c50e679daec98f5e80704a19e4aaf8e62c.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas] + #pragma omp simd aligned(f,f_deriv,g,g_deriv:32) + +Operator `Kernel` run in 0.01 s +------------------------------ Captured log call ------------------------------- +INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s +INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s +======= 3 failed, 800 passed, 77 skipped, 1 xfailed in 866.14s (0:14:26) ======= From f8ffadd9587790cb193397e8617533a06d4704e2 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Thu, 17 Oct 2019 18:40:43 +0000 Subject: [PATCH 060/207] Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 --- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 2097 +++++++++++++++-- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 4 +- ...zureml_buildexperimentationdockerimage.log | 32 +- 3 files changed, 1977 insertions(+), 156 deletions(-) diff --git a/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb index 5504af80..4c17dc7c 100755 --- a/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -48,20 +48,6 @@ "import sys, os\n", "import shutil\n", "import urllib\n", - "import azureml.core\n", - "from azureml.core import Workspace, Experiment\n", - "from azureml.core.datastore import Datastore\n", - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.runconfig import MpiConfiguration\n", - "from azureml.exceptions import ComputeTargetException\n", - "from azureml.data.data_reference import DataReference\n", - "from azureml.pipeline.steps import HyperDriveStep\n", - "from azureml.pipeline.core import Pipeline, PipelineData\n", - "from azureml.train.dnn import TensorFlow\n", - "\n", - "from azureml.train.estimator import Estimator\n", - "from azureml.widgets import RunDetails\n", - "\n", "\n", "import platform\n", "import math\n", @@ -73,13 +59,6 @@ "execution_count": 3, "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Azure ML SDK Version: 1.0.65\n" - ] - }, { "data": { "text/plain": [ @@ -102,7 +81,6 @@ } ], "source": [ - "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", "platform.platform()\n", "os.getcwd()" ] @@ -126,8 +104,8 @@ "metadata": {}, "outputs": [], "source": [ - "docker_build_no_cache = '' # '--no-cache' # or '' #\n", - "docker_test_run_devito_tests = False # True # False" + "docker_build_no_cache = '--no-cache' # '--no-cache' # or '' #\n", + "docker_test_run_devito_tests = True # True # False" ] }, { @@ -194,6 +172,23 @@ "dotenv_file_path" ] }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/workspace/examples/imaging/azureml_devito/notebooks\r\n" + ] + } + ], + "source": [ + "!pwd" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -208,7 +203,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -217,7 +212,7 @@ "(True, 'EXPERIMENTATION_IMAGE_VERSION', 'sdk.v1.0.65')" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" }, @@ -227,7 +222,7 @@ "(True, 'EXPERIMENTATION_IMAGE_TAG', 'fwi01_azureml')" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" }, @@ -239,7 +234,7 @@ " '/datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks')" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -259,7 +254,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -268,7 +263,7 @@ "'georgedockeraccount/fwi01_azureml:sdk.v1.0.65'" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, @@ -278,7 +273,7 @@ "'conda_env_fwi01_azureml_sdk.v1.0.65.yml'" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, @@ -288,7 +283,7 @@ "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.65.yml'" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, @@ -298,7 +293,7 @@ "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build'" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, @@ -308,7 +303,7 @@ "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.65'" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -344,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -395,7 +390,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -449,7 +444,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -459,10 +454,10 @@ "total 24\r\n", "-rw-r--r-- 1 root root 1098 Sep 25 00:39 Dockerfile_fwi01_azureml_sdk.v1.0.60\r\n", "-rw-r--r-- 1 root root 1098 Sep 26 19:04 Dockerfile_fwi01_azureml_sdk.v1.0.62\r\n", - "-rw-r--r-- 1 root root 1085 Oct 7 22:25 Dockerfile_fwi01_azureml_sdk.v1.0.65\r\n", + "-rw-r--r-- 1 root root 1085 Oct 9 17:54 Dockerfile_fwi01_azureml_sdk.v1.0.65\r\n", "-rw-r--r-- 1 root root 713 Sep 25 00:39 conda_env_fwi01_azureml_sdk.v1.0.60.yml\r\n", "-rw-r--r-- 1 root root 713 Sep 26 19:04 conda_env_fwi01_azureml_sdk.v1.0.62.yml\r\n", - "-rw-r--r-- 1 root root 733 Oct 7 22:25 conda_env_fwi01_azureml_sdk.v1.0.65.yml\r\n" + "-rw-r--r-- 1 root root 733 Oct 9 17:54 conda_env_fwi01_azureml_sdk.v1.0.65.yml\r\n" ] } ], @@ -472,16 +467,16 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'docker build -t georgedockeraccount/fwi01_azureml:sdk.v1.0.65 -f /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.65 /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build '" + "'docker build -t georgedockeraccount/fwi01_azureml:sdk.v1.0.65 -f /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.65 /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build --no-cache'" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, @@ -493,48 +488,1845 @@ "Step 1/15 : FROM continuumio/miniconda3:4.7.10\n", " ---> 4a51de2367be\n", "Step 2/15 : MAINTAINER George Iordanescu \n", - " ---> Using cache\n", - " ---> fd7cf6c96c9d\n", + " ---> Running in 084695efc71b\n", + "Removing intermediate container 084695efc71b\n", + " ---> 447e38875551\n", "Step 3/15 : RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends gcc g++ wget bzip2 curl git make mpich libmpich-dev && apt-get clean && rm -rf /var/lib/apt/lists/*\n", - " ---> Using cache\n", - " ---> 8d9b9aae4809\n", + " ---> Running in d09c88390a5b\n", + "Get:1 http://deb.debian.org/debian buster InRelease [122 kB]\n", + "Get:2 http://deb.debian.org/debian buster-updates InRelease [49.3 kB]\n", + "Get:3 http://deb.debian.org/debian buster/main amd64 Packages [7899 kB]\n", + "Get:4 http://security.debian.org/debian-security buster/updates InRelease [39.1 kB]\n", + "Get:5 http://deb.debian.org/debian buster-updates/main amd64 Packages.diff/Index [1720 B]\n", + "Ign:5 http://deb.debian.org/debian buster-updates/main amd64 Packages.diff/Index\n", + "Get:6 http://deb.debian.org/debian buster-updates/main amd64 Packages [5792 B]\n", + "Get:7 http://security.debian.org/debian-security buster/updates/main amd64 Packages [98.2 kB]\n", + "Fetched 8214 kB in 1s (6166 kB/s)\n", + "Reading package lists...\n", + "Reading package lists...\n", + "Building dependency tree...\n", + "Reading state information...\n", + "git is already the newest version (1:2.20.1-2).\n", + "wget is already the newest version (1.20.1-1.1).\n", + "The following additional packages will be installed:\n", + " binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-8 g++-8 gcc-8\n", + " gfortran gfortran-8 hwloc-nox libasan5 libatomic1 libbinutils libbz2-1.0\n", + " libc-dev-bin libc6-dev libcc1-0 libcurl4 libgcc-8-dev libgfortran-8-dev\n", + " libgfortran5 libgomp1 libhwloc5 libisl19 libitm1 liblsan0 libltdl7 libmpc3\n", + " libmpfr6 libmpich12 libmpx2 libnuma1 libquadmath0 libstdc++-8-dev libtsan0\n", + " libubsan1 linux-libc-dev\n", + "Suggested packages:\n", + " binutils-doc bzip2-doc cpp-doc gcc-8-locales g++-multilib g++-8-multilib\n", + " gcc-8-doc libstdc++6-8-dbg gcc-multilib manpages-dev autoconf automake\n", + " libtool flex bison gdb gcc-doc gcc-8-multilib libgcc1-dbg libgomp1-dbg\n", + " libitm1-dbg libatomic1-dbg libasan5-dbg liblsan0-dbg libtsan0-dbg\n", + " libubsan1-dbg libmpx2-dbg libquadmath0-dbg gfortran-multilib gfortran-doc\n", + " gfortran-8-multilib gfortran-8-doc libgfortran5-dbg libcoarrays-dev\n", + " glibc-doc libhwloc-contrib-plugins libstdc++-8-doc make-doc mpich-doc\n", + "Recommended packages:\n", + " manpages manpages-dev libhwloc-plugins\n", + "The following NEW packages will be installed:\n", + " binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-8 curl g++ g++-8\n", + " gcc gcc-8 gfortran gfortran-8 hwloc-nox libasan5 libatomic1 libbinutils\n", + " libc-dev-bin libc6-dev libcc1-0 libcurl4 libgcc-8-dev libgfortran-8-dev\n", + " libgfortran5 libgomp1 libhwloc5 libisl19 libitm1 liblsan0 libltdl7 libmpc3\n", + " libmpfr6 libmpich-dev libmpich12 libmpx2 libnuma1 libquadmath0\n", + " libstdc++-8-dev libtsan0 libubsan1 linux-libc-dev make mpich\n", + "The following packages will be upgraded:\n", + " bzip2 libbz2-1.0\n", + "2 upgraded, 42 newly installed, 0 to remove and 22 not upgraded.\n", + "Need to get 58.5 MB of archives.\n", + "After this operation, 228 MB of additional disk space will be used.\n", + "Get:1 http://deb.debian.org/debian buster/main amd64 bzip2 amd64 1.0.6-9.2~deb10u1 [48.4 kB]\n", + "Get:2 http://security.debian.org/debian-security buster/updates/main amd64 linux-libc-dev amd64 4.19.67-2+deb10u1 [1233 kB]\n", + "Get:3 http://deb.debian.org/debian buster/main amd64 libbz2-1.0 amd64 1.0.6-9.2~deb10u1 [45.3 kB]\n", + "Get:4 http://deb.debian.org/debian buster/main amd64 binutils-common amd64 2.31.1-16 [2073 kB]\n", + "Get:5 http://deb.debian.org/debian buster/main amd64 libbinutils amd64 2.31.1-16 [478 kB]\n", + "Get:6 http://deb.debian.org/debian buster/main amd64 binutils-x86-64-linux-gnu amd64 2.31.1-16 [1823 kB]\n", + "Get:7 http://deb.debian.org/debian buster/main amd64 binutils amd64 2.31.1-16 [56.8 kB]\n", + "Get:8 http://deb.debian.org/debian buster/main amd64 libisl19 amd64 0.20-2 [587 kB]\n", + "Get:9 http://deb.debian.org/debian buster/main amd64 libmpfr6 amd64 4.0.2-1 [775 kB]\n", + "Get:10 http://deb.debian.org/debian buster/main amd64 libmpc3 amd64 1.1.0-1 [41.3 kB]\n", + "Get:11 http://deb.debian.org/debian buster/main amd64 cpp-8 amd64 8.3.0-6 [8914 kB]\n", + "Get:12 http://deb.debian.org/debian buster/main amd64 cpp amd64 4:8.3.0-1 [19.4 kB]\n", + "Get:13 http://deb.debian.org/debian buster/main amd64 libcurl4 amd64 7.64.0-4 [332 kB]\n", + "Get:14 http://deb.debian.org/debian buster/main amd64 curl amd64 7.64.0-4 [264 kB]\n", + "Get:15 http://deb.debian.org/debian buster/main amd64 libcc1-0 amd64 8.3.0-6 [46.6 kB]\n", + "Get:16 http://deb.debian.org/debian buster/main amd64 libgomp1 amd64 8.3.0-6 [75.8 kB]\n", + "Get:17 http://deb.debian.org/debian buster/main amd64 libitm1 amd64 8.3.0-6 [27.7 kB]\n", + "Get:18 http://deb.debian.org/debian buster/main amd64 libatomic1 amd64 8.3.0-6 [9032 B]\n", + "Get:19 http://deb.debian.org/debian buster/main amd64 libasan5 amd64 8.3.0-6 [362 kB]\n", + "Get:20 http://deb.debian.org/debian buster/main amd64 liblsan0 amd64 8.3.0-6 [131 kB]\n", + "Get:21 http://deb.debian.org/debian buster/main amd64 libtsan0 amd64 8.3.0-6 [283 kB]\n", + "Get:22 http://deb.debian.org/debian buster/main amd64 libubsan1 amd64 8.3.0-6 [120 kB]\n", + "Get:23 http://deb.debian.org/debian buster/main amd64 libmpx2 amd64 8.3.0-6 [11.4 kB]\n", + "Get:24 http://deb.debian.org/debian buster/main amd64 libquadmath0 amd64 8.3.0-6 [133 kB]\n", + "Get:25 http://deb.debian.org/debian buster/main amd64 libgcc-8-dev amd64 8.3.0-6 [2298 kB]\n", + "Get:26 http://deb.debian.org/debian buster/main amd64 gcc-8 amd64 8.3.0-6 [9452 kB]\n", + "Get:27 http://deb.debian.org/debian buster/main amd64 gcc amd64 4:8.3.0-1 [5196 B]\n", + "Get:28 http://deb.debian.org/debian buster/main amd64 libc-dev-bin amd64 2.28-10 [275 kB]\n", + "Get:29 http://deb.debian.org/debian buster/main amd64 libc6-dev amd64 2.28-10 [2691 kB]\n", + "Get:30 http://deb.debian.org/debian buster/main amd64 libstdc++-8-dev amd64 8.3.0-6 [1532 kB]\n", + "Get:31 http://deb.debian.org/debian buster/main amd64 g++-8 amd64 8.3.0-6 [9752 kB]\n", + "Get:32 http://deb.debian.org/debian buster/main amd64 g++ amd64 4:8.3.0-1 [1644 B]\n", + "Get:33 http://deb.debian.org/debian buster/main amd64 libgfortran5 amd64 8.3.0-6 [581 kB]\n", + "Get:34 http://deb.debian.org/debian buster/main amd64 libgfortran-8-dev amd64 8.3.0-6 [616 kB]\n", + "Get:35 http://deb.debian.org/debian buster/main amd64 gfortran-8 amd64 8.3.0-6 [9375 kB]\n", + "Get:36 http://deb.debian.org/debian buster/main amd64 gfortran amd64 4:8.3.0-1 [1432 B]\n", + "Get:37 http://deb.debian.org/debian buster/main amd64 libltdl7 amd64 2.4.6-9 [390 kB]\n", + "Get:38 http://deb.debian.org/debian buster/main amd64 libnuma1 amd64 2.0.12-1 [26.2 kB]\n", + "Get:39 http://deb.debian.org/debian buster/main amd64 libhwloc5 amd64 1.11.12-3 [111 kB]\n", + "Get:40 http://deb.debian.org/debian buster/main amd64 hwloc-nox amd64 1.11.12-3 [159 kB]\n", + "Get:41 http://deb.debian.org/debian buster/main amd64 libmpich12 amd64 3.3-3 [1169 kB]\n", + "Get:42 http://deb.debian.org/debian buster/main amd64 mpich amd64 3.3-3 [411 kB]\n", + "Get:43 http://deb.debian.org/debian buster/main amd64 libmpich-dev amd64 3.3-3 [1429 kB]\n", + "Get:44 http://deb.debian.org/debian buster/main amd64 make amd64 4.2.1-1.2 [341 kB]\n", + "\u001b[91mdebconf: delaying package configuration, since apt-utils is not installed\n", + "\u001b[0mFetched 58.5 MB in 1s (91.7 MB/s)\n", + "(Reading database ... 12557 files and directories currently installed.)\n", + "Preparing to unpack .../bzip2_1.0.6-9.2~deb10u1_amd64.deb ...\n", + "Unpacking bzip2 (1.0.6-9.2~deb10u1) over (1.0.6-9.1) ...\n", + "Preparing to unpack .../libbz2-1.0_1.0.6-9.2~deb10u1_amd64.deb ...\n", + "Unpacking libbz2-1.0:amd64 (1.0.6-9.2~deb10u1) over (1.0.6-9.1) ...\n", + "Setting up libbz2-1.0:amd64 (1.0.6-9.2~deb10u1) ...\n", + "Selecting previously unselected package binutils-common:amd64.\n", + "(Reading database ... 12557 files and directories currently installed.)\n", + "Preparing to unpack .../00-binutils-common_2.31.1-16_amd64.deb ...\n", + "Unpacking binutils-common:amd64 (2.31.1-16) ...\n", + "Selecting previously unselected package libbinutils:amd64.\n", + "Preparing to unpack .../01-libbinutils_2.31.1-16_amd64.deb ...\n", + "Unpacking libbinutils:amd64 (2.31.1-16) ...\n", + "Selecting previously unselected package binutils-x86-64-linux-gnu.\n", + "Preparing to unpack .../02-binutils-x86-64-linux-gnu_2.31.1-16_amd64.deb ...\n", + "Unpacking binutils-x86-64-linux-gnu (2.31.1-16) ...\n", + "Selecting previously unselected package binutils.\n", + "Preparing to unpack .../03-binutils_2.31.1-16_amd64.deb ...\n", + "Unpacking binutils (2.31.1-16) ...\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Selecting previously unselected package libisl19:amd64.\n", + "Preparing to unpack .../04-libisl19_0.20-2_amd64.deb ...\n", + "Unpacking libisl19:amd64 (0.20-2) ...\n", + "Selecting previously unselected package libmpfr6:amd64.\n", + "Preparing to unpack .../05-libmpfr6_4.0.2-1_amd64.deb ...\n", + "Unpacking libmpfr6:amd64 (4.0.2-1) ...\n", + "Selecting previously unselected package libmpc3:amd64.\n", + "Preparing to unpack .../06-libmpc3_1.1.0-1_amd64.deb ...\n", + "Unpacking libmpc3:amd64 (1.1.0-1) ...\n", + "Selecting previously unselected package cpp-8.\n", + "Preparing to unpack .../07-cpp-8_8.3.0-6_amd64.deb ...\n", + "Unpacking cpp-8 (8.3.0-6) ...\n", + "Selecting previously unselected package cpp.\n", + "Preparing to unpack .../08-cpp_4%3a8.3.0-1_amd64.deb ...\n", + "Unpacking cpp (4:8.3.0-1) ...\n", + "Selecting previously unselected package libcurl4:amd64.\n", + "Preparing to unpack .../09-libcurl4_7.64.0-4_amd64.deb ...\n", + "Unpacking libcurl4:amd64 (7.64.0-4) ...\n", + "Selecting previously unselected package curl.\n", + "Preparing to unpack .../10-curl_7.64.0-4_amd64.deb ...\n", + "Unpacking curl (7.64.0-4) ...\n", + "Selecting previously unselected package libcc1-0:amd64.\n", + "Preparing to unpack .../11-libcc1-0_8.3.0-6_amd64.deb ...\n", + "Unpacking libcc1-0:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libgomp1:amd64.\n", + "Preparing to unpack .../12-libgomp1_8.3.0-6_amd64.deb ...\n", + "Unpacking libgomp1:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libitm1:amd64.\n", + "Preparing to unpack .../13-libitm1_8.3.0-6_amd64.deb ...\n", + "Unpacking libitm1:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libatomic1:amd64.\n", + "Preparing to unpack .../14-libatomic1_8.3.0-6_amd64.deb ...\n", + "Unpacking libatomic1:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libasan5:amd64.\n", + "Preparing to unpack .../15-libasan5_8.3.0-6_amd64.deb ...\n", + "Unpacking libasan5:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package liblsan0:amd64.\n", + "Preparing to unpack .../16-liblsan0_8.3.0-6_amd64.deb ...\n", + "Unpacking liblsan0:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libtsan0:amd64.\n", + "Preparing to unpack .../17-libtsan0_8.3.0-6_amd64.deb ...\n", + "Unpacking libtsan0:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libubsan1:amd64.\n", + "Preparing to unpack .../18-libubsan1_8.3.0-6_amd64.deb ...\n", + "Unpacking libubsan1:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libmpx2:amd64.\n", + "Preparing to unpack .../19-libmpx2_8.3.0-6_amd64.deb ...\n", + "Unpacking libmpx2:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libquadmath0:amd64.\n", + "Preparing to unpack .../20-libquadmath0_8.3.0-6_amd64.deb ...\n", + "Unpacking libquadmath0:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libgcc-8-dev:amd64.\n", + "Preparing to unpack .../21-libgcc-8-dev_8.3.0-6_amd64.deb ...\n", + "Unpacking libgcc-8-dev:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package gcc-8.\n", + "Preparing to unpack .../22-gcc-8_8.3.0-6_amd64.deb ...\n", + "Unpacking gcc-8 (8.3.0-6) ...\n", + "Selecting previously unselected package gcc.\n", + "Preparing to unpack .../23-gcc_4%3a8.3.0-1_amd64.deb ...\n", + "Unpacking gcc (4:8.3.0-1) ...\n", + "Selecting previously unselected package libc-dev-bin.\n", + "Preparing to unpack .../24-libc-dev-bin_2.28-10_amd64.deb ...\n", + "Unpacking libc-dev-bin (2.28-10) ...\n", + "Selecting previously unselected package linux-libc-dev:amd64.\n", + "Preparing to unpack .../25-linux-libc-dev_4.19.67-2+deb10u1_amd64.deb ...\n", + "Unpacking linux-libc-dev:amd64 (4.19.67-2+deb10u1) ...\n", + "Selecting previously unselected package libc6-dev:amd64.\n", + "Preparing to unpack .../26-libc6-dev_2.28-10_amd64.deb ...\n", + "Unpacking libc6-dev:amd64 (2.28-10) ...\n", + "Selecting previously unselected package libstdc++-8-dev:amd64.\n", + "Preparing to unpack .../27-libstdc++-8-dev_8.3.0-6_amd64.deb ...\n", + "Unpacking libstdc++-8-dev:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package g++-8.\n", + "Preparing to unpack .../28-g++-8_8.3.0-6_amd64.deb ...\n", + "Unpacking g++-8 (8.3.0-6) ...\n", + "Selecting previously unselected package g++.\n", + "Preparing to unpack .../29-g++_4%3a8.3.0-1_amd64.deb ...\n", + "Unpacking g++ (4:8.3.0-1) ...\n", + "Selecting previously unselected package libgfortran5:amd64.\n", + "Preparing to unpack .../30-libgfortran5_8.3.0-6_amd64.deb ...\n", + "Unpacking libgfortran5:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package libgfortran-8-dev:amd64.\n", + "Preparing to unpack .../31-libgfortran-8-dev_8.3.0-6_amd64.deb ...\n", + "Unpacking libgfortran-8-dev:amd64 (8.3.0-6) ...\n", + "Selecting previously unselected package gfortran-8.\n", + "Preparing to unpack .../32-gfortran-8_8.3.0-6_amd64.deb ...\n", + "Unpacking gfortran-8 (8.3.0-6) ...\n", + "Selecting previously unselected package gfortran.\n", + "Preparing to unpack .../33-gfortran_4%3a8.3.0-1_amd64.deb ...\n", + "Unpacking gfortran (4:8.3.0-1) ...\n", + "Selecting previously unselected package libltdl7:amd64.\n", + "Preparing to unpack .../34-libltdl7_2.4.6-9_amd64.deb ...\n", + "Unpacking libltdl7:amd64 (2.4.6-9) ...\n", + "Selecting previously unselected package libnuma1:amd64.\n", + "Preparing to unpack .../35-libnuma1_2.0.12-1_amd64.deb ...\n", + "Unpacking libnuma1:amd64 (2.0.12-1) ...\n", + "Selecting previously unselected package libhwloc5:amd64.\n", + "Preparing to unpack .../36-libhwloc5_1.11.12-3_amd64.deb ...\n", + "Unpacking libhwloc5:amd64 (1.11.12-3) ...\n", + "Selecting previously unselected package hwloc-nox.\n", + "Preparing to unpack .../37-hwloc-nox_1.11.12-3_amd64.deb ...\n", + "Unpacking hwloc-nox (1.11.12-3) ...\n", + "Selecting previously unselected package libmpich12:amd64.\n", + "Preparing to unpack .../38-libmpich12_3.3-3_amd64.deb ...\n", + "Unpacking libmpich12:amd64 (3.3-3) ...\n", + "Selecting previously unselected package mpich.\n", + "Preparing to unpack .../39-mpich_3.3-3_amd64.deb ...\n", + "Unpacking mpich (3.3-3) ...\n", + "Selecting previously unselected package libmpich-dev:amd64.\n", + "Preparing to unpack .../40-libmpich-dev_3.3-3_amd64.deb ...\n", + "Unpacking libmpich-dev:amd64 (3.3-3) ...\n", + "Selecting previously unselected package make.\n", + "Preparing to unpack .../41-make_4.2.1-1.2_amd64.deb ...\n", + "Unpacking make (4.2.1-1.2) ...\n", + "Setting up binutils-common:amd64 (2.31.1-16) ...\n", + "Setting up linux-libc-dev:amd64 (4.19.67-2+deb10u1) ...\n", + "Setting up libgomp1:amd64 (8.3.0-6) ...\n", + "Setting up bzip2 (1.0.6-9.2~deb10u1) ...\n", + "Setting up libasan5:amd64 (8.3.0-6) ...\n", + "Setting up make (4.2.1-1.2) ...\n", + "Setting up libmpfr6:amd64 (4.0.2-1) ...\n", + "Setting up libquadmath0:amd64 (8.3.0-6) ...\n", + "Setting up libmpc3:amd64 (1.1.0-1) ...\n", + "Setting up libatomic1:amd64 (8.3.0-6) ...\n", + "Setting up libltdl7:amd64 (2.4.6-9) ...\n", + "Setting up libgfortran5:amd64 (8.3.0-6) ...\n", + "Setting up libmpx2:amd64 (8.3.0-6) ...\n", + "Setting up libubsan1:amd64 (8.3.0-6) ...\n", + "Setting up libnuma1:amd64 (2.0.12-1) ...\n", + "Setting up libisl19:amd64 (0.20-2) ...\n", + "Setting up libmpich12:amd64 (3.3-3) ...\n", + "Setting up libcurl4:amd64 (7.64.0-4) ...\n", + "Setting up curl (7.64.0-4) ...\n", + "Setting up libbinutils:amd64 (2.31.1-16) ...\n", + "Setting up cpp-8 (8.3.0-6) ...\n", + "Setting up libc-dev-bin (2.28-10) ...\n", + "Setting up libcc1-0:amd64 (8.3.0-6) ...\n", + "Setting up liblsan0:amd64 (8.3.0-6) ...\n", + "Setting up libitm1:amd64 (8.3.0-6) ...\n", + "Setting up binutils-x86-64-linux-gnu (2.31.1-16) ...\n", + "Setting up libtsan0:amd64 (8.3.0-6) ...\n", + "Setting up libhwloc5:amd64 (1.11.12-3) ...\n", + "Setting up hwloc-nox (1.11.12-3) ...\n", + "Setting up binutils (2.31.1-16) ...\n", + "Setting up libgcc-8-dev:amd64 (8.3.0-6) ...\n", + "Setting up cpp (4:8.3.0-1) ...\n", + "Setting up libc6-dev:amd64 (2.28-10) ...\n", + "Setting up libstdc++-8-dev:amd64 (8.3.0-6) ...\n", + "Setting up libgfortran-8-dev:amd64 (8.3.0-6) ...\n", + "Setting up gcc-8 (8.3.0-6) ...\n", + "Setting up mpich (3.3-3) ...\n", + "update-alternatives: using /usr/bin/mpicc.mpich to provide /usr/bin/mpicc (mpi) in auto mode\n", + "update-alternatives: warning: skip creation of /usr/share/man/man1/mpicc.1.gz because associated file /usr/share/man/man1/mpicc.mpich.1.gz (of link group mpi) doesn't exist\n", + "update-alternatives: warning: skip creation of /usr/share/man/man1/mpic++.1.gz because associated file /usr/share/man/man1/mpicxx.mpich.1.gz (of link group mpi) doesn't exist\n", + "update-alternatives: warning: skip creation of /usr/share/man/man1/mpicxx.1.gz because associated file /usr/share/man/man1/mpicxx.mpich.1.gz (of link group mpi) doesn't exist\n", + "update-alternatives: warning: skip creation of /usr/share/man/man1/mpiCC.1.gz because associated file /usr/share/man/man1/mpicxx.mpich.1.gz (of link group mpi) doesn't exist\n", + "update-alternatives: warning: skip creation of /usr/share/man/man1/mpif77.1.gz because associated file /usr/share/man/man1/mpif77.mpich.1.gz (of link group mpi) doesn't exist\n", + "update-alternatives: warning: skip creation of /usr/share/man/man1/mpif90.1.gz because associated file /usr/share/man/man1/mpif90.mpich.1.gz (of link group mpi) doesn't exist\n", + "update-alternatives: warning: skip creation of /usr/share/man/man1/mpifort.1.gz because associated file /usr/share/man/man1/mpifort.mpich.1.gz (of link group mpi) doesn't exist\n", + "update-alternatives: using /usr/bin/mpirun.mpich to provide /usr/bin/mpirun (mpirun) in auto mode\n", + "update-alternatives: warning: skip creation of /usr/share/man/man1/mpirun.1.gz because associated file /usr/share/man/man1/mpirun.mpich.1.gz (of link group mpirun) doesn't exist\n", + "update-alternatives: warning: skip creation of /usr/share/man/man1/mpiexec.1.gz because associated file /usr/share/man/man1/mpiexec.mpich.1.gz (of link group mpirun) doesn't exist\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setting up gfortran-8 (8.3.0-6) ...\n", + "Setting up gcc (4:8.3.0-1) ...\n", + "Setting up g++-8 (8.3.0-6) ...\n", + "Setting up gfortran (4:8.3.0-1) ...\n", + "update-alternatives: using /usr/bin/gfortran to provide /usr/bin/f95 (f95) in auto mode\n", + "update-alternatives: using /usr/bin/gfortran to provide /usr/bin/f77 (f77) in auto mode\n", + "Setting up g++ (4:8.3.0-1) ...\n", + "update-alternatives: using /usr/bin/g++ to provide /usr/bin/c++ (c++) in auto mode\n", + "Setting up libmpich-dev:amd64 (3.3-3) ...\n", + "update-alternatives: using /usr/include/x86_64-linux-gnu/mpich to provide /usr/include/x86_64-linux-gnu/mpi (mpi-x86_64-linux-gnu) in auto mode\n", + "Processing triggers for libc-bin (2.28-10) ...\n", + "Removing intermediate container d09c88390a5b\n", + " ---> 768917e3839d\n", "Step 4/15 : ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml_sdk.v1.0.65.yml\n", - " ---> Using cache\n", - " ---> 98c1084d1571\n", + " ---> Running in f82224761f1f\n", + "Removing intermediate container f82224761f1f\n", + " ---> 6fc45a3f7f12\n", "Step 5/15 : ADD $CONDA_ENV_FILE_NAME /tmp/$CONDA_ENV_FILE_NAME\n", - " ---> Using cache\n", - " ---> 75c937721b70\n", + " ---> 956a64671412\n", "Step 6/15 : ENV CONDA_DIR /opt/conda\n", - " ---> Using cache\n", - " ---> 3dc77d946814\n", + " ---> Running in 3eafe956d10d\n", + "Removing intermediate container 3eafe956d10d\n", + " ---> c11fc83e6bab\n", "Step 7/15 : ENV CONDA_ENV_NAME fwi01_conda_env\n", - " ---> Using cache\n", - " ---> 6c04ce507b84\n", + " ---> Running in 7eea1645616f\n", + "Removing intermediate container 7eea1645616f\n", + " ---> 4e42d4a8b84f\n", "Step 8/15 : RUN git clone https://github.com/opesci/devito.git && cd devito && /opt/conda/bin/conda env create -q --name $CONDA_ENV_NAME -f environment.yml && pip install -e .\n", - " ---> Using cache\n", - " ---> 29bd65e2093a\n", + " ---> Running in dda878848f67\n", + "\u001b[91mCloning into 'devito'...\n", + "\u001b[0mCollecting package metadata (repodata.json): ...working... done\n", + "Solving environment: ...working... done\n", + "Preparing transaction: ...working... done\n", + "Verifying transaction: ...working... done\n", + "Executing transaction: ...working... b'Enabling nb_conda_kernels...\\nStatus: enabled\\n'\n", + "done\n", + "Ran pip subprocess with arguments:\n", + "['/opt/conda/envs/fwi01_conda_env/bin/python', '-m', 'pip', 'install', '-U', '-r', '/devito/condaenv.rnqbkkc7.requirements.txt']\n", + "Pip subprocess output:\n", + "Collecting git+https://github.com/inducer/codepy (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 1))\n", + " Cloning https://github.com/inducer/codepy to /tmp/pip-req-build-tx3j8uvz\n", + "Collecting pyzfp>=0.2 (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/33/ff/1390221dc5ed78eb6573613a76927baeea02c31e73b9db91353b8834bb95/pyzfp-0.3.tar.gz (127kB)\n", + "Collecting pyrevolve>=2.1.3 (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 3))\n", + " Downloading https://files.pythonhosted.org/packages/0d/c4/63a36aa56969de861e181b86ebc1aba6764e9d2566c42e4faf1f360cb3cc/pyrevolve-2.1.3.tar.gz (190kB)\n", + "Collecting py-cpuinfo (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/42/60/63f28a5401da733043abe7053e7d9591491b4784c4f87c339bf51215aa0a/py-cpuinfo-5.0.0.tar.gz (82kB)\n", + "Collecting anytree>=2.4.3 (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 5))\n", + " Downloading https://files.pythonhosted.org/packages/62/89/640f607533415dd4b6d7a6d981614651a5763c1a1b55124bce8d27834073/anytree-2.7.1.tar.gz\n", + "Requirement already satisfied, skipping upgrade: pytools>=2015.1.2 in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (2019.1.1)\n", + "Requirement already satisfied, skipping upgrade: numpy>=1.6 in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (1.17.2)\n", + "Requirement already satisfied, skipping upgrade: appdirs>=1.4.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (1.4.3)\n", + "Requirement already satisfied, skipping upgrade: six in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (1.12.0)\n", + "Requirement already satisfied, skipping upgrade: cgen in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (2019.1)\n", + "Collecting blosc (from pyrevolve>=2.1.3->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 3))\n", + " Downloading https://files.pythonhosted.org/packages/6d/3b/2b707cd330a205ba5c69b5e8bfa9c05691442e45ce9ce882c4c8d343e61a/blosc-1.8.1.tar.gz (769kB)\n", + "Collecting contexttimer (from pyrevolve>=2.1.3->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 3))\n", + " Downloading https://files.pythonhosted.org/packages/1d/e0/504aa08a83dc2ff90f61a83b5f70d689e1f5138ab30576124ea2ff9f5076/contexttimer-0.3.3.tar.gz\n", + "Requirement already satisfied, skipping upgrade: decorator>=3.2.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from pytools>=2015.1.2->codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (4.4.0)\n", + "Building wheels for collected packages: pyzfp, pyrevolve, py-cpuinfo, anytree, codepy, blosc, contexttimer\n", + " Building wheel for pyzfp (setup.py): started\n", + " Building wheel for pyzfp (setup.py): finished with status 'done'\n", + " Created wheel for pyzfp: filename=pyzfp-0.3-cp37-cp37m-linux_x86_64.whl size=469789 sha256=871081f556377ec4faddab41185319f0fdbe5189d6ab502d845ee9a09f21a2b3\n", + " Stored in directory: /root/.cache/pip/wheels/a6/b5/c8/60b5a2d3fd3cbb49c37935bd498037ce18dae811d9e301b885\n", + " Building wheel for pyrevolve (setup.py): started\n", + " Building wheel for pyrevolve (setup.py): finished with status 'done'\n", + " Created wheel for pyrevolve: filename=pyrevolve-2.1.3-cp37-cp37m-linux_x86_64.whl size=333186 sha256=71a0bf26497a5684fff5b0fede906dc9622bcadfff76e51115c7af234b35e4cf\n", + " Stored in directory: /root/.cache/pip/wheels/df/d7/36/3e8e92a06a23446febeb604b528faeffba1a26c0d63e924a0a\n", + " Building wheel for py-cpuinfo (setup.py): started\n", + " Building wheel for py-cpuinfo (setup.py): finished with status 'done'\n", + " Created wheel for py-cpuinfo: filename=py_cpuinfo-5.0.0-cp37-none-any.whl size=18685 sha256=e8317812ee2b9be2e7fc142f7568e9526c4eb9625ee36f6851d487714f60afc5\n", + " Stored in directory: /root/.cache/pip/wheels/01/7e/a9/b982d0fea22b7e4ae5619de949570cde5ad55420cec16e86a5\n", + " Building wheel for anytree (setup.py): started\n", + " Building wheel for anytree (setup.py): finished with status 'done'\n", + " Created wheel for anytree: filename=anytree-2.7.1-py2.py3-none-any.whl size=30359 sha256=7dec01c26143b500939abac549de0065909ac0f03c72b8fd64543231ba47a39d\n", + " Stored in directory: /root/.cache/pip/wheels/2e/91/6d/10d009931e25814dfa7161d81b12d0d80cbdd18457ef7835e2\n", + " Building wheel for codepy (setup.py): started\n", + " Building wheel for codepy (setup.py): finished with status 'done'\n", + " Created wheel for codepy: filename=codepy-2019.1-cp37-none-any.whl size=19363 sha256=baa045a5fd2b2c4d95c316c8a429e69137d46a29b5efbef76646161974c39373\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-88yzxyb8/wheels/8b/e5/f8/5fa48f28841d0433a91e37d17a60fb9d8d5d20e603ebe4419b\n", + " Building wheel for blosc (setup.py): started\n", + " Building wheel for blosc (setup.py): finished with status 'done'\n", + " Created wheel for blosc: filename=blosc-1.8.1-cp37-cp37m-linux_x86_64.whl size=2108795 sha256=14a8b68475663e0f8a9e5c63d2b74bcea5199468082583846e23dde5b29700c3\n", + " Stored in directory: /root/.cache/pip/wheels/3b/e0/b9/99a77fb1821f0df30e52b9ce470c74efa2ca359ff8c21f8e17\n", + " Building wheel for contexttimer (setup.py): started\n", + " Building wheel for contexttimer (setup.py): finished with status 'done'\n", + " Created wheel for contexttimer: filename=contexttimer-0.3.3-cp37-none-any.whl size=5817 sha256=86745a9db4b2c2e01a7abfed5519809e3c0856c0508cef37cdb7cc3ba0bc336f\n", + " Stored in directory: /root/.cache/pip/wheels/b3/e2/35/565145ce0127c7451b6503dfabb2b56e9908c863e40c6b1870\n", + "Successfully built pyzfp pyrevolve py-cpuinfo anytree codepy blosc contexttimer\n", + "Installing collected packages: pyzfp, blosc, contexttimer, pyrevolve, py-cpuinfo, anytree, codepy\n", + "Successfully installed anytree-2.7.1 blosc-1.8.1 codepy-2019.1 contexttimer-0.3.3 py-cpuinfo-5.0.0 pyrevolve-2.1.3 pyzfp-0.3\n", + "\n", + "#\n", + "# To activate this environment, use\n", + "#\n", + "# $ conda activate fwi01_conda_env\n", + "#\n", + "# To deactivate an active environment, use\n", + "#\n", + "# $ conda deactivate\n", + "\n", + "Obtaining file:///devito\n", + "Requirement already satisfied: pip>=9.0.1 in /opt/conda/lib/python3.7/site-packages (from devito==3.5+214.g24ede913) (19.1.1)\n", + "Collecting numpy>=1.14 (from devito==3.5+214.g24ede913)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Downloading https://files.pythonhosted.org/packages/ba/e0/46e2f0540370f2661b044647fa447fef2ecbcc8f7cdb4329ca2feb03fb23/numpy-1.17.2-cp37-cp37m-manylinux1_x86_64.whl (20.3MB)\n", + "Collecting sympy>=1.4 (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/21/21/f4105795ca7f35c541d82c5b06be684dd2f5cb4f508fb487cd7aea4de776/sympy-1.4-py2.py3-none-any.whl (5.3MB)\n", + "Collecting scipy (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/94/7f/b535ec711cbcc3246abea4385d17e1b325d4c3404dd86f15fc4f3dba1dbb/scipy-1.3.1-cp37-cp37m-manylinux1_x86_64.whl (25.2MB)\n", + "Collecting pytest>=3.6 (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/0c/91/d68f68ce54cd3e8afa1ef73ea1ad44df2438521b64c0820e5fd9b9f13b7d/pytest-5.2.1-py3-none-any.whl (226kB)\n", + "Collecting pytest-runner (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/f8/31/f291d04843523406f242e63b5b90f7b204a756169b4250ff213e10326deb/pytest_runner-5.1-py2.py3-none-any.whl\n", + "Collecting flake8>=2.1.0 (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/26/de/3f815a99d86eb10464ea7bd6059c0172c7ca97d4bdcfca41051b388a653b/flake8-3.7.8-py2.py3-none-any.whl (70kB)\n", + "Collecting jedi (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/55/54/da994f359e4e7da4776a200e76dbc85ba5fc319eefc22e33d55296d95a1d/jedi-0.15.1-py2.py3-none-any.whl (1.0MB)\n", + "Collecting nbval (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/80/08/6b1e7d87a90edb02c272e30f4f6f325da5a5c8dfc3eaeaa4a8138fb97e43/nbval-0.9.3-py2.py3-none-any.whl\n", + "Collecting cached-property (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/3b/86/85c1be2e8db9e13ef9a350aecd6dea292bd612fa288c2f40d035bb750ded/cached_property-1.5.1-py2.py3-none-any.whl\n", + "Collecting psutil>=5.1.0 (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/1c/ca/5b8c1fe032a458c2c4bcbe509d1401dca9dda35c7fc46b36bb81c2834740/psutil-5.6.3.tar.gz (435kB)\n", + "Collecting py-cpuinfo (from devito==3.5+214.g24ede913)\n", + "Collecting cgen (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/92/4e/3f90d389714e005853d98041ec75ec06815179f47db735e5c45a41d579d6/cgen-2019.1.tar.gz\n", + "Collecting codepy (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/6c/81/338a4d4145af7857f9b6fdf9b4d53c58c7eb4c1d092ff6c010efdb4dfdf3/codepy-2019.1.tar.gz\n", + "Collecting click (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl (81kB)\n", + "Collecting codecov (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/8b/28/4c1950a61c3c5786f0f34d643d0d28ec832433c9a7c0bd157690d4eb1d5f/codecov-2.0.15-py2.py3-none-any.whl\n", + "Collecting pytest-cov (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/b9/54/3673ee8be482f81527678ac894276223b9814bb7262e4f730469bb7bf70e/pytest_cov-2.8.1-py2.py3-none-any.whl\n", + "Collecting multidict (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/88/f0/4d4cbd1a3744e3985efa49682352d0703df653ffa76b81f10fed86599a50/multidict-4.5.2-cp37-cp37m-manylinux1_x86_64.whl (309kB)\n", + "Collecting frozendict (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/4e/55/a12ded2c426a4d2bee73f88304c9c08ebbdbadb82569ebdd6a0c007cfd08/frozendict-1.2.tar.gz\n", + "Collecting anytree>=2.4.3 (from devito==3.5+214.g24ede913)\n", + "Collecting pyzfp>=0.2 (from devito==3.5+214.g24ede913)\n", + "Collecting pyrevolve>=2.1.3 (from devito==3.5+214.g24ede913)\n", + "Collecting distributed>=1.27 (from devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/3d/4a/1d1b356c16cefce3e91237b5e10b3e992d480414ade26493730c75d28ee0/distributed-2.5.2-py3-none-any.whl (557kB)\n", + "Collecting mpmath>=0.19 (from sympy>=1.4->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/ca/63/3384ebb3b51af9610086b23ea976e6d27d6d97bf140a76a365bd77a3eb32/mpmath-1.1.0.tar.gz (512kB)\n", + "Collecting wcwidth (from pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/7e/9f/526a6947247599b084ee5232e4f9190a38f398d7300d866af3ab571a5bfe/wcwidth-0.1.7-py2.py3-none-any.whl\n", + "Collecting pluggy<1.0,>=0.12 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/92/c7/48439f7d5fd6bddb4c04b850bb862b42e3e2b98570040dfaf68aedd8114b/pluggy-0.13.0-py2.py3-none-any.whl\n", + "Collecting py>=1.5.0 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/76/bc/394ad449851729244a97857ee14d7cba61ddb268dce3db538ba2f2ba1f0f/py-1.8.0-py2.py3-none-any.whl (83kB)\n", + "Collecting atomicwrites>=1.0 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/52/90/6155aa926f43f2b2a22b01be7241be3bfd1ceaf7d0b3267213e8127d41f4/atomicwrites-1.3.0-py2.py3-none-any.whl\n", + "Collecting importlib-metadata>=0.12; python_version < \"3.8\" (from pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/f6/d2/40b3fa882147719744e6aa50ac39cf7a22a913cbcba86a0371176c425a3b/importlib_metadata-0.23-py2.py3-none-any.whl\n", + "Collecting attrs>=17.4.0 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/6b/e8/2ecaf86b128a34e225807f03b22664302937ab826bd3b7eccab6754d29ea/attrs-19.2.0-py2.py3-none-any.whl (40kB)\n", + "Collecting packaging (from pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/cf/94/9672c2d4b126e74c4496c6b3c58a8b51d6419267be9e70660ba23374c875/packaging-19.2-py2.py3-none-any.whl\n", + "Collecting more-itertools>=4.0.0 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/45/dc/3241eef99eb45f1def35cf93af35d1cf9ef4c0991792583b8f33ea41b092/more_itertools-7.2.0-py3-none-any.whl (57kB)\n", + "Collecting pyflakes<2.2.0,>=2.1.0 (from flake8>=2.1.0->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/84/f2/ed0ffb887f8138a8fe5a621b8c0bb9598bfb3989e029f6c6a85ee66628ee/pyflakes-2.1.1-py2.py3-none-any.whl (59kB)\n", + "Collecting pycodestyle<2.6.0,>=2.5.0 (from flake8>=2.1.0->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/0e/0c/04a353e104d2f324f8ee5f4b32012618c1c86dd79e52a433b64fceed511b/pycodestyle-2.5.0-py2.py3-none-any.whl (51kB)\n", + "Collecting mccabe<0.7.0,>=0.6.0 (from flake8>=2.1.0->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/87/89/479dc97e18549e21354893e4ee4ef36db1d237534982482c3681ee6e7b57/mccabe-0.6.1-py2.py3-none-any.whl\n", + "Collecting entrypoints<0.4.0,>=0.3.0 (from flake8>=2.1.0->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/ac/c6/44694103f8c221443ee6b0041f69e2740d89a25641e62fb4f2ee568f2f9c/entrypoints-0.3-py2.py3-none-any.whl\n", + "Collecting parso>=0.5.0 (from jedi->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/a3/bd/bf4e5bd01d79906e5b945a7af033154da49fd2b0d5b5c705a21330323305/parso-0.5.1-py2.py3-none-any.whl (95kB)\n", + "Collecting nbformat (from nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/da/27/9a654d2b6cc1eaa517d1c5a4405166c7f6d72f04f6e7eea41855fe808a46/nbformat-4.4.0-py2.py3-none-any.whl (155kB)\n", + "Requirement already satisfied: six in /opt/conda/lib/python3.7/site-packages (from nbval->devito==3.5+214.g24ede913) (1.12.0)\n", + "Collecting jupyter-client (from nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/13/81/fe0eee1bcf949851a120254b1f530ae1e01bdde2d3ab9710c6ff81525061/jupyter_client-5.3.4-py2.py3-none-any.whl (92kB)\n", + "Collecting coverage (from nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/82/8f/a2a687fa00059360858023c5cb74e94b8afaf318726e9a256934066a9d90/coverage-4.5.4-cp37-cp37m-manylinux1_x86_64.whl (205kB)\n", + "Collecting ipykernel (from nbval->devito==3.5+214.g24ede913)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Downloading https://files.pythonhosted.org/packages/d4/16/43f51f65a8a08addf04f909a0938b06ba1ee1708b398a9282474531bd893/ipykernel-5.1.2-py3-none-any.whl (116kB)\n", + "Collecting pytools>=2015.1.2 (from cgen->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/00/96/00416762a3eda8876a17d007df4a946f46b2e4ee1057e0b9714926472ef8/pytools-2019.1.1.tar.gz (58kB)\n", + "Collecting appdirs>=1.4.0 (from codepy->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/56/eb/810e700ed1349edde4cbdc1b2a21e28cdf115f9faf263f6bbf8447c1abf3/appdirs-1.4.3-py2.py3-none-any.whl\n", + "Requirement already satisfied: requests>=2.7.9 in /opt/conda/lib/python3.7/site-packages (from codecov->devito==3.5+214.g24ede913) (2.22.0)\n", + "Collecting contexttimer (from pyrevolve>=2.1.3->devito==3.5+214.g24ede913)\n", + "Collecting blosc (from pyrevolve>=2.1.3->devito==3.5+214.g24ede913)\n", + "Collecting msgpack (from distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/25/f8/6009e73f5b08743718d0660a18ecbc44b013a68980347a3835b63e875cdb/msgpack-0.6.2-cp37-cp37m-manylinux1_x86_64.whl (243kB)\n", + "Collecting tblib (from distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/64/b5/ebb1af4d843047ccd7292b92f5e5f8643153e8b95d14508d9fe3b35f7004/tblib-1.4.0-py2.py3-none-any.whl\n", + "Collecting toolz>=0.7.4 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/22/8e/037b9ba5c6a5739ef0dcde60578c64d49f45f64c5e5e886531bfbc39157f/toolz-0.10.0.tar.gz (49kB)\n", + "Collecting zict>=0.1.3 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/64/b4/a904be4184814adb9dfc2e679c4e611392080a32726a657a34cab93b38c2/zict-1.0.0-py2.py3-none-any.whl\n", + "Collecting tornado>=5 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/30/78/2d2823598496127b21423baffaa186b668f73cd91887fcef78b6eade136b/tornado-6.0.3.tar.gz (482kB)\n", + "Collecting pyyaml (from distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/e3/e8/b3212641ee2718d556df0f23f78de8303f068fe29cdaa7a91018849582fe/PyYAML-5.1.2.tar.gz (265kB)\n", + "Collecting dask>=2.3 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/2a/56/565c82ec663ba1f2f12d2666fbac97f100c60ac49dd4a7549228de14f097/dask-2.5.2-py3-none-any.whl (760kB)\n", + "Collecting cloudpickle>=0.2.2 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/c1/49/334e279caa3231255725c8e860fa93e72083567625573421db8875846c14/cloudpickle-1.2.2-py2.py3-none-any.whl\n", + "Collecting sortedcontainers!=2.0.0,!=2.0.1 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/13/f3/cf85f7c3a2dbd1a515d51e1f1676d971abe41bba6f4ab5443240d9a78e5b/sortedcontainers-2.1.0-py2.py3-none-any.whl\n", + "Collecting zipp>=0.5 (from importlib-metadata>=0.12; python_version < \"3.8\"->pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/74/3d/1ee25a26411ba0401b43c6376d2316a71addcc72ef8690b101b4ea56d76a/zipp-0.6.0-py2.py3-none-any.whl\n", + "Collecting pyparsing>=2.0.2 (from packaging->pytest>=3.6->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/11/fa/0160cd525c62d7abd076a070ff02b2b94de589f1a9789774f17d7c54058e/pyparsing-2.4.2-py2.py3-none-any.whl (65kB)\n", + "Collecting ipython-genutils (from nbformat->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/fa/bc/9bd3b5c2b4774d5f33b2d544f1460be9df7df2fe42f352135381c347c69a/ipython_genutils-0.2.0-py2.py3-none-any.whl\n", + "Collecting traitlets>=4.1 (from nbformat->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/ca/ab/872a23e29cec3cf2594af7e857f18b687ad21039c1f9b922fac5b9b142d5/traitlets-4.3.3-py2.py3-none-any.whl (75kB)\n", + "Collecting jupyter-core (from nbformat->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/56/a6/fe4b7029d4994870df6685bdc7bae5417bea30b627c4ce36106f9cac31fc/jupyter_core-4.6.0-py2.py3-none-any.whl (82kB)\n", + "Collecting jsonschema!=2.5.0,>=2.4 (from nbformat->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/11/9c/a0a2c70be340603c8ff5a692a8e6a4997fb858c7fd8701ff2afe087a3b58/jsonschema-3.1.0-py2.py3-none-any.whl (56kB)\n", + "Collecting python-dateutil>=2.1 (from jupyter-client->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/41/17/c62faccbfbd163c7f57f3844689e3a78bae1f403648a6afb1d0866d87fbb/python_dateutil-2.8.0-py2.py3-none-any.whl (226kB)\n", + "Collecting pyzmq>=13 (from jupyter-client->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/c7/6a/307e4a576787c7df1df6ebf56754c3fc8defcafa1a09ee22e9b961a390be/pyzmq-18.1.0-cp37-cp37m-manylinux1_x86_64.whl (1.1MB)\n", + "Collecting ipython>=5.0.0 (from ipykernel->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/fb/44/f28a13852e562af719f9de1761680a84a93e8b4c50e22d00d68f60ee2e8b/ipython-7.8.0-py3-none-any.whl (775kB)\n", + "Collecting decorator>=3.2.0 (from pytools>=2015.1.2->cgen->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/5f/88/0075e461560a1e750a0dcbf77f1d9de775028c37a19a346a6c565a257399/decorator-4.4.0-py2.py3-none-any.whl\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.7/site-packages (from requests>=2.7.9->codecov->devito==3.5+214.g24ede913) (2019.6.16)\n", + "Requirement already satisfied: idna<2.9,>=2.5 in /opt/conda/lib/python3.7/site-packages (from requests>=2.7.9->codecov->devito==3.5+214.g24ede913) (2.8)\n", + "Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /opt/conda/lib/python3.7/site-packages (from requests>=2.7.9->codecov->devito==3.5+214.g24ede913) (3.0.4)\n", + "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /opt/conda/lib/python3.7/site-packages (from requests>=2.7.9->codecov->devito==3.5+214.g24ede913) (1.24.2)\n", + "Collecting heapdict (from zict>=0.1.3->distributed>=1.27->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/b6/9d/cd4777dbcf3bef9d9627e0fe4bc43d2e294b1baeb01d0422399d5e9de319/HeapDict-1.0.1-py3-none-any.whl\n", + "Collecting js-regex>=1.0.0 (from jsonschema!=2.5.0,>=2.4->nbformat->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/b1/4e/3493c42964a91f5d96f92f30c924421ee3b346d89db5c0fc45d0d8b04788/js_regex-1.0.0-py3-none-any.whl\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.7/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat->nbval->devito==3.5+214.g24ede913) (41.0.1)\n", + "Collecting pyrsistent>=0.14.0 (from jsonschema!=2.5.0,>=2.4->nbformat->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/b9/66/b2638d96a2d128b168d0dba60fdc77b7800a9b4a5340cefcc5fc4eae6295/pyrsistent-0.15.4.tar.gz (107kB)\n", + "Collecting backcall (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/84/71/c8ca4f5bb1e08401b916c68003acf0a0655df935d74d93bf3f3364b310e0/backcall-0.1.0.tar.gz\n", + "Collecting pickleshare (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/9a/41/220f49aaea88bc6fa6cba8d05ecf24676326156c23b991e80b3f2fc24c77/pickleshare-0.7.5-py2.py3-none-any.whl\n", + "Collecting pygments (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/5c/73/1dfa428150e3ccb0fa3e68db406e5be48698f2a979ccbcec795f28f44048/Pygments-2.4.2-py2.py3-none-any.whl (883kB)\n", + "Collecting prompt-toolkit<2.1.0,>=2.0.0 (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/87/61/2dfea88583d5454e3a64f9308a686071d58d59a55db638268a6413e1eb6d/prompt_toolkit-2.0.10-py3-none-any.whl (340kB)\n", + "Collecting pexpect; sys_platform != \"win32\" (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/0e/3e/377007e3f36ec42f1b84ec322ee12141a9e10d808312e5738f52f80a232c/pexpect-4.7.0-py2.py3-none-any.whl (58kB)\n", + "Collecting ptyprocess>=0.5 (from pexpect; sys_platform != \"win32\"->ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", + " Downloading https://files.pythonhosted.org/packages/d1/29/605c2cc68a9992d18dada28206eeada56ea4bd07a239669da41674648b6f/ptyprocess-0.6.0-py2.py3-none-any.whl\n", + "Building wheels for collected packages: psutil, cgen, codepy, frozendict, mpmath, pytools, toolz, tornado, pyyaml, pyrsistent, backcall\n", + " Building wheel for psutil (setup.py): started\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Building wheel for psutil (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/90/7e/74/bb640d77775e6b6a78bcc3120f9fea4d2a28b2706de1cff37d\n", + " Building wheel for cgen (setup.py): started\n", + " Building wheel for cgen (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/58/80/1c/4b18cf63778aafa8f62c37395448164667143bbbb20bf16b9d\n", + " Building wheel for codepy (setup.py): started\n", + " Building wheel for codepy (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/f4/53/e7/b53cf7ba45381b676bbd5eaaedc19ae82e1c397e9c1766ddf4\n", + " Building wheel for frozendict (setup.py): started\n", + " Building wheel for frozendict (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/6c/6c/e9/534386165bd12cf1885582c75eb6d0ffcb321b65c23fe0f834\n", + " Building wheel for mpmath (setup.py): started\n", + " Building wheel for mpmath (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/63/9d/8e/37c3f6506ed3f152733a699e92d8e0c9f5e5f01dea262f80ad\n", + " Building wheel for pytools (setup.py): started\n", + " Building wheel for pytools (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/83/df/0b/75ac4572aaa93e3eba6a58472635d0fda907f5f4cf884a3a0c\n", + " Building wheel for toolz (setup.py): started\n", + " Building wheel for toolz (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/e1/8b/65/3294e5b727440250bda09e8c0153b7ba19d328f661605cb151\n", + " Building wheel for tornado (setup.py): started\n", + " Building wheel for tornado (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/84/bf/40/2f6ef700f48401ca40e5e3dd7d0e3c0a90e064897b7fe5fc08\n", + " Building wheel for pyyaml (setup.py): started\n", + " Building wheel for pyyaml (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/d9/45/dd/65f0b38450c47cf7e5312883deb97d065e030c5cca0a365030\n", + " Building wheel for pyrsistent (setup.py): started\n", + " Building wheel for pyrsistent (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/bb/46/00/6d471ef0b813e3621f0abe6cb723c20d529d39a061de3f7c51\n", + " Building wheel for backcall (setup.py): started\n", + " Building wheel for backcall (setup.py): finished with status 'done'\n", + " Stored in directory: /root/.cache/pip/wheels/98/b0/dd/29e28ff615af3dda4c67cab719dd51357597eabff926976b45\n", + "Successfully built psutil cgen codepy frozendict mpmath pytools toolz tornado pyyaml pyrsistent backcall\n", + "Installing collected packages: numpy, mpmath, sympy, scipy, wcwidth, more-itertools, zipp, importlib-metadata, pluggy, py, atomicwrites, attrs, pyparsing, packaging, pytest, pytest-runner, pyflakes, pycodestyle, mccabe, entrypoints, flake8, parso, jedi, ipython-genutils, decorator, traitlets, jupyter-core, js-regex, pyrsistent, jsonschema, nbformat, python-dateutil, tornado, pyzmq, jupyter-client, coverage, backcall, pickleshare, pygments, prompt-toolkit, ptyprocess, pexpect, ipython, ipykernel, nbval, cached-property, psutil, py-cpuinfo, appdirs, pytools, cgen, codepy, click, codecov, pytest-cov, multidict, frozendict, anytree, pyzfp, contexttimer, blosc, pyrevolve, msgpack, tblib, toolz, heapdict, zict, pyyaml, dask, cloudpickle, sortedcontainers, distributed, devito\n", + " Running setup.py develop for devito\n", + "Successfully installed anytree-2.7.1 appdirs-1.4.3 atomicwrites-1.3.0 attrs-19.2.0 backcall-0.1.0 blosc-1.8.1 cached-property-1.5.1 cgen-2019.1 click-7.0 cloudpickle-1.2.2 codecov-2.0.15 codepy-2019.1 contexttimer-0.3.3 coverage-4.5.4 dask-2.5.2 decorator-4.4.0 devito distributed-2.5.2 entrypoints-0.3 flake8-3.7.8 frozendict-1.2 heapdict-1.0.1 importlib-metadata-0.23 ipykernel-5.1.2 ipython-7.8.0 ipython-genutils-0.2.0 jedi-0.15.1 js-regex-1.0.0 jsonschema-3.1.0 jupyter-client-5.3.4 jupyter-core-4.6.0 mccabe-0.6.1 more-itertools-7.2.0 mpmath-1.1.0 msgpack-0.6.2 multidict-4.5.2 nbformat-4.4.0 nbval-0.9.3 numpy-1.17.2 packaging-19.2 parso-0.5.1 pexpect-4.7.0 pickleshare-0.7.5 pluggy-0.13.0 prompt-toolkit-2.0.10 psutil-5.6.3 ptyprocess-0.6.0 py-1.8.0 py-cpuinfo-5.0.0 pycodestyle-2.5.0 pyflakes-2.1.1 pygments-2.4.2 pyparsing-2.4.2 pyrevolve-2.1.3 pyrsistent-0.15.4 pytest-5.2.1 pytest-cov-2.8.1 pytest-runner-5.1 python-dateutil-2.8.0 pytools-2019.1.1 pyyaml-5.1.2 pyzfp-0.3 pyzmq-18.1.0 scipy-1.3.1 sortedcontainers-2.1.0 sympy-1.4 tblib-1.4.0 toolz-0.10.0 tornado-6.0.3 traitlets-4.3.3 wcwidth-0.1.7 zict-1.0.0 zipp-0.6.0\n", + "Removing intermediate container dda878848f67\n", + " ---> aca43feeae5e\n", "Step 9/15 : ENV CONDA_AUTO_UPDATE_CONDA=false\n", - " ---> Using cache\n", - " ---> 7b012fbffee0\n", + " ---> Running in 0b8ba14eee73\n", + "Removing intermediate container 0b8ba14eee73\n", + " ---> 8e908fdb3492\n", "Step 10/15 : ENV CONDA_DEFAULT_ENV=$CONDA_ENV_NAME\n", - " ---> Using cache\n", - " ---> d693e9ba185b\n", + " ---> Running in b04f04081369\n", + "Removing intermediate container b04f04081369\n", + " ---> 9be194321ba2\n", "Step 11/15 : ENV CONDA_PREFIX=$CONDA_DIR/envs/$CONDA_DEFAULT_ENV\n", - " ---> Using cache\n", - " ---> 35f2022ed1b4\n", + " ---> Running in 464f87b1ebbe\n", + "Removing intermediate container 464f87b1ebbe\n", + " ---> c907c3cffd3e\n", "Step 12/15 : ENV PATH=$CONDA_PREFIX/bin:/opt/conda/bin:$PATH\n", - " ---> Using cache\n", - " ---> b917898481e7\n", + " ---> Running in 307eed25ed06\n", + "Removing intermediate container 307eed25ed06\n", + " ---> 071dfa04aba7\n", "Step 13/15 : RUN /opt/conda/bin/conda env update --name $CONDA_ENV_NAME -f /tmp/$CONDA_ENV_FILE_NAME && /opt/conda/bin/conda clean --yes --all\n", - " ---> Using cache\n", - " ---> dccfdefe03c4\n", + " ---> Running in 042a9e35656f\n", + "Collecting package metadata (repodata.json): ...working... done\n", + "Solving environment: ...working... done\n", + "\n", + "Downloading and Extracting Packages\n", + "babel-2.7.0 | 5.8 MB | ########## | 100% \n", + "pycodestyle-2.5.0 | 60 KB | ########## | 100% \n", + "gmpy2-2.0.8 | 165 KB | ########## | 100% \n", + "intel-openmp-2019.5 | 895 KB | ########## | 100% \n", + "graphite2-1.3.13 | 101 KB | ########## | 100% \n", + "fsspec-0.5.2 | 46 KB | ########## | 100% \n", + "alabaster-0.7.12 | 17 KB | ########## | 100% \n", + "pluggy-0.13.0 | 31 KB | ########## | 100% \n", + "wcwidth-0.1.7 | 25 KB | ########## | 100% \n", + "multidict-4.5.2 | 142 KB | ########## | 100% \n", + "locket-0.2.0 | 8 KB | ########## | 100% \n", + "pywavelets-1.0.3 | 4.4 MB | ########## | 100% \n", + "snakeviz-2.0.1 | 316 KB | ########## | 100% \n", + "blosc-1.17.0 | 862 KB | ########## | 100% \n", + "pixman-0.38.0 | 618 KB | ########## | 100% \n", + "distributed-2.5.2 | 396 KB | ########## | 100% \n", + "xz-5.2.4 | 366 KB | ########## | 100% \n", + "numexpr-2.7.0 | 196 KB | ########## | 100% \n", + "libgcc-ng-9.1.0 | 8.1 MB | ########## | 100% \n", + "cloudpickle-1.2.2 | 29 KB | ########## | 100% \n", + "pytest-5.2.1 | 366 KB | ########## | 100% \n", + "nb_conda_kernels-2.2 | 37 KB | ########## | 100% \n", + "libgfortran-ng-7.3.0 | 1.3 MB | ########## | 100% \n", + "jsonschema-3.0.2 | 90 KB | ########## | 100% \n", + "sphinxcontrib-qthelp | 26 KB | ########## | 100% \n", + "olefile-0.46 | 48 KB | ########## | 100% \n", + "cytoolz-0.10.0 | 439 KB | ########## | 100% \n", + "psutil-5.6.3 | 328 KB | ########## | 100% \n", + "networkx-2.3 | 1.1 MB | ########## | 100% \n", + "graphviz-2.40.1 | 6.9 MB | ########## | 100% \n", + "pyqt-5.9.2 | 5.6 MB | ########## | 100% \n", + "ipython_genutils-0.2 | 39 KB | ########## | 100% \n", + "scikit-image-0.15.0 | 28.4 MB | ########## | 100% \n", + "mkl_random-1.1.0 | 369 KB | ########## | 100% \n", + "coverage-4.5.4 | 226 KB | ########## | 100% \n", + "six-1.12.0 | 22 KB | ########## | 100% \n", + "gst-plugins-base-1.1 | 6.3 MB | ########## | 100% \n", + "libpng-1.6.37 | 364 KB | ########## | 100% \n", + "more-itertools-7.2.0 | 99 KB | ########## | 100% \n", + "pytables-3.5.2 | 1.5 MB | ########## | 100% \n", + "openssl-1.1.1 | 5.0 MB | ########## | 100% \n", + "libtiff-4.0.10 | 604 KB | ########## | 100% \n", + "ptyprocess-0.6.0 | 23 KB | ########## | 100% \n", + "urllib3-1.24.2 | 153 KB | ########## | 100% \n", + "dask-core-2.5.2 | 579 KB | ########## | 100% \n", + "defusedxml-0.6.0 | 23 KB | ########## | 100% \n", + "bokeh-1.3.4 | 4.0 MB | ########## | 100% \n", + "qt-5.9.7 | 85.9 MB | ########## | 100% \n", + "pyzmq-18.1.0 | 520 KB | ########## | 100% \n", + "prompt_toolkit-2.0.1 | 227 KB | ########## | 100% \n", + "mock-3.0.5 | 47 KB | ########## | 100% \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pandas-0.25.1 | 11.4 MB | ########## | 100% \n", + "icu-58.2 | 22.5 MB | ########## | 100% \n", + "dask-2.5.2 | 12 KB | ########## | 100% \n", + "ca-certificates-2019 | 132 KB | ########## | 100% \n", + "libedit-3.1.20181209 | 188 KB | ########## | 100% \n", + "numpy-1.17.2 | 4 KB | ########## | 100% \n", + "yaml-0.1.7 | 84 KB | ########## | 100% \n", + "certifi-2019.9.11 | 154 KB | ########## | 100% \n", + "imageio-2.6.0 | 3.3 MB | ########## | 100% \n", + "pexpect-4.7.0 | 82 KB | ########## | 100% \n", + "asn1crypto-1.0.1 | 161 KB | ########## | 100% \n", + "hdf5-1.10.4 | 5.3 MB | ########## | 100% \n", + "prometheus_client-0. | 42 KB | ########## | 100% \n", + "packaging-19.2 | 30 KB | ########## | 100% \n", + "appdirs-1.4.3 | 16 KB | ########## | 100% \n", + "cycler-0.10.0 | 13 KB | ########## | 100% \n", + "cython-0.29.13 | 2.2 MB | ########## | 100% \n", + "libstdcxx-ng-9.1.0 | 4.0 MB | ########## | 100% \n", + "cached-property-1.5. | 11 KB | ########## | 100% \n", + "docutils-0.15.2 | 742 KB | ########## | 100% \n", + "tk-8.6.8 | 3.1 MB | ########## | 100% \n", + "bzip2-1.0.8 | 105 KB | ########## | 100% \n", + "attrs-19.2.0 | 39 KB | ########## | 100% \n", + "glib-2.56.2 | 5.0 MB | ########## | 100% \n", + "ipykernel-5.1.2 | 165 KB | ########## | 100% \n", + "nb_conda-2.2.1 | 33 KB | ########## | 100% \n", + "pango-1.42.4 | 528 KB | ########## | 100% \n", + "mpmath-1.1.0 | 970 KB | ########## | 100% \n", + "pysocks-1.7.1 | 30 KB | ########## | 100% \n", + "dbus-1.13.6 | 587 KB | ########## | 100% \n", + "traitlets-4.3.3 | 137 KB | ########## | 100% \n", + "jupyter_client-5.3.3 | 137 KB | ########## | 100% \n", + "cffi-1.12.3 | 222 KB | ########## | 100% \n", + "mistune-0.8.4 | 54 KB | ########## | 100% \n", + "harfbuzz-1.8.8 | 863 KB | ########## | 100% \n", + "pcre-8.43 | 260 KB | ########## | 100% \n", + "pickleshare-0.7.5 | 13 KB | ########## | 100% \n", + "sip-4.19.13 | 293 KB | ########## | 100% \n", + "cryptography-2.7 | 617 KB | ########## | 100% \n", + "cairo-1.14.12 | 1.3 MB | ########## | 100% \n", + "python-3.6.9 | 34.4 MB | ########## | 100% \n", + "jpeg-9b | 247 KB | ########## | 100% \n", + "libffi-3.2.1 | 43 KB | ########## | 100% \n", + "mpc-1.1.0 | 94 KB | ########## | 100% \n", + "pyyaml-5.1.2 | 189 KB | ########## | 100% \n", + "nbconvert-5.6.0 | 494 KB | ########## | 100% \n", + "sortedcontainers-2.1 | 45 KB | ########## | 100% \n", + "heapdict-1.0.1 | 9 KB | ########## | 100% \n", + "sphinxcontrib-jsmath | 8 KB | ########## | 100% \n", + "pandocfilters-1.4.2 | 13 KB | ########## | 100% \n", + "jinja2-2.10.3 | 95 KB | ########## | 100% \n", + "markupsafe-1.1.1 | 29 KB | ########## | 100% \n", + "ipython-7.8.0 | 1.1 MB | ########## | 100% \n", + "sphinxcontrib-devhel | 23 KB | ########## | 100% \n", + "terminado-0.8.2 | 22 KB | ########## | 100% \n", + "chardet-3.0.4 | 197 KB | ########## | 100% \n", + "pytz-2019.3 | 231 KB | ########## | 100% \n", + "jedi-0.15.1 | 721 KB | ########## | 100% \n", + "pandoc-2.2.3.2 | 20.8 MB | ########## | 100% \n", + "click-7.0 | 118 KB | ########## | 100% \n", + "pip-19.2.3 | 1.9 MB | ########## | 100% \n", + "tornado-6.0.3 | 643 KB | ########## | 100% \n", + "zeromq-4.3.1 | 666 KB | ########## | 100% \n", + "mccabe-0.6.1 | 14 KB | ########## | 100% \n", + "notebook-6.0.1 | 6.0 MB | ########## | 100% \n", + "jupyter_core-4.5.0 | 48 KB | ########## | 100% \n", + "gstreamer-1.14.0 | 3.8 MB | ########## | 100% \n", + "kiwisolver-1.1.0 | 90 KB | ########## | 100% \n", + "backcall-0.1.0 | 19 KB | ########## | 100% \n", + "atomicwrites-1.3.0 | 13 KB | ########## | 100% \n", + "pyparsing-2.4.2 | 61 KB | ########## | 100% \n", + "py-cpuinfo-5.0.0 | 22 KB | ########## | 100% \n", + "pillow-6.2.0 | 647 KB | ########## | 100% \n", + "sympy-1.4 | 9.7 MB | ########## | 100% \n", + "partd-1.0.0 | 19 KB | ########## | 100% \n", + "toolz-0.10.0 | 50 KB | ########## | 100% \n", + "sphinxcontrib-htmlhe | 28 KB | ########## | 100% \n", + "zict-1.0.0 | 12 KB | ########## | 100% \n", + "expat-2.2.6 | 187 KB | ########## | 100% \n", + "idna-2.8 | 133 KB | ########## | 100% \n", + "decorator-4.4.0 | 18 KB | ########## | 100% \n", + "testpath-0.4.2 | 91 KB | ########## | 100% \n", + "readline-7.0 | 392 KB | ########## | 100% \n", + "fastcache-1.1.0 | 31 KB | ########## | 100% \n", + "zstd-1.3.7 | 887 KB | ########## | 100% \n", + "mkl-service-2.3.0 | 208 KB | ########## | 100% \n", + "pyflakes-2.1.1 | 105 KB | ########## | 100% \n", + "send2trash-1.5.0 | 16 KB | ########## | 100% \n", + "libsodium-1.0.16 | 302 KB | ########## | 100% \n", + "tblib-1.4.0 | 14 KB | ########## | 100% \n", + "gmp-6.1.2 | 744 KB | ########## | 100% \n", + "requests-2.22.0 | 89 KB | ########## | 100% \n", + "sqlite-3.30.0 | 1.9 MB | ########## | 100% \n", + "snowballstemmer-2.0. | 58 KB | ########## | 100% \n", + "libuuid-1.0.3 | 16 KB | ########## | 100% \n", + "libxml2-2.9.9 | 2.0 MB | ########## | 100% \n", + "pyrsistent-0.15.4 | 92 KB | ########## | 100% \n", + "python-dateutil-2.8. | 281 KB | ########## | 100% \n", + "flake8-3.7.8 | 134 KB | ########## | 100% \n", + "entrypoints-0.3 | 12 KB | ########## | 100% \n", + "h5py-2.9.0 | 1.2 MB | ########## | 100% \n", + "ncurses-6.1 | 958 KB | ########## | 100% \n", + "webencodings-0.5.1 | 19 KB | ########## | 100% \n", + "pycparser-2.19 | 174 KB | ########## | 100% \n", + "scipy-1.3.1 | 18.1 MB | ########## | 100% \n", + "freetype-2.9.1 | 822 KB | ########## | 100% \n", + "sphinxcontrib-appleh | 29 KB | ########## | 100% \n", + "blas-1.0 | 6 KB | ########## | 100% \n", + "importlib_metadata-0 | 43 KB | ########## | 100% \n", + "matplotlib-3.1.1 | 6.7 MB | ########## | 100% \n", + "wheel-0.33.6 | 40 KB | ########## | 100% \n", + "lzo-2.10 | 314 KB | ########## | 100% \n", + "pyopenssl-19.0.0 | 82 KB | ########## | 100% \n", + "fribidi-1.0.5 | 112 KB | ########## | 100% \n", + "mkl-2019.5 | 205.3 MB | ########## | 100% \n", + "msgpack-python-0.6.1 | 92 KB | ########## | 100% \n", + "py-1.8.0 | 140 KB | ########## | 100% \n", + "python-graphviz-0.10 | 22 KB | ########## | 100% \n", + "mkl_fft-1.0.14 | 173 KB | ########## | 100% \n", + "imagesize-1.1.0 | 9 KB | ########## | 100% \n", + "pygments-2.4.2 | 664 KB | ########## | 100% \n", + "libxcb-1.13 | 502 KB | ########## | 100% \n", + "setuptools-41.4.0 | 673 KB | ########## | 100% \n", + "nbformat-4.4.0 | 141 KB | ########## | 100% \n", + "fontconfig-2.13.0 | 291 KB | ########## | 100% \n", + "bleach-3.1.0 | 226 KB | ########## | 100% \n", + "zlib-1.2.11 | 120 KB | ########## | 100% \n", + "sphinxcontrib-serial | 24 KB | ########## | 100% \n", + "numpy-base-1.17.2 | 5.3 MB | ########## | 100% \n", + "zipp-0.6.0 | 9 KB | ########## | 100% \n", + "mpfr-4.0.1 | 575 KB | ########## | 100% \n", + "Preparing transaction: ...working... done\n", + "Verifying transaction: ...working... done\n", + "Executing transaction: ...working... b'Disabling nb_conda_kernels...\\nStatus: disabled\\n'\n", + "b'Enabling nb_conda_kernels...\\nStatus: enabled\\n'\n", + "b'Enabling notebook extension nb_conda/main...\\n - Validating: \\x1b[32mOK\\x1b[0m\\nEnabling tree extension nb_conda/tree...\\n - Validating: \\x1b[32mOK\\x1b[0m\\nEnabling: nb_conda\\n- Writing config: /opt/conda/envs/fwi01_conda_env/etc/jupyter\\n - Validating...\\n nb_conda 2.2.1 \\x1b[32mOK\\x1b[0m\\n'\n", + "done\n", + "Ran pip subprocess with arguments:\n", + "['/opt/conda/envs/fwi01_conda_env/bin/python', '-m', 'pip', 'install', '-U', '-r', '/tmp/condaenv.gapu_08k.requirements.txt']\n", + "Pip subprocess output:\n", + "Collecting anytree (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 1))\n", + "Collecting azureml-sdk[automl,notebooks]==1.0.65 (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/13/db/263ba9e8665480a0e567dbe45b5c7b5c6e041fa5bf5233b2faa78aeacc38/azureml_sdk-1.0.65-py3-none-any.whl\n", + "Collecting codepy (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 3))\n", + " Using cached https://files.pythonhosted.org/packages/6c/81/338a4d4145af7857f9b6fdf9b4d53c58c7eb4c1d092ff6c010efdb4dfdf3/codepy-2019.1.tar.gz\n", + "Collecting papermill[azure] (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/33/f8/6d821081eddec7405d719838c532beb8e09beb7b911db200e7768c239058/papermill-1.2.0-py2.py3-none-any.whl\n", + "Collecting pyrevolve (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 5))\n", + " Using cached https://files.pythonhosted.org/packages/0d/c4/63a36aa56969de861e181b86ebc1aba6764e9d2566c42e4faf1f360cb3cc/pyrevolve-2.1.3.tar.gz\n", + "Requirement already satisfied, skipping upgrade: six>=1.9.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from anytree->-r /tmp/condaenv.gapu_08k.requirements.txt (line 1)) (1.12.0)\n", + "Collecting azureml-core==1.0.65.* (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/a0/13/65465d5b95a0d9bf1b11f436d462e332c4157db0667d761648a8a49b3bda/azureml_core-1.0.65.1-py2.py3-none-any.whl (1.1MB)\n", + "Collecting azureml-dataprep[fuse]<1.2.0a,>=1.1.19a (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/06/cf/bdbeee2533e1837bbc26fae8ef0abf0d3abf4a502ebd545fff3f9b32055d/azureml_dataprep-1.1.20-py3-none-any.whl (26.8MB)\n", + "Collecting azureml-pipeline==1.0.65.* (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/9f/91/ea1691ad094fa398f445c2324115e7c351f35c513931bb4e4153de88c563/azureml_pipeline-1.0.65-py3-none-any.whl\n", + "Collecting azureml-train==1.0.65.* (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/0f/94/c92c9540e119b810e96441a619bdd7d5c1be003af0670bb1c3d8caf90994/azureml_train-1.0.65-py3-none-any.whl\n", + "Collecting azureml-train-automl==1.0.65.*; extra == \"automl\" (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/de/04/16f4ef876c80a0c9b97bfdc622a03f29996d2f43fa343db242907c601c79/azureml_train_automl-1.0.65-py3-none-any.whl (111kB)\n", + "Collecting azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\" (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/e8/2c/46419283bc6b4e504893c4222048ea6bbdb60dfeaced1772650f0f044b10/azureml_contrib_notebook-1.0.65-py2.py3-none-any.whl\n", + "Collecting azureml-widgets==1.0.65.*; extra == \"notebooks\" (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/cd/1b/2cfafcb7ffee606d2a7ed2237535a3b8903e04a323a080ad7118191de7f5/azureml_widgets-1.0.65-py3-none-any.whl (9.2MB)\n", + "Requirement already satisfied, skipping upgrade: pytools>=2015.1.2 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (2019.1.1)\n", + "Requirement already satisfied, skipping upgrade: numpy>=1.6 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (1.17.2)\n", + "Requirement already satisfied, skipping upgrade: appdirs>=1.4.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (1.4.3)\n", + "Requirement already satisfied, skipping upgrade: cgen in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (2019.1)\n", + "Requirement already satisfied, skipping upgrade: entrypoints in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.3)\n", + "Requirement already satisfied, skipping upgrade: jupyter-client in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (5.3.3)\n", + "Requirement already satisfied, skipping upgrade: nbconvert>=5.5 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (5.6.0)\n", + "Requirement already satisfied, skipping upgrade: click in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (7.0)\n", + "Collecting future (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/90/52/e20466b85000a181e1e144fd8305caf2cf475e2f9674e797b222f8105f5f/future-0.17.1.tar.gz (829kB)\n", + "Collecting tenacity (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/1e/a1/be8c8610f4620c56790965ba2b564dd76d13cbcd7c2ff8f6053ce63027fb/tenacity-5.1.1-py2.py3-none-any.whl\n", + "Requirement already satisfied, skipping upgrade: pyyaml in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (5.1.2)\n", + "Requirement already satisfied, skipping upgrade: requests in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.22.0)\n", + "Collecting ansiwrap (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/03/50/43e775a63e0d632d9be3b3fa1c9b2cbaf3b7870d203655710a3426f47c26/ansiwrap-0.8.4-py2.py3-none-any.whl\n", + "Collecting tqdm>=4.32.2 (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/e1/c1/bc1dba38b48f4ae3c4428aea669c5e27bd5a7642a74c8348451e0bd8ff86/tqdm-4.36.1-py2.py3-none-any.whl (52kB)\n", + "Requirement already satisfied, skipping upgrade: nbformat in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (4.4.0)\n", + "Collecting azure-storage-blob; extra == \"azure\" (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/3e/84/610f379b46d7d3c2d48eadeed6a12b6d46a43100fea70534f5992d0ac996/azure_storage_blob-2.1.0-py2.py3-none-any.whl (88kB)\n", + "Collecting azure-datalake-store>=0.0.30; extra == \"azure\" (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/2d/8e/7f990443bd3d3b6a9112ff8d15d93216e67378a037ec9090d7dd248eec2d/azure_datalake_store-0.0.47-py2.py3-none-any.whl (53kB)\n", + "Collecting blosc (from pyrevolve->-r /tmp/condaenv.gapu_08k.requirements.txt (line 5))\n", + " Using cached https://files.pythonhosted.org/packages/6d/3b/2b707cd330a205ba5c69b5e8bfa9c05691442e45ce9ce882c4c8d343e61a/blosc-1.8.1.tar.gz\n", + "Collecting contexttimer (from pyrevolve->-r /tmp/condaenv.gapu_08k.requirements.txt (line 5))\n", + " Using cached https://files.pythonhosted.org/packages/1d/e0/504aa08a83dc2ff90f61a83b5f70d689e1f5138ab30576124ea2ff9f5076/contexttimer-0.3.3.tar.gz\n", + "Collecting pyzfp (from pyrevolve->-r /tmp/condaenv.gapu_08k.requirements.txt (line 5))\n", + " Using cached https://files.pythonhosted.org/packages/33/ff/1390221dc5ed78eb6573613a76927baeea02c31e73b9db91353b8834bb95/pyzfp-0.3.tar.gz\n", + "Collecting azure-common>=1.1.12 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/00/55/a703923c12cd3172d5c007beda0c1a34342a17a6a72779f8a7c269af0cd6/azure_common-1.1.23-py2.py3-none-any.whl\n", + "Collecting pathspec (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/7a/68/5902e8cd7f7b17c5879982a3a3ee2ad0c3b92b80c79989a2d3e1ca8d29e1/pathspec-0.6.0.tar.gz\n", + "Collecting jsonpickle (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/07/07/c157520a3ebd166c8c24c6ae0ecae7c3968eb4653ff0e5af369bb82f004d/jsonpickle-1.2-py2.py3-none-any.whl\n", + "Requirement already satisfied, skipping upgrade: pyopenssl in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (19.0.0)\n", + "Collecting msrest>=0.5.1 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/27/b0/c34b3ea9b2ed74b800520fbefb312cdb7f05c20b8bd42e5e7662a5614f98/msrest-0.6.10-py2.py3-none-any.whl (82kB)\n", + "Collecting SecretStorage (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/82/59/cb226752e20d83598d7fdcabd7819570b0329a61db07cfbdd21b2ef546e3/SecretStorage-3.1.1-py3-none-any.whl\n", + "Collecting contextlib2 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/cf/e5/989798d38831a8505d62687c94b0f2954ff0a40782e25f9add8ed675dc1f/contextlib2-0.6.0-py2.py3-none-any.whl\n", + "Collecting ruamel.yaml<=0.15.89,>=0.15.35 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/36/e1/cc2fa400fa5ffde3efa834ceb15c464075586de05ca3c553753dcd6f1d3b/ruamel.yaml-0.15.89-cp36-cp36m-manylinux1_x86_64.whl (651kB)\n", + "Collecting PyJWT (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/87/8b/6a9f14b5f781697e51259d81657e6048fd31a113229cf346880bb7545565/PyJWT-1.7.1-py2.py3-none-any.whl\n", + "Requirement already satisfied, skipping upgrade: cryptography!=1.9,!=2.0.*,!=2.1.*,!=2.2.* in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (2.7)\n", + "Collecting azure-mgmt-resource>=1.2.1 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/7c/0d/80815326fa04f2a73ea94b0f57c29669c89df5aa5f5e285952f6445a91c4/azure_mgmt_resource-5.1.0-py2.py3-none-any.whl (681kB)\n", + "Collecting msrestazure>=0.4.33 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/68/75/5cb56ca8cbc6c5fe476e4878c73f57a331edcf55e5d3fcb4a7377d7d659d/msrestazure-0.6.2-py2.py3-none-any.whl (40kB)\n", + "Requirement already satisfied, skipping upgrade: python-dateutil>=2.7.3 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (2.8.0)\n", + "Collecting adal>=1.2.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/4f/b5/3ea9ae3d1096b9ff31e8f1846c47d49f3129a12464ac0a73b602de458298/adal-1.2.2-py2.py3-none-any.whl (53kB)\n", + "Requirement already satisfied, skipping upgrade: urllib3>=1.23 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (1.24.2)\n", + "Collecting azure-mgmt-authorization>=0.40.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/5e/17/4724694ddb3311955ddc367eddcd0928f8ee2c7b12d5a6f0b12bca0b03db/azure_mgmt_authorization-0.60.0-py2.py3-none-any.whl (82kB)\n", + "Requirement already satisfied, skipping upgrade: pytz in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (2019.3)\n", + "Collecting azure-mgmt-storage>=1.5.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/a1/71/c1f73fa452b7f2e1b5567621a1386907cb4591ef8335c012ab4b358fb090/azure_mgmt_storage-4.2.0-py2.py3-none-any.whl (435kB)\n", + "Collecting backports.tempfile (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/b4/5c/077f910632476281428fe254807952eb47ca78e720d059a46178c541e669/backports.tempfile-1.0-py2.py3-none-any.whl\n", + "Collecting docker (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/cc/ca/699d4754a932787ef353a157ada74efd1ceb6d1fc0bfb7989ae1e7b33111/docker-4.1.0-py2.py3-none-any.whl (139kB)\n", + "Collecting azure-mgmt-keyvault>=0.40.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/b3/d1/9fed0a3a3b43d0b1ad59599b5c836ccc4cf117e26458075385bafe79575b/azure_mgmt_keyvault-2.0.0-py2.py3-none-any.whl (80kB)\n", + "Collecting ndg-httpsclient (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/fb/67/c2f508c00ed2a6911541494504b7cac16fe0b0473912568df65fd1801132/ndg_httpsclient-0.5.1-py3-none-any.whl\n", + "Collecting jmespath (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/83/94/7179c3832a6d45b266ddb2aac329e101367fbdb11f425f13771d27f225bb/jmespath-0.9.4-py2.py3-none-any.whl\n", + "Collecting azure-graphrbac>=0.40.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/3e/93/02056aca45162f9fc275d1eaad12a2a07ef92375afb48eabddc4134b8315/azure_graphrbac-0.61.1-py2.py3-none-any.whl (141kB)\n", + "Collecting azure-mgmt-containerregistry>=2.0.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/97/70/8c2d0509db466678eba16fa2b0a539499f3b351b1f2993126ad843d5be13/azure_mgmt_containerregistry-2.8.0-py2.py3-none-any.whl (718kB)\n", + "Collecting dotnetcore2>=2.1.9 (from azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/e7/11/699ec6c3ec5e73437834203e4b530fdc760517498208eaa0b37a7f1e10af/dotnetcore2-2.1.9-py3-none-manylinux1_x86_64.whl (29.3MB)\n", + "Requirement already satisfied, skipping upgrade: cloudpickle>=1.1.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (1.2.2)\n", + "Collecting azureml-dataprep-native<14.0.0,>=13.1.0 (from azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/2a/b8/36399cebff8045a05cb69181771e450101ea3ac14f02ad564d63634b8aca/azureml_dataprep_native-13.1.0-cp36-cp36m-manylinux1_x86_64.whl (1.3MB)\n", + "Collecting fusepy>=3.0.1; extra == \"fuse\" (from azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/04/0b/4506cb2e831cea4b0214d3625430e921faaa05a7fb520458c75a2dbd2152/fusepy-3.0.1.tar.gz\n", + "Collecting azureml-pipeline-steps==1.0.65.* (from azureml-pipeline==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/68/23/f90b08fbd0313a8846fb4fc0df1f0298dfe85d52b6b92ef73e19386ad953/azureml_pipeline_steps-1.0.65-py3-none-any.whl\n", + "Collecting azureml-pipeline-core==1.0.65.* (from azureml-pipeline==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/9b/11/7f38ae758036515937abe745507132530a0bee56482d3c9c0af4a1d54ad6/azureml_pipeline_core-1.0.65-py2.py3-none-any.whl (252kB)\n", + "Collecting azureml-train-core==1.0.65.* (from azureml-train==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/11/63/b61d89600236648f0afe909bea5afb69912ea33f2b492a9722aad24eaf1e/azureml_train_core-1.0.65-py3-none-any.whl (76kB)\n", + "Collecting dill>=0.2.8 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/c7/11/345f3173809cea7f1a193bfbf02403fff250a3360e0e118a1630985e547d/dill-0.3.1.1.tar.gz (151kB)\n", + "Collecting statsmodels>=0.9.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/60/d6/e9859e68e7d6c916fdff7d8e0958a7f5813485c52fc20d061273eaaddb0c/statsmodels-0.10.1-cp36-cp36m-manylinux1_x86_64.whl (8.1MB)\n", + "Collecting sklearn-pandas<=1.7.0,>=1.4.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/7e/9c/c94f46b40b86d2c77c46c4c1b858fc66c117b4390665eca28f2e0812db45/sklearn_pandas-1.7.0-py2.py3-none-any.whl\n", + "Collecting skl2onnx==1.4.9 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/40/4d/630598ba5cbf7b6522158c236b9e74930f14bbc9324b61ddc40f252bab40/skl2onnx-1.4.9-py2.py3-none-any.whl (114kB)\n", + "Collecting resource>=0.1.8 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/34/ad/9cd037c01c075f9a273c23557f8e71195d773d59d3881bbb26011d396c8b/Resource-0.2.1-py2.py3-none-any.whl\n", + "Collecting azureml-telemetry==1.0.65.* (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/98/e8/71a5e9a68f81255eeb87217f5bb79043da3f665a819a1962f109e46ba0cd/azureml_telemetry-1.0.65-py3-none-any.whl\n", + "Collecting scikit-learn<=0.20.3,>=0.19.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/5e/82/c0de5839d613b82bddd088599ac0bbfbbbcbd8ca470680658352d2c435bd/scikit_learn-0.20.3-cp36-cp36m-manylinux1_x86_64.whl (5.4MB)\n", + "Collecting lightgbm<=2.2.3,>=2.0.11 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/77/0f/5157e6b153b3d4a70dc5fbe2ab6f209604197590f387f03177b7a249ac60/lightgbm-2.2.3-py2.py3-none-manylinux1_x86_64.whl (1.2MB)\n", + "Collecting gensim (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/d1/dd/112bd4258cee11e0baaaba064060eb156475a42362e59e3ff28e7ca2d29d/gensim-3.8.1-cp36-cp36m-manylinux1_x86_64.whl (24.2MB)\n", + "Collecting onnx>=1.5.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/f5/f4/e126b60d109ad1e80020071484b935980b7cce1e4796073aab086a2d6902/onnx-1.6.0-cp36-cp36m-manylinux1_x86_64.whl (4.8MB)\n", + "Collecting wheel==0.30.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/0c/80/16a85b47702a1f47a63c104c91abdd0a6704ee8ae3b4ce4afc49bc39f9d9/wheel-0.30.0-py2.py3-none-any.whl (49kB)\n", + "Collecting pandas<=0.23.4,>=0.21.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/e1/d8/feeb346d41f181e83fba45224ab14a8d8af019b48af742e047f3845d8cff/pandas-0.23.4-cp36-cp36m-manylinux1_x86_64.whl (8.9MB)\n", + "Collecting scipy<=1.1.0,>=1.0.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/a8/0b/f163da98d3a01b3e0ef1cab8dd2123c34aee2bafbb1c5bffa354cc8a1730/scipy-1.1.0-cp36-cp36m-manylinux1_x86_64.whl (31.2MB)\n", + "Collecting azureml-automl-core==1.0.65.* (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/02/e6/8bd092836c9d2a3f37fc0da29cb3052a0c200a49a22232c202bc8a15db5b/azureml_automl_core-1.0.65.1-py3-none-any.whl (2.0MB)\n", + "Collecting onnxconverter-common>=1.4.2 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/52/55/73c3b38364dff5eeab4442d6c5e96c336483cd188c536a2e30c625f41cd7/onnxconverter_common-1.5.5-py2.py3-none-any.whl (42kB)\n", + "Collecting patsy>=0.5.1 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/ea/0c/5f61f1a3d4385d6bf83b83ea495068857ff8dfb89e74824c6e9eb63286d8/patsy-0.5.1-py2.py3-none-any.whl (231kB)\n", + "Collecting onnxmltools==1.4.1 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/78/7f/1d47b82f1e98f742018e4c4c227ec9bd0ae1efdba3a6dfd7d30c45881fb9/onnxmltools-1.4.1-py2.py3-none-any.whl (371kB)\n", + "Requirement already satisfied, skipping upgrade: ipykernel in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (5.1.2)\n", + "Requirement already satisfied, skipping upgrade: ipython in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (7.8.0)\n", + "Collecting ipywidgets>=7.0.0 (from azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/56/a0/dbcf5881bb2f51e8db678211907f16ea0a182b232c591a6d6f276985ca95/ipywidgets-7.5.1-py2.py3-none-any.whl (121kB)\n", + "Requirement already satisfied, skipping upgrade: decorator>=3.2.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from pytools>=2015.1.2->codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (4.4.0)\n", + "Requirement already satisfied, skipping upgrade: traitlets in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jupyter-client->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (4.3.3)\n", + "Requirement already satisfied, skipping upgrade: tornado>=4.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jupyter-client->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (6.0.3)\n", + "Requirement already satisfied, skipping upgrade: pyzmq>=13 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jupyter-client->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (18.1.0)\n", + "Requirement already satisfied, skipping upgrade: jupyter-core in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jupyter-client->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (4.5.0)\n", + "Requirement already satisfied, skipping upgrade: defusedxml in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.6.0)\n", + "Requirement already satisfied, skipping upgrade: jinja2>=2.4 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.10.3)\n", + "Requirement already satisfied, skipping upgrade: pygments in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.4.2)\n", + "Requirement already satisfied, skipping upgrade: mistune<2,>=0.8.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.8.4)\n", + "Requirement already satisfied, skipping upgrade: bleach in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (3.1.0)\n", + "Requirement already satisfied, skipping upgrade: pandocfilters>=1.4.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (1.4.2)\n", + "Requirement already satisfied, skipping upgrade: testpath in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.4.2)\n", + "Requirement already satisfied, skipping upgrade: idna<2.9,>=2.5 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from requests->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.8)\n", + "Requirement already satisfied, skipping upgrade: certifi>=2017.4.17 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from requests->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2019.9.11)\n", + "Requirement already satisfied, skipping upgrade: chardet<3.1.0,>=3.0.2 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from requests->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (3.0.4)\n", + "Collecting textwrap3>=0.9.2 (from ansiwrap->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/77/9c/a53e561d496ee5866bbeea4d3a850b3b545ed854f8a21007c1e0d872e94d/textwrap3-0.9.2-py2.py3-none-any.whl\n", + "Requirement already satisfied, skipping upgrade: ipython-genutils in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbformat->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.2.0)\n", + "Requirement already satisfied, skipping upgrade: jsonschema!=2.5.0,>=2.4 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbformat->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (3.0.2)\n", + "Collecting azure-storage-common~=2.1 (from azure-storage-blob; extra == \"azure\"->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", + " Downloading https://files.pythonhosted.org/packages/6b/a0/6794b318ce0118d1a4053bdf0149a60807407db9b710354f2b203c2f5975/azure_storage_common-2.1.0-py2.py3-none-any.whl (47kB)\n", + "Requirement already satisfied, skipping upgrade: cffi in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azure-datalake-store>=0.0.30; extra == \"azure\"->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (1.12.3)\n", + "Collecting requests-oauthlib>=0.5.0 (from msrest>=0.5.1->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/c2/e2/9fd03d55ffb70fe51f587f20bcf407a6927eb121de86928b34d162f0b1ac/requests_oauthlib-1.2.0-py2.py3-none-any.whl\n", + "Collecting isodate>=0.6.0 (from msrest>=0.5.1->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/9b/9f/b36f7774ff5ea8e428fdcfc4bb332c39ee5b9362ddd3d40d9516a55221b2/isodate-0.6.0-py2.py3-none-any.whl (45kB)\n", + "Collecting jeepney (from SecretStorage->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/0a/4c/ef880713a6c6d628869596703167eab2edf8e0ec2d870d1089dcb0901b81/jeepney-0.4.1-py3-none-any.whl (60kB)\n", + "Requirement already satisfied, skipping upgrade: asn1crypto>=0.21.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from cryptography!=1.9,!=2.0.*,!=2.1.*,!=2.2.*->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (1.0.1)\n", + "Collecting backports.weakref (from backports.tempfile->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/88/ec/f598b633c3d5ffe267aaada57d961c94fdfa183c5c3ebda2b6d151943db6/backports.weakref-1.0.post1-py2.py3-none-any.whl\n", + "Collecting websocket-client>=0.32.0 (from docker->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/29/19/44753eab1fdb50770ac69605527e8859468f3c0fd7dc5a76dd9c4dbd7906/websocket_client-0.56.0-py2.py3-none-any.whl (200kB)\n", + "Collecting pyasn1>=0.1.1 (from ndg-httpsclient->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/a1/71/8f0d444e3a74e5640a3d5d967c1c6b015da9c655f35b2d308a55d907a517/pyasn1-0.4.7-py2.py3-none-any.whl (76kB)\n", + "Collecting distro>=1.2.0 (from dotnetcore2>=2.1.9->azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/ea/35/82f79b92fa4d937146c660a6482cee4f3dfa1f97ff3d2a6f3ecba33e712e/distro-1.4.0-py2.py3-none-any.whl\n", + "Collecting azureml-train-restclients-hyperdrive==1.0.65.* (from azureml-train-core==1.0.65.*->azureml-train==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/f7/d2/56427a10de73db01360d7a8c87f75b68e06a8eec6db6ec1c31d3cac2ab12/azureml_train_restclients_hyperdrive-1.0.65-py3-no\u001b[91m\n", + "\n", + "==> WARNING: A newer version of conda exists. <==\n", + " current version: 4.7.10\n", + " latest version: 4.7.12\n", + "\n", + "Please update conda by running\n", + "\n", + " $ conda update -n base -c defaults conda\n", + "\n", + "\n", + "\u001b[0mne-any.whl\n", + "Collecting protobuf (from skl2onnx==1.4.9->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/a8/52/d8d2dbff74b8bf517c42db8d44c3f9ef6555e6f5d6caddfa3f207b9143df/protobuf-3.10.0-cp36-cp36m-manylinux1_x86_64.whl (1.3MB)\n", + "Collecting python-easyconfig>=0.1.0 (from resource>=0.1.8->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/b1/86/1138081cca360a02066eedaf301d0f358c35e0e0d67572acf9d6354edca9/Python_EasyConfig-0.1.7-py2.py3-none-any.whl\n", + "Collecting JsonForm>=0.0.2 (from resource>=0.1.8->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/4f/b7/b9491ba4b709d0616fab15a89f8efe4d3a7924652e1fdd4f15303e9ecdf0/JsonForm-0.0.2.tar.gz\n", + "Collecting JsonSir>=0.0.2 (from resource>=0.1.8->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/aa/bf/5c00c1dafaa3ca2c32e7641d9c2c6f9d6d76e127bde00eb600333a60c5bc/JsonSir-0.0.2.tar.gz\n", + "Collecting applicationinsights (from azureml-telemetry==1.0.65.*->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/a1/53/234c53004f71f0717d8acd37876e0b65c121181167057b9ce1b1795f96a0/applicationinsights-0.11.9-py2.py3-none-any.whl (58kB)\n", + "Collecting smart-open>=1.8.1 (from gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/37/c0/25d19badc495428dec6a4bf7782de617ee0246a9211af75b302a2681dea7/smart_open-1.8.4.tar.gz (63kB)\n", + "Collecting typing-extensions>=3.6.2.1 (from onnx>=1.5.0->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/27/aa/bd1442cfb0224da1b671ab334d3b0a4302e4161ea916e28904ff9618d471/typing_extensions-3.7.4-py3-none-any.whl\n", + "Collecting nimbusml>=1.4.1 (from azureml-automl-core==1.0.65.*->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/31/f8/b9cd6f214cd4fb24081c379d6374fdf549c3960f64229937e08803bdfdd1/nimbusml-1.5.0-cp36-none-manylinux1_x86_64.whl (104.9MB)\n", + "Collecting pmdarima==1.1.1 (from azureml-automl-core==1.0.65.*->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/a2/2a/f982baa2ea936c576597e957d0e2e57f7eac1b4618d2b18c16fa6d652b18/pmdarima-1.1.1-cp36-cp36m-manylinux1_x86_64.whl (682kB)\n", + "Collecting keras2onnx (from onnxmltools==1.4.1->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/3b/76/f6b4afe9c0b3b46318498324897b8696cd9f976de8c0e4058ec619850c8d/keras2onnx-1.5.2-py3-none-any.whl (216kB)\n", + "Requirement already satisfied, skipping upgrade: backcall in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.1.0)\n", + "Requirement already satisfied, skipping upgrade: setuptools>=18.5 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (41.4.0)\n", + "Requirement already satisfied, skipping upgrade: pickleshare in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.7.5)\n", + "Requirement already satisfied, skipping upgrade: pexpect; sys_platform != \"win32\" in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (4.7.0)\n", + "Requirement already satisfied, skipping upgrade: jedi>=0.10 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.15.1)\n", + "Requirement already satisfied, skipping upgrade: prompt-toolkit<2.1.0,>=2.0.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (2.0.10)\n", + "Collecting widgetsnbextension~=3.5.0 (from ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/6c/7b/7ac231c20d2d33c445eaacf8a433f4e22c60677eb9776c7c5262d7ddee2d/widgetsnbextension-3.5.1-py2.py3-none-any.whl (2.2MB)\n", + "Requirement already satisfied, skipping upgrade: MarkupSafe>=0.23 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jinja2>=2.4->nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (1.1.1)\n", + "Requirement already satisfied, skipping upgrade: webencodings in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from bleach->nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.5.1)\n", + "Requirement already satisfied, skipping upgrade: pyrsistent>=0.14.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.15.4)\n", + "Requirement already satisfied, skipping upgrade: attrs>=17.4.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (19.2.0)\n", + "Requirement already satisfied, skipping upgrade: pycparser in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from cffi->azure-datalake-store>=0.0.30; extra == \"azure\"->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.19)\n", + "Collecting oauthlib>=3.0.0 (from requests-oauthlib>=0.5.0->msrest>=0.5.1->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/05/57/ce2e7a8fa7c0afb54a0581b14a65b56e62b5759dbc98e80627142b8a3704/oauthlib-3.1.0-py2.py3-none-any.whl (147kB)\n", + "Collecting boto>=2.32 (from smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/23/10/c0b78c27298029e4454a472a1919bde20cb182dab1662cec7f2ca1dcc523/boto-2.49.0-py2.py3-none-any.whl (1.4MB)\n", + "Collecting boto3 (from smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/42/b5/03da45c451b4e7d31bc42e3b844cbb2e9fc6e8757705504d828a7295b27e/boto3-1.9.245-py2.py3-none-any.whl (128kB)\n", + "Requirement already satisfied, skipping upgrade: Cython>=0.29 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from pmdarima==1.1.1->azureml-automl-core==1.0.65.*->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.29.13)\n", + "Collecting fire (from keras2onnx->onnxmltools==1.4.1->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/d9/69/faeaae8687f4de0f5973694d02e9d6c3eb827636a009157352d98de1129e/fire-0.2.1.tar.gz (76kB)\n", + "Requirement already satisfied, skipping upgrade: ptyprocess>=0.5 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from pexpect; sys_platform != \"win32\"->ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.6.0)\n", + "Requirement already satisfied, skipping upgrade: parso>=0.5.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jedi>=0.10->ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.5.1)\n", + "Requirement already satisfied, skipping upgrade: wcwidth in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from prompt-toolkit<2.1.0,>=2.0.0->ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.1.7)\n", + "Requirement already satisfied, skipping upgrade: notebook>=4.4.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from widgetsnbextension~=3.5.0->ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (6.0.1)\n", + "Collecting s3transfer<0.3.0,>=0.2.0 (from boto3->smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/16/8a/1fc3dba0c4923c2a76e1ff0d52b305c44606da63f718d14d3231e21c51b0/s3transfer-0.2.1-py2.py3-none-any.whl (70kB)\n", + "Collecting botocore<1.13.0,>=1.12.245 (from boto3->smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/0e/84/b13b233a344de26796b7b457571c16f2b3d1fab8fb48931208d650cc4d97/botocore-1.12.245-py2.py3-none-any.whl (5.7MB)\n", + "Collecting termcolor (from fire->keras2onnx->onnxmltools==1.4.1->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", + " Downloading https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz\n", + "Requirement already satisfied, skipping upgrade: terminado>=0.8.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.8.2)\n", + "Requirement already satisfied, skipping upgrade: Send2Trash in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (1.5.0)\n", + "Requirement already satisfied, skipping upgrade: prometheus-client in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.7.1)\n", + "Requirement already satisfied, skipping upgrade: docutils<0.16,>=0.10 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from botocore<1.13.0,>=1.12.245->boto3->smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.15.2)\n", + "Building wheels for collected packages: codepy, pyrevolve, future, blosc, contexttimer, pyzfp, pathspec, fusepy, dill, JsonForm, JsonSir, smart-open, fire, termcolor\n", + " Building wheel for codepy (setup.py): started\n", + " Building wheel for codepy (setup.py): finished with status 'done'\n", + " Created wheel for codepy: filename=codepy-2019.1-cp36-none-any.whl size=19314 sha256=b962a33a50bc0209652a378feb522f721b57fb8d3b2230ffbce0750ef5f82f5c\n", + " Stored in directory: /root/.cache/pip/wheels/f4/53/e7/b53cf7ba45381b676bbd5eaaedc19ae82e1c397e9c1766ddf4\n", + " Building wheel for pyrevolve (setup.py): started\n", + " Building wheel for pyrevolve (setup.py): finished with status 'done'\n", + " Created wheel for pyrevolve: filename=pyrevolve-2.1.3-cp36-cp36m-linux_x86_64.whl size=332357 sha256=68a372a67f717b85fd217faa42b484c869405a4a888880f4f0fbb79b6962fd09\n", + " Stored in directory: /root/.cache/pip/wheels/df/d7/36/3e8e92a06a23446febeb604b528faeffba1a26c0d63e924a0a\n", + " Building wheel for future (setup.py): started\n", + " Building wheel for future (setup.py): finished with status 'done'\n", + " Created wheel for future: filename=future-0.17.1-cp36-none-any.whl size=488730 sha256=9b057117fd3a3945eef31c3e9cfa9c768c5c7423da8d1b62bb9046f2e1d48f7d\n", + " Stored in directory: /root/.cache/pip/wheels/0c/61/d2/d6b7317325828fbb39ee6ad559dbe4664d0896da4721bf379e\n", + " Building wheel for blosc (setup.py): started\n", + " Building wheel for blosc (setup.py): finished with status 'done'\n", + " Created wheel for blosc: filename=blosc-1.8.1-cp36-cp36m-linux_x86_64.whl size=695333 sha256=4c695d6b4e45ac7c104e964ec63a4633f7d302328f5e167e9c21f7699aedf007\n", + " Stored in directory: /root/.cache/pip/wheels/3b/e0/b9/99a77fb1821f0df30e52b9ce470c74efa2ca359ff8c21f8e17\n", + " Building wheel for contexttimer (setup.py): started\n", + " Building wheel for contexttimer (setup.py): finished with status 'done'\n", + " Created wheel for contexttimer: filename=contexttimer-0.3.3-cp36-none-any.whl size=5818 sha256=cdee0441a4d88676fd0819908a4afd35dbb8bdcfb53975ffe0c70fe18e3572af\n", + " Stored in directory: /root/.cache/pip/wheels/b3/e2/35/565145ce0127c7451b6503dfabb2b56e9908c863e40c6b1870\n", + " Building wheel for pyzfp (setup.py): started\n", + " Building wheel for pyzfp (setup.py): finished with status 'done'\n", + " Created wheel for pyzfp: filename=pyzfp-0.3-cp36-cp36m-linux_x86_64.whl size=469637 sha256=335eadb4f3f696e6c3c4ee58f681a81d2f16f22c3c39f81ec1557ebec040e0a7\n", + " Stored in directory: /root/.cache/pip/wheels/a6/b5/c8/60b5a2d3fd3cbb49c37935bd498037ce18dae811d9e301b885\n", + " Building wheel for pathspec (setup.py): started\n", + " Building wheel for pathspec (setup.py): finished with status 'done'\n", + " Created wheel for pathspec: filename=pathspec-0.6.0-cp36-none-any.whl size=26671 sha256=af8fe05aef88c29ea485c42c71d4b99b3d2428e33b80fb00faa98285bb568323\n", + " Stored in directory: /root/.cache/pip/wheels/62/b8/e1/e2719465b5947c40cd85d613d6cb33449b86a1ca5a6c574269\n", + " Building wheel for fusepy (setup.py): started\n", + " Building wheel for fusepy (setup.py): finished with status 'done'\n", + " Created wheel for fusepy: filename=fusepy-3.0.1-cp36-none-any.whl size=10505 sha256=eaa956fa434045ed0897919d164426bb30f4e8bc0f35240498fcbaa85b252ffa\n", + " Stored in directory: /root/.cache/pip/wheels/4c/a5/91/7772af9e21c461f07bb40f26d928d7d231d224977dd8353bab\n", + " Building wheel for dill (setup.py): started\n", + " Building wheel for dill (setup.py): finished with status 'done'\n", + " Created wheel for dill: filename=dill-0.3.1.1-cp36-none-any.whl size=78532 sha256=7baff27933ff72b24f50f953e42888e5cc6b6a59c781c6ee239e35d1c368d9ea\n", + " Stored in directory: /root/.cache/pip/wheels/59/b1/91/f02e76c732915c4015ab4010f3015469866c1eb9b14058d8e7\n", + " Building wheel for JsonForm (setup.py): started\n", + " Building wheel for JsonForm (setup.py): finished with status 'done'\n", + " Created wheel for JsonForm: filename=JsonForm-0.0.2-cp36-none-any.whl size=3326 sha256=6811f0fedba357c208217f7688597d76a449698946d961153a799734092bddab\n", + " Stored in directory: /root/.cache/pip/wheels/e8/74/51/42c2d41c02bdc6f0e604476b7e4293b8c98d0bcbfa1dff78c8\n", + " Building wheel for JsonSir (setup.py): started\n", + " Building wheel for JsonSir (setup.py): finished with status 'done'\n", + " Created wheel for JsonSir: filename=JsonSir-0.0.2-cp36-none-any.whl size=4774 sha256=744793daaba7257e386891014d0823185a21fc03bcdf94d47abce54971661810\n", + " Stored in directory: /root/.cache/pip/wheels/ee/30/5c/3a3b5e1386c8db9a3be5f5c3933644ae0533c1351c6a8eb4b5\n", + " Building wheel for smart-open (setup.py): started\n", + " Building wheel for smart-open (setup.py): finished with status 'done'\n", + " Created wheel for smart-open: filename=smart_open-1.8.4-cp36-none-any.whl size=68202 sha256=1259196b3d373f67552777e33241841c2ce4b1e728c0191185899539c0918fa8\n", + " Stored in directory: /root/.cache/pip/wheels/5f/ea/fb/5b1a947b369724063b2617011f1540c44eb00e28c3d2ca8692\n", + " Building wheel for fire (setup.py): started\n", + " Building wheel for fire (setup.py): finished with status 'done'\n", + " Created wheel for fire: filename=fire-0.2.1-py2.py3-none-any.whl size=103527 sha256=6029e13e8c87a53b3eeaa071b79eafedb39057d1fc0ac35c550be5a2efaf6a14\n", + " Stored in directory: /root/.cache/pip/wheels/31/9c/c0/07b6dc7faf1844bb4688f46b569efe6cafaa2179c95db821da\n", + " Building wheel for termcolor (setup.py): started\n", + " Building wheel for termcolor (setup.py): finished with status 'done'\n", + " Created wheel for termcolor: filename=termcolor-1.1.0-cp36-none-any.whl size=4832 sha256=0d04b365d3647f95b87f0688881a5b0d0a79fdf035f94c5bda88fc35bbc5bbe1\n", + " Stored in directory: /root/.cache/pip/wheels/7c/06/54/bc84598ba1daf8f970247f550b175aaaee85f68b4b0c5ab2c6\n", + "Successfully built codepy pyrevolve future blosc contexttimer pyzfp pathspec fusepy dill JsonForm JsonSir smart-open fire termcolor\n", + "Installing collected packages: anytree, azure-common, pathspec, jsonpickle, oauthlib, requests-oauthlib, isodate, msrest, jeepney, SecretStorage, contextlib2, ruamel.yaml, PyJWT, adal, msrestazure, azure-mgmt-resource, azure-mgmt-authorization, azure-mgmt-storage, backports.weakref, backports.tempfile, websocket-client, docker, azure-mgmt-keyvault, pyasn1, ndg-httpsclient, jmespath, azure-graphrbac, azure-mgmt-containerregistry, azureml-core, distro, dotnetcore2, azureml-dataprep-native, fusepy, azureml-dataprep, azureml-train-restclients-hyperdrive, applicationinsights, azureml-telemetry, azureml-train-core, azureml-pipeline-core, azureml-pipeline-steps, azureml-pipeline, azureml-train, dill, scipy, pandas, patsy, statsmodels, scikit-learn, sklearn-pandas, protobuf, typing-extensions, onnx, onnxconverter-common, skl2onnx, python-easyconfig, JsonForm, JsonSir, resource, lightgbm, boto, botocore, s3transfer, boto3, smart-open, gensim, wheel, termcolor, fire, keras2onnx, onnxmltools, nimbusml, pmdarima, azureml-automl-core, azureml-train-automl, future, tenacity, textwrap3, ansiwrap, tqdm, azure-storage-common, azure-storage-blob, azure-datalake-store, papermill, azureml-contrib-notebook, widgetsnbextension, ipywidgets, azureml-widgets, azureml-sdk, codepy, blosc, contexttimer, pyzfp, pyrevolve\n", + " Found existing installation: scipy 1.3.1\n", + " Uninstalling scipy-1.3.1:\n", + " Successfully uninstalled scipy-1.3.1\n", + " Found existing installation: pandas 0.25.1\n", + " Uninstalling pandas-0.25.1:\n", + " Successfully uninstalled pandas-0.25.1\n", + " Found existing installation: wheel 0.33.6\n", + " Uninstalling wheel-0.33.6:\n", + " Successfully uninstalled wheel-0.33.6\n", + "Successfully installed JsonForm-0.0.2 JsonSir-0.0.2 PyJWT-1.7.1 SecretStorage-3.1.1 adal-1.2.2 ansiwrap-0.8.4 anytree-2.7.1 applicationinsights-0.11.9 azure-common-1.1.23 azure-datalake-store-0.0.47 azure-graphrbac-0.61.1 azure-mgmt-authorization-0.60.0 azure-mgmt-containerregistry-2.8.0 azure-mgmt-keyvault-2.0.0 azure-mgmt-resource-5.1.0 azure-mgmt-storage-4.2.0 azure-storage-blob-2.1.0 azure-storage-common-2.1.0 azureml-automl-core-1.0.65.1 azureml-contrib-notebook-1.0.65 azureml-core-1.0.65.1 azureml-dataprep-1.1.20 azureml-dataprep-native-13.1.0 azureml-pipeline-1.0.65 azureml-pipeline-core-1.0.65 azureml-pipeline-steps-1.0.65 azureml-sdk-1.0.65 azureml-telemetry-1.0.65 azureml-train-1.0.65 azureml-train-automl-1.0.65 azureml-train-core-1.0.65 azureml-train-restclients-hyperdrive-1.0.65 azureml-widgets-1.0.65 backports.tempfile-1.0 backports.weakref-1.0.post1 blosc-1.8.1 boto-2.49.0 boto3-1.9.245 botocore-1.12.245 codepy-2019.1 contextlib2-0.6.0 contexttimer-0.3.3 dill-0.3.1.1 distro-1.4.0 docker-4.1.0 dotnetcore2-2.1.9 fire-0.2.1 fusepy-3.0.1 future-0.17.1 gensim-3.8.1 ipywidgets-7.5.1 isodate-0.6.0 jeepney-0.4.1 jmespath-0.9.4 jsonpickle-1.2 keras2onnx-1.5.2 lightgbm-2.2.3 msrest-0.6.10 msrestazure-0.6.2 ndg-httpsclient-0.5.1 nimbusml-1.5.0 oauthlib-3.1.0 onnx-1.6.0 onnxconverter-common-1.5.5 onnxmltools-1.4.1 pandas-0.23.4 papermill-1.2.0 pathspec-0.6.0 patsy-0.5.1 pmdarima-1.1.1 protobuf-3.10.0 pyasn1-0.4.7 pyrevolve-2.1.3 python-easyconfig-0.1.7 pyzfp-0.3 requests-oauthlib-1.2.0 resource-0.2.1 ruamel.yaml-0.15.89 s3transfer-0.2.1 scikit-learn-0.20.3 scipy-1.1.0 skl2onnx-1.4.9 sklearn-pandas-1.7.0 smart-open-1.8.4 statsmodels-0.10.1 tenacity-5.1.1 termcolor-1.1.0 textwrap3-0.9.2 tqdm-4.36.1 typing-extensions-3.7.4 websocket-client-0.56.0 wheel-0.30.0 widgetsnbextension-3.5.1\n", + "\n", + "#\n", + "# To activate this environment, use\n", + "#\n", + "# $ conda activate fwi01_conda_env\n", + "#\n", + "# To deactivate an active environment, use\n", + "#\n", + "# $ conda deactivate\n", + "\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cache location: /opt/conda/pkgs\r\n", + "Will remove the following tarballs:\r\n", + "\r\n", + "/opt/conda/pkgs\r\n", + "---------------\r\n", + "click-7.0-py37_0.conda 120 KB\r\n", + "pyflakes-2.1.1-py37_0.conda 106 KB\r\n", + "tblib-1.4.0-py_0.tar.bz2 14 KB\r\n", + "libsodium-1.0.16-h1bed415_0.conda 214 KB\r\n", + "kiwisolver-1.1.0-py37he6710b0_0.conda 82 KB\r\n", + "jinja2-2.10.3-py_0.tar.bz2 95 KB\r\n", + "zict-1.0.0-py_0.tar.bz2 12 KB\r\n", + "ipython-7.8.0-py37h39e3cac_0.conda 985 KB\r\n", + "traitlets-4.3.3-py37_0.tar.bz2 138 KB\r\n", + "libstdcxx-ng-9.1.0-hdf63c60_0.conda 3.1 MB\r\n", + "pycparser-2.19-py37_0.conda 171 KB\r\n", + "cgen-2019.1-py_0.tar.bz2 16 KB\r\n", + "imagesize-1.1.0-py37_0.conda 9 KB\r\n", + "jedi-0.15.1-py37_0.conda 704 KB\r\n", + "zlib-1.2.11-h7b6447c_3.conda 103 KB\r\n", + "importlib_metadata-0.23-py37_0.tar.bz2 43 KB\r\n", + "nb_conda_kernels-2.2.2-py37_0.conda 39 KB\r\n", + "heapdict-1.0.1-py_0.conda 9 KB\r\n", + "cytoolz-0.10.0-py37h7b6447c_0.conda 374 KB\r\n", + "babel-2.7.0-py_0.tar.bz2 5.8 MB\r\n", + "libxml2-2.9.9-hea5a465_1.conda 1.6 MB\r\n", + "mpc-1.1.0-h10f8cd9_1.conda 90 KB\r\n", + "mkl_fft-1.0.14-py37ha843d7b_0.conda 155 KB\r\n", + "decorator-4.4.0-py37_1.conda 19 KB\r\n", + "sphinx-2.2.0-py_0.tar.bz2 1.5 MB\r\n", + "pycodestyle-2.5.0-py37_0.conda 61 KB\r\n", + "send2trash-1.5.0-py37_0.conda 16 KB\r\n", + "cycler-0.10.0-py37_0.conda 13 KB\r\n", + "mpfr-4.0.1-hdf1c602_3.conda 429 KB\r\n", + "six-1.12.0-py37_0.conda 23 KB\r\n", + "frozendict-1.2-py_2.tar.bz2 6 KB\r\n", + "pexpect-4.7.0-py37_0.conda 80 KB\r\n", + "sip-4.19.8-py37hf484d3e_0.conda 274 KB\r\n", + "setuptools-41.4.0-py37_0.tar.bz2 651 KB\r\n", + "dask-core-2.5.2-py_0.tar.bz2 579 KB\r\n", + "zipp-0.6.0-py_0.tar.bz2 9 KB\r\n", + "pandoc-2.2.3.2-0.conda 14.0 MB\r\n", + "cython-0.29.13-py37he6710b0_0.conda 2.0 MB\r\n", + "mpmath-1.1.0-py37_0.conda 766 KB\r\n", + "numpy-base-1.17.2-py37hde5b4d6_0.conda 4.2 MB\r\n", + "webencodings-0.5.1-py37_1.conda 19 KB\r\n", + "sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2 8 KB\r\n", + "defusedxml-0.6.0-py_0.tar.bz2 23 KB\r\n", + "wheel-0.33.6-py37_0.tar.bz2 40 KB\r\n", + "markupsafe-1.1.1-py37h7b6447c_0.conda 29 KB\r\n", + "pip-19.2.3-py37_0.tar.bz2 1.9 MB\r\n", + "libuuid-1.0.3-h1bed415_2.conda 15 KB\r\n", + "glib-2.56.2-hd408876_0.conda 3.9 MB\r\n", + "codecov-2.0.15-py_1.tar.bz2 18 KB\r\n", + "prompt_toolkit-2.0.10-py_0.tar.bz2 227 KB\r\n", + "coverage-4.5.4-py37h7b6447c_0.conda 224 KB\r\n", + "pysocks-1.7.1-py37_0.tar.bz2 30 KB\r\n", + "sqlite-3.30.0-h7b6447c_0.tar.bz2 1.9 MB\r\n", + "ipython_genutils-0.2.0-py37_0.conda 39 KB\r\n", + "mistune-0.8.4-py37h7b6447c_0.conda 55 KB\r\n", + "testpath-0.4.2-py37_0.conda 86 KB\r\n", + "sphinxcontrib-serializinghtml-1.1.3-py_0.tar.bz2 24 KB\r\n", + "_libgcc_mutex-0.1-main.conda 3 KB\r\n", + "libgcc-ng-9.1.0-hdf63c60_0.conda 5.1 MB\r\n", + "mkl-service-2.3.0-py37he904b0f_0.conda 218 KB\r\n", + "gmp-6.1.2-h6c8ec71_1.conda 514 KB\r\n", + "psutil-5.6.3-py37h7b6447c_0.conda 313 KB\r\n", + "cryptography-2.7-py37h1ba5d50_0.conda 544 KB\r\n", + "fontconfig-2.13.0-h9420a91_0.conda 227 KB\r\n", + "libpng-1.6.37-hbc83047_0.conda 278 KB\r\n", + "notebook-6.0.1-py37_0.tar.bz2 6.0 MB\r\n", + "mkl_random-1.1.0-py37hd6b4f25_0.conda 321 KB\r\n", + "pytools-2019.1.1-py_0.tar.bz2 52 KB\r\n", + "toolz-0.10.0-py_0.tar.bz2 50 KB\r\n", + "nbformat-4.4.0-py37_0.conda 128 KB\r\n", + "chardet-3.0.4-py37_1003.conda 174 KB\r\n", + "ipykernel-5.1.2-py37h39e3cac_0.conda 170 KB\r\n", + "scipy-1.3.1-py37h7c811a0_0.conda 14.0 MB\r\n", + "sphinxcontrib-qthelp-1.0.2-py_0.tar.bz2 26 KB\r\n", + "fastcache-1.1.0-py37h7b6447c_0.conda 30 KB\r\n", + "docutils-0.15.2-py37_0.conda 660 KB\r\n", + "pyopenssl-19.0.0-py37_0.conda 84 KB\r\n", + "mccabe-0.6.1-py37_1.conda 14 KB\r\n", + "attrs-19.2.0-py_0.tar.bz2 39 KB\r\n", + "nbval-0.9.3-py_0.tar.bz2 21 KB\r\n", + "libffi-3.2.1-hd88cf55_4.conda 40 KB\r\n", + "freetype-2.9.1-h8a8886c_1.conda 550 KB\r\n", + "pytz-2019.3-py_0.tar.bz2 231 KB\r\n", + "jupyter_core-4.5.0-py_0.tar.bz2 48 KB\r\n", + "pickleshare-0.7.5-py37_0.conda 13 KB\r\n", + "tornado-6.0.3-py37h7b6447c_0.conda 584 KB\r\n", + "py-1.8.0-py37_0.conda 148 KB\r\n", + "pluggy-0.13.0-py37_0.tar.bz2 31 KB\r\n", + "bleach-3.1.0-py37_0.conda 220 KB\r\n", + "nbconvert-5.6.0-py37_1.tar.bz2 491 KB\r\n", + "idna-2.8-py37_0.conda 85 KB\r\n", + "appdirs-1.4.3-py37h28b3542_0.conda 15 KB\r\n", + "terminado-0.8.2-py37_0.conda 23 KB\r\n", + "pytest-5.2.1-py37_0.tar.bz2 364 KB\r\n", + "mkl-2019.4-243.conda 131.2 MB\r\n", + "icu-58.2-h9c2bf20_1.conda 10.3 MB\r\n", + "gmpy2-2.0.8-py37h10f8cd9_2.conda 150 KB\r\n", + "parso-0.5.1-py_0.tar.bz2 68 KB\r\n", + "sphinxcontrib-applehelp-1.0.1-py_0.tar.bz2 29 KB\r\n", + "expat-2.2.6-he6710b0_0.conda 146 KB\r\n", + "distributed-2.5.2-py_0.tar.bz2 396 KB\r\n", + "pyzmq-18.1.0-py37he6710b0_0.conda 455 KB\r\n", + "python-3.7.4-h265db76_1.conda 32.1 MB\r\n", + "sortedcontainers-2.1.0-py37_0.conda 43 KB\r\n", + "atomicwrites-1.3.0-py37_1.conda 13 KB\r\n", + "libxcb-1.13-h1bed415_1.conda 421 KB\r\n", + "snowballstemmer-2.0.0-py_0.tar.bz2 58 KB\r\n", + "python-dateutil-2.8.0-py37_0.conda 266 KB\r\n", + "openssl-1.1.1d-h7b6447c_2.conda 2.5 MB\r\n", + "dbus-1.13.6-h746ee38_0.conda 499 KB\r\n", + "prometheus_client-0.7.1-py_0.tar.bz2 42 KB\r\n", + "asn1crypto-1.0.1-py37_0.tar.bz2 161 KB\r\n", + "pandocfilters-1.4.2-py37_1.conda 13 KB\r\n", + "flake8-3.7.8-py37_1.tar.bz2 134 KB\r\n", + "cffi-1.12.3-py37h2e261b9_0.conda 222 KB\r\n", + "urllib3-1.24.2-py37_0.conda 159 KB\r\n", + "readline-7.0-h7b6447c_5.conda 324 KB\r\n", + "wcwidth-0.1.7-py37_0.conda 22 KB\r\n", + "sphinx_rtd_theme-0.4.3-py_0.tar.bz2 5.1 MB\r\n", + "certifi-2019.9.11-py37_0.tar.bz2 154 KB\r\n", + "matplotlib-3.1.1-py37h5429711_0.conda 5.0 MB\r\n", + "yaml-0.1.7-had09818_2.conda 73 KB\r\n", + "jsonschema-3.0.2-py37_0.conda 92 KB\r\n", + "tk-8.6.8-hbc83047_0.conda 2.8 MB\r\n", + "qt-5.9.7-h5867ecd_1.conda 68.5 MB\r\n", + "cloudpickle-1.2.2-py_0.tar.bz2 29 KB\r\n", + "gst-plugins-base-1.14.0-hbbd80ab_1.conda 4.8 MB\r\n", + "requests-2.22.0-py37_0.conda 90 KB\r\n", + "pyrsistent-0.15.4-py37h7b6447c_0.tar.bz2 92 KB\r\n", + "zeromq-4.3.1-he6710b0_3.conda 496 KB\r\n", + "blas-1.0-mkl.conda 6 KB\r\n", + "pyparsing-2.4.2-py_0.tar.bz2 61 KB\r\n", + "packaging-19.2-py_0.tar.bz2 30 KB\r\n", + "jpeg-9b-h024ee3a_2.conda 214 KB\r\n", + "pyqt-5.9.2-py37h05f1152_2.conda 4.5 MB\r\n", + "sympy-1.4-py37_0.conda 7.9 MB\r\n", + "libgfortran-ng-7.3.0-hdf63c60_0.conda 1006 KB\r\n", + "pcre-8.43-he6710b0_0.conda 209 KB\r\n", + "more-itertools-7.2.0-py37_0.conda 100 KB\r\n", + "pygments-2.4.2-py_0.tar.bz2 664 KB\r\n", + "pytest-cov-2.7.1-py_0.tar.bz2 21 KB\r\n", + "intel-openmp-2019.4-243.conda 729 KB\r\n", + "entrypoints-0.3-py37_0.conda 12 KB\r\n", + "xz-5.2.4-h14c3975_4.conda 283 KB\r\n", + "backcall-0.1.0-py37_0.conda 20 KB\r\n", + "libedit-3.1.20181209-hc058e9b_0.conda 163 KB\r\n", + "msgpack-python-0.6.1-py37hfd86e86_1.conda 87 KB\r\n", + "pyyaml-5.1.2-py37h7b6447c_0.conda 179 KB\r\n", + "ca-certificates-2019.8.28-0.tar.bz2 132 KB\r\n", + "alabaster-0.7.12-py37_0.conda 18 KB\r\n", + "multidict-4.5.2-py37h7b6447c_0.conda 137 KB\r\n", + "ncurses-6.1-he6710b0_1.conda 777 KB\r\n", + "ptyprocess-0.6.0-py37_0.conda 23 KB\r\n", + "cached-property-1.5.1-py37_0.conda 12 KB\r\n", + "gstreamer-1.14.0-hb453b48_1.conda 3.1 MB\r\n", + "jupyter_client-5.3.3-py37_1.tar.bz2 137 KB\r\n", + "sphinxcontrib-devhelp-1.0.1-py_0.tar.bz2 23 KB\r\n", + "numpy-1.17.2-py37haad9e8e_0.conda 4 KB\r\n", + "sphinxcontrib-htmlhelp-1.0.2-py_0.tar.bz2 28 KB\r\n", + "graphviz-2.40.1-h21bd128_2.tar.bz2 6.9 MB\r\n", + "six-1.12.0-py36_0.tar.bz2 22 KB\r\n", + "asn1crypto-1.0.1-py36_0.tar.bz2 161 KB\r\n", + "cryptography-2.7-py36h1ba5d50_0.tar.bz2 617 KB\r\n", + "pyyaml-5.1.2-py36h7b6447c_0.tar.bz2 189 KB\r\n", + "libtiff-4.0.10-h2733197_2.tar.bz2 604 KB\r\n", + "cffi-1.12.3-py36h2e261b9_0.tar.bz2 222 KB\r\n", + "ptyprocess-0.6.0-py36_0.tar.bz2 23 KB\r\n", + "gst-plugins-base-1.14.0-hbbd80ab_1.tar.bz2 6.3 MB\r\n", + "libgfortran-ng-7.3.0-hdf63c60_0.tar.bz2 1.3 MB\r\n", + "msgpack-python-0.6.1-py36hfd86e86_1.tar.bz2 92 KB\r\n", + "mpc-1.1.0-h10f8cd9_1.tar.bz2 94 KB\r\n", + "pyzmq-18.1.0-py36he6710b0_0.tar.bz2 520 KB\r\n", + "olefile-0.46-py36_0.tar.bz2 48 KB\r\n", + "pyrsistent-0.15.4-py36h7b6447c_0.tar.bz2 92 KB\r\n", + "expat-2.2.6-he6710b0_0.tar.bz2 187 KB\r\n", + "yaml-0.1.7-h96e3832_1.tar.bz2 84 KB\r\n", + "h5py-2.9.0-py36h7918eee_0.tar.bz2 1.2 MB\r\n", + "libffi-3.2.1-h4deb6c0_3.tar.bz2 43 KB\r\n", + "testpath-0.4.2-py36_0.tar.bz2 91 KB\r\n", + "markupsafe-1.1.1-py36h7b6447c_0.tar.bz2 29 KB\r\n", + "blas-1.0-mkl.tar.bz2 6 KB\r\n", + "py-1.8.0-py36_0.tar.bz2 140 KB\r\n", + "mpmath-1.1.0-py36_0.tar.bz2 970 KB\r\n", + "pytest-5.2.1-py36_0.tar.bz2 366 KB\r\n", + "pyopenssl-19.0.0-py36_0.tar.bz2 82 KB\r\n", + "imagesize-1.1.0-py36_0.tar.bz2 9 KB\r\n", + "zeromq-4.3.1-he6710b0_3.tar.bz2 666 KB\r\n", + "pillow-6.2.0-py36h34e0f95_0.tar.bz2 647 KB\r\n", + "terminado-0.8.2-py36_0.tar.bz2 22 KB\r\n", + "requests-2.22.0-py36_0.tar.bz2 89 KB\r\n", + "jupyter_client-5.3.3-py36_1.tar.bz2 137 KB\r\n", + "qt-5.9.7-h5867ecd_1.tar.bz2 85.9 MB\r\n", + "mkl_random-1.1.0-py36hd6b4f25_0.tar.bz2 369 KB\r\n", + "mkl_fft-1.0.14-py36ha843d7b_0.tar.bz2 173 KB\r\n", + "pycodestyle-2.5.0-py36_0.tar.bz2 60 KB\r\n", + "pango-1.42.4-h049681c_0.tar.bz2 528 KB\r\n", + "flake8-3.7.8-py36_1.tar.bz2 134 KB\r\n", + "ipython-7.8.0-py36h39e3cac_0.tar.bz2 1.1 MB\r\n", + "pandocfilters-1.4.2-py36_1.tar.bz2 13 KB\r\n", + "heapdict-1.0.1-py_0.tar.bz2 9 KB\r\n", + "notebook-6.0.1-py36_0.tar.bz2 6.0 MB\r\n", + "mistune-0.8.4-py36h7b6447c_0.tar.bz2 54 KB\r\n", + "alabaster-0.7.12-py36_0.tar.bz2 17 KB\r\n", + "jedi-0.15.1-py36_0.tar.bz2 721 KB\r\n", + "certifi-2019.9.11-py36_0.tar.bz2 154 KB\r\n", + "gstreamer-1.14.0-hb453b48_1.tar.bz2 3.8 MB\r\n", + "pandoc-2.2.3.2-0.tar.bz2 20.8 MB\r\n", + "pandas-0.25.1-py36he6710b0_0.tar.bz2 11.4 MB\r\n", + "chardet-3.0.4-py36_1003.tar.bz2 197 KB\r\n", + "libedit-3.1.20181209-hc058e9b_0.tar.bz2 188 KB\r\n", + "cached-property-1.5.1-py36_0.tar.bz2 11 KB\r\n", + "pyqt-5.9.2-py36h22d08a2_1.tar.bz2 5.6 MB\r\n", + "tornado-6.0.3-py36h7b6447c_0.tar.bz2 643 KB\r\n", + "psutil-5.6.3-py36h7b6447c_0.tar.bz2 328 KB\r\n", + "libsodium-1.0.16-h1bed415_0.tar.bz2 302 KB\r\n", + "gmp-6.1.2-hb3b607b_0.tar.bz2 744 KB\r\n", + "pexpect-4.7.0-py36_0.tar.bz2 82 KB\r\n", + "nbformat-4.4.0-py36_0.tar.bz2 141 KB\r\n", + "python-3.6.9-h265db76_0.tar.bz2 34.4 MB\r\n", + "docutils-0.15.2-py36_0.tar.bz2 742 KB\r\n", + "coverage-4.5.4-py36h7b6447c_0.tar.bz2 226 KB\r\n", + "xz-5.2.4-h14c3975_4.tar.bz2 366 KB\r\n", + "mccabe-0.6.1-py36_1.tar.bz2 14 KB\r\n", + "jsonschema-3.0.2-py36_0.tar.bz2 90 KB\r\n", + "fontconfig-2.13.0-h9420a91_0.tar.bz2 291 KB\r\n", + "networkx-2.3-py_0.tar.bz2 1.1 MB\r\n", + "importlib_metadata-0.23-py36_0.tar.bz2 43 KB\r\n", + "send2trash-1.5.0-py36_0.tar.bz2 16 KB\r\n", + "pluggy-0.13.0-py36_0.tar.bz2 31 KB\r\n", + "bokeh-1.3.4-py36_0.tar.bz2 4.0 MB\r\n", + "pcre-8.43-he6710b0_0.tar.bz2 260 KB\r\n", + "libstdcxx-ng-9.1.0-hdf63c60_0.tar.bz2 4.0 MB\r\n", + "numpy-base-1.17.2-py36hde5b4d6_0.tar.bz2 5.3 MB\r\n", + "wheel-0.33.6-py36_0.tar.bz2 40 KB\r\n", + "scikit-image-0.15.0-py36he6710b0_0.tar.bz2 28.4 MB\r\n", + "blosc-1.17.0-he1b5a44_1.tar.bz2 862 KB\r\n", + "zlib-1.2.11-h7b6447c_3.tar.bz2 120 KB\r\n", + "cairo-1.14.12-h8948797_3.tar.bz2 1.3 MB\r\n", + "mpfr-4.0.1-hdf1c602_3.tar.bz2 575 KB\r\n", + "zstd-1.3.7-h0b5b093_0.tar.bz2 887 KB\r\n", + "numpy-1.17.2-py36haad9e8e_0.tar.bz2 4 KB\r\n", + "bleach-3.1.0-py36_0.tar.bz2 226 KB\r\n", + "pyflakes-2.1.1-py36_0.tar.bz2 105 KB\r\n", + "bzip2-1.0.8-h7b6447c_0.tar.bz2 105 KB\r\n", + "sympy-1.4-py36_0.tar.bz2 9.7 MB\r\n", + "urllib3-1.24.2-py36_0.tar.bz2 153 KB\r\n", + "more-itertools-7.2.0-py36_0.tar.bz2 99 KB\r\n", + "pip-19.2.3-py36_0.tar.bz2 1.9 MB\r\n", + "python-dateutil-2.8.0-py36_0.tar.bz2 281 KB\r\n", + "wcwidth-0.1.7-py36_0.tar.bz2 25 KB\r\n", + "dask-2.5.2-py_0.tar.bz2 12 KB\r\n", + "glib-2.56.2-hd408876_0.tar.bz2 5.0 MB\r\n", + "setuptools-41.4.0-py36_0.tar.bz2 673 KB\r\n", + "scipy-1.3.1-py36h7c811a0_0.tar.bz2 18.1 MB\r\n", + "libgcc-ng-9.1.0-hdf63c60_0.tar.bz2 8.1 MB\r\n", + "sortedcontainers-2.1.0-py36_0.tar.bz2 45 KB\r\n", + "webencodings-0.5.1-py36_1.tar.bz2 19 KB\r\n", + "numexpr-2.7.0-py36h9e4a6bb_0.tar.bz2 196 KB\r\n", + "appdirs-1.4.3-py36h28b3542_0.tar.bz2 16 KB\r\n", + "pycparser-2.19-py36_0.tar.bz2 174 KB\r\n", + "pickleshare-0.7.5-py36_0.tar.bz2 13 KB\r\n", + "harfbuzz-1.8.8-hffaf4a1_0.tar.bz2 863 KB\r\n", + "idna-2.8-py36_0.tar.bz2 133 KB\r\n", + "libxml2-2.9.9-hea5a465_1.tar.bz2 2.0 MB\r\n", + "mkl-2019.5-281.tar.bz2 205.3 MB\r\n", + "mock-3.0.5-py36_0.tar.bz2 47 KB\r\n", + "traitlets-4.3.3-py36_0.tar.bz2 137 KB\r\n", + "graphite2-1.3.13-h23475e2_0.tar.bz2 101 KB\r\n", + "fastcache-1.1.0-py36h7b6447c_0.tar.bz2 31 KB\r\n", + "partd-1.0.0-py_0.tar.bz2 19 KB\r\n", + "lzo-2.10-h1bfc0ba_1.tar.bz2 314 KB\r\n", + "jpeg-9b-habf39ab_1.tar.bz2 247 KB\r\n", + "backcall-0.1.0-py36_0.tar.bz2 19 KB\r\n", + "cython-0.29.13-py36he6710b0_0.tar.bz2 2.2 MB\r\n", + "py-cpuinfo-5.0.0-py_0.tar.bz2 22 KB\r\n", + "readline-7.0-h7b6447c_5.tar.bz2 392 KB\r\n", + "ncurses-6.1-he6710b0_1.tar.bz2 958 KB\r\n", + "libpng-1.6.37-hbc83047_0.tar.bz2 364 KB\r\n", + "pytables-3.5.2-py36h71ec239_1.tar.bz2 1.5 MB\r\n", + "gmpy2-2.0.8-py36h10f8cd9_2.tar.bz2 165 KB\r\n", + "atomicwrites-1.3.0-py36_1.tar.bz2 13 KB\r\n", + "dbus-1.13.6-h746ee38_0.tar.bz2 587 KB\r\n", + "matplotlib-3.1.1-py36h5429711_0.tar.bz2 6.7 MB\r\n", + "locket-0.2.0-py36_1.tar.bz2 8 KB\r\n", + "pywavelets-1.0.3-py36hdd07704_1.tar.bz2 4.4 MB\r\n", + "imageio-2.6.0-py36_0.tar.bz2 3.3 MB\r\n", + "multidict-4.5.2-py36h7b6447c_0.tar.bz2 142 KB\r\n", + "mkl-service-2.3.0-py36he904b0f_0.tar.bz2 208 KB\r\n", + "entrypoints-0.3-py36_0.tar.bz2 12 KB\r\n", + "intel-openmp-2019.5-281.tar.bz2 895 KB\r\n", + "fsspec-0.5.2-py_0.tar.bz2 46 KB\r\n", + "ipython_genutils-0.2.0-py36_0.tar.bz2 39 KB\r\n", + "icu-58.2-h211956c_0.tar.bz2 22.5 MB\r\n", + "nb_conda_kernels-2.2.2-py36_0.tar.bz2 37 KB\r\n", + "sip-4.19.13-py36he6710b0_0.tar.bz2 293 KB\r\n", + "kiwisolver-1.1.0-py36he6710b0_0.tar.bz2 90 KB\r\n", + "libuuid-1.0.3-h1bed415_2.tar.bz2 16 KB\r\n", + "hdf5-1.10.4-hb1b8bf9_0.tar.bz2 5.3 MB\r\n", + "libxcb-1.13-h1bed415_1.tar.bz2 502 KB\r\n", + "decorator-4.4.0-py36_1.tar.bz2 18 KB\r\n", + "tk-8.6.8-hbc83047_0.tar.bz2 3.1 MB\r\n", + "openssl-1.1.1-h7b6447c_0.tar.bz2 5.0 MB\r\n", + "python-graphviz-0.10.1-py_0.tar.bz2 22 KB\r\n", + "snakeviz-2.0.1-py36_0.tar.bz2 316 KB\r\n", + "pixman-0.38.0-h7b6447c_0.tar.bz2 618 KB\r\n", + "nb_conda-2.2.1-py36_0.tar.bz2 33 KB\r\n", + "click-7.0-py36_0.tar.bz2 118 KB\r\n", + "cytoolz-0.10.0-py36h7b6447c_0.tar.bz2 439 KB\r\n", + "ipykernel-5.1.2-py36h39e3cac_0.tar.bz2 165 KB\r\n", + "fribidi-1.0.5-h7b6447c_0.tar.bz2 112 KB\r\n", + "pysocks-1.7.1-py36_0.tar.bz2 30 KB\r\n", + "cycler-0.10.0-py36_0.tar.bz2 13 KB\r\n", + "freetype-2.9.1-h8a8886c_1.tar.bz2 822 KB\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "nbconvert-5.6.0-py36_1.tar.bz2 494 KB\n", + "\n", + "---------------------------------------------------\n", + "Total: 926.1 MB\n", + "\n", + "Removed click-7.0-py37_0.conda\n", + "Removed pyflakes-2.1.1-py37_0.conda\n", + "Removed tblib-1.4.0-py_0.tar.bz2\n", + "Removed libsodium-1.0.16-h1bed415_0.conda\n", + "Removed kiwisolver-1.1.0-py37he6710b0_0.conda\n", + "Removed jinja2-2.10.3-py_0.tar.bz2\n", + "Removed zict-1.0.0-py_0.tar.bz2\n", + "Removed ipython-7.8.0-py37h39e3cac_0.conda\n", + "Removed traitlets-4.3.3-py37_0.tar.bz2\n", + "Removed libstdcxx-ng-9.1.0-hdf63c60_0.conda\n", + "Removed pycparser-2.19-py37_0.conda\n", + "Removed cgen-2019.1-py_0.tar.bz2\n", + "Removed imagesize-1.1.0-py37_0.conda\n", + "Removed jedi-0.15.1-py37_0.conda\n", + "Removed zlib-1.2.11-h7b6447c_3.conda\n", + "Removed importlib_metadata-0.23-py37_0.tar.bz2\n", + "Removed nb_conda_kernels-2.2.2-py37_0.conda\n", + "Removed heapdict-1.0.1-py_0.conda\n", + "Removed cytoolz-0.10.0-py37h7b6447c_0.conda\n", + "Removed babel-2.7.0-py_0.tar.bz2\n", + "Removed libxml2-2.9.9-hea5a465_1.conda\n", + "Removed mpc-1.1.0-h10f8cd9_1.conda\n", + "Removed mkl_fft-1.0.14-py37ha843d7b_0.conda\n", + "Removed decorator-4.4.0-py37_1.conda\n", + "Removed sphinx-2.2.0-py_0.tar.bz2\n", + "Removed pycodestyle-2.5.0-py37_0.conda\n", + "Removed send2trash-1.5.0-py37_0.conda\n", + "Removed cycler-0.10.0-py37_0.conda\n", + "Removed mpfr-4.0.1-hdf1c602_3.conda\n", + "Removed six-1.12.0-py37_0.conda\n", + "Removed frozendict-1.2-py_2.tar.bz2\n", + "Removed pexpect-4.7.0-py37_0.conda\n", + "Removed sip-4.19.8-py37hf484d3e_0.conda\n", + "Removed setuptools-41.4.0-py37_0.tar.bz2\n", + "Removed dask-core-2.5.2-py_0.tar.bz2\n", + "Removed zipp-0.6.0-py_0.tar.bz2\n", + "Removed pandoc-2.2.3.2-0.conda\n", + "Removed cython-0.29.13-py37he6710b0_0.conda\n", + "Removed mpmath-1.1.0-py37_0.conda\n", + "Removed numpy-base-1.17.2-py37hde5b4d6_0.conda\n", + "Removed webencodings-0.5.1-py37_1.conda\n", + "Removed sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2\n", + "Removed defusedxml-0.6.0-py_0.tar.bz2\n", + "Removed wheel-0.33.6-py37_0.tar.bz2\n", + "Removed markupsafe-1.1.1-py37h7b6447c_0.conda\n", + "Removed pip-19.2.3-py37_0.tar.bz2\n", + "Removed libuuid-1.0.3-h1bed415_2.conda\n", + "Removed glib-2.56.2-hd408876_0.conda\n", + "Removed codecov-2.0.15-py_1.tar.bz2\n", + "Removed prompt_toolkit-2.0.10-py_0.tar.bz2\n", + "Removed coverage-4.5.4-py37h7b6447c_0.conda\n", + "Removed pysocks-1.7.1-py37_0.tar.bz2\n", + "Removed sqlite-3.30.0-h7b6447c_0.tar.bz2\n", + "Removed ipython_genutils-0.2.0-py37_0.conda\n", + "Removed mistune-0.8.4-py37h7b6447c_0.conda\n", + "Removed testpath-0.4.2-py37_0.conda\n", + "Removed sphinxcontrib-serializinghtml-1.1.3-py_0.tar.bz2\n", + "Removed _libgcc_mutex-0.1-main.conda\n", + "Removed libgcc-ng-9.1.0-hdf63c60_0.conda\n", + "Removed mkl-service-2.3.0-py37he904b0f_0.conda\n", + "Removed gmp-6.1.2-h6c8ec71_1.conda\n", + "Removed psutil-5.6.3-py37h7b6447c_0.conda\n", + "Removed cryptography-2.7-py37h1ba5d50_0.conda\n", + "Removed fontconfig-2.13.0-h9420a91_0.conda\n", + "Removed libpng-1.6.37-hbc83047_0.conda\n", + "Removed notebook-6.0.1-py37_0.tar.bz2\n", + "Removed mkl_random-1.1.0-py37hd6b4f25_0.conda\n", + "Removed pytools-2019.1.1-py_0.tar.bz2\n", + "Removed toolz-0.10.0-py_0.tar.bz2\n", + "Removed nbformat-4.4.0-py37_0.conda\n", + "Removed chardet-3.0.4-py37_1003.conda\n", + "Removed ipykernel-5.1.2-py37h39e3cac_0.conda\n", + "Removed scipy-1.3.1-py37h7c811a0_0.conda\n", + "Removed sphinxcontrib-qthelp-1.0.2-py_0.tar.bz2\n", + "Removed fastcache-1.1.0-py37h7b6447c_0.conda\n", + "Removed docutils-0.15.2-py37_0.conda\n", + "Removed pyopenssl-19.0.0-py37_0.conda\n", + "Removed mccabe-0.6.1-py37_1.conda\n", + "Removed attrs-19.2.0-py_0.tar.bz2\n", + "Removed nbval-0.9.3-py_0.tar.bz2\n", + "Removed libffi-3.2.1-hd88cf55_4.conda\n", + "Removed freetype-2.9.1-h8a8886c_1.conda\n", + "Removed pytz-2019.3-py_0.tar.bz2\n", + "Removed jupyter_core-4.5.0-py_0.tar.bz2\n", + "Removed pickleshare-0.7.5-py37_0.conda\n", + "Removed tornado-6.0.3-py37h7b6447c_0.conda\n", + "Removed py-1.8.0-py37_0.conda\n", + "Removed pluggy-0.13.0-py37_0.tar.bz2\n", + "Removed bleach-3.1.0-py37_0.conda\n", + "Removed nbconvert-5.6.0-py37_1.tar.bz2\n", + "Removed idna-2.8-py37_0.conda\n", + "Removed appdirs-1.4.3-py37h28b3542_0.conda\n", + "Removed terminado-0.8.2-py37_0.conda\n", + "Removed pytest-5.2.1-py37_0.tar.bz2\n", + "Removed mkl-2019.4-243.conda\n", + "Removed icu-58.2-h9c2bf20_1.conda\n", + "Removed gmpy2-2.0.8-py37h10f8cd9_2.conda\n", + "Removed parso-0.5.1-py_0.tar.bz2\n", + "Removed sphinxcontrib-applehelp-1.0.1-py_0.tar.bz2\n", + "Removed expat-2.2.6-he6710b0_0.conda\n", + "Removed distributed-2.5.2-py_0.tar.bz2\n", + "Removed pyzmq-18.1.0-py37he6710b0_0.conda\n", + "Removed python-3.7.4-h265db76_1.conda\n", + "Removed sortedcontainers-2.1.0-py37_0.conda\n", + "Removed atomicwrites-1.3.0-py37_1.conda\n", + "Removed libxcb-1.13-h1bed415_1.conda\n", + "Removed snowballstemmer-2.0.0-py_0.tar.bz2\n", + "Removed python-dateutil-2.8.0-py37_0.conda\n", + "Removed openssl-1.1.1d-h7b6447c_2.conda\n", + "Removed dbus-1.13.6-h746ee38_0.conda\n", + "Removed prometheus_client-0.7.1-py_0.tar.bz2\n", + "Removed asn1crypto-1.0.1-py37_0.tar.bz2\n", + "Removed pandocfilters-1.4.2-py37_1.conda\n", + "Removed flake8-3.7.8-py37_1.tar.bz2\n", + "Removed cffi-1.12.3-py37h2e261b9_0.conda\n", + "Removed urllib3-1.24.2-py37_0.conda\n", + "Removed readline-7.0-h7b6447c_5.conda\n", + "Removed wcwidth-0.1.7-py37_0.conda\n", + "Removed sphinx_rtd_theme-0.4.3-py_0.tar.bz2\n", + "Removed certifi-2019.9.11-py37_0.tar.bz2\n", + "Removed matplotlib-3.1.1-py37h5429711_0.conda\n", + "Removed yaml-0.1.7-had09818_2.conda\n", + "Removed jsonschema-3.0.2-py37_0.conda\n", + "Removed tk-8.6.8-hbc83047_0.conda\n", + "Removed qt-5.9.7-h5867ecd_1.conda\n", + "Removed cloudpickle-1.2.2-py_0.tar.bz2\n", + "Removed gst-plugins-base-1.14.0-hbbd80ab_1.conda\n", + "Removed requests-2.22.0-py37_0.conda\n", + "Removed pyrsistent-0.15.4-py37h7b6447c_0.tar.bz2\n", + "Removed zeromq-4.3.1-he6710b0_3.conda\n", + "Removed blas-1.0-mkl.conda\n", + "Removed pyparsing-2.4.2-py_0.tar.bz2\n", + "Removed packaging-19.2-py_0.tar.bz2\n", + "Removed jpeg-9b-h024ee3a_2.conda\n", + "Removed pyqt-5.9.2-py37h05f1152_2.conda\n", + "Removed sympy-1.4-py37_0.conda\n", + "Removed libgfortran-ng-7.3.0-hdf63c60_0.conda\n", + "Removed pcre-8.43-he6710b0_0.conda\n", + "Removed more-itertools-7.2.0-py37_0.conda\n", + "Removed pygments-2.4.2-py_0.tar.bz2\n", + "Removed pytest-cov-2.7.1-py_0.tar.bz2\n", + "Removed intel-openmp-2019.4-243.conda\n", + "Removed entrypoints-0.3-py37_0.conda\n", + "Removed xz-5.2.4-h14c3975_4.conda\n", + "Removed backcall-0.1.0-py37_0.conda\n", + "Removed libedit-3.1.20181209-hc058e9b_0.conda\n", + "Removed msgpack-python-0.6.1-py37hfd86e86_1.conda\n", + "Removed pyyaml-5.1.2-py37h7b6447c_0.conda\n", + "Removed ca-certificates-2019.8.28-0.tar.bz2\n", + "Removed alabaster-0.7.12-py37_0.conda\n", + "Removed multidict-4.5.2-py37h7b6447c_0.conda\n", + "Removed ncurses-6.1-he6710b0_1.conda\n", + "Removed ptyprocess-0.6.0-py37_0.conda\n", + "Removed cached-property-1.5.1-py37_0.conda\n", + "Removed gstreamer-1.14.0-hb453b48_1.conda\n", + "Removed jupyter_client-5.3.3-py37_1.tar.bz2\n", + "Removed sphinxcontrib-devhelp-1.0.1-py_0.tar.bz2\n", + "Removed numpy-1.17.2-py37haad9e8e_0.conda\n", + "Removed sphinxcontrib-htmlhelp-1.0.2-py_0.tar.bz2\n", + "Removed graphviz-2.40.1-h21bd128_2.tar.bz2\n", + "Removed six-1.12.0-py36_0.tar.bz2\n", + "Removed asn1crypto-1.0.1-py36_0.tar.bz2\n", + "Removed cryptography-2.7-py36h1ba5d50_0.tar.bz2\n", + "Removed pyyaml-5.1.2-py36h7b6447c_0.tar.bz2\n", + "Removed libtiff-4.0.10-h2733197_2.tar.bz2\n", + "Removed cffi-1.12.3-py36h2e261b9_0.tar.bz2\n", + "Removed ptyprocess-0.6.0-py36_0.tar.bz2\n", + "Removed gst-plugins-base-1.14.0-hbbd80ab_1.tar.bz2\n", + "Removed libgfortran-ng-7.3.0-hdf63c60_0.tar.bz2\n", + "Removed msgpack-python-0.6.1-py36hfd86e86_1.tar.bz2\n", + "Removed mpc-1.1.0-h10f8cd9_1.tar.bz2\n", + "Removed pyzmq-18.1.0-py36he6710b0_0.tar.bz2\n", + "Removed olefile-0.46-py36_0.tar.bz2\n", + "Removed pyrsistent-0.15.4-py36h7b6447c_0.tar.bz2\n", + "Removed expat-2.2.6-he6710b0_0.tar.bz2\n", + "Removed yaml-0.1.7-h96e3832_1.tar.bz2\n", + "Removed h5py-2.9.0-py36h7918eee_0.tar.bz2\n", + "Removed libffi-3.2.1-h4deb6c0_3.tar.bz2\n", + "Removed testpath-0.4.2-py36_0.tar.bz2\n", + "Removed markupsafe-1.1.1-py36h7b6447c_0.tar.bz2\n", + "Removed blas-1.0-mkl.tar.bz2\n", + "Removed py-1.8.0-py36_0.tar.bz2\n", + "Removed mpmath-1.1.0-py36_0.tar.bz2\n", + "Removed pytest-5.2.1-py36_0.tar.bz2\n", + "Removed pyopenssl-19.0.0-py36_0.tar.bz2\n", + "Removed imagesize-1.1.0-py36_0.tar.bz2\n", + "Removed zeromq-4.3.1-he6710b0_3.tar.bz2\n", + "Removed pillow-6.2.0-py36h34e0f95_0.tar.bz2\n", + "Removed terminado-0.8.2-py36_0.tar.bz2\n", + "Removed requests-2.22.0-py36_0.tar.bz2\n", + "Removed jupyter_client-5.3.3-py36_1.tar.bz2\n", + "Removed qt-5.9.7-h5867ecd_1.tar.bz2\n", + "Removed mkl_random-1.1.0-py36hd6b4f25_0.tar.bz2\n", + "Removed mkl_fft-1.0.14-py36ha843d7b_0.tar.bz2\n", + "Removed pycodestyle-2.5.0-py36_0.tar.bz2\n", + "Removed pango-1.42.4-h049681c_0.tar.bz2\n", + "Removed flake8-3.7.8-py36_1.tar.bz2\n", + "Removed ipython-7.8.0-py36h39e3cac_0.tar.bz2\n", + "Removed pandocfilters-1.4.2-py36_1.tar.bz2\n", + "Removed heapdict-1.0.1-py_0.tar.bz2\n", + "Removed notebook-6.0.1-py36_0.tar.bz2\n", + "Removed mistune-0.8.4-py36h7b6447c_0.tar.bz2\n", + "Removed alabaster-0.7.12-py36_0.tar.bz2\n", + "Removed jedi-0.15.1-py36_0.tar.bz2\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Removed certifi-2019.9.11-py36_0.tar.bz2\n", + "Removed gstreamer-1.14.0-hb453b48_1.tar.bz2\n", + "Removed pandoc-2.2.3.2-0.tar.bz2\n", + "Removed pandas-0.25.1-py36he6710b0_0.tar.bz2\n", + "Removed chardet-3.0.4-py36_1003.tar.bz2\n", + "Removed libedit-3.1.20181209-hc058e9b_0.tar.bz2\n", + "Removed cached-property-1.5.1-py36_0.tar.bz2\n", + "Removed pyqt-5.9.2-py36h22d08a2_1.tar.bz2\n", + "Removed tornado-6.0.3-py36h7b6447c_0.tar.bz2\n", + "Removed psutil-5.6.3-py36h7b6447c_0.tar.bz2\n", + "Removed libsodium-1.0.16-h1bed415_0.tar.bz2\n", + "Removed gmp-6.1.2-hb3b607b_0.tar.bz2\n", + "Removed pexpect-4.7.0-py36_0.tar.bz2\n", + "Removed nbformat-4.4.0-py36_0.tar.bz2\n", + "Removed python-3.6.9-h265db76_0.tar.bz2\n", + "Removed docutils-0.15.2-py36_0.tar.bz2\n", + "Removed coverage-4.5.4-py36h7b6447c_0.tar.bz2\n", + "Removed xz-5.2.4-h14c3975_4.tar.bz2\n", + "Removed mccabe-0.6.1-py36_1.tar.bz2\n", + "Removed jsonschema-3.0.2-py36_0.tar.bz2\n", + "Removed fontconfig-2.13.0-h9420a91_0.tar.bz2\n", + "Removed networkx-2.3-py_0.tar.bz2\n", + "Removed importlib_metadata-0.23-py36_0.tar.bz2\n", + "Removed send2trash-1.5.0-py36_0.tar.bz2\n", + "Removed pluggy-0.13.0-py36_0.tar.bz2\n", + "Removed bokeh-1.3.4-py36_0.tar.bz2\n", + "Removed pcre-8.43-he6710b0_0.tar.bz2\n", + "Removed libstdcxx-ng-9.1.0-hdf63c60_0.tar.bz2\n", + "Removed numpy-base-1.17.2-py36hde5b4d6_0.tar.bz2\n", + "Removed wheel-0.33.6-py36_0.tar.bz2\n", + "Removed scikit-image-0.15.0-py36he6710b0_0.tar.bz2\n", + "Removed blosc-1.17.0-he1b5a44_1.tar.bz2\n", + "Removed zlib-1.2.11-h7b6447c_3.tar.bz2\n", + "Removed cairo-1.14.12-h8948797_3.tar.bz2\n", + "Removed mpfr-4.0.1-hdf1c602_3.tar.bz2\n", + "Removed zstd-1.3.7-h0b5b093_0.tar.bz2\n", + "Removed numpy-1.17.2-py36haad9e8e_0.tar.bz2\n", + "Removed bleach-3.1.0-py36_0.tar.bz2\n", + "Removed pyflakes-2.1.1-py36_0.tar.bz2\n", + "Removed bzip2-1.0.8-h7b6447c_0.tar.bz2\n", + "Removed sympy-1.4-py36_0.tar.bz2\n", + "Removed urllib3-1.24.2-py36_0.tar.bz2\n", + "Removed more-itertools-7.2.0-py36_0.tar.bz2\n", + "Removed pip-19.2.3-py36_0.tar.bz2\n", + "Removed python-dateutil-2.8.0-py36_0.tar.bz2\n", + "Removed wcwidth-0.1.7-py36_0.tar.bz2\n", + "Removed dask-2.5.2-py_0.tar.bz2\n", + "Removed glib-2.56.2-hd408876_0.tar.bz2\n", + "Removed setuptools-41.4.0-py36_0.tar.bz2\n", + "Removed scipy-1.3.1-py36h7c811a0_0.tar.bz2\n", + "Removed libgcc-ng-9.1.0-hdf63c60_0.tar.bz2\n", + "Removed sortedcontainers-2.1.0-py36_0.tar.bz2\n", + "Removed webencodings-0.5.1-py36_1.tar.bz2\n", + "Removed numexpr-2.7.0-py36h9e4a6bb_0.tar.bz2\n", + "Removed appdirs-1.4.3-py36h28b3542_0.tar.bz2\n", + "Removed pycparser-2.19-py36_0.tar.bz2\n", + "Removed pickleshare-0.7.5-py36_0.tar.bz2\n", + "Removed harfbuzz-1.8.8-hffaf4a1_0.tar.bz2\n", + "Removed idna-2.8-py36_0.tar.bz2\n", + "Removed libxml2-2.9.9-hea5a465_1.tar.bz2\n", + "Removed mkl-2019.5-281.tar.bz2\n", + "Removed mock-3.0.5-py36_0.tar.bz2\n", + "Removed traitlets-4.3.3-py36_0.tar.bz2\n", + "Removed graphite2-1.3.13-h23475e2_0.tar.bz2\n", + "Removed fastcache-1.1.0-py36h7b6447c_0.tar.bz2\n", + "Removed partd-1.0.0-py_0.tar.bz2\n", + "Removed lzo-2.10-h1bfc0ba_1.tar.bz2\n", + "Removed jpeg-9b-habf39ab_1.tar.bz2\n", + "Removed backcall-0.1.0-py36_0.tar.bz2\n", + "Removed cython-0.29.13-py36he6710b0_0.tar.bz2\n", + "Removed py-cpuinfo-5.0.0-py_0.tar.bz2\n", + "Removed readline-7.0-h7b6447c_5.tar.bz2\n", + "Removed ncurses-6.1-he6710b0_1.tar.bz2\n", + "Removed libpng-1.6.37-hbc83047_0.tar.bz2\n", + "Removed pytables-3.5.2-py36h71ec239_1.tar.bz2\n", + "Removed gmpy2-2.0.8-py36h10f8cd9_2.tar.bz2\n", + "Removed atomicwrites-1.3.0-py36_1.tar.bz2\n", + "Removed dbus-1.13.6-h746ee38_0.tar.bz2\n", + "Removed matplotlib-3.1.1-py36h5429711_0.tar.bz2\n", + "Removed locket-0.2.0-py36_1.tar.bz2\n", + "Removed pywavelets-1.0.3-py36hdd07704_1.tar.bz2\n", + "Removed imageio-2.6.0-py36_0.tar.bz2\n", + "Removed multidict-4.5.2-py36h7b6447c_0.tar.bz2\n", + "Removed mkl-service-2.3.0-py36he904b0f_0.tar.bz2\n", + "Removed entrypoints-0.3-py36_0.tar.bz2\n", + "Removed intel-openmp-2019.5-281.tar.bz2\n", + "Removed fsspec-0.5.2-py_0.tar.bz2\n", + "Removed ipython_genutils-0.2.0-py36_0.tar.bz2\n", + "Removed icu-58.2-h211956c_0.tar.bz2\n", + "Removed nb_conda_kernels-2.2.2-py36_0.tar.bz2\n", + "Removed sip-4.19.13-py36he6710b0_0.tar.bz2\n", + "Removed kiwisolver-1.1.0-py36he6710b0_0.tar.bz2\n", + "Removed libuuid-1.0.3-h1bed415_2.tar.bz2\n", + "Removed hdf5-1.10.4-hb1b8bf9_0.tar.bz2\n", + "Removed libxcb-1.13-h1bed415_1.tar.bz2\n", + "Removed decorator-4.4.0-py36_1.tar.bz2\n", + "Removed tk-8.6.8-hbc83047_0.tar.bz2\n", + "Removed openssl-1.1.1-h7b6447c_0.tar.bz2\n", + "Removed python-graphviz-0.10.1-py_0.tar.bz2\n", + "Removed snakeviz-2.0.1-py36_0.tar.bz2\n", + "Removed pixman-0.38.0-h7b6447c_0.tar.bz2\n", + "Removed nb_conda-2.2.1-py36_0.tar.bz2\n", + "Removed click-7.0-py36_0.tar.bz2\n", + "Removed cytoolz-0.10.0-py36h7b6447c_0.tar.bz2\n", + "Removed ipykernel-5.1.2-py36h39e3cac_0.tar.bz2\n", + "Removed fribidi-1.0.5-h7b6447c_0.tar.bz2\n", + "Removed pysocks-1.7.1-py36_0.tar.bz2\n", + "Removed cycler-0.10.0-py36_0.tar.bz2\n", + "Removed freetype-2.9.1-h8a8886c_1.tar.bz2\n", + "Removed nbconvert-5.6.0-py36_1.tar.bz2\n", + "WARNING: /root/.conda/pkgs does not exist\n", + "Cache location: /opt/conda/pkgs\n", + "Will remove the following packages:\n", + "/opt/conda/pkgs\n", + "---------------\n", + "\n", + "blas-1.0-mkl 15 KB\n", + "_libgcc_mutex-0.1-main 7 KB\n", + "numpy-1.17.2-py37haad9e8e_0 11 KB\n", + "wheel-0.33.6-py36_0 129 KB\n", + "scipy-1.3.1-py36h7c811a0_0 61.6 MB\n", + "numpy-1.17.2-py36haad9e8e_0 11 KB\n", + "dask-2.5.2-py_0 40 KB\n", + "pandas-0.25.1-py36he6710b0_0 46.2 MB\n", + "\n", + "---------------------------------------------------\n", + "Total: 108.0 MB\n", + "\n", + "removing blas-1.0-mkl\n", + "removing _libgcc_mutex-0.1-main\n", + "removing numpy-1.17.2-py37haad9e8e_0\n", + "removing wheel-0.33.6-py36_0\n", + "removing scipy-1.3.1-py36h7c811a0_0\n", + "removing numpy-1.17.2-py36haad9e8e_0\n", + "removing dask-2.5.2-py_0\n", + "removing pandas-0.25.1-py36he6710b0_0\n", + "Removing intermediate container 042a9e35656f\n", + " ---> 1a22adac58e1\n", "Step 14/15 : ENV PYTHONPATH=$PYTHONPATH:devito/app\n", - " ---> Using cache\n", - " ---> ef2ab56fd9ae\n", + " ---> Running in 8b5d8220c6e3\n", + "Removing intermediate container 8b5d8220c6e3\n", + " ---> 1448c7f7a95c\n", "Step 15/15 : CMD /bin/bash\n", - " ---> Using cache\n", - " ---> 5b225e465622\n", - "Successfully built 5b225e465622\n", + " ---> Running in 6cc39f3123be\n", + "Removing intermediate container 6cc39f3123be\n", + " ---> 12e47a24fca8\n", + "Successfully built 12e47a24fca8\n", "Successfully tagged georgedockeraccount/fwi01_azureml:sdk.v1.0.65\n" ] } @@ -559,7 +2351,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -568,7 +2360,7 @@ "'georgedockeraccount/fwi01_azureml:sdk.v1.0.65'" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, @@ -578,7 +2370,7 @@ "'bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, @@ -588,7 +2380,7 @@ "b'/\\n1.0.65\\n'" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -618,7 +2410,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -627,7 +2419,7 @@ "'./fwi01_azureml_buildexperimentationdockerimage.log'" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -646,16 +2438,25 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "content of devito tests log file before testing:\n", + "Before running e13n container... \n" + ] + }, { "data": { "text/plain": [ - "''" + "' python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -679,16 +2480,16 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks:/workspace:rw georgedockeraccount/fwi01_azureml:sdk.v1.0.65 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; \"'" + "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks:/workspace:rw georgedockeraccount/fwi01_azureml:sdk.v1.0.65 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/ \"'" ] }, - "execution_count": 16, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" }, @@ -701,59 +2502,59 @@ "base /opt/conda\n", "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", "\n", - "total 504\n", - "-rw-r--r-- 1 root root 11521 Oct 7 21:43 conftest.py\n", - "-rw-r--r-- 1 root root 6425 Oct 7 21:43 test_adjoint.py\n", - "-rw-r--r-- 1 root root 13882 Oct 7 21:43 test_autotuner.py\n", - "-rw-r--r-- 1 root root 9727 Oct 7 21:43 test_checkpointing.py\n", - "-rw-r--r-- 1 root root 1095 Oct 7 21:43 test_constant.py\n", - "-rw-r--r-- 1 root root 52392 Oct 7 21:43 test_data.py\n", - "-rw-r--r-- 1 root root 481 Oct 7 21:43 test_dependency_bugs.py\n", - "-rw-r--r-- 1 root root 16585 Oct 7 21:43 test_derivatives.py\n", - "-rw-r--r-- 1 root root 30846 Oct 7 21:43 test_dimension.py\n", - "-rw-r--r-- 1 root root 21233 Oct 7 21:43 test_dle.py\n", - "-rw-r--r-- 1 root root 1138 Oct 7 21:43 test_docstrings.py\n", - "-rw-r--r-- 1 root root 26251 Oct 7 21:43 test_dse.py\n", - "-rw-r--r-- 1 root root 8612 Oct 7 21:43 test_gradient.py\n", - "-rw-r--r-- 1 root root 15229 Oct 7 21:43 test_interpolation.py\n", - "-rw-r--r-- 1 root root 31514 Oct 7 21:43 test_ir.py\n", - "-rw-r--r-- 1 root root 60563 Oct 7 21:43 test_mpi.py\n", - "-rw-r--r-- 1 root root 61542 Oct 7 21:43 test_operator.py\n", - "-rw-r--r-- 1 root root 11839 Oct 7 21:43 test_ops.py\n", - "-rw-r--r-- 1 root root 11252 Oct 7 21:43 test_pickle.py\n", - "-rw-r--r-- 1 root root 1815 Oct 7 21:43 test_resample.py\n", - "-rw-r--r-- 1 root root 1754 Oct 7 21:43 test_save.py\n", - "-rw-r--r-- 1 root root 5711 Oct 7 21:43 test_subdomains.py\n", - "-rw-r--r-- 1 root root 10526 Oct 7 21:43 test_symbol_caching.py\n", - "-rw-r--r-- 1 root root 1896 Oct 7 21:43 test_symbolic_coefficients.py\n", - "-rw-r--r-- 1 root root 3186 Oct 7 21:43 test_timestepping.py\n", - "-rw-r--r-- 1 root root 603 Oct 7 21:43 test_tools.py\n", - "-rw-r--r-- 1 root root 3302 Oct 7 21:43 test_tti.py\n", - "-rw-r--r-- 1 root root 8835 Oct 7 21:43 test_visitors.py\n", - "-rw-r--r-- 1 root root 21810 Oct 7 21:43 test_yask.py\n", + "total 508\n", + "-rw-r--r-- 1 root root 11521 Oct 9 17:55 conftest.py\n", + "-rw-r--r-- 1 root root 6425 Oct 9 17:55 test_adjoint.py\n", + "-rw-r--r-- 1 root root 13882 Oct 9 17:55 test_autotuner.py\n", + "-rw-r--r-- 1 root root 9727 Oct 9 17:55 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Oct 9 17:55 test_constant.py\n", + "-rw-r--r-- 1 root root 53290 Oct 9 17:55 test_data.py\n", + "-rw-r--r-- 1 root root 481 Oct 9 17:55 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16585 Oct 9 17:55 test_derivatives.py\n", + "-rw-r--r-- 1 root root 30846 Oct 9 17:55 test_dimension.py\n", + "-rw-r--r-- 1 root root 21233 Oct 9 17:55 test_dle.py\n", + "-rw-r--r-- 1 root root 1157 Oct 9 17:55 test_docstrings.py\n", + "-rw-r--r-- 1 root root 26251 Oct 9 17:55 test_dse.py\n", + "-rw-r--r-- 1 root root 8612 Oct 9 17:55 test_gradient.py\n", + "-rw-r--r-- 1 root root 15229 Oct 9 17:55 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31514 Oct 9 17:55 test_ir.py\n", + "-rw-r--r-- 1 root root 60563 Oct 9 17:55 test_mpi.py\n", + "-rw-r--r-- 1 root root 61542 Oct 9 17:55 test_operator.py\n", + "-rw-r--r-- 1 root root 12214 Oct 9 17:55 test_ops.py\n", + "-rw-r--r-- 1 root root 11252 Oct 9 17:55 test_pickle.py\n", + "-rw-r--r-- 1 root root 1815 Oct 9 17:55 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Oct 9 17:55 test_save.py\n", + "-rw-r--r-- 1 root root 5711 Oct 9 17:55 test_subdomains.py\n", + "-rw-r--r-- 1 root root 10526 Oct 9 17:55 test_symbol_caching.py\n", + "-rw-r--r-- 1 root root 1896 Oct 9 17:55 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 3186 Oct 9 17:55 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Oct 9 17:55 test_tools.py\n", + "-rw-r--r-- 1 root root 3302 Oct 9 17:55 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Oct 9 17:55 test_visitors.py\n", + "-rw-r--r-- 1 root root 21810 Oct 9 17:55 test_yask.py\n", "1.0.65\n", "\n", "content of devito tests log file after testing:\n", "============================= test session starts ==============================\n", - "platform linux -- Python 3.6.9, pytest-5.2.0, py-1.8.0, pluggy-0.13.0\n", + "platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0\n", "rootdir: /devito, inifile: setup.cfg\n", "plugins: nbval-0.9.3, cov-2.7.1\n", - "collected 879 items / 2 skipped / 877 selected\n", + "collected 881 items / 2 skipped / 879 selected\n", "\n", "tests/test_adjoint.py .......................... [ 2%]\n", "tests/test_autotuner.py ..........s.... [ 4%]\n", "tests/test_checkpointing.py ....... [ 5%]\n", "tests/test_constant.py . [ 5%]\n", - "tests/test_data.py ..........................ssssssssssssss.ss. [ 10%]\n", + "tests/test_data.py ..........................ssssssssssssss.ss.. [ 10%]\n", "tests/test_dependency_bugs.py . [ 10%]\n", - "tests/test_derivatives.py .............................................. [ 15%]\n", + "tests/test_derivatives.py .............................................. [ 16%]\n", "........................................................................ [ 24%]\n", "........................................................FF...F.......... [ 32%]\n", - "...... [ 32%]\n", + "...... [ 33%]\n", "tests/test_dimension.py ............................... [ 36%]\n", "tests/test_dle.py ...................................................... [ 42%]\n", "......................................... [ 47%]\n", - "tests/test_docstrings.py .............. [ 48%]\n", + "tests/test_docstrings.py ............... [ 49%]\n", "tests/test_dse.py ......x............................................... [ 55%]\n", "......................s.... [ 58%]\n", "tests/test_gradient.py .... [ 58%]\n", @@ -779,7 +2580,7 @@ "=================================== FAILURES ===================================\n", "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-8] _____________________\n", "\n", - "self = , so = 8, ndim = 1\n", + "self = , so = 8, ndim = 1\n", "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", "\n", " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", @@ -818,8 +2619,8 @@ " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", "E assert False\n", - "E + where False = ((1 - (0.3281994 / 0.3282032)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", + "E + where False = ((1 - (0.3281994 / 0.3282032)), 0, atol=1e-05)\n", + "E + where = np.isclose\n", "\n", "tests/test_derivatives.py:397: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", @@ -830,7 +2631,7 @@ "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________\n", "\n", - "self = , so = 12, ndim = 1\n", + "self = , so = 12, ndim = 1\n", "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", "\n", " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", @@ -869,8 +2670,8 @@ " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", "E assert False\n", - "E + where False = ((1 - (0.2223196 / 0.22231674)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", + "E + where False = ((1 - (0.2223196 / 0.22231674)), 0, atol=1e-05)\n", + "E + where = np.isclose\n", "\n", "tests/test_derivatives.py:397: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", @@ -881,7 +2682,7 @@ "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________\n", "\n", - "self = , so = 12, ndim = 2\n", + "self = , so = 12, ndim = 2\n", "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", "\n", " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", @@ -920,8 +2721,8 @@ " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", "E assert False\n", - "E + where False = ((1 - (11.338287 / 11.338157)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", + "E + where False = ((1 - (11.338287 / 11.338157)), 0, atol=1e-05)\n", + "E + where = np.isclose\n", "\n", "tests/test_derivatives.py:397: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", @@ -934,7 +2735,7 @@ "------------------------------ Captured log call -------------------------------\n", "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "======= 3 failed, 800 passed, 77 skipped, 1 xfailed in 866.14s (0:14:26) =======\n" + "======= 3 failed, 802 passed, 77 skipped, 1 xfailed in 885.98s (0:14:45) =======\n" ] } ], @@ -957,7 +2758,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -981,7 +2782,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -990,13 +2791,33 @@ "text": [ "The push refers to repository [docker.io/georgedockeraccount/fwi01_azureml]\n", "\n", - "\u001b[1B8da5e589: Preparing \n", - "\u001b[1Ba559fdba: Preparing \n", - "\u001b[1B772bb00d: Preparing \n", - "\u001b[1B54377100: Preparing \n", + "\u001b[1B9886dd1f: Preparing \n", + "\u001b[1B99799fa2: Preparing \n", + "\u001b[1Bec869ef0: Preparing \n", + "\u001b[1B55141871: Preparing \n", "\u001b[1Bf8fc4c9a: Preparing \n", - "\u001b[1Bba47210e: Preparing \n", - "\u001b[4B54377100: Layer already exists \u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[Ksdk.v1.0.65: digest: sha256:f327c88a842c9e77df4df9ae1b980367ea053be4a0c778e1647e105d2cbf08a3 size: 1800\n" + "\u001b[1Bba47210e: Preparing \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[6B99799fa2: Pushing 1.134GB/3.024GB\u001b[5A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 552.1MB/3.024GB\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 823.1MB/3.024GB\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 861.2MB/2.81GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 978.4MB/3.024GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[6B99799fa2: Pushing 2.206GB/3.024GB\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 1.825GB/2.81GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 1.896GB/2.81GB\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[7B9886dd1f: Pushed 3.099GB/3.024GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 2.445GB/2.81GB\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 2.798GB/3.024GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[Ksdk.v1.0.65: digest: sha256:3639f4b469e0fb59c8531022448f240bff49d9d06c8d9e788656d5dd1bfbb07b size: 1800\n" ] } ], @@ -1007,7 +2828,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [ { diff --git a/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb index 5dcc9299..f4c941c5 100755 --- a/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -764,7 +764,7 @@ ], "source": [ "# Verify that cluster does not exist already\n", - "max_nodes_value = 3\n", + "max_nodes_value = 5\n", "try:\n", " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", " print(\"Found existing gpu cluster\")\n", @@ -802,7 +802,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1eaca5d2dfe54a7a8cec9c2a33c0352e", + "model_id": "adce141a753841b08c9861dd30246ec1", "version_major": 2, "version_minor": 0 }, diff --git a/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log b/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log index 5dfe1288..01597fe0 100644 --- a/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log +++ b/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log @@ -1,23 +1,23 @@ ============================= test session starts ============================== -platform linux -- Python 3.6.9, pytest-5.2.0, py-1.8.0, pluggy-0.13.0 +platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 rootdir: /devito, inifile: setup.cfg plugins: nbval-0.9.3, cov-2.7.1 -collected 879 items / 2 skipped / 877 selected +collected 881 items / 2 skipped / 879 selected tests/test_adjoint.py .......................... [ 2%] tests/test_autotuner.py ..........s.... [ 4%] tests/test_checkpointing.py ....... [ 5%] tests/test_constant.py . [ 5%] -tests/test_data.py ..........................ssssssssssssss.ss. [ 10%] +tests/test_data.py ..........................ssssssssssssss.ss.. [ 10%] tests/test_dependency_bugs.py . [ 10%] -tests/test_derivatives.py .............................................. [ 15%] +tests/test_derivatives.py .............................................. [ 16%] ........................................................................ [ 24%] ........................................................FF...F.......... [ 32%] -...... [ 32%] +...... [ 33%] tests/test_dimension.py ............................... [ 36%] tests/test_dle.py ...................................................... [ 42%] ......................................... [ 47%] -tests/test_docstrings.py .............. [ 48%] +tests/test_docstrings.py ............... [ 49%] tests/test_dse.py ......x............................................... [ 55%] ......................s.... [ 58%] tests/test_gradient.py .... [ 58%] @@ -43,7 +43,7 @@ tests/test_visitors.py ......... [100%] =================================== FAILURES =================================== ____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-8] _____________________ -self = , so = 8, ndim = 1 +self = , so = 8, ndim = 1 derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 @pytest.mark.parametrize('so', [2, 4, 8, 12]) @@ -82,8 +82,8 @@ derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) > assert np.isclose(1 - a/b, 0, atol=1e-5) E assert False -E + where False = ((1 - (0.3281994 / 0.3282032)), 0, atol=1e-05) -E + where = np.isclose +E + where False = ((1 - (0.3281994 / 0.3282032)), 0, atol=1e-05) +E + where = np.isclose tests/test_derivatives.py:397: AssertionError ----------------------------- Captured stderr call ----------------------------- @@ -94,7 +94,7 @@ INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s ____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________ -self = , so = 12, ndim = 1 +self = , so = 12, ndim = 1 derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 @pytest.mark.parametrize('so', [2, 4, 8, 12]) @@ -133,8 +133,8 @@ derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) > assert np.isclose(1 - a/b, 0, atol=1e-5) E assert False -E + where False = ((1 - (0.2223196 / 0.22231674)), 0, atol=1e-05) -E + where = np.isclose +E + where False = ((1 - (0.2223196 / 0.22231674)), 0, atol=1e-05) +E + where = np.isclose tests/test_derivatives.py:397: AssertionError ----------------------------- Captured stderr call ----------------------------- @@ -145,7 +145,7 @@ INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s ____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________ -self = , so = 12, ndim = 2 +self = , so = 12, ndim = 2 derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 @pytest.mark.parametrize('so', [2, 4, 8, 12]) @@ -184,8 +184,8 @@ derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) > assert np.isclose(1 - a/b, 0, atol=1e-5) E assert False -E + where False = ((1 - (11.338287 / 11.338157)), 0, atol=1e-05) -E + where = np.isclose +E + where False = ((1 - (11.338287 / 11.338157)), 0, atol=1e-05) +E + where = np.isclose tests/test_derivatives.py:397: AssertionError ----------------------------- Captured stderr call ----------------------------- @@ -198,4 +198,4 @@ Operator `Kernel` run in 0.01 s ------------------------------ Captured log call ------------------------------- INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -======= 3 failed, 800 passed, 77 skipped, 1 xfailed in 866.14s (0:14:26) ======= +======= 3 failed, 802 passed, 77 skipped, 1 xfailed in 885.98s (0:14:45) ======= From c94d094b9ce007fd08eb84d3aec3fdbcddaf2d6b Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Fri, 18 Oct 2019 13:42:22 +0000 Subject: [PATCH 061/207] Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 --- .flake8 | 5 +++++ .pre-commit-config.yaml | 10 ++++++++++ .pylintrc | 9 --------- CONTRIBUTING.md | 14 +++++++++++++- pyproject.toml | 16 +++++++++++++++- 5 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 .flake8 create mode 100644 .pre-commit-config.yaml delete mode 100644 .pylintrc diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..bd718670 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203, E266, E501, W503 +max-line-length = 88 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..cddba3d4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: +- repo: https://github.com/psf/black + rev: stable + hooks: + - id: black + language_version: python3.6 +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v1.2.3 + hooks: + - id: flake8 diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 14131a70..00000000 --- a/.pylintrc +++ /dev/null @@ -1,9 +0,0 @@ -[MASTER] -extension-pkg-whitelist=numpy,torch,cv2 - -[TYPECHECK] - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members=numpy.*,torch.*,cv2.* \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 52e268c4..4c422e46 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Contributions are welcomed! Here's a few things to know: * [Steps to Contributing](#steps-to-contributing) -* [Coding Guidelines](#TODO) +* [Coding Guidelines](#coding-guidelines) * [Microsoft Contributor License Agreement](#microsoft-contributor-license-agreement) * [Code of Conduct](#code-of-conduct) @@ -26,6 +26,18 @@ Once the features included in a [milestone](https://github.com/Microsoft/DeepSei We strive to maintain high quality code to make the utilities in the repository easy to understand, use, and extend. We also work hard to maintain a friendly and constructive environment. We've found that having clear expectations on the development process and consistent style helps to ensure everyone can contribute and collaborate effectively. +### Code formatting and style checking +We use `git-hooks` to automate the process of formatting and style checking the code. In particular, we use `black` as a code formatter, `flake8` for style checking, and the `pre-commit` Python framework, which ensures that both, code formatter and checker, are ran on the code during commit. If they are executed with no issues, then the commit is made, otherwise, the commit is denied until stylistic or formatting changes are made. + +Please follow these instructions to set up `pre-commit` in your environment. + +``` +pip install pre-commit +pre-commit install +``` + +The above will install the pre-commit package, and install git hooks specified in `.pre-commit-config.yaml` into your `.git/` directory. + ## Microsoft Contributor License Agreement Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. diff --git a/pyproject.toml b/pyproject.toml index a8f43fef..fed6c975 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,16 @@ [tool.black] -line-length = 79 +line-length = 88 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' From b1f71c8447bdc33f291ff6c3c37f4715509ede9c Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Mon, 21 Oct 2019 16:31:46 +0000 Subject: [PATCH 062/207] Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 --- README.md | 18 + TODO.md | 2 + environment/anaconda/local/environment.yml | 5 +- .../local/configs/section_deconvnet_skip.yaml | 2 +- .../dutchf3_section/local/train.py | 2 +- .../interpretation/dutchf3_voxel/README.md | 15 + .../dutchf3_voxel/configs/texture_net.yaml | 41 ++ .../interpretation/dutchf3_voxel/default.py | 85 +++ .../interpretation/dutchf3_voxel/train.py | 239 +++++++++ .../voxel2pixel/test_parallel.py | 6 +- .../interpretation/voxel2pixel/texture_net.py | 24 +- .../interpretation/voxel2pixel/train.py | 2 +- .../dutchf3/data.py | 357 +++++++++++-- .../dutchf3/utils}/__init__.py | 0 .../dutchf3/utils/batch.py | 503 ++++++++++++++++++ .../models/__init__.py | 0 .../models/texture_net.py | 0 scripts/data_symlink.sh | 8 + 18 files changed, 1233 insertions(+), 76 deletions(-) create mode 100644 TODO.md create mode 100644 experiments/interpretation/dutchf3_voxel/README.md create mode 100644 experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml create mode 100644 experiments/interpretation/dutchf3_voxel/default.py create mode 100644 experiments/interpretation/dutchf3_voxel/train.py rename interpretation/{models => deepseismic_interpretation/dutchf3/utils}/__init__.py (100%) create mode 100644 interpretation/deepseismic_interpretation/dutchf3/utils/batch.py create mode 100644 interpretation/deepseismic_interpretation/models/__init__.py rename interpretation/{ => deepseismic_interpretation}/models/texture_net.py (100%) create mode 100755 scripts/data_symlink.sh diff --git a/README.md b/README.md index 8f1d7e8b..8cccbace 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,24 @@ conda env update --file environment/anaconda/local/environment.yml ``` from the root of DeepSeismic repo. +### Viewers + +#### segyviewer + +For seismic interpretation (segmentation), if you want to visualize cross-sections of a 3D volume (both the input velocity model and the segmented output) you can use +[segyviewer](https://github.com/equinor/segyviewer), for example like so: +```bash +segyviewer /mnt/dutchf3/data.segy +``` + +To install [segyviewer](https://github.com/equinor/segyviewer) run +```bash +conda env -n segyviewer python=2.7 +conda activate segyviewer +conda install -c anaconda pyqt=4.11.4 +pip install segyviewer +``` + ## Benchmarks ### Dense Labels diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..4d20c8b2 --- /dev/null +++ b/TODO.md @@ -0,0 +1,2 @@ +1. recode to add dynamically LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf +1. other diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 4f3d2f22..15973ae0 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -12,6 +12,8 @@ dependencies: - pandas - opencv - scikit-learn + - tensorflow-gpu=1.14 + - tqdm - pip: - segyio - pytorch-ignite @@ -20,7 +22,6 @@ dependencies: - tabulate==0.8.2 - Jinja2 - gitpython - - tensorflow # Installing for Tensorboard - tensorboard - tensorboardx - tqdm @@ -30,4 +31,4 @@ dependencies: - black - pylint - scipy==1.1.0 - + diff --git a/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml index 9018a5e9..cbab8c60 100644 --- a/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml +++ b/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/vapaunic/repos/DeepSeismic/logging.conf +LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf SEED: 2019 DATASET: diff --git a/experiments/interpretation/dutchf3_section/local/train.py b/experiments/interpretation/dutchf3_section/local/train.py index 5c11e650..16ddcc4d 100644 --- a/experiments/interpretation/dutchf3_section/local/train.py +++ b/experiments/interpretation/dutchf3_section/local/train.py @@ -52,7 +52,7 @@ from torch.utils import data -def prepare_batch(batch, device=None, non_blocking=False): +def prepare_batch(batch, device="cuda", non_blocking=False): x, y = batch return ( convert_tensor(x, device=device, non_blocking=non_blocking), diff --git a/experiments/interpretation/dutchf3_voxel/README.md b/experiments/interpretation/dutchf3_voxel/README.md new file mode 100644 index 00000000..0a016ff8 --- /dev/null +++ b/experiments/interpretation/dutchf3_voxel/README.md @@ -0,0 +1,15 @@ +First, make sure that `/mnt/dutch_f3` folder exists and you have write access. + +Next, to get the main input dataset which is the [Dutch F3 dataset](https://terranubis.com/datainfo/Netherlands-Offshore-F3-Block-Complete), +navigate to [MalenoV](https://github.com/bolgebrygg/MalenoV) project website and follow the links (which will lead to +[this](https://drive.google.com/drive/folders/0B7brcf-eGK8CbGhBdmZoUnhiTWs) download). Save this file as +`/mnt/dutch_f3/data.segy` + +To download the train and validation masks, from the root of the repo, run +```bash +./scripts/get_F3_voxel.sh /mnt/dutch_f3 +``` + +This will also download train and validation masks to the same location as data.segy. + +That's it! diff --git a/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml b/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml new file mode 100644 index 00000000..76358ef1 --- /dev/null +++ b/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml @@ -0,0 +1,41 @@ +# TextureNet configuration + +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'output' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /home/maxkaz/DeepSeismic/logging.conf +SEED: 2019 +WINDOW_SIZE: 65 + +DATASET: + NUM_CLASSES: 2 + ROOT: /mnt/dutchf3 + FILENAME: data.segy + +MODEL: + NAME: texture_net + IN_CHANNELS: 1 + NUM_FILTERS: 50 + +TRAIN: + BATCH_SIZE_PER_GPU: 32 + END_EPOCH: 5000 + LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + DEPTH: "voxel" # Options are No, Patch, Section and Voxel + MODEL_DIR: "models" + +VALIDATION: + BATCH_SIZE_PER_GPU: 32 + +TEST: + MODEL_PATH: "" + SPLIT: 'Both' # Can be Both, Test1, Test2 + diff --git a/experiments/interpretation/dutchf3_voxel/default.py b/experiments/interpretation/dutchf3_voxel/default.py new file mode 100644 index 00000000..a79761f4 --- /dev/null +++ b/experiments/interpretation/dutchf3_voxel/default.py @@ -0,0 +1,85 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from yacs.config import CfgNode as CN + +_C = CN() + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +_C.GPUS = (0,) +_C.OUTPUT_DIR = ( + "output" +) # This will be the base directory for all output, such as logs and saved models +_C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +# TODO: this should be loaded by automatically figuring out the file path location +_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" +_C.SEED = 42 +# size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only +_C.WINDOW_SIZE = 65 + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.NUM_CLASSES = 2 +_C.DATASET.ROOT = "" +_C.DATASET.FILENAME = "data.segy" + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "texture_net" +_C.MODEL.IN_CHANNELS = 1 +_C.MODEL.NUM_FILTERS = 50 +_C.MODEL.EXTRA = CN(new_allowed=True) + +# training +_C.TRAIN = CN() +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +# number of batches per epoch +_C.TRAIN.BATCH_PER_EPOCH = 10 +# total number of epochs +_C.TRAIN.END_EPOCH = 200 +_C.TRAIN.LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.DEPTH = "voxel" # Options are None, Patch and Section +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR + +# validation +_C.VALIDATION = CN() +_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 + +# TEST +_C.TEST = CN() +_C.TEST.MODEL_PATH = "" +_C.TEST.SPLIT = "Both" # Can be Both, Test1, Test2 + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) diff --git a/experiments/interpretation/dutchf3_voxel/train.py b/experiments/interpretation/dutchf3_voxel/train.py new file mode 100644 index 00000000..62e58428 --- /dev/null +++ b/experiments/interpretation/dutchf3_voxel/train.py @@ -0,0 +1,239 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# /* spell-checker: disable */ + +import logging +import logging.config +from os import path + +import cv2 +import fire +import numpy as np +import torch +from albumentations import ( + Compose, + HorizontalFlip, + Normalize, + PadIfNeeded, + Resize, +) +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Accuracy, Loss +from ignite.utils import convert_tensor +from toolz import compose +from torch.utils import data +from tqdm import tqdm + +from deepseismic_interpretation.dutchf3.data import ( + get_voxel_loader, + decode_segmap, +) +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) + +# from cv_lib.segmentation.dutchf3.engine import ( +# create_supervised_evaluator, +# create_supervised_trainer, +# ) +# Use ignite generic versions for now +from ignite.engine import ( + create_supervised_trainer, + create_supervised_evaluator, +) +from cv_lib.segmentation.dutchf3.metrics import ( + FrequencyWeightedIoU, + MeanClassAccuracy, + MeanIoU, + PixelwiseAccuracy, +) +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) + +from interpretation.deepseismic_interpretation.models.texture_net import ( + TextureNet, +) + +from default import _C as config +from default import update_config + + +def prepare_batch( + batch, device=None, non_blocking=False, t_type=torch.FloatTensor +): + x, y = batch + new_x = convert_tensor( + torch.squeeze(x, 1), device=device, non_blocking=non_blocking + ) + new_y = convert_tensor(torch.unsqueeze(y, 2), device=device, non_blocking=non_blocking) + if device == "cuda": + return new_x.type(t_type).cuda(), torch.unsqueeze(new_y, 3).type(torch.LongTensor).cuda() + else: + return new_x.type(t_type), torch.unsqueeze(new_y, 3).type(torch.LongTensor) + + +def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + logger.debug(config.WORKERS) + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + + torch.manual_seed(config.SEED) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(config.SEED) + np.random.seed(seed=config.SEED) + + # load the data + TrainVoxelLoader = get_voxel_loader(config) + + train_set = TrainVoxelLoader( + config.DATASET.ROOT, + config.DATASET.FILENAME, + split="train", + window_size=config.WINDOW_SIZE, + len=config.TRAIN.BATCH_SIZE_PER_GPU*config.TRAIN.BATCH_PER_EPOCH, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + ) + val_set = TrainVoxelLoader( + config.DATASET.ROOT, + config.DATASET.FILENAME, + split="val", + window_size=config.WINDOW_SIZE, + len=config.TRAIN.BATCH_SIZE_PER_GPU*config.TRAIN.BATCH_PER_EPOCH, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + ) + + n_classes = train_set.n_classes + + # set dataset length to batch size to be consistent with 5000 iterations each of size + # 32 in the original Waldeland implementation + train_loader = data.DataLoader( + train_set, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + shuffle=False + ) + val_loader = data.DataLoader( + val_set, + batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + shuffle=False + ) + + # this is how we import model for CV - here we're importing a seismic segmentation model + # model = getattr(models, config.MODEL.NAME).get_seg_model(config) + # TODO: pass more model parameters into the mode from config + model = TextureNet(n_classes=config.DATASET.NUM_CLASSES) + + optimizer = torch.optim.Adam( + model.parameters(), + lr=config.TRAIN.LR, + # momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + device = "cpu" + log_interval = 10 + if torch.cuda.is_available(): + device = "cuda" + model = model.cuda() + + loss = torch.nn.CrossEntropyLoss() + + def _select_pred_and_mask(model_out_dict): + return ( + model_out_dict["y_pred"].squeeze(), + model_out_dict["mask"].squeeze(), + ) + + trainer = create_supervised_trainer( + model, + optimizer, + loss, + prepare_batch=prepare_batch, + device=device, + ) + + evaluator = create_supervised_evaluator( + model, + prepare_batch=prepare_batch, + metrics={ + "accuracy": Accuracy(), + "nll": Loss(loss), + }, + device=device, + ) + + desc = "ITERATION - loss: {:.2f}" + pbar = tqdm( + initial=0, leave=False, total=len(train_loader), desc=desc.format(0) + ) + + @trainer.on(Events.ITERATION_COMPLETED) + def log_training_loss(engine): + iter = (engine.state.iteration - 1) % len(train_loader) + 1 + + if iter % log_interval == 0: + pbar.desc = desc.format(engine.state.output) + pbar.update(log_interval) + + @trainer.on(Events.EPOCH_COMPLETED) + def log_training_results(engine): + pbar.refresh() + evaluator.run(train_loader) + metrics = evaluator.state.metrics + avg_accuracy = metrics["accuracy"] + avg_loss = metrics["nll"] + tqdm.write( + "Training Results - Epoch: {} Avg accuracy: {:.2f} Avg loss: {:.2f}".format( + engine.state.epoch, avg_accuracy, avg_loss + ) + ) + + @trainer.on(Events.EPOCH_COMPLETED) + def log_validation_results(engine): + evaluator.run(val_loader) + metrics = evaluator.state.metrics + avg_accuracy = metrics["accuracy"] + avg_loss = metrics["nll"] + tqdm.write( + "Validation Results - Epoch: {} Avg accuracy: {:.2f} Avg loss: {:.2f}".format( + engine.state.epoch, avg_accuracy, avg_loss + ) + ) + + pbar.n = pbar.last_print_n = 0 + + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH//config.TRAIN.BATCH_PER_EPOCH) + pbar.close() + +if __name__ == "__main__": + fire.Fire(run) + diff --git a/experiments/interpretation/voxel2pixel/test_parallel.py b/experiments/interpretation/voxel2pixel/test_parallel.py index d20ca6df..becebec0 100644 --- a/experiments/interpretation/voxel2pixel/test_parallel.py +++ b/experiments/interpretation/voxel2pixel/test_parallel.py @@ -7,7 +7,7 @@ import os # set default number of GPUs which are discoverable -N_GPU = 8 +N_GPU = 1 DEVICE_IDS = list(range(N_GPU)) os.environ["CUDA_VISIBLE_DEVICES"] = ",".join([str(x) for x in DEVICE_IDS]) @@ -258,7 +258,7 @@ def main_worker(gpu, ngpus_per_node, args): parser = argparse.ArgumentParser(description="Seismic Distributed Scoring") parser.add_argument( - "-d", "--data", default="F3", type=str, help="default dataset folder name" + "-d", "--data", default="/mnt/dutchf3", type=str, help="default dataset folder name" ) parser.add_argument( "-s", @@ -278,7 +278,7 @@ def main_worker(gpu, ngpus_per_node, args): parser.add_argument( "-b", "--batch-size", - default=2 ** 15, + default=2 ** 11, type=int, help="batch size which we use for scoring", ) diff --git a/experiments/interpretation/voxel2pixel/texture_net.py b/experiments/interpretation/voxel2pixel/texture_net.py index 95768383..4935a647 100644 --- a/experiments/interpretation/voxel2pixel/texture_net.py +++ b/experiments/interpretation/voxel2pixel/texture_net.py @@ -10,32 +10,32 @@ class TextureNet(nn.Module): - def __init__(self, n_classes=2): + def __init__(self, n_classes=2, n_filters=50): super(TextureNet, self).__init__() # Network definition # Parameters #in_channels, #out_channels, filter_size, stride (downsampling factor) self.net = nn.Sequential( nn.Conv3d( - 1, 50, 5, 4, padding=2 + 1, n_filters, 5, 4, padding=2 ), - nn.BatchNorm3d(50), + nn.BatchNorm3d(n_filters), # nn.Dropout3d() #Droput can be added like this ... nn.ReLU(), - nn.Conv3d(50, 50, 3, 2, padding=1, bias=False), - nn.BatchNorm3d(50), + nn.Conv3d(n_filters, n_filters, 3, 2, padding=1, bias=False), + nn.BatchNorm3d(n_filters), nn.ReLU(), - nn.Conv3d(50, 50, 3, 2, padding=1, bias=False), - nn.BatchNorm3d(50), + nn.Conv3d(n_filters, n_filters, 3, 2, padding=1, bias=False), + nn.BatchNorm3d(n_filters), nn.ReLU(), - nn.Conv3d(50, 50, 3, 2, padding=1, bias=False), - nn.BatchNorm3d(50), + nn.Conv3d(n_filters, n_filters, 3, 2, padding=1, bias=False), + nn.BatchNorm3d(n_filters), nn.ReLU(), - nn.Conv3d(50, 50, 3, 3, padding=1, bias=False), - nn.BatchNorm3d(50), + nn.Conv3d(n_filters, n_filters, 3, 3, padding=1, bias=False), + nn.BatchNorm3d(n_filters), nn.ReLU(), nn.Conv3d( - 50, n_classes, 1, 1 + n_filters, n_classes, 1, 1 ), # This is the equivalent of a fully connected layer since input has width/height/depth = 1 nn.ReLU(), ) diff --git a/experiments/interpretation/voxel2pixel/train.py b/experiments/interpretation/voxel2pixel/train.py index 250311b1..6cf73417 100644 --- a/experiments/interpretation/voxel2pixel/train.py +++ b/experiments/interpretation/voxel2pixel/train.py @@ -65,7 +65,7 @@ ) # Training loop -for i in range(2000): +for i in range(5000): # Get random training batch with augmentation # This is the bottle-neck for training and could be done more efficient on the GPU... diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index 850a4385..5d2f263a 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -3,9 +3,10 @@ import json import logging import math -import os import warnings +import segyio from os import path +import scipy import matplotlib.pyplot as plt import numpy as np @@ -14,6 +15,19 @@ from toolz import curry from torch.utils import data +from interpretation.deepseismic_interpretation.dutchf3.utils.batch import ( + interpolate_to_fit_data, + parse_labels_in_image, + get_coordinates_for_slice, + get_grid, + augment_flip, + augment_rot_xy, + augment_rot_z, + augment_stretch, + rand_int, + trilinear_interpolation, +) + def _train_data_for(data_dir): return path.join(data_dir, "train", "train_seismic.npy") @@ -49,9 +63,9 @@ def readSEGY(filename): Returns: [type] -- 3D segy data as numy array and a dictionary with metadata information """ - + # TODO: we really need to add logging to this repo - print('Loading data cube from', filename,'with:') + print("Loading data cube from", filename, "with:") # Read full data cube data = segyio.tools.cube(filename) @@ -59,27 +73,192 @@ def readSEGY(filename): # Put temporal axis first data = np.moveaxis(data, -1, 0) - #Make data cube fast to acess - data = np.ascontiguousarray(data,'float32') + # Make data cube fast to acess + data = np.ascontiguousarray(data, "float32") - #Read meta data + # Read meta data segyfile = segyio.open(filename, "r") - print(' Crosslines: ', segyfile.xlines[0], ':', segyfile.xlines[-1]) - print(' Inlines: ', segyfile.ilines[0], ':', segyfile.ilines[-1]) - print(' Timeslices: ', '1', ':', data.shape[0]) + print(" Crosslines: ", segyfile.xlines[0], ":", segyfile.xlines[-1]) + print(" Inlines: ", segyfile.ilines[0], ":", segyfile.ilines[-1]) + print(" Timeslices: ", "1", ":", data.shape[0]) - #Make dict with cube-info + # Make dict with cube-info data_info = {} - data_info['crossline_start'] = segyfile.xlines[0] - data_info['inline_start'] = segyfile.ilines[0] - data_info['timeslice_start'] = 1 #Todo: read this from segy - data_info['shape'] = data.shape - #Read dt and other params needed to do create a new - + data_info["crossline_start"] = segyfile.xlines[0] + data_info["inline_start"] = segyfile.ilines[0] + data_info["timeslice_start"] = 1 # Todo: read this from segy + data_info["shape"] = data.shape + # Read dt and other params needed to do create a new return data, data_info +def read_labels(fname, data_info): + """ + Read labels from an image. + + Args: + fname: filename of labelling mask (image) + data_info: dictionary describing the data + + Returns: + list of labels and list of coordinates + """ + + # Alternative writings for slice-type + inline_alias = ["inline", "in-line", "iline", "y"] + crossline_alias = ["crossline", "cross-line", "xline", "x"] + timeslice_alias = [ + "timeslice", + "time-slice", + "t", + "z", + "depthslice", + "depth", + ] + + label_imgs = [] + label_coordinates = {} + + # Find image files in folder + + tmp = fname.split("/")[-1].split("_") + slice_type = tmp[0].lower() + tmp = tmp[1].split(".") + slice_no = int(tmp[0]) + + if slice_type not in inline_alias + crossline_alias + timeslice_alias: + print("File:", fname, "could not be loaded.", "Unknown slice type") + return None + + if slice_type in inline_alias: + slice_type = "inline" + if slice_type in crossline_alias: + slice_type = "crossline" + if slice_type in timeslice_alias: + slice_type = "timeslice" + + # Read file + print("Loading labels for", slice_type, slice_no, "with") + img = scipy.misc.imread(fname) + img = interpolate_to_fit_data(img, slice_type, slice_no, data_info) + label_img = parse_labels_in_image(img) + + # Get coordinates for slice + coords = get_coordinates_for_slice(slice_type, slice_no, data_info) + + # Loop through labels in label_img and append to label_coordinates + for cls in np.unique(label_img): + if cls > -1: + if str(cls) not in label_coordinates.keys(): + label_coordinates[str(cls)] = np.array(np.zeros([3, 0])) + inds_with_cls = label_img == cls + cords_with_cls = coords[:, inds_with_cls.ravel()] + label_coordinates[str(cls)] = np.concatenate( + (label_coordinates[str(cls)], cords_with_cls), 1 + ) + print( + " ", str(np.sum(inds_with_cls)), "labels for class", str(cls) + ) + if len(np.unique(label_img)) == 1: + print(" ", 0, "labels", str(cls)) + + # Add label_img to output + label_imgs.append([label_img, slice_type, slice_no]) + + return label_imgs, label_coordinates + + +def get_random_batch( + data_cube, + label_coordinates, + im_size, + batch_size, + index, + random_flip=False, + random_stretch=None, + random_rot_xy=None, + random_rot_z=None, +): + """ + Returns a batch of augmented samples with center pixels randomly drawn from label_coordinates + + Args: + data_cube: 3D numpy array with floating point velocity values + label_coordinates: 3D coordinates of the labeled training slice + im_size: size of the 3D voxel which we're cutting out around each label_coordinate + batch_size: size of the batch + index: element index of this element in a batch + random_flip: bool to perform random voxel flip + random_stretch: bool to enable random stretch + random_rot_xy: bool to enable random rotation of the voxel around dim-0 and dim-1 + random_rot_z: bool to enable random rotation around dim-2 + + Returns: + a tuple of batch numpy array array of data with dimension + (batch, 1, data_cube.shape[0], data_cube.shape[1], data_cube.shape[2]) and the associated labels as an array + of size (batch). + """ + + # always generate only one datapoint - batch_size controls class balance + num_batch_size=1 + + # Make 3 im_size elements + if isinstance(im_size, int): + im_size = [im_size, im_size, im_size] + + # Output arrays + batch = np.zeros([num_batch_size, 1, im_size[0], im_size[1], im_size[2]]) + ret_labels = np.zeros([num_batch_size]) + + class_keys = list(label_coordinates) + n_classes = len(class_keys) + + # We seek to have a balanced batch with equally many samples from each class. + # get total number of samples per class + samples_per_class = batch_size//n_classes + # figure out index relative to zero (not sequentially counting points) + index = index - batch_size*(index//batch_size) + # figure out which class to sample for this datapoint + class_ind = index//samples_per_class + + # Start by getting a grid centered around (0,0,0) + grid = get_grid(im_size) + + # Apply random flip + if random_flip: + grid = augment_flip(grid) + + # Apply random rotations + if random_rot_xy: + grid = augment_rot_xy(grid, random_rot_xy) + if random_rot_z: + grid = augment_rot_z(grid, random_rot_z) + + # Apply random stretch + if random_stretch: + grid = augment_stretch(grid, random_stretch) + + # Pick random location from the label_coordinates for this class: + coords_for_class = label_coordinates[class_keys[class_ind]] + random_index = rand_int(0, coords_for_class.shape[1]) + coord = coords_for_class[:, random_index : random_index + 1] + + # Move grid to be centered around this location + grid += coord + + # Interpolate samples at grid from the data: + sample = trilinear_interpolation(data_cube, grid) + + # Insert in output arrays + ret_labels[0] = class_ind + batch[0, 0, :, :, :] = np.reshape( + sample, (im_size[0], im_size[1], im_size[2]) + ) + + return batch, ret_labels + + class SectionLoader(data.Dataset): def __init__( self, data_dir, split="train", is_transform=True, augmentations=None @@ -127,19 +306,48 @@ def transform(self, img, lbl): class VoxelLoader(data.Dataset): def __init__( - self, data_dir, window, coord_list, split="train", n_classes = 2 + self, + root_path, + filename, + window_size=65, + split="train", + n_classes=2, + gen_coord_list=False, + len = None ): - self.data_dir = data_dir - # TODO: write loader to poppulate data from directory + + assert split == "train" or split == "val" + + # location of the file + self.root_path = root_path self.split = split self.n_classes = n_classes - self.window = window - self.len = len(coord_list) + self.window_size = window_size + self.coord_list = None + self.filename = filename + self.full_filename = path.join(root_path, filename) # Read 3D cube # NOTE: we cannot pass this data manually as serialization of data into each python process is costly, # so each worker has to load the data on its own. - self.data, self.data_info = readSEGY(os.path.join(self.data_dir, "data.segy")) + self.data, self.data_info = readSEGY(self.full_filename) + if len: + self.len = len + else: + self.len = self.data.size + self.labels = None + + if gen_coord_list: + # generate a list of coordinates to index the entire voxel + # memory footprint of this isn't large yet, so not need to wrap as a generator + nx, ny, nz = self.data.shape + x_list = range(self.window_size, nx - self.window_size) + y_list = range(self.window_size, ny - self.window_size) + z_list = range(self.window_size, nz - self.window_size) + + print("-- generating coord list --") + # TODO: is there any way to use a generator with pyTorch data loader? + self.coord_list = list(itertools.product(x_list, y_list, z_list)) def __len__(self): return self.len @@ -168,6 +376,7 @@ def transform(self, img, lbl): return torch.from_numpy(img).float(), torch.from_numpy(lbl).long() """ + class TrainSectionLoader(SectionLoader): def __init__( self, data_dir, split="train", is_transform=True, augmentations=None @@ -213,7 +422,7 @@ def __getitem__(self, index): lbl = self.labels[int(number), :, :] elif direction == "x": im = self.seismic[:, :, int(number), :] - lbl = self.labels[ :, int(number), :] + lbl = self.labels[:, int(number), :] im = np.swapaxes(im, 0, 1) # From WCH to CWH @@ -231,17 +440,46 @@ def __getitem__(self, index): return im, lbl -class TrainVoxelLoader(VoxelLoader): - def __init__( - self, data_dir, split="train" - ): - super(TrainVoxelLoader, self).__init__( - data_dir, - split=split +class TrainVoxelWaldelandLoader(VoxelLoader): + def __init__(self, root_path, filename, split="train", window_size=65, batch_size=None, len=None): + super(TrainVoxelWaldelandLoader, self).__init__( + root_path, filename, split=split, window_size=window_size, len=len ) + label_fname = None + if split == "train": + label_fname = path.join(self.root_path, "inline_339.png") + elif split == "val": + label_fname = path.join(self.root_path, "inline_405.png") + else: + raise Exception("undefined split") + + self.class_imgs, self.coordinates = read_labels( + label_fname, self.data_info + ) + + self.batch_size = batch_size if batch_size else 1 + + def __getitem__(self, index): + # print(index) + batch, labels = get_random_batch( + self.data, + self.coordinates, + self.window_size, + self.batch_size, + index, + random_flip=True, + random_stretch=0.2, + random_rot_xy=180, + random_rot_z=15, + ) + + return batch, labels + + # TODO: write TrainVoxelLoaderWithDepth -TrainVoxelLoaderWithDepth = TrainVoxelLoader +TrainVoxelLoaderWithDepth = TrainVoxelWaldelandLoader + class TestSectionLoader(SectionLoader): def __init__( @@ -310,17 +548,15 @@ def __getitem__(self, index): return im, lbl -class TestVoxelLoader(VoxelLoader): - def __init__( - self, data_dir, split="test" - ): - super(TestVoxelLoader, self).__init__( - data_dir, - split=split - ) + +class TestVoxelWaldelandLoader(VoxelLoader): + def __init__(self, data_dir, split="test"): + super(TestVoxelWaldelandLoader, self).__init__(data_dir, split=split) + # TODO: write TestVoxelLoaderWithDepth -TestVoxelLoaderWithDepth = TestVoxelLoader +TestVoxelLoaderWithDepth = TestVoxelWaldelandLoader + def _transform_WH_to_HW(numpy_array): assert len(numpy_array.shape) >= 2, "This method needs at least 2D arrays" @@ -423,7 +659,7 @@ def __init__( augmentations=augmentations, ) ## Warning: this is not used or tested - raise NotImplementedError('This class is not correctly implemented.') + raise NotImplementedError("This class is not correctly implemented.") self.seismic = np.load(_train_data_for(self.data_dir)) self.labels = np.load(_train_labels_for(self.data_dir)) @@ -465,8 +701,8 @@ def __init__( self.split = split # reading the file names for split txt_path = path.join( - self.data_dir, "splits", "patch_" + split + ".txt" - ) + self.data_dir, "splits", "patch_" + split + ".txt" + ) patch_list = tuple(open(txt_path, "r")) patch_list = [id_.rstrip() for id_ in patch_list] self.patches = patch_list @@ -608,32 +844,41 @@ def __getitem__(self, index): "patch": TrainPatchLoaderWithDepth, } -_TRAIN_SECTION_LOADERS = { - "section": TrainSectionLoaderWithDepth -} +_TRAIN_SECTION_LOADERS = {"section": TrainSectionLoaderWithDepth} + +_TRAIN_VOXEL_LOADERS = {"voxel": TrainVoxelLoaderWithDepth} -_TRAIN_VOXEL_LOADERS = { - "voxel": TrainVoxelLoaderWithDepth, -} def get_patch_loader(cfg): - assert cfg.TRAIN.DEPTH in ["section", "patch", "none"], f"Depth {cfg.TRAIN.DEPTH} not supported for patch data. \ + assert cfg.TRAIN.DEPTH in [ + "section", + "patch", + "none", + ], f"Depth {cfg.TRAIN.DEPTH} not supported for patch data. \ Valid values: section, patch, none." return _TRAIN_PATCH_LOADERS.get(cfg.TRAIN.DEPTH, TrainPatchLoader) + def get_section_loader(cfg): - assert cfg.TRAIN.DEPTH in ["section", "none"], f"Depth {cfg.TRAIN.DEPTH} not supported for section data. \ + assert cfg.TRAIN.DEPTH in [ + "section", + "none", + ], f"Depth {cfg.TRAIN.DEPTH} not supported for section data. \ Valid values: section, none." return _TRAIN_SECTION_LOADERS.get(cfg.TRAIN.DEPTH, TrainSectionLoader) + def get_voxel_loader(cfg): - assert cfg.TRAIN.DEPTH in ["voxel", "none"], f"Depth {cfg.TRAIN.DEPTH} not supported for section data. \ + assert cfg.TRAIN.DEPTH in [ + "voxel", + "none", + ], f"Depth {cfg.TRAIN.DEPTH} not supported for section data. \ Valid values: voxel, none." - return _TRAIN_SECTION_LOADERS.get(cfg.TRAIN.DEPTH, TrainVoxelLoader) + return _TRAIN_SECTION_LOADERS.get(cfg.TRAIN.DEPTH, TrainVoxelWaldelandLoader) + + +_TEST_LOADERS = {"section": TestSectionLoaderWithDepth} -_TEST_LOADERS = { - "section": TestSectionLoaderWithDepth, -} def get_test_loader(cfg): return _TEST_LOADERS.get(cfg.TRAIN.DEPTH, TestSectionLoader) diff --git a/interpretation/models/__init__.py b/interpretation/deepseismic_interpretation/dutchf3/utils/__init__.py similarity index 100% rename from interpretation/models/__init__.py rename to interpretation/deepseismic_interpretation/dutchf3/utils/__init__.py diff --git a/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py b/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py new file mode 100644 index 00000000..f9a3a4ea --- /dev/null +++ b/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py @@ -0,0 +1,503 @@ +import numpy as np +import scipy + + +def get_coordinates_for_slice(slice_type, slice_no, data_info): + """ + + Get coordinates for slice in the full cube + + Args: + slice_type: type of slice, e.g. inline, crossline, etc + slice_no: slice number + data_info: data dictionary array + + Returns: + index coordinates of the voxel + + """ + ds = data_info["shape"] + + # Coordinates for cube + x0, x1, x2 = np.meshgrid( + np.linspace(0, ds[0] - 1, ds[0]), + np.linspace(0, ds[1] - 1, ds[1]), + np.linspace(0, ds[2] - 1, ds[2]), + indexing="ij", + ) + if slice_type == "inline": + start = data_info["inline_start"] + slice_no = slice_no - start + + x0 = x0[:, slice_no, :] + x1 = x1[:, slice_no, :] + x2 = x2[:, slice_no, :] + elif slice_type == "crossline": + start = data_info["crossline_start"] + slice_no = slice_no - start + x0 = x0[:, :, slice_no] + x1 = x1[:, :, slice_no] + x2 = x2[:, :, slice_no] + + elif slice_type == "timeslice": + start = data_info["timeslice_start"] + slice_no = slice_no - start + x0 = x0[slice_no, :, :] + x1 = x1[slice_no, :, :] + x2 = x2[slice_no, :, :] + + # Collect indexes + x0 = np.expand_dims(x0.ravel(), 0) + x1 = np.expand_dims(x1.ravel(), 0) + x2 = np.expand_dims(x2.ravel(), 0) + coords = np.concatenate((x0, x1, x2), axis=0) + + return coords + + +def parse_labels_in_image(img): + """ + Convert RGB image to class img. + + Args: + img: 3-channel image array + + Returns: + monotonically increasing class labels + """ + + # Add colors to this table to make it possible to have more classes + class_color_coding = [ + [0, 0, 255], # blue + [0, 255, 0], # green + [0, 255, 255], # cyan + [255, 0, 0], # red + [255, 0, 255], # blue + [255, 255, 0], # yellow + ] + + label_img = np.int16(img[:, :, 0]) * 0 - 1 # -1 = no class + + # decompose color channels (#Alpha is ignored) + r = img[:, :, 0] + g = img[:, :, 1] + b = img[:, :, 2] + + # Alpha channel + if img.shape[2] == 4: + a = 1 - img.shape[2] // 255 + r = r * a + g = g * a + b = b * a + + tolerance = 1 + # Go through classes and find pixels with this class + cls = 0 + for color in class_color_coding: + # Find pixels with these labels + inds = ( + (np.abs(r - color[0]) < tolerance) + & (np.abs(g - color[1]) < tolerance) + & (np.abs(b - color[2]) < tolerance) + ) + label_img[inds] = cls + cls += 1 + + return label_img + + +def interpolate_to_fit_data(img, slice_type, slice_no, data_info): + """ + Function to resize image if needed + + Args: + img: image array + slice_type: inline, crossline or timeslice slice type + slice_no: slice number + data_info: data info dictionary distracted from SEGY file + + Returns: + resized image array + + """ + + # Get wanted output size + if slice_type == "inline": + n0 = data_info["shape"][0] + n1 = data_info["shape"][2] + elif slice_type == "crossline": + n0 = data_info["shape"][0] + n1 = data_info["shape"][1] + elif slice_type == "timeslice": + n0 = data_info["shape"][1] + n1 = data_info["shape"][2] + return scipy.misc.imresize(img, (n0, n1), interp="nearest") + + +def get_grid(im_size): + """ + getGrid returns z,x,y coordinates centered around (0,0,0) + + Args: + im_size: size of window + + Returns + numpy int array with size: 3 x im_size**3 + """ + win0 = np.linspace(-im_size[0] // 2, im_size[0] // 2, im_size[0]) + win1 = np.linspace(-im_size[1] // 2, im_size[1] // 2, im_size[1]) + win2 = np.linspace(-im_size[2] // 2, im_size[2] // 2, im_size[2]) + + x0, x1, x2 = np.meshgrid(win0, win1, win2, indexing="ij") + + ex0 = np.expand_dims(x0.ravel(), 0) + ex1 = np.expand_dims(x1.ravel(), 0) + ex2 = np.expand_dims(x2.ravel(), 0) + + grid = np.concatenate((ex0, ex1, ex2), axis=0) + + return grid + + +def augment_flip(grid): + """ + Random flip of non-depth axes. + + Args: + grid: 3D coordinates of the voxel + + Returns: + flipped grid coordinates + """ + + # Flip x axis + if rand_bool(): + grid[1, :] = -grid[1, :] + + # Flip y axis + if rand_bool(): + grid[2, :] = -grid[2, :] + + return grid + + +def augment_stretch(grid, stretch_factor): + """ + Random stretch/scale + + Args: + grid: 3D coordinate grid of the voxel + stretch_factor: this is actually a boolean which triggers stretching + TODO: change this to just call the function and not do -1,1 in rand_float + + Returns: + stretched grid coordinates + """ + stretch = rand_float(-stretch_factor, stretch_factor) + grid *= 1 + stretch + return grid + + +def augment_rot_xy(grid, random_rot_xy): + """ + Random rotation + + Args: + grid: coordinate grid list of 3D points + random_rot_xy: this is actually a boolean which triggers rotation + TODO: change this to just call the function and not do -1,1 in rand_float + + Returns: + randomly rotated grid + """ + theta = np.deg2rad(rand_float(-random_rot_xy, random_rot_xy)) + x = grid[2, :] * np.cos(theta) - grid[1, :] * np.sin(theta) + y = grid[2, :] * np.sin(theta) + grid[1, :] * np.cos(theta) + grid[1, :] = x + grid[2, :] = y + return grid + + +def augment_rot_z(grid, random_rot_z): + """ + Random tilt around z-axis (dim-2) + + Args: + grid: coordinate grid list of 3D points + random_rot_z: this is actually a boolean which triggers rotation + TODO: change this to just call the function and not do -1,1 in rand_float + + Returns: + randomly tilted coordinate grid + """ + theta = np.deg2rad(rand_float(-random_rot_z, random_rot_z)) + z = grid[0, :] * np.cos(theta) - grid[1, :] * np.sin(theta) + x = grid[0, :] * np.sin(theta) + grid[1, :] * np.cos(theta) + grid[0, :] = z + grid[1, :] = x + return grid + + +def trilinear_interpolation(input_array, indices): + """ + Linear interpolation + code taken from + http://stackoverflow.com/questions/6427276/3d-interpolation-of-numpy-arrays-without-scipy + + Args: + input_array: 3D data array + indices: 3D grid coordinates + + Returns: + interpolated input array + """ + + x_indices, y_indices, z_indices = indices[0:3] + + n0, n1, n2 = input_array.shape + + x0 = x_indices.astype(np.integer) + y0 = y_indices.astype(np.integer) + z0 = z_indices.astype(np.integer) + x1 = x0 + 1 + y1 = y0 + 1 + z1 = z0 + 1 + + # put all samples outside datacube to 0 + inds_out_of_range = ( + (x0 < 0) + | (x1 < 0) + | (y0 < 0) + | (y1 < 0) + | (z0 < 0) + | (z1 < 0) + | (x0 >= n0) + | (x1 >= n0) + | (y0 >= n1) + | (y1 >= n1) + | (z0 >= n2) + | (z1 >= n2) + ) + + x0[inds_out_of_range] = 0 + y0[inds_out_of_range] = 0 + z0[inds_out_of_range] = 0 + x1[inds_out_of_range] = 0 + y1[inds_out_of_range] = 0 + z1[inds_out_of_range] = 0 + + x = x_indices - x0 + y = y_indices - y0 + z = z_indices - z0 + output = ( + input_array[x0, y0, z0] * (1 - x) * (1 - y) * (1 - z) + + input_array[x1, y0, z0] * x * (1 - y) * (1 - z) + + input_array[x0, y1, z0] * (1 - x) * y * (1 - z) + + input_array[x0, y0, z1] * (1 - x) * (1 - y) * z + + input_array[x1, y0, z1] * x * (1 - y) * z + + input_array[x0, y1, z1] * (1 - x) * y * z + + input_array[x1, y1, z0] * x * y * (1 - z) + + input_array[x1, y1, z1] * x * y * z + ) + + output[inds_out_of_range] = 0 + return output + + +def rand_float(low, high): + """ + Generate random floating point number between two limits + + Args: + low: low limit + high: high limit + + Returns: + single random floating point number + """ + return (high - low) * np.random.random_sample() + low + + +def rand_int(low, high): + """ + Generate random integer between two limits + + Args: + low: low limit + high: high limit + + Returns: + random integer between two limits + """ + return np.random.randint(low, high) + + +def rand_bool(): + """ + Generate random boolean. + + Returns: + Random boolean + """ + return bool(np.random.randint(0, 2)) + + +def augment_stretch(grid, stretch_factor): + """ + Random stretch/scale + + Args: + grid: 3D coordinate grid of the voxel + stretch_factor: this is actually a boolean which triggers stretching + TODO: change this to just call the function and not do -1,1 in rand_float + + Returns: + stretched grid coordinates + """ + stretch = rand_float(-stretch_factor, stretch_factor) + grid *= 1 + stretch + return grid + + +def augment_rot_xy(grid, random_rot_xy): + """ + Random rotation + + Args: + grid: coordinate grid list of 3D points + random_rot_xy: this is actually a boolean which triggers rotation + TODO: change this to just call the function and not do -1,1 in rand_float + + Returns: + randomly rotated grid + """ + theta = np.deg2rad(rand_float(-random_rot_xy, random_rot_xy)) + x = grid[2, :] * np.cos(theta) - grid[1, :] * np.sin(theta) + y = grid[2, :] * np.sin(theta) + grid[1, :] * np.cos(theta) + grid[1, :] = x + grid[2, :] = y + return grid + + +def augment_rot_z(grid, random_rot_z): + """ + Random tilt around z-axis (dim-2) + + Args: + grid: coordinate grid list of 3D points + random_rot_z: this is actually a boolean which triggers rotation + TODO: change this to just call the function and not do -1,1 in rand_float + + Returns: + randomly tilted coordinate grid + """ + theta = np.deg2rad(rand_float(-random_rot_z, random_rot_z)) + z = grid[0, :] * np.cos(theta) - grid[1, :] * np.sin(theta) + x = grid[0, :] * np.sin(theta) + grid[1, :] * np.cos(theta) + grid[0, :] = z + grid[1, :] = x + return grid + + +def trilinear_interpolation(input_array, indices): + """ + Linear interpolation + code taken from + http://stackoverflow.com/questions/6427276/3d-interpolation-of-numpy-arrays-without-scipy + + Args: + input_array: 3D data array + indices: 3D grid coordinates + + Returns: + interpolated input array + """ + + x_indices, y_indices, z_indices = indices[0:3] + + n0, n1, n2 = input_array.shape + + x0 = x_indices.astype(np.integer) + y0 = y_indices.astype(np.integer) + z0 = z_indices.astype(np.integer) + x1 = x0 + 1 + y1 = y0 + 1 + z1 = z0 + 1 + + # put all samples outside datacube to 0 + inds_out_of_range = ( + (x0 < 0) + | (x1 < 0) + | (y0 < 0) + | (y1 < 0) + | (z0 < 0) + | (z1 < 0) + | (x0 >= n0) + | (x1 >= n0) + | (y0 >= n1) + | (y1 >= n1) + | (z0 >= n2) + | (z1 >= n2) + ) + + x0[inds_out_of_range] = 0 + y0[inds_out_of_range] = 0 + z0[inds_out_of_range] = 0 + x1[inds_out_of_range] = 0 + y1[inds_out_of_range] = 0 + z1[inds_out_of_range] = 0 + + x = x_indices - x0 + y = y_indices - y0 + z = z_indices - z0 + output = ( + input_array[x0, y0, z0] * (1 - x) * (1 - y) * (1 - z) + + input_array[x1, y0, z0] * x * (1 - y) * (1 - z) + + input_array[x0, y1, z0] * (1 - x) * y * (1 - z) + + input_array[x0, y0, z1] * (1 - x) * (1 - y) * z + + input_array[x1, y0, z1] * x * (1 - y) * z + + input_array[x0, y1, z1] * (1 - x) * y * z + + input_array[x1, y1, z0] * x * y * (1 - z) + + input_array[x1, y1, z1] * x * y * z + ) + + output[inds_out_of_range] = 0 + return output + + +def rand_float(low, high): + """ + Generate random floating point number between two limits + + Args: + low: low limit + high: high limit + + Returns: + single random floating point number + """ + return (high - low) * np.random.random_sample() + low + + +def rand_int(low, high): + """ + Generate random integer between two limits + + Args: + low: low limit + high: high limit + + Returns: + random integer between two limits + """ + return np.random.randint(low, high) + + +def rand_bool(): + """ + Generate random boolean. + + Returns: + Random boolean + """ + return bool(np.random.randint(0, 2)) diff --git a/interpretation/deepseismic_interpretation/models/__init__.py b/interpretation/deepseismic_interpretation/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/interpretation/models/texture_net.py b/interpretation/deepseismic_interpretation/models/texture_net.py similarity index 100% rename from interpretation/models/texture_net.py rename to interpretation/deepseismic_interpretation/models/texture_net.py diff --git a/scripts/data_symlink.sh b/scripts/data_symlink.sh new file mode 100755 index 00000000..6f70378c --- /dev/null +++ b/scripts/data_symlink.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Azure VMs lose mounts after restart - this symlinks the data folder from user's +# home directory after VM restart + +sudo chown -R maxkaz /mnt +sudo chgrp -R maxkaz /mnt +ln -s ~/dutchf3 /mnt From 5bfb342da84db8ef8f802637b2441d9c5a2826d3 Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Tue, 29 Oct 2019 14:17:44 +0000 Subject: [PATCH 063/207] Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 --- .pre-commit-config.yaml | 1 + .../configs/patch_deconvnet_skip.yaml | 4 +- .../interpretation/notebooks/utilities.py | 97 ++++++++------ .../dutchf3/data.py | 123 +++++------------- 4 files changed, 95 insertions(+), 130 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cddba3d4..d4105a32 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,3 +8,4 @@ repos: rev: v1.2.3 hooks: - id: flake8 + args: [--max-line-length=88] diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml index d4875ec0..d671dce8 100644 --- a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml +++ b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml @@ -23,7 +23,7 @@ MODEL: TRAIN: BATCH_SIZE_PER_GPU: 64 BEGIN_EPOCH: 0 - END_EPOCH: 10 + END_EPOCH: 100 MIN_LR: 0.001 MAX_LR: 0.02 MOMENTUM: 0.9 @@ -48,7 +48,7 @@ VALIDATION: BATCH_SIZE_PER_GPU: 512 TEST: - MODEL_PATH: '/data/home/vapaunic/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_8.pth' + MODEL_PATH: '/data/home/vapaunic/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_100.pth' TEST_STRIDE: 10 SPLIT: 'test1' # Can be both, test1, test2 INLINE: True diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index 7d49708b..669de6c9 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -8,7 +8,9 @@ from toolz import compose, curry, itertoolz, pipe import matplotlib.pyplot as plt -# plt.rcParams.update({"font.size": 16}) +import plotly.graph_objects as go +from scipy.ndimage import zoom + class runningScore(object): def __init__(self, n_classes): @@ -40,9 +42,7 @@ def get_scores(self): acc = np.diag(hist).sum() / hist.sum() acc_cls = np.diag(hist) / hist.sum(axis=1) mean_acc_cls = np.nanmean(acc_cls) - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) + iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) mean_iu = np.nanmean(iu) freq = ( hist.sum(axis=1) / hist.sum() @@ -72,7 +72,8 @@ def prepare_batch(batch, device=None, non_blocking=False): convert_tensor(x, device=device, non_blocking=non_blocking), convert_tensor(y, device=device, non_blocking=non_blocking), ) - + + def _transform_CHW_to_HWC(numpy_array): return np.moveaxis(numpy_array, 0, -1) @@ -132,14 +133,10 @@ def _expand_dims_if_necessary(torch_tensor): @curry def _extract_patch(hdx, wdx, ps, patch_size, img_p): if len(img_p.shape) == 2: # 2D - return img_p[ - hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size - ] + return img_p[hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size] else: # 3D return img_p[ - :, - hdx + ps : hdx + ps + patch_size, - wdx + ps : wdx + ps + patch_size, + :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size ] @@ -159,13 +156,10 @@ def _compose_processing_pipeline(depth, aug=None): def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): hdc_wdx_generator = itertools.product( - range(0, h - patch_size + ps, stride), - range(0, w - patch_size + ps, stride), + range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride) ) - for batch_indexes in itertoolz.partition_all( - batch_size, hdc_wdx_generator - ): + for batch_indexes in itertoolz.partition_all(batch_size, hdc_wdx_generator): yield batch_indexes @@ -173,16 +167,10 @@ def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): def _output_processing_pipeline(config, output): output = output.unsqueeze(0) _, _, h, w = output.shape - if ( - config.TEST.POST_PROCESSING.SIZE != h - or config.TEST.POST_PROCESSING.SIZE != w - ): + if config.TEST.POST_PROCESSING.SIZE != h or config.TEST.POST_PROCESSING.SIZE != w: output = F.interpolate( output, - size=( - config.TEST.POST_PROCESSING.SIZE, - config.TEST.POST_PROCESSING.SIZE, - ), + size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE), mode="bilinear", ) @@ -225,26 +213,17 @@ def _patch_label_2d( ): batch = torch.stack( [ - pipe( - img_p, - _extract_patch(hdx, wdx, ps, patch_size), - pre_processing, - ) + pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing) for hdx, wdx in batch_indexes ], dim=0, ) model_output = model(batch.to(device)) - for (hdx, wdx), output in zip( - batch_indexes, model_output.detach().cpu() - ): + for (hdx, wdx), output in zip(batch_indexes, model_output.detach().cpu()): output = output_processing(output) output_p[ - :, - :, - hdx + ps : hdx + ps + patch_size, - wdx + ps : wdx + ps + patch_size, + :, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size ] += output # crop the output_p in the middle @@ -262,14 +241,13 @@ def to_image(label_mask, n_classes=6): r[label_mask == ll] = label_colours[ll, 0] g[label_mask == ll] = label_colours[ll, 1] b[label_mask == ll] = label_colours[ll, 2] - rgb = np.zeros( - (label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3) - ) + rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3)) rgb[:, :, :, 0] = r rgb[:, :, :, 1] = g rgb[:, :, :, 2] = b return rgb + def _write_section_file(labels, section_file, config): # define indices of the array irange, xrange, depth = labels.shape @@ -292,7 +270,7 @@ def _write_section_file(labels, section_file, config): file_object.write("\n".join(list_test)) file_object.close() - + def plot_aline(aline, labels, xlabel, ylabel="depth"): """Plot a section of the data.""" plt.figure(figsize=(18, 6)) @@ -306,4 +284,41 @@ def plot_aline(aline, labels, xlabel, ylabel="depth"): plt.subplot(1, 2, 2) plt.imshow(labels) plt.xlabel(xlabel) - plt.title("Label") \ No newline at end of file + plt.title("Label") + + +def plot_f3block_interactive(data, x_slice_locations=[0.25], y_slice_locations=[0.8]): + """Plot interactive graph of F3 block""" + values = zoom(data, 0.2) + values = values[:, :, ::-1] + + x, y, z = values.shape + X, Y, Z = np.mgrid[0:x, 0:y, 0:z] + + fig = go.Figure( + data=go.Volume( + x=X.flatten(), + y=Y.flatten(), + z=Z.flatten(), + value=values.flatten(), + slices_x=dict(show=True, locations=[i * x for i in x_slice_locations]), + slices_y=dict(show=True, locations=[i * y for i in y_slice_locations]), + opacity=0.5, # needs to be small to see through all surfaces + showscale=False, + caps=dict(x_show=True, y_show=True, z_show=True), + colorscale="Viridis", + ) + ) + + fig.update_layout( + scene_xaxis_showticklabels=False, + scene_yaxis_showticklabels=False, + scene_zaxis_showticklabels=False, + height=800, + width=1000, + title="F3 Block Netherlands", + scene=dict( + xaxis_title="Inlines", yaxis_title="Crosslines", zaxis_title="Depth" + ), + ) + fig.show() diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index 5d2f263a..9c13e966 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -1,21 +1,14 @@ -import collections import itertools -import json -import logging -import math import warnings import segyio from os import path import scipy - -import matplotlib.pyplot as plt import numpy as np import torch -from sklearn.model_selection import train_test_split from toolz import curry from torch.utils import data -from interpretation.deepseismic_interpretation.dutchf3.utils.batch import ( +from deepseismic_interpretation.dutchf3.utils.batch import ( interpolate_to_fit_data, parse_labels_in_image, get_coordinates_for_slice, @@ -108,14 +101,7 @@ def read_labels(fname, data_info): # Alternative writings for slice-type inline_alias = ["inline", "in-line", "iline", "y"] crossline_alias = ["crossline", "cross-line", "xline", "x"] - timeslice_alias = [ - "timeslice", - "time-slice", - "t", - "z", - "depthslice", - "depth", - ] + timeslice_alias = ["timeslice", "time-slice", "t", "z", "depthslice", "depth"] label_imgs = [] label_coordinates = {} @@ -157,9 +143,7 @@ def read_labels(fname, data_info): label_coordinates[str(cls)] = np.concatenate( (label_coordinates[str(cls)], cords_with_cls), 1 ) - print( - " ", str(np.sum(inds_with_cls)), "labels for class", str(cls) - ) + print(" ", str(np.sum(inds_with_cls)), "labels for class", str(cls)) if len(np.unique(label_img)) == 1: print(" ", 0, "labels", str(cls)) @@ -201,7 +185,7 @@ def get_random_batch( """ # always generate only one datapoint - batch_size controls class balance - num_batch_size=1 + num_batch_size = 1 # Make 3 im_size elements if isinstance(im_size, int): @@ -216,11 +200,11 @@ def get_random_batch( # We seek to have a balanced batch with equally many samples from each class. # get total number of samples per class - samples_per_class = batch_size//n_classes + samples_per_class = batch_size // n_classes # figure out index relative to zero (not sequentially counting points) - index = index - batch_size*(index//batch_size) + index = index - batch_size * (index // batch_size) # figure out which class to sample for this datapoint - class_ind = index//samples_per_class + class_ind = index // samples_per_class # Start by getting a grid centered around (0,0,0) grid = get_grid(im_size) @@ -239,7 +223,7 @@ def get_random_batch( if random_stretch: grid = augment_stretch(grid, random_stretch) - # Pick random location from the label_coordinates for this class: + # Pick random location from the label_coordinates for this class: coords_for_class = label_coordinates[class_keys[class_ind]] random_index = rand_int(0, coords_for_class.shape[1]) coord = coords_for_class[:, random_index : random_index + 1] @@ -252,17 +236,13 @@ def get_random_batch( # Insert in output arrays ret_labels[0] = class_ind - batch[0, 0, :, :, :] = np.reshape( - sample, (im_size[0], im_size[1], im_size[2]) - ) + batch[0, 0, :, :, :] = np.reshape(sample, (im_size[0], im_size[1], im_size[2])) return batch, ret_labels class SectionLoader(data.Dataset): - def __init__( - self, data_dir, split="train", is_transform=True, augmentations=None - ): + def __init__(self, data_dir, split="train", is_transform=True, augmentations=None): self.split = split self.data_dir = data_dir self.is_transform = is_transform @@ -313,7 +293,7 @@ def __init__( split="train", n_classes=2, gen_coord_list=False, - len = None + len=None, ): assert split == "train" or split == "val" @@ -378,9 +358,7 @@ def transform(self, img, lbl): class TrainSectionLoader(SectionLoader): - def __init__( - self, data_dir, split="train", is_transform=True, augmentations=None - ): + def __init__(self, data_dir, split="train", is_transform=True, augmentations=None): super(TrainSectionLoader, self).__init__( data_dir, split=split, @@ -392,18 +370,14 @@ def __init__( self.labels = np.load(_train_labels_for(self.data_dir)) # reading the file names for split - txt_path = path.join( - self.data_dir, "splits", "section_" + split + ".txt" - ) + txt_path = path.join(self.data_dir, "splits", "section_" + split + ".txt") file_list = tuple(open(txt_path, "r")) file_list = [id_.rstrip() for id_ in file_list] self.sections = file_list class TrainSectionLoaderWithDepth(TrainSectionLoader): - def __init__( - self, data_dir, split="train", is_transform=True, augmentations=None - ): + def __init__(self, data_dir, split="train", is_transform=True, augmentations=None): super(TrainSectionLoaderWithDepth, self).__init__( data_dir, split=split, @@ -441,7 +415,15 @@ def __getitem__(self, index): class TrainVoxelWaldelandLoader(VoxelLoader): - def __init__(self, root_path, filename, split="train", window_size=65, batch_size=None, len=None): + def __init__( + self, + root_path, + filename, + split="train", + window_size=65, + batch_size=None, + len=None, + ): super(TrainVoxelWaldelandLoader, self).__init__( root_path, filename, split=split, window_size=window_size, len=len ) @@ -454,9 +436,7 @@ def __init__(self, root_path, filename, split="train", window_size=65, batch_siz else: raise Exception("undefined split") - self.class_imgs, self.coordinates = read_labels( - label_fname, self.data_info - ) + self.class_imgs, self.coordinates = read_labels(label_fname, self.data_info) self.batch_size = batch_size if batch_size else 1 @@ -472,7 +452,7 @@ def __getitem__(self, index): random_stretch=0.2, random_rot_xy=180, random_rot_z=15, - ) + ) return batch, labels @@ -482,9 +462,7 @@ def __getitem__(self, index): class TestSectionLoader(SectionLoader): - def __init__( - self, data_dir, split="test1", is_transform=True, augmentations=None - ): + def __init__(self, data_dir, split="test1", is_transform=True, augmentations=None): super(TestSectionLoader, self).__init__( data_dir, split=split, @@ -501,18 +479,14 @@ def __init__( # We are in test mode. Only read the given split. The other one might not # be available. - txt_path = path.join( - self.data_dir, "splits", "section_" + split + ".txt" - ) + txt_path = path.join(self.data_dir, "splits", "section_" + split + ".txt") file_list = tuple(open(txt_path, "r")) file_list = [id_.rstrip() for id_ in file_list] self.sections = file_list class TestSectionLoaderWithDepth(TestSectionLoader): - def __init__( - self, data_dir, split="test1", is_transform=True, augmentations=None - ): + def __init__(self, data_dir, split="test1", is_transform=True, augmentations=None): super(TestSectionLoaderWithDepth, self).__init__( data_dir, split=split, @@ -569,12 +543,7 @@ class PatchLoader(data.Dataset): """ def __init__( - self, - data_dir, - stride=30, - patch_size=99, - is_transform=True, - augmentations=None, + self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None ): self.data_dir = data_dir self.is_transform = is_transform @@ -589,10 +558,7 @@ def pad_volume(self, volume): Only used for train/val!! Not test. """ return np.pad( - volume, - pad_width=self.patch_size, - mode="constant", - constant_values=255, + volume, pad_width=self.patch_size, mode="constant", constant_values=255 ) def __len__(self): @@ -644,12 +610,7 @@ def transform(self, img, lbl): class TestPatchLoader(PatchLoader): def __init__( - self, - data_dir, - stride=30, - patch_size=99, - is_transform=True, - augmentations=None, + self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None ): super(TestPatchLoader, self).__init__( data_dir, @@ -666,9 +627,7 @@ def __init__( # We are in test mode. Only read the given split. The other one might not # be available. self.split = "test1" # TODO: Fix this can also be test2 - txt_path = path.join( - self.data_dir, "splits", "patch_" + self.split + ".txt" - ) + txt_path = path.join(self.data_dir, "splits", "patch_" + self.split + ".txt") patch_list = tuple(open(txt_path, "r")) patch_list = [id_.rstrip() for id_ in patch_list] self.patches = patch_list @@ -700,9 +659,7 @@ def __init__( # so don't attempt to load them. self.split = split # reading the file names for split - txt_path = path.join( - self.data_dir, "splits", "patch_" + split + ".txt" - ) + txt_path = path.join(self.data_dir, "splits", "patch_" + split + ".txt") patch_list = tuple(open(txt_path, "r")) patch_list = [id_.rstrip() for id_ in patch_list] self.patches = patch_list @@ -806,20 +763,14 @@ def __getitem__(self, index): idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift if direction == "i": im = self.seismic[ - idx, - :, - xdx : xdx + self.patch_size, - ddx : ddx + self.patch_size, + idx, :, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size ] lbl = self.labels[ idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size ] elif direction == "x": im = self.seismic[ - idx : idx + self.patch_size, - :, - xdx, - ddx : ddx + self.patch_size, + idx : idx + self.patch_size, :, xdx, ddx : ddx + self.patch_size ] lbl = self.labels[ idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size @@ -953,9 +904,7 @@ def decode_segmap(label_mask, n_classes=6, label_colours=get_seismic_labels()): r[label_mask == ll] = label_colours[ll, 0] g[label_mask == ll] = label_colours[ll, 1] b[label_mask == ll] = label_colours[ll, 2] - rgb = np.zeros( - (label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3) - ) + rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3)) rgb[:, :, :, 0] = r / 255.0 rgb[:, :, :, 1] = g / 255.0 rgb[:, :, :, 2] = b / 255.0 From 4f15bab108989a7c39ae118f772fad47d1712e16 Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Tue, 29 Oct 2019 15:44:02 +0000 Subject: [PATCH 064/207] Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 --- .flake8 | 14 +++++++++++++- .pre-commit-config.yaml | 1 - 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.flake8 b/.flake8 index bd718670..696bba44 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,17 @@ [flake8] -ignore = E203, E266, E501, W503 max-line-length = 88 max-complexity = 18 select = B,C,E,F,W,T4,B9 +ignore = + # slice notation whitespace, invalid + E203 + # too many leading ‘#’ for block comment + E266 + # module level import not at top of file + E402 + # do not use bare except, specify exception instead + E722 + # line break before binary operator + W503 + # blank line contains whitespace + W293 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4105a32..cddba3d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,4 +8,3 @@ repos: rev: v1.2.3 hooks: - id: flake8 - args: [--max-line-length=88] From 339314dbdb676a85adcf578493826749067272c8 Mon Sep 17 00:00:00 2001 From: Mathew Salvaris Date: Thu, 31 Oct 2019 10:07:45 +0000 Subject: [PATCH 065/207] Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 --- .../segmentation/{tgs_salt => }/__init__.py | 0 .../segmentation/dutchf3/metrics/apex.py | 69 ++- .../segmentation/dutchf3/metrics/horovod.py | 67 --- cv_lib/cv_lib/segmentation/tgs_salt/data.py | 315 ------------ cv_lib/cv_lib/segmentation/tgs_salt/engine.py | 131 ----- cv_lib/cv_lib/segmentation/tgs_salt/losses.py | 270 ---------- .../segmentation/tgs_salt/metrics/__init__.py | 179 ------- .../segmentation/tgs_salt/metrics/apex.py | 102 ---- .../segmentation/tgs_salt/metrics/horovod.py | 87 ---- .../segmentation/tgs_salt/models/__init__.py | 1 - .../tgs_salt/models/resnet_unet.py | 349 ------------- .../segmentation/tgs_salt/models/seg_hrnet.py | 477 ------------------ .../segmentation/tgs_salt/models/unet.py | 132 ----- .../cv_lib/segmentation/tgs_salt/transform.py | 187 ------- .../dutchf3_patch/distributed/train.py | 4 +- .../dutchf3_patch/horovod/configs/hrnet.yaml | 102 ---- .../horovod/configs/patch_deconvnet.yaml | 59 --- .../horovod/configs/patch_deconvnet_skip.yaml | 34 -- .../horovod/configs/seresnet_unet.yaml | 59 --- .../dutchf3_patch/horovod/configs/unet.yaml | 63 --- .../dutchf3_patch/horovod/default.py | 111 ---- .../dutchf3_patch/horovod/train.py | 411 --------------- .../dutchf3_patch/horovod/train.sh | 3 - .../dutchf3_patch/local/train.py | 4 + .../dutchf3_section/local/train.py | 5 +- .../interpretation/dutchf3_voxel/train.py | 1 + .../interpretation/penobscot/local/test.py | 25 +- .../interpretation/penobscot/local/train.py | 3 + .../tgs_salt/apex/configs/hrnet.yaml | 76 --- .../tgs_salt/apex/configs/unet.yaml | 34 -- .../interpretation/tgs_salt/apex/default.py | 89 ---- .../interpretation/tgs_salt/apex/run.sh | 3 - .../interpretation/tgs_salt/apex/train.py | 290 ----------- .../tgs_salt/distributed/configs/hrnet.yaml | 76 --- .../tgs_salt/distributed/configs/unet.yaml | 34 -- .../tgs_salt/distributed/default.py | 89 ---- .../tgs_salt/distributed/run.sh | 3 - .../tgs_salt/distributed/train.py | 291 ----------- .../tgs_salt/horovod/configs/hrnet.yaml | 76 --- .../tgs_salt/horovod/configs/unet.yaml | 34 -- .../tgs_salt/horovod/default.py | 94 ---- .../interpretation/tgs_salt/horovod/run.sh | 3 - .../interpretation/tgs_salt/horovod/train.py | 291 ----------- .../tgs_salt/local/configs/hrnet.yaml | 75 --- .../tgs_salt/local/configs/unet.yaml | 34 -- .../interpretation/tgs_salt/local/default.py | 88 ---- .../interpretation/tgs_salt/local/run.sh | 3 - .../interpretation/tgs_salt/local/train.py | 179 ------- 48 files changed, 79 insertions(+), 5033 deletions(-) rename cv_lib/cv_lib/segmentation/{tgs_salt => }/__init__.py (100%) delete mode 100644 cv_lib/cv_lib/segmentation/dutchf3/metrics/horovod.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/data.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/engine.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/losses.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/metrics/__init__.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/metrics/apex.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/metrics/horovod.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/models/__init__.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/models/resnet_unet.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/models/seg_hrnet.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/models/unet.py delete mode 100644 cv_lib/cv_lib/segmentation/tgs_salt/transform.py delete mode 100644 experiments/interpretation/dutchf3_patch/horovod/configs/hrnet.yaml delete mode 100644 experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet.yaml delete mode 100644 experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet_skip.yaml delete mode 100644 experiments/interpretation/dutchf3_patch/horovod/configs/seresnet_unet.yaml delete mode 100644 experiments/interpretation/dutchf3_patch/horovod/configs/unet.yaml delete mode 100644 experiments/interpretation/dutchf3_patch/horovod/default.py delete mode 100644 experiments/interpretation/dutchf3_patch/horovod/train.py delete mode 100755 experiments/interpretation/dutchf3_patch/horovod/train.sh delete mode 100644 experiments/interpretation/tgs_salt/apex/configs/hrnet.yaml delete mode 100644 experiments/interpretation/tgs_salt/apex/configs/unet.yaml delete mode 100644 experiments/interpretation/tgs_salt/apex/default.py delete mode 100644 experiments/interpretation/tgs_salt/apex/run.sh delete mode 100644 experiments/interpretation/tgs_salt/apex/train.py delete mode 100644 experiments/interpretation/tgs_salt/distributed/configs/hrnet.yaml delete mode 100644 experiments/interpretation/tgs_salt/distributed/configs/unet.yaml delete mode 100644 experiments/interpretation/tgs_salt/distributed/default.py delete mode 100644 experiments/interpretation/tgs_salt/distributed/run.sh delete mode 100644 experiments/interpretation/tgs_salt/distributed/train.py delete mode 100644 experiments/interpretation/tgs_salt/horovod/configs/hrnet.yaml delete mode 100644 experiments/interpretation/tgs_salt/horovod/configs/unet.yaml delete mode 100644 experiments/interpretation/tgs_salt/horovod/default.py delete mode 100644 experiments/interpretation/tgs_salt/horovod/run.sh delete mode 100644 experiments/interpretation/tgs_salt/horovod/train.py delete mode 100644 experiments/interpretation/tgs_salt/local/configs/hrnet.yaml delete mode 100644 experiments/interpretation/tgs_salt/local/configs/unet.yaml delete mode 100644 experiments/interpretation/tgs_salt/local/default.py delete mode 100644 experiments/interpretation/tgs_salt/local/run.sh delete mode 100644 experiments/interpretation/tgs_salt/local/train.py diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/__init__.py b/cv_lib/cv_lib/segmentation/__init__.py similarity index 100% rename from cv_lib/cv_lib/segmentation/tgs_salt/__init__.py rename to cv_lib/cv_lib/segmentation/__init__.py diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py index 44a7a3c5..10967f09 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py @@ -1,9 +1,10 @@ -import torch.distributed as dist -from ignite.metrics.metric import Metric -from ignite.exceptions import NotComputableError +import numpy as np import torch +import torch.distributed as dist from cv_lib.segmentation.dutchf3 import metrics -import numpy as np +from ignite.exceptions import NotComputableError +from ignite.metrics.metric import Metric + @torch.no_grad() def reduce_tensor(tensor, world_size): @@ -12,12 +13,14 @@ def reduce_tensor(tensor, world_size): rt /= world_size return rt + @torch.no_grad() def sum_reduce_tensor(tensor): rt = tensor.clone() dist.all_reduce(rt, op=dist.reduce_op.SUM) return rt + @torch.no_grad() def gather_tensor(tensor, world_size): gather_t = [torch.ones_like(tensor).cuda() for _ in range(dist.get_world_size())] @@ -25,23 +28,63 @@ def gather_tensor(tensor, world_size): return gather_t +class AverageMetric(Metric): + def __init__(self, world_size, batch_size, output_transform=lambda x: x): + super(AverageMetric, self).__init__(output_transform=output_transform) + self._world_size = world_size + self._batch_size = batch_size + self._metric_name = "Metric" + + def reset(self): + self._sum = 0 + self._num_examples = 0 + + @torch.no_grad() + def update(self, output): + reduced_metric = reduce_tensor(output, self._world_size) + self._sum += reduced_metric * self._batch_size + self._num_examples += self._batch_size + + @torch.no_grad() + def compute(self): + if self._num_examples == 0: + raise NotComputableError( + f"{self._metric_name} must have at least one example before it can be computed." + ) + return self._sum / self._num_examples + + +class LossMetric(AverageMetric): + def __init__(self, loss_fn, world_size, batch_size, output_transform=lambda x: x): + super(LossMetric, self).__init__( + world_size, batch_size, output_transform=output_transform + ) + self._loss_fn = loss_fn + self._metric_name = "Loss" + + def update(self, output): + pred, y = output + loss = self._loss_fn(pred, y) + super().update(loss) + + class ConfusionMatrix(metrics.ConfusionMatrix): def compute(self): reduced_metric = sum_reduce_tensor(self._confusion_matrix) return reduced_metric.cpu().numpy() + class PixelwiseAccuracy(ConfusionMatrix): def compute(self): - hist = super(PixelwiseAccuracy,self).compute() + hist = super(PixelwiseAccuracy, self).compute() acc = np.diag(hist).sum() / hist.sum() return acc + class MeanIoU(ConfusionMatrix): def compute(self): hist = super(MeanIoU, self).compute() - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) + iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) mean_iu = np.nanmean(iu) return mean_iu @@ -49,10 +92,10 @@ def compute(self): class FrequencyWeightedIoU(ConfusionMatrix): def compute(self): hist = super(FrequencyWeightedIoU, self).compute() - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) - freq = hist.sum(axis=1) / hist.sum() # fraction of the pixels that come from each class + iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) + freq = ( + hist.sum(axis=1) / hist.sum() + ) # fraction of the pixels that come from each class fwiou = (freq[freq > 0] * iu[freq > 0]).sum() return fwiou @@ -63,4 +106,4 @@ def compute(self): acc = np.diag(hist).sum() / hist.sum() acc_cls = np.diag(hist) / hist.sum(axis=1) mean_acc_cls = np.nanmean(acc_cls) - return mean_acc_cls \ No newline at end of file + return mean_acc_cls diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/horovod.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/horovod.py deleted file mode 100644 index 2fc898ff..00000000 --- a/cv_lib/cv_lib/segmentation/dutchf3/metrics/horovod.py +++ /dev/null @@ -1,67 +0,0 @@ -import torch.distributed as dist -from ignite.metrics.metric import Metric -from ignite.exceptions import NotComputableError -import torch -from cv_lib.segmentation.dutchf3 import metrics -import numpy as np -from ignite.metrics.metric import Metric -from ignite.exceptions import NotComputableError -import torch -import horovod.torch as hvd - - -def reduce_tensor(tensor): - return hvd.allreduce(tensor, average=False) - - -def gather_tensor(tensor): - return hvd.allgather(tensor) - - -def sum_reduce_tensor(tensor): - return hvd.allreduce(tensor, average=False) - - -class ConfusionMatrix(metrics.ConfusionMatrix): - def compute(self): - reduced_metric = sum_reduce_tensor(self._confusion_matrix) - return reduced_metric.cpu().numpy() - - -class PixelwiseAccuracy(ConfusionMatrix): - def compute(self): - hist = super(PixelwiseAccuracy, self).compute() - acc = np.diag(hist).sum() / hist.sum() - return acc - - -class MeanIoU(ConfusionMatrix): - def compute(self): - hist = super(MeanIoU, self).compute() - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) - mean_iu = np.nanmean(iu) - return mean_iu - - -class FrequencyWeightedIoU(ConfusionMatrix): - def compute(self): - hist = super(FrequencyWeightedIoU, self).compute() - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) - freq = ( - hist.sum(axis=1) / hist.sum() - ) # fraction of the pixels that come from each class - fwiou = (freq[freq > 0] * iu[freq > 0]).sum() - return fwiou - - -class MeanClassAccuracy(ConfusionMatrix): - def compute(self): - hist = super(MeanClassAccuracy, self).compute() - acc = np.diag(hist).sum() / hist.sum() - acc_cls = np.diag(hist) / hist.sum(axis=1) - mean_acc_cls = np.nanmean(acc_cls) - return mean_acc_cls diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/data.py b/cv_lib/cv_lib/segmentation/tgs_salt/data.py deleted file mode 100644 index 88a7d5a8..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/data.py +++ /dev/null @@ -1,315 +0,0 @@ -from copy import deepcopy -import os -import numpy as np -import pandas as pd -from tqdm import tqdm - -import cv2 -import torch -from torch.utils.data import Dataset - -from torch.utils.data import DataLoader -from torch.utils.data.sampler import RandomSampler -from ignite.utils import convert_tensor -from sklearn.model_selection import KFold -import torch.nn.functional as F - - -def get_data_ids(data_root, train_csv="train.csv", depths_csv="depths.csv"): - train_id = pd.read_csv(os.path.join(data_root, "train.csv"))["id"].values - depth_id = pd.read_csv(os.path.join(data_root, "depths.csv"))["id"].values - test_id = np.setdiff1d(depth_id, train_id) - return train_id,depth_id,test_id - - -def add_depth_channels(image_tensor): - _, h, w = image_tensor.size() - image = torch.zeros([3, h, w]) - image[0] = image_tensor - for row, const in enumerate(np.linspace(0, 1, h)): - image[1, row, :] = const - image[2] = image[0] * image[1] - return image - - -def train_aug(image, mask): - if np.random.rand() < 0.5: - image, mask = do_horizontal_flip2(image, mask) - - if np.random.rand() < 0.5: - c = np.random.choice(3) - if c == 0: - image, mask = do_random_shift_scale_crop_pad2(image, mask, 0.2) - - if c == 1: - image, mask = do_horizontal_shear2( - image, mask, dx=np.random.uniform(-0.07, 0.07) - ) - - if c == 2: - image, mask = do_shift_scale_rotate2( - image, mask, dx=0, dy=0, scale=1, angle=np.random.uniform(0, 15) - ) - - if np.random.rand() < 0.5: - c = np.random.choice(2) - if c == 0: - image = do_brightness_shift(image, np.random.uniform(-0.1, +0.1)) - if c == 1: - image = do_brightness_multiply(image, np.random.uniform(1 - 0.08, 1 + 0.08)) - - return image, mask - - -class SaltDataset(Dataset): - def __init__( - self, - image_list, - mode, - mask_list=None, - is_tta=False, - is_semi=False, - fine_size=202, - pad_left=0, - pad_right=0, - ): - self.imagelist = image_list - self.mode = mode - self.masklist = mask_list - self.is_tta = is_tta - self.is_semi = is_semi - self.fine_size = fine_size - self.pad_left = pad_left - self.pad_right = pad_right - - def __len__(self): - return len(self.imagelist) - - def __getitem__(self, idx): - image = deepcopy(self.imagelist[idx]) - - if self.mode == "train": - mask = deepcopy(self.masklist[idx]) - - image, mask = train_aug(image, mask) - label = np.where(mask.sum() == 0, 1.0, 0.0).astype(np.float32) - - if self.fine_size != image.shape[0]: - image, mask = do_resize2(image, mask, self.fine_size, self.fine_size) - - if self.pad_left != 0: - image, mask = do_center_pad2(image, mask, self.pad_left, self.pad_right) - - image = image.reshape( - 1, - self.fine_size + self.pad_left + self.pad_right, - self.fine_size + self.pad_left + self.pad_right, - ) - mask = mask.reshape( - 1, - self.fine_size + self.pad_left + self.pad_right, - self.fine_size + self.pad_left + self.pad_right, - ) - image, mask = torch.from_numpy(image), torch.from_numpy(mask) - image = add_depth_channels(image) - return image, mask, torch.from_numpy(label) - - elif self.mode == "val": - mask = deepcopy(self.masklist[idx]) - if self.fine_size != image.shape[0]: - image, mask = do_resize2(image, mask, self.fine_size, self.fine_size) - if self.pad_left != 0: - image = do_center_pad(image, self.pad_left, self.pad_right) - - image = image.reshape( - 1, - self.fine_size + self.pad_left + self.pad_right, - self.fine_size + self.pad_left + self.pad_right, - ) - mask = mask.reshape(1, self.fine_size, self.fine_size) - - image, mask = torch.from_numpy(image), torch.from_numpy(mask) - image = add_depth_channels(image) - - return image, mask - - elif self.mode == "test": - if self.is_tta: - image = cv2.flip(image, 1) - if self.fine_size != image.shape[0]: - image = cv2.resize(image, dsize=(self.fine_size, self.fine_size)) - if self.pad_left != 0: - image = do_center_pad(image, self.pad_left, self.pad_right) - - image = image.reshape( - 1, - self.fine_size + self.pad_left + self.pad_right, - self.fine_size + self.pad_left + self.pad_right, - ) - image = torch.from_numpy(image) - image = add_depth_channels(image) - return image - - -def train_image_fetch(images_id, train_root): - image_train = np.zeros((images_id.shape[0], 101, 101), dtype=np.float32) - mask_train = np.zeros((images_id.shape[0], 101, 101), dtype=np.float32) - - for idx, image_id in tqdm(enumerate(images_id), total=images_id.shape[0]): - image_path = os.path.join(train_root, "images", "{}.png".format(image_id)) - mask_path = os.path.join(train_root, "masks", "{}.png".format(image_id)) - - image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255 - mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255 - image_train[idx] = image - mask_train[idx] = mask - - return image_train, mask_train - - -def test_image_fetch(test_id, test_root): - image_test = np.zeros((len(test_id), 101, 101), dtype=np.float32) - - for n, image_id in tqdm(enumerate(test_id), total=len(test_id)): - image_path = os.path.join(test_root, "images", "{}.png".format(image_id)) - - img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255 - image_test[n] = img - - return image_test - - -def kfold_split(examples_index_list, n_splits=5, random_state=42, shuffle=True): - kf = KFold(n_splits=n_splits, random_state=random_state, shuffle=shuffle) - for train_index, test_index in kf.split(examples_index_list): - yield train_index, test_index - - -def get_data_loaders( - train_ids, - val_ids, - train_batch_size, - val_batch_size, - fine_size, - pad_left, - pad_right, - data_root, - num_workers=4, - pin_memory=True, -): - train_root = os.path.join(data_root, "train") - image_train, mask_train = train_image_fetch(train_ids, train_root) - train_data = SaltDataset( - image_train, - mode="train", - mask_list=mask_train, - fine_size=fine_size, - pad_left=pad_left, - pad_right=pad_right, - ) - - train_loader = DataLoader( - train_data, - sampler=RandomSampler(train_data), - batch_size=train_batch_size, - num_workers=num_workers, - pin_memory=pin_memory, - ) - - image_val, mask_val = train_image_fetch(val_ids, train_root) - val_data = SaltDataset( - image_val, - mode="val", - mask_list=mask_val, - fine_size=fine_size, - pad_left=pad_left, - pad_right=pad_right, - ) - - val_loader = DataLoader( - val_data, - shuffle=False, - batch_size=val_batch_size, - num_workers=num_workers, - pin_memory=pin_memory, - ) - - return train_loader, val_loader - - -def get_distributed_data_loaders( - train_ids, - val_ids, - train_batch_size, - val_batch_size, - fine_size, - pad_left, - pad_right, - rank, - size, - data_root, - num_workers=4, - pin_memory=True, - -): - train_root = os.path.join(data_root, "train") - image_train, mask_train = train_image_fetch(train_ids, train_root) - train_data = SaltDataset( - image_train, - mode="train", - mask_list=mask_train, - fine_size=fine_size, - pad_left=pad_left, - pad_right=pad_right, - ) - - train_sampler = torch.utils.data.distributed.DistributedSampler( - train_data, num_replicas=size, rank=rank - ) - train_loader = DataLoader( - train_data, - sampler=train_sampler, - batch_size=train_batch_size, - num_workers=num_workers, - pin_memory=pin_memory, - ) - - image_val, mask_val = train_image_fetch(val_ids, train_root) - val_data = SaltDataset( - image_val, - mode="val", - mask_list=mask_val, - fine_size=fine_size, - pad_left=pad_left, - pad_right=pad_right, - ) - - val_sampler = torch.utils.data.distributed.DistributedSampler( - val_data, num_replicas=size, rank=rank - ) - - val_loader = DataLoader( - val_data, - sampler=val_sampler, - batch_size=val_batch_size, - num_workers=num_workers, - pin_memory=pin_memory, - ) - - return train_loader, val_loader - - -def prepare_train_batch(batch, device=None, non_blocking=False): - x, y, _ = batch - return ( - convert_tensor(x, device=device, non_blocking=non_blocking), - convert_tensor(y, device=device, non_blocking=non_blocking), - ) - - -def prepare_val_batch(batch, device=None, non_blocking=False): - x, y = batch - return ( - convert_tensor(x, device=device, non_blocking=non_blocking), - convert_tensor(y, device=device, non_blocking=non_blocking), - ) diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/engine.py b/cv_lib/cv_lib/segmentation/tgs_salt/engine.py deleted file mode 100644 index 560c1130..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/engine.py +++ /dev/null @@ -1,131 +0,0 @@ -import torch - -from ignite.engine.engine import Engine, State, Events -from ignite.utils import convert_tensor -import torch.nn.functional as F -from toolz import curry -from torch.nn import functional as F - - -def _upscale_model_output(y_pred, y): - ph, pw = y_pred.size(2), y_pred.size(3) - h, w = y.size(2), y.size(3) - if ph != h or pw != w: - y_pred = F.upsample(input=y_pred, size=(h, w), mode='bilinear') - return y_pred - -def create_supervised_trainer( - model, - optimizer, - loss_fn, - prepare_batch, - device=None, - non_blocking=False, - output_transform=lambda x, y, y_pred, loss: {"loss": loss.item()}, -): - if device: - model.to(device) - - def _update(engine, batch): - model.train() - optimizer.zero_grad() - x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) - y_pred = model(x) - y_pred = _upscale_model_output(y_pred, y) - loss = loss_fn(y_pred.squeeze(1), y.squeeze(1)) - loss.backward() - optimizer.step() - return output_transform(x, y, y_pred, loss) - - return Engine(_update) - - -@curry -def val_transform(x, y, y_pred): - return {"image":x, "y_pred": F.sigmoid(y_pred).detach(), "mask":y.detach()} - - -def create_supervised_evaluator( - model, - prepare_batch, - metrics=None, - device=None, - non_blocking=False, - output_transform=val_transform, -): - metrics = metrics or {} - - if device: - model.to(device) - - def _inference(engine, batch): - model.eval() - with torch.no_grad(): - x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) - y_pred = model(x) - y_pred = _upscale_model_output(y_pred, x) - return output_transform(x, y, y_pred) - - engine = Engine(_inference) - - for name, metric in metrics.items(): - metric.attach(engine, name) - - return engine - - -def create_supervised_trainer_apex( - model, - optimizer, - loss_fn, - prepare_batch, - device=None, - non_blocking=False, - output_transform=lambda x, y, y_pred, loss: {"loss": loss.item()}, -): - from apex import amp - - if device: - model.to(device) - - def _update(engine, batch): - model.train() - optimizer.zero_grad() - x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) - y_pred = model(x) - loss = loss_fn(y_pred.squeeze(1), y.squeeze(1)) - with amp.scale_loss(loss, optimizer) as scaled_loss: - scaled_loss.backward() - optimizer.step() - return output_transform(x, y, y_pred, loss) - - return Engine(_update) - - -# def create_supervised_evaluator_apex( -# model, -# prepare_batch, -# metrics=None, -# device=None, -# non_blocking=False, -# output_transform=lambda x, y, y_pred: (x, y, pred), -# ): -# metrics = metrics or {} - -# if device: -# model.to(device) - -# def _inference(engine, batch): -# model.eval() -# with torch.no_grad(): -# x, y = prepare_batch(batch, device=device, non_blocking=non_blocking) -# y_pred = model(x) -# return output_transform(x, y, y_pred) - -# engine = Engine(_inference) - -# for name, metric in metrics.items(): -# metric.attach(engine, name) - -# return engine - diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/losses.py b/cv_lib/cv_lib/segmentation/tgs_salt/losses.py deleted file mode 100644 index 9740083b..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/losses.py +++ /dev/null @@ -1,270 +0,0 @@ -""" -Lovasz-Softmax and Jaccard hinge loss in PyTorch -Maxim Berman 2018 ESAT-PSI KU Leuven (MIT License) -""" - -from __future__ import print_function, division - -import torch -from torch.autograd import Variable -import torch.nn.functional as F -import numpy as np -try: - from itertools import ifilterfalse -except ImportError: # py3k - from itertools import filterfalse - - -def lovasz_grad(gt_sorted): - """ - Computes gradient of the Lovasz extension w.r.t sorted errors - See Alg. 1 in paper - """ - p = len(gt_sorted) - gts = gt_sorted.sum() - intersection = gts - gt_sorted.float().cumsum(0) - union = gts + (1 - gt_sorted).float().cumsum(0) - jaccard = 1. - intersection / union - if p > 1: # cover 1-pixel case - jaccard[1:p] = jaccard[1:p] - jaccard[0:-1] - return jaccard - - -def iou_binary(preds, labels, EMPTY=1., ignore=None, per_image=True): - """ - IoU for foreground class - binary: 1 foreground, 0 background - """ - if not per_image: - preds, labels = (preds,), (labels,) - ious = [] - for pred, label in zip(preds, labels): - intersection = ((label == 1) & (pred == 1)).sum() - union = ((label == 1) | ((pred == 1) & (label != ignore))).sum() - if not union: - iou = EMPTY - else: - iou = float(intersection) / union - ious.append(iou) - iou = mean(ious) # mean accross images if per_image - return 100 * iou - - -def iou(preds, labels, C, EMPTY=1., ignore=None, per_image=False): - """ - Array of IoU for each (non ignored) class - """ - if not per_image: - preds, labels = (preds,), (labels,) - ious = [] - for pred, label in zip(preds, labels): - iou = [] - for i in range(C): - if i != ignore: # The ignored label is sometimes among predicted classes (ENet - CityScapes) - intersection = ((label == i) & (pred == i)).sum() - union = ((label == i) | ((pred == i) & (label != ignore))).sum() - if not union: - iou.append(EMPTY) - else: - iou.append(float(intersection) / union) - ious.append(iou) - ious = map(mean, zip(*ious)) # mean accross images if per_image - return 100 * np.array(ious) - - -# --------------------------- BINARY LOSSES --------------------------- - - -def lovasz_hinge(logits, labels, per_image=True, ignore=None): - """ - Binary Lovasz hinge loss - logits: [B, H, W] Variable, logits at each pixel (between -\infty and +\infty) - labels: [B, H, W] Tensor, binary ground truth masks (0 or 1) - per_image: compute the loss per image instead of per batch - ignore: void class id - """ - if per_image: - loss = mean(lovasz_hinge_flat(*flatten_binary_scores(log.unsqueeze(0), lab.unsqueeze(0), ignore)) - for log, lab in zip(logits, labels)) - else: - loss = lovasz_hinge_flat(*flatten_binary_scores(logits, labels, ignore)) - return loss - - -def lovasz_hinge_flat(logits, labels): - """ - Binary Lovasz hinge loss - logits: [P] Variable, logits at each prediction (between -\infty and +\infty) - labels: [P] Tensor, binary ground truth labels (0 or 1) - ignore: label to ignore - """ - if len(labels) == 0: - # only void pixels, the gradients should be 0 - return logits.sum() * 0. - signs = 2. * labels.float() - 1. - errors = (1. - logits * Variable(signs)) - errors_sorted, perm = torch.sort(errors, dim=0, descending=True) - perm = perm.data - gt_sorted = labels[perm] - grad = lovasz_grad(gt_sorted) - loss = torch.dot(F.relu(errors_sorted), Variable(grad)) - return loss - -def lovasz_hinge2(logits, labels, per_image=True, ignore=None): - """ - Binary Lovasz hinge loss - logits: [B, H, W] Variable, logits at each pixel (between -\infty and +\infty) - labels: [B, H, W] Tensor, binary ground truth masks (0 or 1) - per_image: compute the loss per image instead of per batch - ignore: void class id - """ - if per_image: - loss = mean(lovasz_hinge_flat2(*flatten_binary_scores(log.unsqueeze(0), lab.unsqueeze(0), ignore)) - for log, lab in zip(logits, labels)) - else: - loss = lovasz_hinge_flat2(*flatten_binary_scores(logits, labels, ignore)) - return loss - - -def lovasz_hinge_flat2(logits, labels): - """ - Binary Lovasz hinge loss - logits: [P] Variable, logits at each prediction (between -\infty and +\infty) - labels: [P] Tensor, binary ground truth labels (0 or 1) - ignore: label to ignore - """ - if len(labels) == 0: - # only void pixels, the gradients should be 0 - return logits.sum() * 0. - signs = 2. * labels.float() - 1. - errors = (1. - logits * Variable(signs)) - errors_sorted, perm = torch.sort(errors, dim=0, descending=True) - perm = perm.data - gt_sorted = labels[perm] - grad = lovasz_grad(gt_sorted) - weight = 1 - if labels.sum() == 0: - weight = 0 - loss = torch.dot(F.relu(errors_sorted), Variable(grad)) * weight - return loss - - -def flatten_binary_scores(scores, labels, ignore=None): - """ - Flattens predictions in the batch (binary case) - Remove labels equal to 'ignore' - """ - scores = scores.view(-1) - labels = labels.view(-1) - if ignore is None: - return scores, labels - valid = (labels != ignore) - vscores = scores[valid] - vlabels = labels[valid] - return vscores, vlabels - - -class StableBCELoss(torch.nn.modules.Module): - def __init__(self): - super(StableBCELoss, self).__init__() - def forward(self, input, target): - neg_abs = - input.abs() - loss = input.clamp(min=0) - input * target + (1 + neg_abs.exp()).log() - return loss.mean() - - -def binary_xloss(logits, labels, ignore=None): - """ - Binary Cross entropy loss - logits: [B, H, W] Variable, logits at each pixel (between -\infty and +\infty) - labels: [B, H, W] Tensor, binary ground truth masks (0 or 1) - ignore: void class id - """ - logits, labels = flatten_binary_scores(logits, labels, ignore) - loss = StableBCELoss()(logits, Variable(labels.float())) - return loss - - -# --------------------------- MULTICLASS LOSSES --------------------------- - - -def lovasz_softmax(probas, labels, only_present=False, per_image=False, ignore=None): - """ - Multi-class Lovasz-Softmax loss - probas: [B, C, H, W] Variable, class probabilities at each prediction (between 0 and 1) - labels: [B, H, W] Tensor, ground truth labels (between 0 and C - 1) - only_present: average only on classes present in ground truth - per_image: compute the loss per image instead of per batch - ignore: void class labels - """ - if per_image: - loss = mean(lovasz_softmax_flat(*flatten_probas(prob.unsqueeze(0), lab.unsqueeze(0), ignore), only_present=only_present) - for prob, lab in zip(probas, labels)) - else: - loss = lovasz_softmax_flat(*flatten_probas(probas, labels, ignore), only_present=only_present) - return loss - - -def lovasz_softmax_flat(probas, labels, only_present=False): - """ - Multi-class Lovasz-Softmax loss - probas: [P, C] Variable, class probabilities at each prediction (between 0 and 1) - labels: [P] Tensor, ground truth labels (between 0 and C - 1) - only_present: average only on classes present in ground truth - """ - C = probas.size(1) - losses = [] - for c in range(C): - fg = (labels == c).float() # foreground for class c - if only_present and fg.sum() == 0: - continue - errors = (Variable(fg) - probas[:, c]).abs() - errors_sorted, perm = torch.sort(errors, 0, descending=True) - perm = perm.data - fg_sorted = fg[perm] - losses.append(torch.dot(errors_sorted, Variable(lovasz_grad(fg_sorted)))) - return mean(losses) - - -def flatten_probas(probas, labels, ignore=None): - """ - Flattens predictions in the batch - """ - B, C, H, W = probas.size() - probas = probas.permute(0, 2, 3, 1).contiguous().view(-1, C) # B * H * W, C = P, C - labels = labels.view(-1) - if ignore is None: - return probas, labels - valid = (labels != ignore) - vprobas = probas[valid.nonzero().squeeze()] - vlabels = labels[valid] - return vprobas, vlabels - -def xloss(logits, labels, ignore=None): - """ - Cross entropy loss - """ - return F.cross_entropy(logits, Variable(labels), ignore_index=255) - - -# --------------------------- HELPER FUNCTIONS --------------------------- - -def mean(l, ignore_nan=False, empty=0): - """ - nanmean compatible with generators. - """ - l = iter(l) - if ignore_nan: - l = ifilterfalse(np.isnan, l) - try: - n = 1 - acc = next(l) - except StopIteration: - if empty == 'raise': - raise ValueError('Empty mean') - return empty - for n, v in enumerate(l, 2): - acc += v - if n == 1: - return acc - return acc / n \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/metrics/__init__.py b/cv_lib/cv_lib/segmentation/tgs_salt/metrics/__init__.py deleted file mode 100644 index 130104f8..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/metrics/__init__.py +++ /dev/null @@ -1,179 +0,0 @@ -import warnings - -import numpy as np -import torch -from ignite.metrics import Metric - - -def do_kaggle_metric(predict, truth, threshold=0.5): - N = len(predict) - predict = predict.reshape(N, -1) - truth = truth.reshape(N, -1) - - predict = predict > threshold - truth = truth > 0.5 - intersection = truth & predict - union = truth | predict - iou = intersection.sum(1) / (union.sum(1) + 1e-8) - - # ------------------------------------------- - result = [] - precision = [] - is_empty_truth = truth.sum(1) == 0 - is_empty_predict = predict.sum(1) == 0 - - threshold = np.array([0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95]) - for t in threshold: - p = iou >= t - - tp = (~is_empty_truth) & (~is_empty_predict) & (iou > t) - fp = (~is_empty_truth) & (~is_empty_predict) & (iou <= t) - fn = (~is_empty_truth) & (is_empty_predict) - fp_empty = (is_empty_truth) & (~is_empty_predict) - tn_empty = (is_empty_truth) & (is_empty_predict) - - p = (tp + tn_empty) / (tp + tn_empty + fp + fp_empty + fn) - - result.append(np.column_stack((tp, fp, fn, tn_empty, fp_empty))) - precision.append(p) - - result = np.array(result).transpose(1, 2, 0) - precision = np.column_stack(precision) - precision = precision.mean(1) - - return precision, result, threshold - - -class KaggleMetric(Metric): - def __init__(self, output_transform=lambda x: x): - super(KaggleMetric, self).__init__(output_transform=output_transform) - - def reset(self): - self._predictions = torch.tensor([], dtype=torch.float32) - self._targets = torch.tensor([], dtype=torch.long) - - def update(self, output): - y_pred, y = output - - y_pred = y_pred.type_as(self._predictions) - y = y.type_as(self._targets) - - self._predictions = torch.cat([self._predictions, y_pred], dim=0) - self._targets = torch.cat([self._targets, y], dim=0) - - # Check once the signature and execution of compute_fn - if self._predictions.shape == y_pred.shape: - try: - self.compute() - except Exception as e: - warnings.warn( - "Probably, there can be a problem with `compute_fn`:\n {}.".format( - e - ), - RuntimeWarning, - ) - - def compute(self): - precision, _, _ = do_kaggle_metric( - self._predictions.numpy(), self._targets.numpy(), 0.5 - ) - precision = precision.mean() - return precision - - -def var_to_np(var): - """Take a pytorch variable and make numpy - """ - if type(var) in [np.array, np.ndarray]: - return var - - # If input is list we do this for all elements - if type(var) == type([]): - out = [] - for v in var: - out.append(var_to_np(v)) - return out - - # TODO: Replace this is from the original implementation and is a really bad idea - try: - var = var.cpu() - except: - None - try: - var = var.data - except: - None - try: - var = var.numpy() - except: - None - - if type(var) == tuple: - var = var[0] - return var - - -def pixel_wise_accuracy(predicted_class, labels): - labels = var_to_np(labels) - predicted_class = var_to_np(predicted_class) - - accuracies = {} - for cls in np.unique(labels): - if cls >= 0: - accuracies["accuracy_class_" + str(cls)] = int( - np.mean(predicted_class[labels == cls] == cls) * 100 - ) - accuracies["average_class_accuracy"] = np.mean([acc for acc in accuracies.values()]) - return accuracies - -EPS = 1e-10 - - - -def overall_pixel_accuracy(hist): - """Computes the total pixel accuracy. - The overall pixel accuracy provides an intuitive - approximation for the qualitative perception of the - label when it is viewed in its overall shape but not - its details. - Args: - hist: confusion matrix. - Returns: - overall_acc: the overall pixel accuracy. - """ - correct = torch.diag(hist).sum() - total = hist.sum() - overall_acc = correct / (total + EPS) - return overall_acc - -def pixel_wise_accuracy2(predicted_class, labels, num_classes=1): - hist = torch.zeros(num_classes+1).cuda() - for i in range(num_classes+1): - hist[i]=torch.mean((predicted_class[labels == i] == i).float()) * 100 - return torch.mean(hist) - -class PixelwiseAccuracy(Metric): - def __init__(self, output_transform=lambda x: x): - super(PixelwiseAccuracy, self).__init__(output_transform=output_transform) - self._threshold=0.5 - - def reset(self): - self._accuracies = [] - self._weights = [] - - def update(self, output): - y_pred, y = output - y_pred[y_pred>self._threshold]=1 - y_pred[y_pred<=self._threshold]=0 - try: - acc_dict = pixel_wise_accuracy(y_pred, y) - print(acc_dict) - self._accuracies.append(acc_dict["average_class_accuracy"]) - self._weights.append(y_pred.shape[0]) # Weight by batch size - except Exception as e: - warnings.warn( - "Error computing accuracy:\n {}.".format(e), RuntimeWarning - ) - - def compute(self): - return np.average(self._accuracies, weights=self._weights) diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/metrics/apex.py b/cv_lib/cv_lib/segmentation/tgs_salt/metrics/apex.py deleted file mode 100644 index f271133c..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/metrics/apex.py +++ /dev/null @@ -1,102 +0,0 @@ -import torch.distributed as dist -from ignite.metrics.metric import Metric -from ignite.exceptions import NotComputableError -import torch - -@torch.no_grad() -def reduce_tensor(tensor, world_size): - rt = tensor.clone() - dist.all_reduce(rt, op=dist.reduce_op.SUM) - rt /= world_size - return rt - -@torch.no_grad() -def gather_tensor(tensor, world_size): - gather_t = [torch.ones_like(tensor).cuda() for _ in range(dist.get_world_size())] - dist.all_gather(gather_t, tensor) - return gather_t - - -class AverageMetric(Metric): - def __init__(self, world_size, batch_size, output_transform=lambda x: x): - super(AverageMetric, self).__init__(output_transform=output_transform) - self._world_size = world_size - self._batch_size = batch_size - self._metric_name = "Metric" - - def reset(self): - self._sum = 0 - self._num_examples = 0 - - @torch.no_grad() - def update(self, output): - reduced_metric = reduce_tensor(output, self._world_size) - self._sum += reduced_metric * self._batch_size - self._num_examples += self._batch_size - - @torch.no_grad() - def compute(self): - if self._num_examples == 0: - raise NotComputableError( - f"{self._metric_name} must have at least one example before it can be computed." - ) - return self._sum / self._num_examples - - -class LossMetric(AverageMetric): - def __init__(self, loss_fn, world_size, batch_size, output_transform=lambda x: x): - super(LossMetric, self).__init__( - world_size, batch_size, output_transform=output_transform - ) - self._loss_fn = loss_fn - self._metric_name = "Loss" - - def update(self, output): - pred, y = output - loss = self._loss_fn(pred, y) - super().update(loss) - - -class KaggleMetric(Metric): - def __init__(self, output_transform=lambda x: x): - super(KaggleMetric, self).__init__(output_transform=output_transform) - - @torch.no_grad() - def reset(self): - self._predictions = torch.tensor([], dtype=torch.float32).cuda() - self._targets = torch.tensor([], dtype=torch.float32).cuda() - - @torch.no_grad() - def update(self, output): - y_pred, y = output - y_pred = y_pred.type_as(self._predictions) - y = y.type_as(self._targets) - self._predictions = torch.cat([self._predictions, y_pred], dim=0) - self._targets = torch.cat([self._targets, y], dim=0) - - @torch.no_grad() - def compute(self): - gather_predictions = gather_tensor(self._predictions, self._world_size) - gather_targets = gather_tensor(self._targets, self._world_size) - predictions = torch.cat(gather_predictions, dim=0) - targets = torch.cat(gather_targets, dim=0) - precision, _, _ = do_kaggle_metric(predictions.detach().cpu().numpy(), targets.detach().cpu().numpy(), 0.5) - precision = precision.mean() - return precision - - - -class PixelwiseAccuracyMetric(AverageMetric): - def __init__(self, world_size, batch_size, output_transform=lambda x: x, threshold=0.5): - super(PixelwiseAccuracyMetric, self).__init__( - world_size, batch_size, output_transform=output_transform - ) - self._acc_fn = pixel_wise_accuracy2 - self._metric_name = "PixelwiseAccuracy" - self._threshold = threshold - - def update(self, output): - y_pred, y = output - y_pred[y_pred>self._threshold] = 1 - y_pred[y_pred<=self._threshold] = 0 - super().update(self._acc_fn(y_pred, y)) diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/metrics/horovod.py b/cv_lib/cv_lib/segmentation/tgs_salt/metrics/horovod.py deleted file mode 100644 index d6d017a2..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/metrics/horovod.py +++ /dev/null @@ -1,87 +0,0 @@ -from ignite.metrics.metric import Metric -from ignite.exceptions import NotComputableError -import torch -import horovod.torch as hvd - - -def reduce_tensor(tensor): - """Computes average of tensor - - Args: - tensor ([type]): [description] - - Returns: - [type]: [description] - """ - return hvd.allreduce(tensor) - - -def gather_tensor(tensor): - return hvd.allgather(tensor) - - -class AverageMetric(Metric): - def __init__(self, world_size, batch_size, output_transform=lambda x: x): - super(AverageMetric, self).__init__(output_transform=output_transform) - self._world_size = world_size - self._batch_size = batch_size - self._metric_name = "Metric" - - def reset(self): - self._sum = 0 - self._num_examples = 0 - - @torch.no_grad() - def update(self, output): - reduced_metric = reduce_tensor(output) - self._sum += reduced_metric * self._batch_size - self._num_examples += self._batch_size - - @torch.no_grad() - def compute(self): - if self._num_examples == 0: - raise NotComputableError( - f"{self._metric_name} must have at least one example before it can be computed." - ) - return self._sum / self._num_examples - - -class LossMetric(AverageMetric): - def __init__(self, loss_fn, world_size, batch_size, output_transform=lambda x: x): - super(LossMetric, self).__init__( - world_size, batch_size, output_transform=output_transform - ) - self._loss_fn = loss_fn - self._metric_name = "Loss" - - def update(self, output): - pred, y = output - loss = self._loss_fn(pred, y) - super().update(loss) - - -class KaggleMetric(Metric): - def __init__(self, output_transform=lambda x: x): - super(KaggleMetric, self).__init__(output_transform=output_transform) - - @torch.no_grad() - def reset(self): - self._predictions = torch.tensor([], dtype=torch.float32).cuda() - self._targets = torch.tensor([], dtype=torch.float32).cuda() - - @torch.no_grad() - def update(self, output): - y_pred, y = output - y_pred = y_pred.type_as(self._predictions) - y = y.type_as(self._targets) - self._predictions = torch.cat([self._predictions, y_pred], dim=0) - self._targets = torch.cat([self._targets, y], dim=0) - - @torch.no_grad() - def compute(self): - predictions = gather_tensor(self._predictions) - targets = gather_tensor(self._targets) - precision, _, _ = do_kaggle_metric(predictions.detach().cpu().numpy(), targets.detach().cpu().numpy(), 0.5) - precision = precision.mean() - return precision - diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/models/__init__.py b/cv_lib/cv_lib/segmentation/tgs_salt/models/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/models/resnet_unet.py b/cv_lib/cv_lib/segmentation/tgs_salt/models/resnet_unet.py deleted file mode 100644 index 8b3906c9..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/models/resnet_unet.py +++ /dev/null @@ -1,349 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F -import torchvision - - -class FPAv2(nn.Module): - def __init__(self, input_dim, output_dim): - super(FPAv2, self).__init__() - self.glob = nn.Sequential(nn.AdaptiveAvgPool2d(1), - nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False)) - - self.down2_1 = nn.Sequential(nn.Conv2d(input_dim, input_dim, kernel_size=5, stride=2, padding=2, bias=False), - nn.BatchNorm2d(input_dim), - nn.ELU(True)) - self.down2_2 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=5, padding=2, bias=False), - nn.BatchNorm2d(output_dim), - nn.ELU(True)) - - self.down3_1 = nn.Sequential(nn.Conv2d(input_dim, input_dim, kernel_size=3, stride=2, padding=1, bias=False), - nn.BatchNorm2d(input_dim), - nn.ELU(True)) - self.down3_2 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=3, padding=1, bias=False), - nn.BatchNorm2d(output_dim), - nn.ELU(True)) - - self.conv1 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False), - nn.BatchNorm2d(output_dim), - nn.ELU(True)) - - def forward(self, x): - # x shape: 512, 16, 16 - x_glob = self.glob(x) # 256, 1, 1 - x_glob = F.upsample(x_glob, scale_factor=16, mode='bilinear', align_corners=True) # 256, 16, 16 - - d2 = self.down2_1(x) # 512, 8, 8 - d3 = self.down3_1(d2) # 512, 4, 4 - - d2 = self.down2_2(d2) # 256, 8, 8 - d3 = self.down3_2(d3) # 256, 4, 4 - - d3 = F.upsample(d3, scale_factor=2, mode='bilinear', align_corners=True) # 256, 8, 8 - d2 = d2 + d3 - - d2 = F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True) # 256, 16, 16 - x = self.conv1(x) # 256, 16, 16 - x = x * d2 - - x = x + x_glob - - return x - - -def conv3x3(input_dim, output_dim, rate=1): - return nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=3, dilation=rate, padding=rate, bias=False), - nn.BatchNorm2d(output_dim), - nn.ELU(True)) - - -class SpatialAttention2d(nn.Module): - def __init__(self, channel): - super(SpatialAttention2d, self).__init__() - self.squeeze = nn.Conv2d(channel, 1, kernel_size=1, bias=False) - self.sigmoid = nn.Sigmoid() - - def forward(self, x): - z = self.squeeze(x) - z = self.sigmoid(z) - return x * z - - -class GAB(nn.Module): - def __init__(self, input_dim, reduction=4): - super(GAB, self).__init__() - self.global_avgpool = nn.AdaptiveAvgPool2d(1) - self.conv1 = nn.Conv2d(input_dim, input_dim // reduction, kernel_size=1, stride=1) - self.conv2 = nn.Conv2d(input_dim // reduction, input_dim, kernel_size=1, stride=1) - self.relu = nn.ReLU(inplace=True) - self.sigmoid = nn.Sigmoid() - - def forward(self, x): - z = self.global_avgpool(x) - z = self.relu(self.conv1(z)) - z = self.sigmoid(self.conv2(z)) - return x * z - - -class Decoder(nn.Module): - def __init__(self, in_channels, channels, out_channels): - super(Decoder, self).__init__() - self.conv1 = conv3x3(in_channels, channels) - self.conv2 = conv3x3(channels, out_channels) - self.s_att = SpatialAttention2d(out_channels) - self.c_att = GAB(out_channels, 16) - - def forward(self, x, e=None): - x = F.upsample(input=x, scale_factor=2, mode='bilinear', align_corners=True) - if e is not None: - x = torch.cat([x, e], 1) - x = self.conv1(x) - x = self.conv2(x) - s = self.s_att(x) - c = self.c_att(x) - output = s + c - return output - - -class Decoderv2(nn.Module): - def __init__(self, up_in, x_in, n_out): - super(Decoderv2, self).__init__() - up_out = x_out = n_out // 2 - self.x_conv = nn.Conv2d(x_in, x_out, 1, bias=False) - self.tr_conv = nn.ConvTranspose2d(up_in, up_out, 2, stride=2) - self.bn = nn.BatchNorm2d(n_out) - self.relu = nn.ReLU(True) - self.s_att = SpatialAttention2d(n_out) - self.c_att = GAB(n_out, 16) - - def forward(self, up_p, x_p): - up_p = self.tr_conv(up_p) - x_p = self.x_conv(x_p) - - cat_p = torch.cat([up_p, x_p], 1) - cat_p = self.relu(self.bn(cat_p)) - s = self.s_att(cat_p) - c = self.c_att(cat_p) - return s + c - - -class SCse(nn.Module): - def __init__(self, dim): - super(SCse, self).__init__() - self.satt = SpatialAttention2d(dim) - self.catt = GAB(dim) - - def forward(self, x): - return self.satt(x) + self.catt(x) - - -# stage1 model -class Res34Unetv4(nn.Module): - def __init__(self): - super(Res34Unetv4, self).__init__() - self.resnet = torchvision.models.resnet34(True) - - self.conv1 = nn.Sequential( - self.resnet.conv1, - self.resnet.bn1, - self.resnet.relu) - - self.encode2 = nn.Sequential(self.resnet.layer1, - SCse(64)) - self.encode3 = nn.Sequential(self.resnet.layer2, - SCse(128)) - self.encode4 = nn.Sequential(self.resnet.layer3, - SCse(256)) - self.encode5 = nn.Sequential(self.resnet.layer4, - SCse(512)) - - self.center = nn.Sequential(FPAv2(512, 256), - nn.MaxPool2d(2, 2)) - - self.decode5 = Decoderv2(256, 512, 64) - self.decode4 = Decoderv2(64, 256, 64) - self.decode3 = Decoderv2(64, 128, 64) - self.decode2 = Decoderv2(64, 64, 64) - self.decode1 = Decoder(64, 32, 64) - - self.logit = nn.Sequential(nn.Conv2d(320, 64, kernel_size=3, padding=1), - nn.ELU(True), - nn.Conv2d(64, 1, kernel_size=1, bias=False)) - - def forward(self, x): - # x: (batch_size, 3, 256, 256) - - x = self.conv1(x) # 64, 128, 128 - e2 = self.encode2(x) # 64, 128, 128 - e3 = self.encode3(e2) # 128, 64, 64 - e4 = self.encode4(e3) # 256, 32, 32 - e5 = self.encode5(e4) # 512, 16, 16 - - f = self.center(e5) # 256, 8, 8 - - d5 = self.decode5(f, e5) # 64, 16, 16 - d4 = self.decode4(d5, e4) # 64, 32, 32 - d3 = self.decode3(d4, e3) # 64, 64, 64 - d2 = self.decode2(d3, e2) # 64, 128, 128 - d1 = self.decode1(d2) # 64, 256, 256 - - f = torch.cat((d1, - F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True), - F.upsample(d3, scale_factor=4, mode='bilinear', align_corners=True), - F.upsample(d4, scale_factor=8, mode='bilinear', align_corners=True), - F.upsample(d5, scale_factor=16, mode='bilinear', align_corners=True)), 1) # 320, 256, 256 - - logit = self.logit(f) # 1, 256, 256 - - return logit - - -# stage2 model -class Res34Unetv3(nn.Module): - def __init__(self): - super(Res34Unetv3, self).__init__() - self.resnet = torchvision.models.resnet34(True) - - self.conv1 = nn.Sequential( - self.resnet.conv1, - self.resnet.bn1, - self.resnet.relu) - - self.encode2 = nn.Sequential(self.resnet.layer1, - SCse(64)) - self.encode3 = nn.Sequential(self.resnet.layer2, - SCse(128)) - self.encode4 = nn.Sequential(self.resnet.layer3, - SCse(256)) - self.encode5 = nn.Sequential(self.resnet.layer4, - SCse(512)) - - self.center = nn.Sequential(FPAv2(512, 256), - nn.MaxPool2d(2, 2)) - - self.decode5 = Decoderv2(256, 512, 64) - self.decode4 = Decoderv2(64, 256, 64) - self.decode3 = Decoderv2(64, 128, 64) - self.decode2 = Decoderv2(64, 64, 64) - self.decode1 = Decoder(64, 32, 64) - - self.dropout2d = nn.Dropout2d(0.4) - self.dropout = nn.Dropout(0.4) - - self.fuse_pixel = conv3x3(320, 64) - self.logit_pixel = nn.Conv2d(64, 1, kernel_size=1, bias=False) - - self.fuse_image = nn.Sequential(nn.Linear(512, 64), - nn.ELU(True)) - self.logit_image = nn.Sequential(nn.Linear(64, 1), - nn.Sigmoid()) - self.logit = nn.Sequential(nn.Conv2d(128, 64, kernel_size=3, padding=1, bias=False), - nn.ELU(True), - nn.Conv2d(64, 1, kernel_size=1, bias=False)) - - def forward(self, x): - # x: (batch_size, 3, 256, 256) - batch_size, c, h, w = x.shape - - x = self.conv1(x) # 64, 128, 128 - e2 = self.encode2(x) # 64, 128, 128 - e3 = self.encode3(e2) # 128, 64, 64 - e4 = self.encode4(e3) # 256, 32, 32 - e5 = self.encode5(e4) # 512, 16, 16 - - e = F.adaptive_avg_pool2d(e5, output_size=1).view(batch_size, -1) # 512 - e = self.dropout(e) - - f = self.center(e5) # 256, 8, 8 - - d5 = self.decode5(f, e5) # 64, 16, 16 - d4 = self.decode4(d5, e4) # 64, 32, 32 - d3 = self.decode3(d4, e3) # 64, 64, 64 - d2 = self.decode2(d3, e2) # 64, 128, 128 - d1 = self.decode1(d2) # 64, 256, 256 - - f = torch.cat((d1, - F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True), - F.upsample(d3, scale_factor=4, mode='bilinear', align_corners=True), - F.upsample(d4, scale_factor=8, mode='bilinear', align_corners=True), - F.upsample(d5, scale_factor=16, mode='bilinear', align_corners=True)), 1) # 320, 256, 256 - f = self.dropout2d(f) - - # segmentation process - fuse_pixel = self.fuse_pixel(f) # 64, 256, 256 - logit_pixel = self.logit_pixel(fuse_pixel) # 1, 256, 256 - - # classification process - fuse_image = self.fuse_image(e) # 64 - logit_image = self.logit_image(fuse_image) # 1 - - # combine segmentation and classification - fuse = torch.cat([fuse_pixel, - F.upsample(fuse_image.view(batch_size, -1, 1, 1), scale_factor=256, mode='bilinear', - align_corners=True)], 1) # 128, 256, 256 - logit = self.logit(fuse) # 1, 256, 256 - - return logit, logit_pixel, logit_image.view(-1) - - -# stage3 model -class Res34Unetv5(nn.Module): - def __init__(self): - super(Res34Unetv5, self).__init__() - self.resnet = torchvision.models.resnet34(True) - - self.conv1 = nn.Sequential( - nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False), - self.resnet.bn1, - self.resnet.relu) - - self.encode2 = nn.Sequential(self.resnet.layer1, - SCse(64)) - self.encode3 = nn.Sequential(self.resnet.layer2, - SCse(128)) - self.encode4 = nn.Sequential(self.resnet.layer3, - SCse(256)) - self.encode5 = nn.Sequential(self.resnet.layer4, - SCse(512)) - - self.center = nn.Sequential(FPAv2(512, 256), - nn.MaxPool2d(2, 2)) - - self.decode5 = Decoderv2(256, 512, 64) - self.decode4 = Decoderv2(64, 256, 64) - self.decode3 = Decoderv2(64, 128, 64) - self.decode2 = Decoderv2(64, 64, 64) - - self.logit = nn.Sequential(nn.Conv2d(256, 32, kernel_size=3, padding=1), - nn.ELU(True), - nn.Conv2d(32, 1, kernel_size=1, bias=False)) - - def forward(self, x): - # x: batch_size, 3, 128, 128 - x = self.conv1(x) # 64, 128, 128 - e2 = self.encode2(x) # 64, 128, 128 - e3 = self.encode3(e2) # 128, 64, 64 - e4 = self.encode4(e3) # 256, 32, 32 - e5 = self.encode5(e4) # 512, 16, 16 - - f = self.center(e5) # 256, 8, 8 - - d5 = self.decode5(f, e5) # 64, 16, 16 - d4 = self.decode4(d5, e4) # 64, 32, 32 - d3 = self.decode3(d4, e3) # 64, 64, 64 - d2 = self.decode2(d3, e2) # 64, 128, 128 - - f = torch.cat((d2, - F.upsample(d3, scale_factor=2, mode='bilinear', align_corners=True), - F.upsample(d4, scale_factor=4, mode='bilinear', align_corners=True), - F.upsample(d5, scale_factor=8, mode='bilinear', align_corners=True)), 1) # 256, 128, 128 - - f = F.dropout2d(f, p=0.4) - logit = self.logit(f) # 1, 128, 128 - - return logit - - -def get_seg_model(cfg, **kwargs): - model = Res34Unetv4() - return model \ No newline at end of file diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/models/seg_hrnet.py b/cv_lib/cv_lib/segmentation/tgs_salt/models/seg_hrnet.py deleted file mode 100644 index 947bf052..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/models/seg_hrnet.py +++ /dev/null @@ -1,477 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright (c) Microsoft -# Licensed under the MIT License. -# Written by Ke Sun (sunk@mail.ustc.edu.cn) -# ------------------------------------------------------------------------------ -"""HRNET for segmentation taken from https://github.com/HRNet/HRNet-Semantic-Segmentation -pytorch-v1.1 branch -hash: 06142dc1c7026e256a7561c3e875b06622b5670f - -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import logging -import os - -import numpy as np -import torch -import torch._utils -import torch.nn as nn -import torch.nn.functional as F - -BatchNorm2d = nn.BatchNorm2d -BN_MOMENTUM = 0.1 -logger = logging.getLogger(__name__) - -def conv3x3(in_planes, out_planes, stride=1): - """3x3 convolution with padding""" - return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, - padding=1, bias=False) - - -class BasicBlock(nn.Module): - expansion = 1 - - def __init__(self, inplanes, planes, stride=1, downsample=None): - super(BasicBlock, self).__init__() - self.conv1 = conv3x3(inplanes, planes, stride) - self.bn1 = BatchNorm2d(planes, momentum=BN_MOMENTUM) - self.relu = nn.ReLU(inplace=True) - self.conv2 = conv3x3(planes, planes) - self.bn2 = BatchNorm2d(planes, momentum=BN_MOMENTUM) - self.downsample = downsample - self.stride = stride - - def forward(self, x): - residual = x - - out = self.conv1(x) - out = self.bn1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.bn2(out) - - if self.downsample is not None: - residual = self.downsample(x) - - out += residual - out = self.relu(out) - - return out - - -class Bottleneck(nn.Module): - expansion = 4 - - def __init__(self, inplanes, planes, stride=1, downsample=None): - super(Bottleneck, self).__init__() - self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) - self.bn1 = BatchNorm2d(planes, momentum=BN_MOMENTUM) - self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, - padding=1, bias=False) - self.bn2 = BatchNorm2d(planes, momentum=BN_MOMENTUM) - self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, - bias=False) - self.bn3 = BatchNorm2d(planes * self.expansion, - momentum=BN_MOMENTUM) - self.relu = nn.ReLU(inplace=True) - self.downsample = downsample - self.stride = stride - - def forward(self, x): - residual = x - - out = self.conv1(x) - out = self.bn1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.bn2(out) - out = self.relu(out) - - out = self.conv3(out) - out = self.bn3(out) - - if self.downsample is not None: - residual = self.downsample(x) - - out += residual - out = self.relu(out) - - return out - - -class HighResolutionModule(nn.Module): - def __init__(self, num_branches, blocks, num_blocks, num_inchannels, - num_channels, fuse_method, multi_scale_output=True): - super(HighResolutionModule, self).__init__() - self._check_branches( - num_branches, blocks, num_blocks, num_inchannels, num_channels) - - self.num_inchannels = num_inchannels - self.fuse_method = fuse_method - self.num_branches = num_branches - - self.multi_scale_output = multi_scale_output - - self.branches = self._make_branches( - num_branches, blocks, num_blocks, num_channels) - self.fuse_layers = self._make_fuse_layers() - self.relu = nn.ReLU(inplace=True) - - def _check_branches(self, num_branches, blocks, num_blocks, - num_inchannels, num_channels): - if num_branches != len(num_blocks): - error_msg = 'NUM_BRANCHES({}) <> NUM_BLOCKS({})'.format( - num_branches, len(num_blocks)) - logger.error(error_msg) - raise ValueError(error_msg) - - if num_branches != len(num_channels): - error_msg = 'NUM_BRANCHES({}) <> NUM_CHANNELS({})'.format( - num_branches, len(num_channels)) - logger.error(error_msg) - raise ValueError(error_msg) - - if num_branches != len(num_inchannels): - error_msg = 'NUM_BRANCHES({}) <> NUM_INCHANNELS({})'.format( - num_branches, len(num_inchannels)) - logger.error(error_msg) - raise ValueError(error_msg) - - def _make_one_branch(self, branch_index, block, num_blocks, num_channels, - stride=1): - downsample = None - if stride != 1 or \ - self.num_inchannels[branch_index] != num_channels[branch_index] * block.expansion: - downsample = nn.Sequential( - nn.Conv2d(self.num_inchannels[branch_index], - num_channels[branch_index] * block.expansion, - kernel_size=1, stride=stride, bias=False), - BatchNorm2d(num_channels[branch_index] * block.expansion, - momentum=BN_MOMENTUM), - ) - - layers = [] - layers.append(block(self.num_inchannels[branch_index], - num_channels[branch_index], stride, downsample)) - self.num_inchannels[branch_index] = \ - num_channels[branch_index] * block.expansion - for i in range(1, num_blocks[branch_index]): - layers.append(block(self.num_inchannels[branch_index], - num_channels[branch_index])) - - return nn.Sequential(*layers) - - def _make_branches(self, num_branches, block, num_blocks, num_channels): - branches = [] - - for i in range(num_branches): - branches.append( - self._make_one_branch(i, block, num_blocks, num_channels)) - - return nn.ModuleList(branches) - - def _make_fuse_layers(self): - if self.num_branches == 1: - return None - - num_branches = self.num_branches - num_inchannels = self.num_inchannels - fuse_layers = [] - for i in range(num_branches if self.multi_scale_output else 1): - fuse_layer = [] - for j in range(num_branches): - if j > i: - fuse_layer.append(nn.Sequential( - nn.Conv2d(num_inchannels[j], - num_inchannels[i], - 1, - 1, - 0, - bias=False), - BatchNorm2d(num_inchannels[i], momentum=BN_MOMENTUM))) - elif j == i: - fuse_layer.append(None) - else: - conv3x3s = [] - for k in range(i-j): - if k == i - j - 1: - num_outchannels_conv3x3 = num_inchannels[i] - conv3x3s.append(nn.Sequential( - nn.Conv2d(num_inchannels[j], - num_outchannels_conv3x3, - 3, 2, 1, bias=False), - BatchNorm2d(num_outchannels_conv3x3, - momentum=BN_MOMENTUM))) - else: - num_outchannels_conv3x3 = num_inchannels[j] - conv3x3s.append(nn.Sequential( - nn.Conv2d(num_inchannels[j], - num_outchannels_conv3x3, - 3, 2, 1, bias=False), - BatchNorm2d(num_outchannels_conv3x3, - momentum=BN_MOMENTUM), - nn.ReLU(inplace=True))) - fuse_layer.append(nn.Sequential(*conv3x3s)) - fuse_layers.append(nn.ModuleList(fuse_layer)) - - return nn.ModuleList(fuse_layers) - - def get_num_inchannels(self): - return self.num_inchannels - - def forward(self, x): - if self.num_branches == 1: - return [self.branches[0](x[0])] - - for i in range(self.num_branches): - x[i] = self.branches[i](x[i]) - - x_fuse = [] - for i in range(len(self.fuse_layers)): - y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]) - for j in range(1, self.num_branches): - if i == j: - y = y + x[j] - elif j > i: - width_output = x[i].shape[-1] - height_output = x[i].shape[-2] - y = y + F.interpolate( - self.fuse_layers[i][j](x[j]), - size=[height_output, width_output], - mode='bilinear') - else: - y = y + self.fuse_layers[i][j](x[j]) - x_fuse.append(self.relu(y)) - - return x_fuse - - -blocks_dict = { - 'BASIC': BasicBlock, - 'BOTTLENECK': Bottleneck -} - - -class HighResolutionNet(nn.Module): - - def __init__(self, config, **kwargs): - extra = config.MODEL.EXTRA - super(HighResolutionNet, self).__init__() - - # stem net - self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, - bias=False) - self.bn1 = BatchNorm2d(64, momentum=BN_MOMENTUM) - self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, - bias=False) - self.bn2 = BatchNorm2d(64, momentum=BN_MOMENTUM) - self.relu = nn.ReLU(inplace=True) - - self.layer1 = self._make_layer(Bottleneck, 64, 64, 4) - - self.stage2_cfg = extra['STAGE2'] - num_channels = self.stage2_cfg['NUM_CHANNELS'] - block = blocks_dict[self.stage2_cfg['BLOCK']] - num_channels = [ - num_channels[i] * block.expansion for i in range(len(num_channels))] - self.transition1 = self._make_transition_layer([256], num_channels) - self.stage2, pre_stage_channels = self._make_stage( - self.stage2_cfg, num_channels) - - self.stage3_cfg = extra['STAGE3'] - num_channels = self.stage3_cfg['NUM_CHANNELS'] - block = blocks_dict[self.stage3_cfg['BLOCK']] - num_channels = [ - num_channels[i] * block.expansion for i in range(len(num_channels))] - self.transition2 = self._make_transition_layer( - pre_stage_channels, num_channels) - self.stage3, pre_stage_channels = self._make_stage( - self.stage3_cfg, num_channels) - - self.stage4_cfg = extra['STAGE4'] - num_channels = self.stage4_cfg['NUM_CHANNELS'] - block = blocks_dict[self.stage4_cfg['BLOCK']] - num_channels = [ - num_channels[i] * block.expansion for i in range(len(num_channels))] - self.transition3 = self._make_transition_layer( - pre_stage_channels, num_channels) - self.stage4, pre_stage_channels = self._make_stage( - self.stage4_cfg, num_channels, multi_scale_output=True) - - last_inp_channels = np.int(np.sum(pre_stage_channels)) - - self.last_layer = nn.Sequential( - nn.Conv2d( - in_channels=last_inp_channels, - out_channels=last_inp_channels, - kernel_size=1, - stride=1, - padding=0), - BatchNorm2d(last_inp_channels, momentum=BN_MOMENTUM), - nn.ReLU(inplace=True), - nn.Conv2d( - in_channels=last_inp_channels, - out_channels=config.DATASET.NUM_CLASSES, - kernel_size=extra.FINAL_CONV_KERNEL, - stride=1, - padding=1 if extra.FINAL_CONV_KERNEL == 3 else 0) - ) - - def _make_transition_layer( - self, num_channels_pre_layer, num_channels_cur_layer): - num_branches_cur = len(num_channels_cur_layer) - num_branches_pre = len(num_channels_pre_layer) - - transition_layers = [] - for i in range(num_branches_cur): - if i < num_branches_pre: - if num_channels_cur_layer[i] != num_channels_pre_layer[i]: - transition_layers.append(nn.Sequential( - nn.Conv2d(num_channels_pre_layer[i], - num_channels_cur_layer[i], - 3, - 1, - 1, - bias=False), - BatchNorm2d( - num_channels_cur_layer[i], momentum=BN_MOMENTUM), - nn.ReLU(inplace=True))) - else: - transition_layers.append(None) - else: - conv3x3s = [] - for j in range(i+1-num_branches_pre): - inchannels = num_channels_pre_layer[-1] - outchannels = num_channels_cur_layer[i] \ - if j == i-num_branches_pre else inchannels - conv3x3s.append(nn.Sequential( - nn.Conv2d( - inchannels, outchannels, 3, 2, 1, bias=False), - BatchNorm2d(outchannels, momentum=BN_MOMENTUM), - nn.ReLU(inplace=True))) - transition_layers.append(nn.Sequential(*conv3x3s)) - - return nn.ModuleList(transition_layers) - - def _make_layer(self, block, inplanes, planes, blocks, stride=1): - downsample = None - if stride != 1 or inplanes != planes * block.expansion: - downsample = nn.Sequential( - nn.Conv2d(inplanes, planes * block.expansion, - kernel_size=1, stride=stride, bias=False), - BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), - ) - - layers = [] - layers.append(block(inplanes, planes, stride, downsample)) - inplanes = planes * block.expansion - for i in range(1, blocks): - layers.append(block(inplanes, planes)) - - return nn.Sequential(*layers) - - def _make_stage(self, layer_config, num_inchannels, - multi_scale_output=True): - num_modules = layer_config['NUM_MODULES'] - num_branches = layer_config['NUM_BRANCHES'] - num_blocks = layer_config['NUM_BLOCKS'] - num_channels = layer_config['NUM_CHANNELS'] - block = blocks_dict[layer_config['BLOCK']] - fuse_method = layer_config['FUSE_METHOD'] - - modules = [] - for i in range(num_modules): - # multi_scale_output is only used last module - if not multi_scale_output and i == num_modules - 1: - reset_multi_scale_output = False - else: - reset_multi_scale_output = True - modules.append( - HighResolutionModule(num_branches, - block, - num_blocks, - num_inchannels, - num_channels, - fuse_method, - reset_multi_scale_output) - ) - num_inchannels = modules[-1].get_num_inchannels() - - return nn.Sequential(*modules), num_inchannels - - def forward(self, x): - x = self.conv1(x) - x = self.bn1(x) - x = self.relu(x) - x = self.conv2(x) - x = self.bn2(x) - x = self.relu(x) - x = self.layer1(x) - - x_list = [] - for i in range(self.stage2_cfg['NUM_BRANCHES']): - if self.transition1[i] is not None: - x_list.append(self.transition1[i](x)) - else: - x_list.append(x) - y_list = self.stage2(x_list) - - x_list = [] - for i in range(self.stage3_cfg['NUM_BRANCHES']): - if self.transition2[i] is not None: - x_list.append(self.transition2[i](y_list[-1])) - else: - x_list.append(y_list[i]) - y_list = self.stage3(x_list) - - x_list = [] - for i in range(self.stage4_cfg['NUM_BRANCHES']): - if self.transition3[i] is not None: - x_list.append(self.transition3[i](y_list[-1])) - else: - x_list.append(y_list[i]) - x = self.stage4(x_list) - - # Upsampling - x0_h, x0_w = x[0].size(2), x[0].size(3) - x1 = F.upsample(x[1], size=(x0_h, x0_w), mode='bilinear') - x2 = F.upsample(x[2], size=(x0_h, x0_w), mode='bilinear') - x3 = F.upsample(x[3], size=(x0_h, x0_w), mode='bilinear') - - x = torch.cat([x[0], x1, x2, x3], 1) - - x = self.last_layer(x) - - return x - - def init_weights(self, pretrained='',): - logger.info('=> init weights from normal distribution') - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.normal_(m.weight, std=0.001) - elif isinstance(m, nn.BatchNorm2d): - nn.init.constant_(m.weight, 1) - nn.init.constant_(m.bias, 0) - if os.path.isfile(pretrained): - pretrained_dict = torch.load(pretrained) - logger.info('=> loading pretrained model {}'.format(pretrained)) - model_dict = self.state_dict() - pretrained_dict = {k: v for k, v in pretrained_dict.items() - if k in model_dict.keys()} - #for k, _ in pretrained_dict.items(): - # logger.info( - # '=> loading {} pretrained model {}'.format(k, pretrained)) - model_dict.update(pretrained_dict) - self.load_state_dict(model_dict) - -def get_seg_model(cfg, **kwargs): - model = HighResolutionNet(cfg, **kwargs) - model.init_weights(cfg.MODEL.PRETRAINED) - - return model diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/models/unet.py b/cv_lib/cv_lib/segmentation/tgs_salt/models/unet.py deleted file mode 100644 index 4ed20136..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/models/unet.py +++ /dev/null @@ -1,132 +0,0 @@ -""" Taken from https://github.com/milesial/Pytorch-UNet - -""" -import numpy as np -import torch -import torch.nn as nn -import torch.nn.functional as F -from torch.autograd import Variable -from torch.nn import init - - - - -class double_conv(nn.Module): - '''(conv => BN => ReLU) * 2''' - def __init__(self, in_ch, out_ch): - super(double_conv, self).__init__() - self.conv = nn.Sequential( - nn.Conv2d(in_ch, out_ch, 3, padding=1), - nn.BatchNorm2d(out_ch), - nn.ReLU(inplace=True), - nn.Conv2d(out_ch, out_ch, 3, padding=1), - nn.BatchNorm2d(out_ch), - nn.ReLU(inplace=True) - ) - - def forward(self, x): - x = self.conv(x) - return x - - -class inconv(nn.Module): - def __init__(self, in_ch, out_ch): - super(inconv, self).__init__() - self.conv = double_conv(in_ch, out_ch) - - def forward(self, x): - x = self.conv(x) - return x - - -class down(nn.Module): - def __init__(self, in_ch, out_ch): - super(down, self).__init__() - self.mpconv = nn.Sequential( - nn.MaxPool2d(2), - double_conv(in_ch, out_ch) - ) - - def forward(self, x): - x = self.mpconv(x) - return x - - -class up(nn.Module): - def __init__(self, in_ch, out_ch, bilinear=True): - super(up, self).__init__() - - if bilinear: - self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) - else: - self.up = nn.ConvTranspose2d(in_ch//2, in_ch//2, 2, stride=2) - - self.conv = double_conv(in_ch, out_ch) - - def forward(self, x1, x2): - x1 = self.up(x1) - - # input is CHW - diffY = x2.size()[2] - x1.size()[2] - diffX = x2.size()[3] - x1.size()[3] - - x1 = F.pad(x1, (diffX // 2, diffX - diffX//2, - diffY // 2, diffY - diffY//2)) - - x = torch.cat([x2, x1], dim=1) - x = self.conv(x) - return x - - -class outconv(nn.Module): - def __init__(self, in_ch, out_ch): - super(outconv, self).__init__() - self.conv = nn.Conv2d(in_ch, out_ch, 1) - - def forward(self, x): - x = self.conv(x) - return x - - -class UNet(nn.Module): - def __init__(self, n_channels, n_classes): - super(UNet, self).__init__() - self.inc = inconv(n_channels, 64) - self.down1 = down(64, 128) - self.down2 = down(128, 256) - self.down3 = down(256, 512) - self.down4 = down(512, 512) - self.up1 = up(1024, 256) - self.up2 = up(512, 128) - self.up3 = up(256, 64) - self.up4 = up(128, 64) - self.outc = outconv(64, n_classes) - - def forward(self, x): - x1 = self.inc(x) - x2 = self.down1(x1) - x3 = self.down2(x2) - x4 = self.down3(x3) - x5 = self.down4(x4) - x = self.up1(x5, x4) - x = self.up2(x, x3) - x = self.up3(x, x2) - x = self.up4(x, x1) - x = self.outc(x) - return x - - -def get_seg_model(cfg, **kwargs): - model = UNet(3, cfg.DATASET.NUM_CLASSES) - return model - - -# if __name__ == "__main__": -# """ -# testing -# """ -# model = UNet(1, in_channels=3, depth=5, merge_mode='concat') -# x = Variable(torch.FloatTensor(np.random.random((1, 3, 320, 320)))) -# out = model(x) -# loss = torch.sum(out) -# loss.backward() diff --git a/cv_lib/cv_lib/segmentation/tgs_salt/transform.py b/cv_lib/cv_lib/segmentation/tgs_salt/transform.py deleted file mode 100644 index 85acd2bd..00000000 --- a/cv_lib/cv_lib/segmentation/tgs_salt/transform.py +++ /dev/null @@ -1,187 +0,0 @@ -import cv2 -import numpy as np -import math -import random - - -def do_resize2(image, mask, H, W): - image = cv2.resize(image,dsize=(W,H)) - mask = cv2.resize(mask,dsize=(W,H)) - mask = (mask>0.5).astype(np.float32) - - return image,mask - -def do_horizontal_flip(image): - #flip left-right - image = cv2.flip(image,1) - return image - -def do_horizontal_flip2(image,mask): - image = do_horizontal_flip(image) - mask = do_horizontal_flip(mask ) - return image, mask - - -def do_center_pad(image, pad_left, pad_right): - return np.pad(image, (pad_left, pad_right), 'edge') - -def do_center_pad2(image, mask, pad_left, pad_right): - image = do_center_pad(image, pad_left, pad_right) - mask = do_center_pad(mask, pad_left, pad_right) - return image, mask - -def do_invert_intensity(image): - #flip left-right - image = np.clip(1-image,0,1) - return image - -def do_brightness_shift(image, alpha=0.125): - image = image + alpha - image = np.clip(image, 0, 1) - return image - - -def do_brightness_multiply(image, alpha=1): - image = alpha*image - image = np.clip(image, 0, 1) - return image - - -#https://www.pyimagesearch.com/2015/10/05/opencv-gamma-correction/ -def do_gamma(image, gamma=1.0): - - image = image ** (1.0 / gamma) - image = np.clip(image, 0, 1) - return image - -def do_shift_scale_crop( image, mask, x0=0, y0=0, x1=1, y1=1 ): - #cv2.BORDER_REFLECT_101 - #cv2.BORDER_CONSTANT - - height, width = image.shape[:2] - image = image[y0:y1,x0:x1] - mask = mask [y0:y1,x0:x1] - - image = cv2.resize(image,dsize=(width,height)) - mask = cv2.resize(mask,dsize=(width,height)) - mask = (mask>0.5).astype(np.float32) - return image, mask - - -def do_random_shift_scale_crop_pad2(image, mask, limit=0.10): - - H, W = image.shape[:2] - - dy = int(H*limit) - y0 = np.random.randint(0,dy) - y1 = H-np.random.randint(0,dy) - - dx = int(W*limit) - x0 = np.random.randint(0,dx) - x1 = W-np.random.randint(0,dx) - - #y0, y1, x0, x1 - image, mask = do_shift_scale_crop( image, mask, x0, y0, x1, y1 ) - return image, mask - -#=========================================================================== - -def do_shift_scale_rotate2( image, mask, dx=0, dy=0, scale=1, angle=0 ): - borderMode=cv2.BORDER_REFLECT_101 - #cv2.BORDER_REFLECT_101 cv2.BORDER_CONSTANT - - height, width = image.shape[:2] - sx = scale - sy = scale - cc = math.cos(angle/180*math.pi)*(sx) - ss = math.sin(angle/180*math.pi)*(sy) - rotate_matrix = np.array([ [cc,-ss], [ss,cc] ]) - - box0 = np.array([ [0,0], [width,0], [width,height], [0,height], ],np.float32) - box1 = box0 - np.array([width/2,height/2]) - box1 = np.dot(box1,rotate_matrix.T) + np.array([width/2+dx,height/2+dy]) - - box0 = box0.astype(np.float32) - box1 = box1.astype(np.float32) - mat = cv2.getPerspectiveTransform(box0,box1) - - image = cv2.warpPerspective(image, mat, (width,height),flags=cv2.INTER_LINEAR, - borderMode=borderMode,borderValue=(0,0,0,)) #cv2.BORDER_CONSTANT, borderValue = (0, 0, 0)) #cv2.BORDER_REFLECT_101 - mask = cv2.warpPerspective(mask, mat, (width,height),flags=cv2.INTER_NEAREST,#cv2.INTER_LINEAR - borderMode=borderMode,borderValue=(0,0,0,)) #cv2.BORDER_CONSTANT, borderValue = (0, 0, 0)) #cv2.BORDER_REFLECT_101 - mask = (mask>0.5).astype(np.float32) - return image, mask - -#https://www.kaggle.com/ori226/data-augmentation-with-elastic-deformations -#https://github.com/letmaik/lensfunpy/blob/master/lensfunpy/util.py -def do_elastic_transform2(image, mask, grid=32, distort=0.2): - borderMode=cv2.BORDER_REFLECT_101 - height, width = image.shape[:2] - - x_step = int(grid) - xx = np.zeros(width,np.float32) - prev = 0 - for x in range(0, width, x_step): - start = x - end = x + x_step - if end > width: - end = width - cur = width - else: - cur = prev + x_step*(1+random.uniform(-distort,distort)) - - xx[start:end] = np.linspace(prev,cur,end-start) - prev=cur - - - y_step = int(grid) - yy = np.zeros(height,np.float32) - prev = 0 - for y in range(0, height, y_step): - start = y - end = y + y_step - if end > height: - end = height - cur = height - else: - cur = prev + y_step*(1+random.uniform(-distort,distort)) - - yy[start:end] = np.linspace(prev,cur,end-start) - prev=cur - - #grid - map_x,map_y = np.meshgrid(xx, yy) - map_x = map_x.astype(np.float32) - map_y = map_y.astype(np.float32) - - #image = map_coordinates(image, coords, order=1, mode='reflect').reshape(shape) - image = cv2.remap(image, map_x, map_y, interpolation=cv2.INTER_LINEAR, borderMode=borderMode,borderValue=(0,0,0,)) - - - mask = cv2.remap(mask, map_x, map_y, interpolation=cv2.INTER_NEAREST, borderMode=borderMode,borderValue=(0,0,0,)) - mask = (mask>0.5).astype(np.float32) - return image, mask - - - - -def do_horizontal_shear2( image, mask, dx=0 ): - borderMode=cv2.BORDER_REFLECT_101 - #cv2.BORDER_REFLECT_101 cv2.BORDER_CONSTANT - - height, width = image.shape[:2] - dx = int(dx*width) - - box0 = np.array([ [0,0], [width,0], [width,height], [0,height], ],np.float32) - box1 = np.array([ [+dx,0], [width+dx,0], [width-dx,height], [-dx,height], ],np.float32) - - box0 = box0.astype(np.float32) - box1 = box1.astype(np.float32) - mat = cv2.getPerspectiveTransform(box0,box1) - - image = cv2.warpPerspective(image, mat, (width,height),flags=cv2.INTER_LINEAR, - borderMode=borderMode,borderValue=(0,0,0,)) #cv2.BORDER_CONSTANT, borderValue = (0, 0, 0)) #cv2.BORDER_REFLECT_101 - mask = cv2.warpPerspective(mask, mat, (width,height),flags=cv2.INTER_NEAREST,#cv2.INTER_LINEAR - borderMode=borderMode,borderValue=(0,0,0,)) #cv2.BORDER_CONSTANT, borderValue = (0, 0, 0)) #cv2.BORDER_REFLECT_101 - mask = (mask>0.5).astype(np.float32) - return image, mask \ No newline at end of file diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py index 84c5cc16..6c58c408 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -32,6 +32,7 @@ create_summary_writer, ) from cv_lib.segmentation import models +from cv_lib.segmentation import extract_metric_from from deepseismic_interpretation.dutchf3.data import ( get_patch_loader, decode_segmap, @@ -267,7 +268,7 @@ def _select_pred_and_mask(model_out_dict): "IoU": apex.MeanIoU( n_classes, device, output_transform=_select_pred_and_mask ), - "nll": salt_metrics.LossMetric( + "nll": apex.LossMetric( criterion, world_size, config.VALIDATION.BATCH_SIZE_PER_GPU, @@ -390,6 +391,7 @@ def snapshot_function(): checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, + extract_metric_from("fiou"), snapshot_function, ) evaluator.add_event_handler( diff --git a/experiments/interpretation/dutchf3_patch/horovod/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/hrnet.yaml deleted file mode 100644 index 2fdf6d9d..00000000 --- a/experiments/interpretation/dutchf3_patch/horovod/configs/hrnet.yaml +++ /dev/null @@ -1,102 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf -SEED: 2019 - - -DATASET: - NUM_CLASSES: 6 - ROOT: /mnt/dutchf3 - CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - - -MODEL: - NAME: seg_hrnet - IN_CHANNELS: 3 - PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' - EXTRA: - FINAL_CONV_KERNEL: 1 - STAGE2: - NUM_MODULES: 1 - NUM_BRANCHES: 2 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - FUSE_METHOD: SUM - STAGE3: - NUM_MODULES: 4 - NUM_BRANCHES: 3 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - FUSE_METHOD: SUM - STAGE4: - NUM_MODULES: 3 - NUM_BRANCHES: 4 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - - 384 - FUSE_METHOD: SUM - -TRAIN: - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "section" #"patch" # Options are No, Patch and Section - STRIDE: 50 - PATCH_SIZE: 100 - AUGMENTATIONS: - RESIZE: - HEIGHT: 200 - WIDTH: 200 - PAD: - HEIGHT: 256 - WIDTH: 256 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 - MODEL_DIR: "models" - - -VALIDATION: - BATCH_SIZE_PER_GPU: 32 - -TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/ccb7206b41dc7411609705e49d9f4c2d74c6eb88/seg_hrnet/Aug30_141919/models/seg_hrnet_running_model_18.pth" - TEST_STRIDE: 10 - SPLIT: 'Both' # Can be Both, Test1, Test2 - INLINE: True - CROSSLINE: True - POST_PROCESSING: - SIZE: 128 # - CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right diff --git a/experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet.yaml deleted file mode 100644 index ca56f9b4..00000000 --- a/experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet.yaml +++ /dev/null @@ -1,59 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf -SEED: 2019 - - -DATASET: - NUM_CLASSES: 6 - ROOT: /mnt/dutchf3 - CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - -MODEL: - NAME: patch_deconvnet - IN_CHANNELS: 1 - - -TRAIN: - BATCH_SIZE_PER_GPU: 64 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "No" # Options are No, Patch and Section - STRIDE: 50 - PATCH_SIZE: 99 - AUGMENTATIONS: - RESIZE: - HEIGHT: 99 - WIDTH: 99 - PAD: - HEIGHT: 99 - WIDTH: 99 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 - MODEL_DIR: "models" - -VALIDATION: - BATCH_SIZE_PER_GPU: 512 - -TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/5cc37bbe5302e1989ef1388d629400a16f82d1a9/patch_deconvnet/Aug27_200339/models/patch_deconvnet_snapshot1model_50.pth" - TEST_STRIDE: 10 - SPLIT: 'Both' # Can be Both, Test1, Test2 - INLINE: True - CROSSLINE: True - POST_PROCESSING: - SIZE: 99 - CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right diff --git a/experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet_skip.yaml deleted file mode 100644 index d0450cc5..00000000 --- a/experiments/interpretation/dutchf3_patch/horovod/configs/patch_deconvnet_skip.yaml +++ /dev/null @@ -1,34 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf -SEED: 2019 - -DATASET: - NUM_CLASSES: 6 - ROOT: /mnt/dutchf3 - DEPTH: 'no' - -MODEL: - NAME: patch_deconvnet_skip - IN_CHANNELS: 1 - - -TRAIN: - BATCH_SIZE_PER_GPU: 64 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - -TEST: - BATCH_SIZE_PER_GPU: 128 diff --git a/experiments/interpretation/dutchf3_patch/horovod/configs/seresnet_unet.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/seresnet_unet.yaml deleted file mode 100644 index 24b8ee36..00000000 --- a/experiments/interpretation/dutchf3_patch/horovod/configs/seresnet_unet.yaml +++ /dev/null @@ -1,59 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf -SEED: 2019 - - -DATASET: - NUM_CLASSES: 6 - ROOT: /mnt/dutchf3 - CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - -MODEL: - NAME: resnet_unet - IN_CHANNELS: 3 - -TRAIN: - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "section" # Options are No, Patch and Section - STRIDE: 50 - PATCH_SIZE: 100 - AUGMENTATIONS: - RESIZE: - HEIGHT: 200 - WIDTH: 200 - PAD: - HEIGHT: 256 - WIDTH: 256 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 - MODEL_DIR: "models" - - -VALIDATION: - BATCH_SIZE_PER_GPU: 32 - -TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/dc2e2d20b7f6d508beb779ffff37c77d0139e588/resnet_unet/Sep01_125513/models/resnet_unet_snapshot1model_52.pth" - TEST_STRIDE: 10 - SPLIT: 'Both' # Can be Both, Test1, Test2 - INLINE: True - CROSSLINE: True - POST_PROCESSING: - SIZE: 128 - CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right \ No newline at end of file diff --git a/experiments/interpretation/dutchf3_patch/horovod/configs/unet.yaml b/experiments/interpretation/dutchf3_patch/horovod/configs/unet.yaml deleted file mode 100644 index 74fdc13a..00000000 --- a/experiments/interpretation/dutchf3_patch/horovod/configs/unet.yaml +++ /dev/null @@ -1,63 +0,0 @@ -# UNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf -SEED: 2019 - - -DATASET: - NUM_CLASSES: 6 - ROOT: /mnt/dutchf3 - CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - -MODEL: - NAME: resnet_unet - IN_CHANNELS: 3 - - -TRAIN: - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "section" # Options are No, Patch and Section - STRIDE: 50 - PATCH_SIZE: 100 - AUGMENTATIONS: - RESIZE: - HEIGHT: 200 - WIDTH: 200 - PAD: - HEIGHT: 256 - WIDTH: 256 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 - MODEL_DIR: "models" - - -VALIDATION: - BATCH_SIZE_PER_GPU: 32 - -TEST: - MODEL_PATH: "" - TEST_STRIDE: 10 - SPLIT: 'Both' # Can be Both, Test1, Test2 - INLINE: True - CROSSLINE: True - POST_PROCESSING: - SIZE: 128 - CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right - diff --git a/experiments/interpretation/dutchf3_patch/horovod/default.py b/experiments/interpretation/dutchf3_patch/horovod/default.py deleted file mode 100644 index 60286bf7..00000000 --- a/experiments/interpretation/dutchf3_patch/horovod/default.py +++ /dev/null @@ -1,111 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright (c) Microsoft -# Licensed under the MIT License. -# ------------------------------------------------------------------------------ - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from yacs.config import CfgNode as CN - -_C = CN() - -_C.OUTPUT_DIR = "output" -_C.LOG_DIR = "log" -_C.GPUS = (0,) -_C.WORKERS = 4 -_C.PRINT_FREQ = 20 -_C.AUTO_RESUME = False -_C.PIN_MEMORY = True -_C.LOG_CONFIG = "/data/home/mat/repos/DeepSeismic/logging.conf" -_C.SEED = 42 - -# Cudnn related params -_C.CUDNN = CN() -_C.CUDNN.BENCHMARK = True -_C.CUDNN.DETERMINISTIC = False -_C.CUDNN.ENABLED = True - - -# DATASET related params -_C.DATASET = CN() -_C.DATASET.ROOT = "" -_C.DATASET.NUM_CLASSES = 6 -_C.DATASET.CLASS_WEIGHTS = [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - -# common params for NETWORK -_C.MODEL = CN() -_C.MODEL.NAME = "patch_deconvnet" -_C.MODEL.IN_CHANNELS = 1 -_C.MODEL.PRETRAINED = "" -_C.MODEL.EXTRA = CN(new_allowed=True) - - -# training -_C.TRAIN = CN() -_C.TRAIN.MIN_LR = 0.001 -_C.TRAIN.MAX_LR = 0.01 -_C.TRAIN.MOMENTUM = 0.9 -_C.TRAIN.BEGIN_EPOCH = 0 -_C.TRAIN.END_EPOCH = 484 -_C.TRAIN.BATCH_SIZE_PER_GPU = 32 -_C.TRAIN.WEIGHT_DECAY = 0.0001 -_C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.MODEL_DIR = "models" -_C.TRAIN.AUGMENTATION = True -_C.TRAIN.STRIDE = 50 -_C.TRAIN.PATCH_SIZE = 99 -_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 -_C.TRAIN.STD = 0.21 # 0.20976548783479299 -_C.TRAIN.DEPTH = 'None' # Options are None, Patch and Section -# None adds no depth information and the num of channels remains at 1 -# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 -# Section adds depth per section so contains depth information for the whole section, channels=3 -_C.TRAIN.AUGMENTATIONS = CN() -_C.TRAIN.AUGMENTATIONS.RESIZE = CN() -_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 -_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 -_C.TRAIN.AUGMENTATIONS.PAD = CN() -_C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 -_C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 - - -# validation -_C.VALIDATION = CN() -_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 - -# TEST -_C.TEST = CN() -_C.TEST.MODEL_PATH = "" -_C.TEST.TEST_STRIDE = 10 -_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 -_C.TEST.INLINE = True -_C.TEST.CROSSLINE = True -_C.TEST.POST_PROCESSING = CN() # Model output postprocessing -_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels -_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right - -# Horovod related params -_C.HOROVOD = CN() -_C.HOROVOD.FP16 = False - -def update_config(cfg, options=None, config_file=None): - cfg.defrost() - - if config_file: - cfg.merge_from_file(config_file) - - if options: - cfg.merge_from_list(options) - - cfg.freeze() - - -if __name__ == "__main__": - import sys - - with open(sys.argv[1], "w") as f: - print(_C, file=f) - - diff --git a/experiments/interpretation/dutchf3_patch/horovod/train.py b/experiments/interpretation/dutchf3_patch/horovod/train.py deleted file mode 100644 index afdf8353..00000000 --- a/experiments/interpretation/dutchf3_patch/horovod/train.py +++ /dev/null @@ -1,411 +0,0 @@ -"""Train models on Dutch F3 salt dataset - -Trains models using PyTorch DistributedDataParallel -Uses a warmup schedule that then goes into a cyclic learning rate -""" - -import logging -import logging.config -import os -from os import path - -import cv2 -import fire -import numpy as np -import torch -from albumentations import ( - Compose, - HorizontalFlip, - Normalize, - Resize, - PadIfNeeded, -) -import fire -import horovod.torch as hvd -import torch -import torch.nn.functional as F -from default import _C as config -from default import update_config -from ignite.contrib.handlers import ( - CustomPeriodicEvent, - CosineAnnealingScheduler, - LinearCyclicalScheduler, - ConcatScheduler, -) -from ignite.engine import Events -from toolz import curry - -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) -from cv_lib.segmentation import models -from deepseismic_interpretation.dutchf3.data import ( - get_patch_loader, - decode_segmap, -) -from cv_lib.segmentation.dutchf3.engine import ( - create_supervised_evaluator, - create_supervised_trainer, -) -from cv_lib.segmentation.dutchf3.metrics import horovod -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, - np_to_tb, -) -from default import _C as config -from default import update_config -from ignite.contrib.handlers import ( - ConcatScheduler, - CosineAnnealingScheduler, - LinearCyclicalScheduler, -) -from ignite.engine import Events -from ignite.utils import convert_tensor -from toolz import compose, curry -from torch.utils import data - - -def prepare_batch(batch, device=None, non_blocking=False): - x, y = batch - return ( - convert_tensor(x, device=device, non_blocking=non_blocking), - convert_tensor(y, device=device, non_blocking=non_blocking), - ) - -@curry -def update_sampler_epoch(data_loader, engine): - data_loader.sampler.epoch = engine.state.epoch - - -def run(*options, cfg=None): - """Run training and validation of model - - Notes: - Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file - - Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py - cfg (str, optional): Location of config file to load. Defaults to None. - """ - - update_config(config, options=options, config_file=cfg) - hvd.init() - silence_other_ranks = True - logging.config.fileConfig(config.LOG_CONFIG) - logger = logging.getLogger(__name__) - torch.manual_seed(config.SEED) - torch.cuda.set_device(hvd.local_rank()) - torch.cuda.manual_seed(config.SEED) - rank, world_size = hvd.rank(), hvd.size() - - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - torch.manual_seed(config.SEED) - if torch.cuda.is_available(): - torch.cuda.manual_seed_all(config.SEED) - np.random.seed(seed=config.SEED) - # Setup Augmentations - basic_aug = Compose( - [ - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, - ), - Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, - ), - PadIfNeeded( - min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, - min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=255, - ), - ] - ) - if config.TRAIN.AUGMENTATION: - train_aug = Compose( - [ - basic_aug, - HorizontalFlip(p=0.5), - ] - ) - val_aug = basic_aug - else: - train_aug = val_aug = basic_aug - - TrainPatchLoader = get_patch_loader(config) - - train_set = TrainPatchLoader( - config.DATASET.ROOT, - split="train", - is_transform=True, - stride=config.TRAIN.STRIDE, - patch_size=config.TRAIN.PATCH_SIZE, - augmentations=train_aug, - ) - logger.info(f"Training examples {len(train_set)}") - - val_set = TrainPatchLoader( - config.DATASET.ROOT, - split="val", - is_transform=True, - stride=config.TRAIN.STRIDE, - patch_size=config.TRAIN.PATCH_SIZE, - augmentations=val_aug - ) - logger.info(f"Validation examples {len(val_set)}") - n_classes = train_set.n_classes - - train_sampler = torch.utils.data.distributed.DistributedSampler( - train_set, num_replicas=world_size, rank=rank - ) - - train_loader = data.DataLoader( - train_set, - batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - sampler=train_sampler, - ) - - val_sampler = torch.utils.data.distributed.DistributedSampler( - val_set, num_replicas=world_size, rank=rank - ) - - val_loader = data.DataLoader( - val_set, - batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - sampler=val_sampler, - ) - - model = getattr(models, config.MODEL.NAME).get_seg_model(config) - - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" - model = model.to(device) # Send to GPU - - optimizer = torch.optim.SGD( - model.parameters(), - lr=config.TRAIN.MAX_LR, - momentum=config.TRAIN.MOMENTUM, - weight_decay=config.TRAIN.WEIGHT_DECAY, - ) - # weights are inversely proportional to the frequency of the classes in the training set - class_weights = torch.tensor( - config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False - ) - - criterion = torch.nn.CrossEntropyLoss( - weight=class_weights, ignore_index=255, reduction="mean" - ) - # Horovod: broadcast parameters & optimizer state. - hvd.broadcast_parameters(model.state_dict(), root_rank=0) - hvd.broadcast_optimizer_state(optimizer, root_rank=0) - - # Horovod: (optional) compression algorithm. - compression = hvd.Compression.fp16 if config.HOROVOD.FP16 else hvd.Compression.none - - # Horovod: wrap optimizer with DistributedOptimizer. - optimizer = hvd.DistributedOptimizer(optimizer, - named_parameters=model.named_parameters(), - compression=compression) - - # summary_writer = create_summary_writer(log_dir=config.LOG_DIR) - snapshot_duration = scheduler_step * len(train_loader) - warmup_duration = 5 * len(train_loader) - warmup_scheduler = LinearCyclicalScheduler( - optimizer, - "lr", - start_value=config.TRAIN.MAX_LR, - end_value=config.TRAIN.MAX_LR * world_size, - cycle_size=10 * len(train_loader), - ) - cosine_scheduler = CosineAnnealingScheduler( - optimizer, - "lr", - config.TRAIN.MAX_LR * world_size, - config.TRAIN.MIN_LR * world_size, - snapshot_duration, - ) - - scheduler = ConcatScheduler( - schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] - ) - - trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_batch, device=device - ) - - trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles - trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) - - if silence_other_ranks & rank != 0: - logging.getLogger("ignite.engine.engine.Engine").setLevel( - logging.WARNING - ) - - def _select_pred_and_mask(model_out_dict): - return ( - model_out_dict["y_pred"].squeeze(), - model_out_dict["mask"].squeeze(), - ) - - - evaluator = create_supervised_evaluator( - model, - prepare_batch, - metrics={ - "IoU": horovod.MeanIoU( - n_classes, device, output_transform=_select_pred_and_mask - ), - "nll": salt_metrics.LossMetric( - criterion, - world_size, - config.VALIDATION.BATCH_SIZE_PER_GPU, - output_transform=_select_pred_and_mask, - ), - "mca": horovod.MeanClassAccuracy( - n_classes, device, output_transform=_select_pred_and_mask - ), - "fiou": horovod.FrequencyWeightedIoU( - n_classes, device, output_transform=_select_pred_and_mask - ), - "pixa": horovod.PixelwiseAccuracy( - n_classes, device, output_transform=_select_pred_and_mask - ), - }, - device=device, - ) - - # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) - - if rank == 0: # Run only on master process - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) - logger.info(f"Logging Tensorboard to {path.join(output_dir, config.LOG_DIR)}") - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), - ) - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), - ) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - logging_handlers.log_metrics( - "Validation results", - metrics_dict={ - "IoU": "IoU :", - "nll": "Avg loss :", - "pixa": "Pixelwise Accuracy :", - "mca": "Mean Class Accuracy :", - "fiou": "Freq Weighted IoU :", - }, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - tensorboard_handlers.log_metrics( - summary_writer, - trainer, - "epoch", - metrics_dict={ - "IoU": "Validation/IoU", - "nll": "Validation/Loss", - "mca": "Validation/MCA", - "fiou": "Validation/FIoU", - }, - ), - ) - def _select_max(pred_tensor): - return pred_tensor.max(1)[1] - - def _tensor_to_numpy(pred_tensor): - return pred_tensor.squeeze().cpu().numpy() - - transform_func = compose( - np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy - ) - - transform_pred = compose(transform_func, _select_max) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, - "Validation/Mask", - "mask", - transform_func=transform_func, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, - "Validation/Pred", - "y_pred", - transform_func=transform_pred, - ), - ) - - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - - checkpoint_handler = SnapshotHandler( - path.join(output_dir, config.TRAIN.MODEL_DIR), - config.MODEL.NAME, - snapshot_function, - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) - - - logger.info("Starting training") - - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) - - -if __name__ == "__main__": - fire.Fire(run) diff --git a/experiments/interpretation/dutchf3_patch/horovod/train.sh b/experiments/interpretation/dutchf3_patch/horovod/train.sh deleted file mode 100755 index 1dd57685..00000000 --- a/experiments/interpretation/dutchf3_patch/horovod/train.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -export PYTHONPATH=/workspace:$PYTHONPATH -mpirun -np 8 -bind-to none -map-by slot -x NCCL_DEBUG=INFO -x LD_LIBRARY_PATH -x PATH -mca pml ob1 -mca btl ^openib python train.py LOG_CONFIG /workspace/logging.conf DATASET.ROOT /data/dutchf3 MODEL.PRETRAINED /data/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth --cfg "/workspace/experiments/segmentation/dutchf3/horovod/configs/hrnet.yaml" \ No newline at end of file diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index 2e898063..4b1fb17f 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -25,6 +25,7 @@ from cv_lib.event_handlers.logging_handlers import Evaluator from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, create_summary_writer) +from cv_lib.segmentation import models from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, create_supervised_trainer) from cv_lib.segmentation.dutchf3.metrics import (FrequencyWeightedIoU, @@ -36,6 +37,8 @@ from default import _C as config from default import update_config +from cv_lib.segmentation import extract_metric_from + def prepare_batch(batch, device=None, non_blocking=False): x, y = batch @@ -304,6 +307,7 @@ def snapshot_function(): checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, + extract_metric_from("fiou"), snapshot_function, ) evaluator.add_event_handler( diff --git a/experiments/interpretation/dutchf3_section/local/train.py b/experiments/interpretation/dutchf3_section/local/train.py index 16ddcc4d..04db2b12 100644 --- a/experiments/interpretation/dutchf3_section/local/train.py +++ b/experiments/interpretation/dutchf3_section/local/train.py @@ -6,7 +6,6 @@ import logging.config from os import path -# import cv2 import fire import numpy as np import torch @@ -25,6 +24,7 @@ create_image_writer, create_summary_writer, ) +from cv_lib.segmentation import models from cv_lib.segmentation.dutchf3.engine import ( create_supervised_evaluator, create_supervised_trainer, @@ -51,6 +51,8 @@ from toolz import compose from torch.utils import data +from cv_lib.segmentation import extract_metric_from + def prepare_batch(batch, device="cuda", non_blocking=False): x, y = batch @@ -327,6 +329,7 @@ def snapshot_function(): checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, + extract_metric_from("fiou"), snapshot_function, ) diff --git a/experiments/interpretation/dutchf3_voxel/train.py b/experiments/interpretation/dutchf3_voxel/train.py index 62e58428..1e09c609 100644 --- a/experiments/interpretation/dutchf3_voxel/train.py +++ b/experiments/interpretation/dutchf3_voxel/train.py @@ -39,6 +39,7 @@ create_image_writer, create_summary_writer, ) +from cv_lib.segmentation import models # from cv_lib.segmentation.dutchf3.engine import ( # create_supervised_evaluator, diff --git a/experiments/interpretation/penobscot/local/test.py b/experiments/interpretation/penobscot/local/test.py index 8178809d..91899519 100644 --- a/experiments/interpretation/penobscot/local/test.py +++ b/experiments/interpretation/penobscot/local/test.py @@ -11,43 +11,28 @@ import fire import numpy as np import torch -import torch.nn.functional as F import torchvision from albumentations import ( - CenterCrop, Compose, - HorizontalFlip, Normalize, PadIfNeeded, Resize, ) -from ignite.contrib.handlers import CosineAnnealingScheduler -from ignite.engine import Events -from ignite.metrics import Loss, Metric -from ignite.utils import convert_tensor -from PIL import Image -from toolz import compose, pipe, tail, take -from toolz.sandbox.core import unzip -from torch.utils import data - from cv_lib.event_handlers import ( - SnapshotHandler, logging_handlers, tensorboard_handlers, ) -from cv_lib.event_handlers.logging_handlers import Evaluator from cv_lib.event_handlers.tensorboard_handlers import ( create_image_writer, create_summary_writer, ) +from cv_lib.segmentation import models from cv_lib.segmentation.dutchf3.metrics import ( FrequencyWeightedIoU, MeanClassAccuracy, MeanIoU, PixelwiseAccuracy, - _torch_hist, ) -from deepseismic_interpretation.penobscot.metrics import InlineMeanIoU from cv_lib.segmentation.dutchf3.utils import ( current_datetime, generate_path, @@ -57,14 +42,20 @@ ) from cv_lib.segmentation.penobscot.engine import ( create_supervised_evaluator, - create_supervised_trainer, ) from deepseismic_interpretation.dutchf3.data import decode_segmap from deepseismic_interpretation.penobscot.data import ( get_patch_dataset, ) +from deepseismic_interpretation.penobscot.metrics import InlineMeanIoU from default import _C as config from default import update_config +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from toolz import compose, tail, take +from toolz.sandbox.core import unzip +from torch.utils import data def _prepare_batch(batch, device=None, non_blocking=False): diff --git a/experiments/interpretation/penobscot/local/train.py b/experiments/interpretation/penobscot/local/train.py index bf2eea22..82168244 100644 --- a/experiments/interpretation/penobscot/local/train.py +++ b/experiments/interpretation/penobscot/local/train.py @@ -38,6 +38,7 @@ create_image_writer, create_summary_writer, ) +from cv_lib.segmentation import models from cv_lib.segmentation.penobscot.engine import ( create_supervised_evaluator, create_supervised_trainer, @@ -59,6 +60,7 @@ from default import _C as config from default import update_config +from cv_lib.segmentation import extract_metric_from mask_value = 255 _SEG_COLOURS = np.asarray( @@ -352,6 +354,7 @@ def snapshot_function(): checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, + extract_metric_from("IoU"), snapshot_function, ) evaluator.add_event_handler( diff --git a/experiments/interpretation/tgs_salt/apex/configs/hrnet.yaml b/experiments/interpretation/tgs_salt/apex/configs/hrnet.yaml deleted file mode 100644 index b6082ed8..00000000 --- a/experiments/interpretation/tgs_salt/apex/configs/hrnet.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# HRNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /workspace/logging.conf - -DATASET: - NUM_CLASSES: 1 - ROOT: /data/tgssalt - -MODEL: - NAME: seg_hrnet - PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' - EXTRA: - FINAL_CONV_KERNEL: 1 - STAGE2: - NUM_MODULES: 1 - NUM_BRANCHES: 2 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - FUSE_METHOD: SUM - STAGE3: - NUM_MODULES: 4 - NUM_BRANCHES: 3 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - FUSE_METHOD: SUM - STAGE4: - NUM_MODULES: 3 - NUM_BRANCHES: 4 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - - 384 - FUSE_METHOD: SUM - -TRAIN: - BATCH_SIZE_PER_GPU: 32 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.01 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 - SNAPSHOTS: 5 - -TEST: - BATCH_SIZE_PER_GPU: 64 diff --git a/experiments/interpretation/tgs_salt/apex/configs/unet.yaml b/experiments/interpretation/tgs_salt/apex/configs/unet.yaml deleted file mode 100644 index 858bc587..00000000 --- a/experiments/interpretation/tgs_salt/apex/configs/unet.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# UNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /workspace/logging.conf - -DATASET: - NUM_CLASSES: 1 - ROOT: /data/tgssalt - -MODEL: - NAME: resnet_unet - -TRAIN: - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.01 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 - SNAPSHOTS: 5 - -TEST: - BATCH_SIZE_PER_GPU: 32 diff --git a/experiments/interpretation/tgs_salt/apex/default.py b/experiments/interpretation/tgs_salt/apex/default.py deleted file mode 100644 index 79e679b7..00000000 --- a/experiments/interpretation/tgs_salt/apex/default.py +++ /dev/null @@ -1,89 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright (c) Microsoft -# Licensed under the MIT License. -# ------------------------------------------------------------------------------ - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -from yacs.config import CfgNode as CN - - -_C = CN() - -_C.OUTPUT_DIR = "" -_C.LOG_DIR = "" -_C.WORKERS = 4 -_C.PRINT_FREQ = 20 -_C.AUTO_RESUME = False -_C.PIN_MEMORY = True -_C.LOG_CONFIG = "logging.conf" - -# Cudnn related params -_C.CUDNN = CN() -_C.CUDNN.BENCHMARK = True -_C.CUDNN.DETERMINISTIC = False -_C.CUDNN.ENABLED = True - -# DATASET related params -_C.DATASET = CN() -_C.DATASET.ROOT = "/mnt/tgssalt" -_C.DATASET.NUM_CLASSES = 1 - -# common params for NETWORK -_C.MODEL = CN() -_C.MODEL.NAME = "seg_hrnet" -_C.MODEL.PRETRAINED = "" -_C.MODEL.EXTRA = CN(new_allowed=True) - -_C.LOSS = CN() -_C.LOSS.WEIGHTS = (0.01, 1) -_C.LOSS.ADJUST_EPOCH = 50 -_C.LOSS.ADJUSTED_WEIGHTS = (0.3, 0.7) - - -# training -_C.TRAIN = CN() -_C.TRAIN.MIN_LR = 0.001 -_C.TRAIN.MAX_LR = 0.01 -_C.TRAIN.MOMENTUM = 0.9 -_C.TRAIN.BEGIN_EPOCH = 0 -_C.TRAIN.END_EPOCH = 484 -_C.TRAIN.BATCH_SIZE_PER_GPU = 32 -_C.TRAIN.WEIGHT_DECAY = 0.0001 -_C.TRAIN.PAD_LEFT = 27 -_C.TRAIN.PAD_RIGHT = 27 -_C.TRAIN.FINE_SIZE = 202 -_C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.SAVE_LOCATION = "/tmp/models" - -# testing -_C.TEST = CN() -_C.TEST.BATCH_SIZE_PER_GPU = 32 -_C.TEST.CV = CN() -_C.TEST.CV.N_SPLITS = 5 -_C.TEST.CV.SEED = 42 -_C.TEST.CV.SHUFFLE = True - - -def update_config(cfg, options=None, config_file=None): - cfg.defrost() - - if config_file: - cfg.merge_from_file(config_file) - - if options: - cfg.merge_from_list(options) - - cfg.freeze() - - -if __name__ == "__main__": - import sys - - with open(sys.argv[1], "w") as f: - print(_C, file=f) - diff --git a/experiments/interpretation/tgs_salt/apex/run.sh b/experiments/interpretation/tgs_salt/apex/run.sh deleted file mode 100644 index c9f6ba98..00000000 --- a/experiments/interpretation/tgs_salt/apex/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -export PYTHONPATH=/workspace:$PYTHONPATH -python -m torch.distributed.launch --nproc_per_node=2 train.py --cfg configs/unet.yaml \ No newline at end of file diff --git a/experiments/interpretation/tgs_salt/apex/train.py b/experiments/interpretation/tgs_salt/apex/train.py deleted file mode 100644 index 0f187474..00000000 --- a/experiments/interpretation/tgs_salt/apex/train.py +++ /dev/null @@ -1,290 +0,0 @@ -"""Train models on TGS salt dataset - -Trains models using PyTorch DistributedDataParallel -Uses a warmup schedule that then goes into a cyclic learning rate -Uses a weighted combination of Lovasz and BCE loss -""" - -import logging -import logging.config -import os - -import fire -import torch -import torch.nn.functional as F -from apex import amp -from apex.parallel import DistributedDataParallel -from default import _C as config -from default import update_config -from ignite.contrib.handlers import ( - CustomPeriodicEvent, - CosineAnnealingScheduler, - LinearCyclicalScheduler, - ConcatScheduler, -) -from ignite.engine import Events -from toolz import curry - -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) - get_distributed_data_loaders, - kfold_split, - prepare_train_batch, - prepare_val_batch, -) - create_supervised_evaluator, - create_supervised_trainer_apex) - - -@curry -def update_sampler_epoch(data_loader, engine): - data_loader.sampler.epoch = engine.state.epoch - - -class CombinedLoss: - """Creates a function that calculates weighted combined loss - """ - - def __init__(self, loss_functions, weights): - """Initialise CombinedLoss - - Args: - loss_functions (list): A list of PyTorch loss functions - weights (list[int]): A list of weights to use when combining loss functions - """ - self._losses = loss_functions - self.weights = weights - - def __call__(self, input, target): - # if weight is zero remove loss from calculations - loss_functions_and_weights = filter( - lambda l_w: l_w[1] > 0, zip(self._losses, self.weights) - ) - loss_list = [ - weight * loss(input, target) for loss, weight in loss_functions_and_weights - ] - combined_loss = torch.stack(loss_list).sum() - return combined_loss - - -@curry -def adjust_loss(loss_obj, weights, engine): - loss_obj.weights = weights - - -def run(*options, cfg=None, local_rank=0): - """Run training and validation of model - - Notes: - Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file - - Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py - cfg (str, optional): Location of config file to load. Defaults to None. - """ - - update_config(config, options=options, config_file=cfg) - silence_other_ranks = True - logging.config.fileConfig(config.LOG_CONFIG) - world_size = int(os.environ.get("WORLD_SIZE", 1)) - distributed = world_size > 1 - - if distributed: - # FOR DISTRIBUTED: Set the device according to local_rank. - torch.cuda.set_device(local_rank) - - # FOR DISTRIBUTED: Initialize the backend. torch.distributed.launch will provide - # environment variables, and requires that you use init_method=`env://`. - torch.distributed.init_process_group(backend="nccl", init_method="env://") - - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - train_ids, _, _ = get_data_ids( - config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" - ) - fold_generator = kfold_split( - train_ids, - n_splits=config.TEST.CV.N_SPLITS, - random_state=config.TEST.CV.SEED, - shuffle=config.TEST.CV.SHUFFLE, - ) - train_idx, val_idx = next(fold_generator) - val_ids = train_ids[val_idx] - train_ids = train_ids[train_idx] - - train_loader, val_loader = get_distributed_data_loaders( - train_ids, - val_ids, - config.TRAIN.BATCH_SIZE_PER_GPU, - config.TEST.BATCH_SIZE_PER_GPU, - config.TRAIN.FINE_SIZE, - config.TRAIN.PAD_LEFT, - config.TRAIN.PAD_RIGHT, - local_rank, - world_size, - config.DATASET.ROOT, - ) - - model = getattr(models, config.MODEL.NAME).get_seg_model(config) - criterion = CombinedLoss( - (lovasz_hinge, F.binary_cross_entropy_with_logits), config.LOSS.WEIGHTS - ) - - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" - model.to(device) - - optimizer = torch.optim.SGD( - model.parameters(), - lr=config.TRAIN.MAX_LR, - momentum=config.TRAIN.MOMENTUM, - weight_decay=config.TRAIN.WEIGHT_DECAY, - ) - - model, optimizer = amp.initialize(model, optimizer, opt_level="O0") - # FOR DISTRIBUTED: After amp.initialize, wrap the model with - # apex.parallel.DistributedDataParallel. - model = DistributedDataParallel(model, delay_allreduce=True) - - summary_writer = create_summary_writer(log_dir=config.LOG_DIR) - snapshot_duration = scheduler_step * len(train_loader) - warmup_duration = 5 * len(train_loader) - warmup_scheduler = LinearCyclicalScheduler( - optimizer, - "lr", - start_value=config.TRAIN.MAX_LR, - end_value=config.TRAIN.MAX_LR * world_size, - cycle_size=10 * len(train_loader), - ) - cosine_scheduler = CosineAnnealingScheduler( - optimizer, - "lr", - config.TRAIN.MAX_LR * world_size, - config.TRAIN.MIN_LR * world_size, - snapshot_duration, - ) - - scheduler = ConcatScheduler( - schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] - ) - - trainer = create_supervised_trainer_apex( - model, optimizer, criterion, prepare_train_batch, device=device - ) - - trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles - trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) - - adjust_loss_event = CustomPeriodicEvent(n_epochs=config.LOSS.ADJUST_EPOCH) - adjust_loss_event.attach(trainer) - trainer.add_event_handler( - getattr(adjust_loss_event.Events, "EPOCHS_{}_COMPLETED".format(config.LOSS.ADJUST_EPOCH)), - adjust_loss(criterion, config.LOSS.ADJUSTED_WEIGHTS), - ) - - if silence_other_ranks & local_rank != 0: - logging.getLogger("ignite.engine.engine.Engine").setLevel(logging.WARNING) - - evaluator = create_supervised_evaluator( - model, - prepare_val_batch, - metrics={ - "kaggle": apex.KaggleMetric( - output_transform=lambda x: (x["y_pred"], x["mask"]) - ), - "nll": apex.LossMetric( - lovasz_hinge, - world_size, - config.TEST.BATCH_SIZE_PER_GPU, - output_transform=lambda x: (x["y_pred"], x["mask"]), - ), - }, - device=device, - output_transform=padded_val_transform( - config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE - ), - ) - - # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) - - if local_rank == 0: # Run only on master process - - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), - ) - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), - ) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - logging_handlers.log_metrics( - "Validation results", - metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - tensorboard_handlers.log_metrics( - summary_writer, - trainer, - "epoch", - metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Mask", "mask"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Pred", "y_pred"), - ) - - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - - checkpoint_handler = SnapshotHandler( - config.OUTPUT_DIR, - config.MODEL.NAME, - snapshot_function, - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) - - logger = logging.getLogger(__name__) - logger.info("Starting training") - - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) - - -if __name__ == "__main__": - fire.Fire(run) diff --git a/experiments/interpretation/tgs_salt/distributed/configs/hrnet.yaml b/experiments/interpretation/tgs_salt/distributed/configs/hrnet.yaml deleted file mode 100644 index 738bc9f3..00000000 --- a/experiments/interpretation/tgs_salt/distributed/configs/hrnet.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# HRNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf - -DATASET: - NUM_CLASSES: 1 - ROOT: /mnt/tgssalt - -MODEL: - NAME: seg_hrnet - PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' - EXTRA: - FINAL_CONV_KERNEL: 1 - STAGE2: - NUM_MODULES: 1 - NUM_BRANCHES: 2 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - FUSE_METHOD: SUM - STAGE3: - NUM_MODULES: 4 - NUM_BRANCHES: 3 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - FUSE_METHOD: SUM - STAGE4: - NUM_MODULES: 3 - NUM_BRANCHES: 4 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - - 384 - FUSE_METHOD: SUM - -TRAIN: - BATCH_SIZE_PER_GPU: 32 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.01 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 - SNAPSHOTS: 5 - -TEST: - BATCH_SIZE_PER_GPU: 64 diff --git a/experiments/interpretation/tgs_salt/distributed/configs/unet.yaml b/experiments/interpretation/tgs_salt/distributed/configs/unet.yaml deleted file mode 100644 index 86415d38..00000000 --- a/experiments/interpretation/tgs_salt/distributed/configs/unet.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# UNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf - -DATASET: - NUM_CLASSES: 1 - ROOT: /mnt/tgssalt - -MODEL: - NAME: resnet_unet - -TRAIN: - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.01 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 - SNAPSHOTS: 5 - -TEST: - BATCH_SIZE_PER_GPU: 32 diff --git a/experiments/interpretation/tgs_salt/distributed/default.py b/experiments/interpretation/tgs_salt/distributed/default.py deleted file mode 100644 index 79e679b7..00000000 --- a/experiments/interpretation/tgs_salt/distributed/default.py +++ /dev/null @@ -1,89 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright (c) Microsoft -# Licensed under the MIT License. -# ------------------------------------------------------------------------------ - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -from yacs.config import CfgNode as CN - - -_C = CN() - -_C.OUTPUT_DIR = "" -_C.LOG_DIR = "" -_C.WORKERS = 4 -_C.PRINT_FREQ = 20 -_C.AUTO_RESUME = False -_C.PIN_MEMORY = True -_C.LOG_CONFIG = "logging.conf" - -# Cudnn related params -_C.CUDNN = CN() -_C.CUDNN.BENCHMARK = True -_C.CUDNN.DETERMINISTIC = False -_C.CUDNN.ENABLED = True - -# DATASET related params -_C.DATASET = CN() -_C.DATASET.ROOT = "/mnt/tgssalt" -_C.DATASET.NUM_CLASSES = 1 - -# common params for NETWORK -_C.MODEL = CN() -_C.MODEL.NAME = "seg_hrnet" -_C.MODEL.PRETRAINED = "" -_C.MODEL.EXTRA = CN(new_allowed=True) - -_C.LOSS = CN() -_C.LOSS.WEIGHTS = (0.01, 1) -_C.LOSS.ADJUST_EPOCH = 50 -_C.LOSS.ADJUSTED_WEIGHTS = (0.3, 0.7) - - -# training -_C.TRAIN = CN() -_C.TRAIN.MIN_LR = 0.001 -_C.TRAIN.MAX_LR = 0.01 -_C.TRAIN.MOMENTUM = 0.9 -_C.TRAIN.BEGIN_EPOCH = 0 -_C.TRAIN.END_EPOCH = 484 -_C.TRAIN.BATCH_SIZE_PER_GPU = 32 -_C.TRAIN.WEIGHT_DECAY = 0.0001 -_C.TRAIN.PAD_LEFT = 27 -_C.TRAIN.PAD_RIGHT = 27 -_C.TRAIN.FINE_SIZE = 202 -_C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.SAVE_LOCATION = "/tmp/models" - -# testing -_C.TEST = CN() -_C.TEST.BATCH_SIZE_PER_GPU = 32 -_C.TEST.CV = CN() -_C.TEST.CV.N_SPLITS = 5 -_C.TEST.CV.SEED = 42 -_C.TEST.CV.SHUFFLE = True - - -def update_config(cfg, options=None, config_file=None): - cfg.defrost() - - if config_file: - cfg.merge_from_file(config_file) - - if options: - cfg.merge_from_list(options) - - cfg.freeze() - - -if __name__ == "__main__": - import sys - - with open(sys.argv[1], "w") as f: - print(_C, file=f) - diff --git a/experiments/interpretation/tgs_salt/distributed/run.sh b/experiments/interpretation/tgs_salt/distributed/run.sh deleted file mode 100644 index d3f9b57a..00000000 --- a/experiments/interpretation/tgs_salt/distributed/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -export PYTHONPATH=/data/home/mat/repos/ignite_test:$PYTHONPATH -python -m torch.distributed.launch --nproc_per_node=8 train.py --cfg configs/unet.yaml \ No newline at end of file diff --git a/experiments/interpretation/tgs_salt/distributed/train.py b/experiments/interpretation/tgs_salt/distributed/train.py deleted file mode 100644 index 5955ba89..00000000 --- a/experiments/interpretation/tgs_salt/distributed/train.py +++ /dev/null @@ -1,291 +0,0 @@ -"""Train models on TGS salt dataset - -Trains models using PyTorch DistributedDataParallel -Uses a warmup schedule that then goes into a cyclic learning rate -Uses a weighted combination of Lovasz and BCE loss -""" - -import logging -import logging.config -import os - -import fire -import torch -import torch.nn.functional as F -from default import _C as config -from default import update_config -from ignite.contrib.handlers import ( - CustomPeriodicEvent, - CosineAnnealingScheduler, - LinearCyclicalScheduler, - ConcatScheduler, -) -from ignite.engine import Events -from toolz import curry - -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) - get_distributed_data_loaders, - kfold_split, - prepare_train_batch, - prepare_val_batch, -) - create_supervised_evaluator, - create_supervised_trainer, -) - - -@curry -def update_sampler_epoch(data_loader, engine): - data_loader.sampler.epoch = engine.state.epoch - - -class CombinedLoss: - """Creates a function that calculates weighted combined loss - """ - - def __init__(self, loss_functions, weights): - """Initialise CombinedLoss - - Args: - loss_functions (list): A list of PyTorch loss functions - weights (list[int]): A list of weights to use when combining loss functions - """ - self._losses = loss_functions - self.weights = weights - - def __call__(self, input, target): - # if weight is zero remove loss from calculations - loss_functions_and_weights = filter( - lambda l_w: l_w[1] > 0, zip(self._losses, self.weights) - ) - loss_list = [ - weight * loss(input, target) for loss, weight in loss_functions_and_weights - ] - combined_loss = torch.stack(loss_list).sum() - return combined_loss - - -@curry -def adjust_loss(loss_obj, weights, engine): - loss_obj.weights = weights - - -def run(*options, cfg=None, local_rank=0): - """Run training and validation of model - - Notes: - Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file - - Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py - cfg (str, optional): Location of config file to load. Defaults to None. - """ - - update_config(config, options=options, config_file=cfg) - silence_other_ranks = True - logging.config.fileConfig(config.LOG_CONFIG) - world_size = int(os.environ.get("WORLD_SIZE", 1)) - distributed = world_size > 1 - - if distributed: - # FOR DISTRIBUTED: Set the device according to local_rank. - torch.cuda.set_device(local_rank) - - # FOR DISTRIBUTED: Initialize the backend. torch.distributed.launch will provide - # environment variables, and requires that you use init_method=`env://`. - torch.distributed.init_process_group(backend="nccl", init_method="env://") - - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - train_ids, _, _ = get_data_ids( - config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" - ) - fold_generator = kfold_split( - train_ids, - n_splits=config.TEST.CV.N_SPLITS, - random_state=config.TEST.CV.SEED, - shuffle=config.TEST.CV.SHUFFLE, - ) - train_idx, val_idx = next(fold_generator) - val_ids = train_ids[val_idx] - train_ids = train_ids[train_idx] - - train_loader, val_loader = get_distributed_data_loaders( - train_ids, - val_ids, - config.TRAIN.BATCH_SIZE_PER_GPU, - config.TEST.BATCH_SIZE_PER_GPU, - config.TRAIN.FINE_SIZE, - config.TRAIN.PAD_LEFT, - config.TRAIN.PAD_RIGHT, - local_rank, - world_size, - config.DATASET.ROOT, - ) - - model = getattr(models, config.MODEL.NAME).get_seg_model(config) - criterion = CombinedLoss( - (lovasz_hinge, F.binary_cross_entropy_with_logits), config.LOSS.WEIGHTS - ) - - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" - model.to(device) - - optimizer = torch.optim.SGD( - model.parameters(), - lr=config.TRAIN.MAX_LR, - momentum=config.TRAIN.MOMENTUM, - weight_decay=config.TRAIN.WEIGHT_DECAY, - ) - - model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[device], find_unused_parameters=True) - - summary_writer = create_summary_writer(log_dir=config.LOG_DIR) - snapshot_duration = scheduler_step * len(train_loader) - warmup_duration = 5 * len(train_loader) - warmup_scheduler = LinearCyclicalScheduler( - optimizer, - "lr", - start_value=config.TRAIN.MAX_LR, - end_value=config.TRAIN.MAX_LR * world_size, - cycle_size=10 * len(train_loader), - ) - cosine_scheduler = CosineAnnealingScheduler( - optimizer, - "lr", - config.TRAIN.MAX_LR * world_size, - config.TRAIN.MIN_LR * world_size, - snapshot_duration, - ) - - scheduler = ConcatScheduler( - schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] - ) - - trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_train_batch, device=device - ) - - trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles - trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) - - adjust_loss_event = CustomPeriodicEvent(n_epochs=config.LOSS.ADJUST_EPOCH) - adjust_loss_event.attach(trainer) - trainer.add_event_handler( - getattr(adjust_loss_event.Events, "EPOCHS_{}_COMPLETED".format(config.LOSS.ADJUST_EPOCH)), - adjust_loss(criterion, config.LOSS.ADJUSTED_WEIGHTS), - ) - - if silence_other_ranks & local_rank != 0: - logging.getLogger("ignite.engine.engine.Engine").setLevel(logging.WARNING) - - evaluator = create_supervised_evaluator( - model, - prepare_val_batch, - metrics={ - "kaggle": apex.KaggleMetric( - output_transform=lambda x: (x["y_pred"], x["mask"]) - ), - "nll": apex.LossMetric( - lovasz_hinge, - world_size, - config.TEST.BATCH_SIZE_PER_GPU, - output_transform=lambda x: (x["y_pred"], x["mask"]), - ), - "pixa": apex.PixelwiseAccuracyMetric( - world_size, - config.TEST.BATCH_SIZE_PER_GPU, - output_transform=lambda x: (x["y_pred"], x["mask"]) - ), - }, - device=device, - output_transform=padded_val_transform( - config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE - ), - ) - - # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) - - if local_rank == 0: # Run only on master process - - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), - ) - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), - ) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - logging_handlers.log_metrics( - "Validation results", - metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - tensorboard_handlers.log_metrics( - summary_writer, - trainer, - "epoch", - metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Mask", "mask"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Pred", "y_pred"), - ) - - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - - checkpoint_handler = SnapshotHandler( - config.OUTPUT_DIR, - config.MODEL.NAME, - snapshot_function, - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) - - logger = logging.getLogger(__name__) - logger.info("Starting training") - - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) - - -if __name__ == "__main__": - fire.Fire(run) diff --git a/experiments/interpretation/tgs_salt/horovod/configs/hrnet.yaml b/experiments/interpretation/tgs_salt/horovod/configs/hrnet.yaml deleted file mode 100644 index b6082ed8..00000000 --- a/experiments/interpretation/tgs_salt/horovod/configs/hrnet.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# HRNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /workspace/logging.conf - -DATASET: - NUM_CLASSES: 1 - ROOT: /data/tgssalt - -MODEL: - NAME: seg_hrnet - PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' - EXTRA: - FINAL_CONV_KERNEL: 1 - STAGE2: - NUM_MODULES: 1 - NUM_BRANCHES: 2 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - FUSE_METHOD: SUM - STAGE3: - NUM_MODULES: 4 - NUM_BRANCHES: 3 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - FUSE_METHOD: SUM - STAGE4: - NUM_MODULES: 3 - NUM_BRANCHES: 4 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - - 384 - FUSE_METHOD: SUM - -TRAIN: - BATCH_SIZE_PER_GPU: 32 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.01 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 - SNAPSHOTS: 5 - -TEST: - BATCH_SIZE_PER_GPU: 64 diff --git a/experiments/interpretation/tgs_salt/horovod/configs/unet.yaml b/experiments/interpretation/tgs_salt/horovod/configs/unet.yaml deleted file mode 100644 index 858bc587..00000000 --- a/experiments/interpretation/tgs_salt/horovod/configs/unet.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# UNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /workspace/logging.conf - -DATASET: - NUM_CLASSES: 1 - ROOT: /data/tgssalt - -MODEL: - NAME: resnet_unet - -TRAIN: - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.01 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 - SNAPSHOTS: 5 - -TEST: - BATCH_SIZE_PER_GPU: 32 diff --git a/experiments/interpretation/tgs_salt/horovod/default.py b/experiments/interpretation/tgs_salt/horovod/default.py deleted file mode 100644 index 0bfb6faf..00000000 --- a/experiments/interpretation/tgs_salt/horovod/default.py +++ /dev/null @@ -1,94 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright (c) Microsoft -# Licensed under the MIT License. -# ------------------------------------------------------------------------------ - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -from yacs.config import CfgNode as CN - - -_C = CN() - -_C.OUTPUT_DIR = "" -_C.LOG_DIR = "" -_C.WORKERS = 4 -_C.PRINT_FREQ = 20 -_C.AUTO_RESUME = False -_C.PIN_MEMORY = True -_C.LOG_CONFIG = "logging.conf" -_C.SEED = 42 - -# Cudnn related params -_C.CUDNN = CN() -_C.CUDNN.BENCHMARK = True -_C.CUDNN.DETERMINISTIC = False -_C.CUDNN.ENABLED = True - -# HOROVOD related params -_C.HOROVOD = CN() -_C.HOROVOD.FP16 = False - -# DATASET related params -_C.DATASET = CN() -_C.DATASET.ROOT = "/mnt/tgssalt" -_C.DATASET.NUM_CLASSES = 1 - -# common params for NETWORK -_C.MODEL = CN() -_C.MODEL.NAME = "seg_hrnet" -_C.MODEL.PRETRAINED = "" -_C.MODEL.EXTRA = CN(new_allowed=True) - -_C.LOSS = CN() -_C.LOSS.WEIGHTS = (0.01, 1) -_C.LOSS.ADJUST_EPOCH = 50 -_C.LOSS.ADJUSTED_WEIGHTS = (0.3, 0.7) - - -# training -_C.TRAIN = CN() -_C.TRAIN.MIN_LR = 0.001 -_C.TRAIN.MAX_LR = 0.01 -_C.TRAIN.MOMENTUM = 0.9 -_C.TRAIN.BEGIN_EPOCH = 0 -_C.TRAIN.END_EPOCH = 484 -_C.TRAIN.BATCH_SIZE_PER_GPU = 32 -_C.TRAIN.WEIGHT_DECAY = 0.0001 -_C.TRAIN.PAD_LEFT = 27 -_C.TRAIN.PAD_RIGHT = 27 -_C.TRAIN.FINE_SIZE = 202 -_C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.SAVE_LOCATION = "/tmp/models" - -# testing -_C.TEST = CN() -_C.TEST.BATCH_SIZE_PER_GPU = 32 -_C.TEST.CV = CN() -_C.TEST.CV.N_SPLITS = 5 -_C.TEST.CV.SEED = 42 -_C.TEST.CV.SHUFFLE = True - - -def update_config(cfg, options=None, config_file=None): - cfg.defrost() - - if config_file: - cfg.merge_from_file(config_file) - - if options: - cfg.merge_from_list(options) - - cfg.freeze() - - -if __name__ == "__main__": - import sys - - with open(sys.argv[1], "w") as f: - print(_C, file=f) - diff --git a/experiments/interpretation/tgs_salt/horovod/run.sh b/experiments/interpretation/tgs_salt/horovod/run.sh deleted file mode 100644 index 5fa42adb..00000000 --- a/experiments/interpretation/tgs_salt/horovod/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -export PYTHONPATH=/workspace:$PYTHONPATH -mpirun -np 8 -bind-to none -map-by slot -x NCCL_DEBUG=INFO -x LD_LIBRARY_PATH -x PATH -mca pml ob1 -mca btl ^openib python train.py --cfg "/workspace/experiments/segmentation/tgs_salt/horovod/configs/unet.yaml" \ No newline at end of file diff --git a/experiments/interpretation/tgs_salt/horovod/train.py b/experiments/interpretation/tgs_salt/horovod/train.py deleted file mode 100644 index 26a6ecf1..00000000 --- a/experiments/interpretation/tgs_salt/horovod/train.py +++ /dev/null @@ -1,291 +0,0 @@ -"""Train models on TGS salt dataset - -Trains models using PyTorch DistributedDataParallel -Uses a warmup schedule that then goes into a cyclic learning rate -Uses a weighted combination of Lovasz and BCE loss -""" - -import logging -import logging.config - -import fire -import horovod.torch as hvd -import torch -import torch.nn.functional as F -from default import _C as config -from default import update_config -from ignite.contrib.handlers import ( - CustomPeriodicEvent, - CosineAnnealingScheduler, - LinearCyclicalScheduler, - ConcatScheduler, -) -from ignite.engine import Events -from toolz import curry - -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) - get_distributed_data_loaders, - kfold_split, - prepare_train_batch, - prepare_val_batch, -) - create_supervised_evaluator, - create_supervised_trainer, -) - - -@curry -def update_sampler_epoch(data_loader, engine): - data_loader.sampler.epoch = engine.state.epoch - - -class CombinedLoss: - """Creates a function that calculates weighted combined loss - """ - - def __init__(self, loss_functions, weights): - """Initialise CombinedLoss - - Args: - loss_functions (list): A list of PyTorch loss functions - weights (list[int]): A list of weights to use when combining loss functions - """ - self._losses = loss_functions - self.weights = weights - - def __call__(self, input, target): - # if weight is zero remove loss from calculations - loss_functions_and_weights = filter( - lambda l_w: l_w[1] > 0, zip(self._losses, self.weights) - ) - loss_list = [ - weight * loss(input, target) for loss, weight in loss_functions_and_weights - ] - combined_loss = torch.stack(loss_list).sum() - return combined_loss - - -@curry -def adjust_loss(loss_obj, weights, engine): - loss_obj.weights = weights - - -def run(*options, cfg=None): - """Run training and validation of model - - Notes: - Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file - - Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py - cfg (str, optional): Location of config file to load. Defaults to None. - """ - - update_config(config, options=options, config_file=cfg) - hvd.init() - silence_other_ranks = True - logging.config.fileConfig(config.LOG_CONFIG) - - torch.manual_seed(config.SEED) - torch.cuda.set_device(hvd.local_rank()) - torch.cuda.manual_seed(config.SEED) - rank, world_size = hvd.rank(), hvd.size() - - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - train_ids, _, _ = get_data_ids( - config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" - ) - fold_generator = kfold_split( - train_ids, - n_splits=config.TEST.CV.N_SPLITS, - random_state=config.TEST.CV.SEED, - shuffle=config.TEST.CV.SHUFFLE, - ) - train_idx, val_idx = next(fold_generator) - val_ids = train_ids[val_idx] - train_ids = train_ids[train_idx] - - train_loader, val_loader = get_distributed_data_loaders( - train_ids, - val_ids, - config.TRAIN.BATCH_SIZE_PER_GPU, - config.TEST.BATCH_SIZE_PER_GPU, - config.TRAIN.FINE_SIZE, - config.TRAIN.PAD_LEFT, - config.TRAIN.PAD_RIGHT, - rank, - world_size, - config.DATASET.ROOT, - ) - - model = getattr(models, config.MODEL.NAME).get_seg_model(config) - criterion = CombinedLoss( - (lovasz_hinge, F.binary_cross_entropy_with_logits), config.LOSS.WEIGHTS - ) - - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" - - optimizer = torch.optim.SGD( - model.parameters(), - lr=config.TRAIN.MAX_LR, - momentum=config.TRAIN.MOMENTUM, - weight_decay=config.TRAIN.WEIGHT_DECAY, - ) - - # Horovod: broadcast parameters & optimizer state. - hvd.broadcast_parameters(model.state_dict(), root_rank=0) - hvd.broadcast_optimizer_state(optimizer, root_rank=0) - - # Horovod: (optional) compression algorithm. - compression = hvd.Compression.fp16 if config.HOROVOD.FP16 else hvd.Compression.none - - # Horovod: wrap optimizer with DistributedOptimizer. - optimizer = hvd.DistributedOptimizer(optimizer, - named_parameters=model.named_parameters(), - compression=compression) - - summary_writer = create_summary_writer(log_dir=config.LOG_DIR) - snapshot_duration = scheduler_step * len(train_loader) - warmup_duration = 5 * len(train_loader) - warmup_scheduler = LinearCyclicalScheduler( - optimizer, - "lr", - start_value=config.TRAIN.MAX_LR, - end_value=config.TRAIN.MAX_LR * world_size, - cycle_size=10 * len(train_loader), - ) - cosine_scheduler = CosineAnnealingScheduler( - optimizer, - "lr", - config.TRAIN.MAX_LR * world_size, - config.TRAIN.MIN_LR * world_size, - snapshot_duration, - ) - - scheduler = ConcatScheduler( - schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] - ) - - trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_train_batch, device=device - ) - - trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles - trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) - - adjust_loss_event = CustomPeriodicEvent(n_epochs=config.LOSS.ADJUST_EPOCH) - adjust_loss_event.attach(trainer) - trainer.add_event_handler( - getattr(adjust_loss_event.Events, "EPOCHS_{}_COMPLETED".format(config.LOSS.ADJUST_EPOCH)), - adjust_loss(criterion, config.LOSS.ADJUSTED_WEIGHTS), - ) - - if silence_other_ranks & rank != 0: - logging.getLogger("ignite.engine.engine.Engine").setLevel(logging.WARNING) - - evaluator = create_supervised_evaluator( - model, - prepare_val_batch, - metrics={ - "kaggle": horovod.KaggleMetric( - output_transform=lambda x: (x["y_pred"], x["mask"]) - ), - "nll": horovod.LossMetric( - lovasz_hinge, - world_size, - config.TEST.BATCH_SIZE_PER_GPU, - output_transform=lambda x: (x["y_pred"], x["mask"]), - ), - }, - device=device, - output_transform=padded_val_transform( - config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE - ), - ) - - # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) - - if rank == 0: # Run only on master process - - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), - ) - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), - ) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - logging_handlers.log_metrics( - "Validation results", - metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - tensorboard_handlers.log_metrics( - summary_writer, - trainer, - "epoch", - metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Mask", "mask"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Pred", "y_pred"), - ) - - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - - checkpoint_handler = SnapshotHandler( - config.OUTPUT_DIR, - config.MODEL.NAME, - snapshot_function, - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) - - logger = logging.getLogger(__name__) - logger.info("Starting training") - - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) - - -if __name__ == "__main__": - fire.Fire(run) diff --git a/experiments/interpretation/tgs_salt/local/configs/hrnet.yaml b/experiments/interpretation/tgs_salt/local/configs/hrnet.yaml deleted file mode 100644 index 81e8aca0..00000000 --- a/experiments/interpretation/tgs_salt/local/configs/hrnet.yaml +++ /dev/null @@ -1,75 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf - -DATASET: - NUM_CLASSES: 1 - ROOT: /mnt/tgssalt - -MODEL: - NAME: seg_hrnet - PRETRAINED: 'pretrained_models/hrnetv2_w48_imagenet_pretrained.pth' - EXTRA: - FINAL_CONV_KERNEL: 1 - STAGE2: - NUM_MODULES: 1 - NUM_BRANCHES: 2 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - FUSE_METHOD: SUM - STAGE3: - NUM_MODULES: 4 - NUM_BRANCHES: 3 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - FUSE_METHOD: SUM - STAGE4: - NUM_MODULES: 3 - NUM_BRANCHES: 4 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - - 384 - FUSE_METHOD: SUM - -TRAIN: - BATCH_SIZE_PER_GPU: 32 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.01 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 - SNAPSHOTS: 5 - -TEST: - BATCH_SIZE_PER_GPU: 64 diff --git a/experiments/interpretation/tgs_salt/local/configs/unet.yaml b/experiments/interpretation/tgs_salt/local/configs/unet.yaml deleted file mode 100644 index 1fdcd49c..00000000 --- a/experiments/interpretation/tgs_salt/local/configs/unet.yaml +++ /dev/null @@ -1,34 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/ignite_test/logging.conf - -DATASET: - NUM_CLASSES: 1 - ROOT: /mnt/tgssalt - -MODEL: - NAME: unet - - -TRAIN: - BATCH_SIZE_PER_GPU: 32 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.1 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - PAD_LEFT: 27 - PAD_RIGHT: 27 - FINE_SIZE: 202 - SNAPSHOTS: 5 - -TEST: - BATCH_SIZE_PER_GPU: 64 diff --git a/experiments/interpretation/tgs_salt/local/default.py b/experiments/interpretation/tgs_salt/local/default.py deleted file mode 100644 index 535ad03b..00000000 --- a/experiments/interpretation/tgs_salt/local/default.py +++ /dev/null @@ -1,88 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright (c) Microsoft -# Licensed under the MIT License. -# ------------------------------------------------------------------------------ - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -from yacs.config import CfgNode as CN - - -_C = CN() - -_C.OUTPUT_DIR = "" -_C.LOG_DIR = "" -_C.GPUS = (0,) -_C.WORKERS = 4 -_C.PRINT_FREQ = 20 -_C.AUTO_RESUME = False -_C.PIN_MEMORY = True -_C.LOG_CONFIG = "logging.conf" -_C.SEED = 42 - -# Cudnn related params -_C.CUDNN = CN() -_C.CUDNN.BENCHMARK = True -_C.CUDNN.DETERMINISTIC = False -_C.CUDNN.ENABLED = True - -# DATASET related params -_C.DATASET = CN() -_C.DATASET.ROOT = "/mnt/tgssalt" -_C.DATASET.NUM_CLASSES = 1 - - -# common params for NETWORK -_C.MODEL = CN() -_C.MODEL.NAME = "seg_hrnet" -_C.MODEL.PRETRAINED = "" -_C.MODEL.EXTRA = CN(new_allowed=True) - - -# training -_C.TRAIN = CN() -_C.TRAIN.MIN_LR = 0.001 -_C.TRAIN.MAX_LR = 0.01 -_C.TRAIN.MOMENTUM = 0.9 -_C.TRAIN.BEGIN_EPOCH = 0 -_C.TRAIN.END_EPOCH = 484 -_C.TRAIN.BATCH_SIZE_PER_GPU = 32 -_C.TRAIN.WEIGHT_DECAY = 0.0001 -_C.TRAIN.PAD_LEFT = 27 -_C.TRAIN.PAD_RIGHT = 27 -_C.TRAIN.FINE_SIZE = 202 -_C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.SAVE_LOCATION = "/tmp/models" - -# testing -_C.TEST = CN() -_C.TEST.BATCH_SIZE_PER_GPU = 32 -_C.TEST.CV = CN() -_C.TEST.CV.N_SPLITS = 5 -_C.TEST.CV.SEED = 42 -_C.TEST.CV.SHUFFLE = True - - - -def update_config(cfg, options=None, config_file=None): - cfg.defrost() - - if config_file: - cfg.merge_from_file(config_file) - - if options: - cfg.merge_from_list(options) - - cfg.freeze() - - -if __name__ == "__main__": - import sys - - with open(sys.argv[1], "w") as f: - print(_C, file=f) - diff --git a/experiments/interpretation/tgs_salt/local/run.sh b/experiments/interpretation/tgs_salt/local/run.sh deleted file mode 100644 index df3fd99e..00000000 --- a/experiments/interpretation/tgs_salt/local/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -export PYTHONPATH=/data/home/mat/repos/DeepSeismic/interpretation:$PYTHONPATH -python train.py --cfg "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/tgs_salt/local/configs/unet.yaml" \ No newline at end of file diff --git a/experiments/interpretation/tgs_salt/local/train.py b/experiments/interpretation/tgs_salt/local/train.py deleted file mode 100644 index 3329f841..00000000 --- a/experiments/interpretation/tgs_salt/local/train.py +++ /dev/null @@ -1,179 +0,0 @@ -""" Train model on TGS Salt Dataset - -""" - -import logging -import logging.config - -import fire -import torch -from default import _C as config -from default import update_config -from ignite.contrib.handlers import CosineAnnealingScheduler -from ignite.engine import Events -from ignite.metrics import Loss - -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) - get_data_loaders, - kfold_split, - prepare_train_batch, - prepare_val_batch, -) - create_supervised_evaluator, - create_supervised_trainer, -) - - -def run(*options, cfg=None): - """Run training and validation of model - - Notes: - Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file - - Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py - cfg (str, optional): Location of config file to load. Defaults to None. - """ - - update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - train_ids, _, _ = get_data_ids( - config.DATASET.ROOT, train_csv="train.csv", depths_csv="depths.csv" - ) - fold_generator = kfold_split( - train_ids, - n_splits=config.TEST.CV.N_SPLITS, - random_state=config.TEST.CV.SEED, - shuffle=config.TEST.CV.SHUFFLE, - ) - train_idx, val_idx = next(fold_generator) - val_ids = train_ids[val_idx] - train_ids = train_ids[train_idx] - train_loader, val_loader = get_data_loaders( - train_ids, - val_ids, - config.TRAIN.BATCH_SIZE_PER_GPU, - config.TEST.BATCH_SIZE_PER_GPU, - config.TRAIN.FINE_SIZE, - config.TRAIN.PAD_LEFT, - config.TRAIN.PAD_RIGHT, - config.DATASET.ROOT, - ) - - model = getattr(models, config.MODEL.NAME).get_seg_model(config) - criterion = torch.nn.BCEWithLogitsLoss() - - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" - - optimizer = torch.optim.SGD( - model.parameters(), - lr=config.TRAIN.MAX_LR, - momentum=config.TRAIN.MOMENTUM, - weight_decay=config.TRAIN.WEIGHT_DECAY, - ) - - summary_writer = create_summary_writer(log_dir=config.LOG_DIR) - snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration - ) - - trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_train_batch, device=device - ) - - trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) - trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), - ) - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), - ) - - evaluator = create_supervised_evaluator( - model, - prepare_val_batch, - metrics={ - "kaggle": KaggleMetric(output_transform=lambda x: (x["y_pred"], x["mask"])), - "nll": Loss(criterion, output_transform=lambda x: (x["y_pred"], x["mask"])), - "pixa": PixelwiseAccuracy(output_transform=lambda x: (x["y_pred"], x["mask"])) - }, - device=device, - output_transform=padded_val_transform(config.TRAIN.PAD_LEFT, config.TRAIN.FINE_SIZE), - ) - - # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - logging_handlers.log_metrics( - "Validation results", - metrics_dict={"kaggle": "Kaggle :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - tensorboard_handlers.log_metrics( - summary_writer, - trainer, - "epoch", - metrics_dict={"kaggle": "Validation/Kaggle", "nll": "Validation/Loss"}, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Mask", "mask"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Pred", "y_pred"), - ) - - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - - checkpoint_handler = SnapshotHandler( - config.OUTPUT_DIR, - config.MODEL.NAME, - snapshot_function, - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) - - logger = logging.getLogger(__name__) - logger.info("Starting training") - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) - - -if __name__ == "__main__": - fire.Fire(run) From 385be38e5189ef7a0e5352763caa25e8160b815e Mon Sep 17 00:00:00 2001 From: Vanja Paunic Date: Fri, 1 Nov 2019 15:15:02 +0000 Subject: [PATCH 066/207] Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 --- README.md | 29 ++++++- .../penobscot/local/configs/hrnet.yaml | 6 +- .../local/configs/patch_deconvnet.yaml | 64 -------------- .../local/configs/patch_deconvnet_skip.yaml | 63 -------------- .../local/configs/seresnet_unet.yaml | 6 +- .../penobscot/local/configs/unet.yaml | 68 --------------- .../interpretation/penobscot/local/default.py | 47 +++++----- .../interpretation/penobscot/local/test.sh | 2 +- .../interpretation/penobscot/local/train.sh | 2 +- .../penobscot/data.py | 86 +++++++------------ .../prepare_dutchf3.py | 70 +++++++-------- .../prepare_penobscot.py | 39 ++++----- 12 files changed, 139 insertions(+), 343 deletions(-) delete mode 100644 experiments/interpretation/penobscot/local/configs/patch_deconvnet.yaml delete mode 100644 experiments/interpretation/penobscot/local/configs/patch_deconvnet_skip.yaml delete mode 100644 experiments/interpretation/penobscot/local/configs/unet.yaml rename experiments/interpretation/dutchf3_patch/prepare_data.py => scripts/prepare_dutchf3.py (85%) rename experiments/interpretation/penobscot/prepare_data.py => scripts/prepare_penobscot.py (78%) diff --git a/README.md b/README.md index 8cccbace..022076bc 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,37 @@ We present results of algorithms which are based on pixel-level annotations, whe ### Data #### Netherlands F3 +To download the F3 Netherlands dataset for 2D experiments, please follow the data download instructions at +[this github repository](https://github.com/olivesgatech/facies_classification_benchmark). + +To prepare the data for the experiments (e.g. split into train/val/test), please run the following script: + +``` +# For section-based experiments +python scripts/prepare_dutchf3.py split_train_val section --data-dir=/mnt/dutchf3 + + +# For patch-based experiments +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutchf3 --stride=50 --patch=100 + +``` + +Refer to the script itself for more argument options. #### Penobscot -To download the Penobscot dataset run the [download_penobscot.sh](scripts/download_penobscot.sh) script +To download the Penobscot dataset run the [download_penobscot.sh](scripts/download_penobscot.sh) script, e.g. +``` +./download_penobscot.sh /data/penobscot +``` + +Note that the specified download location (e.g `/data/penobscot`) should be created beforehand, and configured appropriate `write` pemissions. + +To prepare the data for the experiments (e.g. split into train/val/test), please run the following script (modifying arguments as desired): + +``` +python scripts/prepare_penobscot.py split_inline --data-dir=/mnt/penobscot --val-ratio=.1 --test-ratio=.2 +``` ### Scripts diff --git a/experiments/interpretation/penobscot/local/configs/hrnet.yaml b/experiments/interpretation/penobscot/local/configs/hrnet.yaml index 9389a39a..0d42c1ad 100644 --- a/experiments/interpretation/penobscot/local/configs/hrnet.yaml +++ b/experiments/interpretation/penobscot/local/configs/hrnet.yaml @@ -7,13 +7,13 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: /home/vapaunic/repos/DeepSeismic/logging.conf SEED: 2019 DATASET: NUM_CLASSES: 7 - ROOT: /data/penobscot + ROOT: /mnt/penobscot CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] INLINE_HEIGHT: 1501 INLINE_WIDTH: 481 @@ -75,7 +75,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" #"patch" # Options are none, patch and section + DEPTH: "patch" # Options are none, patch and section STRIDE: 64 PATCH_SIZE: 128 AUGMENTATIONS: diff --git a/experiments/interpretation/penobscot/local/configs/patch_deconvnet.yaml b/experiments/interpretation/penobscot/local/configs/patch_deconvnet.yaml deleted file mode 100644 index 86315a39..00000000 --- a/experiments/interpretation/penobscot/local/configs/patch_deconvnet.yaml +++ /dev/null @@ -1,64 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf -SEED: 2019 - - -DATASET: - NUM_CLASSES: 7 - ROOT: /data/penobscot - CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] - INLINE_HEIGHT: 1501 - INLINE_WIDTH: 481 - -MODEL: - NAME: patch_deconvnet - IN_CHANNELS: 1 - - -TRAIN: - COMPLETE_PATCHES_ONLY: False - BATCH_SIZE_PER_GPU: 64 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "none" # Options are non, patch and section - STRIDE: 50 - PATCH_SIZE: 99 - AUGMENTATIONS: - RESIZE: - HEIGHT: 99 - WIDTH: 99 - PAD: - HEIGHT: 99 - WIDTH: 99 - MEAN: -6.0668 # -6.0668283 # The statistics are estimated from a filtered value that are less than 10000 as dataset contains outliers - STD: 1367.8212 # 1367.8212 - MAX: 10000 # The possible max is actually 32767 but the dataset contains outliers so limiting to 10000 - MODEL_DIR: "models" -VALIDATION: - BATCH_SIZE_PER_GPU: 512 - COMPLETE_PATCHES_ONLY: False - -TEST: - COMPLETE_PATCHES_ONLY: False - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" - AUGMENTATIONS: - RESIZE: - HEIGHT: 99 - WIDTH: 99 - PAD: - HEIGHT: 99 - WIDTH: 99 diff --git a/experiments/interpretation/penobscot/local/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/penobscot/local/configs/patch_deconvnet_skip.yaml deleted file mode 100644 index 16f680a5..00000000 --- a/experiments/interpretation/penobscot/local/configs/patch_deconvnet_skip.yaml +++ /dev/null @@ -1,63 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf -SEED: 2019 - -DATASET: - NUM_CLASSES: 7 - ROOT: /data/penobscot - CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] - INLINE_HEIGHT: 1501 - INLINE_WIDTH: 481 - -MODEL: - NAME: patch_deconvnet_skip - IN_CHANNELS: 1 - -TRAIN: - COMPLETE_PATCHES_ONLY: False - BATCH_SIZE_PER_GPU: 64 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "none" #"patch" # Options are none, patch and section - STRIDE: 50 - PATCH_SIZE: 99 - AUGMENTATIONS: - RESIZE: - HEIGHT: 99 - WIDTH: 99 - PAD: - HEIGHT: 99 - WIDTH: 99 - MEAN: -6.0668 # -6.0668283 # The statistics are estimated from a filtered value that are less than 10000 as dataset contains outliers - STD: 1367.8212 # 1367.8212 - MAX: 10000 # The possible max is actually 32767 but the dataset contains outliers so limiting to 10000 - MODEL_DIR: "models" - -VALIDATION: - BATCH_SIZE_PER_GPU: 512 - COMPLETE_PATCHES_ONLY: False - -TEST: - COMPLETE_PATCHES_ONLY: False - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" - AUGMENTATIONS: - RESIZE: - HEIGHT: 99 - WIDTH: 99 - PAD: - HEIGHT: 99 - WIDTH: 99 diff --git a/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml b/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml index e0457122..36b315a7 100644 --- a/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml +++ b/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml @@ -7,13 +7,13 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: /home/vapaunic/repos/DeepSeismic/logging.conf SEED: 2019 DATASET: NUM_CLASSES: 7 - ROOT: /data/penobscot + ROOT: /mnt/penobscot CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] INLINE_HEIGHT: 1501 INLINE_WIDTH: 481 @@ -32,7 +32,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" #"patch" # Options are none, patch and section + DEPTH: "patch" # Options are none, patch and section STRIDE: 64 PATCH_SIZE: 128 AUGMENTATIONS: diff --git a/experiments/interpretation/penobscot/local/configs/unet.yaml b/experiments/interpretation/penobscot/local/configs/unet.yaml deleted file mode 100644 index 7ee89169..00000000 --- a/experiments/interpretation/penobscot/local/configs/unet.yaml +++ /dev/null @@ -1,68 +0,0 @@ -# UNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf -SEED: 2019 - - -DATASET: - NUM_CLASSES: 7 - ROOT: /data/penobscot - CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] - INLINE_HEIGHT: 1501 - INLINE_WIDTH: 481 - -MODEL: - NAME: resnet_unet - IN_CHANNELS: 3 - - -TRAIN: - COMPLETE_PATCHES_ONLY: False - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "none" #"patch" # Options are none, patch and section - STRIDE: 50 - PATCH_SIZE: 100 - AUGMENTATIONS: - RESIZE: - HEIGHT: 200 - WIDTH: 200 - PAD: - HEIGHT: 256 - WIDTH: 256 - MEAN: -6.0668 # -6.0668283 # The statistics are estimated from a filtered value that are less than 10000 as dataset contains outliers - STD: 1367.8212 # 1367.8212 - MAX: 10000 # The possible max is actually 32767 but the dataset contains outliers so limiting to 10000 - MODEL_DIR: "models" - - -VALIDATION: - BATCH_SIZE_PER_GPU: 32 - COMPLETE_PATCHES_ONLY: False - -TEST: - COMPLETE_PATCHES_ONLY: False - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" - AUGMENTATIONS: - RESIZE: - HEIGHT: 200 - WIDTH: 200 - PAD: - HEIGHT: 256 - WIDTH: 256 diff --git a/experiments/interpretation/penobscot/local/default.py b/experiments/interpretation/penobscot/local/default.py index f4999678..a15615f2 100644 --- a/experiments/interpretation/penobscot/local/default.py +++ b/experiments/interpretation/penobscot/local/default.py @@ -11,7 +11,9 @@ _C = CN() -_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models +_C.OUTPUT_DIR = ( + "output" +) # This will be the base directory for all output, such as logs and saved models _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.GPUS = (0,) _C.WORKERS = 4 @@ -35,44 +37,49 @@ _C.DATASET = CN() _C.DATASET.ROOT = "" _C.DATASET.NUM_CLASSES = 7 -_C.DATASET.CLASS_WEIGHTS = [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] +_C.DATASET.CLASS_WEIGHTS = [ + 0.02630481, + 0.05448931, + 0.0811898, + 0.01866496, + 0.15868563, + 0.0875993, + 0.5730662, +] _C.DATASET.INLINE_HEIGHT = 1501 _C.DATASET.INLINE_WIDTH = 481 # common params for NETWORK _C.MODEL = CN() -_C.MODEL.NAME = "patch_deconvnet" +_C.MODEL.NAME = "resnet_unet" _C.MODEL.IN_CHANNELS = 1 -_C.MODEL.PRETRAINED = "" -_C.MODEL.EXTRA = CN(new_allowed=True) - # training _C.TRAIN = CN() -_C.TRAIN.COMPLETE_PATCHES_ONLY = False +_C.TRAIN.COMPLETE_PATCHES_ONLY = True _C.TRAIN.MIN_LR = 0.001 _C.TRAIN.MAX_LR = 0.01 _C.TRAIN.MOMENTUM = 0.9 _C.TRAIN.BEGIN_EPOCH = 0 -_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.END_EPOCH = 300 _C.TRAIN.BATCH_SIZE_PER_GPU = 32 _C.TRAIN.WEIGHT_DECAY = 0.0001 _C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR _C.TRAIN.AUGMENTATION = True -_C.TRAIN.STRIDE = 50 -_C.TRAIN.PATCH_SIZE = 99 -_C.TRAIN.MEAN = [0.0009997] # 0.0009996710808862074 -_C.TRAIN.STD = [0.20977] # 0.20976548783479299 -_C.TRAIN.MAX = 255 -_C.TRAIN.DEPTH = 'no' # Options are None, Patch and Section +_C.TRAIN.STRIDE = 64 +_C.TRAIN.PATCH_SIZE = 128 +_C.TRAIN.MEAN = [-0.0001777, 0.49, -0.0000688] # 0.0009996710808862074 +_C.TRAIN.STD = [0.14076, 0.2717, 0.06286] # 0.20976548783479299 +_C.TRAIN.MAX = 1 +_C.TRAIN.DEPTH = "patch" # Options are none, patch and section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 _C.TRAIN.AUGMENTATIONS = CN() _C.TRAIN.AUGMENTATIONS.RESIZE = CN() -_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 -_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 256 +_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 256 _C.TRAIN.AUGMENTATIONS.PAD = CN() _C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 _C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 @@ -80,7 +87,7 @@ # validation _C.VALIDATION = CN() _C.VALIDATION.BATCH_SIZE_PER_GPU = 32 -_C.VALIDATION.COMPLETE_PATCHES_ONLY = False +_C.VALIDATION.COMPLETE_PATCHES_ONLY = True # TEST _C.TEST = CN() @@ -88,8 +95,8 @@ _C.TEST.COMPLETE_PATCHES_ONLY = True _C.TEST.AUGMENTATIONS = CN() _C.TEST.AUGMENTATIONS.RESIZE = CN() -_C.TEST.AUGMENTATIONS.RESIZE.HEIGHT = 200 -_C.TEST.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TEST.AUGMENTATIONS.RESIZE.HEIGHT = 256 +_C.TEST.AUGMENTATIONS.RESIZE.WIDTH = 256 _C.TEST.AUGMENTATIONS.PAD = CN() _C.TEST.AUGMENTATIONS.PAD.HEIGHT = 256 _C.TEST.AUGMENTATIONS.PAD.WIDTH = 256 diff --git a/experiments/interpretation/penobscot/local/test.sh b/experiments/interpretation/penobscot/local/test.sh index a497e127..ad68cf2e 100755 --- a/experiments/interpretation/penobscot/local/test.sh +++ b/experiments/interpretation/penobscot/local/test.sh @@ -1,2 +1,2 @@ #!/bin/bash -python test.py --cfg "configs/hrnet.yaml" \ No newline at end of file +python test.py --cfg "configs/seresnet_unet.yaml" \ No newline at end of file diff --git a/experiments/interpretation/penobscot/local/train.sh b/experiments/interpretation/penobscot/local/train.sh index e826c10c..eb885b98 100755 --- a/experiments/interpretation/penobscot/local/train.sh +++ b/experiments/interpretation/penobscot/local/train.sh @@ -1,2 +1,2 @@ #!/bin/bash -python train.py --cfg "configs/patch_deconvnet_skip.yaml" \ No newline at end of file +python train.py --cfg "configs/seresnet_unet.yaml" \ No newline at end of file diff --git a/interpretation/deepseismic_interpretation/penobscot/data.py b/interpretation/deepseismic_interpretation/penobscot/data.py index 53e4edd1..f0b64dd4 100644 --- a/interpretation/deepseismic_interpretation/penobscot/data.py +++ b/interpretation/deepseismic_interpretation/penobscot/data.py @@ -28,18 +28,14 @@ def _pad_right_and_bottom(pad_size, numpy_array, pad_value=255): len(numpy_array.shape) == 2 ), f"_pad_right_and_bottom only accepts 2D arrays. Input is {len(numpy_array.shape)}D" return np.pad( - numpy_array, - pad_width=[(0, pad_size), (0, pad_size)], - constant_values=pad_value, + numpy_array, pad_width=[(0, pad_size), (0, pad_size)], constant_values=pad_value ) def _get_classes_and_counts(mask_list): class_counts_dict = defaultdict(int) for mask in mask_list: - for class_label, class_count in zip( - *np.unique(mask, return_counts=True) - ): + for class_label, class_count in zip(*np.unique(mask, return_counts=True)): class_counts_dict[class_label] += class_count return list(class_counts_dict.keys()), list(class_counts_dict.values()) @@ -66,9 +62,7 @@ def _combine_classes(mask_array_list): def _replicate_channels(image_array, n_channels): - new_image_array = np.zeros( - (n_channels, image_array.shape[0], image_array.shape[1]) - ) + new_image_array = np.zeros((n_channels, image_array.shape[0], image_array.shape[1])) for i in range(n_channels): new_image_array[i] = image_array return new_image_array @@ -92,9 +86,7 @@ def _generate_images_and_masks(images_iter, mask_dir): ) -def _number_patches_in( - height_or_width, patch_size, stride, complete_patches_only=True -): +def _number_patches_in(height_or_width, patch_size, stride, complete_patches_only=True): strides_in_hw = (height_or_width - patch_size) / stride if complete_patches_only: return int(np.floor(strides_in_hw)) @@ -111,9 +103,7 @@ def _is_3D(numpy_array): @curry -def _extract_patches( - patch_size, stride, complete_patches_only, img_array, mask_array -): +def _extract_patches(patch_size, stride, complete_patches_only, img_array, mask_array): height, width = img_array.shape[-2], img_array.shape[-1] num_h_patches = _number_patches_in( height, patch_size, stride, complete_patches_only=complete_patches_only @@ -197,7 +187,7 @@ def _transform_HWC_to_CHW(numpy_array): def _rescale(numpy_array): - """ Rescale the numpy array by 10000. The maximum value achievable is 32737 + """ Rescale the numpy array by 10000. The maximum value achievable is 32737. This will bring the values between -n and n """ return numpy_array / 10000 @@ -210,9 +200,6 @@ class PenobscotInlinePatchDataset(VisionDataset): Loads inlines only and splits into patches """ - _open_image = compose(_rescale, _open_to_array) - _open_mask = _open_to_array - def __init__( self, root, @@ -239,9 +226,7 @@ def __init__( complete_patches_only (bool, optional): whether to load incomplete patches that are padded to patch_size. Defaults to True """ - super(PenobscotInlinePatchDataset, self).__init__( - root, transforms=transforms - ) + super(PenobscotInlinePatchDataset, self).__init__(root, transforms=transforms) self._image_dir = os.path.join(self.root, "inlines", split) self._mask_dir = os.path.join(self.root, "masks") self._split = split @@ -257,16 +242,14 @@ def __init__( self._patch_locations = [] valid_modes = ("train", "test", "val") - msg = ( - "Unknown value '{}' for argument split. " - "Valid values are {{{}}}." - ) + msg = "Unknown value '{}' for argument split. " "Valid values are {{{}}}." msg = msg.format(split, iterable_to_str(valid_modes)) verify_str_arg(split, "split", valid_modes, msg) if not os.path.exists(self._image_dir): raise DataNotSplitException( - "The dataset has not been appropriately split into train, val and test" + f"Directory {self._image_dir} does not exist. The dataset has not been \ + appropriately split into train, val and test." ) # Get the number of inlines that make up dataset @@ -306,16 +289,18 @@ def __init__( len(self._patch_locations) % len(self._file_ids) == 0 ), "Something is wrong with the patches" - self._patches_per_image = int( - len(self._patch_locations) / len(self._file_ids) - ) + self._patches_per_image = int(len(self._patch_locations) / len(self._file_ids)) # Combine classes 2 and 3 self._mask_array = _combine_classes(self._mask_array) - self._classes, self._class_counts = _get_classes_and_counts( - self._mask_array - ) + self._classes, self._class_counts = _get_classes_and_counts(self._mask_array) + + def _open_image(self, image_path): + return pipe(image_path, _open_to_array, _rescale) + + def _open_mask(self, mask_path): + return pipe(mask_path, _open_to_array) def __len__(self): return len(self._image_array) @@ -327,9 +312,7 @@ def n_classes(self): @property def class_proportions(self): total = np.sum(self._class_counts) - return [ - (i, w / total) for i, w in zip(self._classes, self._class_counts) - ] + return [(i, w / total) for i, w in zip(self._classes, self._class_counts)] def _add_extra_channels(self, image): if self._n_channels > 1: @@ -365,12 +348,9 @@ def __getitem__(self, index): @property def statistics(self): - flat_image_array = np.concatenate( - [i.flatten() for i in self._image_array] - ) + flat_image_array = np.concatenate([i.flatten() for i in self._image_array]) stats = { - stat: statfunc(flat_image_array) - for stat, statfunc in _STATS_FUNCS.items() + stat: statfunc(flat_image_array) for stat, statfunc in _STATS_FUNCS.items() } return "Mean: {mean} Std: {std} Max: {max}".format(**stats) @@ -420,9 +400,6 @@ class PenobscotInlinePatchSectionDepthDataset(PenobscotInlinePatchDataset): The patches are augmented with section depth """ - _open_image = compose(add_depth_channels, _rescale, _open_to_array) - _open_mask = _open_to_array - def __init__( self, root, @@ -464,8 +441,11 @@ def __init__( complete_patches_only=complete_patches_only, ) - def _add_extra_channels(self, image): - return image + def _open_image(self, image_path): + return pipe(image_path, _open_to_array, _rescale, add_depth_channels) + + def _add_extra_channels(self, image): + return image class PenobscotInlinePatchDepthDataset(PenobscotInlinePatchDataset): @@ -476,9 +456,6 @@ class PenobscotInlinePatchDepthDataset(PenobscotInlinePatchDataset): The patches are augmented with patch depth """ - _open_image = compose(_rescale, _open_to_array) - _open_mask = _open_to_array - def __init__( self, root, @@ -519,6 +496,9 @@ def __init__( complete_patches_only=complete_patches_only, ) + def _open_image(self, image_path): + return pipe(image_path, _open_to_array, _rescale) + def _add_extra_channels(self, image): return add_depth_channels(image) @@ -544,13 +524,9 @@ def get_patch_dataset(cfg): "none", ], f"Depth {cfg.TRAIN.DEPTH} not supported for patch data. \ Valid values: section, patch, none." - return _TRAIN_PATCH_DATASETS.get( - cfg.TRAIN.DEPTH, PenobscotInlinePatchDataset - ) + return _TRAIN_PATCH_DATASETS.get(cfg.TRAIN.DEPTH, PenobscotInlinePatchDataset) if __name__ == "__main__": - dataset = PenobscotInlinePatchDataset( - "/mnt/penobscot", 100, 50, split="train" - ) + dataset = PenobscotInlinePatchDataset("/mnt/penobscot", 100, 50, split="train") print(len(dataset)) diff --git a/experiments/interpretation/dutchf3_patch/prepare_data.py b/scripts/prepare_dutchf3.py similarity index 85% rename from experiments/interpretation/dutchf3_patch/prepare_data.py rename to scripts/prepare_dutchf3.py index ca7d6319..4ac7018a 100644 --- a/experiments/interpretation/dutchf3_patch/prepare_data.py +++ b/scripts/prepare_dutchf3.py @@ -25,9 +25,7 @@ def _get_labels_path(data_dir): def _write_split_files(splits_path, train_list, test_list, loader_type): - file_object = open( - path.join(splits_path, loader_type + "_train_val.txt"), "w" - ) + file_object = open(path.join(splits_path, loader_type + "_train_val.txt"), "w") file_object.write("\n".join(train_list + test_list)) file_object.close() file_object = open(path.join(splits_path, loader_type + "_train.txt"), "w") @@ -54,7 +52,8 @@ def split_section_train_val(data_dir, per_val=0.2, log_config=None): Args: data_dir (str): data directory path - per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + per_val (float, optional): the fraction of the volume to use for validation. + Defaults to 0.2. """ if log_config is not None: @@ -96,7 +95,8 @@ def split_patch_train_val(data_dir, stride, patch, per_val=0.2, log_config=None) data_dir (str): data directory path stride (int): stride to use when sectioning of the volume patch (int): size of patch to extract - per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + per_val (float, optional): the fraction of the volume to use for validation. + Defaults to 0.2. """ if log_config is not None: @@ -129,9 +129,7 @@ def split_patch_train_val(data_dir, stride, patch, per_val=0.2, log_config=None) def _i_extract_patches(iline_range, horz_locations, vert_locations): for i in iline_range: - locations = ( - [j, k] for j in horz_locations for k in vert_locations - ) + locations = ([j, k] for j in horz_locations for k in vert_locations) for j, k in locations: yield "i_" + str(i) + "_" + str(j) + "_" + str(k) @@ -148,9 +146,7 @@ def _i_extract_patches(iline_range, horz_locations, vert_locations): def _x_extract_patches(xline_range, horz_locations, vert_locations): for j in xline_range: - locations = ( - [i, k] for i in horz_locations for k in vert_locations - ) + locations = ([i, k] for i in horz_locations for k in vert_locations) for i, k in locations: yield "x_" + str(i) + "_" + str(j) + "_" + str(k) @@ -170,10 +166,7 @@ def _x_extract_patches(xline_range, horz_locations, vert_locations): _write_split_files(splits_path, train_list, test_list, "patch") -_LOADER_TYPES = { - "section": split_section_train_val, - "patch": split_patch_train_val, -} +_LOADER_TYPES = {"section": split_section_train_val, "patch": split_patch_train_val} def get_split_function(loader_type): @@ -186,25 +179,24 @@ def run_split_func(loader_type, *args, **kwargs): def split_alaudah_et_al_19( - data_dir, - stride, - fraction_validation=0.2, - loader_type="patch", - log_config=None, + data_dir, stride, fraction_validation=0.2, loader_type="patch", log_config=None ): """Generate train and validation files (with overlap) for Netherlands F3 dataset. - This is the original spliting method from https://github.com/olivesgatech/facies_classification_benchmark + The original split method from https://github.com/olivesgatech/facies_classification_benchmark DON'T USE, SEE NOTES BELOW Args: data_dir (str): data directory path stride (int): stride to use when sectioning of the volume - fraction_validation (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. - loader_type (str, optional): type of data loader, can be "patch" or "section". Defaults to "patch". + fraction_validation (float, optional): the fraction of the volume to use for validation. + Defaults to 0.2. + loader_type (str, optional): type of data loader, can be "patch" or "section". + Defaults to "patch". log_config (str, optional): path to log config. Defaults to None. Notes: - Only kept for reproducibility. It generates overlapping train and val which makes validation results unreliable. + Only kept for reproducibility. It generates overlapping train and val which makes + validation results unreliable. """ if log_config is not None: @@ -242,12 +234,9 @@ def split_alaudah_et_al_19( for i in range(iline): # for every inline: # images are references by top-left corner: - locations = [ - [j, k] for j in horz_locations for k in vert_locations - ] + locations = [[j, k] for j in horz_locations for k in vert_locations] patches_list = [ - "i_" + str(i) + "_" + str(j) + "_" + str(k) - for j, k in locations + "i_" + str(i) + "_" + str(j) + "_" + str(k) for j, k in locations ] i_list.append(patches_list) @@ -260,12 +249,9 @@ def split_alaudah_et_al_19( for j in range(xline): # for every xline: # images are references by top-left corner: - locations = [ - [i, k] for i in horz_locations for k in vert_locations - ] + locations = [[i, k] for i in horz_locations for k in vert_locations] patches_list = [ - "x_" + str(i) + "_" + str(j) + "_" + str(k) - for i, k in locations + "x_" + str(i) + "_" + str(j) + "_" + str(k) for i, k in locations ] x_list.append(patches_list) @@ -283,15 +269,16 @@ def split_alaudah_et_al_19( splits_path = _get_splits_path(data_dir) _write_split_files(splits_path, train_list, test_list, loader_type) -#TODO: Try https://github.com/Chilipp/docrep for doscstring reuse -class SplitTrainValCLI(object): +# TODO: Try https://github.com/Chilipp/docrep for doscstring reuse +class SplitTrainValCLI(object): def section(self, data_dir, per_val=0.2, log_config=None): """Generate section based train and validation files for Netherlands F3 dataset. Args: data_dir (str): data directory path - per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + per_val (float, optional): the fraction of the volume to use for validation. + Defaults to 0.2. log_config (str): path to log configurations """ return split_section_train_val(data_dir, per_val=per_val, log_config=log_config) @@ -303,15 +290,18 @@ def patch(self, data_dir, stride, patch, per_val=0.2, log_config=None): data_dir (str): data directory path stride (int): stride to use when sectioning of the volume patch (int): size of patch to extract - per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. + per_val (float, optional): the fraction of the volume to use for validation. + Defaults to 0.2. log_config (str): path to log configurations """ - return split_patch_train_val(data_dir, stride, patch, per_val=per_val, log_config=log_config) + return split_patch_train_val( + data_dir, stride, patch, per_val=per_val, log_config=log_config + ) if __name__ == "__main__": """Example: - python prepare_data.py split_train_val section --data-dir=/mnt/dutch + python prepare_data.py split_train_val section --data-dir=/mnt/dutch or python prepare_data.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=100 diff --git a/experiments/interpretation/penobscot/prepare_data.py b/scripts/prepare_penobscot.py similarity index 78% rename from experiments/interpretation/penobscot/prepare_data.py rename to scripts/prepare_penobscot.py index 103bbdca..c5557f6a 100644 --- a/experiments/interpretation/penobscot/prepare_data.py +++ b/scripts/prepare_penobscot.py @@ -8,12 +8,7 @@ import logging import logging.config import math -import warnings -from os import path - import fire -import numpy as np -from sklearn.model_selection import train_test_split import os import shutil from toolz import partition_all @@ -32,9 +27,10 @@ def _create_directory(dir_path, overwrite=False): return dir_path except FileExistsError as e: logger.warn( - f"Can't write to {dir_path} as it already exists. Please specify overwrite=true or delete folder" + f"Can't write to {dir_path} as it already exists. Please specify \ + overwrite=true or delete folder" ) - raise + raise e def _copy_files(files_iter, new_dir): @@ -52,38 +48,34 @@ def _split_train_val_test(partition, val_ratio, test_ratio): train_list = partition[:train_samples] val_list = partition[train_samples : train_samples + val_samples] test_list = partition[ - train_samples - + val_samples : train_samples - + val_samples - + test_samples + train_samples + val_samples : train_samples + val_samples + test_samples ] return train_list, val_list, test_list def split_inline(data_dir, val_ratio, test_ratio, overwrite=False, exclude_files=None): - """Splits the inline data into train, val and test - + """Splits the inline data into train, val and test. + Args: data_dir (str): path to directory that holds the data - val_ratio (float): the ratio of the partition that they should use for validation + val_ratio (float): the ratio of the partition that will be used for validation test_ratio (float): the ratio of the partition that they should use for testing - exclude_files (list[str]): filenames to exclude from dataset, such as ones that contain artifacts. Example:['image1.tiff'] + exclude_files (list[str]): filenames to exclude from dataset, such as ones that contain + artifacts. Example:['image1.tiff'] """ num_partitions = 5 image_dir = os.path.join(data_dir, "inlines") - dir_paths = ( - os.path.join(image_dir, ddir) for ddir in ("train", "val", "test") - ) + dir_paths = (os.path.join(image_dir, ddir) for ddir in ("train", "val", "test")) locations_list = [ _create_directory(d, overwrite=overwrite) for d in dir_paths ] # train, val, test images_iter = glob.iglob(os.path.join(image_dir, "*.tiff")) - + if exclude_files is not None: - images_list = list(filterfalse( - lambda x: x in exclude_files, images_iter - )) + images_list = list( + itertools.filterfalse(lambda x: x in exclude_files, images_iter) + ) else: images_list = list(images_iter) @@ -92,8 +84,7 @@ def split_inline(data_dir, val_ratio, test_ratio, overwrite=False, exclude_files num_elements, images_list ): # Partition files into N partitions for files_list, dest_dir in zip( - _split_train_val_test(partition, val_ratio, test_ratio), - locations_list, + _split_train_val_test(partition, val_ratio, test_ratio), locations_list ): _copy_files(files_list, dest_dir) From 31ddf633467bae7f499d841c3ecb6d16fa968227 Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Fri, 1 Nov 2019 15:18:55 +0000 Subject: [PATCH 067/207] Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 --- .../interpretation/dutchf3_voxel/train.py | 171 +++++++++++------- .../dutchf3/data.py | 2 + 2 files changed, 109 insertions(+), 64 deletions(-) diff --git a/experiments/interpretation/dutchf3_voxel/train.py b/experiments/interpretation/dutchf3_voxel/train.py index 1e09c609..7ea63254 100644 --- a/experiments/interpretation/dutchf3_voxel/train.py +++ b/experiments/interpretation/dutchf3_voxel/train.py @@ -6,23 +6,18 @@ import logging.config from os import path -import cv2 import fire import numpy as np import torch -from albumentations import ( - Compose, - HorizontalFlip, - Normalize, - PadIfNeeded, - Resize, -) -from ignite.contrib.handlers import CosineAnnealingScheduler +from torch.utils import data from ignite.engine import Events +from ignite.handlers import ModelCheckpoint from ignite.metrics import Accuracy, Loss +# TODO: get mertircs from Ignite +# from ignite.metrics import MIoU, MeanClassAccuracy, FrequencyWeightedIoU, PixelwiseAccuracy from ignite.utils import convert_tensor -from toolz import compose -from torch.utils import data +from ignite.engine.engine import Engine +from toolz import compose, curry from tqdm import tqdm from deepseismic_interpretation.dutchf3.data import ( @@ -50,12 +45,15 @@ create_supervised_trainer, create_supervised_evaluator, ) + +# TODO: replace with Ignite metrics from cv_lib.segmentation.dutchf3.metrics import ( FrequencyWeightedIoU, MeanClassAccuracy, MeanIoU, PixelwiseAccuracy, ) + from cv_lib.segmentation.dutchf3.utils import ( current_datetime, generate_path, @@ -72,7 +70,7 @@ from default import update_config -def prepare_batch( +def _prepare_batch( batch, device=None, non_blocking=False, t_type=torch.FloatTensor ): x, y = batch @@ -148,9 +146,7 @@ def run(*options, cfg=None): shuffle=False ) - # this is how we import model for CV - here we're importing a seismic segmentation model - # model = getattr(models, config.MODEL.NAME).get_seg_model(config) - # TODO: pass more model parameters into the mode from config + # this is how we import model for CV - here we're importing a seismic segmentation model model = TextureNet(n_classes=config.DATASET.NUM_CLASSES) optimizer = torch.optim.Adam( @@ -166,72 +162,119 @@ def run(*options, cfg=None): device = "cuda" model = model.cuda() - loss = torch.nn.CrossEntropyLoss() - - def _select_pred_and_mask(model_out_dict): - return ( - model_out_dict["y_pred"].squeeze(), - model_out_dict["mask"].squeeze(), - ) + loss = torch.nn.CrossEntropyLoss() trainer = create_supervised_trainer( model, optimizer, loss, - prepare_batch=prepare_batch, + prepare_batch=_prepare_batch, device=device, ) + desc = "ITERATION - loss: {:.2f}" + pbar = tqdm( + initial=0, leave=False, total=len(train_loader), desc=desc.format(0) + ) + + # add model checkpointing + output_dir = path.join(config.OUTPUT_DIR, config.TRAIN.MODEL_DIR) + checkpoint_handler = ModelCheckpoint( + output_dir, "model", save_interval=1, n_saved=3, create_dir=True, require_empty=False) + + criterion = torch.nn.CrossEntropyLoss(reduction="mean") + + # save model at each epoch + trainer.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model} + ) + + def _select_pred_and_mask(model_out): + # receive a tuple of (x, y_pred), y + # so actually in line 51 of cv_lib/cv_lib/segmentation/dutch_f3/metrics/__init__.py + # we do the following line, so here we just select the model + #_, y_pred = torch.max(model_out[0].squeeze(), 1, keepdim=True) + y_pred = model_out[0].squeeze() + y = model_out[1].squeeze() + return (y_pred.squeeze(), y,) + evaluator = create_supervised_evaluator( - model, - prepare_batch=prepare_batch, + model, metrics={ - "accuracy": Accuracy(), - "nll": Loss(loss), + "IoU": MeanIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "nll": Loss(criterion), + "mca": MeanClassAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), + "fiou": FrequencyWeightedIoU( + n_classes, device, output_transform=_select_pred_and_mask + ), + "pixa": PixelwiseAccuracy( + n_classes, device, output_transform=_select_pred_and_mask + ), }, device=device, + prepare_batch=_prepare_batch, + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler( + Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) ) - desc = "ITERATION - loss: {:.2f}" - pbar = tqdm( - initial=0, leave=False, total=len(train_loader), desc=desc.format(0) + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={ + "IoU": "IoU :", + "nll": "Avg loss :", + "pixa": "Pixelwise Accuracy :", + "mca": "Mean Class Accuracy :", + "fiou": "Freq Weighted IoU :", + }, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={ + "IoU": "Validation/IoU", + "nll": "Validation/Loss", + "mca": "Validation/MCA", + "fiou": "Validation/FIoU", + }, + ), ) + + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + - @trainer.on(Events.ITERATION_COMPLETED) - def log_training_loss(engine): - iter = (engine.state.iteration - 1) % len(train_loader) + 1 - - if iter % log_interval == 0: - pbar.desc = desc.format(engine.state.output) - pbar.update(log_interval) - - @trainer.on(Events.EPOCH_COMPLETED) - def log_training_results(engine): - pbar.refresh() - evaluator.run(train_loader) - metrics = evaluator.state.metrics - avg_accuracy = metrics["accuracy"] - avg_loss = metrics["nll"] - tqdm.write( - "Training Results - Epoch: {} Avg accuracy: {:.2f} Avg loss: {:.2f}".format( - engine.state.epoch, avg_accuracy, avg_loss - ) - ) - - @trainer.on(Events.EPOCH_COMPLETED) - def log_validation_results(engine): - evaluator.run(val_loader) - metrics = evaluator.state.metrics - avg_accuracy = metrics["accuracy"] - avg_loss = metrics["nll"] - tqdm.write( - "Validation Results - Epoch: {} Avg accuracy: {:.2f} Avg loss: {:.2f}".format( - engine.state.epoch, avg_accuracy, avg_loss - ) - ) - - pbar.n = pbar.last_print_n = 0 + snapshot_duration=1 + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + path.join(output_dir, config.TRAIN.MODEL_DIR), + config.MODEL.NAME, + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + logger.info("Starting training") trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH//config.TRAIN.BATCH_PER_EPOCH) pbar.close() diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index 9c13e966..f80f218d 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -3,6 +3,8 @@ import segyio from os import path import scipy +# bugfix for scipy imports +import scipy.misc import numpy as np import torch from toolz import curry From 93ba611215e356a744f3b1396042436d4e3c05f2 Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Thu, 7 Nov 2019 15:01:11 +0000 Subject: [PATCH 068/207] added cela copyright headers to all non-empty .py files (#3) --- cv_lib/cv_lib/__version__.py | 6 +- cv_lib/cv_lib/event_handlers/__init__.py | 23 +- .../cv_lib/event_handlers/logging_handlers.py | 11 +- .../event_handlers/tensorboard_handlers.py | 30 ++- cv_lib/cv_lib/segmentation/__init__.py | 6 +- .../segmentation/dutchf3/augmentations.py | 86 ++++-- cv_lib/cv_lib/segmentation/dutchf3/engine.py | 11 +- .../segmentation/dutchf3/metrics/__init__.py | 28 +- .../segmentation/dutchf3/metrics/apex.py | 3 + cv_lib/cv_lib/segmentation/dutchf3/utils.py | 19 +- cv_lib/cv_lib/segmentation/models/__init__.py | 2 + .../segmentation/models/patch_deconvnet.py | 134 +++++----- .../models/patch_deconvnet_skip.py | 142 +++++----- .../cv_lib/segmentation/models/resnet_unet.py | 248 +++++++++++------- .../segmentation/models/section_deconvnet.py | 156 ++++++----- .../models/section_deconvnet_skip.py | 149 +++++------ cv_lib/cv_lib/segmentation/models/unet.py | 26 +- cv_lib/cv_lib/segmentation/models/utils.py | 101 +++++-- .../cv_lib/segmentation/penobscot/engine.py | 3 + cv_lib/cv_lib/segmentation/utils.py | 29 +- cv_lib/setup.py | 34 +-- deepseismic/__init__.py | 3 + deepseismic/cli/__init__.py | 3 + deepseismic/cli/forward.py | 35 +-- deepseismic/cli/velocity.py | 20 +- deepseismic/forward/__init__.py | 3 + deepseismic/forward/models.py | 26 +- deepseismic/forward/sources.py | 17 +- deepseismic/forward/subdomains.py | 3 + deepseismic/forward/time.py | 3 + deepseismic/forward/types.py | 3 + deepseismic/velocity/__init__.py | 3 + deepseismic/velocity/generator.py | 3 + deepseismic/velocity/roeth_tarantola.py | 9 +- .../interpretation/notebooks/utilities.py | 3 + .../dutchf3_patch/distributed/train.py | 44 +--- imaging/deepseismic_imaging/__init__.py | 3 + imaging/deepseismic_imaging/cli/__init__.py | 3 + imaging/deepseismic_imaging/cli/forward.py | 35 +-- imaging/deepseismic_imaging/cli/velocity.py | 20 +- .../deepseismic_imaging/forward/__init__.py | 3 + imaging/deepseismic_imaging/forward/models.py | 26 +- .../deepseismic_imaging/forward/sources.py | 17 +- .../deepseismic_imaging/forward/subdomains.py | 3 + imaging/deepseismic_imaging/forward/time.py | 3 + imaging/deepseismic_imaging/forward/types.py | 3 + .../deepseismic_imaging/velocity/__init__.py | 3 + .../deepseismic_imaging/velocity/generator.py | 3 + .../velocity/roeth_tarantola.py | 9 +- imaging/setup.py | 5 +- .../dutchf3/data.py | 3 + .../dutchf3/utils/batch.py | 3 + .../penobscot/data.py | 3 + .../penobscot/metrics.py | 11 +- interpretation/setup.py | 6 +- setup.py | 7 +- 56 files changed, 871 insertions(+), 723 deletions(-) diff --git a/cv_lib/cv_lib/__version__.py b/cv_lib/cv_lib/__version__.py index 99c4176c..04ecc805 100644 --- a/cv_lib/cv_lib/__version__.py +++ b/cv_lib/cv_lib/__version__.py @@ -1 +1,5 @@ -__version__ = '0.0.1' \ No newline at end of file +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +__version__ = "0.0.1" + diff --git a/cv_lib/cv_lib/event_handlers/__init__.py b/cv_lib/cv_lib/event_handlers/__init__.py index 589c131c..a2dab4ca 100644 --- a/cv_lib/cv_lib/event_handlers/__init__.py +++ b/cv_lib/cv_lib/event_handlers/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from ignite.handlers import ModelCheckpoint import glob import os @@ -5,12 +8,10 @@ class SnapshotHandler: - def __init__( - self, dir_name, filename_prefix, score_function, snapshot_function - ): + def __init__(self, dir_name, filename_prefix, score_function, snapshot_function): self._model_save_location = dir_name - self._running_model_prefix = filename_prefix+"_running" - self._snapshot_prefix = filename_prefix+"_snapshot" + self._running_model_prefix = filename_prefix + "_running" + self._snapshot_prefix = filename_prefix + "_snapshot" self._snapshot_function = snapshot_function self._snapshot_num = 1 self._score_function = score_function @@ -20,18 +21,20 @@ def _create_checkpoint_handler(self): return ModelCheckpoint( self._model_save_location, self._running_model_prefix, - score_function=self._score_function, + score_function=self._score_function, n_saved=1, create_dir=True, save_as_state_dict=True, - require_empty=False + require_empty=False, ) def __call__(self, engine, to_save): self._checkpoint_handler(engine, to_save) if self._snapshot_function(): files = glob.glob( - os.path.join(self._model_save_location, self._running_model_prefix + "*") + os.path.join( + self._model_save_location, self._running_model_prefix + "*" + ) ) print(files) name_postfix = os.path.basename(files[0]).lstrip(self._running_model_prefix) @@ -42,5 +45,7 @@ def __call__(self, engine, to_save): f"{self._snapshot_prefix}{self._snapshot_num}{name_postfix}", ), ) - self._checkpoint_handler = self._create_checkpoint_handler() # Reset the checkpoint handler + self._checkpoint_handler = ( + self._create_checkpoint_handler() + ) # Reset the checkpoint handler self._snapshot_num += 1 diff --git a/cv_lib/cv_lib/event_handlers/logging_handlers.py b/cv_lib/cv_lib/event_handlers/logging_handlers.py index a6d8f97c..29ea6e76 100644 --- a/cv_lib/cv_lib/event_handlers/logging_handlers.py +++ b/cv_lib/cv_lib/event_handlers/logging_handlers.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import logging import logging.config from toolz import curry @@ -8,7 +11,9 @@ def log_training_output(engine, log_interval=100): logger = logging.getLogger(__name__) if engine.state.iteration % log_interval == 0: - logger.info(f"Epoch: {engine.state.epoch} Iter: {engine.state.iteration} loss {engine.state.output['loss']}") + logger.info( + f"Epoch: {engine.state.epoch} Iter: {engine.state.iteration} loss {engine.state.output['loss']}" + ) @curry @@ -25,7 +30,9 @@ def log_lr(optimizer, engine): def log_metrics(log_msg, engine, metrics_dict=_DEFAULT_METRICS): logger = logging.getLogger(__name__) metrics = engine.state.metrics - metrics_msg = " ".join([f"{metrics_dict[k]} {metrics[k]:.2f}" for k in metrics_dict]) + metrics_msg = " ".join( + [f"{metrics_dict[k]} {metrics[k]:.2f}" for k in metrics_dict] + ) logger.info( f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}] " + metrics_msg diff --git a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py index 30e12abd..1c3a574a 100644 --- a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py +++ b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from toolz import curry import torchvision import logging @@ -17,7 +20,9 @@ def create_summary_writer(log_dir): def _log_model_output(log_label, summary_writer, engine): - summary_writer.add_scalar(log_label, engine.state.output["loss"], engine.state.iteration) + summary_writer.add_scalar( + log_label, engine.state.output["loss"], engine.state.iteration + ) @curry @@ -43,17 +48,26 @@ def log_lr(summary_writer, optimizer, log_interval, engine): lr = [param_group["lr"] for param_group in optimizer.param_groups] summary_writer.add_scalar("lr", lr[0], getattr(engine.state, log_interval)) + _DEFAULT_METRICS = {"accuracy": "Avg accuracy :", "nll": "Avg loss :"} + @curry -def log_metrics(summary_writer, train_engine, log_interval, engine, metrics_dict=_DEFAULT_METRICS): +def log_metrics( + summary_writer, train_engine, log_interval, engine, metrics_dict=_DEFAULT_METRICS +): metrics = engine.state.metrics for m in metrics_dict: - summary_writer.add_scalar(metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval)) + summary_writer.add_scalar( + metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval) + ) -def create_image_writer(summary_writer, label, output_variable, normalize=False, transform_func=lambda x: x): +def create_image_writer( + summary_writer, label, output_variable, normalize=False, transform_func=lambda x: x +): logger = logging.getLogger(__name__) + def write_to(engine): try: data_tensor = transform_func(engine.state.output[output_variable]) @@ -62,6 +76,8 @@ def write_to(engine): ) summary_writer.add_image(label, image_grid, engine.state.epoch) except KeyError: - logger.warning("Predictions and or ground truth labels not available to report") - - return write_to \ No newline at end of file + logger.warning( + "Predictions and or ground truth labels not available to report" + ) + + return write_to diff --git a/cv_lib/cv_lib/segmentation/__init__.py b/cv_lib/cv_lib/segmentation/__init__.py index e48edba2..4473ef65 100644 --- a/cv_lib/cv_lib/segmentation/__init__.py +++ b/cv_lib/cv_lib/segmentation/__init__.py @@ -1,6 +1,10 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from toolz import curry import torch.nn.functional as F + @curry def extract_metric_from(metric, engine): metrics = engine.state.metrics @@ -12,4 +16,4 @@ def padded_val_transform(pad_left, fine_size, x, y, y_pred): y_pred = y_pred[ :, :, pad_left : pad_left + fine_size, pad_left : pad_left + fine_size ].contiguous() - return {"image":x, "y_pred": F.sigmoid(y_pred).detach(), "mask":y.detach()} + return {"image": x, "y_pred": F.sigmoid(y_pred).detach(), "mask": y.detach()} diff --git a/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py b/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py index ba9c86ae..d14cc8c8 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import math import numbers import random @@ -5,24 +8,27 @@ from PIL import Image, ImageOps, ImageChops + class Compose(object): def __init__(self, augmentations): self.augmentations = augmentations def __call__(self, img, mask): - img, mask = Image.fromarray(img, mode=None), Image.fromarray(mask, mode='L') + img, mask = Image.fromarray(img, mode=None), Image.fromarray(mask, mode="L") assert img.size == mask.size for a in self.augmentations: img, mask = a(img, mask) return np.array(img), np.array(mask, dtype=np.uint8) + class AddNoise(object): def __call__(self, img, mask): - noise = np.random.normal(loc=0,scale=0.02,size=(img.size[1], img.size[0])) + noise = np.random.normal(loc=0, scale=0.02, size=(img.size[1], img.size[0])) return img + noise, mask + class RandomCrop(object): def __init__(self, size, padding=0): if isinstance(size, numbers.Number): @@ -42,11 +48,17 @@ def __call__(self, img, mask): if w == tw and h == th: return img, mask if w < tw or h < th: - return img.resize((tw, th), Image.BILINEAR), mask.resize((tw, th), Image.NEAREST) + return ( + img.resize((tw, th), Image.BILINEAR), + mask.resize((tw, th), Image.NEAREST), + ) x1 = random.randint(0, w - tw) y1 = random.randint(0, h - th) - return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) + return ( + img.crop((x1, y1, x1 + tw, y1 + th)), + mask.crop((x1, y1, x1 + tw, y1 + th)), + ) class CenterCrop(object): @@ -60,32 +72,46 @@ def __call__(self, img, mask): assert img.size == mask.size w, h = img.size th, tw = self.size - x1 = int(round((w - tw) / 2.)) - y1 = int(round((h - th) / 2.)) - return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) + x1 = int(round((w - tw) / 2.0)) + y1 = int(round((h - th) / 2.0)) + return ( + img.crop((x1, y1, x1 + tw, y1 + th)), + mask.crop((x1, y1, x1 + tw, y1 + th)), + ) class RandomHorizontallyFlip(object): def __call__(self, img, mask): if random.random() < 0.5: - #Note: we use FLIP_TOP_BOTTOM here intentionaly. Due to the dimensions of the image, + # Note: we use FLIP_TOP_BOTTOM here intentionaly. Due to the dimensions of the image, # it ends up being a horizontal flip. - return img.transpose(Image.FLIP_TOP_BOTTOM), mask.transpose(Image.FLIP_TOP_BOTTOM) + return ( + img.transpose(Image.FLIP_TOP_BOTTOM), + mask.transpose(Image.FLIP_TOP_BOTTOM), + ) return img, mask - + + class RandomVerticallyFlip(object): def __call__(self, img, mask): if random.random() < 0.5: - return img.transpose(Image.FLIP_LEFT_RIGHT), mask.transpose(Image.FLIP_LEFT_RIGHT) + return ( + img.transpose(Image.FLIP_LEFT_RIGHT), + mask.transpose(Image.FLIP_LEFT_RIGHT), + ) return img, mask - + + class FreeScale(object): def __init__(self, size): self.size = tuple(reversed(size)) # size: (h, w) def __call__(self, img, mask): assert img.size == mask.size - return img.resize(self.size, Image.BILINEAR), mask.resize(self.size, Image.NEAREST) + return ( + img.resize(self.size, Image.BILINEAR), + mask.resize(self.size, Image.NEAREST), + ) class Scale(object): @@ -100,11 +126,17 @@ def __call__(self, img, mask): if w > h: ow = self.size oh = int(self.size * h / w) - return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) + return ( + img.resize((ow, oh), Image.BILINEAR), + mask.resize((ow, oh), Image.NEAREST), + ) else: oh = self.size ow = int(self.size * w / h) - return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) + return ( + img.resize((ow, oh), Image.BILINEAR), + mask.resize((ow, oh), Image.NEAREST), + ) class RandomSizedCrop(object): @@ -130,10 +162,12 @@ def __call__(self, img, mask): img = img.crop((x1, y1, x1 + w, y1 + h)) mask = mask.crop((x1, y1, x1 + w, y1 + h)) - assert (img.size == (w, h)) + assert img.size == (w, h) - return img.resize((self.size, self.size), Image.BILINEAR), mask.resize((self.size, self.size), - Image.NEAREST) + return ( + img.resize((self.size, self.size), Image.BILINEAR), + mask.resize((self.size, self.size), Image.NEAREST), + ) # Fallback scale = Scale(self.size) @@ -146,26 +180,27 @@ def __init__(self, degree): self.degree = degree def __call__(self, img, mask): - ''' + """ PIL automatically adds zeros to the borders of images that rotated. To fix this issue, the code in the botton sets anywhere in the labels (mask) that is zero to 255 (the value used for ignore_index). - ''' + """ rotate_degree = random.random() * 2 * self.degree - self.degree img = img.rotate(rotate_degree, Image.BILINEAR) - mask = mask.rotate(rotate_degree, Image.NEAREST) + mask = mask.rotate(rotate_degree, Image.NEAREST) binary_mask = Image.fromarray(np.ones([mask.size[1], mask.size[0]])) binary_mask = binary_mask.rotate(rotate_degree, Image.NEAREST) binary_mask = np.array(binary_mask) mask_arr = np.array(mask) - mask_arr[binary_mask==0] = 255 + mask_arr[binary_mask == 0] = 255 mask = Image.fromarray(mask_arr) return img, mask + class RandomSized(object): def __init__(self, size): self.size = size @@ -178,6 +213,9 @@ def __call__(self, img, mask): w = int(random.uniform(0.5, 2) * img.size[0]) h = int(random.uniform(0.5, 2) * img.size[1]) - img, mask = img.resize((w, h), Image.BILINEAR), mask.resize((w, h), Image.NEAREST) + img, mask = ( + img.resize((w, h), Image.BILINEAR), + mask.resize((w, h), Image.NEAREST), + ) - return self.crop(*self.scale(img, mask)) \ No newline at end of file + return self.crop(*self.scale(img, mask)) diff --git a/cv_lib/cv_lib/segmentation/dutchf3/engine.py b/cv_lib/cv_lib/segmentation/dutchf3/engine.py index 1bb8aa39..5d3afbbf 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/engine.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/engine.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import torch from ignite.engine.engine import Engine, State, Events @@ -7,13 +10,15 @@ from torch.nn import functional as F import numpy as np + def _upscale_model_output(y_pred, y): ph, pw = y_pred.size(2), y_pred.size(3) h, w = y.size(2), y.size(3) if ph != h or pw != w: - y_pred = F.upsample(input=y_pred, size=(h, w), mode='bilinear') + y_pred = F.upsample(input=y_pred, size=(h, w), mode="bilinear") return y_pred - + + def create_supervised_trainer( model, optimizer, @@ -42,7 +47,7 @@ def _update(engine, batch): @curry def val_transform(x, y, y_pred): - return {"image":x, "y_pred": y_pred.detach(), "mask":y.detach()} + return {"image": x, "y_pred": y_pred.detach(), "mask": y.detach()} def create_supervised_evaluator( diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py index 2e50423e..3c4d5540 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import numpy as np import warnings @@ -43,15 +46,17 @@ def __init__(self, num_classes, device, output_transform=lambda x: x): super(ConfusionMatrix, self).__init__(output_transform=output_transform) def reset(self): - self._confusion_matrix = torch.zeros((self._num_classes, self._num_classes),dtype=torch.long).to(self._device) + self._confusion_matrix = torch.zeros( + (self._num_classes, self._num_classes), dtype=torch.long + ).to(self._device) def update(self, output): y_pred, y = output - #TODO: Make assertion exception - assert y.shape==y_pred.max(1)[1].squeeze().shape, "Shape not the same" + # TODO: Make assertion exception + assert y.shape == y_pred.max(1)[1].squeeze().shape, "Shape not the same" self._confusion_matrix += _torch_hist( torch.flatten(y), - torch.flatten(y_pred.max(1)[1].squeeze()), # Get the maximum index + torch.flatten(y_pred.max(1)[1].squeeze()), # Get the maximum index self._num_classes, ) @@ -62,9 +67,7 @@ def compute(self): class MeanIoU(ConfusionMatrix): def compute(self): hist = self._confusion_matrix.cpu().numpy() - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) + iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) mean_iu = np.nanmean(iu) return mean_iu @@ -75,13 +78,14 @@ def compute(self): acc = np.diag(hist).sum() / hist.sum() return acc + class FrequencyWeightedIoU(ConfusionMatrix): def compute(self): hist = self._confusion_matrix.cpu().numpy() - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) - freq = hist.sum(axis=1) / hist.sum() # fraction of the pixels that come from each class + iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) + freq = ( + hist.sum(axis=1) / hist.sum() + ) # fraction of the pixels that come from each class fwiou = (freq[freq > 0] * iu[freq > 0]).sum() return fwiou @@ -92,4 +96,4 @@ def compute(self): acc = np.diag(hist).sum() / hist.sum() acc_cls = np.diag(hist) / hist.sum(axis=1) mean_acc_cls = np.nanmean(acc_cls) - return mean_acc_cls \ No newline at end of file + return mean_acc_cls diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py index 10967f09..400bacca 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import numpy as np import torch import torch.distributed as dist diff --git a/cv_lib/cv_lib/segmentation/dutchf3/utils.py b/cv_lib/cv_lib/segmentation/dutchf3/utils.py index 52bfa0bc..1d509b95 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/utils.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/utils.py @@ -1,30 +1,34 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import numpy as np -import torch +import torch import torchvision.utils as vutils from git import Repo from datetime import datetime import os + def np_to_tb(array): # if 2D : if array.ndim == 2: # HW => CHW - array = np.expand_dims(array,axis=0) + array = np.expand_dims(array, axis=0) # CHW => NCHW - array = np.expand_dims(array,axis=0) + array = np.expand_dims(array, axis=0) elif array.ndim == 3: # HWC => CHW array = array.transpose(2, 0, 1) # CHW => NCHW - array = np.expand_dims(array,axis=0) - + array = np.expand_dims(array, axis=0) + array = torch.from_numpy(array) # array = vutils.make_grid(array, normalize=True, scale_each=True) return array def current_datetime(): - return datetime.now().strftime('%b%d_%H%M%S') + return datetime.now().strftime("%b%d_%H%M%S") def git_branch(): @@ -36,8 +40,9 @@ def git_hash(): repo = Repo(search_parent_directories=True) return repo.active_branch.commit.hexsha + def generate_path(base_path, *directories): path = os.path.join(base_path, *directories) if not os.path.exists(path): os.makedirs(path) - return path \ No newline at end of file + return path diff --git a/cv_lib/cv_lib/segmentation/models/__init__.py b/cv_lib/cv_lib/segmentation/models/__init__.py index 93967f4e..499803f5 100644 --- a/cv_lib/cv_lib/segmentation/models/__init__.py +++ b/cv_lib/cv_lib/segmentation/models/__init__.py @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. import cv_lib.segmentation.models.seg_hrnet import cv_lib.segmentation.models.resnet_unet diff --git a/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py b/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py index b45f6fc4..1d274446 100644 --- a/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py +++ b/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py @@ -1,259 +1,241 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import torch.nn as nn -class patch_deconvnet(nn.Module): +class patch_deconvnet(nn.Module): def __init__(self, n_classes=4, learned_billinear=False): super(patch_deconvnet, self).__init__() self.learned_billinear = learned_billinear self.n_classes = n_classes self.unpool = nn.MaxUnpool2d(2, stride=2) self.conv_block1 = nn.Sequential( - # conv1_1 nn.Conv2d(1, 64, 3, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv1_2 nn.Conv2d(64, 64, 3, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool1 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_1 # 48*48 self.conv_block2 = nn.Sequential( - # conv2_1 nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv2_2 nn.Conv2d(128, 128, 3, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool2 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_2 # 24*24 self.conv_block3 = nn.Sequential( - # conv3_1 nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv3_2 nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv3_3 nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool3 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_3 # 12*12 self.conv_block4 = nn.Sequential( - # conv4_1 nn.Conv2d(256, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv4_2 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv4_3 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool4 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_4 # 6*6 self.conv_block5 = nn.Sequential( - # conv5_1 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv5_2 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv5_3 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool5 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_5 # 3*3 self.conv_block6 = nn.Sequential( - # fc6 nn.Conv2d(512, 4096, 3), # set the filter size and nor padding to make output into 1*1 nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) # 1*1 self.conv_block7 = nn.Sequential( - # fc7 nn.Conv2d(4096, 4096, 1), # set the filter size to make output into 1*1 nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.deconv_block8 = nn.Sequential( - # fc6-deconv nn.ConvTranspose2d(4096, 512, 3, stride=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) # 3*3 self.unpool_block9 = nn.Sequential( - # unpool5 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # usage unpool(output, indices) # 6*6 self.deconv_block10 = nn.Sequential( - # deconv5_1 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv5_2 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv5_3 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block11 = nn.Sequential( - # unpool4 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 12*12 self.deconv_block12 = nn.Sequential( - # deconv4_1 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv4_2 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv4_3 nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block13 = nn.Sequential( - # unpool3 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 24*24 self.deconv_block14 = nn.Sequential( - # deconv3_1 nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv3_2 nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv3_3 nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block15 = nn.Sequential( - # unpool2 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 48*48 self.deconv_block16 = nn.Sequential( - # deconv2_1 nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv2_2 nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block17 = nn.Sequential( - # unpool1 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 96*96 self.deconv_block18 = nn.Sequential( - # deconv1_1 nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv1_2 nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.seg_score19 = nn.Sequential( - # seg-score - nn.Conv2d(64, self.n_classes, 1), ) + nn.Conv2d(64, self.n_classes, 1), + ) if self.learned_billinear: raise NotImplementedError @@ -288,24 +270,30 @@ def forward(self, x): return out def init_vgg16_params(self, vgg16, copy_fc8=True): - blocks = [self.conv_block1, - self.conv_block2, - self.conv_block3, - self.conv_block4, - self.conv_block5] + blocks = [ + self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5, + ] ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] features = list(vgg16.features.children()) - i_layer = 0; + i_layer = 0 # copy convolutional filters from vgg16 for idx, conv_block in enumerate(blocks): - for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + for l1, l2 in zip(features[ranges[idx][0] : ranges[idx][1]], conv_block): if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): if i_layer == 0: - l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, - 2, :, - :]) / 3.0).view( - l2.weight.size()) + l2.weight.data = ( + ( + l1.weight.data[:, 0, :, :] + + l1.weight.data[:, 1, :, :] + + l1.weight.data[:, 2, :, :] + ) + / 3.0 + ).view(l2.weight.size()) l2.bias.data = l1.bias.data i_layer = i_layer + 1 else: @@ -317,7 +305,9 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): def get_seg_model(cfg, **kwargs): - assert cfg.MODEL.IN_CHANNELS==1, f"Patch deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" + assert ( + cfg.MODEL.IN_CHANNELS == 1 + ), f"Patch deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" model = patch_deconvnet(n_classes=cfg.DATASET.NUM_CLASSES) return model diff --git a/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py b/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py index 52b8eda2..1b42a19d 100644 --- a/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py +++ b/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py @@ -1,259 +1,241 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import torch.nn as nn -class patch_deconvnet_skip(nn.Module): +class patch_deconvnet_skip(nn.Module): def __init__(self, n_classes=4, learned_billinear=False): super(patch_deconvnet_skip, self).__init__() self.learned_billinear = learned_billinear self.n_classes = n_classes self.unpool = nn.MaxUnpool2d(2, stride=2) self.conv_block1 = nn.Sequential( - # conv1_1 nn.Conv2d(1, 64, 3, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv1_2 nn.Conv2d(64, 64, 3, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool1 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_1 # 48*48 self.conv_block2 = nn.Sequential( - # conv2_1 nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv2_2 nn.Conv2d(128, 128, 3, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool2 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_2 # 24*24 self.conv_block3 = nn.Sequential( - # conv3_1 nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv3_2 nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv3_3 nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool3 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_3 # 12*12 self.conv_block4 = nn.Sequential( - # conv4_1 nn.Conv2d(256, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv4_2 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv4_3 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool4 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_4 # 6*6 self.conv_block5 = nn.Sequential( - # conv5_1 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv5_2 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv5_3 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool5 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_5 # 3*3 self.conv_block6 = nn.Sequential( - # fc6 nn.Conv2d(512, 4096, 3), # set the filter size and nor padding to make output into 1*1 nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) # 1*1 self.conv_block7 = nn.Sequential( - # fc7 nn.Conv2d(4096, 4096, 1), # set the filter size to make output into 1*1 nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.deconv_block8 = nn.Sequential( - # fc6-deconv nn.ConvTranspose2d(4096, 512, 3, stride=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) # 3*3 self.unpool_block9 = nn.Sequential( - # unpool5 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # usage unpool(output, indices) # 6*6 self.deconv_block10 = nn.Sequential( - # deconv5_1 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv5_2 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv5_3 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block11 = nn.Sequential( - # unpool4 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 12*12 self.deconv_block12 = nn.Sequential( - # deconv4_1 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv4_2 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv4_3 nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block13 = nn.Sequential( - # unpool3 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 24*24 self.deconv_block14 = nn.Sequential( - # deconv3_1 nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv3_2 nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv3_3 nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block15 = nn.Sequential( - # unpool2 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 48*48 self.deconv_block16 = nn.Sequential( - # deconv2_1 nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv2_2 nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block17 = nn.Sequential( - # unpool1 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 96*96 self.deconv_block18 = nn.Sequential( - # deconv1_1 nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv1_2 nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.seg_score19 = nn.Sequential( - # seg-score - nn.Conv2d(64, self.n_classes, 1), ) + nn.Conv2d(64, self.n_classes, 1), + ) if self.learned_billinear: raise NotImplementedError @@ -273,39 +255,45 @@ def forward(self, x): conv6 = self.conv_block6(conv5) conv7 = self.conv_block7(conv6) conv8 = self.deconv_block8(conv7) + conv5 - conv9 = self.unpool(conv8,indices5, output_size=size4) + conv9 = self.unpool(conv8, indices5, output_size=size4) conv10 = self.deconv_block10(conv9) + conv4 - conv11 = self.unpool(conv10,indices4, output_size=size3) + conv11 = self.unpool(conv10, indices4, output_size=size3) conv12 = self.deconv_block12(conv11) + conv3 - conv13 = self.unpool(conv12,indices3, output_size=size2) + conv13 = self.unpool(conv12, indices3, output_size=size2) conv14 = self.deconv_block14(conv13) + conv2 - conv15 = self.unpool(conv14,indices2, output_size=size1) + conv15 = self.unpool(conv14, indices2, output_size=size1) conv16 = self.deconv_block16(conv15) + conv1 - conv17 = self.unpool(conv16,indices1, output_size=size0) + conv17 = self.unpool(conv16, indices1, output_size=size0) conv18 = self.deconv_block18(conv17) out = self.seg_score19(conv18) return out def init_vgg16_params(self, vgg16, copy_fc8=True): - blocks = [self.conv_block1, - self.conv_block2, - self.conv_block3, - self.conv_block4, - self.conv_block5] + blocks = [ + self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5, + ] ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] features = list(vgg16.features.children()) i_layer = 0 # copy convolutional filters from vgg16 for idx, conv_block in enumerate(blocks): - for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + for l1, l2 in zip(features[ranges[idx][0] : ranges[idx][1]], conv_block): if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): if i_layer == 0: - l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, - 2, :, - :]) / 3.0).view( - l2.weight.size()) + l2.weight.data = ( + ( + l1.weight.data[:, 0, :, :] + + l1.weight.data[:, 1, :, :] + + l1.weight.data[:, 2, :, :] + ) + / 3.0 + ).view(l2.weight.size()) l2.bias.data = l1.bias.data i_layer = i_layer + 1 else: @@ -317,7 +305,9 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): def get_seg_model(cfg, **kwargs): - assert cfg.MODEL.IN_CHANNELS==1, f"Patch deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" + assert ( + cfg.MODEL.IN_CHANNELS == 1 + ), f"Patch deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" model = patch_deconvnet_skip(n_classes=cfg.DATASET.NUM_CLASSES) return model diff --git a/cv_lib/cv_lib/segmentation/models/resnet_unet.py b/cv_lib/cv_lib/segmentation/models/resnet_unet.py index f226125f..dbf6eb13 100644 --- a/cv_lib/cv_lib/segmentation/models/resnet_unet.py +++ b/cv_lib/cv_lib/segmentation/models/resnet_unet.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import torch import torch.nn as nn import torch.nn.functional as F @@ -7,31 +10,49 @@ class FPAv2(nn.Module): def __init__(self, input_dim, output_dim): super(FPAv2, self).__init__() - self.glob = nn.Sequential(nn.AdaptiveAvgPool2d(1), - nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False)) - - self.down2_1 = nn.Sequential(nn.Conv2d(input_dim, input_dim, kernel_size=5, stride=2, padding=2, bias=False), - nn.BatchNorm2d(input_dim), - nn.ELU(True)) - self.down2_2 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=5, padding=2, bias=False), - nn.BatchNorm2d(output_dim), - nn.ELU(True)) - - self.down3_1 = nn.Sequential(nn.Conv2d(input_dim, input_dim, kernel_size=3, stride=2, padding=1, bias=False), - nn.BatchNorm2d(input_dim), - nn.ELU(True)) - self.down3_2 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=3, padding=1, bias=False), - nn.BatchNorm2d(output_dim), - nn.ELU(True)) - - self.conv1 = nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False), - nn.BatchNorm2d(output_dim), - nn.ELU(True)) + self.glob = nn.Sequential( + nn.AdaptiveAvgPool2d(1), + nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False), + ) + + self.down2_1 = nn.Sequential( + nn.Conv2d( + input_dim, input_dim, kernel_size=5, stride=2, padding=2, bias=False + ), + nn.BatchNorm2d(input_dim), + nn.ELU(True), + ) + self.down2_2 = nn.Sequential( + nn.Conv2d(input_dim, output_dim, kernel_size=5, padding=2, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True), + ) + + self.down3_1 = nn.Sequential( + nn.Conv2d( + input_dim, input_dim, kernel_size=3, stride=2, padding=1, bias=False + ), + nn.BatchNorm2d(input_dim), + nn.ELU(True), + ) + self.down3_2 = nn.Sequential( + nn.Conv2d(input_dim, output_dim, kernel_size=3, padding=1, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True), + ) + + self.conv1 = nn.Sequential( + nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False), + nn.BatchNorm2d(output_dim), + nn.ELU(True), + ) def forward(self, x): # x shape: 512, 16, 16 x_glob = self.glob(x) # 256, 1, 1 - x_glob = F.upsample(x_glob, scale_factor=16, mode='bilinear', align_corners=True) # 256, 16, 16 + x_glob = F.upsample( + x_glob, scale_factor=16, mode="bilinear", align_corners=True + ) # 256, 16, 16 d2 = self.down2_1(x) # 512, 8, 8 d3 = self.down3_1(d2) # 512, 4, 4 @@ -39,10 +60,14 @@ def forward(self, x): d2 = self.down2_2(d2) # 256, 8, 8 d3 = self.down3_2(d3) # 256, 4, 4 - d3 = F.upsample(d3, scale_factor=2, mode='bilinear', align_corners=True) # 256, 8, 8 + d3 = F.upsample( + d3, scale_factor=2, mode="bilinear", align_corners=True + ) # 256, 8, 8 d2 = d2 + d3 - d2 = F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True) # 256, 16, 16 + d2 = F.upsample( + d2, scale_factor=2, mode="bilinear", align_corners=True + ) # 256, 16, 16 x = self.conv1(x) # 256, 16, 16 x = x * d2 @@ -52,9 +77,18 @@ def forward(self, x): def conv3x3(input_dim, output_dim, rate=1): - return nn.Sequential(nn.Conv2d(input_dim, output_dim, kernel_size=3, dilation=rate, padding=rate, bias=False), - nn.BatchNorm2d(output_dim), - nn.ELU(True)) + return nn.Sequential( + nn.Conv2d( + input_dim, + output_dim, + kernel_size=3, + dilation=rate, + padding=rate, + bias=False, + ), + nn.BatchNorm2d(output_dim), + nn.ELU(True), + ) class SpatialAttention2d(nn.Module): @@ -73,8 +107,12 @@ class GAB(nn.Module): def __init__(self, input_dim, reduction=4): super(GAB, self).__init__() self.global_avgpool = nn.AdaptiveAvgPool2d(1) - self.conv1 = nn.Conv2d(input_dim, input_dim // reduction, kernel_size=1, stride=1) - self.conv2 = nn.Conv2d(input_dim // reduction, input_dim, kernel_size=1, stride=1) + self.conv1 = nn.Conv2d( + input_dim, input_dim // reduction, kernel_size=1, stride=1 + ) + self.conv2 = nn.Conv2d( + input_dim // reduction, input_dim, kernel_size=1, stride=1 + ) self.relu = nn.ReLU(inplace=True) self.sigmoid = nn.Sigmoid() @@ -94,7 +132,7 @@ def __init__(self, in_channels, channels, out_channels): self.c_att = GAB(out_channels, 16) def forward(self, x, e=None): - x = F.upsample(input=x, scale_factor=2, mode='bilinear', align_corners=True) + x = F.upsample(input=x, scale_factor=2, mode="bilinear", align_corners=True) if e is not None: x = torch.cat([x, e], 1) x = self.conv1(x) @@ -143,22 +181,14 @@ def __init__(self, n_classes=1): super(Res34Unetv4, self).__init__() self.resnet = torchvision.models.resnet34(True) - self.conv1 = nn.Sequential( - self.resnet.conv1, - self.resnet.bn1, - self.resnet.relu) + self.conv1 = nn.Sequential(self.resnet.conv1, self.resnet.bn1, self.resnet.relu) - self.encode2 = nn.Sequential(self.resnet.layer1, - SCse(64)) - self.encode3 = nn.Sequential(self.resnet.layer2, - SCse(128)) - self.encode4 = nn.Sequential(self.resnet.layer3, - SCse(256)) - self.encode5 = nn.Sequential(self.resnet.layer4, - SCse(512)) + self.encode2 = nn.Sequential(self.resnet.layer1, SCse(64)) + self.encode3 = nn.Sequential(self.resnet.layer2, SCse(128)) + self.encode4 = nn.Sequential(self.resnet.layer3, SCse(256)) + self.encode5 = nn.Sequential(self.resnet.layer4, SCse(512)) - self.center = nn.Sequential(FPAv2(512, 256), - nn.MaxPool2d(2, 2)) + self.center = nn.Sequential(FPAv2(512, 256), nn.MaxPool2d(2, 2)) self.decode5 = Decoderv2(256, 512, 64) self.decode4 = Decoderv2(64, 256, 64) @@ -166,9 +196,11 @@ def __init__(self, n_classes=1): self.decode2 = Decoderv2(64, 64, 64) self.decode1 = Decoder(64, 32, 64) - self.logit = nn.Sequential(nn.Conv2d(320, 64, kernel_size=3, padding=1), - nn.ELU(True), - nn.Conv2d(64, n_classes, kernel_size=1, bias=False)) + self.logit = nn.Sequential( + nn.Conv2d(320, 64, kernel_size=3, padding=1), + nn.ELU(True), + nn.Conv2d(64, n_classes, kernel_size=1, bias=False), + ) def forward(self, x): # x: (batch_size, 3, 256, 256) @@ -187,11 +219,16 @@ def forward(self, x): d2 = self.decode2(d3, e2) # 64, 128, 128 d1 = self.decode1(d2) # 64, 256, 256 - f = torch.cat((d1, - F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True), - F.upsample(d3, scale_factor=4, mode='bilinear', align_corners=True), - F.upsample(d4, scale_factor=8, mode='bilinear', align_corners=True), - F.upsample(d5, scale_factor=16, mode='bilinear', align_corners=True)), 1) # 320, 256, 256 + f = torch.cat( + ( + d1, + F.upsample(d2, scale_factor=2, mode="bilinear", align_corners=True), + F.upsample(d3, scale_factor=4, mode="bilinear", align_corners=True), + F.upsample(d4, scale_factor=8, mode="bilinear", align_corners=True), + F.upsample(d5, scale_factor=16, mode="bilinear", align_corners=True), + ), + 1, + ) # 320, 256, 256 logit = self.logit(f) # 1, 256, 256 @@ -204,22 +241,14 @@ def __init__(self): super(Res34Unetv3, self).__init__() self.resnet = torchvision.models.resnet34(True) - self.conv1 = nn.Sequential( - self.resnet.conv1, - self.resnet.bn1, - self.resnet.relu) + self.conv1 = nn.Sequential(self.resnet.conv1, self.resnet.bn1, self.resnet.relu) - self.encode2 = nn.Sequential(self.resnet.layer1, - SCse(64)) - self.encode3 = nn.Sequential(self.resnet.layer2, - SCse(128)) - self.encode4 = nn.Sequential(self.resnet.layer3, - SCse(256)) - self.encode5 = nn.Sequential(self.resnet.layer4, - SCse(512)) + self.encode2 = nn.Sequential(self.resnet.layer1, SCse(64)) + self.encode3 = nn.Sequential(self.resnet.layer2, SCse(128)) + self.encode4 = nn.Sequential(self.resnet.layer3, SCse(256)) + self.encode5 = nn.Sequential(self.resnet.layer4, SCse(512)) - self.center = nn.Sequential(FPAv2(512, 256), - nn.MaxPool2d(2, 2)) + self.center = nn.Sequential(FPAv2(512, 256), nn.MaxPool2d(2, 2)) self.decode5 = Decoderv2(256, 512, 64) self.decode4 = Decoderv2(64, 256, 64) @@ -233,13 +262,13 @@ def __init__(self): self.fuse_pixel = conv3x3(320, 64) self.logit_pixel = nn.Conv2d(64, 1, kernel_size=1, bias=False) - self.fuse_image = nn.Sequential(nn.Linear(512, 64), - nn.ELU(True)) - self.logit_image = nn.Sequential(nn.Linear(64, 1), - nn.Sigmoid()) - self.logit = nn.Sequential(nn.Conv2d(128, 64, kernel_size=3, padding=1, bias=False), - nn.ELU(True), - nn.Conv2d(64, 1, kernel_size=1, bias=False)) + self.fuse_image = nn.Sequential(nn.Linear(512, 64), nn.ELU(True)) + self.logit_image = nn.Sequential(nn.Linear(64, 1), nn.Sigmoid()) + self.logit = nn.Sequential( + nn.Conv2d(128, 64, kernel_size=3, padding=1, bias=False), + nn.ELU(True), + nn.Conv2d(64, 1, kernel_size=1, bias=False), + ) def forward(self, x): # x: (batch_size, 3, 256, 256) @@ -262,11 +291,16 @@ def forward(self, x): d2 = self.decode2(d3, e2) # 64, 128, 128 d1 = self.decode1(d2) # 64, 256, 256 - f = torch.cat((d1, - F.upsample(d2, scale_factor=2, mode='bilinear', align_corners=True), - F.upsample(d3, scale_factor=4, mode='bilinear', align_corners=True), - F.upsample(d4, scale_factor=8, mode='bilinear', align_corners=True), - F.upsample(d5, scale_factor=16, mode='bilinear', align_corners=True)), 1) # 320, 256, 256 + f = torch.cat( + ( + d1, + F.upsample(d2, scale_factor=2, mode="bilinear", align_corners=True), + F.upsample(d3, scale_factor=4, mode="bilinear", align_corners=True), + F.upsample(d4, scale_factor=8, mode="bilinear", align_corners=True), + F.upsample(d5, scale_factor=16, mode="bilinear", align_corners=True), + ), + 1, + ) # 320, 256, 256 f = self.dropout2d(f) # segmentation process @@ -278,9 +312,18 @@ def forward(self, x): logit_image = self.logit_image(fuse_image) # 1 # combine segmentation and classification - fuse = torch.cat([fuse_pixel, - F.upsample(fuse_image.view(batch_size, -1, 1, 1), scale_factor=256, mode='bilinear', - align_corners=True)], 1) # 128, 256, 256 + fuse = torch.cat( + [ + fuse_pixel, + F.upsample( + fuse_image.view(batch_size, -1, 1, 1), + scale_factor=256, + mode="bilinear", + align_corners=True, + ), + ], + 1, + ) # 128, 256, 256 logit = self.logit(fuse) # 1, 256, 256 return logit, logit_pixel, logit_image.view(-1) @@ -295,28 +338,26 @@ def __init__(self): self.conv1 = nn.Sequential( nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False), self.resnet.bn1, - self.resnet.relu) + self.resnet.relu, + ) - self.encode2 = nn.Sequential(self.resnet.layer1, - SCse(64)) - self.encode3 = nn.Sequential(self.resnet.layer2, - SCse(128)) - self.encode4 = nn.Sequential(self.resnet.layer3, - SCse(256)) - self.encode5 = nn.Sequential(self.resnet.layer4, - SCse(512)) + self.encode2 = nn.Sequential(self.resnet.layer1, SCse(64)) + self.encode3 = nn.Sequential(self.resnet.layer2, SCse(128)) + self.encode4 = nn.Sequential(self.resnet.layer3, SCse(256)) + self.encode5 = nn.Sequential(self.resnet.layer4, SCse(512)) - self.center = nn.Sequential(FPAv2(512, 256), - nn.MaxPool2d(2, 2)) + self.center = nn.Sequential(FPAv2(512, 256), nn.MaxPool2d(2, 2)) self.decode5 = Decoderv2(256, 512, 64) self.decode4 = Decoderv2(64, 256, 64) self.decode3 = Decoderv2(64, 128, 64) self.decode2 = Decoderv2(64, 64, 64) - self.logit = nn.Sequential(nn.Conv2d(256, 32, kernel_size=3, padding=1), - nn.ELU(True), - nn.Conv2d(32, 1, kernel_size=1, bias=False)) + self.logit = nn.Sequential( + nn.Conv2d(256, 32, kernel_size=3, padding=1), + nn.ELU(True), + nn.Conv2d(32, 1, kernel_size=1, bias=False), + ) def forward(self, x): # x: batch_size, 3, 128, 128 @@ -333,10 +374,15 @@ def forward(self, x): d3 = self.decode3(d4, e3) # 64, 64, 64 d2 = self.decode2(d3, e2) # 64, 128, 128 - f = torch.cat((d2, - F.upsample(d3, scale_factor=2, mode='bilinear', align_corners=True), - F.upsample(d4, scale_factor=4, mode='bilinear', align_corners=True), - F.upsample(d5, scale_factor=8, mode='bilinear', align_corners=True)), 1) # 256, 128, 128 + f = torch.cat( + ( + d2, + F.upsample(d3, scale_factor=2, mode="bilinear", align_corners=True), + F.upsample(d4, scale_factor=4, mode="bilinear", align_corners=True), + F.upsample(d5, scale_factor=8, mode="bilinear", align_corners=True), + ), + 1, + ) # 256, 128, 128 f = F.dropout2d(f, p=0.4) logit = self.logit(f) # 1, 128, 128 @@ -345,6 +391,8 @@ def forward(self, x): def get_seg_model(cfg, **kwargs): - assert cfg.MODEL.IN_CHANNELS==3, f"SEResnet Unet deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 3 for cfg.MODEL.IN_CHANNELS" + assert ( + cfg.MODEL.IN_CHANNELS == 3 + ), f"SEResnet Unet deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 3 for cfg.MODEL.IN_CHANNELS" model = Res34Unetv4(n_classes=cfg.DATASET.NUM_CLASSES) - return model \ No newline at end of file + return model diff --git a/cv_lib/cv_lib/segmentation/models/section_deconvnet.py b/cv_lib/cv_lib/segmentation/models/section_deconvnet.py index d4edfd45..b8cf73e8 100644 --- a/cv_lib/cv_lib/segmentation/models/section_deconvnet.py +++ b/cv_lib/cv_lib/segmentation/models/section_deconvnet.py @@ -1,259 +1,241 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import torch.nn as nn -class section_deconvnet(nn.Module): +class section_deconvnet(nn.Module): def __init__(self, n_classes=4, learned_billinear=False): super(section_deconvnet, self).__init__() self.learned_billinear = learned_billinear self.n_classes = n_classes self.unpool = nn.MaxUnpool2d(2, stride=2) self.conv_block1 = nn.Sequential( - # conv1_1 nn.Conv2d(1, 64, 3, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv1_2 nn.Conv2d(64, 64, 3, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool1 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_1 # 48*48 self.conv_block2 = nn.Sequential( - # conv2_1 nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv2_2 nn.Conv2d(128, 128, 3, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool2 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_2 # 24*24 self.conv_block3 = nn.Sequential( - # conv3_1 nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv3_2 nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv3_3 nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool3 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_3 # 12*12 self.conv_block4 = nn.Sequential( - # conv4_1 nn.Conv2d(256, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv4_2 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv4_3 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool4 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_4 # 6*6 self.conv_block5 = nn.Sequential( - # conv5_1 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv5_2 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv5_3 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool5 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_5 # 3*3 self.conv_block6 = nn.Sequential( - # fc6 nn.Conv2d(512, 4096, 3), # set the filter size and nor padding to make output into 1*1 nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) # 1*1 self.conv_block7 = nn.Sequential( - # fc7 nn.Conv2d(4096, 4096, 1), # set the filter size to make output into 1*1 nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.deconv_block8 = nn.Sequential( - # fc6-deconv nn.ConvTranspose2d(4096, 512, 3, stride=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) # 3*3 self.unpool_block9 = nn.Sequential( - # unpool5 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # usage unpool(output, indices) # 6*6 self.deconv_block10 = nn.Sequential( - # deconv5_1 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv5_2 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv5_3 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block11 = nn.Sequential( - # unpool4 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 12*12 self.deconv_block12 = nn.Sequential( - # deconv4_1 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv4_2 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv4_3 nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block13 = nn.Sequential( - # unpool3 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 24*24 self.deconv_block14 = nn.Sequential( - # deconv3_1 nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv3_2 nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv3_3 nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block15 = nn.Sequential( - # unpool2 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 48*48 self.deconv_block16 = nn.Sequential( - # deconv2_1 nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv2_2 nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block17 = nn.Sequential( - # unpool1 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 96*96 self.deconv_block18 = nn.Sequential( - # deconv1_1 nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv1_2 nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.seg_score19 = nn.Sequential( - # seg-score - nn.Conv2d(64, self.n_classes, 1), ) + nn.Conv2d(64, self.n_classes, 1), + ) if self.learned_billinear: raise NotImplementedError @@ -272,40 +254,46 @@ def forward(self, x): conv6 = self.conv_block6(conv5) conv7 = self.conv_block7(conv6) - conv8 = self.deconv_block8(conv7) - conv9 = self.unpool(conv8,indices5, output_size=size4) - conv10 = self.deconv_block10(conv9) - conv11 = self.unpool(conv10,indices4, output_size=size3) - conv12 = self.deconv_block12(conv11) - conv13 = self.unpool(conv12,indices3, output_size=size2) - conv14 = self.deconv_block14(conv13) - conv15 = self.unpool(conv14,indices2, output_size=size1) - conv16 = self.deconv_block16(conv15) - conv17 = self.unpool(conv16,indices1, output_size=size0) + conv8 = self.deconv_block8(conv7) + conv9 = self.unpool(conv8, indices5, output_size=size4) + conv10 = self.deconv_block10(conv9) + conv11 = self.unpool(conv10, indices4, output_size=size3) + conv12 = self.deconv_block12(conv11) + conv13 = self.unpool(conv12, indices3, output_size=size2) + conv14 = self.deconv_block14(conv13) + conv15 = self.unpool(conv14, indices2, output_size=size1) + conv16 = self.deconv_block16(conv15) + conv17 = self.unpool(conv16, indices1, output_size=size0) conv18 = self.deconv_block18(conv17) out = self.seg_score19(conv18) return out def init_vgg16_params(self, vgg16, copy_fc8=True): - blocks = [self.conv_block1, - self.conv_block2, - self.conv_block3, - self.conv_block4, - self.conv_block5] + blocks = [ + self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5, + ] ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] features = list(vgg16.features.children()) - i_layer = 0; + i_layer = 0 # copy convolutional filters from vgg16 for idx, conv_block in enumerate(blocks): - for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + for l1, l2 in zip(features[ranges[idx][0] : ranges[idx][1]], conv_block): if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): if i_layer == 0: - l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, - 2, :, - :]) / 3.0).view( - l2.weight.size()) + l2.weight.data = ( + ( + l1.weight.data[:, 0, :, :] + + l1.weight.data[:, 1, :, :] + + l1.weight.data[:, 2, :, :] + ) + / 3.0 + ).view(l2.weight.size()) l2.bias.data = l1.bias.data i_layer = i_layer + 1 else: @@ -317,6 +305,8 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): def get_seg_model(cfg, **kwargs): - assert cfg.MODEL.IN_CHANNELS==1, f"Section deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" + assert ( + cfg.MODEL.IN_CHANNELS == 1 + ), f"Section deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" model = section_deconvnet(n_classes=cfg.DATASET.NUM_CLASSES) - return model \ No newline at end of file + return model diff --git a/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py b/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py index 602d1f4a..cfcfbcd1 100644 --- a/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py +++ b/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py @@ -1,259 +1,241 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import torch.nn as nn -class section_deconvnet_skip(nn.Module): +class section_deconvnet_skip(nn.Module): def __init__(self, n_classes=4, learned_billinear=False): super(section_deconvnet_skip, self).__init__() self.learned_billinear = learned_billinear self.n_classes = n_classes self.unpool = nn.MaxUnpool2d(2, stride=2) self.conv_block1 = nn.Sequential( - # conv1_1 nn.Conv2d(1, 64, 3, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv1_2 nn.Conv2d(64, 64, 3, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool1 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_1 # 48*48 self.conv_block2 = nn.Sequential( - # conv2_1 nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv2_2 nn.Conv2d(128, 128, 3, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool2 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_2 # 24*24 self.conv_block3 = nn.Sequential( - # conv3_1 nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv3_2 nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv3_3 nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool3 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_3 # 12*12 self.conv_block4 = nn.Sequential( - # conv4_1 nn.Conv2d(256, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv4_2 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv4_3 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool4 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_4 # 6*6 self.conv_block5 = nn.Sequential( - # conv5_1 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv5_2 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # conv5_3 nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # pool5 - nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), ) + nn.MaxPool2d(2, stride=2, return_indices=True, ceil_mode=True), + ) # it returns outputs and pool_indices_5 # 3*3 self.conv_block6 = nn.Sequential( - # fc6 nn.Conv2d(512, 4096, 3), # set the filter size and nor padding to make output into 1*1 nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) # 1*1 self.conv_block7 = nn.Sequential( - # fc7 nn.Conv2d(4096, 4096, 1), # set the filter size to make output into 1*1 nn.BatchNorm2d(4096, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.deconv_block8 = nn.Sequential( - # fc6-deconv nn.ConvTranspose2d(4096, 512, 3, stride=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) # 3*3 self.unpool_block9 = nn.Sequential( - # unpool5 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # usage unpool(output, indices) # 6*6 self.deconv_block10 = nn.Sequential( - # deconv5_1 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv5_2 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv5_3 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block11 = nn.Sequential( - # unpool4 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 12*12 self.deconv_block12 = nn.Sequential( - # deconv4_1 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv4_2 nn.ConvTranspose2d(512, 512, 3, stride=1, padding=1), nn.BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv4_3 nn.ConvTranspose2d(512, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block13 = nn.Sequential( - # unpool3 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 24*24 self.deconv_block14 = nn.Sequential( - # deconv3_1 nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv3_2 nn.ConvTranspose2d(256, 256, 3, stride=1, padding=1), nn.BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv3_3 nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block15 = nn.Sequential( - # unpool2 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 48*48 self.deconv_block16 = nn.Sequential( - # deconv2_1 nn.ConvTranspose2d(128, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv2_2 nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.unpool_block17 = nn.Sequential( - # unpool1 - nn.MaxUnpool2d(2, stride=2), ) + nn.MaxUnpool2d(2, stride=2), + ) # 96*96 self.deconv_block18 = nn.Sequential( - # deconv1_1 nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), nn.ReLU(inplace=True), - # deconv1_2 nn.ConvTranspose2d(64, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True), - nn.ReLU(inplace=True), ) + nn.ReLU(inplace=True), + ) self.seg_score19 = nn.Sequential( - # seg-score - nn.Conv2d(64, self.n_classes, 1), ) + nn.Conv2d(64, self.n_classes, 1), + ) if self.learned_billinear: raise NotImplementedError @@ -273,39 +255,45 @@ def forward(self, x): conv6 = self.conv_block6(conv5) conv7 = self.conv_block7(conv6) conv8 = self.deconv_block8(conv7) + conv5 - conv9 = self.unpool(conv8,indices5, output_size=size4) + conv9 = self.unpool(conv8, indices5, output_size=size4) conv10 = self.deconv_block10(conv9) + conv4 - conv11 = self.unpool(conv10,indices4, output_size=size3) + conv11 = self.unpool(conv10, indices4, output_size=size3) conv12 = self.deconv_block12(conv11) + conv3 - conv13 = self.unpool(conv12,indices3, output_size=size2) + conv13 = self.unpool(conv12, indices3, output_size=size2) conv14 = self.deconv_block14(conv13) + conv2 - conv15 = self.unpool(conv14,indices2, output_size=size1) + conv15 = self.unpool(conv14, indices2, output_size=size1) conv16 = self.deconv_block16(conv15) + conv1 - conv17 = self.unpool(conv16,indices1, output_size=size0) + conv17 = self.unpool(conv16, indices1, output_size=size0) conv18 = self.deconv_block18(conv17) out = self.seg_score19(conv18) - + return out def init_vgg16_params(self, vgg16, copy_fc8=True): - blocks = [self.conv_block1, - self.conv_block2, - self.conv_block3, - self.conv_block4, - self.conv_block5] + blocks = [ + self.conv_block1, + self.conv_block2, + self.conv_block3, + self.conv_block4, + self.conv_block5, + ] ranges = [[0, 4], [5, 9], [10, 16], [17, 23], [24, 29]] features = list(vgg16.features.children()) - i_layer = 0; + i_layer = 0 # copy convolutional filters from vgg16 for idx, conv_block in enumerate(blocks): - for l1, l2 in zip(features[ranges[idx][0]:ranges[idx][1]], conv_block): + for l1, l2 in zip(features[ranges[idx][0] : ranges[idx][1]], conv_block): if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): if i_layer == 0: - l2.weight.data = ((l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, - 2, :, - :]) / 3.0).view( - l2.weight.size()) + l2.weight.data = ( + ( + l1.weight.data[:, 0, :, :] + + l1.weight.data[:, 1, :, :] + + l1.weight.data[:, 2, :, :] + ) + / 3.0 + ).view(l2.weight.size()) l2.bias.data = l1.bias.data i_layer = i_layer + 1 else: @@ -315,7 +303,10 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): l2.bias.data = l1.bias.data i_layer = i_layer + 1 + def get_seg_model(cfg, **kwargs): - assert cfg.MODEL.IN_CHANNELS==1, f"Section deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" + assert ( + cfg.MODEL.IN_CHANNELS == 1 + ), f"Section deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" model = section_deconvnet_skip(n_classes=cfg.DATASET.NUM_CLASSES) - return model \ No newline at end of file + return model diff --git a/cv_lib/cv_lib/segmentation/models/unet.py b/cv_lib/cv_lib/segmentation/models/unet.py index 3c37d983..95143856 100644 --- a/cv_lib/cv_lib/segmentation/models/unet.py +++ b/cv_lib/cv_lib/segmentation/models/unet.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + """ Taken from https://github.com/milesial/Pytorch-UNet """ @@ -9,10 +12,9 @@ from torch.nn import init - - class double_conv(nn.Module): - '''(conv => BN => ReLU) * 2''' + """(conv => BN => ReLU) * 2""" + def __init__(self, in_ch, out_ch): super(double_conv, self).__init__() self.conv = nn.Sequential( @@ -21,7 +23,7 @@ def __init__(self, in_ch, out_ch): nn.ReLU(inplace=True), nn.Conv2d(out_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), - nn.ReLU(inplace=True) + nn.ReLU(inplace=True), ) def forward(self, x): @@ -42,10 +44,7 @@ def forward(self, x): class down(nn.Module): def __init__(self, in_ch, out_ch): super(down, self).__init__() - self.mpconv = nn.Sequential( - nn.MaxPool2d(2), - double_conv(in_ch, out_ch) - ) + self.mpconv = nn.Sequential(nn.MaxPool2d(2), double_conv(in_ch, out_ch)) def forward(self, x): x = self.mpconv(x) @@ -57,22 +56,21 @@ def __init__(self, in_ch, out_ch, bilinear=True): super(up, self).__init__() if bilinear: - self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) + self.up = nn.Upsample(scale_factor=2, mode="bilinear", align_corners=True) else: - self.up = nn.ConvTranspose2d(in_ch//2, in_ch//2, 2, stride=2) + self.up = nn.ConvTranspose2d(in_ch // 2, in_ch // 2, 2, stride=2) self.conv = double_conv(in_ch, out_ch) def forward(self, x1, x2): x1 = self.up(x1) - + # input is CHW diffY = x2.size()[2] - x1.size()[2] diffX = x2.size()[3] - x1.size()[3] - x1 = F.pad(x1, (diffX // 2, diffX - diffX//2, - diffY // 2, diffY - diffY//2)) - + x1 = F.pad(x1, (diffX // 2, diffX - diffX // 2, diffY // 2, diffY - diffY // 2)) + x = torch.cat([x2, x1], dim=1) x = self.conv(x) return x diff --git a/cv_lib/cv_lib/segmentation/models/utils.py b/cv_lib/cv_lib/segmentation/models/utils.py index 359544b9..591ddd60 100644 --- a/cv_lib/cv_lib/segmentation/models/utils.py +++ b/cv_lib/cv_lib/segmentation/models/utils.py @@ -1,23 +1,40 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import torch import torch.nn as nn import torch.nn.functional as F class conv2DBatchNorm(nn.Module): - def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): + def __init__( + self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1 + ): super(conv2DBatchNorm, self).__init__() if dilation > 1: - conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias, dilation=dilation) + conv_mod = nn.Conv2d( + int(in_channels), + int(n_filters), + kernel_size=k_size, + padding=padding, + stride=stride, + bias=bias, + dilation=dilation, + ) else: - conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias, dilation=1) - - - self.cb_unit = nn.Sequential(conv_mod, - nn.BatchNorm2d(int(n_filters)),) + conv_mod = nn.Conv2d( + int(in_channels), + int(n_filters), + kernel_size=k_size, + padding=padding, + stride=stride, + bias=bias, + dilation=1, + ) + + self.cb_unit = nn.Sequential(conv_mod, nn.BatchNorm2d(int(n_filters)),) def forward(self, inputs): outputs = self.cb_unit(inputs) @@ -25,12 +42,20 @@ def forward(self, inputs): class deconv2DBatchNorm(nn.Module): - def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): super(deconv2DBatchNorm, self).__init__() - self.dcb_unit = nn.Sequential(nn.ConvTranspose2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias), - nn.BatchNorm2d(int(n_filters)),) + self.dcb_unit = nn.Sequential( + nn.ConvTranspose2d( + int(in_channels), + int(n_filters), + kernel_size=k_size, + padding=padding, + stride=stride, + bias=bias, + ), + nn.BatchNorm2d(int(n_filters)), + ) def forward(self, inputs): outputs = self.dcb_unit(inputs) @@ -38,20 +63,36 @@ def forward(self, inputs): class conv2DBatchNormRelu(nn.Module): - def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): + def __init__( + self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1 + ): super(conv2DBatchNormRelu, self).__init__() if dilation > 1: - conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias, dilation=dilation) + conv_mod = nn.Conv2d( + int(in_channels), + int(n_filters), + kernel_size=k_size, + padding=padding, + stride=stride, + bias=bias, + dilation=dilation, + ) else: - conv_mod = nn.Conv2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias, dilation=1) - - self.cbr_unit = nn.Sequential(conv_mod, - nn.BatchNorm2d(int(n_filters)), - nn.ReLU(inplace=True),) + conv_mod = nn.Conv2d( + int(in_channels), + int(n_filters), + kernel_size=k_size, + padding=padding, + stride=stride, + bias=bias, + dilation=1, + ) + + self.cbr_unit = nn.Sequential( + conv_mod, nn.BatchNorm2d(int(n_filters)), nn.ReLU(inplace=True), + ) def forward(self, inputs): outputs = self.cbr_unit(inputs) @@ -62,10 +103,18 @@ class deconv2DBatchNormRelu(nn.Module): def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): super(deconv2DBatchNormRelu, self).__init__() - self.dcbr_unit = nn.Sequential(nn.ConvTranspose2d(int(in_channels), int(n_filters), kernel_size=k_size, - padding=padding, stride=stride, bias=bias), - nn.BatchNorm2d(int(n_filters)), - nn.ReLU(inplace=True),) + self.dcbr_unit = nn.Sequential( + nn.ConvTranspose2d( + int(in_channels), + int(n_filters), + kernel_size=k_size, + padding=padding, + stride=stride, + bias=bias, + ), + nn.BatchNorm2d(int(n_filters)), + nn.ReLU(inplace=True), + ) def forward(self, inputs): outputs = self.dcbr_unit(inputs) diff --git a/cv_lib/cv_lib/segmentation/penobscot/engine.py b/cv_lib/cv_lib/segmentation/penobscot/engine.py index 2d733a45..634fd6ce 100644 --- a/cv_lib/cv_lib/segmentation/penobscot/engine.py +++ b/cv_lib/cv_lib/segmentation/penobscot/engine.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import torch from ignite.engine.engine import Engine diff --git a/cv_lib/cv_lib/segmentation/utils.py b/cv_lib/cv_lib/segmentation/utils.py index 2c31143a..2410fa9a 100644 --- a/cv_lib/cv_lib/segmentation/utils.py +++ b/cv_lib/cv_lib/segmentation/utils.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import numpy as np from deepseismic_interpretation.dutchf3.data import decode_segmap from os import path @@ -9,14 +12,20 @@ def _chw_to_hwc(image_array_numpy): return np.moveaxis(image_array_numpy, 0, -1) -def save_images( - pred_dict, output_dir, num_classes, colours, extra_identifier="" -): +def save_images(pred_dict, output_dir, num_classes, colours, extra_identifier=""): for id in pred_dict: - save_image(pred_dict[id].unsqueeze(0).cpu().numpy(), output_dir, num_classes, colours, extra_identifier=extra_identifier) + save_image( + pred_dict[id].unsqueeze(0).cpu().numpy(), + output_dir, + num_classes, + colours, + extra_identifier=extra_identifier, + ) -def save_image(image_numpy_array, output_dir, num_classes, colours, extra_identifier=""): +def save_image( + image_numpy_array, output_dir, num_classes, colours, extra_identifier="" +): """Save segmentation map as image Args: @@ -27,14 +36,10 @@ def save_image(image_numpy_array, output_dir, num_classes, colours, extra_identi extra_identifier (str, optional): [description]. Defaults to "". """ im_array = decode_segmap( - image_numpy_array, - n_classes=num_classes, - label_colours=colours, + image_numpy_array, n_classes=num_classes, label_colours=colours, ) im = pipe( - (im_array * 255).astype(np.uint8).squeeze(), - _chw_to_hwc, - Image.fromarray, + (im_array * 255).astype(np.uint8).squeeze(), _chw_to_hwc, Image.fromarray, ) filename = path.join(output_dir, f"{id}_{extra_identifier}.png") - im.save(filename) \ No newline at end of file + im.save(filename) diff --git a/cv_lib/setup.py b/cv_lib/setup.py index 9f170040..23353ae0 100644 --- a/cv_lib/setup.py +++ b/cv_lib/setup.py @@ -1,4 +1,8 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# /* spell-checker: disable */ import os + try: from setuptools import setup, find_packages except ImportError: @@ -6,16 +10,16 @@ # Package meta-data. -NAME = 'cv_lib' -DESCRIPTION = 'A library for computer vision' -URL = '' -EMAIL = 'msalvaris@users.noreply.github.com' -AUTHOR = 'AUTHORS.md' -LICENSE = '' +NAME = "cv_lib" +DESCRIPTION = "A library for computer vision" +URL = "" +EMAIL = "msalvaris@users.noreply.github.com" +AUTHOR = "AUTHORS.md" +LICENSE = "" LONG_DESCRIPTION = DESCRIPTION -with open('requirements.txt') as f: +with open("requirements.txt") as f: requirements = f.read().splitlines() @@ -23,13 +27,13 @@ # Load the package's __version__.py module as a dictionary. about = {} -with open(os.path.join(here, NAME, '__version__.py')) as f: +with open(os.path.join(here, NAME, "__version__.py")) as f: exec(f.read(), about) setup( name=NAME, - version=about['__version__'], + version=about["__version__"], url=URL, license=LICENSE, author=AUTHOR, @@ -41,10 +45,10 @@ include_package_data=True, install_requires=requirements, classifiers=[ - 'Development Status :: 1 - Alpha', - 'Intended Audience :: Data Scientists & Developers', - 'Operating System :: POSIX', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python :: 3.6', - ] + "Development Status :: 1 - Alpha", + "Intended Audience :: Data Scientists & Developers", + "Operating System :: POSIX", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3.6", + ], ) diff --git a/deepseismic/__init__.py b/deepseismic/__init__.py index 8dc07e06..0f06c3a5 100644 --- a/deepseismic/__init__.py +++ b/deepseismic/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from . import cli, forward, velocity __all__ = ["cli", "forward", "velocity"] diff --git a/deepseismic/cli/__init__.py b/deepseismic/cli/__init__.py index 1b0db11d..1497eac6 100644 --- a/deepseismic/cli/__init__.py +++ b/deepseismic/cli/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from functools import partial import click diff --git a/deepseismic/cli/forward.py b/deepseismic/cli/forward.py index 0ef69d39..77b94ef7 100644 --- a/deepseismic/cli/forward.py +++ b/deepseismic/cli/forward.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from functools import partial import click @@ -13,16 +16,10 @@ @click.argument("input", type=click.Path()) @click.argument("output", type=click.Path()) @click.option( - "-d", - "--duration", - default=1000.0, - type=float, - help="Simulation duration (in ms)", + "-d", "--duration", default=1000.0, type=float, help="Simulation duration (in ms)", ) @click.option("-dt", default=2.0, type=float, help="Time increment (in ms)") -@click.option( - "--n-pml", default=10, type=int, help="PML size (in grid points)" -) +@click.option("--n-pml", default=10, type=int, help="PML size (in grid points)") @click.option( "--n-receivers", default=11, @@ -30,9 +27,7 @@ help="Number of receivers per horizontal dimension", ) @click.option("--space-order", default=2, type=int, help="Space order") -@click.option( - "--spacing", default=10.0, type=float, help="Spacing between grid points" -) +@click.option("--spacing", default=10.0, type=float, help="Spacing between grid points") @click.pass_context def fwd( ctx, @@ -58,9 +53,7 @@ def fwd( @fwd.command() -@click.option( - "-f0", default=0.01, type=float, help="Source peak frequency (in kHz)" -) +@click.option("-f0", default=0.01, type=float, help="Source peak frequency (in kHz)") @click.pass_context def ricker(ctx, f0: float): """Ricker source""" @@ -84,11 +77,7 @@ def ricker(ctx, f0: float): start=0.0, stop=ctx.obj["duration"], step=ctx.obj["dt"] ) source = RickerSource( - name="source", - grid=model.grid, - f0=f0, - npoint=1, - time_range=time_range, + name="source", grid=model.grid, f0=f0, npoint=1, time_range=time_range, ) source.coordinates.data[0, :] = np.array(model.domain_size) * 0.5 source.coordinates.data[0, -1] = 0.0 @@ -107,9 +96,7 @@ def ricker(ctx, f0: float): ) ) for d in range(len(receivers_coords)): - receivers.coordinates.data[:, d] = receivers_coords[ - d - ].flatten() + receivers.coordinates.data[:, d] = receivers_coords[d].flatten() receivers.coordinates.data[:, -1] = 0.0 output_group = output_file.create_group(input_group_name) for input_dataset_name, vp in input_group.items(): @@ -117,7 +104,5 @@ def ricker(ctx, f0: float): seismograms = model.solve( source=source, receivers=receivers, time_range=time_range ) - output_group.create_dataset( - input_dataset_name, data=seismograms - ) + output_group.create_dataset(input_dataset_name, data=seismograms) bar.update(1) diff --git a/deepseismic/cli/velocity.py b/deepseismic/cli/velocity.py index 1c87c340..c340c7d9 100644 --- a/deepseismic/cli/velocity.py +++ b/deepseismic/cli/velocity.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from functools import partial from itertools import islice from typing import Tuple @@ -13,9 +16,7 @@ @click.group() @click.argument("output", type=click.Path()) @click.option( - "--append/--no-append", - default=False, - help="Whether to append to output file", + "--append/--no-append", default=False, help="Whether to append to output file", ) @click.option("-n", default=1, type=int, help="Number of simulations") @click.option( @@ -30,20 +31,11 @@ type=int, help="Number of grid points along the second dimension", ) -@click.option( - "-nz", type=int, help="Number of grid points along the third dimension" -) +@click.option("-nz", type=int, help="Number of grid points along the third dimension") @click.option("-s", "--seed", default=42, type=int, help="Random seed") @click.pass_context def vp( - ctx, - append: bool, - n: int, - nx: int, - ny: int, - nz: int, - output: str, - seed: int, + ctx, append: bool, n: int, nx: int, ny: int, nz: int, output: str, seed: int, ): """Vp simulation""" shape = (nx, ny) diff --git a/deepseismic/forward/__init__.py b/deepseismic/forward/__init__.py index f9a9083f..8aaed73e 100644 --- a/deepseismic/forward/__init__.py +++ b/deepseismic/forward/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from .models import Model, VelocityModel from .sources import Receiver, RickerSource, WaveletSource from .time import TimeAxis diff --git a/deepseismic/forward/models.py b/deepseismic/forward/models.py index f07b7a1c..eaa0e56d 100644 --- a/deepseismic/forward/models.py +++ b/deepseismic/forward/models.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional, Tuple, Union import numpy as np @@ -37,9 +40,7 @@ def __init__( subdomains = tuple(subdomains) + (PhysicalDomain(n_pml),) shape_pml = tuple(x + 2 * n_pml for x in shape) extent_pml = tuple(s * (d - 1) for s, d in zip(spacing, shape_pml)) - origin_pml = tuple( - dtype(o - s * n_pml) for o, s in zip(origin, spacing) - ) + origin_pml = tuple(dtype(o - s * n_pml) for o, s in zip(origin, spacing)) self.grid = Grid( shape=shape_pml, extent=extent_pml, @@ -62,14 +63,10 @@ def __init__( idx = [slice(0, x) for x in pml_data.shape] idx[d] = slice(i, i + 1) pml_data[tuple(idx)] += val / self.grid.spacing[d] - idx[d] = slice( - pml_data.shape[d] - i, pml_data.shape[d] - i + 1 - ) + idx[d] = slice(pml_data.shape[d] - i, pml_data.shape[d] - i + 1) pml_data[tuple(idx)] += val / self.grid.spacing[d] pml_data = np.pad( - pml_data, - [(i.left, i.right) for i in self.pml._size_halo], - mode="edge", + pml_data, [(i.left, i.right) for i in self.pml._size_halo], mode="edge", ) self.pml.data_with_halo[:] = pml_data self.shape = shape @@ -110,9 +107,7 @@ def __init__( super().__init__(shape, origin, spacing, n_pml, dtype, subdomains) if isinstance(vp, np.ndarray): assert space_order is not None - self.m = Function( - name="m", grid=self.grid, space_order=int(space_order) - ) + self.m = Function(name="m", grid=self.grid, space_order=int(space_order)) else: self.m = Constant(name="m", value=1.0 / float(vp) ** 2.0) self.vp = vp @@ -126,8 +121,7 @@ def vp(self, vp: Union[float, np.ndarray]) -> None: self._vp = vp if isinstance(vp, np.ndarray): pad_widths = [ - (self.n_pml + i.left, self.n_pml + i.right) - for i in self.m._size_halo + (self.n_pml + i.left, self.n_pml + i.right) for i in self.m._size_halo ] self.m.data_with_halo[:] = np.pad( 1.0 / self.vp ** 2.0, pad_widths, mode="edge" @@ -150,9 +144,7 @@ def solve( H = u.laplace if kernel is Kernel.OT4: H += self.time_spacing ** 2 / 12 * u.laplace2(1 / self.m) - eq = Eq( - u.forward, solve(self.m * u.dt2 - H + self.pml * u.dt, u.forward) - ) + eq = Eq(u.forward, solve(self.m * u.dt2 - H + self.pml * u.dt, u.forward)) src_term = source.inject( field=u.forward, expr=source * self.time_spacing ** 2 / self.m ) diff --git a/deepseismic/forward/sources.py b/deepseismic/forward/sources.py index 5a0470e2..576e3934 100644 --- a/deepseismic/forward/sources.py +++ b/deepseismic/forward/sources.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional import numpy as np @@ -22,9 +25,7 @@ def __new__(cls, *args, **kwargs): time_order = kwargs.pop("time_order", 2) p_dim = kwargs.pop("dimension", Dimension(name="p_%s" % name)) npoint = kwargs.pop("npoint", None) - coordinates = kwargs.pop( - "coordinates", kwargs.pop("coordinates_data", None) - ) + coordinates = kwargs.pop("coordinates", kwargs.pop("coordinates_data", None)) if npoint is None: assert ( coordinates is not None @@ -76,16 +77,12 @@ def resample( if np.isclose(dt0, dt, rtol=rtol): return self n_traces = self.data.shape[1] - new_traces = np.zeros( - (new_time_range.num, n_traces), dtype=self.data.dtype - ) + new_traces = np.zeros((new_time_range.num, n_traces), dtype=self.data.dtype) for j in range(n_traces): tck = interpolate.splrep( self._time_range.time_values, self.data[:, j], k=order ) - new_traces[:, j] = interpolate.splev( - new_time_range.time_values, tck - ) + new_traces[:, j] = interpolate.splev(new_time_range.time_values, tck) return PointSource( name=self.name, grid=self.grid, @@ -129,4 +126,4 @@ def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: class RickerSource(WaveletSource): def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: r = np.pi * f0 * (t - 1.0 / f0) - return (1.0 - 2.0 * r ** 2.0) * np.exp(-r ** 2.0) + return (1.0 - 2.0 * r ** 2.0) * np.exp(-(r ** 2.0)) diff --git a/deepseismic/forward/subdomains.py b/deepseismic/forward/subdomains.py index 2ed6cedb..fcabe59b 100644 --- a/deepseismic/forward/subdomains.py +++ b/deepseismic/forward/subdomains.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Dict, Iterable, Tuple from devito import Dimension, SubDomain diff --git a/deepseismic/forward/time.py b/deepseismic/forward/time.py index d3dfc00d..7ce51d5d 100644 --- a/deepseismic/forward/time.py +++ b/deepseismic/forward/time.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional import numpy as np diff --git a/deepseismic/forward/types.py b/deepseismic/forward/types.py index 772f67b7..3b4fe2ac 100644 --- a/deepseismic/forward/types.py +++ b/deepseismic/forward/types.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from enum import Enum, auto diff --git a/deepseismic/velocity/__init__.py b/deepseismic/velocity/__init__.py index 98225180..34ef3009 100644 --- a/deepseismic/velocity/__init__.py +++ b/deepseismic/velocity/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from .generator import Generator from .roeth_tarantola import RoethTarantolaGenerator diff --git a/deepseismic/velocity/generator.py b/deepseismic/velocity/generator.py index ddc2eb4a..5b28721c 100644 --- a/deepseismic/velocity/generator.py +++ b/deepseismic/velocity/generator.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional, Tuple import numpy as np diff --git a/deepseismic/velocity/roeth_tarantola.py b/deepseismic/velocity/roeth_tarantola.py index 6c3c0cc4..89f860ae 100644 --- a/deepseismic/velocity/roeth_tarantola.py +++ b/deepseismic/velocity/roeth_tarantola.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional, Tuple import numpy as np @@ -25,9 +28,9 @@ def __init__( def generate(self) -> np.ndarray: vp = np.zeros(self.shape, dtype=self.dtype) dim = self.depth_dim - layer_idx = np.round( - np.linspace(0, self.shape[dim], self.n_layers + 1) - ).astype(np.int) + layer_idx = np.round(np.linspace(0, self.shape[dim], self.n_layers + 1)).astype( + np.int + ) vp_idx = [slice(0, x) for x in vp.shape] layer_vp = None for i in range(self.n_layers): diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index 669de6c9..135273f3 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import itertools import numpy as np import torch diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py index 6c58c408..ae0c17f7 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -1,9 +1,11 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# /* spell-checker: disable */ """Train models on Dutch F3 salt dataset Trains models using PyTorch DistributedDataParallel Uses a warmup schedule that then goes into a cyclic learning rate """ -# /* spell-checker: disable */ import logging import logging.config @@ -102,9 +104,7 @@ def run(*options, cfg=None, local_rank=0): # FOR DISTRIBUTED: Initialize the backend. torch.distributed.launch will provide # environment variables, and requires that you use init_method=`env://`. - torch.distributed.init_process_group( - backend="nccl", init_method="env://" - ) + torch.distributed.init_process_group(backend="nccl", init_method="env://") scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK @@ -117,9 +117,7 @@ def run(*options, cfg=None, local_rank=0): basic_aug = Compose( [ Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, + mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1, ), Resize( config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, @@ -136,12 +134,7 @@ def run(*options, cfg=None, local_rank=0): ] ) if config.TRAIN.AUGMENTATION: - train_aug = Compose( - [ - basic_aug, - HorizontalFlip(p=0.5), - ] - ) + train_aug = Compose([basic_aug, HorizontalFlip(p=0.5),]) val_aug = basic_aug else: train_aug = val_aug = basic_aug @@ -164,7 +157,7 @@ def run(*options, cfg=None, local_rank=0): is_transform=True, stride=config.TRAIN.STRIDE, patch_size=config.TRAIN.PATCH_SIZE, - augmentations=val_aug + augmentations=val_aug, ) logger.info(f"Validation examples {len(val_set)}") n_classes = train_set.n_classes @@ -236,8 +229,7 @@ def run(*options, cfg=None, local_rank=0): ) scheduler = ConcatScheduler( - schedulers=[warmup_scheduler, cosine_scheduler], - durations=[warmup_duration], + schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration], ) trainer = create_supervised_trainer( @@ -246,21 +238,17 @@ def run(*options, cfg=None, local_rank=0): trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles - trainer.add_event_handler( - Events.EPOCH_STARTED, update_sampler_epoch(train_loader) - ) + trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) if silence_other_ranks & local_rank != 0: - logging.getLogger("ignite.engine.engine.Engine").setLevel( - logging.WARNING - ) + logging.getLogger("ignite.engine.engine.Engine").setLevel(logging.WARNING) def _select_pred_and_mask(model_out_dict): return ( model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze(), ) - + evaluator = create_supervised_evaluator( model, prepare_batch, @@ -288,9 +276,7 @@ def _select_pred_and_mask(model_out_dict): ) # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler( - Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) - ) + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) if local_rank == 0: # Run only on master process @@ -330,7 +316,7 @@ def _select_pred_and_mask(model_out_dict): "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :", "mca": "Mean Class Accuracy :", - "fiou": "Freq Weighted IoU :", + "fiou": "Freq Weighted IoU :", }, ), ) @@ -354,7 +340,7 @@ def _select_max(pred_tensor): def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() - + transform_func = compose( np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy ) @@ -387,7 +373,6 @@ def _tensor_to_numpy(pred_tensor): def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 - checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, @@ -398,7 +383,6 @@ def snapshot_function(): Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} ) - logger.info("Starting training") trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/imaging/deepseismic_imaging/__init__.py b/imaging/deepseismic_imaging/__init__.py index 34749819..b063f14b 100644 --- a/imaging/deepseismic_imaging/__init__.py +++ b/imaging/deepseismic_imaging/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from deepseismic_imaging import velocity, forward, cli __all__ = ["cli", "forward", "velocity"] diff --git a/imaging/deepseismic_imaging/cli/__init__.py b/imaging/deepseismic_imaging/cli/__init__.py index 1b0db11d..1497eac6 100644 --- a/imaging/deepseismic_imaging/cli/__init__.py +++ b/imaging/deepseismic_imaging/cli/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from functools import partial import click diff --git a/imaging/deepseismic_imaging/cli/forward.py b/imaging/deepseismic_imaging/cli/forward.py index 2f924735..3e0658c5 100644 --- a/imaging/deepseismic_imaging/cli/forward.py +++ b/imaging/deepseismic_imaging/cli/forward.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from functools import partial import click @@ -13,16 +16,10 @@ @click.argument("input", type=click.Path()) @click.argument("output", type=click.Path()) @click.option( - "-d", - "--duration", - default=1000.0, - type=float, - help="Simulation duration (in ms)", + "-d", "--duration", default=1000.0, type=float, help="Simulation duration (in ms)", ) @click.option("-dt", default=2.0, type=float, help="Time increment (in ms)") -@click.option( - "--n-pml", default=10, type=int, help="PML size (in grid points)" -) +@click.option("--n-pml", default=10, type=int, help="PML size (in grid points)") @click.option( "--n-receivers", default=11, @@ -30,9 +27,7 @@ help="Number of receivers per horizontal dimension", ) @click.option("--space-order", default=2, type=int, help="Space order") -@click.option( - "--spacing", default=10.0, type=float, help="Spacing between grid points" -) +@click.option("--spacing", default=10.0, type=float, help="Spacing between grid points") @click.pass_context def fwd( ctx, @@ -58,9 +53,7 @@ def fwd( @fwd.command() -@click.option( - "-f0", default=0.01, type=float, help="Source peak frequency (in kHz)" -) +@click.option("-f0", default=0.01, type=float, help="Source peak frequency (in kHz)") @click.pass_context def ricker(ctx, f0: float): """Ricker source""" @@ -84,11 +77,7 @@ def ricker(ctx, f0: float): start=0.0, stop=ctx.obj["duration"], step=ctx.obj["dt"] ) source = RickerSource( - name="source", - grid=model.grid, - f0=f0, - npoint=1, - time_range=time_range, + name="source", grid=model.grid, f0=f0, npoint=1, time_range=time_range, ) source.coordinates.data[0, :] = np.array(model.domain_size) * 0.5 source.coordinates.data[0, -1] = 0.0 @@ -107,9 +96,7 @@ def ricker(ctx, f0: float): ) ) for d in range(len(receivers_coords)): - receivers.coordinates.data[:, d] = receivers_coords[ - d - ].flatten() + receivers.coordinates.data[:, d] = receivers_coords[d].flatten() receivers.coordinates.data[:, -1] = 0.0 output_group = output_file.create_group(input_group_name) for input_dataset_name, vp in input_group.items(): @@ -117,7 +104,5 @@ def ricker(ctx, f0: float): seismograms = model.solve( source=source, receivers=receivers, time_range=time_range ) - output_group.create_dataset( - input_dataset_name, data=seismograms - ) + output_group.create_dataset(input_dataset_name, data=seismograms) bar.update(1) diff --git a/imaging/deepseismic_imaging/cli/velocity.py b/imaging/deepseismic_imaging/cli/velocity.py index b8e51da5..c805d6af 100644 --- a/imaging/deepseismic_imaging/cli/velocity.py +++ b/imaging/deepseismic_imaging/cli/velocity.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from functools import partial from itertools import islice from typing import Tuple @@ -13,9 +16,7 @@ @click.group() @click.argument("output", type=click.Path()) @click.option( - "--append/--no-append", - default=False, - help="Whether to append to output file", + "--append/--no-append", default=False, help="Whether to append to output file", ) @click.option("-n", default=1, type=int, help="Number of simulations") @click.option( @@ -30,20 +31,11 @@ type=int, help="Number of grid points along the second dimension", ) -@click.option( - "-nz", type=int, help="Number of grid points along the third dimension" -) +@click.option("-nz", type=int, help="Number of grid points along the third dimension") @click.option("-s", "--seed", default=42, type=int, help="Random seed") @click.pass_context def vp( - ctx, - append: bool, - n: int, - nx: int, - ny: int, - nz: int, - output: str, - seed: int, + ctx, append: bool, n: int, nx: int, ny: int, nz: int, output: str, seed: int, ): """Vp simulation""" shape = (nx, ny) diff --git a/imaging/deepseismic_imaging/forward/__init__.py b/imaging/deepseismic_imaging/forward/__init__.py index f9a9083f..8aaed73e 100644 --- a/imaging/deepseismic_imaging/forward/__init__.py +++ b/imaging/deepseismic_imaging/forward/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from .models import Model, VelocityModel from .sources import Receiver, RickerSource, WaveletSource from .time import TimeAxis diff --git a/imaging/deepseismic_imaging/forward/models.py b/imaging/deepseismic_imaging/forward/models.py index f07b7a1c..eaa0e56d 100644 --- a/imaging/deepseismic_imaging/forward/models.py +++ b/imaging/deepseismic_imaging/forward/models.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional, Tuple, Union import numpy as np @@ -37,9 +40,7 @@ def __init__( subdomains = tuple(subdomains) + (PhysicalDomain(n_pml),) shape_pml = tuple(x + 2 * n_pml for x in shape) extent_pml = tuple(s * (d - 1) for s, d in zip(spacing, shape_pml)) - origin_pml = tuple( - dtype(o - s * n_pml) for o, s in zip(origin, spacing) - ) + origin_pml = tuple(dtype(o - s * n_pml) for o, s in zip(origin, spacing)) self.grid = Grid( shape=shape_pml, extent=extent_pml, @@ -62,14 +63,10 @@ def __init__( idx = [slice(0, x) for x in pml_data.shape] idx[d] = slice(i, i + 1) pml_data[tuple(idx)] += val / self.grid.spacing[d] - idx[d] = slice( - pml_data.shape[d] - i, pml_data.shape[d] - i + 1 - ) + idx[d] = slice(pml_data.shape[d] - i, pml_data.shape[d] - i + 1) pml_data[tuple(idx)] += val / self.grid.spacing[d] pml_data = np.pad( - pml_data, - [(i.left, i.right) for i in self.pml._size_halo], - mode="edge", + pml_data, [(i.left, i.right) for i in self.pml._size_halo], mode="edge", ) self.pml.data_with_halo[:] = pml_data self.shape = shape @@ -110,9 +107,7 @@ def __init__( super().__init__(shape, origin, spacing, n_pml, dtype, subdomains) if isinstance(vp, np.ndarray): assert space_order is not None - self.m = Function( - name="m", grid=self.grid, space_order=int(space_order) - ) + self.m = Function(name="m", grid=self.grid, space_order=int(space_order)) else: self.m = Constant(name="m", value=1.0 / float(vp) ** 2.0) self.vp = vp @@ -126,8 +121,7 @@ def vp(self, vp: Union[float, np.ndarray]) -> None: self._vp = vp if isinstance(vp, np.ndarray): pad_widths = [ - (self.n_pml + i.left, self.n_pml + i.right) - for i in self.m._size_halo + (self.n_pml + i.left, self.n_pml + i.right) for i in self.m._size_halo ] self.m.data_with_halo[:] = np.pad( 1.0 / self.vp ** 2.0, pad_widths, mode="edge" @@ -150,9 +144,7 @@ def solve( H = u.laplace if kernel is Kernel.OT4: H += self.time_spacing ** 2 / 12 * u.laplace2(1 / self.m) - eq = Eq( - u.forward, solve(self.m * u.dt2 - H + self.pml * u.dt, u.forward) - ) + eq = Eq(u.forward, solve(self.m * u.dt2 - H + self.pml * u.dt, u.forward)) src_term = source.inject( field=u.forward, expr=source * self.time_spacing ** 2 / self.m ) diff --git a/imaging/deepseismic_imaging/forward/sources.py b/imaging/deepseismic_imaging/forward/sources.py index 5a0470e2..576e3934 100644 --- a/imaging/deepseismic_imaging/forward/sources.py +++ b/imaging/deepseismic_imaging/forward/sources.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional import numpy as np @@ -22,9 +25,7 @@ def __new__(cls, *args, **kwargs): time_order = kwargs.pop("time_order", 2) p_dim = kwargs.pop("dimension", Dimension(name="p_%s" % name)) npoint = kwargs.pop("npoint", None) - coordinates = kwargs.pop( - "coordinates", kwargs.pop("coordinates_data", None) - ) + coordinates = kwargs.pop("coordinates", kwargs.pop("coordinates_data", None)) if npoint is None: assert ( coordinates is not None @@ -76,16 +77,12 @@ def resample( if np.isclose(dt0, dt, rtol=rtol): return self n_traces = self.data.shape[1] - new_traces = np.zeros( - (new_time_range.num, n_traces), dtype=self.data.dtype - ) + new_traces = np.zeros((new_time_range.num, n_traces), dtype=self.data.dtype) for j in range(n_traces): tck = interpolate.splrep( self._time_range.time_values, self.data[:, j], k=order ) - new_traces[:, j] = interpolate.splev( - new_time_range.time_values, tck - ) + new_traces[:, j] = interpolate.splev(new_time_range.time_values, tck) return PointSource( name=self.name, grid=self.grid, @@ -129,4 +126,4 @@ def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: class RickerSource(WaveletSource): def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: r = np.pi * f0 * (t - 1.0 / f0) - return (1.0 - 2.0 * r ** 2.0) * np.exp(-r ** 2.0) + return (1.0 - 2.0 * r ** 2.0) * np.exp(-(r ** 2.0)) diff --git a/imaging/deepseismic_imaging/forward/subdomains.py b/imaging/deepseismic_imaging/forward/subdomains.py index 2ed6cedb..fcabe59b 100644 --- a/imaging/deepseismic_imaging/forward/subdomains.py +++ b/imaging/deepseismic_imaging/forward/subdomains.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Dict, Iterable, Tuple from devito import Dimension, SubDomain diff --git a/imaging/deepseismic_imaging/forward/time.py b/imaging/deepseismic_imaging/forward/time.py index d3dfc00d..7ce51d5d 100644 --- a/imaging/deepseismic_imaging/forward/time.py +++ b/imaging/deepseismic_imaging/forward/time.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional import numpy as np diff --git a/imaging/deepseismic_imaging/forward/types.py b/imaging/deepseismic_imaging/forward/types.py index 772f67b7..3b4fe2ac 100644 --- a/imaging/deepseismic_imaging/forward/types.py +++ b/imaging/deepseismic_imaging/forward/types.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from enum import Enum, auto diff --git a/imaging/deepseismic_imaging/velocity/__init__.py b/imaging/deepseismic_imaging/velocity/__init__.py index 98225180..34ef3009 100644 --- a/imaging/deepseismic_imaging/velocity/__init__.py +++ b/imaging/deepseismic_imaging/velocity/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from .generator import Generator from .roeth_tarantola import RoethTarantolaGenerator diff --git a/imaging/deepseismic_imaging/velocity/generator.py b/imaging/deepseismic_imaging/velocity/generator.py index ddc2eb4a..5b28721c 100644 --- a/imaging/deepseismic_imaging/velocity/generator.py +++ b/imaging/deepseismic_imaging/velocity/generator.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional, Tuple import numpy as np diff --git a/imaging/deepseismic_imaging/velocity/roeth_tarantola.py b/imaging/deepseismic_imaging/velocity/roeth_tarantola.py index 6c3c0cc4..89f860ae 100644 --- a/imaging/deepseismic_imaging/velocity/roeth_tarantola.py +++ b/imaging/deepseismic_imaging/velocity/roeth_tarantola.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from typing import Optional, Tuple import numpy as np @@ -25,9 +28,9 @@ def __init__( def generate(self) -> np.ndarray: vp = np.zeros(self.shape, dtype=self.dtype) dim = self.depth_dim - layer_idx = np.round( - np.linspace(0, self.shape[dim], self.n_layers + 1) - ).astype(np.int) + layer_idx = np.round(np.linspace(0, self.shape[dim], self.n_layers + 1)).astype( + np.int + ) vp_idx = [slice(0, x) for x in vp.shape] layer_vp = None for i in range(self.n_layers): diff --git a/imaging/setup.py b/imaging/setup.py index 6b074193..5b67c692 100644 --- a/imaging/setup.py +++ b/imaging/setup.py @@ -1,9 +1,12 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import setuptools with open("../README.md", "r") as f: long_description = f.read() -with open('requirements.txt') as f: +with open("requirements.txt") as f: requirements = f.read().splitlines() setuptools.setup( diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index f80f218d..a227c178 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import itertools import warnings import segyio diff --git a/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py b/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py index f9a3a4ea..41048def 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py +++ b/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import numpy as np import scipy diff --git a/interpretation/deepseismic_interpretation/penobscot/data.py b/interpretation/deepseismic_interpretation/penobscot/data.py index f0b64dd4..ca25c0a3 100644 --- a/interpretation/deepseismic_interpretation/penobscot/data.py +++ b/interpretation/deepseismic_interpretation/penobscot/data.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import glob import itertools import os diff --git a/interpretation/deepseismic_interpretation/penobscot/metrics.py b/interpretation/deepseismic_interpretation/penobscot/metrics.py index 28502d00..1be9a5a5 100644 --- a/interpretation/deepseismic_interpretation/penobscot/metrics.py +++ b/interpretation/deepseismic_interpretation/penobscot/metrics.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + from collections import defaultdict from cv_lib.segmentation.dutchf3.metrics import _torch_hist from ignite.metrics import Metric @@ -74,9 +77,7 @@ def update(self, output): max_prediction = y_pred.max(1)[1].squeeze() assert y.shape == max_prediction.shape, "Shape not the same" - for pred, mask, id, patch_loc in zip( - max_prediction, y, ids, patch_locations - ): + for pred, mask, id, patch_loc in zip(max_prediction, y, ids, patch_locations): # ! With overlapping patches this does not aggregate the results it simply overwrites them # If patch is padded ingore padding pad = int(self._padding // 2) @@ -111,9 +112,7 @@ def iou_per_inline(self): self._num_classes, ) hist = confusion_matrix.cpu().numpy() - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) + iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) iou_per_inline[id] = np.nanmean(iu) return iou_per_inline diff --git a/interpretation/setup.py b/interpretation/setup.py index ebc40ba1..132d8044 100644 --- a/interpretation/setup.py +++ b/interpretation/setup.py @@ -1,9 +1,12 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import setuptools with open("../README.md", "r") as f: long_description = f.read() -with open('requirements.txt') as f: +with open("requirements.txt") as f: requirements = f.read().splitlines() @@ -21,7 +24,6 @@ "Topic :: Scientific/Engineering", "Topic :: Software Development", ], - description="DeepSeismic", install_requires=requirements, license="MIT", diff --git a/setup.py b/setup.py index 3d2cc7b3..8c3f73af 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import setuptools with open("README.md", "r") as f: @@ -34,9 +37,7 @@ long_description=long_description, long_description_content_type="text/markdown", name="deepseismic", - packages=setuptools.find_packages( - include=["deepseismic", "deepseismic.*"] - ), + packages=setuptools.find_packages(include=["deepseismic", "deepseismic.*"]), platforms="any", python_requires=">= 3.5", scripts=["bin/ds"], From 16652c731b0945c33e5d1cdbbf561115fbdad1eb Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Thu, 7 Nov 2019 11:23:16 -0500 Subject: [PATCH 069/207] switched to ACR instead of docker hub (#4) * sdk.v1.0.69, plus switched to ACR push. ACR pull coming next * full acr use, push and pull, and use in Estimator * temp fix for dcker image bug * fixed the az acr login --username and --password issue * full switch to ACR for docker image storage --- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 245 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 2340 +++-------------- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 77 +- ...zureml_buildexperimentationdockerimage.log | 185 +- 4 files changed, 680 insertions(+), 2167 deletions(-) diff --git a/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index 3e4f397b..af554269 100755 --- a/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -19,6 +19,8 @@ "User input requiring steps:\n", " - [Fill in and save sensitive information](#dot_env_description)\n", " - [Azure login](#Azure_login) (may be required first time the notebook is run) \n", + " - [Set __create_ACR_FLAG__ to true to trigger ACR creation and to save of ACR login info](#set_create_ACR_flag)\n", + " - [Azure CLI login ](#Azure_cli_login) (may be required once to create an [ACR](https://azure.microsoft.com/en-us/services/container-registry/)) \n", "\n" ] }, @@ -53,7 +55,7 @@ "from azureml.core import Workspace, Experiment\n", "from azureml.core.compute import ComputeTarget, AmlCompute\n", "from azureml.core.compute_target import ComputeTargetException\n", - "import platform" + "import platform, dotenv" ] }, { @@ -65,13 +67,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.65\n" + "Azure ML SDK Version: 1.0.69\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1060-azure-x86_64-with-debian-10.0'" + "'Linux-4.15.0-1061-azure-x86_64-with-debian-10.0'" ] }, "execution_count": 3, @@ -266,7 +268,7 @@ "\n", "\n", "import project_utils\n", - "prj_consts = project_utils.project_consts()" + "prj_consts = project_utils.project_consts()\n" ] }, { @@ -282,11 +284,37 @@ "* The notebook tries to find and use an existing Azure resource group (rsg) defined by __crt_resource_group__. It creates a new one if needed. " ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "##### Create [ACR]() first time this notebook is run. \n", + "Either docker hub or ACR can be used to store the experimentation image. To create the ACR, set: \n", + "```\n", + "create_ACR_FLAG=True \n", + "```\n", + "It will create an ACR by running severral steps described below in section 2.7. __Create an [ACR]__ \n", + " \n", + " \n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], + "source": [ + "create_ACR_FLAG = False #True False" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], "source": [ "sensitive_info = {}" ] @@ -296,7 +324,6 @@ "metadata": {}, "source": [ "\n", - "\n", "##### 2.1. Input here sensitive and configuration information\n", "[dotenv](https://github.com/theskumar/python-dotenv) is used to hide sensitive info, like Azure subscription name/ID. The serialized info needs to be manually input once. \n", " \n", @@ -317,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -328,16 +355,20 @@ "# gpu_cluster_name = \"gpuclstfwi02\"\n", "# gpucluster_admin_user_name = \"\"\n", "# gpucluster_admin_user_password = \"\"\n", + "\n", + "# experimentation_docker_image_name = \"fwi01_azureml\"\n", + "# experimentation_docker_image_tag = \"sdk.v1.0.60\"\n", + "# docker_container_mount_point = '/datadrive01/prj/DeepSeismic/fwi' # use project directory or a subdirectory\n", + "\n", "# docker_login = \"georgedockeraccount\"\n", "# docker_pwd = \"\"\n", - "# experimentation_image_tag = \"fwi01_azureml\"\n", - "# experimentation_image_version = \"sdk.v1.0.60\"\n", - "# docker_container_mount_point = '/datadrive01/prj/DeepSeismic/fwi' # use project directory or a subdirectory" + "\n", + "# acr_name=\"fwi01acr\"" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -349,11 +380,12 @@ "# 'GPU_CLUSTER_NAME':gpu_cluster_name,\n", "# 'GPU_CLUSTER_ADMIN_USER_NAME':gpucluster_admin_user_name,\n", "# 'GPU_CLUSTER_ADMIN_USER_PASSWORD':gpucluster_admin_user_password,\n", + "# 'EXPERIMENTATION_DOCKER_IMAGE_NAME':experimentation_docker_image_name,\n", + "# 'EXPERIMENTATION_DOCKER_IMAGE_TAG':experimentation_docker_image_tag,\n", + "# 'DOCKER_CONTAINER_MOUNT_POINT':docker_container_mount_point,\n", "# 'DOCKER_LOGIN':docker_login,\n", "# 'DOCKER_PWD':docker_pwd,\n", - "# 'EXPERIMENTATION_IMAGE_TAG':experimentation_image_tag,\n", - "# 'EXPERIMENTATION_IMAGE_VERSION':experimentation_image_version,\n", - "# 'DOCKER_CONTAINER_MOUNT_POINT':docker_container_mount_point\n", + "# 'ACR_NAME':acr_name\n", "# }" ] }, @@ -368,7 +400,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -377,7 +409,7 @@ "'./../not_shared/general.env'" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -405,7 +437,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -439,7 +471,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -462,7 +494,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -518,7 +550,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -540,7 +572,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -549,7 +581,7 @@ "'gpuclstfwi02'" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -561,7 +593,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -594,9 +626,176 @@ "# gpu_cluster.wait_for_completion(show_output=True)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### 2.7. Create an [ACR](https://docs.microsoft.com/en-us/azure/container-registry/) if you have not done so using the [portal](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal) \n", + " - Follow the 4 ACR steps described below. \n", + " - Uncomment cells' lines as needed to login and see commands responses while you set the right subscription and then create the ACR. \n", + " - You need [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) to run the commands below. \n", + "\n", + "\n", + "##### ACR Step 1. Select ACR subscription (az cli login into Azure may be required here)\n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "azure-cli 2.0.75\r\n", + "\r\n", + "command-modules-nspkg 2.0.3\r\n", + "core 2.0.75\r\n", + "nspkg 3.0.4\r\n", + "telemetry 1.0.4\r\n", + "\r\n", + "Python location '/opt/az/bin/python3'\r\n", + "Extensions directory '/root/.azure/cliextensions'\r\n", + "\r\n", + "Python (Linux) 3.6.5 (default, Oct 11 2019, 09:04:03) \r\n", + "[GCC 6.3.0 20170516]\r\n", + "\r\n", + "Legal docs and information: aka.ms/AzureCliLegal\r\n", + "\r\n", + "\r\n", + "Your CLI is up-to-date.\r\n" + ] + } + ], + "source": [ + "!az --version\n", + "if create_ACR_FLAG:\n", + " !az login\n", + " response01 = ! az account list --all --refresh -o table\n", + " response02 = ! az account set --subscription $subscription_id\n", + " response03 = ! az account list -o table\n", + " response04 = ! $cli_command\n", + "\n", + " response01\n", + " response02\n", + " response03\n", + " response04" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### ACR Step 2. Create the ACR" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'az acr create --resource-group ghiordanfwirsg01 --name fwi01acr --sku Basic'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%dotenv $dotenv_file_path\n", + "acr_name = os.getenv('ACR_NAME')\n", + "\n", + "cli_command='az acr create --resource-group '+ crt_resource_group +' --name ' + acr_name + ' --sku Basic'\n", + "cli_command\n", + "\n", + "if create_ACR_FLAG:\n", + " !$cli_command" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### ACR Step 3. Also enable password and login via __ [--admin-enabled true](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication) __ and then use the az cli or portal to set up the credentials" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'az acr update -n fwi01acr --admin-enabled true'" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# per https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication\n", + "cli_command='az acr update -n '+acr_name+' --admin-enabled true'\n", + "cli_command\n", + "\n", + "if create_ACR_FLAG:\n", + " response = !$cli_command\n", + " response" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### ACR Step 4. Save the ACR password and login" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "if create_ACR_FLAG:\n", + " import subprocess\n", + " cli_command = 'az acr credential show -n '+acr_name\n", + "\n", + " acr_username = subprocess.Popen(cli_command+' --query username',shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE).\\\n", + " communicate()[0].decode(\"utf-8\").split()[0].strip('\\\"')\n", + "\n", + " acr_password = subprocess.Popen(cli_command+' --query passwords[0].value',shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE).\\\n", + " communicate()[0].decode(\"utf-8\").split()[0].strip('\\\"')\n", + "\n", + " dotenv.set_key(dotenv_file_path, 'ACR_PASSWORD', acr_password)\n", + " dotenv.set_key(dotenv_file_path, 'ACR_USERNAME', acr_username)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "%reload_ext dotenv\n", + "%dotenv -o $dotenv_file_path\n", + "\n", + "# print acr password and login info saved in dotenv file\n", + "if create_ACR_FLAG:\n", + " os.getenv('ACR_PASSWORD')\n", + " os.getenv('ACR_USERNAME')" + ] + }, + { + "cell_type": "code", + "execution_count": 23, "metadata": {}, "outputs": [ { diff --git a/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb index 4c17dc7c..07c16572 100755 --- a/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -62,7 +62,7 @@ { "data": { "text/plain": [ - "'Linux-4.15.0-1060-azure-x86_64-with-debian-10.0'" + "'Linux-4.15.0-1061-azure-x86_64-with-debian-10.0'" ] }, "execution_count": 3, @@ -104,8 +104,11 @@ "metadata": {}, "outputs": [], "source": [ - "docker_build_no_cache = '--no-cache' # '--no-cache' # or '' #\n", - "docker_test_run_devito_tests = True # True # False" + "docker_build_no_cache = '' # '--no-cache' # or '' #\n", + "docker_test_run_devito_tests = False # True # False\n", + "\n", + "# azureml_sdk_version set here must match azureml sdk version pinned in conda env file written to conda_common_file_path below\n", + "azureml_sdk_version = '1.0.69' " ] }, { @@ -209,7 +212,7 @@ { "data": { "text/plain": [ - "(True, 'EXPERIMENTATION_IMAGE_VERSION', 'sdk.v1.0.65')" + "(True, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', 'sdk.v1.0.69')" ] }, "execution_count": 8, @@ -219,7 +222,7 @@ { "data": { "text/plain": [ - "(True, 'EXPERIMENTATION_IMAGE_TAG', 'fwi01_azureml')" + "(True, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')" ] }, "execution_count": 8, @@ -247,8 +250,9 @@ "# - match sdk version in fwi01_conda_env01 environmnet in conda_env_fwi01_azureml_sdk.v1.0.XX.yml file below\n", "# - match the conda env yml file name, e.g. conda_env_fwi01_azureml_sdk.v1.0.62.yml referenced in \n", "# Dockerfile_fwi01_azureml_sdk.v1.0.62\n", - "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_IMAGE_VERSION', 'sdk.v1.0.65')\n", - "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_IMAGE_TAG', 'fwi01_azureml')\n", + "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', ('sdk.v'+azureml_sdk_version))\n", + "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')\n", + "\n", "dotenv.set_key(dotenv_file_path, 'DOCKER_CONTAINER_MOUNT_POINT', '/datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks')" ] }, @@ -260,7 +264,7 @@ { "data": { "text/plain": [ - "'georgedockeraccount/fwi01_azureml:sdk.v1.0.65'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" ] }, "execution_count": 9, @@ -270,7 +274,7 @@ { "data": { "text/plain": [ - "'conda_env_fwi01_azureml_sdk.v1.0.65.yml'" + "'conda_env_fwi01_azureml_sdk.v1.0.69.yml'" ] }, "execution_count": 9, @@ -280,7 +284,17 @@ { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.65.yml'" + "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.69.yml'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml'" ] }, "execution_count": 9, @@ -300,7 +314,7 @@ { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.65'" + "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.69'" ] }, "execution_count": 9, @@ -309,30 +323,41 @@ } ], "source": [ - "%load_ext dotenv\n", + "%reload_ext dotenv\n", "%dotenv $dotenv_file_path\n", "\n", "docker_file_location = os.path.join(*(prj_consts.AML_EXPERIMENT_DIR + ['docker_build']))\n", "\n", - "docker_file_name = 'Dockerfile'+ '_' + os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", - "conda_dependency_file_name = 'conda_env'+ '_' + os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", + "docker_file_name = 'Dockerfile'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "conda_dependency_file_name = 'conda_env'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "conda_dependency_common_file_name = conda_dependency_file_name\n", + "\n", "devito_conda_dependency_file_name = 'devito_conda_env'+'.yml'\n", - "docker_image_name = os.getenv('DOCKER_LOGIN') + '/' + os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", - "image_version = os.getenv('EXPERIMENTATION_IMAGE_VERSION')\n", + "\n", + "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", + "docker_image_name = docker_repo_name + '/' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", "if image_version!=\"\":\n", " docker_file_name = docker_file_name +'_'+ image_version\n", " conda_dependency_file_name = conda_dependency_file_name+'_'+ image_version\n", " docker_image_name = docker_image_name +':'+ image_version\n", "conda_dependency_file_name=conda_dependency_file_name+'.yml'\n", + "conda_dependency_common_file_name = conda_dependency_common_file_name+'.yml'\n", "\n", "docker_file_dir = os.path.join(*([os.getcwd(), docker_file_location]))\n", "os.makedirs(docker_file_dir, exist_ok=True)\n", "docker_file_path = os.path.join(*([docker_file_dir]+[docker_file_name]))\n", "conda_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_file_name]))\n", + "conda_common_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_common_file_name]))\n", "\n", "docker_image_name\n", + "\n", "conda_dependency_file_name\n", "conda_file_path\n", + "conda_common_file_path\n", + "\n", "docker_file_dir\n", "docker_file_path" ] @@ -346,12 +371,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.65.yml\n" + "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" ] } ], "source": [ - "%%writefile $conda_file_path\n", + "%%writefile $conda_common_file_path\n", "name: fwi01_conda_env01\n", " \n", "#https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.13.1-cp37-cp37m-linux_x86_64.whl \n", @@ -382,7 +407,7 @@ " - toolz\n", " - pip:\n", " - anytree # required by devito\n", - " - azureml-sdk[notebooks,automl]==1.0.65\n", + " - azureml-sdk[notebooks,automl]==1.0.69\n", " - codepy # required by devito\n", " - papermill[azure]\n", " - pyrevolve # required by devito" @@ -397,7 +422,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.65\n" + "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.69\n" ] } ], @@ -417,7 +442,7 @@ " apt-get clean && \\\n", " rm -rf /var/lib/apt/lists/*\n", "\n", - "ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml_sdk.v1.0.65.yml\n", + "ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml.yml\n", "ADD $CONDA_ENV_FILE_NAME /tmp/$CONDA_ENV_FILE_NAME\n", "ENV CONDA_DIR /opt/conda\n", "ENV CONDA_ENV_NAME fwi01_conda_env\n", @@ -447,21 +472,36 @@ "execution_count": 12, "metadata": {}, "outputs": [ + { + "data": { + "text/plain": [ + "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.69.yml'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, { "name": "stdout", "output_type": "stream", "text": [ - "total 24\r\n", - "-rw-r--r-- 1 root root 1098 Sep 25 00:39 Dockerfile_fwi01_azureml_sdk.v1.0.60\r\n", + "total 36\r\n", + "-rw-r--r-- 1 root root 1073 Oct 30 05:29 Dockerfile_fwi01_azureml_sdk.v1.0.60\r\n", "-rw-r--r-- 1 root root 1098 Sep 26 19:04 Dockerfile_fwi01_azureml_sdk.v1.0.62\r\n", - "-rw-r--r-- 1 root root 1085 Oct 9 17:54 Dockerfile_fwi01_azureml_sdk.v1.0.65\r\n", - "-rw-r--r-- 1 root root 713 Sep 25 00:39 conda_env_fwi01_azureml_sdk.v1.0.60.yml\r\n", + "-rw-r--r-- 1 root root 1085 Oct 24 01:05 Dockerfile_fwi01_azureml_sdk.v1.0.65\r\n", + "-rw-r--r-- 1 root root 1073 Nov 3 14:43 Dockerfile_fwi01_azureml_sdk.v1.0.69\r\n", + "-rw-r--r-- 1 root root 733 Nov 3 14:43 conda_env_fwi01_azureml.yml\r\n", + "-rw-r--r-- 1 root root 733 Oct 30 05:29 conda_env_fwi01_azureml_sdk.v1.0.60.yml\r\n", "-rw-r--r-- 1 root root 713 Sep 26 19:04 conda_env_fwi01_azureml_sdk.v1.0.62.yml\r\n", - "-rw-r--r-- 1 root root 733 Oct 9 17:54 conda_env_fwi01_azureml_sdk.v1.0.65.yml\r\n" + "-rw-r--r-- 1 root root 733 Oct 24 01:05 conda_env_fwi01_azureml_sdk.v1.0.65.yml\r\n", + "-rw-r--r-- 1 root root 733 Nov 3 14:43 conda_env_fwi01_azureml_sdk.v1.0.69.yml\r\n" ] } ], "source": [ + "shutil.copyfile(conda_common_file_path, conda_file_path)\n", + "\n", "! ls -l $docker_file_dir" ] }, @@ -473,7 +513,7 @@ { "data": { "text/plain": [ - "'docker build -t georgedockeraccount/fwi01_azureml:sdk.v1.0.65 -f /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.65 /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build --no-cache'" + "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69 -f /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.69 /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build '" ] }, "execution_count": 13, @@ -481,1854 +521,32 @@ "output_type": "execute_result" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sending build context to Docker daemon 11.78kB\n", - "Step 1/15 : FROM continuumio/miniconda3:4.7.10\n", - " ---> 4a51de2367be\n", - "Step 2/15 : MAINTAINER George Iordanescu \n", - " ---> Running in 084695efc71b\n", - "Removing intermediate container 084695efc71b\n", - " ---> 447e38875551\n", - "Step 3/15 : RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends gcc g++ wget bzip2 curl git make mpich libmpich-dev && apt-get clean && rm -rf /var/lib/apt/lists/*\n", - " ---> Running in d09c88390a5b\n", - "Get:1 http://deb.debian.org/debian buster InRelease [122 kB]\n", - "Get:2 http://deb.debian.org/debian buster-updates InRelease [49.3 kB]\n", - "Get:3 http://deb.debian.org/debian buster/main amd64 Packages [7899 kB]\n", - "Get:4 http://security.debian.org/debian-security buster/updates InRelease [39.1 kB]\n", - "Get:5 http://deb.debian.org/debian buster-updates/main amd64 Packages.diff/Index [1720 B]\n", - "Ign:5 http://deb.debian.org/debian buster-updates/main amd64 Packages.diff/Index\n", - "Get:6 http://deb.debian.org/debian buster-updates/main amd64 Packages [5792 B]\n", - "Get:7 http://security.debian.org/debian-security buster/updates/main amd64 Packages [98.2 kB]\n", - "Fetched 8214 kB in 1s (6166 kB/s)\n", - "Reading package lists...\n", - "Reading package lists...\n", - "Building dependency tree...\n", - "Reading state information...\n", - "git is already the newest version (1:2.20.1-2).\n", - "wget is already the newest version (1.20.1-1.1).\n", - "The following additional packages will be installed:\n", - " binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-8 g++-8 gcc-8\n", - " gfortran gfortran-8 hwloc-nox libasan5 libatomic1 libbinutils libbz2-1.0\n", - " libc-dev-bin libc6-dev libcc1-0 libcurl4 libgcc-8-dev libgfortran-8-dev\n", - " libgfortran5 libgomp1 libhwloc5 libisl19 libitm1 liblsan0 libltdl7 libmpc3\n", - " libmpfr6 libmpich12 libmpx2 libnuma1 libquadmath0 libstdc++-8-dev libtsan0\n", - " libubsan1 linux-libc-dev\n", - "Suggested packages:\n", - " binutils-doc bzip2-doc cpp-doc gcc-8-locales g++-multilib g++-8-multilib\n", - " gcc-8-doc libstdc++6-8-dbg gcc-multilib manpages-dev autoconf automake\n", - " libtool flex bison gdb gcc-doc gcc-8-multilib libgcc1-dbg libgomp1-dbg\n", - " libitm1-dbg libatomic1-dbg libasan5-dbg liblsan0-dbg libtsan0-dbg\n", - " libubsan1-dbg libmpx2-dbg libquadmath0-dbg gfortran-multilib gfortran-doc\n", - " gfortran-8-multilib gfortran-8-doc libgfortran5-dbg libcoarrays-dev\n", - " glibc-doc libhwloc-contrib-plugins libstdc++-8-doc make-doc mpich-doc\n", - "Recommended packages:\n", - " manpages manpages-dev libhwloc-plugins\n", - "The following NEW packages will be installed:\n", - " binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-8 curl g++ g++-8\n", - " gcc gcc-8 gfortran gfortran-8 hwloc-nox libasan5 libatomic1 libbinutils\n", - " libc-dev-bin libc6-dev libcc1-0 libcurl4 libgcc-8-dev libgfortran-8-dev\n", - " libgfortran5 libgomp1 libhwloc5 libisl19 libitm1 liblsan0 libltdl7 libmpc3\n", - " libmpfr6 libmpich-dev libmpich12 libmpx2 libnuma1 libquadmath0\n", - " libstdc++-8-dev libtsan0 libubsan1 linux-libc-dev make mpich\n", - "The following packages will be upgraded:\n", - " bzip2 libbz2-1.0\n", - "2 upgraded, 42 newly installed, 0 to remove and 22 not upgraded.\n", - "Need to get 58.5 MB of archives.\n", - "After this operation, 228 MB of additional disk space will be used.\n", - "Get:1 http://deb.debian.org/debian buster/main amd64 bzip2 amd64 1.0.6-9.2~deb10u1 [48.4 kB]\n", - "Get:2 http://security.debian.org/debian-security buster/updates/main amd64 linux-libc-dev amd64 4.19.67-2+deb10u1 [1233 kB]\n", - "Get:3 http://deb.debian.org/debian buster/main amd64 libbz2-1.0 amd64 1.0.6-9.2~deb10u1 [45.3 kB]\n", - "Get:4 http://deb.debian.org/debian buster/main amd64 binutils-common amd64 2.31.1-16 [2073 kB]\n", - "Get:5 http://deb.debian.org/debian buster/main amd64 libbinutils amd64 2.31.1-16 [478 kB]\n", - "Get:6 http://deb.debian.org/debian buster/main amd64 binutils-x86-64-linux-gnu amd64 2.31.1-16 [1823 kB]\n", - "Get:7 http://deb.debian.org/debian buster/main amd64 binutils amd64 2.31.1-16 [56.8 kB]\n", - "Get:8 http://deb.debian.org/debian buster/main amd64 libisl19 amd64 0.20-2 [587 kB]\n", - "Get:9 http://deb.debian.org/debian buster/main amd64 libmpfr6 amd64 4.0.2-1 [775 kB]\n", - "Get:10 http://deb.debian.org/debian buster/main amd64 libmpc3 amd64 1.1.0-1 [41.3 kB]\n", - "Get:11 http://deb.debian.org/debian buster/main amd64 cpp-8 amd64 8.3.0-6 [8914 kB]\n", - "Get:12 http://deb.debian.org/debian buster/main amd64 cpp amd64 4:8.3.0-1 [19.4 kB]\n", - "Get:13 http://deb.debian.org/debian buster/main amd64 libcurl4 amd64 7.64.0-4 [332 kB]\n", - "Get:14 http://deb.debian.org/debian buster/main amd64 curl amd64 7.64.0-4 [264 kB]\n", - "Get:15 http://deb.debian.org/debian buster/main amd64 libcc1-0 amd64 8.3.0-6 [46.6 kB]\n", - "Get:16 http://deb.debian.org/debian buster/main amd64 libgomp1 amd64 8.3.0-6 [75.8 kB]\n", - "Get:17 http://deb.debian.org/debian buster/main amd64 libitm1 amd64 8.3.0-6 [27.7 kB]\n", - "Get:18 http://deb.debian.org/debian buster/main amd64 libatomic1 amd64 8.3.0-6 [9032 B]\n", - "Get:19 http://deb.debian.org/debian buster/main amd64 libasan5 amd64 8.3.0-6 [362 kB]\n", - "Get:20 http://deb.debian.org/debian buster/main amd64 liblsan0 amd64 8.3.0-6 [131 kB]\n", - "Get:21 http://deb.debian.org/debian buster/main amd64 libtsan0 amd64 8.3.0-6 [283 kB]\n", - "Get:22 http://deb.debian.org/debian buster/main amd64 libubsan1 amd64 8.3.0-6 [120 kB]\n", - "Get:23 http://deb.debian.org/debian buster/main amd64 libmpx2 amd64 8.3.0-6 [11.4 kB]\n", - "Get:24 http://deb.debian.org/debian buster/main amd64 libquadmath0 amd64 8.3.0-6 [133 kB]\n", - "Get:25 http://deb.debian.org/debian buster/main amd64 libgcc-8-dev amd64 8.3.0-6 [2298 kB]\n", - "Get:26 http://deb.debian.org/debian buster/main amd64 gcc-8 amd64 8.3.0-6 [9452 kB]\n", - "Get:27 http://deb.debian.org/debian buster/main amd64 gcc amd64 4:8.3.0-1 [5196 B]\n", - "Get:28 http://deb.debian.org/debian buster/main amd64 libc-dev-bin amd64 2.28-10 [275 kB]\n", - "Get:29 http://deb.debian.org/debian buster/main amd64 libc6-dev amd64 2.28-10 [2691 kB]\n", - "Get:30 http://deb.debian.org/debian buster/main amd64 libstdc++-8-dev amd64 8.3.0-6 [1532 kB]\n", - "Get:31 http://deb.debian.org/debian buster/main amd64 g++-8 amd64 8.3.0-6 [9752 kB]\n", - "Get:32 http://deb.debian.org/debian buster/main amd64 g++ amd64 4:8.3.0-1 [1644 B]\n", - "Get:33 http://deb.debian.org/debian buster/main amd64 libgfortran5 amd64 8.3.0-6 [581 kB]\n", - "Get:34 http://deb.debian.org/debian buster/main amd64 libgfortran-8-dev amd64 8.3.0-6 [616 kB]\n", - "Get:35 http://deb.debian.org/debian buster/main amd64 gfortran-8 amd64 8.3.0-6 [9375 kB]\n", - "Get:36 http://deb.debian.org/debian buster/main amd64 gfortran amd64 4:8.3.0-1 [1432 B]\n", - "Get:37 http://deb.debian.org/debian buster/main amd64 libltdl7 amd64 2.4.6-9 [390 kB]\n", - "Get:38 http://deb.debian.org/debian buster/main amd64 libnuma1 amd64 2.0.12-1 [26.2 kB]\n", - "Get:39 http://deb.debian.org/debian buster/main amd64 libhwloc5 amd64 1.11.12-3 [111 kB]\n", - "Get:40 http://deb.debian.org/debian buster/main amd64 hwloc-nox amd64 1.11.12-3 [159 kB]\n", - "Get:41 http://deb.debian.org/debian buster/main amd64 libmpich12 amd64 3.3-3 [1169 kB]\n", - "Get:42 http://deb.debian.org/debian buster/main amd64 mpich amd64 3.3-3 [411 kB]\n", - "Get:43 http://deb.debian.org/debian buster/main amd64 libmpich-dev amd64 3.3-3 [1429 kB]\n", - "Get:44 http://deb.debian.org/debian buster/main amd64 make amd64 4.2.1-1.2 [341 kB]\n", - "\u001b[91mdebconf: delaying package configuration, since apt-utils is not installed\n", - "\u001b[0mFetched 58.5 MB in 1s (91.7 MB/s)\n", - "(Reading database ... 12557 files and directories currently installed.)\n", - "Preparing to unpack .../bzip2_1.0.6-9.2~deb10u1_amd64.deb ...\n", - "Unpacking bzip2 (1.0.6-9.2~deb10u1) over (1.0.6-9.1) ...\n", - "Preparing to unpack .../libbz2-1.0_1.0.6-9.2~deb10u1_amd64.deb ...\n", - "Unpacking libbz2-1.0:amd64 (1.0.6-9.2~deb10u1) over (1.0.6-9.1) ...\n", - "Setting up libbz2-1.0:amd64 (1.0.6-9.2~deb10u1) ...\n", - "Selecting previously unselected package binutils-common:amd64.\n", - "(Reading database ... 12557 files and directories currently installed.)\n", - "Preparing to unpack .../00-binutils-common_2.31.1-16_amd64.deb ...\n", - "Unpacking binutils-common:amd64 (2.31.1-16) ...\n", - "Selecting previously unselected package libbinutils:amd64.\n", - "Preparing to unpack .../01-libbinutils_2.31.1-16_amd64.deb ...\n", - "Unpacking libbinutils:amd64 (2.31.1-16) ...\n", - "Selecting previously unselected package binutils-x86-64-linux-gnu.\n", - "Preparing to unpack .../02-binutils-x86-64-linux-gnu_2.31.1-16_amd64.deb ...\n", - "Unpacking binutils-x86-64-linux-gnu (2.31.1-16) ...\n", - "Selecting previously unselected package binutils.\n", - "Preparing to unpack .../03-binutils_2.31.1-16_amd64.deb ...\n", - "Unpacking binutils (2.31.1-16) ...\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Selecting previously unselected package libisl19:amd64.\n", - "Preparing to unpack .../04-libisl19_0.20-2_amd64.deb ...\n", - "Unpacking libisl19:amd64 (0.20-2) ...\n", - "Selecting previously unselected package libmpfr6:amd64.\n", - "Preparing to unpack .../05-libmpfr6_4.0.2-1_amd64.deb ...\n", - "Unpacking libmpfr6:amd64 (4.0.2-1) ...\n", - "Selecting previously unselected package libmpc3:amd64.\n", - "Preparing to unpack .../06-libmpc3_1.1.0-1_amd64.deb ...\n", - "Unpacking libmpc3:amd64 (1.1.0-1) ...\n", - "Selecting previously unselected package cpp-8.\n", - "Preparing to unpack .../07-cpp-8_8.3.0-6_amd64.deb ...\n", - "Unpacking cpp-8 (8.3.0-6) ...\n", - "Selecting previously unselected package cpp.\n", - "Preparing to unpack .../08-cpp_4%3a8.3.0-1_amd64.deb ...\n", - "Unpacking cpp (4:8.3.0-1) ...\n", - "Selecting previously unselected package libcurl4:amd64.\n", - "Preparing to unpack .../09-libcurl4_7.64.0-4_amd64.deb ...\n", - "Unpacking libcurl4:amd64 (7.64.0-4) ...\n", - "Selecting previously unselected package curl.\n", - "Preparing to unpack .../10-curl_7.64.0-4_amd64.deb ...\n", - "Unpacking curl (7.64.0-4) ...\n", - "Selecting previously unselected package libcc1-0:amd64.\n", - "Preparing to unpack .../11-libcc1-0_8.3.0-6_amd64.deb ...\n", - "Unpacking libcc1-0:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libgomp1:amd64.\n", - "Preparing to unpack .../12-libgomp1_8.3.0-6_amd64.deb ...\n", - "Unpacking libgomp1:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libitm1:amd64.\n", - "Preparing to unpack .../13-libitm1_8.3.0-6_amd64.deb ...\n", - "Unpacking libitm1:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libatomic1:amd64.\n", - "Preparing to unpack .../14-libatomic1_8.3.0-6_amd64.deb ...\n", - "Unpacking libatomic1:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libasan5:amd64.\n", - "Preparing to unpack .../15-libasan5_8.3.0-6_amd64.deb ...\n", - "Unpacking libasan5:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package liblsan0:amd64.\n", - "Preparing to unpack .../16-liblsan0_8.3.0-6_amd64.deb ...\n", - "Unpacking liblsan0:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libtsan0:amd64.\n", - "Preparing to unpack .../17-libtsan0_8.3.0-6_amd64.deb ...\n", - "Unpacking libtsan0:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libubsan1:amd64.\n", - "Preparing to unpack .../18-libubsan1_8.3.0-6_amd64.deb ...\n", - "Unpacking libubsan1:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libmpx2:amd64.\n", - "Preparing to unpack .../19-libmpx2_8.3.0-6_amd64.deb ...\n", - "Unpacking libmpx2:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libquadmath0:amd64.\n", - "Preparing to unpack .../20-libquadmath0_8.3.0-6_amd64.deb ...\n", - "Unpacking libquadmath0:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libgcc-8-dev:amd64.\n", - "Preparing to unpack .../21-libgcc-8-dev_8.3.0-6_amd64.deb ...\n", - "Unpacking libgcc-8-dev:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package gcc-8.\n", - "Preparing to unpack .../22-gcc-8_8.3.0-6_amd64.deb ...\n", - "Unpacking gcc-8 (8.3.0-6) ...\n", - "Selecting previously unselected package gcc.\n", - "Preparing to unpack .../23-gcc_4%3a8.3.0-1_amd64.deb ...\n", - "Unpacking gcc (4:8.3.0-1) ...\n", - "Selecting previously unselected package libc-dev-bin.\n", - "Preparing to unpack .../24-libc-dev-bin_2.28-10_amd64.deb ...\n", - "Unpacking libc-dev-bin (2.28-10) ...\n", - "Selecting previously unselected package linux-libc-dev:amd64.\n", - "Preparing to unpack .../25-linux-libc-dev_4.19.67-2+deb10u1_amd64.deb ...\n", - "Unpacking linux-libc-dev:amd64 (4.19.67-2+deb10u1) ...\n", - "Selecting previously unselected package libc6-dev:amd64.\n", - "Preparing to unpack .../26-libc6-dev_2.28-10_amd64.deb ...\n", - "Unpacking libc6-dev:amd64 (2.28-10) ...\n", - "Selecting previously unselected package libstdc++-8-dev:amd64.\n", - "Preparing to unpack .../27-libstdc++-8-dev_8.3.0-6_amd64.deb ...\n", - "Unpacking libstdc++-8-dev:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package g++-8.\n", - "Preparing to unpack .../28-g++-8_8.3.0-6_amd64.deb ...\n", - "Unpacking g++-8 (8.3.0-6) ...\n", - "Selecting previously unselected package g++.\n", - "Preparing to unpack .../29-g++_4%3a8.3.0-1_amd64.deb ...\n", - "Unpacking g++ (4:8.3.0-1) ...\n", - "Selecting previously unselected package libgfortran5:amd64.\n", - "Preparing to unpack .../30-libgfortran5_8.3.0-6_amd64.deb ...\n", - "Unpacking libgfortran5:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package libgfortran-8-dev:amd64.\n", - "Preparing to unpack .../31-libgfortran-8-dev_8.3.0-6_amd64.deb ...\n", - "Unpacking libgfortran-8-dev:amd64 (8.3.0-6) ...\n", - "Selecting previously unselected package gfortran-8.\n", - "Preparing to unpack .../32-gfortran-8_8.3.0-6_amd64.deb ...\n", - "Unpacking gfortran-8 (8.3.0-6) ...\n", - "Selecting previously unselected package gfortran.\n", - "Preparing to unpack .../33-gfortran_4%3a8.3.0-1_amd64.deb ...\n", - "Unpacking gfortran (4:8.3.0-1) ...\n", - "Selecting previously unselected package libltdl7:amd64.\n", - "Preparing to unpack .../34-libltdl7_2.4.6-9_amd64.deb ...\n", - "Unpacking libltdl7:amd64 (2.4.6-9) ...\n", - "Selecting previously unselected package libnuma1:amd64.\n", - "Preparing to unpack .../35-libnuma1_2.0.12-1_amd64.deb ...\n", - "Unpacking libnuma1:amd64 (2.0.12-1) ...\n", - "Selecting previously unselected package libhwloc5:amd64.\n", - "Preparing to unpack .../36-libhwloc5_1.11.12-3_amd64.deb ...\n", - "Unpacking libhwloc5:amd64 (1.11.12-3) ...\n", - "Selecting previously unselected package hwloc-nox.\n", - "Preparing to unpack .../37-hwloc-nox_1.11.12-3_amd64.deb ...\n", - "Unpacking hwloc-nox (1.11.12-3) ...\n", - "Selecting previously unselected package libmpich12:amd64.\n", - "Preparing to unpack .../38-libmpich12_3.3-3_amd64.deb ...\n", - "Unpacking libmpich12:amd64 (3.3-3) ...\n", - "Selecting previously unselected package mpich.\n", - "Preparing to unpack .../39-mpich_3.3-3_amd64.deb ...\n", - "Unpacking mpich (3.3-3) ...\n", - "Selecting previously unselected package libmpich-dev:amd64.\n", - "Preparing to unpack .../40-libmpich-dev_3.3-3_amd64.deb ...\n", - "Unpacking libmpich-dev:amd64 (3.3-3) ...\n", - "Selecting previously unselected package make.\n", - "Preparing to unpack .../41-make_4.2.1-1.2_amd64.deb ...\n", - "Unpacking make (4.2.1-1.2) ...\n", - "Setting up binutils-common:amd64 (2.31.1-16) ...\n", - "Setting up linux-libc-dev:amd64 (4.19.67-2+deb10u1) ...\n", - "Setting up libgomp1:amd64 (8.3.0-6) ...\n", - "Setting up bzip2 (1.0.6-9.2~deb10u1) ...\n", - "Setting up libasan5:amd64 (8.3.0-6) ...\n", - "Setting up make (4.2.1-1.2) ...\n", - "Setting up libmpfr6:amd64 (4.0.2-1) ...\n", - "Setting up libquadmath0:amd64 (8.3.0-6) ...\n", - "Setting up libmpc3:amd64 (1.1.0-1) ...\n", - "Setting up libatomic1:amd64 (8.3.0-6) ...\n", - "Setting up libltdl7:amd64 (2.4.6-9) ...\n", - "Setting up libgfortran5:amd64 (8.3.0-6) ...\n", - "Setting up libmpx2:amd64 (8.3.0-6) ...\n", - "Setting up libubsan1:amd64 (8.3.0-6) ...\n", - "Setting up libnuma1:amd64 (2.0.12-1) ...\n", - "Setting up libisl19:amd64 (0.20-2) ...\n", - "Setting up libmpich12:amd64 (3.3-3) ...\n", - "Setting up libcurl4:amd64 (7.64.0-4) ...\n", - "Setting up curl (7.64.0-4) ...\n", - "Setting up libbinutils:amd64 (2.31.1-16) ...\n", - "Setting up cpp-8 (8.3.0-6) ...\n", - "Setting up libc-dev-bin (2.28-10) ...\n", - "Setting up libcc1-0:amd64 (8.3.0-6) ...\n", - "Setting up liblsan0:amd64 (8.3.0-6) ...\n", - "Setting up libitm1:amd64 (8.3.0-6) ...\n", - "Setting up binutils-x86-64-linux-gnu (2.31.1-16) ...\n", - "Setting up libtsan0:amd64 (8.3.0-6) ...\n", - "Setting up libhwloc5:amd64 (1.11.12-3) ...\n", - "Setting up hwloc-nox (1.11.12-3) ...\n", - "Setting up binutils (2.31.1-16) ...\n", - "Setting up libgcc-8-dev:amd64 (8.3.0-6) ...\n", - "Setting up cpp (4:8.3.0-1) ...\n", - "Setting up libc6-dev:amd64 (2.28-10) ...\n", - "Setting up libstdc++-8-dev:amd64 (8.3.0-6) ...\n", - "Setting up libgfortran-8-dev:amd64 (8.3.0-6) ...\n", - "Setting up gcc-8 (8.3.0-6) ...\n", - "Setting up mpich (3.3-3) ...\n", - "update-alternatives: using /usr/bin/mpicc.mpich to provide /usr/bin/mpicc (mpi) in auto mode\n", - "update-alternatives: warning: skip creation of /usr/share/man/man1/mpicc.1.gz because associated file /usr/share/man/man1/mpicc.mpich.1.gz (of link group mpi) doesn't exist\n", - "update-alternatives: warning: skip creation of /usr/share/man/man1/mpic++.1.gz because associated file /usr/share/man/man1/mpicxx.mpich.1.gz (of link group mpi) doesn't exist\n", - "update-alternatives: warning: skip creation of /usr/share/man/man1/mpicxx.1.gz because associated file /usr/share/man/man1/mpicxx.mpich.1.gz (of link group mpi) doesn't exist\n", - "update-alternatives: warning: skip creation of /usr/share/man/man1/mpiCC.1.gz because associated file /usr/share/man/man1/mpicxx.mpich.1.gz (of link group mpi) doesn't exist\n", - "update-alternatives: warning: skip creation of /usr/share/man/man1/mpif77.1.gz because associated file /usr/share/man/man1/mpif77.mpich.1.gz (of link group mpi) doesn't exist\n", - "update-alternatives: warning: skip creation of /usr/share/man/man1/mpif90.1.gz because associated file /usr/share/man/man1/mpif90.mpich.1.gz (of link group mpi) doesn't exist\n", - "update-alternatives: warning: skip creation of /usr/share/man/man1/mpifort.1.gz because associated file /usr/share/man/man1/mpifort.mpich.1.gz (of link group mpi) doesn't exist\n", - "update-alternatives: using /usr/bin/mpirun.mpich to provide /usr/bin/mpirun (mpirun) in auto mode\n", - "update-alternatives: warning: skip creation of /usr/share/man/man1/mpirun.1.gz because associated file /usr/share/man/man1/mpirun.mpich.1.gz (of link group mpirun) doesn't exist\n", - "update-alternatives: warning: skip creation of /usr/share/man/man1/mpiexec.1.gz because associated file /usr/share/man/man1/mpiexec.mpich.1.gz (of link group mpirun) doesn't exist\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Setting up gfortran-8 (8.3.0-6) ...\n", - "Setting up gcc (4:8.3.0-1) ...\n", - "Setting up g++-8 (8.3.0-6) ...\n", - "Setting up gfortran (4:8.3.0-1) ...\n", - "update-alternatives: using /usr/bin/gfortran to provide /usr/bin/f95 (f95) in auto mode\n", - "update-alternatives: using /usr/bin/gfortran to provide /usr/bin/f77 (f77) in auto mode\n", - "Setting up g++ (4:8.3.0-1) ...\n", - "update-alternatives: using /usr/bin/g++ to provide /usr/bin/c++ (c++) in auto mode\n", - "Setting up libmpich-dev:amd64 (3.3-3) ...\n", - "update-alternatives: using /usr/include/x86_64-linux-gnu/mpich to provide /usr/include/x86_64-linux-gnu/mpi (mpi-x86_64-linux-gnu) in auto mode\n", - "Processing triggers for libc-bin (2.28-10) ...\n", - "Removing intermediate container d09c88390a5b\n", - " ---> 768917e3839d\n", - "Step 4/15 : ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml_sdk.v1.0.65.yml\n", - " ---> Running in f82224761f1f\n", - "Removing intermediate container f82224761f1f\n", - " ---> 6fc45a3f7f12\n", - "Step 5/15 : ADD $CONDA_ENV_FILE_NAME /tmp/$CONDA_ENV_FILE_NAME\n", - " ---> 956a64671412\n", - "Step 6/15 : ENV CONDA_DIR /opt/conda\n", - " ---> Running in 3eafe956d10d\n", - "Removing intermediate container 3eafe956d10d\n", - " ---> c11fc83e6bab\n", - "Step 7/15 : ENV CONDA_ENV_NAME fwi01_conda_env\n", - " ---> Running in 7eea1645616f\n", - "Removing intermediate container 7eea1645616f\n", - " ---> 4e42d4a8b84f\n", - "Step 8/15 : RUN git clone https://github.com/opesci/devito.git && cd devito && /opt/conda/bin/conda env create -q --name $CONDA_ENV_NAME -f environment.yml && pip install -e .\n", - " ---> Running in dda878848f67\n", - "\u001b[91mCloning into 'devito'...\n", - "\u001b[0mCollecting package metadata (repodata.json): ...working... done\n", - "Solving environment: ...working... done\n", - "Preparing transaction: ...working... done\n", - "Verifying transaction: ...working... done\n", - "Executing transaction: ...working... b'Enabling nb_conda_kernels...\\nStatus: enabled\\n'\n", - "done\n", - "Ran pip subprocess with arguments:\n", - "['/opt/conda/envs/fwi01_conda_env/bin/python', '-m', 'pip', 'install', '-U', '-r', '/devito/condaenv.rnqbkkc7.requirements.txt']\n", - "Pip subprocess output:\n", - "Collecting git+https://github.com/inducer/codepy (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 1))\n", - " Cloning https://github.com/inducer/codepy to /tmp/pip-req-build-tx3j8uvz\n", - "Collecting pyzfp>=0.2 (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/33/ff/1390221dc5ed78eb6573613a76927baeea02c31e73b9db91353b8834bb95/pyzfp-0.3.tar.gz (127kB)\n", - "Collecting pyrevolve>=2.1.3 (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 3))\n", - " Downloading https://files.pythonhosted.org/packages/0d/c4/63a36aa56969de861e181b86ebc1aba6764e9d2566c42e4faf1f360cb3cc/pyrevolve-2.1.3.tar.gz (190kB)\n", - "Collecting py-cpuinfo (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/42/60/63f28a5401da733043abe7053e7d9591491b4784c4f87c339bf51215aa0a/py-cpuinfo-5.0.0.tar.gz (82kB)\n", - "Collecting anytree>=2.4.3 (from -r /devito/condaenv.rnqbkkc7.requirements.txt (line 5))\n", - " Downloading https://files.pythonhosted.org/packages/62/89/640f607533415dd4b6d7a6d981614651a5763c1a1b55124bce8d27834073/anytree-2.7.1.tar.gz\n", - "Requirement already satisfied, skipping upgrade: pytools>=2015.1.2 in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (2019.1.1)\n", - "Requirement already satisfied, skipping upgrade: numpy>=1.6 in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (1.17.2)\n", - "Requirement already satisfied, skipping upgrade: appdirs>=1.4.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (1.4.3)\n", - "Requirement already satisfied, skipping upgrade: six in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (1.12.0)\n", - "Requirement already satisfied, skipping upgrade: cgen in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (2019.1)\n", - "Collecting blosc (from pyrevolve>=2.1.3->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 3))\n", - " Downloading https://files.pythonhosted.org/packages/6d/3b/2b707cd330a205ba5c69b5e8bfa9c05691442e45ce9ce882c4c8d343e61a/blosc-1.8.1.tar.gz (769kB)\n", - "Collecting contexttimer (from pyrevolve>=2.1.3->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 3))\n", - " Downloading https://files.pythonhosted.org/packages/1d/e0/504aa08a83dc2ff90f61a83b5f70d689e1f5138ab30576124ea2ff9f5076/contexttimer-0.3.3.tar.gz\n", - "Requirement already satisfied, skipping upgrade: decorator>=3.2.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.7/site-packages (from pytools>=2015.1.2->codepy==2019.1->-r /devito/condaenv.rnqbkkc7.requirements.txt (line 1)) (4.4.0)\n", - "Building wheels for collected packages: pyzfp, pyrevolve, py-cpuinfo, anytree, codepy, blosc, contexttimer\n", - " Building wheel for pyzfp (setup.py): started\n", - " Building wheel for pyzfp (setup.py): finished with status 'done'\n", - " Created wheel for pyzfp: filename=pyzfp-0.3-cp37-cp37m-linux_x86_64.whl size=469789 sha256=871081f556377ec4faddab41185319f0fdbe5189d6ab502d845ee9a09f21a2b3\n", - " Stored in directory: /root/.cache/pip/wheels/a6/b5/c8/60b5a2d3fd3cbb49c37935bd498037ce18dae811d9e301b885\n", - " Building wheel for pyrevolve (setup.py): started\n", - " Building wheel for pyrevolve (setup.py): finished with status 'done'\n", - " Created wheel for pyrevolve: filename=pyrevolve-2.1.3-cp37-cp37m-linux_x86_64.whl size=333186 sha256=71a0bf26497a5684fff5b0fede906dc9622bcadfff76e51115c7af234b35e4cf\n", - " Stored in directory: /root/.cache/pip/wheels/df/d7/36/3e8e92a06a23446febeb604b528faeffba1a26c0d63e924a0a\n", - " Building wheel for py-cpuinfo (setup.py): started\n", - " Building wheel for py-cpuinfo (setup.py): finished with status 'done'\n", - " Created wheel for py-cpuinfo: filename=py_cpuinfo-5.0.0-cp37-none-any.whl size=18685 sha256=e8317812ee2b9be2e7fc142f7568e9526c4eb9625ee36f6851d487714f60afc5\n", - " Stored in directory: /root/.cache/pip/wheels/01/7e/a9/b982d0fea22b7e4ae5619de949570cde5ad55420cec16e86a5\n", - " Building wheel for anytree (setup.py): started\n", - " Building wheel for anytree (setup.py): finished with status 'done'\n", - " Created wheel for anytree: filename=anytree-2.7.1-py2.py3-none-any.whl size=30359 sha256=7dec01c26143b500939abac549de0065909ac0f03c72b8fd64543231ba47a39d\n", - " Stored in directory: /root/.cache/pip/wheels/2e/91/6d/10d009931e25814dfa7161d81b12d0d80cbdd18457ef7835e2\n", - " Building wheel for codepy (setup.py): started\n", - " Building wheel for codepy (setup.py): finished with status 'done'\n", - " Created wheel for codepy: filename=codepy-2019.1-cp37-none-any.whl size=19363 sha256=baa045a5fd2b2c4d95c316c8a429e69137d46a29b5efbef76646161974c39373\n", - " Stored in directory: /tmp/pip-ephem-wheel-cache-88yzxyb8/wheels/8b/e5/f8/5fa48f28841d0433a91e37d17a60fb9d8d5d20e603ebe4419b\n", - " Building wheel for blosc (setup.py): started\n", - " Building wheel for blosc (setup.py): finished with status 'done'\n", - " Created wheel for blosc: filename=blosc-1.8.1-cp37-cp37m-linux_x86_64.whl size=2108795 sha256=14a8b68475663e0f8a9e5c63d2b74bcea5199468082583846e23dde5b29700c3\n", - " Stored in directory: /root/.cache/pip/wheels/3b/e0/b9/99a77fb1821f0df30e52b9ce470c74efa2ca359ff8c21f8e17\n", - " Building wheel for contexttimer (setup.py): started\n", - " Building wheel for contexttimer (setup.py): finished with status 'done'\n", - " Created wheel for contexttimer: filename=contexttimer-0.3.3-cp37-none-any.whl size=5817 sha256=86745a9db4b2c2e01a7abfed5519809e3c0856c0508cef37cdb7cc3ba0bc336f\n", - " Stored in directory: /root/.cache/pip/wheels/b3/e2/35/565145ce0127c7451b6503dfabb2b56e9908c863e40c6b1870\n", - "Successfully built pyzfp pyrevolve py-cpuinfo anytree codepy blosc contexttimer\n", - "Installing collected packages: pyzfp, blosc, contexttimer, pyrevolve, py-cpuinfo, anytree, codepy\n", - "Successfully installed anytree-2.7.1 blosc-1.8.1 codepy-2019.1 contexttimer-0.3.3 py-cpuinfo-5.0.0 pyrevolve-2.1.3 pyzfp-0.3\n", - "\n", - "#\n", - "# To activate this environment, use\n", - "#\n", - "# $ conda activate fwi01_conda_env\n", - "#\n", - "# To deactivate an active environment, use\n", - "#\n", - "# $ conda deactivate\n", - "\n", - "Obtaining file:///devito\n", - "Requirement already satisfied: pip>=9.0.1 in /opt/conda/lib/python3.7/site-packages (from devito==3.5+214.g24ede913) (19.1.1)\n", - "Collecting numpy>=1.14 (from devito==3.5+214.g24ede913)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Downloading https://files.pythonhosted.org/packages/ba/e0/46e2f0540370f2661b044647fa447fef2ecbcc8f7cdb4329ca2feb03fb23/numpy-1.17.2-cp37-cp37m-manylinux1_x86_64.whl (20.3MB)\n", - "Collecting sympy>=1.4 (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/21/21/f4105795ca7f35c541d82c5b06be684dd2f5cb4f508fb487cd7aea4de776/sympy-1.4-py2.py3-none-any.whl (5.3MB)\n", - "Collecting scipy (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/94/7f/b535ec711cbcc3246abea4385d17e1b325d4c3404dd86f15fc4f3dba1dbb/scipy-1.3.1-cp37-cp37m-manylinux1_x86_64.whl (25.2MB)\n", - "Collecting pytest>=3.6 (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/0c/91/d68f68ce54cd3e8afa1ef73ea1ad44df2438521b64c0820e5fd9b9f13b7d/pytest-5.2.1-py3-none-any.whl (226kB)\n", - "Collecting pytest-runner (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/f8/31/f291d04843523406f242e63b5b90f7b204a756169b4250ff213e10326deb/pytest_runner-5.1-py2.py3-none-any.whl\n", - "Collecting flake8>=2.1.0 (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/26/de/3f815a99d86eb10464ea7bd6059c0172c7ca97d4bdcfca41051b388a653b/flake8-3.7.8-py2.py3-none-any.whl (70kB)\n", - "Collecting jedi (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/55/54/da994f359e4e7da4776a200e76dbc85ba5fc319eefc22e33d55296d95a1d/jedi-0.15.1-py2.py3-none-any.whl (1.0MB)\n", - "Collecting nbval (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/80/08/6b1e7d87a90edb02c272e30f4f6f325da5a5c8dfc3eaeaa4a8138fb97e43/nbval-0.9.3-py2.py3-none-any.whl\n", - "Collecting cached-property (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/3b/86/85c1be2e8db9e13ef9a350aecd6dea292bd612fa288c2f40d035bb750ded/cached_property-1.5.1-py2.py3-none-any.whl\n", - "Collecting psutil>=5.1.0 (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/1c/ca/5b8c1fe032a458c2c4bcbe509d1401dca9dda35c7fc46b36bb81c2834740/psutil-5.6.3.tar.gz (435kB)\n", - "Collecting py-cpuinfo (from devito==3.5+214.g24ede913)\n", - "Collecting cgen (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/92/4e/3f90d389714e005853d98041ec75ec06815179f47db735e5c45a41d579d6/cgen-2019.1.tar.gz\n", - "Collecting codepy (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/6c/81/338a4d4145af7857f9b6fdf9b4d53c58c7eb4c1d092ff6c010efdb4dfdf3/codepy-2019.1.tar.gz\n", - "Collecting click (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl (81kB)\n", - "Collecting codecov (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/8b/28/4c1950a61c3c5786f0f34d643d0d28ec832433c9a7c0bd157690d4eb1d5f/codecov-2.0.15-py2.py3-none-any.whl\n", - "Collecting pytest-cov (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/b9/54/3673ee8be482f81527678ac894276223b9814bb7262e4f730469bb7bf70e/pytest_cov-2.8.1-py2.py3-none-any.whl\n", - "Collecting multidict (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/88/f0/4d4cbd1a3744e3985efa49682352d0703df653ffa76b81f10fed86599a50/multidict-4.5.2-cp37-cp37m-manylinux1_x86_64.whl (309kB)\n", - "Collecting frozendict (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/4e/55/a12ded2c426a4d2bee73f88304c9c08ebbdbadb82569ebdd6a0c007cfd08/frozendict-1.2.tar.gz\n", - "Collecting anytree>=2.4.3 (from devito==3.5+214.g24ede913)\n", - "Collecting pyzfp>=0.2 (from devito==3.5+214.g24ede913)\n", - "Collecting pyrevolve>=2.1.3 (from devito==3.5+214.g24ede913)\n", - "Collecting distributed>=1.27 (from devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/3d/4a/1d1b356c16cefce3e91237b5e10b3e992d480414ade26493730c75d28ee0/distributed-2.5.2-py3-none-any.whl (557kB)\n", - "Collecting mpmath>=0.19 (from sympy>=1.4->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/ca/63/3384ebb3b51af9610086b23ea976e6d27d6d97bf140a76a365bd77a3eb32/mpmath-1.1.0.tar.gz (512kB)\n", - "Collecting wcwidth (from pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/7e/9f/526a6947247599b084ee5232e4f9190a38f398d7300d866af3ab571a5bfe/wcwidth-0.1.7-py2.py3-none-any.whl\n", - "Collecting pluggy<1.0,>=0.12 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/92/c7/48439f7d5fd6bddb4c04b850bb862b42e3e2b98570040dfaf68aedd8114b/pluggy-0.13.0-py2.py3-none-any.whl\n", - "Collecting py>=1.5.0 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/76/bc/394ad449851729244a97857ee14d7cba61ddb268dce3db538ba2f2ba1f0f/py-1.8.0-py2.py3-none-any.whl (83kB)\n", - "Collecting atomicwrites>=1.0 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/52/90/6155aa926f43f2b2a22b01be7241be3bfd1ceaf7d0b3267213e8127d41f4/atomicwrites-1.3.0-py2.py3-none-any.whl\n", - "Collecting importlib-metadata>=0.12; python_version < \"3.8\" (from pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/f6/d2/40b3fa882147719744e6aa50ac39cf7a22a913cbcba86a0371176c425a3b/importlib_metadata-0.23-py2.py3-none-any.whl\n", - "Collecting attrs>=17.4.0 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/6b/e8/2ecaf86b128a34e225807f03b22664302937ab826bd3b7eccab6754d29ea/attrs-19.2.0-py2.py3-none-any.whl (40kB)\n", - "Collecting packaging (from pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/cf/94/9672c2d4b126e74c4496c6b3c58a8b51d6419267be9e70660ba23374c875/packaging-19.2-py2.py3-none-any.whl\n", - "Collecting more-itertools>=4.0.0 (from pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/45/dc/3241eef99eb45f1def35cf93af35d1cf9ef4c0991792583b8f33ea41b092/more_itertools-7.2.0-py3-none-any.whl (57kB)\n", - "Collecting pyflakes<2.2.0,>=2.1.0 (from flake8>=2.1.0->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/84/f2/ed0ffb887f8138a8fe5a621b8c0bb9598bfb3989e029f6c6a85ee66628ee/pyflakes-2.1.1-py2.py3-none-any.whl (59kB)\n", - "Collecting pycodestyle<2.6.0,>=2.5.0 (from flake8>=2.1.0->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/0e/0c/04a353e104d2f324f8ee5f4b32012618c1c86dd79e52a433b64fceed511b/pycodestyle-2.5.0-py2.py3-none-any.whl (51kB)\n", - "Collecting mccabe<0.7.0,>=0.6.0 (from flake8>=2.1.0->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/87/89/479dc97e18549e21354893e4ee4ef36db1d237534982482c3681ee6e7b57/mccabe-0.6.1-py2.py3-none-any.whl\n", - "Collecting entrypoints<0.4.0,>=0.3.0 (from flake8>=2.1.0->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/ac/c6/44694103f8c221443ee6b0041f69e2740d89a25641e62fb4f2ee568f2f9c/entrypoints-0.3-py2.py3-none-any.whl\n", - "Collecting parso>=0.5.0 (from jedi->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/a3/bd/bf4e5bd01d79906e5b945a7af033154da49fd2b0d5b5c705a21330323305/parso-0.5.1-py2.py3-none-any.whl (95kB)\n", - "Collecting nbformat (from nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/da/27/9a654d2b6cc1eaa517d1c5a4405166c7f6d72f04f6e7eea41855fe808a46/nbformat-4.4.0-py2.py3-none-any.whl (155kB)\n", - "Requirement already satisfied: six in /opt/conda/lib/python3.7/site-packages (from nbval->devito==3.5+214.g24ede913) (1.12.0)\n", - "Collecting jupyter-client (from nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/13/81/fe0eee1bcf949851a120254b1f530ae1e01bdde2d3ab9710c6ff81525061/jupyter_client-5.3.4-py2.py3-none-any.whl (92kB)\n", - "Collecting coverage (from nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/82/8f/a2a687fa00059360858023c5cb74e94b8afaf318726e9a256934066a9d90/coverage-4.5.4-cp37-cp37m-manylinux1_x86_64.whl (205kB)\n", - "Collecting ipykernel (from nbval->devito==3.5+214.g24ede913)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Downloading https://files.pythonhosted.org/packages/d4/16/43f51f65a8a08addf04f909a0938b06ba1ee1708b398a9282474531bd893/ipykernel-5.1.2-py3-none-any.whl (116kB)\n", - "Collecting pytools>=2015.1.2 (from cgen->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/00/96/00416762a3eda8876a17d007df4a946f46b2e4ee1057e0b9714926472ef8/pytools-2019.1.1.tar.gz (58kB)\n", - "Collecting appdirs>=1.4.0 (from codepy->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/56/eb/810e700ed1349edde4cbdc1b2a21e28cdf115f9faf263f6bbf8447c1abf3/appdirs-1.4.3-py2.py3-none-any.whl\n", - "Requirement already satisfied: requests>=2.7.9 in /opt/conda/lib/python3.7/site-packages (from codecov->devito==3.5+214.g24ede913) (2.22.0)\n", - "Collecting contexttimer (from pyrevolve>=2.1.3->devito==3.5+214.g24ede913)\n", - "Collecting blosc (from pyrevolve>=2.1.3->devito==3.5+214.g24ede913)\n", - "Collecting msgpack (from distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/25/f8/6009e73f5b08743718d0660a18ecbc44b013a68980347a3835b63e875cdb/msgpack-0.6.2-cp37-cp37m-manylinux1_x86_64.whl (243kB)\n", - "Collecting tblib (from distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/64/b5/ebb1af4d843047ccd7292b92f5e5f8643153e8b95d14508d9fe3b35f7004/tblib-1.4.0-py2.py3-none-any.whl\n", - "Collecting toolz>=0.7.4 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/22/8e/037b9ba5c6a5739ef0dcde60578c64d49f45f64c5e5e886531bfbc39157f/toolz-0.10.0.tar.gz (49kB)\n", - "Collecting zict>=0.1.3 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/64/b4/a904be4184814adb9dfc2e679c4e611392080a32726a657a34cab93b38c2/zict-1.0.0-py2.py3-none-any.whl\n", - "Collecting tornado>=5 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/30/78/2d2823598496127b21423baffaa186b668f73cd91887fcef78b6eade136b/tornado-6.0.3.tar.gz (482kB)\n", - "Collecting pyyaml (from distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/e3/e8/b3212641ee2718d556df0f23f78de8303f068fe29cdaa7a91018849582fe/PyYAML-5.1.2.tar.gz (265kB)\n", - "Collecting dask>=2.3 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/2a/56/565c82ec663ba1f2f12d2666fbac97f100c60ac49dd4a7549228de14f097/dask-2.5.2-py3-none-any.whl (760kB)\n", - "Collecting cloudpickle>=0.2.2 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/c1/49/334e279caa3231255725c8e860fa93e72083567625573421db8875846c14/cloudpickle-1.2.2-py2.py3-none-any.whl\n", - "Collecting sortedcontainers!=2.0.0,!=2.0.1 (from distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/13/f3/cf85f7c3a2dbd1a515d51e1f1676d971abe41bba6f4ab5443240d9a78e5b/sortedcontainers-2.1.0-py2.py3-none-any.whl\n", - "Collecting zipp>=0.5 (from importlib-metadata>=0.12; python_version < \"3.8\"->pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/74/3d/1ee25a26411ba0401b43c6376d2316a71addcc72ef8690b101b4ea56d76a/zipp-0.6.0-py2.py3-none-any.whl\n", - "Collecting pyparsing>=2.0.2 (from packaging->pytest>=3.6->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/11/fa/0160cd525c62d7abd076a070ff02b2b94de589f1a9789774f17d7c54058e/pyparsing-2.4.2-py2.py3-none-any.whl (65kB)\n", - "Collecting ipython-genutils (from nbformat->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/fa/bc/9bd3b5c2b4774d5f33b2d544f1460be9df7df2fe42f352135381c347c69a/ipython_genutils-0.2.0-py2.py3-none-any.whl\n", - "Collecting traitlets>=4.1 (from nbformat->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/ca/ab/872a23e29cec3cf2594af7e857f18b687ad21039c1f9b922fac5b9b142d5/traitlets-4.3.3-py2.py3-none-any.whl (75kB)\n", - "Collecting jupyter-core (from nbformat->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/56/a6/fe4b7029d4994870df6685bdc7bae5417bea30b627c4ce36106f9cac31fc/jupyter_core-4.6.0-py2.py3-none-any.whl (82kB)\n", - "Collecting jsonschema!=2.5.0,>=2.4 (from nbformat->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/11/9c/a0a2c70be340603c8ff5a692a8e6a4997fb858c7fd8701ff2afe087a3b58/jsonschema-3.1.0-py2.py3-none-any.whl (56kB)\n", - "Collecting python-dateutil>=2.1 (from jupyter-client->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/41/17/c62faccbfbd163c7f57f3844689e3a78bae1f403648a6afb1d0866d87fbb/python_dateutil-2.8.0-py2.py3-none-any.whl (226kB)\n", - "Collecting pyzmq>=13 (from jupyter-client->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/c7/6a/307e4a576787c7df1df6ebf56754c3fc8defcafa1a09ee22e9b961a390be/pyzmq-18.1.0-cp37-cp37m-manylinux1_x86_64.whl (1.1MB)\n", - "Collecting ipython>=5.0.0 (from ipykernel->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/fb/44/f28a13852e562af719f9de1761680a84a93e8b4c50e22d00d68f60ee2e8b/ipython-7.8.0-py3-none-any.whl (775kB)\n", - "Collecting decorator>=3.2.0 (from pytools>=2015.1.2->cgen->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/5f/88/0075e461560a1e750a0dcbf77f1d9de775028c37a19a346a6c565a257399/decorator-4.4.0-py2.py3-none-any.whl\n", - "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.7/site-packages (from requests>=2.7.9->codecov->devito==3.5+214.g24ede913) (2019.6.16)\n", - "Requirement already satisfied: idna<2.9,>=2.5 in /opt/conda/lib/python3.7/site-packages (from requests>=2.7.9->codecov->devito==3.5+214.g24ede913) (2.8)\n", - "Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /opt/conda/lib/python3.7/site-packages (from requests>=2.7.9->codecov->devito==3.5+214.g24ede913) (3.0.4)\n", - "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /opt/conda/lib/python3.7/site-packages (from requests>=2.7.9->codecov->devito==3.5+214.g24ede913) (1.24.2)\n", - "Collecting heapdict (from zict>=0.1.3->distributed>=1.27->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/b6/9d/cd4777dbcf3bef9d9627e0fe4bc43d2e294b1baeb01d0422399d5e9de319/HeapDict-1.0.1-py3-none-any.whl\n", - "Collecting js-regex>=1.0.0 (from jsonschema!=2.5.0,>=2.4->nbformat->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/b1/4e/3493c42964a91f5d96f92f30c924421ee3b346d89db5c0fc45d0d8b04788/js_regex-1.0.0-py3-none-any.whl\n", - "Requirement already satisfied: setuptools in /opt/conda/lib/python3.7/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat->nbval->devito==3.5+214.g24ede913) (41.0.1)\n", - "Collecting pyrsistent>=0.14.0 (from jsonschema!=2.5.0,>=2.4->nbformat->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/b9/66/b2638d96a2d128b168d0dba60fdc77b7800a9b4a5340cefcc5fc4eae6295/pyrsistent-0.15.4.tar.gz (107kB)\n", - "Collecting backcall (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/84/71/c8ca4f5bb1e08401b916c68003acf0a0655df935d74d93bf3f3364b310e0/backcall-0.1.0.tar.gz\n", - "Collecting pickleshare (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/9a/41/220f49aaea88bc6fa6cba8d05ecf24676326156c23b991e80b3f2fc24c77/pickleshare-0.7.5-py2.py3-none-any.whl\n", - "Collecting pygments (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/5c/73/1dfa428150e3ccb0fa3e68db406e5be48698f2a979ccbcec795f28f44048/Pygments-2.4.2-py2.py3-none-any.whl (883kB)\n", - "Collecting prompt-toolkit<2.1.0,>=2.0.0 (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/87/61/2dfea88583d5454e3a64f9308a686071d58d59a55db638268a6413e1eb6d/prompt_toolkit-2.0.10-py3-none-any.whl (340kB)\n", - "Collecting pexpect; sys_platform != \"win32\" (from ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/0e/3e/377007e3f36ec42f1b84ec322ee12141a9e10d808312e5738f52f80a232c/pexpect-4.7.0-py2.py3-none-any.whl (58kB)\n", - "Collecting ptyprocess>=0.5 (from pexpect; sys_platform != \"win32\"->ipython>=5.0.0->ipykernel->nbval->devito==3.5+214.g24ede913)\n", - " Downloading https://files.pythonhosted.org/packages/d1/29/605c2cc68a9992d18dada28206eeada56ea4bd07a239669da41674648b6f/ptyprocess-0.6.0-py2.py3-none-any.whl\n", - "Building wheels for collected packages: psutil, cgen, codepy, frozendict, mpmath, pytools, toolz, tornado, pyyaml, pyrsistent, backcall\n", - " Building wheel for psutil (setup.py): started\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Building wheel for psutil (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/90/7e/74/bb640d77775e6b6a78bcc3120f9fea4d2a28b2706de1cff37d\n", - " Building wheel for cgen (setup.py): started\n", - " Building wheel for cgen (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/58/80/1c/4b18cf63778aafa8f62c37395448164667143bbbb20bf16b9d\n", - " Building wheel for codepy (setup.py): started\n", - " Building wheel for codepy (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/f4/53/e7/b53cf7ba45381b676bbd5eaaedc19ae82e1c397e9c1766ddf4\n", - " Building wheel for frozendict (setup.py): started\n", - " Building wheel for frozendict (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/6c/6c/e9/534386165bd12cf1885582c75eb6d0ffcb321b65c23fe0f834\n", - " Building wheel for mpmath (setup.py): started\n", - " Building wheel for mpmath (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/63/9d/8e/37c3f6506ed3f152733a699e92d8e0c9f5e5f01dea262f80ad\n", - " Building wheel for pytools (setup.py): started\n", - " Building wheel for pytools (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/83/df/0b/75ac4572aaa93e3eba6a58472635d0fda907f5f4cf884a3a0c\n", - " Building wheel for toolz (setup.py): started\n", - " Building wheel for toolz (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/e1/8b/65/3294e5b727440250bda09e8c0153b7ba19d328f661605cb151\n", - " Building wheel for tornado (setup.py): started\n", - " Building wheel for tornado (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/84/bf/40/2f6ef700f48401ca40e5e3dd7d0e3c0a90e064897b7fe5fc08\n", - " Building wheel for pyyaml (setup.py): started\n", - " Building wheel for pyyaml (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/d9/45/dd/65f0b38450c47cf7e5312883deb97d065e030c5cca0a365030\n", - " Building wheel for pyrsistent (setup.py): started\n", - " Building wheel for pyrsistent (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/bb/46/00/6d471ef0b813e3621f0abe6cb723c20d529d39a061de3f7c51\n", - " Building wheel for backcall (setup.py): started\n", - " Building wheel for backcall (setup.py): finished with status 'done'\n", - " Stored in directory: /root/.cache/pip/wheels/98/b0/dd/29e28ff615af3dda4c67cab719dd51357597eabff926976b45\n", - "Successfully built psutil cgen codepy frozendict mpmath pytools toolz tornado pyyaml pyrsistent backcall\n", - "Installing collected packages: numpy, mpmath, sympy, scipy, wcwidth, more-itertools, zipp, importlib-metadata, pluggy, py, atomicwrites, attrs, pyparsing, packaging, pytest, pytest-runner, pyflakes, pycodestyle, mccabe, entrypoints, flake8, parso, jedi, ipython-genutils, decorator, traitlets, jupyter-core, js-regex, pyrsistent, jsonschema, nbformat, python-dateutil, tornado, pyzmq, jupyter-client, coverage, backcall, pickleshare, pygments, prompt-toolkit, ptyprocess, pexpect, ipython, ipykernel, nbval, cached-property, psutil, py-cpuinfo, appdirs, pytools, cgen, codepy, click, codecov, pytest-cov, multidict, frozendict, anytree, pyzfp, contexttimer, blosc, pyrevolve, msgpack, tblib, toolz, heapdict, zict, pyyaml, dask, cloudpickle, sortedcontainers, distributed, devito\n", - " Running setup.py develop for devito\n", - "Successfully installed anytree-2.7.1 appdirs-1.4.3 atomicwrites-1.3.0 attrs-19.2.0 backcall-0.1.0 blosc-1.8.1 cached-property-1.5.1 cgen-2019.1 click-7.0 cloudpickle-1.2.2 codecov-2.0.15 codepy-2019.1 contexttimer-0.3.3 coverage-4.5.4 dask-2.5.2 decorator-4.4.0 devito distributed-2.5.2 entrypoints-0.3 flake8-3.7.8 frozendict-1.2 heapdict-1.0.1 importlib-metadata-0.23 ipykernel-5.1.2 ipython-7.8.0 ipython-genutils-0.2.0 jedi-0.15.1 js-regex-1.0.0 jsonschema-3.1.0 jupyter-client-5.3.4 jupyter-core-4.6.0 mccabe-0.6.1 more-itertools-7.2.0 mpmath-1.1.0 msgpack-0.6.2 multidict-4.5.2 nbformat-4.4.0 nbval-0.9.3 numpy-1.17.2 packaging-19.2 parso-0.5.1 pexpect-4.7.0 pickleshare-0.7.5 pluggy-0.13.0 prompt-toolkit-2.0.10 psutil-5.6.3 ptyprocess-0.6.0 py-1.8.0 py-cpuinfo-5.0.0 pycodestyle-2.5.0 pyflakes-2.1.1 pygments-2.4.2 pyparsing-2.4.2 pyrevolve-2.1.3 pyrsistent-0.15.4 pytest-5.2.1 pytest-cov-2.8.1 pytest-runner-5.1 python-dateutil-2.8.0 pytools-2019.1.1 pyyaml-5.1.2 pyzfp-0.3 pyzmq-18.1.0 scipy-1.3.1 sortedcontainers-2.1.0 sympy-1.4 tblib-1.4.0 toolz-0.10.0 tornado-6.0.3 traitlets-4.3.3 wcwidth-0.1.7 zict-1.0.0 zipp-0.6.0\n", - "Removing intermediate container dda878848f67\n", - " ---> aca43feeae5e\n", - "Step 9/15 : ENV CONDA_AUTO_UPDATE_CONDA=false\n", - " ---> Running in 0b8ba14eee73\n", - "Removing intermediate container 0b8ba14eee73\n", - " ---> 8e908fdb3492\n", - "Step 10/15 : ENV CONDA_DEFAULT_ENV=$CONDA_ENV_NAME\n", - " ---> Running in b04f04081369\n", - "Removing intermediate container b04f04081369\n", - " ---> 9be194321ba2\n", - "Step 11/15 : ENV CONDA_PREFIX=$CONDA_DIR/envs/$CONDA_DEFAULT_ENV\n", - " ---> Running in 464f87b1ebbe\n", - "Removing intermediate container 464f87b1ebbe\n", - " ---> c907c3cffd3e\n", - "Step 12/15 : ENV PATH=$CONDA_PREFIX/bin:/opt/conda/bin:$PATH\n", - " ---> Running in 307eed25ed06\n", - "Removing intermediate container 307eed25ed06\n", - " ---> 071dfa04aba7\n", - "Step 13/15 : RUN /opt/conda/bin/conda env update --name $CONDA_ENV_NAME -f /tmp/$CONDA_ENV_FILE_NAME && /opt/conda/bin/conda clean --yes --all\n", - " ---> Running in 042a9e35656f\n", - "Collecting package metadata (repodata.json): ...working... done\n", - "Solving environment: ...working... done\n", - "\n", - "Downloading and Extracting Packages\n", - "babel-2.7.0 | 5.8 MB | ########## | 100% \n", - "pycodestyle-2.5.0 | 60 KB | ########## | 100% \n", - "gmpy2-2.0.8 | 165 KB | ########## | 100% \n", - "intel-openmp-2019.5 | 895 KB | ########## | 100% \n", - "graphite2-1.3.13 | 101 KB | ########## | 100% \n", - "fsspec-0.5.2 | 46 KB | ########## | 100% \n", - "alabaster-0.7.12 | 17 KB | ########## | 100% \n", - "pluggy-0.13.0 | 31 KB | ########## | 100% \n", - "wcwidth-0.1.7 | 25 KB | ########## | 100% \n", - "multidict-4.5.2 | 142 KB | ########## | 100% \n", - "locket-0.2.0 | 8 KB | ########## | 100% \n", - "pywavelets-1.0.3 | 4.4 MB | ########## | 100% \n", - "snakeviz-2.0.1 | 316 KB | ########## | 100% \n", - "blosc-1.17.0 | 862 KB | ########## | 100% \n", - "pixman-0.38.0 | 618 KB | ########## | 100% \n", - "distributed-2.5.2 | 396 KB | ########## | 100% \n", - "xz-5.2.4 | 366 KB | ########## | 100% \n", - "numexpr-2.7.0 | 196 KB | ########## | 100% \n", - "libgcc-ng-9.1.0 | 8.1 MB | ########## | 100% \n", - "cloudpickle-1.2.2 | 29 KB | ########## | 100% \n", - "pytest-5.2.1 | 366 KB | ########## | 100% \n", - "nb_conda_kernels-2.2 | 37 KB | ########## | 100% \n", - "libgfortran-ng-7.3.0 | 1.3 MB | ########## | 100% \n", - "jsonschema-3.0.2 | 90 KB | ########## | 100% \n", - "sphinxcontrib-qthelp | 26 KB | ########## | 100% \n", - "olefile-0.46 | 48 KB | ########## | 100% \n", - "cytoolz-0.10.0 | 439 KB | ########## | 100% \n", - "psutil-5.6.3 | 328 KB | ########## | 100% \n", - "networkx-2.3 | 1.1 MB | ########## | 100% \n", - "graphviz-2.40.1 | 6.9 MB | ########## | 100% \n", - "pyqt-5.9.2 | 5.6 MB | ########## | 100% \n", - "ipython_genutils-0.2 | 39 KB | ########## | 100% \n", - "scikit-image-0.15.0 | 28.4 MB | ########## | 100% \n", - "mkl_random-1.1.0 | 369 KB | ########## | 100% \n", - "coverage-4.5.4 | 226 KB | ########## | 100% \n", - "six-1.12.0 | 22 KB | ########## | 100% \n", - "gst-plugins-base-1.1 | 6.3 MB | ########## | 100% \n", - "libpng-1.6.37 | 364 KB | ########## | 100% \n", - "more-itertools-7.2.0 | 99 KB | ########## | 100% \n", - "pytables-3.5.2 | 1.5 MB | ########## | 100% \n", - "openssl-1.1.1 | 5.0 MB | ########## | 100% \n", - "libtiff-4.0.10 | 604 KB | ########## | 100% \n", - "ptyprocess-0.6.0 | 23 KB | ########## | 100% \n", - "urllib3-1.24.2 | 153 KB | ########## | 100% \n", - "dask-core-2.5.2 | 579 KB | ########## | 100% \n", - "defusedxml-0.6.0 | 23 KB | ########## | 100% \n", - "bokeh-1.3.4 | 4.0 MB | ########## | 100% \n", - "qt-5.9.7 | 85.9 MB | ########## | 100% \n", - "pyzmq-18.1.0 | 520 KB | ########## | 100% \n", - "prompt_toolkit-2.0.1 | 227 KB | ########## | 100% \n", - "mock-3.0.5 | 47 KB | ########## | 100% \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "pandas-0.25.1 | 11.4 MB | ########## | 100% \n", - "icu-58.2 | 22.5 MB | ########## | 100% \n", - "dask-2.5.2 | 12 KB | ########## | 100% \n", - "ca-certificates-2019 | 132 KB | ########## | 100% \n", - "libedit-3.1.20181209 | 188 KB | ########## | 100% \n", - "numpy-1.17.2 | 4 KB | ########## | 100% \n", - "yaml-0.1.7 | 84 KB | ########## | 100% \n", - "certifi-2019.9.11 | 154 KB | ########## | 100% \n", - "imageio-2.6.0 | 3.3 MB | ########## | 100% \n", - "pexpect-4.7.0 | 82 KB | ########## | 100% \n", - "asn1crypto-1.0.1 | 161 KB | ########## | 100% \n", - "hdf5-1.10.4 | 5.3 MB | ########## | 100% \n", - "prometheus_client-0. | 42 KB | ########## | 100% \n", - "packaging-19.2 | 30 KB | ########## | 100% \n", - "appdirs-1.4.3 | 16 KB | ########## | 100% \n", - "cycler-0.10.0 | 13 KB | ########## | 100% \n", - "cython-0.29.13 | 2.2 MB | ########## | 100% \n", - "libstdcxx-ng-9.1.0 | 4.0 MB | ########## | 100% \n", - "cached-property-1.5. | 11 KB | ########## | 100% \n", - "docutils-0.15.2 | 742 KB | ########## | 100% \n", - "tk-8.6.8 | 3.1 MB | ########## | 100% \n", - "bzip2-1.0.8 | 105 KB | ########## | 100% \n", - "attrs-19.2.0 | 39 KB | ########## | 100% \n", - "glib-2.56.2 | 5.0 MB | ########## | 100% \n", - "ipykernel-5.1.2 | 165 KB | ########## | 100% \n", - "nb_conda-2.2.1 | 33 KB | ########## | 100% \n", - "pango-1.42.4 | 528 KB | ########## | 100% \n", - "mpmath-1.1.0 | 970 KB | ########## | 100% \n", - "pysocks-1.7.1 | 30 KB | ########## | 100% \n", - "dbus-1.13.6 | 587 KB | ########## | 100% \n", - "traitlets-4.3.3 | 137 KB | ########## | 100% \n", - "jupyter_client-5.3.3 | 137 KB | ########## | 100% \n", - "cffi-1.12.3 | 222 KB | ########## | 100% \n", - "mistune-0.8.4 | 54 KB | ########## | 100% \n", - "harfbuzz-1.8.8 | 863 KB | ########## | 100% \n", - "pcre-8.43 | 260 KB | ########## | 100% \n", - "pickleshare-0.7.5 | 13 KB | ########## | 100% \n", - "sip-4.19.13 | 293 KB | ########## | 100% \n", - "cryptography-2.7 | 617 KB | ########## | 100% \n", - "cairo-1.14.12 | 1.3 MB | ########## | 100% \n", - "python-3.6.9 | 34.4 MB | ########## | 100% \n", - "jpeg-9b | 247 KB | ########## | 100% \n", - "libffi-3.2.1 | 43 KB | ########## | 100% \n", - "mpc-1.1.0 | 94 KB | ########## | 100% \n", - "pyyaml-5.1.2 | 189 KB | ########## | 100% \n", - "nbconvert-5.6.0 | 494 KB | ########## | 100% \n", - "sortedcontainers-2.1 | 45 KB | ########## | 100% \n", - "heapdict-1.0.1 | 9 KB | ########## | 100% \n", - "sphinxcontrib-jsmath | 8 KB | ########## | 100% \n", - "pandocfilters-1.4.2 | 13 KB | ########## | 100% \n", - "jinja2-2.10.3 | 95 KB | ########## | 100% \n", - "markupsafe-1.1.1 | 29 KB | ########## | 100% \n", - "ipython-7.8.0 | 1.1 MB | ########## | 100% \n", - "sphinxcontrib-devhel | 23 KB | ########## | 100% \n", - "terminado-0.8.2 | 22 KB | ########## | 100% \n", - "chardet-3.0.4 | 197 KB | ########## | 100% \n", - "pytz-2019.3 | 231 KB | ########## | 100% \n", - "jedi-0.15.1 | 721 KB | ########## | 100% \n", - "pandoc-2.2.3.2 | 20.8 MB | ########## | 100% \n", - "click-7.0 | 118 KB | ########## | 100% \n", - "pip-19.2.3 | 1.9 MB | ########## | 100% \n", - "tornado-6.0.3 | 643 KB | ########## | 100% \n", - "zeromq-4.3.1 | 666 KB | ########## | 100% \n", - "mccabe-0.6.1 | 14 KB | ########## | 100% \n", - "notebook-6.0.1 | 6.0 MB | ########## | 100% \n", - "jupyter_core-4.5.0 | 48 KB | ########## | 100% \n", - "gstreamer-1.14.0 | 3.8 MB | ########## | 100% \n", - "kiwisolver-1.1.0 | 90 KB | ########## | 100% \n", - "backcall-0.1.0 | 19 KB | ########## | 100% \n", - "atomicwrites-1.3.0 | 13 KB | ########## | 100% \n", - "pyparsing-2.4.2 | 61 KB | ########## | 100% \n", - "py-cpuinfo-5.0.0 | 22 KB | ########## | 100% \n", - "pillow-6.2.0 | 647 KB | ########## | 100% \n", - "sympy-1.4 | 9.7 MB | ########## | 100% \n", - "partd-1.0.0 | 19 KB | ########## | 100% \n", - "toolz-0.10.0 | 50 KB | ########## | 100% \n", - "sphinxcontrib-htmlhe | 28 KB | ########## | 100% \n", - "zict-1.0.0 | 12 KB | ########## | 100% \n", - "expat-2.2.6 | 187 KB | ########## | 100% \n", - "idna-2.8 | 133 KB | ########## | 100% \n", - "decorator-4.4.0 | 18 KB | ########## | 100% \n", - "testpath-0.4.2 | 91 KB | ########## | 100% \n", - "readline-7.0 | 392 KB | ########## | 100% \n", - "fastcache-1.1.0 | 31 KB | ########## | 100% \n", - "zstd-1.3.7 | 887 KB | ########## | 100% \n", - "mkl-service-2.3.0 | 208 KB | ########## | 100% \n", - "pyflakes-2.1.1 | 105 KB | ########## | 100% \n", - "send2trash-1.5.0 | 16 KB | ########## | 100% \n", - "libsodium-1.0.16 | 302 KB | ########## | 100% \n", - "tblib-1.4.0 | 14 KB | ########## | 100% \n", - "gmp-6.1.2 | 744 KB | ########## | 100% \n", - "requests-2.22.0 | 89 KB | ########## | 100% \n", - "sqlite-3.30.0 | 1.9 MB | ########## | 100% \n", - "snowballstemmer-2.0. | 58 KB | ########## | 100% \n", - "libuuid-1.0.3 | 16 KB | ########## | 100% \n", - "libxml2-2.9.9 | 2.0 MB | ########## | 100% \n", - "pyrsistent-0.15.4 | 92 KB | ########## | 100% \n", - "python-dateutil-2.8. | 281 KB | ########## | 100% \n", - "flake8-3.7.8 | 134 KB | ########## | 100% \n", - "entrypoints-0.3 | 12 KB | ########## | 100% \n", - "h5py-2.9.0 | 1.2 MB | ########## | 100% \n", - "ncurses-6.1 | 958 KB | ########## | 100% \n", - "webencodings-0.5.1 | 19 KB | ########## | 100% \n", - "pycparser-2.19 | 174 KB | ########## | 100% \n", - "scipy-1.3.1 | 18.1 MB | ########## | 100% \n", - "freetype-2.9.1 | 822 KB | ########## | 100% \n", - "sphinxcontrib-appleh | 29 KB | ########## | 100% \n", - "blas-1.0 | 6 KB | ########## | 100% \n", - "importlib_metadata-0 | 43 KB | ########## | 100% \n", - "matplotlib-3.1.1 | 6.7 MB | ########## | 100% \n", - "wheel-0.33.6 | 40 KB | ########## | 100% \n", - "lzo-2.10 | 314 KB | ########## | 100% \n", - "pyopenssl-19.0.0 | 82 KB | ########## | 100% \n", - "fribidi-1.0.5 | 112 KB | ########## | 100% \n", - "mkl-2019.5 | 205.3 MB | ########## | 100% \n", - "msgpack-python-0.6.1 | 92 KB | ########## | 100% \n", - "py-1.8.0 | 140 KB | ########## | 100% \n", - "python-graphviz-0.10 | 22 KB | ########## | 100% \n", - "mkl_fft-1.0.14 | 173 KB | ########## | 100% \n", - "imagesize-1.1.0 | 9 KB | ########## | 100% \n", - "pygments-2.4.2 | 664 KB | ########## | 100% \n", - "libxcb-1.13 | 502 KB | ########## | 100% \n", - "setuptools-41.4.0 | 673 KB | ########## | 100% \n", - "nbformat-4.4.0 | 141 KB | ########## | 100% \n", - "fontconfig-2.13.0 | 291 KB | ########## | 100% \n", - "bleach-3.1.0 | 226 KB | ########## | 100% \n", - "zlib-1.2.11 | 120 KB | ########## | 100% \n", - "sphinxcontrib-serial | 24 KB | ########## | 100% \n", - "numpy-base-1.17.2 | 5.3 MB | ########## | 100% \n", - "zipp-0.6.0 | 9 KB | ########## | 100% \n", - "mpfr-4.0.1 | 575 KB | ########## | 100% \n", - "Preparing transaction: ...working... done\n", - "Verifying transaction: ...working... done\n", - "Executing transaction: ...working... b'Disabling nb_conda_kernels...\\nStatus: disabled\\n'\n", - "b'Enabling nb_conda_kernels...\\nStatus: enabled\\n'\n", - "b'Enabling notebook extension nb_conda/main...\\n - Validating: \\x1b[32mOK\\x1b[0m\\nEnabling tree extension nb_conda/tree...\\n - Validating: \\x1b[32mOK\\x1b[0m\\nEnabling: nb_conda\\n- Writing config: /opt/conda/envs/fwi01_conda_env/etc/jupyter\\n - Validating...\\n nb_conda 2.2.1 \\x1b[32mOK\\x1b[0m\\n'\n", - "done\n", - "Ran pip subprocess with arguments:\n", - "['/opt/conda/envs/fwi01_conda_env/bin/python', '-m', 'pip', 'install', '-U', '-r', '/tmp/condaenv.gapu_08k.requirements.txt']\n", - "Pip subprocess output:\n", - "Collecting anytree (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 1))\n", - "Collecting azureml-sdk[automl,notebooks]==1.0.65 (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/13/db/263ba9e8665480a0e567dbe45b5c7b5c6e041fa5bf5233b2faa78aeacc38/azureml_sdk-1.0.65-py3-none-any.whl\n", - "Collecting codepy (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 3))\n", - " Using cached https://files.pythonhosted.org/packages/6c/81/338a4d4145af7857f9b6fdf9b4d53c58c7eb4c1d092ff6c010efdb4dfdf3/codepy-2019.1.tar.gz\n", - "Collecting papermill[azure] (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/33/f8/6d821081eddec7405d719838c532beb8e09beb7b911db200e7768c239058/papermill-1.2.0-py2.py3-none-any.whl\n", - "Collecting pyrevolve (from -r /tmp/condaenv.gapu_08k.requirements.txt (line 5))\n", - " Using cached https://files.pythonhosted.org/packages/0d/c4/63a36aa56969de861e181b86ebc1aba6764e9d2566c42e4faf1f360cb3cc/pyrevolve-2.1.3.tar.gz\n", - "Requirement already satisfied, skipping upgrade: six>=1.9.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from anytree->-r /tmp/condaenv.gapu_08k.requirements.txt (line 1)) (1.12.0)\n", - "Collecting azureml-core==1.0.65.* (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/a0/13/65465d5b95a0d9bf1b11f436d462e332c4157db0667d761648a8a49b3bda/azureml_core-1.0.65.1-py2.py3-none-any.whl (1.1MB)\n", - "Collecting azureml-dataprep[fuse]<1.2.0a,>=1.1.19a (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/06/cf/bdbeee2533e1837bbc26fae8ef0abf0d3abf4a502ebd545fff3f9b32055d/azureml_dataprep-1.1.20-py3-none-any.whl (26.8MB)\n", - "Collecting azureml-pipeline==1.0.65.* (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/9f/91/ea1691ad094fa398f445c2324115e7c351f35c513931bb4e4153de88c563/azureml_pipeline-1.0.65-py3-none-any.whl\n", - "Collecting azureml-train==1.0.65.* (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/0f/94/c92c9540e119b810e96441a619bdd7d5c1be003af0670bb1c3d8caf90994/azureml_train-1.0.65-py3-none-any.whl\n", - "Collecting azureml-train-automl==1.0.65.*; extra == \"automl\" (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/de/04/16f4ef876c80a0c9b97bfdc622a03f29996d2f43fa343db242907c601c79/azureml_train_automl-1.0.65-py3-none-any.whl (111kB)\n", - "Collecting azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\" (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/e8/2c/46419283bc6b4e504893c4222048ea6bbdb60dfeaced1772650f0f044b10/azureml_contrib_notebook-1.0.65-py2.py3-none-any.whl\n", - "Collecting azureml-widgets==1.0.65.*; extra == \"notebooks\" (from azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/cd/1b/2cfafcb7ffee606d2a7ed2237535a3b8903e04a323a080ad7118191de7f5/azureml_widgets-1.0.65-py3-none-any.whl (9.2MB)\n", - "Requirement already satisfied, skipping upgrade: pytools>=2015.1.2 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (2019.1.1)\n", - "Requirement already satisfied, skipping upgrade: numpy>=1.6 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (1.17.2)\n", - "Requirement already satisfied, skipping upgrade: appdirs>=1.4.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (1.4.3)\n", - "Requirement already satisfied, skipping upgrade: cgen in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (2019.1)\n", - "Requirement already satisfied, skipping upgrade: entrypoints in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.3)\n", - "Requirement already satisfied, skipping upgrade: jupyter-client in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (5.3.3)\n", - "Requirement already satisfied, skipping upgrade: nbconvert>=5.5 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (5.6.0)\n", - "Requirement already satisfied, skipping upgrade: click in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (7.0)\n", - "Collecting future (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/90/52/e20466b85000a181e1e144fd8305caf2cf475e2f9674e797b222f8105f5f/future-0.17.1.tar.gz (829kB)\n", - "Collecting tenacity (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/1e/a1/be8c8610f4620c56790965ba2b564dd76d13cbcd7c2ff8f6053ce63027fb/tenacity-5.1.1-py2.py3-none-any.whl\n", - "Requirement already satisfied, skipping upgrade: pyyaml in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (5.1.2)\n", - "Requirement already satisfied, skipping upgrade: requests in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.22.0)\n", - "Collecting ansiwrap (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/03/50/43e775a63e0d632d9be3b3fa1c9b2cbaf3b7870d203655710a3426f47c26/ansiwrap-0.8.4-py2.py3-none-any.whl\n", - "Collecting tqdm>=4.32.2 (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/e1/c1/bc1dba38b48f4ae3c4428aea669c5e27bd5a7642a74c8348451e0bd8ff86/tqdm-4.36.1-py2.py3-none-any.whl (52kB)\n", - "Requirement already satisfied, skipping upgrade: nbformat in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (4.4.0)\n", - "Collecting azure-storage-blob; extra == \"azure\" (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/3e/84/610f379b46d7d3c2d48eadeed6a12b6d46a43100fea70534f5992d0ac996/azure_storage_blob-2.1.0-py2.py3-none-any.whl (88kB)\n", - "Collecting azure-datalake-store>=0.0.30; extra == \"azure\" (from papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/2d/8e/7f990443bd3d3b6a9112ff8d15d93216e67378a037ec9090d7dd248eec2d/azure_datalake_store-0.0.47-py2.py3-none-any.whl (53kB)\n", - "Collecting blosc (from pyrevolve->-r /tmp/condaenv.gapu_08k.requirements.txt (line 5))\n", - " Using cached https://files.pythonhosted.org/packages/6d/3b/2b707cd330a205ba5c69b5e8bfa9c05691442e45ce9ce882c4c8d343e61a/blosc-1.8.1.tar.gz\n", - "Collecting contexttimer (from pyrevolve->-r /tmp/condaenv.gapu_08k.requirements.txt (line 5))\n", - " Using cached https://files.pythonhosted.org/packages/1d/e0/504aa08a83dc2ff90f61a83b5f70d689e1f5138ab30576124ea2ff9f5076/contexttimer-0.3.3.tar.gz\n", - "Collecting pyzfp (from pyrevolve->-r /tmp/condaenv.gapu_08k.requirements.txt (line 5))\n", - " Using cached https://files.pythonhosted.org/packages/33/ff/1390221dc5ed78eb6573613a76927baeea02c31e73b9db91353b8834bb95/pyzfp-0.3.tar.gz\n", - "Collecting azure-common>=1.1.12 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/00/55/a703923c12cd3172d5c007beda0c1a34342a17a6a72779f8a7c269af0cd6/azure_common-1.1.23-py2.py3-none-any.whl\n", - "Collecting pathspec (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/7a/68/5902e8cd7f7b17c5879982a3a3ee2ad0c3b92b80c79989a2d3e1ca8d29e1/pathspec-0.6.0.tar.gz\n", - "Collecting jsonpickle (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/07/07/c157520a3ebd166c8c24c6ae0ecae7c3968eb4653ff0e5af369bb82f004d/jsonpickle-1.2-py2.py3-none-any.whl\n", - "Requirement already satisfied, skipping upgrade: pyopenssl in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (19.0.0)\n", - "Collecting msrest>=0.5.1 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/27/b0/c34b3ea9b2ed74b800520fbefb312cdb7f05c20b8bd42e5e7662a5614f98/msrest-0.6.10-py2.py3-none-any.whl (82kB)\n", - "Collecting SecretStorage (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/82/59/cb226752e20d83598d7fdcabd7819570b0329a61db07cfbdd21b2ef546e3/SecretStorage-3.1.1-py3-none-any.whl\n", - "Collecting contextlib2 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/cf/e5/989798d38831a8505d62687c94b0f2954ff0a40782e25f9add8ed675dc1f/contextlib2-0.6.0-py2.py3-none-any.whl\n", - "Collecting ruamel.yaml<=0.15.89,>=0.15.35 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/36/e1/cc2fa400fa5ffde3efa834ceb15c464075586de05ca3c553753dcd6f1d3b/ruamel.yaml-0.15.89-cp36-cp36m-manylinux1_x86_64.whl (651kB)\n", - "Collecting PyJWT (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/87/8b/6a9f14b5f781697e51259d81657e6048fd31a113229cf346880bb7545565/PyJWT-1.7.1-py2.py3-none-any.whl\n", - "Requirement already satisfied, skipping upgrade: cryptography!=1.9,!=2.0.*,!=2.1.*,!=2.2.* in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (2.7)\n", - "Collecting azure-mgmt-resource>=1.2.1 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/7c/0d/80815326fa04f2a73ea94b0f57c29669c89df5aa5f5e285952f6445a91c4/azure_mgmt_resource-5.1.0-py2.py3-none-any.whl (681kB)\n", - "Collecting msrestazure>=0.4.33 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/68/75/5cb56ca8cbc6c5fe476e4878c73f57a331edcf55e5d3fcb4a7377d7d659d/msrestazure-0.6.2-py2.py3-none-any.whl (40kB)\n", - "Requirement already satisfied, skipping upgrade: python-dateutil>=2.7.3 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (2.8.0)\n", - "Collecting adal>=1.2.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/4f/b5/3ea9ae3d1096b9ff31e8f1846c47d49f3129a12464ac0a73b602de458298/adal-1.2.2-py2.py3-none-any.whl (53kB)\n", - "Requirement already satisfied, skipping upgrade: urllib3>=1.23 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (1.24.2)\n", - "Collecting azure-mgmt-authorization>=0.40.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/5e/17/4724694ddb3311955ddc367eddcd0928f8ee2c7b12d5a6f0b12bca0b03db/azure_mgmt_authorization-0.60.0-py2.py3-none-any.whl (82kB)\n", - "Requirement already satisfied, skipping upgrade: pytz in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (2019.3)\n", - "Collecting azure-mgmt-storage>=1.5.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/a1/71/c1f73fa452b7f2e1b5567621a1386907cb4591ef8335c012ab4b358fb090/azure_mgmt_storage-4.2.0-py2.py3-none-any.whl (435kB)\n", - "Collecting backports.tempfile (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/b4/5c/077f910632476281428fe254807952eb47ca78e720d059a46178c541e669/backports.tempfile-1.0-py2.py3-none-any.whl\n", - "Collecting docker (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/cc/ca/699d4754a932787ef353a157ada74efd1ceb6d1fc0bfb7989ae1e7b33111/docker-4.1.0-py2.py3-none-any.whl (139kB)\n", - "Collecting azure-mgmt-keyvault>=0.40.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/b3/d1/9fed0a3a3b43d0b1ad59599b5c836ccc4cf117e26458075385bafe79575b/azure_mgmt_keyvault-2.0.0-py2.py3-none-any.whl (80kB)\n", - "Collecting ndg-httpsclient (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/fb/67/c2f508c00ed2a6911541494504b7cac16fe0b0473912568df65fd1801132/ndg_httpsclient-0.5.1-py3-none-any.whl\n", - "Collecting jmespath (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/83/94/7179c3832a6d45b266ddb2aac329e101367fbdb11f425f13771d27f225bb/jmespath-0.9.4-py2.py3-none-any.whl\n", - "Collecting azure-graphrbac>=0.40.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/3e/93/02056aca45162f9fc275d1eaad12a2a07ef92375afb48eabddc4134b8315/azure_graphrbac-0.61.1-py2.py3-none-any.whl (141kB)\n", - "Collecting azure-mgmt-containerregistry>=2.0.0 (from azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/97/70/8c2d0509db466678eba16fa2b0a539499f3b351b1f2993126ad843d5be13/azure_mgmt_containerregistry-2.8.0-py2.py3-none-any.whl (718kB)\n", - "Collecting dotnetcore2>=2.1.9 (from azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/e7/11/699ec6c3ec5e73437834203e4b530fdc760517498208eaa0b37a7f1e10af/dotnetcore2-2.1.9-py3-none-manylinux1_x86_64.whl (29.3MB)\n", - "Requirement already satisfied, skipping upgrade: cloudpickle>=1.1.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (1.2.2)\n", - "Collecting azureml-dataprep-native<14.0.0,>=13.1.0 (from azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/2a/b8/36399cebff8045a05cb69181771e450101ea3ac14f02ad564d63634b8aca/azureml_dataprep_native-13.1.0-cp36-cp36m-manylinux1_x86_64.whl (1.3MB)\n", - "Collecting fusepy>=3.0.1; extra == \"fuse\" (from azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/04/0b/4506cb2e831cea4b0214d3625430e921faaa05a7fb520458c75a2dbd2152/fusepy-3.0.1.tar.gz\n", - "Collecting azureml-pipeline-steps==1.0.65.* (from azureml-pipeline==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/68/23/f90b08fbd0313a8846fb4fc0df1f0298dfe85d52b6b92ef73e19386ad953/azureml_pipeline_steps-1.0.65-py3-none-any.whl\n", - "Collecting azureml-pipeline-core==1.0.65.* (from azureml-pipeline==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/9b/11/7f38ae758036515937abe745507132530a0bee56482d3c9c0af4a1d54ad6/azureml_pipeline_core-1.0.65-py2.py3-none-any.whl (252kB)\n", - "Collecting azureml-train-core==1.0.65.* (from azureml-train==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/11/63/b61d89600236648f0afe909bea5afb69912ea33f2b492a9722aad24eaf1e/azureml_train_core-1.0.65-py3-none-any.whl (76kB)\n", - "Collecting dill>=0.2.8 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/c7/11/345f3173809cea7f1a193bfbf02403fff250a3360e0e118a1630985e547d/dill-0.3.1.1.tar.gz (151kB)\n", - "Collecting statsmodels>=0.9.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/60/d6/e9859e68e7d6c916fdff7d8e0958a7f5813485c52fc20d061273eaaddb0c/statsmodels-0.10.1-cp36-cp36m-manylinux1_x86_64.whl (8.1MB)\n", - "Collecting sklearn-pandas<=1.7.0,>=1.4.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/7e/9c/c94f46b40b86d2c77c46c4c1b858fc66c117b4390665eca28f2e0812db45/sklearn_pandas-1.7.0-py2.py3-none-any.whl\n", - "Collecting skl2onnx==1.4.9 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/40/4d/630598ba5cbf7b6522158c236b9e74930f14bbc9324b61ddc40f252bab40/skl2onnx-1.4.9-py2.py3-none-any.whl (114kB)\n", - "Collecting resource>=0.1.8 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/34/ad/9cd037c01c075f9a273c23557f8e71195d773d59d3881bbb26011d396c8b/Resource-0.2.1-py2.py3-none-any.whl\n", - "Collecting azureml-telemetry==1.0.65.* (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/98/e8/71a5e9a68f81255eeb87217f5bb79043da3f665a819a1962f109e46ba0cd/azureml_telemetry-1.0.65-py3-none-any.whl\n", - "Collecting scikit-learn<=0.20.3,>=0.19.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/5e/82/c0de5839d613b82bddd088599ac0bbfbbbcbd8ca470680658352d2c435bd/scikit_learn-0.20.3-cp36-cp36m-manylinux1_x86_64.whl (5.4MB)\n", - "Collecting lightgbm<=2.2.3,>=2.0.11 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/77/0f/5157e6b153b3d4a70dc5fbe2ab6f209604197590f387f03177b7a249ac60/lightgbm-2.2.3-py2.py3-none-manylinux1_x86_64.whl (1.2MB)\n", - "Collecting gensim (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/d1/dd/112bd4258cee11e0baaaba064060eb156475a42362e59e3ff28e7ca2d29d/gensim-3.8.1-cp36-cp36m-manylinux1_x86_64.whl (24.2MB)\n", - "Collecting onnx>=1.5.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/f5/f4/e126b60d109ad1e80020071484b935980b7cce1e4796073aab086a2d6902/onnx-1.6.0-cp36-cp36m-manylinux1_x86_64.whl (4.8MB)\n", - "Collecting wheel==0.30.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/0c/80/16a85b47702a1f47a63c104c91abdd0a6704ee8ae3b4ce4afc49bc39f9d9/wheel-0.30.0-py2.py3-none-any.whl (49kB)\n", - "Collecting pandas<=0.23.4,>=0.21.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/e1/d8/feeb346d41f181e83fba45224ab14a8d8af019b48af742e047f3845d8cff/pandas-0.23.4-cp36-cp36m-manylinux1_x86_64.whl (8.9MB)\n", - "Collecting scipy<=1.1.0,>=1.0.0 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/a8/0b/f163da98d3a01b3e0ef1cab8dd2123c34aee2bafbb1c5bffa354cc8a1730/scipy-1.1.0-cp36-cp36m-manylinux1_x86_64.whl (31.2MB)\n", - "Collecting azureml-automl-core==1.0.65.* (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/02/e6/8bd092836c9d2a3f37fc0da29cb3052a0c200a49a22232c202bc8a15db5b/azureml_automl_core-1.0.65.1-py3-none-any.whl (2.0MB)\n", - "Collecting onnxconverter-common>=1.4.2 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/52/55/73c3b38364dff5eeab4442d6c5e96c336483cd188c536a2e30c625f41cd7/onnxconverter_common-1.5.5-py2.py3-none-any.whl (42kB)\n", - "Collecting patsy>=0.5.1 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/ea/0c/5f61f1a3d4385d6bf83b83ea495068857ff8dfb89e74824c6e9eb63286d8/patsy-0.5.1-py2.py3-none-any.whl (231kB)\n", - "Collecting onnxmltools==1.4.1 (from azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/78/7f/1d47b82f1e98f742018e4c4c227ec9bd0ae1efdba3a6dfd7d30c45881fb9/onnxmltools-1.4.1-py2.py3-none-any.whl (371kB)\n", - "Requirement already satisfied, skipping upgrade: ipykernel in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (5.1.2)\n", - "Requirement already satisfied, skipping upgrade: ipython in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (7.8.0)\n", - "Collecting ipywidgets>=7.0.0 (from azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/56/a0/dbcf5881bb2f51e8db678211907f16ea0a182b232c591a6d6f276985ca95/ipywidgets-7.5.1-py2.py3-none-any.whl (121kB)\n", - "Requirement already satisfied, skipping upgrade: decorator>=3.2.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from pytools>=2015.1.2->codepy->-r /tmp/condaenv.gapu_08k.requirements.txt (line 3)) (4.4.0)\n", - "Requirement already satisfied, skipping upgrade: traitlets in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jupyter-client->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (4.3.3)\n", - "Requirement already satisfied, skipping upgrade: tornado>=4.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jupyter-client->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (6.0.3)\n", - "Requirement already satisfied, skipping upgrade: pyzmq>=13 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jupyter-client->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (18.1.0)\n", - "Requirement already satisfied, skipping upgrade: jupyter-core in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jupyter-client->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (4.5.0)\n", - "Requirement already satisfied, skipping upgrade: defusedxml in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.6.0)\n", - "Requirement already satisfied, skipping upgrade: jinja2>=2.4 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.10.3)\n", - "Requirement already satisfied, skipping upgrade: pygments in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.4.2)\n", - "Requirement already satisfied, skipping upgrade: mistune<2,>=0.8.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.8.4)\n", - "Requirement already satisfied, skipping upgrade: bleach in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (3.1.0)\n", - "Requirement already satisfied, skipping upgrade: pandocfilters>=1.4.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (1.4.2)\n", - "Requirement already satisfied, skipping upgrade: testpath in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.4.2)\n", - "Requirement already satisfied, skipping upgrade: idna<2.9,>=2.5 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from requests->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.8)\n", - "Requirement already satisfied, skipping upgrade: certifi>=2017.4.17 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from requests->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2019.9.11)\n", - "Requirement already satisfied, skipping upgrade: chardet<3.1.0,>=3.0.2 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from requests->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (3.0.4)\n", - "Collecting textwrap3>=0.9.2 (from ansiwrap->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/77/9c/a53e561d496ee5866bbeea4d3a850b3b545ed854f8a21007c1e0d872e94d/textwrap3-0.9.2-py2.py3-none-any.whl\n", - "Requirement already satisfied, skipping upgrade: ipython-genutils in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbformat->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.2.0)\n", - "Requirement already satisfied, skipping upgrade: jsonschema!=2.5.0,>=2.4 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from nbformat->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (3.0.2)\n", - "Collecting azure-storage-common~=2.1 (from azure-storage-blob; extra == \"azure\"->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4))\n", - " Downloading https://files.pythonhosted.org/packages/6b/a0/6794b318ce0118d1a4053bdf0149a60807407db9b710354f2b203c2f5975/azure_storage_common-2.1.0-py2.py3-none-any.whl (47kB)\n", - "Requirement already satisfied, skipping upgrade: cffi in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from azure-datalake-store>=0.0.30; extra == \"azure\"->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (1.12.3)\n", - "Collecting requests-oauthlib>=0.5.0 (from msrest>=0.5.1->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/c2/e2/9fd03d55ffb70fe51f587f20bcf407a6927eb121de86928b34d162f0b1ac/requests_oauthlib-1.2.0-py2.py3-none-any.whl\n", - "Collecting isodate>=0.6.0 (from msrest>=0.5.1->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/9b/9f/b36f7774ff5ea8e428fdcfc4bb332c39ee5b9362ddd3d40d9516a55221b2/isodate-0.6.0-py2.py3-none-any.whl (45kB)\n", - "Collecting jeepney (from SecretStorage->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/0a/4c/ef880713a6c6d628869596703167eab2edf8e0ec2d870d1089dcb0901b81/jeepney-0.4.1-py3-none-any.whl (60kB)\n", - "Requirement already satisfied, skipping upgrade: asn1crypto>=0.21.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from cryptography!=1.9,!=2.0.*,!=2.1.*,!=2.2.*->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (1.0.1)\n", - "Collecting backports.weakref (from backports.tempfile->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/88/ec/f598b633c3d5ffe267aaada57d961c94fdfa183c5c3ebda2b6d151943db6/backports.weakref-1.0.post1-py2.py3-none-any.whl\n", - "Collecting websocket-client>=0.32.0 (from docker->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/29/19/44753eab1fdb50770ac69605527e8859468f3c0fd7dc5a76dd9c4dbd7906/websocket_client-0.56.0-py2.py3-none-any.whl (200kB)\n", - "Collecting pyasn1>=0.1.1 (from ndg-httpsclient->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/a1/71/8f0d444e3a74e5640a3d5d967c1c6b015da9c655f35b2d308a55d907a517/pyasn1-0.4.7-py2.py3-none-any.whl (76kB)\n", - "Collecting distro>=1.2.0 (from dotnetcore2>=2.1.9->azureml-dataprep[fuse]<1.2.0a,>=1.1.19a->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/ea/35/82f79b92fa4d937146c660a6482cee4f3dfa1f97ff3d2a6f3ecba33e712e/distro-1.4.0-py2.py3-none-any.whl\n", - "Collecting azureml-train-restclients-hyperdrive==1.0.65.* (from azureml-train-core==1.0.65.*->azureml-train==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/f7/d2/56427a10de73db01360d7a8c87f75b68e06a8eec6db6ec1c31d3cac2ab12/azureml_train_restclients_hyperdrive-1.0.65-py3-no\u001b[91m\n", - "\n", - "==> WARNING: A newer version of conda exists. <==\n", - " current version: 4.7.10\n", - " latest version: 4.7.12\n", - "\n", - "Please update conda by running\n", - "\n", - " $ conda update -n base -c defaults conda\n", - "\n", - "\n", - "\u001b[0mne-any.whl\n", - "Collecting protobuf (from skl2onnx==1.4.9->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/a8/52/d8d2dbff74b8bf517c42db8d44c3f9ef6555e6f5d6caddfa3f207b9143df/protobuf-3.10.0-cp36-cp36m-manylinux1_x86_64.whl (1.3MB)\n", - "Collecting python-easyconfig>=0.1.0 (from resource>=0.1.8->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/b1/86/1138081cca360a02066eedaf301d0f358c35e0e0d67572acf9d6354edca9/Python_EasyConfig-0.1.7-py2.py3-none-any.whl\n", - "Collecting JsonForm>=0.0.2 (from resource>=0.1.8->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/4f/b7/b9491ba4b709d0616fab15a89f8efe4d3a7924652e1fdd4f15303e9ecdf0/JsonForm-0.0.2.tar.gz\n", - "Collecting JsonSir>=0.0.2 (from resource>=0.1.8->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/aa/bf/5c00c1dafaa3ca2c32e7641d9c2c6f9d6d76e127bde00eb600333a60c5bc/JsonSir-0.0.2.tar.gz\n", - "Collecting applicationinsights (from azureml-telemetry==1.0.65.*->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/a1/53/234c53004f71f0717d8acd37876e0b65c121181167057b9ce1b1795f96a0/applicationinsights-0.11.9-py2.py3-none-any.whl (58kB)\n", - "Collecting smart-open>=1.8.1 (from gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/37/c0/25d19badc495428dec6a4bf7782de617ee0246a9211af75b302a2681dea7/smart_open-1.8.4.tar.gz (63kB)\n", - "Collecting typing-extensions>=3.6.2.1 (from onnx>=1.5.0->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/27/aa/bd1442cfb0224da1b671ab334d3b0a4302e4161ea916e28904ff9618d471/typing_extensions-3.7.4-py3-none-any.whl\n", - "Collecting nimbusml>=1.4.1 (from azureml-automl-core==1.0.65.*->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/31/f8/b9cd6f214cd4fb24081c379d6374fdf549c3960f64229937e08803bdfdd1/nimbusml-1.5.0-cp36-none-manylinux1_x86_64.whl (104.9MB)\n", - "Collecting pmdarima==1.1.1 (from azureml-automl-core==1.0.65.*->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/a2/2a/f982baa2ea936c576597e957d0e2e57f7eac1b4618d2b18c16fa6d652b18/pmdarima-1.1.1-cp36-cp36m-manylinux1_x86_64.whl (682kB)\n", - "Collecting keras2onnx (from onnxmltools==1.4.1->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/3b/76/f6b4afe9c0b3b46318498324897b8696cd9f976de8c0e4058ec619850c8d/keras2onnx-1.5.2-py3-none-any.whl (216kB)\n", - "Requirement already satisfied, skipping upgrade: backcall in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.1.0)\n", - "Requirement already satisfied, skipping upgrade: setuptools>=18.5 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (41.4.0)\n", - "Requirement already satisfied, skipping upgrade: pickleshare in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.7.5)\n", - "Requirement already satisfied, skipping upgrade: pexpect; sys_platform != \"win32\" in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (4.7.0)\n", - "Requirement already satisfied, skipping upgrade: jedi>=0.10 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.15.1)\n", - "Requirement already satisfied, skipping upgrade: prompt-toolkit<2.1.0,>=2.0.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (2.0.10)\n", - "Collecting widgetsnbextension~=3.5.0 (from ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/6c/7b/7ac231c20d2d33c445eaacf8a433f4e22c60677eb9776c7c5262d7ddee2d/widgetsnbextension-3.5.1-py2.py3-none-any.whl (2.2MB)\n", - "Requirement already satisfied, skipping upgrade: MarkupSafe>=0.23 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jinja2>=2.4->nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (1.1.1)\n", - "Requirement already satisfied, skipping upgrade: webencodings in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from bleach->nbconvert>=5.5->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.5.1)\n", - "Requirement already satisfied, skipping upgrade: pyrsistent>=0.14.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (0.15.4)\n", - "Requirement already satisfied, skipping upgrade: attrs>=17.4.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (19.2.0)\n", - "Requirement already satisfied, skipping upgrade: pycparser in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from cffi->azure-datalake-store>=0.0.30; extra == \"azure\"->papermill[azure]->-r /tmp/condaenv.gapu_08k.requirements.txt (line 4)) (2.19)\n", - "Collecting oauthlib>=3.0.0 (from requests-oauthlib>=0.5.0->msrest>=0.5.1->azureml-core==1.0.65.*->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/05/57/ce2e7a8fa7c0afb54a0581b14a65b56e62b5759dbc98e80627142b8a3704/oauthlib-3.1.0-py2.py3-none-any.whl (147kB)\n", - "Collecting boto>=2.32 (from smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/23/10/c0b78c27298029e4454a472a1919bde20cb182dab1662cec7f2ca1dcc523/boto-2.49.0-py2.py3-none-any.whl (1.4MB)\n", - "Collecting boto3 (from smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/42/b5/03da45c451b4e7d31bc42e3b844cbb2e9fc6e8757705504d828a7295b27e/boto3-1.9.245-py2.py3-none-any.whl (128kB)\n", - "Requirement already satisfied, skipping upgrade: Cython>=0.29 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from pmdarima==1.1.1->azureml-automl-core==1.0.65.*->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.29.13)\n", - "Collecting fire (from keras2onnx->onnxmltools==1.4.1->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/d9/69/faeaae8687f4de0f5973694d02e9d6c3eb827636a009157352d98de1129e/fire-0.2.1.tar.gz (76kB)\n", - "Requirement already satisfied, skipping upgrade: ptyprocess>=0.5 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from pexpect; sys_platform != \"win32\"->ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.6.0)\n", - "Requirement already satisfied, skipping upgrade: parso>=0.5.0 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from jedi>=0.10->ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.5.1)\n", - "Requirement already satisfied, skipping upgrade: wcwidth in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from prompt-toolkit<2.1.0,>=2.0.0->ipython->azureml-contrib-notebook==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.1.7)\n", - "Requirement already satisfied, skipping upgrade: notebook>=4.4.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from widgetsnbextension~=3.5.0->ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (6.0.1)\n", - "Collecting s3transfer<0.3.0,>=0.2.0 (from boto3->smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/16/8a/1fc3dba0c4923c2a76e1ff0d52b305c44606da63f718d14d3231e21c51b0/s3transfer-0.2.1-py2.py3-none-any.whl (70kB)\n", - "Collecting botocore<1.13.0,>=1.12.245 (from boto3->smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/0e/84/b13b233a344de26796b7b457571c16f2b3d1fab8fb48931208d650cc4d97/botocore-1.12.245-py2.py3-none-any.whl (5.7MB)\n", - "Collecting termcolor (from fire->keras2onnx->onnxmltools==1.4.1->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2))\n", - " Downloading https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz\n", - "Requirement already satisfied, skipping upgrade: terminado>=0.8.1 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.8.2)\n", - "Requirement already satisfied, skipping upgrade: Send2Trash in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (1.5.0)\n", - "Requirement already satisfied, skipping upgrade: prometheus-client in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.0.0->azureml-widgets==1.0.65.*; extra == \"notebooks\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.7.1)\n", - "Requirement already satisfied, skipping upgrade: docutils<0.16,>=0.10 in /opt/conda/envs/fwi01_conda_env/lib/python3.6/site-packages (from botocore<1.13.0,>=1.12.245->boto3->smart-open>=1.8.1->gensim->azureml-train-automl==1.0.65.*; extra == \"automl\"->azureml-sdk[automl,notebooks]==1.0.65->-r /tmp/condaenv.gapu_08k.requirements.txt (line 2)) (0.15.2)\n", - "Building wheels for collected packages: codepy, pyrevolve, future, blosc, contexttimer, pyzfp, pathspec, fusepy, dill, JsonForm, JsonSir, smart-open, fire, termcolor\n", - " Building wheel for codepy (setup.py): started\n", - " Building wheel for codepy (setup.py): finished with status 'done'\n", - " Created wheel for codepy: filename=codepy-2019.1-cp36-none-any.whl size=19314 sha256=b962a33a50bc0209652a378feb522f721b57fb8d3b2230ffbce0750ef5f82f5c\n", - " Stored in directory: /root/.cache/pip/wheels/f4/53/e7/b53cf7ba45381b676bbd5eaaedc19ae82e1c397e9c1766ddf4\n", - " Building wheel for pyrevolve (setup.py): started\n", - " Building wheel for pyrevolve (setup.py): finished with status 'done'\n", - " Created wheel for pyrevolve: filename=pyrevolve-2.1.3-cp36-cp36m-linux_x86_64.whl size=332357 sha256=68a372a67f717b85fd217faa42b484c869405a4a888880f4f0fbb79b6962fd09\n", - " Stored in directory: /root/.cache/pip/wheels/df/d7/36/3e8e92a06a23446febeb604b528faeffba1a26c0d63e924a0a\n", - " Building wheel for future (setup.py): started\n", - " Building wheel for future (setup.py): finished with status 'done'\n", - " Created wheel for future: filename=future-0.17.1-cp36-none-any.whl size=488730 sha256=9b057117fd3a3945eef31c3e9cfa9c768c5c7423da8d1b62bb9046f2e1d48f7d\n", - " Stored in directory: /root/.cache/pip/wheels/0c/61/d2/d6b7317325828fbb39ee6ad559dbe4664d0896da4721bf379e\n", - " Building wheel for blosc (setup.py): started\n", - " Building wheel for blosc (setup.py): finished with status 'done'\n", - " Created wheel for blosc: filename=blosc-1.8.1-cp36-cp36m-linux_x86_64.whl size=695333 sha256=4c695d6b4e45ac7c104e964ec63a4633f7d302328f5e167e9c21f7699aedf007\n", - " Stored in directory: /root/.cache/pip/wheels/3b/e0/b9/99a77fb1821f0df30e52b9ce470c74efa2ca359ff8c21f8e17\n", - " Building wheel for contexttimer (setup.py): started\n", - " Building wheel for contexttimer (setup.py): finished with status 'done'\n", - " Created wheel for contexttimer: filename=contexttimer-0.3.3-cp36-none-any.whl size=5818 sha256=cdee0441a4d88676fd0819908a4afd35dbb8bdcfb53975ffe0c70fe18e3572af\n", - " Stored in directory: /root/.cache/pip/wheels/b3/e2/35/565145ce0127c7451b6503dfabb2b56e9908c863e40c6b1870\n", - " Building wheel for pyzfp (setup.py): started\n", - " Building wheel for pyzfp (setup.py): finished with status 'done'\n", - " Created wheel for pyzfp: filename=pyzfp-0.3-cp36-cp36m-linux_x86_64.whl size=469637 sha256=335eadb4f3f696e6c3c4ee58f681a81d2f16f22c3c39f81ec1557ebec040e0a7\n", - " Stored in directory: /root/.cache/pip/wheels/a6/b5/c8/60b5a2d3fd3cbb49c37935bd498037ce18dae811d9e301b885\n", - " Building wheel for pathspec (setup.py): started\n", - " Building wheel for pathspec (setup.py): finished with status 'done'\n", - " Created wheel for pathspec: filename=pathspec-0.6.0-cp36-none-any.whl size=26671 sha256=af8fe05aef88c29ea485c42c71d4b99b3d2428e33b80fb00faa98285bb568323\n", - " Stored in directory: /root/.cache/pip/wheels/62/b8/e1/e2719465b5947c40cd85d613d6cb33449b86a1ca5a6c574269\n", - " Building wheel for fusepy (setup.py): started\n", - " Building wheel for fusepy (setup.py): finished with status 'done'\n", - " Created wheel for fusepy: filename=fusepy-3.0.1-cp36-none-any.whl size=10505 sha256=eaa956fa434045ed0897919d164426bb30f4e8bc0f35240498fcbaa85b252ffa\n", - " Stored in directory: /root/.cache/pip/wheels/4c/a5/91/7772af9e21c461f07bb40f26d928d7d231d224977dd8353bab\n", - " Building wheel for dill (setup.py): started\n", - " Building wheel for dill (setup.py): finished with status 'done'\n", - " Created wheel for dill: filename=dill-0.3.1.1-cp36-none-any.whl size=78532 sha256=7baff27933ff72b24f50f953e42888e5cc6b6a59c781c6ee239e35d1c368d9ea\n", - " Stored in directory: /root/.cache/pip/wheels/59/b1/91/f02e76c732915c4015ab4010f3015469866c1eb9b14058d8e7\n", - " Building wheel for JsonForm (setup.py): started\n", - " Building wheel for JsonForm (setup.py): finished with status 'done'\n", - " Created wheel for JsonForm: filename=JsonForm-0.0.2-cp36-none-any.whl size=3326 sha256=6811f0fedba357c208217f7688597d76a449698946d961153a799734092bddab\n", - " Stored in directory: /root/.cache/pip/wheels/e8/74/51/42c2d41c02bdc6f0e604476b7e4293b8c98d0bcbfa1dff78c8\n", - " Building wheel for JsonSir (setup.py): started\n", - " Building wheel for JsonSir (setup.py): finished with status 'done'\n", - " Created wheel for JsonSir: filename=JsonSir-0.0.2-cp36-none-any.whl size=4774 sha256=744793daaba7257e386891014d0823185a21fc03bcdf94d47abce54971661810\n", - " Stored in directory: /root/.cache/pip/wheels/ee/30/5c/3a3b5e1386c8db9a3be5f5c3933644ae0533c1351c6a8eb4b5\n", - " Building wheel for smart-open (setup.py): started\n", - " Building wheel for smart-open (setup.py): finished with status 'done'\n", - " Created wheel for smart-open: filename=smart_open-1.8.4-cp36-none-any.whl size=68202 sha256=1259196b3d373f67552777e33241841c2ce4b1e728c0191185899539c0918fa8\n", - " Stored in directory: /root/.cache/pip/wheels/5f/ea/fb/5b1a947b369724063b2617011f1540c44eb00e28c3d2ca8692\n", - " Building wheel for fire (setup.py): started\n", - " Building wheel for fire (setup.py): finished with status 'done'\n", - " Created wheel for fire: filename=fire-0.2.1-py2.py3-none-any.whl size=103527 sha256=6029e13e8c87a53b3eeaa071b79eafedb39057d1fc0ac35c550be5a2efaf6a14\n", - " Stored in directory: /root/.cache/pip/wheels/31/9c/c0/07b6dc7faf1844bb4688f46b569efe6cafaa2179c95db821da\n", - " Building wheel for termcolor (setup.py): started\n", - " Building wheel for termcolor (setup.py): finished with status 'done'\n", - " Created wheel for termcolor: filename=termcolor-1.1.0-cp36-none-any.whl size=4832 sha256=0d04b365d3647f95b87f0688881a5b0d0a79fdf035f94c5bda88fc35bbc5bbe1\n", - " Stored in directory: /root/.cache/pip/wheels/7c/06/54/bc84598ba1daf8f970247f550b175aaaee85f68b4b0c5ab2c6\n", - "Successfully built codepy pyrevolve future blosc contexttimer pyzfp pathspec fusepy dill JsonForm JsonSir smart-open fire termcolor\n", - "Installing collected packages: anytree, azure-common, pathspec, jsonpickle, oauthlib, requests-oauthlib, isodate, msrest, jeepney, SecretStorage, contextlib2, ruamel.yaml, PyJWT, adal, msrestazure, azure-mgmt-resource, azure-mgmt-authorization, azure-mgmt-storage, backports.weakref, backports.tempfile, websocket-client, docker, azure-mgmt-keyvault, pyasn1, ndg-httpsclient, jmespath, azure-graphrbac, azure-mgmt-containerregistry, azureml-core, distro, dotnetcore2, azureml-dataprep-native, fusepy, azureml-dataprep, azureml-train-restclients-hyperdrive, applicationinsights, azureml-telemetry, azureml-train-core, azureml-pipeline-core, azureml-pipeline-steps, azureml-pipeline, azureml-train, dill, scipy, pandas, patsy, statsmodels, scikit-learn, sklearn-pandas, protobuf, typing-extensions, onnx, onnxconverter-common, skl2onnx, python-easyconfig, JsonForm, JsonSir, resource, lightgbm, boto, botocore, s3transfer, boto3, smart-open, gensim, wheel, termcolor, fire, keras2onnx, onnxmltools, nimbusml, pmdarima, azureml-automl-core, azureml-train-automl, future, tenacity, textwrap3, ansiwrap, tqdm, azure-storage-common, azure-storage-blob, azure-datalake-store, papermill, azureml-contrib-notebook, widgetsnbextension, ipywidgets, azureml-widgets, azureml-sdk, codepy, blosc, contexttimer, pyzfp, pyrevolve\n", - " Found existing installation: scipy 1.3.1\n", - " Uninstalling scipy-1.3.1:\n", - " Successfully uninstalled scipy-1.3.1\n", - " Found existing installation: pandas 0.25.1\n", - " Uninstalling pandas-0.25.1:\n", - " Successfully uninstalled pandas-0.25.1\n", - " Found existing installation: wheel 0.33.6\n", - " Uninstalling wheel-0.33.6:\n", - " Successfully uninstalled wheel-0.33.6\n", - "Successfully installed JsonForm-0.0.2 JsonSir-0.0.2 PyJWT-1.7.1 SecretStorage-3.1.1 adal-1.2.2 ansiwrap-0.8.4 anytree-2.7.1 applicationinsights-0.11.9 azure-common-1.1.23 azure-datalake-store-0.0.47 azure-graphrbac-0.61.1 azure-mgmt-authorization-0.60.0 azure-mgmt-containerregistry-2.8.0 azure-mgmt-keyvault-2.0.0 azure-mgmt-resource-5.1.0 azure-mgmt-storage-4.2.0 azure-storage-blob-2.1.0 azure-storage-common-2.1.0 azureml-automl-core-1.0.65.1 azureml-contrib-notebook-1.0.65 azureml-core-1.0.65.1 azureml-dataprep-1.1.20 azureml-dataprep-native-13.1.0 azureml-pipeline-1.0.65 azureml-pipeline-core-1.0.65 azureml-pipeline-steps-1.0.65 azureml-sdk-1.0.65 azureml-telemetry-1.0.65 azureml-train-1.0.65 azureml-train-automl-1.0.65 azureml-train-core-1.0.65 azureml-train-restclients-hyperdrive-1.0.65 azureml-widgets-1.0.65 backports.tempfile-1.0 backports.weakref-1.0.post1 blosc-1.8.1 boto-2.49.0 boto3-1.9.245 botocore-1.12.245 codepy-2019.1 contextlib2-0.6.0 contexttimer-0.3.3 dill-0.3.1.1 distro-1.4.0 docker-4.1.0 dotnetcore2-2.1.9 fire-0.2.1 fusepy-3.0.1 future-0.17.1 gensim-3.8.1 ipywidgets-7.5.1 isodate-0.6.0 jeepney-0.4.1 jmespath-0.9.4 jsonpickle-1.2 keras2onnx-1.5.2 lightgbm-2.2.3 msrest-0.6.10 msrestazure-0.6.2 ndg-httpsclient-0.5.1 nimbusml-1.5.0 oauthlib-3.1.0 onnx-1.6.0 onnxconverter-common-1.5.5 onnxmltools-1.4.1 pandas-0.23.4 papermill-1.2.0 pathspec-0.6.0 patsy-0.5.1 pmdarima-1.1.1 protobuf-3.10.0 pyasn1-0.4.7 pyrevolve-2.1.3 python-easyconfig-0.1.7 pyzfp-0.3 requests-oauthlib-1.2.0 resource-0.2.1 ruamel.yaml-0.15.89 s3transfer-0.2.1 scikit-learn-0.20.3 scipy-1.1.0 skl2onnx-1.4.9 sklearn-pandas-1.7.0 smart-open-1.8.4 statsmodels-0.10.1 tenacity-5.1.1 termcolor-1.1.0 textwrap3-0.9.2 tqdm-4.36.1 typing-extensions-3.7.4 websocket-client-0.56.0 wheel-0.30.0 widgetsnbextension-3.5.1\n", - "\n", - "#\n", - "# To activate this environment, use\n", - "#\n", - "# $ conda activate fwi01_conda_env\n", - "#\n", - "# To deactivate an active environment, use\n", - "#\n", - "# $ conda deactivate\n", - "\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cache location: /opt/conda/pkgs\r\n", - "Will remove the following tarballs:\r\n", - "\r\n", - "/opt/conda/pkgs\r\n", - "---------------\r\n", - "click-7.0-py37_0.conda 120 KB\r\n", - "pyflakes-2.1.1-py37_0.conda 106 KB\r\n", - "tblib-1.4.0-py_0.tar.bz2 14 KB\r\n", - "libsodium-1.0.16-h1bed415_0.conda 214 KB\r\n", - "kiwisolver-1.1.0-py37he6710b0_0.conda 82 KB\r\n", - "jinja2-2.10.3-py_0.tar.bz2 95 KB\r\n", - "zict-1.0.0-py_0.tar.bz2 12 KB\r\n", - "ipython-7.8.0-py37h39e3cac_0.conda 985 KB\r\n", - "traitlets-4.3.3-py37_0.tar.bz2 138 KB\r\n", - "libstdcxx-ng-9.1.0-hdf63c60_0.conda 3.1 MB\r\n", - "pycparser-2.19-py37_0.conda 171 KB\r\n", - "cgen-2019.1-py_0.tar.bz2 16 KB\r\n", - "imagesize-1.1.0-py37_0.conda 9 KB\r\n", - "jedi-0.15.1-py37_0.conda 704 KB\r\n", - "zlib-1.2.11-h7b6447c_3.conda 103 KB\r\n", - "importlib_metadata-0.23-py37_0.tar.bz2 43 KB\r\n", - "nb_conda_kernels-2.2.2-py37_0.conda 39 KB\r\n", - "heapdict-1.0.1-py_0.conda 9 KB\r\n", - "cytoolz-0.10.0-py37h7b6447c_0.conda 374 KB\r\n", - "babel-2.7.0-py_0.tar.bz2 5.8 MB\r\n", - "libxml2-2.9.9-hea5a465_1.conda 1.6 MB\r\n", - "mpc-1.1.0-h10f8cd9_1.conda 90 KB\r\n", - "mkl_fft-1.0.14-py37ha843d7b_0.conda 155 KB\r\n", - "decorator-4.4.0-py37_1.conda 19 KB\r\n", - "sphinx-2.2.0-py_0.tar.bz2 1.5 MB\r\n", - "pycodestyle-2.5.0-py37_0.conda 61 KB\r\n", - "send2trash-1.5.0-py37_0.conda 16 KB\r\n", - "cycler-0.10.0-py37_0.conda 13 KB\r\n", - "mpfr-4.0.1-hdf1c602_3.conda 429 KB\r\n", - "six-1.12.0-py37_0.conda 23 KB\r\n", - "frozendict-1.2-py_2.tar.bz2 6 KB\r\n", - "pexpect-4.7.0-py37_0.conda 80 KB\r\n", - "sip-4.19.8-py37hf484d3e_0.conda 274 KB\r\n", - "setuptools-41.4.0-py37_0.tar.bz2 651 KB\r\n", - "dask-core-2.5.2-py_0.tar.bz2 579 KB\r\n", - "zipp-0.6.0-py_0.tar.bz2 9 KB\r\n", - "pandoc-2.2.3.2-0.conda 14.0 MB\r\n", - "cython-0.29.13-py37he6710b0_0.conda 2.0 MB\r\n", - "mpmath-1.1.0-py37_0.conda 766 KB\r\n", - "numpy-base-1.17.2-py37hde5b4d6_0.conda 4.2 MB\r\n", - "webencodings-0.5.1-py37_1.conda 19 KB\r\n", - "sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2 8 KB\r\n", - "defusedxml-0.6.0-py_0.tar.bz2 23 KB\r\n", - "wheel-0.33.6-py37_0.tar.bz2 40 KB\r\n", - "markupsafe-1.1.1-py37h7b6447c_0.conda 29 KB\r\n", - "pip-19.2.3-py37_0.tar.bz2 1.9 MB\r\n", - "libuuid-1.0.3-h1bed415_2.conda 15 KB\r\n", - "glib-2.56.2-hd408876_0.conda 3.9 MB\r\n", - "codecov-2.0.15-py_1.tar.bz2 18 KB\r\n", - "prompt_toolkit-2.0.10-py_0.tar.bz2 227 KB\r\n", - "coverage-4.5.4-py37h7b6447c_0.conda 224 KB\r\n", - "pysocks-1.7.1-py37_0.tar.bz2 30 KB\r\n", - "sqlite-3.30.0-h7b6447c_0.tar.bz2 1.9 MB\r\n", - "ipython_genutils-0.2.0-py37_0.conda 39 KB\r\n", - "mistune-0.8.4-py37h7b6447c_0.conda 55 KB\r\n", - "testpath-0.4.2-py37_0.conda 86 KB\r\n", - "sphinxcontrib-serializinghtml-1.1.3-py_0.tar.bz2 24 KB\r\n", - "_libgcc_mutex-0.1-main.conda 3 KB\r\n", - "libgcc-ng-9.1.0-hdf63c60_0.conda 5.1 MB\r\n", - "mkl-service-2.3.0-py37he904b0f_0.conda 218 KB\r\n", - "gmp-6.1.2-h6c8ec71_1.conda 514 KB\r\n", - "psutil-5.6.3-py37h7b6447c_0.conda 313 KB\r\n", - "cryptography-2.7-py37h1ba5d50_0.conda 544 KB\r\n", - "fontconfig-2.13.0-h9420a91_0.conda 227 KB\r\n", - "libpng-1.6.37-hbc83047_0.conda 278 KB\r\n", - "notebook-6.0.1-py37_0.tar.bz2 6.0 MB\r\n", - "mkl_random-1.1.0-py37hd6b4f25_0.conda 321 KB\r\n", - "pytools-2019.1.1-py_0.tar.bz2 52 KB\r\n", - "toolz-0.10.0-py_0.tar.bz2 50 KB\r\n", - "nbformat-4.4.0-py37_0.conda 128 KB\r\n", - "chardet-3.0.4-py37_1003.conda 174 KB\r\n", - "ipykernel-5.1.2-py37h39e3cac_0.conda 170 KB\r\n", - "scipy-1.3.1-py37h7c811a0_0.conda 14.0 MB\r\n", - "sphinxcontrib-qthelp-1.0.2-py_0.tar.bz2 26 KB\r\n", - "fastcache-1.1.0-py37h7b6447c_0.conda 30 KB\r\n", - "docutils-0.15.2-py37_0.conda 660 KB\r\n", - "pyopenssl-19.0.0-py37_0.conda 84 KB\r\n", - "mccabe-0.6.1-py37_1.conda 14 KB\r\n", - "attrs-19.2.0-py_0.tar.bz2 39 KB\r\n", - "nbval-0.9.3-py_0.tar.bz2 21 KB\r\n", - "libffi-3.2.1-hd88cf55_4.conda 40 KB\r\n", - "freetype-2.9.1-h8a8886c_1.conda 550 KB\r\n", - "pytz-2019.3-py_0.tar.bz2 231 KB\r\n", - "jupyter_core-4.5.0-py_0.tar.bz2 48 KB\r\n", - "pickleshare-0.7.5-py37_0.conda 13 KB\r\n", - "tornado-6.0.3-py37h7b6447c_0.conda 584 KB\r\n", - "py-1.8.0-py37_0.conda 148 KB\r\n", - "pluggy-0.13.0-py37_0.tar.bz2 31 KB\r\n", - "bleach-3.1.0-py37_0.conda 220 KB\r\n", - "nbconvert-5.6.0-py37_1.tar.bz2 491 KB\r\n", - "idna-2.8-py37_0.conda 85 KB\r\n", - "appdirs-1.4.3-py37h28b3542_0.conda 15 KB\r\n", - "terminado-0.8.2-py37_0.conda 23 KB\r\n", - "pytest-5.2.1-py37_0.tar.bz2 364 KB\r\n", - "mkl-2019.4-243.conda 131.2 MB\r\n", - "icu-58.2-h9c2bf20_1.conda 10.3 MB\r\n", - "gmpy2-2.0.8-py37h10f8cd9_2.conda 150 KB\r\n", - "parso-0.5.1-py_0.tar.bz2 68 KB\r\n", - "sphinxcontrib-applehelp-1.0.1-py_0.tar.bz2 29 KB\r\n", - "expat-2.2.6-he6710b0_0.conda 146 KB\r\n", - "distributed-2.5.2-py_0.tar.bz2 396 KB\r\n", - "pyzmq-18.1.0-py37he6710b0_0.conda 455 KB\r\n", - "python-3.7.4-h265db76_1.conda 32.1 MB\r\n", - "sortedcontainers-2.1.0-py37_0.conda 43 KB\r\n", - "atomicwrites-1.3.0-py37_1.conda 13 KB\r\n", - "libxcb-1.13-h1bed415_1.conda 421 KB\r\n", - "snowballstemmer-2.0.0-py_0.tar.bz2 58 KB\r\n", - "python-dateutil-2.8.0-py37_0.conda 266 KB\r\n", - "openssl-1.1.1d-h7b6447c_2.conda 2.5 MB\r\n", - "dbus-1.13.6-h746ee38_0.conda 499 KB\r\n", - "prometheus_client-0.7.1-py_0.tar.bz2 42 KB\r\n", - "asn1crypto-1.0.1-py37_0.tar.bz2 161 KB\r\n", - "pandocfilters-1.4.2-py37_1.conda 13 KB\r\n", - "flake8-3.7.8-py37_1.tar.bz2 134 KB\r\n", - "cffi-1.12.3-py37h2e261b9_0.conda 222 KB\r\n", - "urllib3-1.24.2-py37_0.conda 159 KB\r\n", - "readline-7.0-h7b6447c_5.conda 324 KB\r\n", - "wcwidth-0.1.7-py37_0.conda 22 KB\r\n", - "sphinx_rtd_theme-0.4.3-py_0.tar.bz2 5.1 MB\r\n", - "certifi-2019.9.11-py37_0.tar.bz2 154 KB\r\n", - "matplotlib-3.1.1-py37h5429711_0.conda 5.0 MB\r\n", - "yaml-0.1.7-had09818_2.conda 73 KB\r\n", - "jsonschema-3.0.2-py37_0.conda 92 KB\r\n", - "tk-8.6.8-hbc83047_0.conda 2.8 MB\r\n", - "qt-5.9.7-h5867ecd_1.conda 68.5 MB\r\n", - "cloudpickle-1.2.2-py_0.tar.bz2 29 KB\r\n", - "gst-plugins-base-1.14.0-hbbd80ab_1.conda 4.8 MB\r\n", - "requests-2.22.0-py37_0.conda 90 KB\r\n", - "pyrsistent-0.15.4-py37h7b6447c_0.tar.bz2 92 KB\r\n", - "zeromq-4.3.1-he6710b0_3.conda 496 KB\r\n", - "blas-1.0-mkl.conda 6 KB\r\n", - "pyparsing-2.4.2-py_0.tar.bz2 61 KB\r\n", - "packaging-19.2-py_0.tar.bz2 30 KB\r\n", - "jpeg-9b-h024ee3a_2.conda 214 KB\r\n", - "pyqt-5.9.2-py37h05f1152_2.conda 4.5 MB\r\n", - "sympy-1.4-py37_0.conda 7.9 MB\r\n", - "libgfortran-ng-7.3.0-hdf63c60_0.conda 1006 KB\r\n", - "pcre-8.43-he6710b0_0.conda 209 KB\r\n", - "more-itertools-7.2.0-py37_0.conda 100 KB\r\n", - "pygments-2.4.2-py_0.tar.bz2 664 KB\r\n", - "pytest-cov-2.7.1-py_0.tar.bz2 21 KB\r\n", - "intel-openmp-2019.4-243.conda 729 KB\r\n", - "entrypoints-0.3-py37_0.conda 12 KB\r\n", - "xz-5.2.4-h14c3975_4.conda 283 KB\r\n", - "backcall-0.1.0-py37_0.conda 20 KB\r\n", - "libedit-3.1.20181209-hc058e9b_0.conda 163 KB\r\n", - "msgpack-python-0.6.1-py37hfd86e86_1.conda 87 KB\r\n", - "pyyaml-5.1.2-py37h7b6447c_0.conda 179 KB\r\n", - "ca-certificates-2019.8.28-0.tar.bz2 132 KB\r\n", - "alabaster-0.7.12-py37_0.conda 18 KB\r\n", - "multidict-4.5.2-py37h7b6447c_0.conda 137 KB\r\n", - "ncurses-6.1-he6710b0_1.conda 777 KB\r\n", - "ptyprocess-0.6.0-py37_0.conda 23 KB\r\n", - "cached-property-1.5.1-py37_0.conda 12 KB\r\n", - "gstreamer-1.14.0-hb453b48_1.conda 3.1 MB\r\n", - "jupyter_client-5.3.3-py37_1.tar.bz2 137 KB\r\n", - "sphinxcontrib-devhelp-1.0.1-py_0.tar.bz2 23 KB\r\n", - "numpy-1.17.2-py37haad9e8e_0.conda 4 KB\r\n", - "sphinxcontrib-htmlhelp-1.0.2-py_0.tar.bz2 28 KB\r\n", - "graphviz-2.40.1-h21bd128_2.tar.bz2 6.9 MB\r\n", - "six-1.12.0-py36_0.tar.bz2 22 KB\r\n", - "asn1crypto-1.0.1-py36_0.tar.bz2 161 KB\r\n", - "cryptography-2.7-py36h1ba5d50_0.tar.bz2 617 KB\r\n", - "pyyaml-5.1.2-py36h7b6447c_0.tar.bz2 189 KB\r\n", - "libtiff-4.0.10-h2733197_2.tar.bz2 604 KB\r\n", - "cffi-1.12.3-py36h2e261b9_0.tar.bz2 222 KB\r\n", - "ptyprocess-0.6.0-py36_0.tar.bz2 23 KB\r\n", - "gst-plugins-base-1.14.0-hbbd80ab_1.tar.bz2 6.3 MB\r\n", - "libgfortran-ng-7.3.0-hdf63c60_0.tar.bz2 1.3 MB\r\n", - "msgpack-python-0.6.1-py36hfd86e86_1.tar.bz2 92 KB\r\n", - "mpc-1.1.0-h10f8cd9_1.tar.bz2 94 KB\r\n", - "pyzmq-18.1.0-py36he6710b0_0.tar.bz2 520 KB\r\n", - "olefile-0.46-py36_0.tar.bz2 48 KB\r\n", - "pyrsistent-0.15.4-py36h7b6447c_0.tar.bz2 92 KB\r\n", - "expat-2.2.6-he6710b0_0.tar.bz2 187 KB\r\n", - "yaml-0.1.7-h96e3832_1.tar.bz2 84 KB\r\n", - "h5py-2.9.0-py36h7918eee_0.tar.bz2 1.2 MB\r\n", - "libffi-3.2.1-h4deb6c0_3.tar.bz2 43 KB\r\n", - "testpath-0.4.2-py36_0.tar.bz2 91 KB\r\n", - "markupsafe-1.1.1-py36h7b6447c_0.tar.bz2 29 KB\r\n", - "blas-1.0-mkl.tar.bz2 6 KB\r\n", - "py-1.8.0-py36_0.tar.bz2 140 KB\r\n", - "mpmath-1.1.0-py36_0.tar.bz2 970 KB\r\n", - "pytest-5.2.1-py36_0.tar.bz2 366 KB\r\n", - "pyopenssl-19.0.0-py36_0.tar.bz2 82 KB\r\n", - "imagesize-1.1.0-py36_0.tar.bz2 9 KB\r\n", - "zeromq-4.3.1-he6710b0_3.tar.bz2 666 KB\r\n", - "pillow-6.2.0-py36h34e0f95_0.tar.bz2 647 KB\r\n", - "terminado-0.8.2-py36_0.tar.bz2 22 KB\r\n", - "requests-2.22.0-py36_0.tar.bz2 89 KB\r\n", - "jupyter_client-5.3.3-py36_1.tar.bz2 137 KB\r\n", - "qt-5.9.7-h5867ecd_1.tar.bz2 85.9 MB\r\n", - "mkl_random-1.1.0-py36hd6b4f25_0.tar.bz2 369 KB\r\n", - "mkl_fft-1.0.14-py36ha843d7b_0.tar.bz2 173 KB\r\n", - "pycodestyle-2.5.0-py36_0.tar.bz2 60 KB\r\n", - "pango-1.42.4-h049681c_0.tar.bz2 528 KB\r\n", - "flake8-3.7.8-py36_1.tar.bz2 134 KB\r\n", - "ipython-7.8.0-py36h39e3cac_0.tar.bz2 1.1 MB\r\n", - "pandocfilters-1.4.2-py36_1.tar.bz2 13 KB\r\n", - "heapdict-1.0.1-py_0.tar.bz2 9 KB\r\n", - "notebook-6.0.1-py36_0.tar.bz2 6.0 MB\r\n", - "mistune-0.8.4-py36h7b6447c_0.tar.bz2 54 KB\r\n", - "alabaster-0.7.12-py36_0.tar.bz2 17 KB\r\n", - "jedi-0.15.1-py36_0.tar.bz2 721 KB\r\n", - "certifi-2019.9.11-py36_0.tar.bz2 154 KB\r\n", - "gstreamer-1.14.0-hb453b48_1.tar.bz2 3.8 MB\r\n", - "pandoc-2.2.3.2-0.tar.bz2 20.8 MB\r\n", - "pandas-0.25.1-py36he6710b0_0.tar.bz2 11.4 MB\r\n", - "chardet-3.0.4-py36_1003.tar.bz2 197 KB\r\n", - "libedit-3.1.20181209-hc058e9b_0.tar.bz2 188 KB\r\n", - "cached-property-1.5.1-py36_0.tar.bz2 11 KB\r\n", - "pyqt-5.9.2-py36h22d08a2_1.tar.bz2 5.6 MB\r\n", - "tornado-6.0.3-py36h7b6447c_0.tar.bz2 643 KB\r\n", - "psutil-5.6.3-py36h7b6447c_0.tar.bz2 328 KB\r\n", - "libsodium-1.0.16-h1bed415_0.tar.bz2 302 KB\r\n", - "gmp-6.1.2-hb3b607b_0.tar.bz2 744 KB\r\n", - "pexpect-4.7.0-py36_0.tar.bz2 82 KB\r\n", - "nbformat-4.4.0-py36_0.tar.bz2 141 KB\r\n", - "python-3.6.9-h265db76_0.tar.bz2 34.4 MB\r\n", - "docutils-0.15.2-py36_0.tar.bz2 742 KB\r\n", - "coverage-4.5.4-py36h7b6447c_0.tar.bz2 226 KB\r\n", - "xz-5.2.4-h14c3975_4.tar.bz2 366 KB\r\n", - "mccabe-0.6.1-py36_1.tar.bz2 14 KB\r\n", - "jsonschema-3.0.2-py36_0.tar.bz2 90 KB\r\n", - "fontconfig-2.13.0-h9420a91_0.tar.bz2 291 KB\r\n", - "networkx-2.3-py_0.tar.bz2 1.1 MB\r\n", - "importlib_metadata-0.23-py36_0.tar.bz2 43 KB\r\n", - "send2trash-1.5.0-py36_0.tar.bz2 16 KB\r\n", - "pluggy-0.13.0-py36_0.tar.bz2 31 KB\r\n", - "bokeh-1.3.4-py36_0.tar.bz2 4.0 MB\r\n", - "pcre-8.43-he6710b0_0.tar.bz2 260 KB\r\n", - "libstdcxx-ng-9.1.0-hdf63c60_0.tar.bz2 4.0 MB\r\n", - "numpy-base-1.17.2-py36hde5b4d6_0.tar.bz2 5.3 MB\r\n", - "wheel-0.33.6-py36_0.tar.bz2 40 KB\r\n", - "scikit-image-0.15.0-py36he6710b0_0.tar.bz2 28.4 MB\r\n", - "blosc-1.17.0-he1b5a44_1.tar.bz2 862 KB\r\n", - "zlib-1.2.11-h7b6447c_3.tar.bz2 120 KB\r\n", - "cairo-1.14.12-h8948797_3.tar.bz2 1.3 MB\r\n", - "mpfr-4.0.1-hdf1c602_3.tar.bz2 575 KB\r\n", - "zstd-1.3.7-h0b5b093_0.tar.bz2 887 KB\r\n", - "numpy-1.17.2-py36haad9e8e_0.tar.bz2 4 KB\r\n", - "bleach-3.1.0-py36_0.tar.bz2 226 KB\r\n", - "pyflakes-2.1.1-py36_0.tar.bz2 105 KB\r\n", - "bzip2-1.0.8-h7b6447c_0.tar.bz2 105 KB\r\n", - "sympy-1.4-py36_0.tar.bz2 9.7 MB\r\n", - "urllib3-1.24.2-py36_0.tar.bz2 153 KB\r\n", - "more-itertools-7.2.0-py36_0.tar.bz2 99 KB\r\n", - "pip-19.2.3-py36_0.tar.bz2 1.9 MB\r\n", - "python-dateutil-2.8.0-py36_0.tar.bz2 281 KB\r\n", - "wcwidth-0.1.7-py36_0.tar.bz2 25 KB\r\n", - "dask-2.5.2-py_0.tar.bz2 12 KB\r\n", - "glib-2.56.2-hd408876_0.tar.bz2 5.0 MB\r\n", - "setuptools-41.4.0-py36_0.tar.bz2 673 KB\r\n", - "scipy-1.3.1-py36h7c811a0_0.tar.bz2 18.1 MB\r\n", - "libgcc-ng-9.1.0-hdf63c60_0.tar.bz2 8.1 MB\r\n", - "sortedcontainers-2.1.0-py36_0.tar.bz2 45 KB\r\n", - "webencodings-0.5.1-py36_1.tar.bz2 19 KB\r\n", - "numexpr-2.7.0-py36h9e4a6bb_0.tar.bz2 196 KB\r\n", - "appdirs-1.4.3-py36h28b3542_0.tar.bz2 16 KB\r\n", - "pycparser-2.19-py36_0.tar.bz2 174 KB\r\n", - "pickleshare-0.7.5-py36_0.tar.bz2 13 KB\r\n", - "harfbuzz-1.8.8-hffaf4a1_0.tar.bz2 863 KB\r\n", - "idna-2.8-py36_0.tar.bz2 133 KB\r\n", - "libxml2-2.9.9-hea5a465_1.tar.bz2 2.0 MB\r\n", - "mkl-2019.5-281.tar.bz2 205.3 MB\r\n", - "mock-3.0.5-py36_0.tar.bz2 47 KB\r\n", - "traitlets-4.3.3-py36_0.tar.bz2 137 KB\r\n", - "graphite2-1.3.13-h23475e2_0.tar.bz2 101 KB\r\n", - "fastcache-1.1.0-py36h7b6447c_0.tar.bz2 31 KB\r\n", - "partd-1.0.0-py_0.tar.bz2 19 KB\r\n", - "lzo-2.10-h1bfc0ba_1.tar.bz2 314 KB\r\n", - "jpeg-9b-habf39ab_1.tar.bz2 247 KB\r\n", - "backcall-0.1.0-py36_0.tar.bz2 19 KB\r\n", - "cython-0.29.13-py36he6710b0_0.tar.bz2 2.2 MB\r\n", - "py-cpuinfo-5.0.0-py_0.tar.bz2 22 KB\r\n", - "readline-7.0-h7b6447c_5.tar.bz2 392 KB\r\n", - "ncurses-6.1-he6710b0_1.tar.bz2 958 KB\r\n", - "libpng-1.6.37-hbc83047_0.tar.bz2 364 KB\r\n", - "pytables-3.5.2-py36h71ec239_1.tar.bz2 1.5 MB\r\n", - "gmpy2-2.0.8-py36h10f8cd9_2.tar.bz2 165 KB\r\n", - "atomicwrites-1.3.0-py36_1.tar.bz2 13 KB\r\n", - "dbus-1.13.6-h746ee38_0.tar.bz2 587 KB\r\n", - "matplotlib-3.1.1-py36h5429711_0.tar.bz2 6.7 MB\r\n", - "locket-0.2.0-py36_1.tar.bz2 8 KB\r\n", - "pywavelets-1.0.3-py36hdd07704_1.tar.bz2 4.4 MB\r\n", - "imageio-2.6.0-py36_0.tar.bz2 3.3 MB\r\n", - "multidict-4.5.2-py36h7b6447c_0.tar.bz2 142 KB\r\n", - "mkl-service-2.3.0-py36he904b0f_0.tar.bz2 208 KB\r\n", - "entrypoints-0.3-py36_0.tar.bz2 12 KB\r\n", - "intel-openmp-2019.5-281.tar.bz2 895 KB\r\n", - "fsspec-0.5.2-py_0.tar.bz2 46 KB\r\n", - "ipython_genutils-0.2.0-py36_0.tar.bz2 39 KB\r\n", - "icu-58.2-h211956c_0.tar.bz2 22.5 MB\r\n", - "nb_conda_kernels-2.2.2-py36_0.tar.bz2 37 KB\r\n", - "sip-4.19.13-py36he6710b0_0.tar.bz2 293 KB\r\n", - "kiwisolver-1.1.0-py36he6710b0_0.tar.bz2 90 KB\r\n", - "libuuid-1.0.3-h1bed415_2.tar.bz2 16 KB\r\n", - "hdf5-1.10.4-hb1b8bf9_0.tar.bz2 5.3 MB\r\n", - "libxcb-1.13-h1bed415_1.tar.bz2 502 KB\r\n", - "decorator-4.4.0-py36_1.tar.bz2 18 KB\r\n", - "tk-8.6.8-hbc83047_0.tar.bz2 3.1 MB\r\n", - "openssl-1.1.1-h7b6447c_0.tar.bz2 5.0 MB\r\n", - "python-graphviz-0.10.1-py_0.tar.bz2 22 KB\r\n", - "snakeviz-2.0.1-py36_0.tar.bz2 316 KB\r\n", - "pixman-0.38.0-h7b6447c_0.tar.bz2 618 KB\r\n", - "nb_conda-2.2.1-py36_0.tar.bz2 33 KB\r\n", - "click-7.0-py36_0.tar.bz2 118 KB\r\n", - "cytoolz-0.10.0-py36h7b6447c_0.tar.bz2 439 KB\r\n", - "ipykernel-5.1.2-py36h39e3cac_0.tar.bz2 165 KB\r\n", - "fribidi-1.0.5-h7b6447c_0.tar.bz2 112 KB\r\n", - "pysocks-1.7.1-py36_0.tar.bz2 30 KB\r\n", - "cycler-0.10.0-py36_0.tar.bz2 13 KB\r\n", - "freetype-2.9.1-h8a8886c_1.tar.bz2 822 KB\r\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "nbconvert-5.6.0-py36_1.tar.bz2 494 KB\n", - "\n", - "---------------------------------------------------\n", - "Total: 926.1 MB\n", - "\n", - "Removed click-7.0-py37_0.conda\n", - "Removed pyflakes-2.1.1-py37_0.conda\n", - "Removed tblib-1.4.0-py_0.tar.bz2\n", - "Removed libsodium-1.0.16-h1bed415_0.conda\n", - "Removed kiwisolver-1.1.0-py37he6710b0_0.conda\n", - "Removed jinja2-2.10.3-py_0.tar.bz2\n", - "Removed zict-1.0.0-py_0.tar.bz2\n", - "Removed ipython-7.8.0-py37h39e3cac_0.conda\n", - "Removed traitlets-4.3.3-py37_0.tar.bz2\n", - "Removed libstdcxx-ng-9.1.0-hdf63c60_0.conda\n", - "Removed pycparser-2.19-py37_0.conda\n", - "Removed cgen-2019.1-py_0.tar.bz2\n", - "Removed imagesize-1.1.0-py37_0.conda\n", - "Removed jedi-0.15.1-py37_0.conda\n", - "Removed zlib-1.2.11-h7b6447c_3.conda\n", - "Removed importlib_metadata-0.23-py37_0.tar.bz2\n", - "Removed nb_conda_kernels-2.2.2-py37_0.conda\n", - "Removed heapdict-1.0.1-py_0.conda\n", - "Removed cytoolz-0.10.0-py37h7b6447c_0.conda\n", - "Removed babel-2.7.0-py_0.tar.bz2\n", - "Removed libxml2-2.9.9-hea5a465_1.conda\n", - "Removed mpc-1.1.0-h10f8cd9_1.conda\n", - "Removed mkl_fft-1.0.14-py37ha843d7b_0.conda\n", - "Removed decorator-4.4.0-py37_1.conda\n", - "Removed sphinx-2.2.0-py_0.tar.bz2\n", - "Removed pycodestyle-2.5.0-py37_0.conda\n", - "Removed send2trash-1.5.0-py37_0.conda\n", - "Removed cycler-0.10.0-py37_0.conda\n", - "Removed mpfr-4.0.1-hdf1c602_3.conda\n", - "Removed six-1.12.0-py37_0.conda\n", - "Removed frozendict-1.2-py_2.tar.bz2\n", - "Removed pexpect-4.7.0-py37_0.conda\n", - "Removed sip-4.19.8-py37hf484d3e_0.conda\n", - "Removed setuptools-41.4.0-py37_0.tar.bz2\n", - "Removed dask-core-2.5.2-py_0.tar.bz2\n", - "Removed zipp-0.6.0-py_0.tar.bz2\n", - "Removed pandoc-2.2.3.2-0.conda\n", - "Removed cython-0.29.13-py37he6710b0_0.conda\n", - "Removed mpmath-1.1.0-py37_0.conda\n", - "Removed numpy-base-1.17.2-py37hde5b4d6_0.conda\n", - "Removed webencodings-0.5.1-py37_1.conda\n", - "Removed sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2\n", - "Removed defusedxml-0.6.0-py_0.tar.bz2\n", - "Removed wheel-0.33.6-py37_0.tar.bz2\n", - "Removed markupsafe-1.1.1-py37h7b6447c_0.conda\n", - "Removed pip-19.2.3-py37_0.tar.bz2\n", - "Removed libuuid-1.0.3-h1bed415_2.conda\n", - "Removed glib-2.56.2-hd408876_0.conda\n", - "Removed codecov-2.0.15-py_1.tar.bz2\n", - "Removed prompt_toolkit-2.0.10-py_0.tar.bz2\n", - "Removed coverage-4.5.4-py37h7b6447c_0.conda\n", - "Removed pysocks-1.7.1-py37_0.tar.bz2\n", - "Removed sqlite-3.30.0-h7b6447c_0.tar.bz2\n", - "Removed ipython_genutils-0.2.0-py37_0.conda\n", - "Removed mistune-0.8.4-py37h7b6447c_0.conda\n", - "Removed testpath-0.4.2-py37_0.conda\n", - "Removed sphinxcontrib-serializinghtml-1.1.3-py_0.tar.bz2\n", - "Removed _libgcc_mutex-0.1-main.conda\n", - "Removed libgcc-ng-9.1.0-hdf63c60_0.conda\n", - "Removed mkl-service-2.3.0-py37he904b0f_0.conda\n", - "Removed gmp-6.1.2-h6c8ec71_1.conda\n", - "Removed psutil-5.6.3-py37h7b6447c_0.conda\n", - "Removed cryptography-2.7-py37h1ba5d50_0.conda\n", - "Removed fontconfig-2.13.0-h9420a91_0.conda\n", - "Removed libpng-1.6.37-hbc83047_0.conda\n", - "Removed notebook-6.0.1-py37_0.tar.bz2\n", - "Removed mkl_random-1.1.0-py37hd6b4f25_0.conda\n", - "Removed pytools-2019.1.1-py_0.tar.bz2\n", - "Removed toolz-0.10.0-py_0.tar.bz2\n", - "Removed nbformat-4.4.0-py37_0.conda\n", - "Removed chardet-3.0.4-py37_1003.conda\n", - "Removed ipykernel-5.1.2-py37h39e3cac_0.conda\n", - "Removed scipy-1.3.1-py37h7c811a0_0.conda\n", - "Removed sphinxcontrib-qthelp-1.0.2-py_0.tar.bz2\n", - "Removed fastcache-1.1.0-py37h7b6447c_0.conda\n", - "Removed docutils-0.15.2-py37_0.conda\n", - "Removed pyopenssl-19.0.0-py37_0.conda\n", - "Removed mccabe-0.6.1-py37_1.conda\n", - "Removed attrs-19.2.0-py_0.tar.bz2\n", - "Removed nbval-0.9.3-py_0.tar.bz2\n", - "Removed libffi-3.2.1-hd88cf55_4.conda\n", - "Removed freetype-2.9.1-h8a8886c_1.conda\n", - "Removed pytz-2019.3-py_0.tar.bz2\n", - "Removed jupyter_core-4.5.0-py_0.tar.bz2\n", - "Removed pickleshare-0.7.5-py37_0.conda\n", - "Removed tornado-6.0.3-py37h7b6447c_0.conda\n", - "Removed py-1.8.0-py37_0.conda\n", - "Removed pluggy-0.13.0-py37_0.tar.bz2\n", - "Removed bleach-3.1.0-py37_0.conda\n", - "Removed nbconvert-5.6.0-py37_1.tar.bz2\n", - "Removed idna-2.8-py37_0.conda\n", - "Removed appdirs-1.4.3-py37h28b3542_0.conda\n", - "Removed terminado-0.8.2-py37_0.conda\n", - "Removed pytest-5.2.1-py37_0.tar.bz2\n", - "Removed mkl-2019.4-243.conda\n", - "Removed icu-58.2-h9c2bf20_1.conda\n", - "Removed gmpy2-2.0.8-py37h10f8cd9_2.conda\n", - "Removed parso-0.5.1-py_0.tar.bz2\n", - "Removed sphinxcontrib-applehelp-1.0.1-py_0.tar.bz2\n", - "Removed expat-2.2.6-he6710b0_0.conda\n", - "Removed distributed-2.5.2-py_0.tar.bz2\n", - "Removed pyzmq-18.1.0-py37he6710b0_0.conda\n", - "Removed python-3.7.4-h265db76_1.conda\n", - "Removed sortedcontainers-2.1.0-py37_0.conda\n", - "Removed atomicwrites-1.3.0-py37_1.conda\n", - "Removed libxcb-1.13-h1bed415_1.conda\n", - "Removed snowballstemmer-2.0.0-py_0.tar.bz2\n", - "Removed python-dateutil-2.8.0-py37_0.conda\n", - "Removed openssl-1.1.1d-h7b6447c_2.conda\n", - "Removed dbus-1.13.6-h746ee38_0.conda\n", - "Removed prometheus_client-0.7.1-py_0.tar.bz2\n", - "Removed asn1crypto-1.0.1-py37_0.tar.bz2\n", - "Removed pandocfilters-1.4.2-py37_1.conda\n", - "Removed flake8-3.7.8-py37_1.tar.bz2\n", - "Removed cffi-1.12.3-py37h2e261b9_0.conda\n", - "Removed urllib3-1.24.2-py37_0.conda\n", - "Removed readline-7.0-h7b6447c_5.conda\n", - "Removed wcwidth-0.1.7-py37_0.conda\n", - "Removed sphinx_rtd_theme-0.4.3-py_0.tar.bz2\n", - "Removed certifi-2019.9.11-py37_0.tar.bz2\n", - "Removed matplotlib-3.1.1-py37h5429711_0.conda\n", - "Removed yaml-0.1.7-had09818_2.conda\n", - "Removed jsonschema-3.0.2-py37_0.conda\n", - "Removed tk-8.6.8-hbc83047_0.conda\n", - "Removed qt-5.9.7-h5867ecd_1.conda\n", - "Removed cloudpickle-1.2.2-py_0.tar.bz2\n", - "Removed gst-plugins-base-1.14.0-hbbd80ab_1.conda\n", - "Removed requests-2.22.0-py37_0.conda\n", - "Removed pyrsistent-0.15.4-py37h7b6447c_0.tar.bz2\n", - "Removed zeromq-4.3.1-he6710b0_3.conda\n", - "Removed blas-1.0-mkl.conda\n", - "Removed pyparsing-2.4.2-py_0.tar.bz2\n", - "Removed packaging-19.2-py_0.tar.bz2\n", - "Removed jpeg-9b-h024ee3a_2.conda\n", - "Removed pyqt-5.9.2-py37h05f1152_2.conda\n", - "Removed sympy-1.4-py37_0.conda\n", - "Removed libgfortran-ng-7.3.0-hdf63c60_0.conda\n", - "Removed pcre-8.43-he6710b0_0.conda\n", - "Removed more-itertools-7.2.0-py37_0.conda\n", - "Removed pygments-2.4.2-py_0.tar.bz2\n", - "Removed pytest-cov-2.7.1-py_0.tar.bz2\n", - "Removed intel-openmp-2019.4-243.conda\n", - "Removed entrypoints-0.3-py37_0.conda\n", - "Removed xz-5.2.4-h14c3975_4.conda\n", - "Removed backcall-0.1.0-py37_0.conda\n", - "Removed libedit-3.1.20181209-hc058e9b_0.conda\n", - "Removed msgpack-python-0.6.1-py37hfd86e86_1.conda\n", - "Removed pyyaml-5.1.2-py37h7b6447c_0.conda\n", - "Removed ca-certificates-2019.8.28-0.tar.bz2\n", - "Removed alabaster-0.7.12-py37_0.conda\n", - "Removed multidict-4.5.2-py37h7b6447c_0.conda\n", - "Removed ncurses-6.1-he6710b0_1.conda\n", - "Removed ptyprocess-0.6.0-py37_0.conda\n", - "Removed cached-property-1.5.1-py37_0.conda\n", - "Removed gstreamer-1.14.0-hb453b48_1.conda\n", - "Removed jupyter_client-5.3.3-py37_1.tar.bz2\n", - "Removed sphinxcontrib-devhelp-1.0.1-py_0.tar.bz2\n", - "Removed numpy-1.17.2-py37haad9e8e_0.conda\n", - "Removed sphinxcontrib-htmlhelp-1.0.2-py_0.tar.bz2\n", - "Removed graphviz-2.40.1-h21bd128_2.tar.bz2\n", - "Removed six-1.12.0-py36_0.tar.bz2\n", - "Removed asn1crypto-1.0.1-py36_0.tar.bz2\n", - "Removed cryptography-2.7-py36h1ba5d50_0.tar.bz2\n", - "Removed pyyaml-5.1.2-py36h7b6447c_0.tar.bz2\n", - "Removed libtiff-4.0.10-h2733197_2.tar.bz2\n", - "Removed cffi-1.12.3-py36h2e261b9_0.tar.bz2\n", - "Removed ptyprocess-0.6.0-py36_0.tar.bz2\n", - "Removed gst-plugins-base-1.14.0-hbbd80ab_1.tar.bz2\n", - "Removed libgfortran-ng-7.3.0-hdf63c60_0.tar.bz2\n", - "Removed msgpack-python-0.6.1-py36hfd86e86_1.tar.bz2\n", - "Removed mpc-1.1.0-h10f8cd9_1.tar.bz2\n", - "Removed pyzmq-18.1.0-py36he6710b0_0.tar.bz2\n", - "Removed olefile-0.46-py36_0.tar.bz2\n", - "Removed pyrsistent-0.15.4-py36h7b6447c_0.tar.bz2\n", - "Removed expat-2.2.6-he6710b0_0.tar.bz2\n", - "Removed yaml-0.1.7-h96e3832_1.tar.bz2\n", - "Removed h5py-2.9.0-py36h7918eee_0.tar.bz2\n", - "Removed libffi-3.2.1-h4deb6c0_3.tar.bz2\n", - "Removed testpath-0.4.2-py36_0.tar.bz2\n", - "Removed markupsafe-1.1.1-py36h7b6447c_0.tar.bz2\n", - "Removed blas-1.0-mkl.tar.bz2\n", - "Removed py-1.8.0-py36_0.tar.bz2\n", - "Removed mpmath-1.1.0-py36_0.tar.bz2\n", - "Removed pytest-5.2.1-py36_0.tar.bz2\n", - "Removed pyopenssl-19.0.0-py36_0.tar.bz2\n", - "Removed imagesize-1.1.0-py36_0.tar.bz2\n", - "Removed zeromq-4.3.1-he6710b0_3.tar.bz2\n", - "Removed pillow-6.2.0-py36h34e0f95_0.tar.bz2\n", - "Removed terminado-0.8.2-py36_0.tar.bz2\n", - "Removed requests-2.22.0-py36_0.tar.bz2\n", - "Removed jupyter_client-5.3.3-py36_1.tar.bz2\n", - "Removed qt-5.9.7-h5867ecd_1.tar.bz2\n", - "Removed mkl_random-1.1.0-py36hd6b4f25_0.tar.bz2\n", - "Removed mkl_fft-1.0.14-py36ha843d7b_0.tar.bz2\n", - "Removed pycodestyle-2.5.0-py36_0.tar.bz2\n", - "Removed pango-1.42.4-h049681c_0.tar.bz2\n", - "Removed flake8-3.7.8-py36_1.tar.bz2\n", - "Removed ipython-7.8.0-py36h39e3cac_0.tar.bz2\n", - "Removed pandocfilters-1.4.2-py36_1.tar.bz2\n", - "Removed heapdict-1.0.1-py_0.tar.bz2\n", - "Removed notebook-6.0.1-py36_0.tar.bz2\n", - "Removed mistune-0.8.4-py36h7b6447c_0.tar.bz2\n", - "Removed alabaster-0.7.12-py36_0.tar.bz2\n", - "Removed jedi-0.15.1-py36_0.tar.bz2\n" - ] + "data": { + "text/plain": [ + "['Sending build context to Docker daemon 16.9kB',\n", + " '',\n", + " 'Step 1/15 : FROM continuumio/miniconda3:4.7.10',\n", + " ' ---> 4a51de2367be',\n", + " 'Step 2/15 : MAINTAINER George Iordanescu ']" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Removed certifi-2019.9.11-py36_0.tar.bz2\n", - "Removed gstreamer-1.14.0-hb453b48_1.tar.bz2\n", - "Removed pandoc-2.2.3.2-0.tar.bz2\n", - "Removed pandas-0.25.1-py36he6710b0_0.tar.bz2\n", - "Removed chardet-3.0.4-py36_1003.tar.bz2\n", - "Removed libedit-3.1.20181209-hc058e9b_0.tar.bz2\n", - "Removed cached-property-1.5.1-py36_0.tar.bz2\n", - "Removed pyqt-5.9.2-py36h22d08a2_1.tar.bz2\n", - "Removed tornado-6.0.3-py36h7b6447c_0.tar.bz2\n", - "Removed psutil-5.6.3-py36h7b6447c_0.tar.bz2\n", - "Removed libsodium-1.0.16-h1bed415_0.tar.bz2\n", - "Removed gmp-6.1.2-hb3b607b_0.tar.bz2\n", - "Removed pexpect-4.7.0-py36_0.tar.bz2\n", - "Removed nbformat-4.4.0-py36_0.tar.bz2\n", - "Removed python-3.6.9-h265db76_0.tar.bz2\n", - "Removed docutils-0.15.2-py36_0.tar.bz2\n", - "Removed coverage-4.5.4-py36h7b6447c_0.tar.bz2\n", - "Removed xz-5.2.4-h14c3975_4.tar.bz2\n", - "Removed mccabe-0.6.1-py36_1.tar.bz2\n", - "Removed jsonschema-3.0.2-py36_0.tar.bz2\n", - "Removed fontconfig-2.13.0-h9420a91_0.tar.bz2\n", - "Removed networkx-2.3-py_0.tar.bz2\n", - "Removed importlib_metadata-0.23-py36_0.tar.bz2\n", - "Removed send2trash-1.5.0-py36_0.tar.bz2\n", - "Removed pluggy-0.13.0-py36_0.tar.bz2\n", - "Removed bokeh-1.3.4-py36_0.tar.bz2\n", - "Removed pcre-8.43-he6710b0_0.tar.bz2\n", - "Removed libstdcxx-ng-9.1.0-hdf63c60_0.tar.bz2\n", - "Removed numpy-base-1.17.2-py36hde5b4d6_0.tar.bz2\n", - "Removed wheel-0.33.6-py36_0.tar.bz2\n", - "Removed scikit-image-0.15.0-py36he6710b0_0.tar.bz2\n", - "Removed blosc-1.17.0-he1b5a44_1.tar.bz2\n", - "Removed zlib-1.2.11-h7b6447c_3.tar.bz2\n", - "Removed cairo-1.14.12-h8948797_3.tar.bz2\n", - "Removed mpfr-4.0.1-hdf1c602_3.tar.bz2\n", - "Removed zstd-1.3.7-h0b5b093_0.tar.bz2\n", - "Removed numpy-1.17.2-py36haad9e8e_0.tar.bz2\n", - "Removed bleach-3.1.0-py36_0.tar.bz2\n", - "Removed pyflakes-2.1.1-py36_0.tar.bz2\n", - "Removed bzip2-1.0.8-h7b6447c_0.tar.bz2\n", - "Removed sympy-1.4-py36_0.tar.bz2\n", - "Removed urllib3-1.24.2-py36_0.tar.bz2\n", - "Removed more-itertools-7.2.0-py36_0.tar.bz2\n", - "Removed pip-19.2.3-py36_0.tar.bz2\n", - "Removed python-dateutil-2.8.0-py36_0.tar.bz2\n", - "Removed wcwidth-0.1.7-py36_0.tar.bz2\n", - "Removed dask-2.5.2-py_0.tar.bz2\n", - "Removed glib-2.56.2-hd408876_0.tar.bz2\n", - "Removed setuptools-41.4.0-py36_0.tar.bz2\n", - "Removed scipy-1.3.1-py36h7c811a0_0.tar.bz2\n", - "Removed libgcc-ng-9.1.0-hdf63c60_0.tar.bz2\n", - "Removed sortedcontainers-2.1.0-py36_0.tar.bz2\n", - "Removed webencodings-0.5.1-py36_1.tar.bz2\n", - "Removed numexpr-2.7.0-py36h9e4a6bb_0.tar.bz2\n", - "Removed appdirs-1.4.3-py36h28b3542_0.tar.bz2\n", - "Removed pycparser-2.19-py36_0.tar.bz2\n", - "Removed pickleshare-0.7.5-py36_0.tar.bz2\n", - "Removed harfbuzz-1.8.8-hffaf4a1_0.tar.bz2\n", - "Removed idna-2.8-py36_0.tar.bz2\n", - "Removed libxml2-2.9.9-hea5a465_1.tar.bz2\n", - "Removed mkl-2019.5-281.tar.bz2\n", - "Removed mock-3.0.5-py36_0.tar.bz2\n", - "Removed traitlets-4.3.3-py36_0.tar.bz2\n", - "Removed graphite2-1.3.13-h23475e2_0.tar.bz2\n", - "Removed fastcache-1.1.0-py36h7b6447c_0.tar.bz2\n", - "Removed partd-1.0.0-py_0.tar.bz2\n", - "Removed lzo-2.10-h1bfc0ba_1.tar.bz2\n", - "Removed jpeg-9b-habf39ab_1.tar.bz2\n", - "Removed backcall-0.1.0-py36_0.tar.bz2\n", - "Removed cython-0.29.13-py36he6710b0_0.tar.bz2\n", - "Removed py-cpuinfo-5.0.0-py_0.tar.bz2\n", - "Removed readline-7.0-h7b6447c_5.tar.bz2\n", - "Removed ncurses-6.1-he6710b0_1.tar.bz2\n", - "Removed libpng-1.6.37-hbc83047_0.tar.bz2\n", - "Removed pytables-3.5.2-py36h71ec239_1.tar.bz2\n", - "Removed gmpy2-2.0.8-py36h10f8cd9_2.tar.bz2\n", - "Removed atomicwrites-1.3.0-py36_1.tar.bz2\n", - "Removed dbus-1.13.6-h746ee38_0.tar.bz2\n", - "Removed matplotlib-3.1.1-py36h5429711_0.tar.bz2\n", - "Removed locket-0.2.0-py36_1.tar.bz2\n", - "Removed pywavelets-1.0.3-py36hdd07704_1.tar.bz2\n", - "Removed imageio-2.6.0-py36_0.tar.bz2\n", - "Removed multidict-4.5.2-py36h7b6447c_0.tar.bz2\n", - "Removed mkl-service-2.3.0-py36he904b0f_0.tar.bz2\n", - "Removed entrypoints-0.3-py36_0.tar.bz2\n", - "Removed intel-openmp-2019.5-281.tar.bz2\n", - "Removed fsspec-0.5.2-py_0.tar.bz2\n", - "Removed ipython_genutils-0.2.0-py36_0.tar.bz2\n", - "Removed icu-58.2-h211956c_0.tar.bz2\n", - "Removed nb_conda_kernels-2.2.2-py36_0.tar.bz2\n", - "Removed sip-4.19.13-py36he6710b0_0.tar.bz2\n", - "Removed kiwisolver-1.1.0-py36he6710b0_0.tar.bz2\n", - "Removed libuuid-1.0.3-h1bed415_2.tar.bz2\n", - "Removed hdf5-1.10.4-hb1b8bf9_0.tar.bz2\n", - "Removed libxcb-1.13-h1bed415_1.tar.bz2\n", - "Removed decorator-4.4.0-py36_1.tar.bz2\n", - "Removed tk-8.6.8-hbc83047_0.tar.bz2\n", - "Removed openssl-1.1.1-h7b6447c_0.tar.bz2\n", - "Removed python-graphviz-0.10.1-py_0.tar.bz2\n", - "Removed snakeviz-2.0.1-py36_0.tar.bz2\n", - "Removed pixman-0.38.0-h7b6447c_0.tar.bz2\n", - "Removed nb_conda-2.2.1-py36_0.tar.bz2\n", - "Removed click-7.0-py36_0.tar.bz2\n", - "Removed cytoolz-0.10.0-py36h7b6447c_0.tar.bz2\n", - "Removed ipykernel-5.1.2-py36h39e3cac_0.tar.bz2\n", - "Removed fribidi-1.0.5-h7b6447c_0.tar.bz2\n", - "Removed pysocks-1.7.1-py36_0.tar.bz2\n", - "Removed cycler-0.10.0-py36_0.tar.bz2\n", - "Removed freetype-2.9.1-h8a8886c_1.tar.bz2\n", - "Removed nbconvert-5.6.0-py36_1.tar.bz2\n", - "WARNING: /root/.conda/pkgs does not exist\n", - "Cache location: /opt/conda/pkgs\n", - "Will remove the following packages:\n", - "/opt/conda/pkgs\n", - "---------------\n", - "\n", - "blas-1.0-mkl 15 KB\n", - "_libgcc_mutex-0.1-main 7 KB\n", - "numpy-1.17.2-py37haad9e8e_0 11 KB\n", - "wheel-0.33.6-py36_0 129 KB\n", - "scipy-1.3.1-py36h7c811a0_0 61.6 MB\n", - "numpy-1.17.2-py36haad9e8e_0 11 KB\n", - "dask-2.5.2-py_0 40 KB\n", - "pandas-0.25.1-py36he6710b0_0 46.2 MB\n", - "\n", - "---------------------------------------------------\n", - "Total: 108.0 MB\n", - "\n", - "removing blas-1.0-mkl\n", - "removing _libgcc_mutex-0.1-main\n", - "removing numpy-1.17.2-py37haad9e8e_0\n", - "removing wheel-0.33.6-py36_0\n", - "removing scipy-1.3.1-py36h7c811a0_0\n", - "removing numpy-1.17.2-py36haad9e8e_0\n", - "removing dask-2.5.2-py_0\n", - "removing pandas-0.25.1-py36he6710b0_0\n", - "Removing intermediate container 042a9e35656f\n", - " ---> 1a22adac58e1\n", - "Step 14/15 : ENV PYTHONPATH=$PYTHONPATH:devito/app\n", - " ---> Running in 8b5d8220c6e3\n", - "Removing intermediate container 8b5d8220c6e3\n", - " ---> 1448c7f7a95c\n", - "Step 15/15 : CMD /bin/bash\n", - " ---> Running in 6cc39f3123be\n", - "Removing intermediate container 6cc39f3123be\n", - " ---> 12e47a24fca8\n", - "Successfully built 12e47a24fca8\n", - "Successfully tagged georgedockeraccount/fwi01_azureml:sdk.v1.0.65\n" - ] + "data": { + "text/plain": [ + "['Step 15/15 : CMD /bin/bash',\n", + " ' ---> Using cache',\n", + " ' ---> 3dc3d5d871a4',\n", + " 'Successfully built 3dc3d5d871a4',\n", + " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69']" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -2339,7 +557,10 @@ "\n", "\n", "cli_command\n", - "! $cli_command" + "docker_build_response = ! $cli_command\n", + "\n", + "docker_build_response[0:5] \n", + "docker_build_response[-5:] " ] }, { @@ -2357,7 +578,7 @@ { "data": { "text/plain": [ - "'georgedockeraccount/fwi01_azureml:sdk.v1.0.65'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" ] }, "execution_count": 14, @@ -2377,7 +598,7 @@ { "data": { "text/plain": [ - "b'/\\n1.0.65\\n'" + "b'/\\n1.0.69\\n'" ] }, "execution_count": 14, @@ -2441,19 +662,10 @@ "execution_count": 16, "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "content of devito tests log file before testing:\n", - "Before running e13n container... \n" - ] - }, { "data": { "text/plain": [ - "' python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'" + "''" ] }, "execution_count": 16, @@ -2486,7 +698,7 @@ { "data": { "text/plain": [ - "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks:/workspace:rw georgedockeraccount/fwi01_azureml:sdk.v1.0.65 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/ \"'" + "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; \"'" ] }, "execution_count": 17, @@ -2502,85 +714,89 @@ "base /opt/conda\n", "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", "\n", - "total 508\n", - "-rw-r--r-- 1 root root 11521 Oct 9 17:55 conftest.py\n", - "-rw-r--r-- 1 root root 6425 Oct 9 17:55 test_adjoint.py\n", - "-rw-r--r-- 1 root root 13882 Oct 9 17:55 test_autotuner.py\n", - "-rw-r--r-- 1 root root 9727 Oct 9 17:55 test_checkpointing.py\n", - "-rw-r--r-- 1 root root 1095 Oct 9 17:55 test_constant.py\n", - "-rw-r--r-- 1 root root 53290 Oct 9 17:55 test_data.py\n", - "-rw-r--r-- 1 root root 481 Oct 9 17:55 test_dependency_bugs.py\n", - "-rw-r--r-- 1 root root 16585 Oct 9 17:55 test_derivatives.py\n", - "-rw-r--r-- 1 root root 30846 Oct 9 17:55 test_dimension.py\n", - "-rw-r--r-- 1 root root 21233 Oct 9 17:55 test_dle.py\n", - "-rw-r--r-- 1 root root 1157 Oct 9 17:55 test_docstrings.py\n", - "-rw-r--r-- 1 root root 26251 Oct 9 17:55 test_dse.py\n", - "-rw-r--r-- 1 root root 8612 Oct 9 17:55 test_gradient.py\n", - "-rw-r--r-- 1 root root 15229 Oct 9 17:55 test_interpolation.py\n", - "-rw-r--r-- 1 root root 31514 Oct 9 17:55 test_ir.py\n", - "-rw-r--r-- 1 root root 60563 Oct 9 17:55 test_mpi.py\n", - "-rw-r--r-- 1 root root 61542 Oct 9 17:55 test_operator.py\n", - "-rw-r--r-- 1 root root 12214 Oct 9 17:55 test_ops.py\n", - "-rw-r--r-- 1 root root 11252 Oct 9 17:55 test_pickle.py\n", - "-rw-r--r-- 1 root root 1815 Oct 9 17:55 test_resample.py\n", - "-rw-r--r-- 1 root root 1754 Oct 9 17:55 test_save.py\n", - "-rw-r--r-- 1 root root 5711 Oct 9 17:55 test_subdomains.py\n", - "-rw-r--r-- 1 root root 10526 Oct 9 17:55 test_symbol_caching.py\n", - "-rw-r--r-- 1 root root 1896 Oct 9 17:55 test_symbolic_coefficients.py\n", - "-rw-r--r-- 1 root root 3186 Oct 9 17:55 test_timestepping.py\n", - "-rw-r--r-- 1 root root 603 Oct 9 17:55 test_tools.py\n", - "-rw-r--r-- 1 root root 3302 Oct 9 17:55 test_tti.py\n", - "-rw-r--r-- 1 root root 8835 Oct 9 17:55 test_visitors.py\n", - "-rw-r--r-- 1 root root 21810 Oct 9 17:55 test_yask.py\n", - "1.0.65\n", + "total 536\n", + "-rw-r--r-- 1 root root 11521 Oct 30 05:30 conftest.py\n", + "-rw-r--r-- 1 root root 6006 Oct 30 05:30 test_adjoint.py\n", + "-rw-r--r-- 1 root root 13882 Oct 30 05:30 test_autotuner.py\n", + "-rw-r--r-- 1 root root 5902 Oct 30 05:30 test_builtins.py\n", + "-rw-r--r-- 1 root root 21885 Oct 30 05:30 test_caching.py\n", + "-rw-r--r-- 1 root root 9721 Oct 30 05:30 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Oct 30 05:30 test_constant.py\n", + "-rw-r--r-- 1 root root 54642 Oct 30 05:30 test_data.py\n", + "-rw-r--r-- 1 root root 481 Oct 30 05:30 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16506 Oct 30 05:30 test_derivatives.py\n", + "-rw-r--r-- 1 root root 1473 Oct 30 05:30 test_differentiable.py\n", + "-rw-r--r-- 1 root root 30846 Oct 30 05:30 test_dimension.py\n", + "-rw-r--r-- 1 root root 21233 Oct 30 05:30 test_dle.py\n", + "-rw-r--r-- 1 root root 1169 Oct 30 05:30 test_docstrings.py\n", + "-rw-r--r-- 1 root root 26928 Oct 30 05:30 test_dse.py\n", + "-rw-r--r-- 1 root root 8205 Oct 30 05:30 test_gradient.py\n", + "-rw-r--r-- 1 root root 15227 Oct 30 05:30 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31514 Oct 30 05:30 test_ir.py\n", + "-rw-r--r-- 1 root root 62102 Oct 30 05:30 test_mpi.py\n", + "-rw-r--r-- 1 root root 61208 Oct 30 05:30 test_operator.py\n", + "-rw-r--r-- 1 root root 13006 Oct 30 05:30 test_ops.py\n", + "-rw-r--r-- 1 root root 12228 Oct 30 05:30 test_pickle.py\n", + "-rw-r--r-- 1 root root 1809 Oct 30 05:30 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Oct 30 05:30 test_save.py\n", + "-rw-r--r-- 1 root root 5711 Oct 30 05:30 test_subdomains.py\n", + "-rw-r--r-- 1 root root 3320 Oct 30 05:30 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 3186 Oct 30 05:30 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Oct 30 05:30 test_tools.py\n", + "-rw-r--r-- 1 root root 3296 Oct 30 05:30 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Oct 30 05:30 test_visitors.py\n", + "-rw-r--r-- 1 root root 21800 Oct 30 05:30 test_yask.py\n", + "1.0.69\n", "\n", "content of devito tests log file after testing:\n", "============================= test session starts ==============================\n", "platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0\n", "rootdir: /devito, inifile: setup.cfg\n", "plugins: nbval-0.9.3, cov-2.7.1\n", - "collected 881 items / 2 skipped / 879 selected\n", + "collected 912 items / 2 skipped / 910 selected\n", "\n", "tests/test_adjoint.py .......................... [ 2%]\n", "tests/test_autotuner.py ..........s.... [ 4%]\n", - "tests/test_checkpointing.py ....... [ 5%]\n", - "tests/test_constant.py . [ 5%]\n", - "tests/test_data.py ..........................ssssssssssssss.ss.. [ 10%]\n", - "tests/test_dependency_bugs.py . [ 10%]\n", - "tests/test_derivatives.py .............................................. [ 16%]\n", - "........................................................................ [ 24%]\n", - "........................................................FF...F.......... [ 32%]\n", - "...... [ 33%]\n", - "tests/test_dimension.py ............................... [ 36%]\n", - "tests/test_dle.py ...................................................... [ 42%]\n", - "......................................... [ 47%]\n", - "tests/test_docstrings.py ............... [ 49%]\n", - "tests/test_dse.py ......x............................................... [ 55%]\n", - "......................s.... [ 58%]\n", - "tests/test_gradient.py .... [ 58%]\n", - "tests/test_interpolation.py ........................ [ 61%]\n", - "tests/test_ir.py ....................................................... [ 67%]\n", - "................ [ 69%]\n", - "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 75%]\n", - "s [ 75%]\n", - "tests/test_operator.py ................................................. [ 81%]\n", - "........................................................................ [ 89%]\n", - ".................. [ 91%]\n", - "tests/test_pickle.py ..............ss. [ 93%]\n", - "tests/test_resample.py . [ 93%]\n", - "tests/test_save.py .. [ 93%]\n", - "tests/test_subdomains.py ... [ 94%]\n", - "tests/test_symbol_caching.py ...................... [ 96%]\n", - "tests/test_symbolic_coefficients.py ..... [ 97%]\n", - "tests/test_timestepping.py ....... [ 97%]\n", + "tests/test_builtins.py ....s.s.s [ 5%]\n", + "tests/test_caching.py .................................. [ 9%]\n", + "tests/test_checkpointing.py ....... [ 9%]\n", + "tests/test_constant.py . [ 10%]\n", + "tests/test_data.py ..........................sssssssssssssss.ss.. [ 15%]\n", + "tests/test_dependency_bugs.py . [ 15%]\n", + "tests/test_derivatives.py .............................................. [ 20%]\n", + "........................................................................ [ 28%]\n", + ".........................................................F...F.......... [ 36%]\n", + "...... [ 36%]\n", + "tests/test_differentiable.py .. [ 36%]\n", + "tests/test_dimension.py ............................... [ 40%]\n", + "tests/test_dle.py ...................................................... [ 46%]\n", + "......................................... [ 50%]\n", + "tests/test_docstrings.py ................ [ 52%]\n", + "tests/test_dse.py ......x............................................... [ 58%]\n", + ".......................s.... [ 61%]\n", + "tests/test_gradient.py .... [ 61%]\n", + "tests/test_interpolation.py ........................ [ 64%]\n", + "tests/test_ir.py ....................................................... [ 70%]\n", + "................ [ 72%]\n", + "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 78%]\n", + "ss [ 78%]\n", + "tests/test_operator.py ................................................. [ 83%]\n", + "........................................................................ [ 91%]\n", + ".................. [ 93%]\n", + "tests/test_pickle.py .................ss. [ 95%]\n", + "tests/test_resample.py . [ 96%]\n", + "tests/test_save.py .. [ 96%]\n", + "tests/test_subdomains.py ... [ 96%]\n", + "tests/test_symbolic_coefficients.py .....F [ 97%]\n", + "tests/test_timestepping.py ....... [ 98%]\n", "tests/test_tools.py ..... [ 98%]\n", - "tests/test_tti.py .... [ 98%]\n", + "tests/test_tti.py .... [ 99%]\n", "tests/test_visitors.py ......... [100%]\n", "\n", "=================================== FAILURES ===================================\n", - "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-8] _____________________\n", + "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________\n", "\n", - "self = , so = 8, ndim = 1\n", + "self = , so = 12, ndim = 1\n", "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", "\n", " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", @@ -2591,7 +807,6 @@ " ('dxl', 'dxr', -1),\n", " ('dxr', 'dxl', -1)])\n", " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", - " clear_cache()\n", " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", " x = grid.dimensions[0]\n", " f = Function(name='f', grid=grid, space_order=so)\n", @@ -2619,19 +834,19 @@ " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", "E assert False\n", - "E + where False = ((1 - (0.3281994 / 0.3282032)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", + "E + where False = ((1 - (0.22231627 / 0.2223134)), 0, atol=1e-05)\n", + "E + where = np.isclose\n", "\n", - "tests/test_derivatives.py:397: AssertionError\n", + "tests/test_derivatives.py:394: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", "Operator `Kernel` run in 0.01 s\n", "Operator `Kernel` run in 0.01 s\n", "------------------------------ Captured log call -------------------------------\n", "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________\n", + "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________\n", "\n", - "self = , so = 12, ndim = 1\n", + "self = , so = 12, ndim = 2\n", "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", "\n", " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", @@ -2642,7 +857,6 @@ " ('dxl', 'dxr', -1),\n", " ('dxr', 'dxl', -1)])\n", " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", - " clear_cache()\n", " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", " x = grid.dimensions[0]\n", " f = Function(name='f', grid=grid, space_order=so)\n", @@ -2670,72 +884,87 @@ " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", "E assert False\n", - "E + where False = ((1 - (0.2223196 / 0.22231674)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", + "E + where False = ((1 - (11.3381815 / 11.338006)), 0, atol=1e-05)\n", + "E + where = np.isclose\n", "\n", - "tests/test_derivatives.py:397: AssertionError\n", + "tests/test_derivatives.py:394: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", "Operator `Kernel` run in 0.01 s\n", + "/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c: In function ‘Kernel’:\n", + "/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + " #pragma omp simd aligned(f,f_deriv,g,g_deriv:32)\n", + " \n", "Operator `Kernel` run in 0.01 s\n", "------------------------------ Captured log call -------------------------------\n", "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________\n", + "______________________ TestSC.test_function_coefficients _______________________\n", "\n", - "self = , so = 12, ndim = 2\n", - "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", + "self = \n", "\n", - " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", - " @pytest.mark.parametrize('ndim', [1, 2])\n", - " @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [\n", - " ('dx', 'dx', -1),\n", - " ('dx2', 'dx2', 1),\n", - " ('dxl', 'dxr', -1),\n", - " ('dxr', 'dxl', -1)])\n", - " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", - " clear_cache()\n", - " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", - " x = grid.dimensions[0]\n", - " f = Function(name='f', grid=grid, space_order=so)\n", - " f_deriv = Function(name='f_deriv', grid=grid, space_order=so)\n", - " g = Function(name='g', grid=grid, space_order=so)\n", - " g_deriv = Function(name='g_deriv', grid=grid, space_order=so)\n", + " def test_function_coefficients(self):\n", + " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", + " so = 2\n", + " grid = Grid(shape=(4, 4))\n", + " f0 = TimeFunction(name='f0', grid=grid, space_order=so, coefficients='symbolic')\n", + " f1 = TimeFunction(name='f1', grid=grid, space_order=so)\n", + " x, y = grid.dimensions\n", " \n", - " # Fill f and g with smooth cos/sin\n", - " Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply()\n", - " # Check symbolic expression are expected ones for the adjoint .T\n", - " deriv = getattr(f, derivative)\n", - " expected = adjoint_coeff * getattr(f, adjoint_name).evaluate\n", - " assert deriv.T.evaluate == expected\n", + " s = Dimension(name='s')\n", + " ncoeffs = so+1\n", " \n", - " # Compute numerical derivatives and verify dot test\n", - " # i.e = \n", + " wshape = list(grid.shape)\n", + " wshape.append(ncoeffs)\n", + " wshape = as_tuple(wshape)\n", " \n", - " eq_f = Eq(f_deriv, deriv)\n", - " eq_g = Eq(g_deriv, getattr(g, derivative).T)\n", + " wdims = list(grid.dimensions)\n", + " wdims.append(s)\n", + " wdims = as_tuple(wdims)\n", " \n", - " op = Operator([eq_f, eq_g])\n", - " op()\n", + " w = Function(name='w', dimensions=wdims, shape=wshape)\n", + " w.data[:, :, 0] = -0.5/grid.spacing[0]\n", + " w.data[:, :, 1] = 0.0\n", + " w.data[:, :, 2] = 0.5/grid.spacing[0]\n", " \n", - " a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1))\n", - " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", - "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", - "E assert False\n", - "E + where False = ((1 - (11.338287 / 11.338157)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", + " f_x_coeffs = Coefficient(1, f0, x, w)\n", + " \n", + " subs = Substitutions(f_x_coeffs)\n", + " \n", + " eq0 = Eq(f0.dt + f0.dx, 1, coefficients=subs)\n", + " eq1 = Eq(f1.dt + f1.dx, 1)\n", + " \n", + " stencil0 = solve(eq0.evaluate, f0.forward)\n", + " stencil1 = solve(eq1.evaluate, f1.forward)\n", + " \n", + " op0 = Operator(Eq(f0.forward, stencil0))\n", + " op1 = Operator(Eq(f1.forward, stencil1))\n", + " \n", + " op0(time_m=0, time_M=5, dt=1.0)\n", + " op1(time_m=0, time_M=5, dt=1.0)\n", + " \n", + "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", + "E assert Data(False)\n", + "E + where Data(False) = (Data([[[ True, True, True, True],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...True],\\n [ True, True, True, True],\\n [ True, True, True, True],\\n [ True, True, True, True]]]))\n", + "E + where = np.all\n", + "E + and Data([[[ True, True, True, True],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...True],\\n [ True, True, True, True],\\n [ True, True, True, True],\\n [ True, True, True, True]]]) = ((Data([[[-11.4375 , -11.4375 , -11.4375 , -11.4375 ],\\n [ 49.59375, 49.59375, 49.59375, 49.59375],\\n [ -6.... [-24.25 , -24.25 , -24.25 , -24.25 ],\\n [ -1.9375 , -1.9375 , -1.9375 , -1.9375 ]]], dtype=float32) - Data([[[-11.437502 , -11.437502 , -11.437502 , -11.437502 ],\\n [ 49.59374 , 49.59374 , 49.59374 , 49.59374 ....249996 , -24.249996 , -24.249996 ],\\n [ -1.9375012, -1.9375012, -1.9375012, -1.9375012]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", + "E + where = np.isclose\n", "\n", - "tests/test_derivatives.py:397: AssertionError\n", + "tests/test_symbolic_coefficients.py:96: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", + "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c: In function ‘Kernel’:\n", + "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + " #pragma omp simd aligned(f0,w:32)\n", + " \n", "Operator `Kernel` run in 0.01 s\n", - "/tmp/devito-jitcache-uid0/d9e038c50e679daec98f5e80704a19e4aaf8e62c.c: In function ‘Kernel’:\n", - "/tmp/devito-jitcache-uid0/d9e038c50e679daec98f5e80704a19e4aaf8e62c.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", - " #pragma omp simd aligned(f,f_deriv,g,g_deriv:32)\n", + "/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c: In function ‘Kernel’:\n", + "/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + " #pragma omp simd aligned(f1:32)\n", " \n", "Operator `Kernel` run in 0.01 s\n", "------------------------------ Captured log call -------------------------------\n", "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "======= 3 failed, 802 passed, 77 skipped, 1 xfailed in 885.98s (0:14:45) =======\n" + "======= 3 failed, 828 passed, 82 skipped, 1 xfailed in 830.53s (0:13:50) =======\n" ] } ], @@ -2756,79 +985,106 @@ "!cat $fwi01_log_file" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "###### Use the ACR created in previous notebook or docker hub to push your image" + ] + }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ + { + "data": { + "text/plain": [ + "'az acr login --name fwi01acr'" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + }, { "name": "stdout", "output_type": "stream", "text": [ - "WARNING! Using --password via the CLI is insecure. Use --password-stdin.\n", - "WARNING! Your password will be stored unencrypted in /root/.docker/config.json.\n", - "Configure a credential helper to remove this warning. See\n", - "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\n", - "\n", - "Login Succeeded\n" + "Login Succeeded\r\n", + "WARNING! Your password will be stored unencrypted in /root/.docker/config.json.\r\n", + "Configure a credential helper to remove this warning. See\r\n", + "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", + "\r\n", + "\u001b[0m" ] } ], "source": [ - "docker_pwd = os.getenv('DOCKER_PWD')\n", - "docker_login = os.getenv('DOCKER_LOGIN')\n", - "!docker login -u=$docker_login -p=$docker_pwd" + "# docker_pwd = os.getenv('DOCKER_PWD')\n", + "# docker_login = os.getenv('DOCKER_LOGIN')\n", + "# !docker login -u=$docker_login -p=$docker_pwd\n", + "# !docker push {docker_image_name}\n", + "\n", + "%dotenv $dotenv_file_path\n", + "cli_command='az acr login --name '+os.getenv('ACR_NAME')\n", + "# print cli command\n", + "cli_command\n", + "\n", + "# run cli command\n", + "cli_command = cli_command+' --username '+os.getenv('ACR_USERNAME') + ' --password ' + os.getenv('ACR_PASSWORD')\n", + "! $cli_command" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cli_command='docker push '+docker_image_name\n", + "cli_command" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The push refers to repository [docker.io/georgedockeraccount/fwi01_azureml]\n", + "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", "\n", - "\u001b[1B9886dd1f: Preparing \n", - "\u001b[1B99799fa2: Preparing \n", - "\u001b[1Bec869ef0: Preparing \n", - "\u001b[1B55141871: Preparing \n", + "\u001b[1B8b071694: Preparing \n", + "\u001b[1B5b417f99: Preparing \n", + "\u001b[1Bd24aa1b4: Preparing \n", + "\u001b[1Be41d536b: Preparing \n", "\u001b[1Bf8fc4c9a: Preparing \n", - "\u001b[1Bba47210e: Preparing \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[6B99799fa2: Pushing 1.134GB/3.024GB\u001b[5A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 552.1MB/3.024GB\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 823.1MB/3.024GB\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 861.2MB/2.81GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 978.4MB/3.024GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[6B99799fa2: Pushing 2.206GB/3.024GB\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 1.825GB/2.81GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 1.896GB/2.81GB\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[7B9886dd1f: Pushed 3.099GB/3.024GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 2.445GB/2.81GB\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 2.798GB/3.024GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[Ksdk.v1.0.65: digest: sha256:3639f4b469e0fb59c8531022448f240bff49d9d06c8d9e788656d5dd1bfbb07b size: 1800\n" + "\u001b[1Bba47210e: Preparing \n", + "\u001b[2Bba47210e: Layer already exists \u001b[3A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[Ksdk.v1.0.69: digest: sha256:9acb10b41bf2e750519b303c365870f67a64c059b90d8868ff139e7d54446948 size: 1800\n" ] } ], "source": [ - "# %%bash\n", - "!docker push {docker_image_name}" + "! $cli_command" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [ { diff --git a/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb index f4c941c5..59843f6e 100755 --- a/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -71,13 +71,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.65\n" + "Azure ML SDK Version: 1.0.69\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1060-azure-x86_64-with-debian-10.0'" + "'Linux-4.15.0-1061-azure-x86_64-with-debian-10.0'" ] }, "execution_count": 3, @@ -477,7 +477,17 @@ { "data": { "text/plain": [ - "'georgedockeraccount/fwi01_azureml:sdk.v1.0.65'" + "'fwi01_azureml:sdk.v1.0.69'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" ] }, "execution_count": 9, @@ -486,13 +496,17 @@ } ], "source": [ - "docker_image_name = os.getenv('DOCKER_LOGIN') + '/' + os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", + "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", + "docker_image_name = os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", "\n", "image_version = os.getenv('EXPERIMENTATION_IMAGE_VERSION')\n", "if image_version!=\"\":\n", " docker_image_name = docker_image_name +':'+ image_version\n", + "\n", + "full_docker_image_name = docker_repo_name + '/' + docker_image_name\n", " \n", - "docker_image_name" + "docker_image_name\n", + "full_docker_image_name" ] }, { @@ -510,7 +524,7 @@ { "data": { "text/plain": [ - "'docker run -i --rm --name fwi01_azureml_container02 georgedockeraccount/fwi01_azureml:sdk.v1.0.65 /bin/bash -c \"which python\" '" + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69 /bin/bash -c \"which python\" '" ] }, "execution_count": 10, @@ -530,7 +544,7 @@ ], "source": [ "get_Python_path_command='docker run -i --rm --name fwi01_azureml_container02 '+ \\\n", - "docker_image_name + \\\n", + "full_docker_image_name + \\\n", "' /bin/bash -c \"which python\" '\n", "get_Python_path_command\n", "\n", @@ -610,9 +624,13 @@ "' > '+ crt_dir + output_dir_AzureML_tracked + '/02_rtm_output.log' \n", "# + \\\n", "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/papermill_cli.log'\n", - "print('Running papermill from cli on 02_rtm.ipynb----BEGIN-----:') \n", - "print(cli_command); print('\\n');os.system(cli_command)\n", - "print('Running papermill from cli on 02_rtm.ipynb----END-----:\\n\\n') \n", + "\n", + "# FIXME - activate right conda env for running papermill from cli\n", + "activate_right_conda_env_fixed = False\n", + "if activate_right_conda_env_fixed:\n", + " print('Running papermill from cli on 02_rtm.ipynb----BEGIN-----:') \n", + " print(cli_command); print('\\n');os.system(cli_command)\n", + " print('Running papermill from cli on 02_rtm.ipynb----END-----:\\n\\n') \n", "\n", "\n", "print('Running papermill from Python API on 03_fwi.ipynb----BEGIN-----:') \n", @@ -802,7 +820,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "adce141a753841b08c9861dd30246ec1", + "model_id": "2066a8170a4e4c11ada50b453920959b", "version_major": 2, "version_minor": 0 }, @@ -822,9 +840,9 @@ "\n", "# you can also point to an image in a private ACR\n", "image_registry_details = ContainerRegistry()\n", - "image_registry_details.address = \"myregistry.azurecr.io\"\n", - "image_registry_details.username = \"username\"\n", - "image_registry_details.password = \"password\"\n", + "image_registry_details.address = docker_repo_name\n", + "image_registry_details.username = os.getenv('ACR_USERNAME')\n", + "image_registry_details.password = os.getenv('ACR_PASSWORD') \n", "\n", "# don't let the system build a new conda environment\n", "user_managed_dependencies = True\n", @@ -845,7 +863,7 @@ " use_docker=True,\n", " custom_docker_image=image_name,\n", " # uncomment below line to use your private ACR\n", - " #image_registry_details=image_registry_details, \n", + " image_registry_details=image_registry_details, \n", " user_managed=user_managed_dependencies,\n", " distributed_training=None,\n", " node_count=1\n", @@ -867,9 +885,34 @@ "cell_type": "code", "execution_count": 18, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'runId= 020_AzureMLEstimator_1572793621_e35441c0'" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'experimentation baseImage: fwi01_azureml:sdk.v1.0.69'" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# run.get_details()" + "run_details = run.get_details()\n", + "\n", + "# print some details of job run\n", + "'runId= {}'.format(run_details['runId'])\n", + "'experimentation baseImage: {}'.format(run_details['runDefinition']['environment']['docker']['baseImage'])" ] }, { diff --git a/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log b/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log index 01597fe0..22b30a9b 100644 --- a/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log +++ b/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log @@ -2,48 +2,50 @@ platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 rootdir: /devito, inifile: setup.cfg plugins: nbval-0.9.3, cov-2.7.1 -collected 881 items / 2 skipped / 879 selected +collected 912 items / 2 skipped / 910 selected tests/test_adjoint.py .......................... [ 2%] tests/test_autotuner.py ..........s.... [ 4%] -tests/test_checkpointing.py ....... [ 5%] -tests/test_constant.py . [ 5%] -tests/test_data.py ..........................ssssssssssssss.ss.. [ 10%] -tests/test_dependency_bugs.py . [ 10%] -tests/test_derivatives.py .............................................. [ 16%] -........................................................................ [ 24%] -........................................................FF...F.......... [ 32%] -...... [ 33%] -tests/test_dimension.py ............................... [ 36%] -tests/test_dle.py ...................................................... [ 42%] -......................................... [ 47%] -tests/test_docstrings.py ............... [ 49%] -tests/test_dse.py ......x............................................... [ 55%] -......................s.... [ 58%] -tests/test_gradient.py .... [ 58%] -tests/test_interpolation.py ........................ [ 61%] -tests/test_ir.py ....................................................... [ 67%] -................ [ 69%] -tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 75%] -s [ 75%] -tests/test_operator.py ................................................. [ 81%] -........................................................................ [ 89%] -.................. [ 91%] -tests/test_pickle.py ..............ss. [ 93%] -tests/test_resample.py . [ 93%] -tests/test_save.py .. [ 93%] -tests/test_subdomains.py ... [ 94%] -tests/test_symbol_caching.py ...................... [ 96%] -tests/test_symbolic_coefficients.py ..... [ 97%] -tests/test_timestepping.py ....... [ 97%] +tests/test_builtins.py ....s.s.s [ 5%] +tests/test_caching.py .................................. [ 9%] +tests/test_checkpointing.py ....... [ 9%] +tests/test_constant.py . [ 10%] +tests/test_data.py ..........................sssssssssssssss.ss.. [ 15%] +tests/test_dependency_bugs.py . [ 15%] +tests/test_derivatives.py .............................................. [ 20%] +........................................................................ [ 28%] +.........................................................F...F.......... [ 36%] +...... [ 36%] +tests/test_differentiable.py .. [ 36%] +tests/test_dimension.py ............................... [ 40%] +tests/test_dle.py ...................................................... [ 46%] +......................................... [ 50%] +tests/test_docstrings.py ................ [ 52%] +tests/test_dse.py ......x............................................... [ 58%] +.......................s.... [ 61%] +tests/test_gradient.py .... [ 61%] +tests/test_interpolation.py ........................ [ 64%] +tests/test_ir.py ....................................................... [ 70%] +................ [ 72%] +tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 78%] +ss [ 78%] +tests/test_operator.py ................................................. [ 83%] +........................................................................ [ 91%] +.................. [ 93%] +tests/test_pickle.py .................ss. [ 95%] +tests/test_resample.py . [ 96%] +tests/test_save.py .. [ 96%] +tests/test_subdomains.py ... [ 96%] +tests/test_symbolic_coefficients.py .....F [ 97%] +tests/test_timestepping.py ....... [ 98%] tests/test_tools.py ..... [ 98%] -tests/test_tti.py .... [ 98%] +tests/test_tti.py .... [ 99%] tests/test_visitors.py ......... [100%] =================================== FAILURES =================================== -____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-8] _____________________ +____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________ -self = , so = 8, ndim = 1 +self = , so = 12, ndim = 1 derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 @pytest.mark.parametrize('so', [2, 4, 8, 12]) @@ -54,7 +56,6 @@ derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 ('dxl', 'dxr', -1), ('dxr', 'dxl', -1)]) def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): - clear_cache() grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) x = grid.dimensions[0] f = Function(name='f', grid=grid, space_order=so) @@ -82,19 +83,19 @@ derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) > assert np.isclose(1 - a/b, 0, atol=1e-5) E assert False -E + where False = ((1 - (0.3281994 / 0.3282032)), 0, atol=1e-05) -E + where = np.isclose +E + where False = ((1 - (0.22231627 / 0.2223134)), 0, atol=1e-05) +E + where = np.isclose -tests/test_derivatives.py:397: AssertionError +tests/test_derivatives.py:394: AssertionError ----------------------------- Captured stderr call ----------------------------- Operator `Kernel` run in 0.01 s Operator `Kernel` run in 0.01 s ------------------------------ Captured log call ------------------------------- INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________ +____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________ -self = , so = 12, ndim = 1 +self = , so = 12, ndim = 2 derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 @pytest.mark.parametrize('so', [2, 4, 8, 12]) @@ -105,7 +106,6 @@ derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 ('dxl', 'dxr', -1), ('dxr', 'dxl', -1)]) def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): - clear_cache() grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) x = grid.dimensions[0] f = Function(name='f', grid=grid, space_order=so) @@ -133,69 +133,84 @@ derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) > assert np.isclose(1 - a/b, 0, atol=1e-5) E assert False -E + where False = ((1 - (0.2223196 / 0.22231674)), 0, atol=1e-05) -E + where = np.isclose +E + where False = ((1 - (11.3381815 / 11.338006)), 0, atol=1e-05) +E + where = np.isclose -tests/test_derivatives.py:397: AssertionError +tests/test_derivatives.py:394: AssertionError ----------------------------- Captured stderr call ----------------------------- Operator `Kernel` run in 0.01 s +/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c: In function ‘Kernel’: +/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas] + #pragma omp simd aligned(f,f_deriv,g,g_deriv:32) + Operator `Kernel` run in 0.01 s ------------------------------ Captured log call ------------------------------- INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________ +______________________ TestSC.test_function_coefficients _______________________ -self = , so = 12, ndim = 2 -derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 +self = - @pytest.mark.parametrize('so', [2, 4, 8, 12]) - @pytest.mark.parametrize('ndim', [1, 2]) - @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [ - ('dx', 'dx', -1), - ('dx2', 'dx2', 1), - ('dxl', 'dxr', -1), - ('dxr', 'dxl', -1)]) - def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): - clear_cache() - grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) - x = grid.dimensions[0] - f = Function(name='f', grid=grid, space_order=so) - f_deriv = Function(name='f_deriv', grid=grid, space_order=so) - g = Function(name='g', grid=grid, space_order=so) - g_deriv = Function(name='g_deriv', grid=grid, space_order=so) + def test_function_coefficients(self): + """Test that custom function coefficients return the expected result""" + so = 2 + grid = Grid(shape=(4, 4)) + f0 = TimeFunction(name='f0', grid=grid, space_order=so, coefficients='symbolic') + f1 = TimeFunction(name='f1', grid=grid, space_order=so) + x, y = grid.dimensions - # Fill f and g with smooth cos/sin - Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply() - # Check symbolic expression are expected ones for the adjoint .T - deriv = getattr(f, derivative) - expected = adjoint_coeff * getattr(f, adjoint_name).evaluate - assert deriv.T.evaluate == expected + s = Dimension(name='s') + ncoeffs = so+1 - # Compute numerical derivatives and verify dot test - # i.e = + wshape = list(grid.shape) + wshape.append(ncoeffs) + wshape = as_tuple(wshape) - eq_f = Eq(f_deriv, deriv) - eq_g = Eq(g_deriv, getattr(g, derivative).T) + wdims = list(grid.dimensions) + wdims.append(s) + wdims = as_tuple(wdims) - op = Operator([eq_f, eq_g]) - op() + w = Function(name='w', dimensions=wdims, shape=wshape) + w.data[:, :, 0] = -0.5/grid.spacing[0] + w.data[:, :, 1] = 0.0 + w.data[:, :, 2] = 0.5/grid.spacing[0] - a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1)) - b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) -> assert np.isclose(1 - a/b, 0, atol=1e-5) -E assert False -E + where False = ((1 - (11.338287 / 11.338157)), 0, atol=1e-05) -E + where = np.isclose + f_x_coeffs = Coefficient(1, f0, x, w) + + subs = Substitutions(f_x_coeffs) + + eq0 = Eq(f0.dt + f0.dx, 1, coefficients=subs) + eq1 = Eq(f1.dt + f1.dx, 1) + + stencil0 = solve(eq0.evaluate, f0.forward) + stencil1 = solve(eq1.evaluate, f1.forward) + + op0 = Operator(Eq(f0.forward, stencil0)) + op1 = Operator(Eq(f1.forward, stencil1)) + + op0(time_m=0, time_M=5, dt=1.0) + op1(time_m=0, time_M=5, dt=1.0) + +> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0)) +E assert Data(False) +E + where Data(False) = (Data([[[ True, True, True, True],\n [False, False, False, False],\n [ True, True, True, True],\n ...True],\n [ True, True, True, True],\n [ True, True, True, True],\n [ True, True, True, True]]])) +E + where = np.all +E + and Data([[[ True, True, True, True],\n [False, False, False, False],\n [ True, True, True, True],\n ...True],\n [ True, True, True, True],\n [ True, True, True, True],\n [ True, True, True, True]]]) = ((Data([[[-11.4375 , -11.4375 , -11.4375 , -11.4375 ],\n [ 49.59375, 49.59375, 49.59375, 49.59375],\n [ -6.... [-24.25 , -24.25 , -24.25 , -24.25 ],\n [ -1.9375 , -1.9375 , -1.9375 , -1.9375 ]]], dtype=float32) - Data([[[-11.437502 , -11.437502 , -11.437502 , -11.437502 ],\n [ 49.59374 , 49.59374 , 49.59374 , 49.59374 ....249996 , -24.249996 , -24.249996 ],\n [ -1.9375012, -1.9375012, -1.9375012, -1.9375012]]],\n dtype=float32)), 0.0, atol=1e-05, rtol=0) +E + where = np.isclose -tests/test_derivatives.py:397: AssertionError +tests/test_symbolic_coefficients.py:96: AssertionError ----------------------------- Captured stderr call ----------------------------- +/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c: In function ‘Kernel’: +/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas] + #pragma omp simd aligned(f0,w:32) + Operator `Kernel` run in 0.01 s -/tmp/devito-jitcache-uid0/d9e038c50e679daec98f5e80704a19e4aaf8e62c.c: In function ‘Kernel’: -/tmp/devito-jitcache-uid0/d9e038c50e679daec98f5e80704a19e4aaf8e62c.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas] - #pragma omp simd aligned(f,f_deriv,g,g_deriv:32) +/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c: In function ‘Kernel’: +/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas] + #pragma omp simd aligned(f1:32) Operator `Kernel` run in 0.01 s ------------------------------ Captured log call ------------------------------- INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -======= 3 failed, 802 passed, 77 skipped, 1 xfailed in 885.98s (0:14:45) ======= +======= 3 failed, 828 passed, 82 skipped, 1 xfailed in 830.53s (0:13:50) ======= From 442a6555dd861c3ebf7df8fd1600e12921f9f5f3 Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Thu, 7 Nov 2019 18:48:20 +0000 Subject: [PATCH 070/207] Vapaunic/metrics (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module --- .vscode/settings.json | 6 - .../cv_lib/event_handlers/logging_handlers.py | 14 +- .../segmentation/dutchf3/metrics/__init__.py | 99 --- .../segmentation/dutchf3/metrics/apex.py | 112 --- cv_lib/cv_lib/segmentation/dutchf3/utils.py | 2 - cv_lib/cv_lib/segmentation/metrics.py | 106 +++ cv_lib/tests/test_metrics.py | 140 ++++ environment/anaconda/local/environment.yml | 2 +- ..._block_training_and_evaluation_local.ipynb | 729 ++++++++++++++++++ .../dutchf3_patch/distributed/train.py | 99 ++- .../dutchf3_patch/local/train.py | 137 ++-- .../dutchf3_section/local/train.py | 117 ++- .../interpretation/dutchf3_voxel/train.py | 178 ++--- .../local/configs/seresnet_unet.yaml | 6 +- .../interpretation/penobscot/local/test.py | 111 ++- .../interpretation/penobscot/local/train.py | 105 +-- .../penobscot/metrics.py | 42 +- 17 files changed, 1357 insertions(+), 648 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py delete mode 100644 cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py create mode 100644 cv_lib/cv_lib/segmentation/metrics.py create mode 100644 cv_lib/tests/test_metrics.py create mode 100644 examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 85fbc9f8..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "python.formatting.provider": "black", - "python.linting.enabled": true, - "python.linting.flake8Enabled": true, - "python.linting.pylintEnabled": false, -} \ No newline at end of file diff --git a/cv_lib/cv_lib/event_handlers/logging_handlers.py b/cv_lib/cv_lib/event_handlers/logging_handlers.py index 29ea6e76..a1045de8 100644 --- a/cv_lib/cv_lib/event_handlers/logging_handlers.py +++ b/cv_lib/cv_lib/event_handlers/logging_handlers.py @@ -5,6 +5,8 @@ import logging.config from toolz import curry +import numpy as np +np.set_printoptions(precision=3) @curry def log_training_output(engine, log_interval=100): @@ -23,7 +25,7 @@ def log_lr(optimizer, engine): logger.info(f"lr - {lr}") -_DEFAULT_METRICS = {"accuracy": "Avg accuracy :", "nll": "Avg loss :"} +_DEFAULT_METRICS = {"pixacc": "Avg accuracy :", "nll": "Avg loss :"} @curry @@ -38,6 +40,16 @@ def log_metrics(log_msg, engine, metrics_dict=_DEFAULT_METRICS): + metrics_msg ) +@curry +def log_class_metrics(log_msg, engine, metrics_dict): + logger = logging.getLogger(__name__) + metrics = engine.state.metrics + metrics_msg = "\n".join(f"{metrics_dict[k]} {metrics[k].numpy()}" for k in metrics_dict) + logger.info( + f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}]\n" + + metrics_msg + ) + class Evaluator: def __init__(self, evaluation_engine, data_loader): diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py deleted file mode 100644 index 3c4d5540..00000000 --- a/cv_lib/cv_lib/segmentation/dutchf3/metrics/__init__.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -import numpy as np -import warnings - -import numpy as np -import torch -from ignite.metrics import Metric - - -def _fast_hist(label_true, label_pred, n_class): - mask = (label_true >= 0) & (label_true < n_class) - hist = np.bincount( - n_class * label_true[mask].astype(int) + label_pred[mask], - minlength=n_class ** 2, - ).reshape(n_class, n_class) - return hist - - -def _torch_hist(label_true, label_pred, n_class): - """Calculates the confusion matrix for the labels - - Args: - label_true ([type]): [description] - label_pred ([type]): [description] - n_class ([type]): [description] - - Returns: - [type]: [description] - """ - # TODO Add exceptions - assert len(label_true.shape) == 1, "Labels need to be 1D" - assert len(label_pred.shape) == 1, "Predictions need to be 1D" - mask = (label_true >= 0) & (label_true < n_class) - hist = torch.bincount( - n_class * label_true[mask] + label_pred[mask], minlength=n_class ** 2 - ).reshape(n_class, n_class) - return hist - - -class ConfusionMatrix(Metric): - def __init__(self, num_classes, device, output_transform=lambda x: x): - self._num_classes = num_classes - self._device = device - super(ConfusionMatrix, self).__init__(output_transform=output_transform) - - def reset(self): - self._confusion_matrix = torch.zeros( - (self._num_classes, self._num_classes), dtype=torch.long - ).to(self._device) - - def update(self, output): - y_pred, y = output - # TODO: Make assertion exception - assert y.shape == y_pred.max(1)[1].squeeze().shape, "Shape not the same" - self._confusion_matrix += _torch_hist( - torch.flatten(y), - torch.flatten(y_pred.max(1)[1].squeeze()), # Get the maximum index - self._num_classes, - ) - - def compute(self): - return self._confusion_matrix.cpu().numpy() - - -class MeanIoU(ConfusionMatrix): - def compute(self): - hist = self._confusion_matrix.cpu().numpy() - iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) - mean_iu = np.nanmean(iu) - return mean_iu - - -class PixelwiseAccuracy(ConfusionMatrix): - def compute(self): - hist = self._confusion_matrix.cpu().numpy() - acc = np.diag(hist).sum() / hist.sum() - return acc - - -class FrequencyWeightedIoU(ConfusionMatrix): - def compute(self): - hist = self._confusion_matrix.cpu().numpy() - iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) - freq = ( - hist.sum(axis=1) / hist.sum() - ) # fraction of the pixels that come from each class - fwiou = (freq[freq > 0] * iu[freq > 0]).sum() - return fwiou - - -class MeanClassAccuracy(ConfusionMatrix): - def compute(self): - hist = self._confusion_matrix.cpu().numpy() - acc = np.diag(hist).sum() / hist.sum() - acc_cls = np.diag(hist) / hist.sum(axis=1) - mean_acc_cls = np.nanmean(acc_cls) - return mean_acc_cls diff --git a/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py b/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py deleted file mode 100644 index 400bacca..00000000 --- a/cv_lib/cv_lib/segmentation/dutchf3/metrics/apex.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -import numpy as np -import torch -import torch.distributed as dist -from cv_lib.segmentation.dutchf3 import metrics -from ignite.exceptions import NotComputableError -from ignite.metrics.metric import Metric - - -@torch.no_grad() -def reduce_tensor(tensor, world_size): - rt = tensor.clone() - dist.all_reduce(rt, op=dist.reduce_op.SUM) - rt /= world_size - return rt - - -@torch.no_grad() -def sum_reduce_tensor(tensor): - rt = tensor.clone() - dist.all_reduce(rt, op=dist.reduce_op.SUM) - return rt - - -@torch.no_grad() -def gather_tensor(tensor, world_size): - gather_t = [torch.ones_like(tensor).cuda() for _ in range(dist.get_world_size())] - dist.all_gather(gather_t, tensor) - return gather_t - - -class AverageMetric(Metric): - def __init__(self, world_size, batch_size, output_transform=lambda x: x): - super(AverageMetric, self).__init__(output_transform=output_transform) - self._world_size = world_size - self._batch_size = batch_size - self._metric_name = "Metric" - - def reset(self): - self._sum = 0 - self._num_examples = 0 - - @torch.no_grad() - def update(self, output): - reduced_metric = reduce_tensor(output, self._world_size) - self._sum += reduced_metric * self._batch_size - self._num_examples += self._batch_size - - @torch.no_grad() - def compute(self): - if self._num_examples == 0: - raise NotComputableError( - f"{self._metric_name} must have at least one example before it can be computed." - ) - return self._sum / self._num_examples - - -class LossMetric(AverageMetric): - def __init__(self, loss_fn, world_size, batch_size, output_transform=lambda x: x): - super(LossMetric, self).__init__( - world_size, batch_size, output_transform=output_transform - ) - self._loss_fn = loss_fn - self._metric_name = "Loss" - - def update(self, output): - pred, y = output - loss = self._loss_fn(pred, y) - super().update(loss) - - -class ConfusionMatrix(metrics.ConfusionMatrix): - def compute(self): - reduced_metric = sum_reduce_tensor(self._confusion_matrix) - return reduced_metric.cpu().numpy() - - -class PixelwiseAccuracy(ConfusionMatrix): - def compute(self): - hist = super(PixelwiseAccuracy, self).compute() - acc = np.diag(hist).sum() / hist.sum() - return acc - - -class MeanIoU(ConfusionMatrix): - def compute(self): - hist = super(MeanIoU, self).compute() - iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) - mean_iu = np.nanmean(iu) - return mean_iu - - -class FrequencyWeightedIoU(ConfusionMatrix): - def compute(self): - hist = super(FrequencyWeightedIoU, self).compute() - iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) - freq = ( - hist.sum(axis=1) / hist.sum() - ) # fraction of the pixels that come from each class - fwiou = (freq[freq > 0] * iu[freq > 0]).sum() - return fwiou - - -class MeanClassAccuracy(ConfusionMatrix): - def compute(self): - hist = super(MeanClassAccuracy, self).compute() - acc = np.diag(hist).sum() / hist.sum() - acc_cls = np.diag(hist) / hist.sum(axis=1) - mean_acc_cls = np.nanmean(acc_cls) - return mean_acc_cls diff --git a/cv_lib/cv_lib/segmentation/dutchf3/utils.py b/cv_lib/cv_lib/segmentation/dutchf3/utils.py index 1d509b95..adad1e97 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/utils.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/utils.py @@ -3,7 +3,6 @@ import numpy as np import torch -import torchvision.utils as vutils from git import Repo from datetime import datetime import os @@ -23,7 +22,6 @@ def np_to_tb(array): array = np.expand_dims(array, axis=0) array = torch.from_numpy(array) - # array = vutils.make_grid(array, normalize=True, scale_each=True) return array diff --git a/cv_lib/cv_lib/segmentation/metrics.py b/cv_lib/cv_lib/segmentation/metrics.py new file mode 100644 index 00000000..02230fa9 --- /dev/null +++ b/cv_lib/cv_lib/segmentation/metrics.py @@ -0,0 +1,106 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import torch +import ignite + + +def pixelwise_accuracy(num_classes, output_transform=lambda x: x, device=None): + """Calculates class accuracy + + Args: + num_classes (int): number of classes + output_transform (callable, optional): a callable that is used to transform the + output into the form expected by the metric. + + Returns: + MetricsLambda + + """ + cm = ignite.metrics.ConfusionMatrix( + num_classes=num_classes, output_transform=output_transform, device=device + ) + # Increase floating point precision and pass to CPU + cm = cm.type(torch.DoubleTensor) + + pix_cls = ignite.metrics.confusion_matrix.cmAccuracy(cm) + + return pix_cls + + +def class_accuracy(num_classes, output_transform=lambda x: x, device=None): + """Calculates class accuracy + + Args: + num_classes (int): number of classes + output_transform (callable, optional): a callable that is used to transform the + output into the form expected by the metric. + + Returns: + MetricsLambda + + """ + cm = ignite.metrics.ConfusionMatrix( + num_classes=num_classes, output_transform=output_transform, device=device + ) + # Increase floating point precision and pass to CPU + cm = cm.type(torch.DoubleTensor) + + acc_cls = cm.diag() / (cm.sum(dim=1) + 1e-15) + + return acc_cls + + +def mean_class_accuracy(num_classes, output_transform=lambda x: x, device=None): + """Calculates mean class accuracy + + Args: + num_classes (int): number of classes + output_transform (callable, optional): a callable that is used to transform the + output into the form expected by the metric. + + Returns: + MetricsLambda + + """ + return class_accuracy( + num_classes=num_classes, output_transform=output_transform, device=device + ).mean() + + +def class_iou( + num_classes, output_transform=lambda x: x, device=None, ignore_index=None +): + """Calculates per-class intersection-over-union + + Args: + num_classes (int): number of classes + output_transform (callable, optional): a callable that is used to transform the + output into the form expected by the metric. + + Returns: + MetricsLambda + + """ + cm = ignite.metrics.ConfusionMatrix( + num_classes=num_classes, output_transform=output_transform, device=device + ) + return ignite.metrics.IoU(cm, ignore_index=ignore_index) + + +def mean_iou(num_classes, output_transform=lambda x: x, device=None, ignore_index=None): + """Calculates mean intersection-over-union + + Args: + num_classes (int): number of classes + output_transform (callable, optional): a callable that is used to transform the + output into the form expected by the metric. + + Returns: + MetricsLambda + + """ + cm = ignite.metrics.ConfusionMatrix( + num_classes=num_classes, output_transform=output_transform, device=device + ) + return ignite.metrics.mIoU(cm, ignore_index=ignore_index) diff --git a/cv_lib/tests/test_metrics.py b/cv_lib/tests/test_metrics.py new file mode 100644 index 00000000..83cf57cf --- /dev/null +++ b/cv_lib/tests/test_metrics.py @@ -0,0 +1,140 @@ +import torch +import numpy as np +from pytest import approx + +from ignite.metrics import ConfusionMatrix, MetricsLambda + +from cv_lib.segmentation.metrics import class_accuracy, mean_class_accuracy + + +# source repo: +# https://github.com/pytorch/ignite/blob/master/tests/ignite/metrics/test_confusion_matrix.py +def _get_y_true_y_pred(): + # Generate an image with labels 0 (background), 1, 2 + # 3 classes: + y_true = np.zeros((30, 30), dtype=np.int) + y_true[1:11, 1:11] = 1 + y_true[15:25, 15:25] = 2 + + y_pred = np.zeros((30, 30), dtype=np.int) + y_pred[20:30, 1:11] = 1 + y_pred[20:30, 20:30] = 2 + return y_true, y_pred + + +# source repo: +# https://github.com/pytorch/ignite/blob/master/tests/ignite/metrics/test_confusion_matrix.py +def _compute_th_y_true_y_logits(y_true, y_pred): + # Create torch.tensor from numpy + th_y_true = torch.from_numpy(y_true).unsqueeze(0) + # Create logits torch.tensor: + num_classes = max(np.max(y_true), np.max(y_pred)) + 1 + y_probas = np.ones((num_classes,) + y_true.shape) * -10 + for i in range(num_classes): + y_probas[i, (y_pred == i)] = 720 + th_y_logits = torch.from_numpy(y_probas).unsqueeze(0) + return th_y_true, th_y_logits + + +# Dependency metrics do not get updated automatically, so need to retrieve and +# update confusion matrix manually +def _get_cm(metriclambda): + metrics = list(metriclambda.args) + while metrics: + metric = metrics[0] + if isinstance(metric, ConfusionMatrix): + return metric + elif isinstance(metric, MetricsLambda): + metrics.extend(metric.args) + del metrics[0] + + +def test_class_accuracy(): + y_true, y_pred = _get_y_true_y_pred() + + ## Perfect prediction + th_y_true, th_y_logits = _compute_th_y_true_y_logits(y_true, y_true) + # Update metric + output = (th_y_logits, th_y_true) + acc_metric = class_accuracy(num_classes=3) + acc_metric.update(output) + + # Retrieve and update confusion matrix + metric_cm = _get_cm(acc_metric) + # assert confusion matrix exists and is all zeroes + assert metric_cm is not None + assert ( + torch.min(metric_cm.confusion_matrix) == 0.0 + and torch.max(metric_cm.confusion_matrix) == 0.0 + ) + metric_cm.update(output) + + # Expected result + true_res = [1.0, 1.0, 1.0] + res = acc_metric.compute().numpy() + assert np.all(res == true_res), "Result {} vs. expected values {}".format( + res, true_res + ) + + ## Imperfect prediction + th_y_true, th_y_logits = _compute_th_y_true_y_logits(y_true, y_pred) + # Update metric + output = (th_y_logits, th_y_true) + acc_metric = class_accuracy(num_classes=3) + acc_metric.update(output) + + # Retrieve and update confusion matrix + metric_cm = _get_cm(acc_metric) + assert metric_cm is not None + assert ( + torch.min(metric_cm.confusion_matrix) == 0.0 + and torch.max(metric_cm.confusion_matrix) == 0.0 + ) + metric_cm.update(output) + + # Expected result + true_res = [0.75, 0.0, 0.25] + res = acc_metric.compute().numpy() + assert np.all(res == true_res), "Result {} vs. expected values {}".format( + res, true_res + ) + + +def test_mean_class_accuracy(): + y_true, y_pred = _get_y_true_y_pred() + + ## Perfect prediction + th_y_true, th_y_logits = _compute_th_y_true_y_logits(y_true, y_true) + # Update metric + output = (th_y_logits, th_y_true) + acc_metric = mean_class_accuracy(num_classes=3) + acc_metric.update(output) + + # Retrieve and update confusion matrix + metric_cm = _get_cm(acc_metric) + metric_cm.update(output) + + # Expected result + true_res = 1.0 + res = acc_metric.compute().numpy() + assert res == approx(true_res), "Result {} vs. expected value {}".format( + res, true_res + ) + + ## Imperfect prediction + th_y_true, th_y_logits = _compute_th_y_true_y_logits(y_true, y_pred) + # Update metric + output = (th_y_logits, th_y_true) + acc_metric = mean_class_accuracy(num_classes=3) + acc_metric.update(output) + + # Retrieve and update confusion matrix + metric_cm = _get_cm(acc_metric) + metric_cm.update(output) + + # Expected result + true_res = 1 / 3 + res = acc_metric.compute().numpy() + assert res == approx(true_res), "Result {} vs. expected value {}".format( + res, true_res + ) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 15973ae0..77099297 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -16,7 +16,7 @@ dependencies: - tqdm - pip: - segyio - - pytorch-ignite + - pytorch-ignite==0.3.0.dev20191105 # pre-release until stable available - fire - toolz - tabulate==0.8.2 diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb new file mode 100644 index 00000000..e6cfd60b --- /dev/null +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -0,0 +1,729 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Model training and evaluation on F3 Netherlands dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Seismic interpretation, also referred to as facies classification, is a task of determining types of rock in the earth’s subsurface, given seismic data. Seismic interpretation is used as a standard approach for determining precise locations of oil deposits for drilling, therefore reducing risks and potential losses. In recent years, there has been a great interest in using fully-supervised deep learning models for seismic interpretation. \n", + "\n", + "In this notebook, we demonstrate how to train a deep neural network for facies prediction using F3 Netherlands dataset. The F3 block is located in the North Sea off the shores of Netherlands. The dataset contains 6 classes (facies or lithostratigraphic units), all of which are of varying thickness (class imbalance). Processed data is available in numpy format as a `401 x 701 x 255` array. The processed F3 data is made available by [Alaudah et al. 2019](https://github.com/olivesgatech/facies_classification_benchmark). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Environment setup\n", + "\n", + "To set up the conda environment, please follow the instructions in the top-level [README.md](../../../README.md) file.\n", + "\n", + "__Note__: To register the conda environment in Jupyter, run:\n", + "`python -m ipykernel install --user --name envname`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Library imports" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's load required libraries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "import logging.config\n", + "from os import path\n", + "import random\n", + "import matplotlib.pyplot as plt\n", + "plt.rcParams.update({\"font.size\": 16})\n", + "\n", + "import yacs.config\n", + "\n", + "import cv2\n", + "import numpy as np\n", + "import torch\n", + "from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize\n", + "from ignite.contrib.handlers import CosineAnnealingScheduler\n", + "from ignite.handlers import ModelCheckpoint\n", + "from ignite.engine import Events\n", + "from ignite.metrics import Loss\n", + "from ignite.utils import convert_tensor\n", + "from toolz import compose\n", + "from torch.utils import data\n", + "\n", + "from cv_lib.event_handlers import SnapshotHandler, logging_handlers\n", + "from cv_lib.segmentation import models, tgs_salt\n", + "from cv_lib.segmentation.dutchf3.engine import create_supervised_trainer\n", + "\n", + "from cv_lib.segmentation.dutchf3.utils import (\n", + " current_datetime,\n", + " generate_path,\n", + " git_branch,\n", + " git_hash,\n", + " np_to_tb,\n", + ")\n", + "\n", + "from deepseismic_interpretation.dutchf3.data import (\n", + " get_patch_loader,\n", + " decode_segmap,\n", + " get_test_loader,\n", + ")\n", + "\n", + "from utilities import (\n", + " plot_aline,\n", + " plot_f3block_interactive,\n", + " prepare_batch,\n", + " _patch_label_2d,\n", + " _compose_processing_pipeline,\n", + " _output_processing_pipeline,\n", + " _write_section_file,\n", + " runningScore,\n", + ")\n", + "\n", + "# set device\n", + "device = \"cpu\"\n", + "if torch.cuda.is_available():\n", + " device = \"cuda\"\n", + "\n", + "# number of images to score\n", + "N_EVALUATE = 20\n", + "# experiment configuration file\n", + "CONFIG_FILE = \"./configs/patch_deconvnet_skip.yaml\"\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data download and preparation\n", + "\n", + "To download and prepare the F3 data set, please follow the instructions [here](../../../interpretation/dutchf3_patch/README.md). Once you've downloaded and prepared the data set, you'll find your files in the following directory tree:\n", + "\n", + "```\n", + "data\n", + "├── splits\n", + "├── test_once\n", + "│ ├── test1_labels.npy\n", + "│ ├── test1_seismic.npy\n", + "│ ├── test2_labels.npy\n", + "│ └── test2_seismic.npy\n", + "└── train\n", + " ├── train_labels.npy\n", + " └── train_seismic.npy\n", + "```\n", + "\n", + "We recommend saving the data under `/mnt/dutchf3` since this notebook will use that location as the data root. Otherwise, modify the `DATASET.ROOT` field in the configuration file, described next. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Experiment configuration file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use configuration files to specify experiment configuration, such as hyperparameters used in training and evaluation, as well as other experiment settings. We provide several configuration files for this notebook, under `./configs`, mainly differing in the DNN architecture used for defining the model.\n", + "\n", + "Modify the `CONFIG_FILE` variable above if you would like to run the experiment using a different configuration file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with open(CONFIG_FILE, \"rt\") as f_read:\n", + " config = yacs.config.load_cfg(f_read)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## F3 data set " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize a few sections of the F3 data set. The processed F3 data set is stored as a 3D numpy array. Let's view slices of the data along inline and crossline directions. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load training data and labels\n", + "train_seismic = np.load(path.join(config.DATASET.ROOT, \"train/train_seismic.npy\"))\n", + "train_labels = np.load(path.join(config.DATASET.ROOT, \"train/train_labels.npy\"))\n", + "\n", + "print(f\"Number of inline slices: {train_seismic.shape[0]}\")\n", + "print(f\"Number of crossline slices: {train_seismic.shape[1]}\")\n", + "print(f\"Depth dimension : {train_seismic.shape[2]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plot_f3block_interactive(train_labels)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's plot an __inline__ slice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "idx = 100\n", + "x_in = train_seismic[idx, :, :].swapaxes(0, 1)\n", + "x_inl = train_labels[idx, :, :].swapaxes(0, 1)\n", + "\n", + "plot_aline(x_in, x_inl, xlabel=\"inline\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's plot a __crossline__ slice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x_cr = train_seismic[:, idx, :].swapaxes(0, 1)\n", + "x_crl = train_labels[:, idx, :].swapaxes(0, 1)\n", + "\n", + "plot_aline(x_cr, x_crl, xlabel=\"crossline\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model training" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set up logging\n", + "logging.config.fileConfig(config.LOG_CONFIG)\n", + "logger = logging.getLogger(__name__)\n", + "logger.debug(config.WORKERS)\n", + "torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up data augmentation\n", + "\n", + "Let's define our data augmentation pipeline, which includes basic transformations, such as _data normalization, resizing, and padding_ if necessary. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Setup Augmentations\n", + "basic_aug = Compose(\n", + " [\n", + " Normalize(\n", + " mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1\n", + " ),\n", + " Resize(\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", + " always_apply=True,\n", + " ),\n", + " PadIfNeeded(\n", + " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", + " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", + " border_mode=cv2.BORDER_CONSTANT,\n", + " always_apply=True,\n", + " mask_value=255,\n", + " ),\n", + " ]\n", + ")\n", + "\n", + "if config.TRAIN.AUGMENTATION:\n", + " train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)])\n", + "else:\n", + " train_aug = basic_aug" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load the data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For training the model, we will use a patch-based approach. Rather than using entire sections (crosslines or inlines) of the data, we extract a large number of small patches from the sections, and use the patches as our data. This allows us to generate larger set of images for training, but is also a more feasible approach for large seismic volumes. \n", + "\n", + "We are using a custom patch data loader from our __`deepseismic_interpretation`__ library for generating and loading patches from seismic section data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "TrainPatchLoader = get_patch_loader(config)\n", + "\n", + "train_set = TrainPatchLoader(\n", + " config.DATASET.ROOT,\n", + " split=\"train\",\n", + " is_transform=True,\n", + " stride=config.TRAIN.STRIDE,\n", + " patch_size=config.TRAIN.PATCH_SIZE,\n", + " augmentations=train_aug,\n", + ")\n", + "\n", + "\n", + "train_loader = data.DataLoader(\n", + " train_set,\n", + " batch_size=config.TRAIN.BATCH_SIZE_PER_GPU,\n", + " num_workers=config.WORKERS,\n", + " shuffle=True,\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up model training" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, let's define a model to train, an optimization algorithm, and a loss function. \n", + "\n", + "Note that the model is loaded from our __`cv_lib`__ library, using the name of the model as specified in the configuration file. To load a different model, either change the `MODEL.NAME` field in the configuration file, or create a new one corresponding to the model you wish to train." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# load a model\n", + "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", + "\n", + "# Send to GPU if available\n", + "model = model.to(device)\n", + "\n", + "# SGD optimizer\n", + "optimizer = torch.optim.SGD(\n", + " model.parameters(),\n", + " lr=config.TRAIN.MAX_LR,\n", + " momentum=config.TRAIN.MOMENTUM,\n", + " weight_decay=config.TRAIN.WEIGHT_DECAY,\n", + ")\n", + "\n", + "# learning rate scheduler\n", + "scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS\n", + "snapshot_duration = scheduler_step * len(train_loader)\n", + "scheduler = CosineAnnealingScheduler(\n", + " optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration\n", + ")\n", + "\n", + "# weights are inversely proportional to the frequency of the classes in the training set\n", + "class_weights = torch.tensor(\n", + " config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False\n", + ")\n", + "\n", + "# loss function\n", + "criterion = torch.nn.CrossEntropyLoss(\n", + " weight=class_weights, ignore_index=255, reduction=\"mean\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Training the model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use [ignite](https://pytorch.org/ignite/index.html) framework to create training and validation loops in our codebase. Ignite provides an easy way to create compact training/validation loops without too much boilerplate code.\n", + "\n", + "In this notebook, we demonstrate the use of ignite on the training loop only. We create a training engine `trainer` that loops multiple times over the training dataset and updates model parameters. In addition, we add various events to the trainer, using an event system, that allows us to interact with the engine on each step of the run, such as, when the trainer is started/completed, when the epoch is started/completed and so on. \n", + "\n", + "In the cell below, we use event handlers to add the following events to the training loop:\n", + "- log training output\n", + "- log and schedule learning rate and\n", + "- periodically save model to disk." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# create training engine\n", + "trainer = create_supervised_trainer(\n", + " model, optimizer, criterion, prepare_batch, device=device\n", + ")\n", + "\n", + "# add learning rate scheduler\n", + "trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)\n", + "\n", + "# add logging of traininig output\n", + "trainer.add_event_handler(\n", + " Events.ITERATION_COMPLETED,\n", + " logging_handlers.log_training_output(log_interval=config.PRINT_FREQ),\n", + ")\n", + "\n", + "# add logging of learning rate\n", + "trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer))\n", + "\n", + "# add model checkpointing\n", + "output_dir = path.join(config.OUTPUT_DIR, config.TRAIN.MODEL_DIR)\n", + "checkpoint_handler = ModelCheckpoint(\n", + " output_dir, \"model\", save_interval=2, n_saved=3, create_dir=True, require_empty=False\n", + ")\n", + "trainer.add_event_handler(\n", + " Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Start the training engine run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Evaluation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will next evaluate the performance of the model by looking at how well it predicts facies labels on samples from the test set.\n", + "\n", + "We will use the following evaluation metrics:\n", + "\n", + "- Pixel Accuracy (PA)\n", + "- Class Accuracy (CA)\n", + "- Mean Class Accuracy (MCA)\n", + "- Frequency Weighted intersection-over-union (FW IoU)\n", + "- Mean IoU (MIoU)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's first load the model saved previously." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", + "model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False)\n", + "model = model.to(device)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we load the test data and define the augmentations on it. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Augmentation\n", + "section_aug = Compose(\n", + " [Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1)]\n", + ")\n", + "\n", + "patch_aug = Compose(\n", + " [\n", + " Resize(\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", + " always_apply=True,\n", + " ),\n", + " PadIfNeeded(\n", + " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", + " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", + " border_mode=cv2.BORDER_CONSTANT,\n", + " always_apply=True,\n", + " mask_value=255,\n", + " ),\n", + " ]\n", + ")\n", + "\n", + "# Process test data\n", + "pre_processing = _compose_processing_pipeline(config.TRAIN.DEPTH, aug=patch_aug)\n", + "output_processing = _output_processing_pipeline(config)\n", + "\n", + "# Select the test split\n", + "split = \"test1\" if \"both\" in config.TEST.SPLIT else config.TEST.SPLIT\n", + "\n", + "labels = np.load(path.join(config.DATASET.ROOT, \"test_once\", split + \"_labels.npy\"))\n", + "section_file = path.join(config.DATASET.ROOT, \"splits\", \"section_\" + split + \".txt\")\n", + "_write_section_file(labels, section_file, config)\n", + "\n", + "# Load test data\n", + "TestSectionLoader = get_test_loader(config)\n", + "test_set = TestSectionLoader(\n", + " config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug\n", + ")\n", + "\n", + "test_loader = data.DataLoader(\n", + " test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Predict segmentation mask on the test data\n", + "\n", + "For demonstration purposes and efficiency, we will only use a subset of the test data to predict segmentation mask on. More precisely, we will score `N_EVALUATE` images. If you would like to evaluate more images, set this variable to the desired number of images." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CLASS_NAMES = [\n", + " \"upper_ns\",\n", + " \"middle_ns\",\n", + " \"lower_ns\",\n", + " \"rijnland_chalk\",\n", + " \"scruff\",\n", + " \"zechstein\",\n", + "]\n", + "\n", + "n_classes = len(CLASS_NAMES)\n", + "\n", + "# keep only N_EVALUATE sections to score\n", + "test_subset = random.sample(list(test_loader), N_EVALUATE)\n", + "\n", + "results = list()\n", + "running_metrics_split = runningScore(n_classes)\n", + "\n", + "# testing mode\n", + "with torch.no_grad():\n", + " model.eval()\n", + " # loop over testing data\n", + " for i, (images, labels) in enumerate(test_subset):\n", + " logger.info(f\"split: {split}, section: {i}\")\n", + " outputs = _patch_label_2d(\n", + " model,\n", + " images,\n", + " pre_processing,\n", + " output_processing,\n", + " config.TRAIN.PATCH_SIZE,\n", + " config.TEST.TEST_STRIDE,\n", + " config.VALIDATION.BATCH_SIZE_PER_GPU,\n", + " device,\n", + " n_classes,\n", + " )\n", + "\n", + " pred = outputs.detach().max(1)[1].numpy()\n", + " gt = labels.numpy()\n", + " \n", + " # update evaluation metrics\n", + " running_metrics_split.update(gt, pred)\n", + " \n", + " # keep ground truth and result for plotting\n", + " results.append((np.squeeze(gt), np.squeeze(pred)))\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's view the obtained metrics on this subset of test images. Note that we trained our model for for a small number of epochs, for demonstration purposes, so the performance results here are not meant to be representative. \n", + "\n", + "The performance exceed the ones shown here when the models are trained properly. For the full report on benchmarking performance results, please refer to the [README.md](../../../README.md) file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get scores\n", + "score, _ = running_metrics_split.get_scores()\n", + "\n", + "# Log split results\n", + "print(f'Pixel Acc: {score[\"Pixel Acc: \"]:.3f}')\n", + "for cdx, class_name in enumerate(CLASS_NAMES):\n", + " print(f' {class_name}_accuracy {score[\"Class Accuracy: \"][cdx]:.3f}')\n", + "\n", + "print(f'Mean Class Acc: {score[\"Mean Class Acc: \"]:.3f}')\n", + "print(f'Freq Weighted IoU: {score[\"Freq Weighted IoU: \"]:.3f}')\n", + "print(f'Mean IoU: {score[\"Mean IoU: \"]:0.3f}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualize predictions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize the predictions on entire test sections. Note that the crosslines and inlines have different dimensions, however we were able to use them jointly for our network training and evaluation, since we were using smaller patches from the sections, whose size we can control via hyperparameter in the experiment configuration file. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "fig = plt.figure(figsize=(15,50))\n", + "\n", + "nplot = min(N_EVALUATE, 10)\n", + "for idx in range(nplot):\n", + " # plot actual\n", + " plt.subplot(nplot, 2, 2*(idx+1)-1)\n", + " plt.imshow(results[idx][0])\n", + " # plot predicted\n", + " plt.subplot(nplot, 2, 2*(idx+1))\n", + " plt.imshow(results[idx][1])\n", + "\n", + "f_axes = fig.axes\n", + "_ = f_axes[0].set_title('Actual')\n", + "_ = f_axes[1].set_title('Predicted') " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "seismic-interpretation", + "language": "python", + "name": "seismic-interpretation" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py index ae0c17f7..3eab5dfb 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -# /* spell-checker: disable */ + """Train models on Dutch F3 salt dataset Trains models using PyTorch DistributedDataParallel @@ -16,13 +16,7 @@ import fire import numpy as np import torch -from albumentations import ( - Compose, - HorizontalFlip, - Normalize, - Resize, - PadIfNeeded, -) +from albumentations import Compose, HorizontalFlip, Normalize, Resize, PadIfNeeded from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -35,15 +29,21 @@ ) from cv_lib.segmentation import models from cv_lib.segmentation import extract_metric_from -from deepseismic_interpretation.dutchf3.data import ( - get_patch_loader, - decode_segmap, -) +from deepseismic_interpretation.dutchf3.data import get_patch_loader, decode_segmap from cv_lib.segmentation.dutchf3.engine import ( create_supervised_evaluator, create_supervised_trainer, ) -from cv_lib.segmentation.dutchf3.metrics import apex + +from ignite.metrics import Loss +from cv_lib.segmentation.metrics import ( + pixelwise_accuracy, + class_accuracy, + mean_class_accuracy, + class_iou, + mean_iou, +) + from cv_lib.segmentation.dutchf3.utils import ( current_datetime, generate_path, @@ -82,12 +82,13 @@ def run(*options, cfg=None, local_rank=0): Notes: Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file - + Options from default.py will be overridden by options loaded from cfg file + Options passed in via options argument will override option loaded from cfg file + Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py + *options (str,int ,optional): Options used to overide what is loaded from the + config. To see what options are available consult + default.py cfg (str, optional): Location of config file to load. Defaults to None. """ update_config(config, options=options, config_file=cfg) @@ -99,11 +100,11 @@ def run(*options, cfg=None, local_rank=0): distributed = world_size > 1 if distributed: - # FOR DISTRIBUTED: Set the device according to local_rank. + # FOR DISTRIBUTED: Set the device according to local_rank. torch.cuda.set_device(local_rank) - # FOR DISTRIBUTED: Initialize the backend. torch.distributed.launch will provide - # environment variables, and requires that you use init_method=`env://`. + # FOR DISTRIBUTED: Initialize the backend. torch.distributed.launch will + # provide environment variables, and requires that you use init_method=`env://`. torch.distributed.init_process_group(backend="nccl", init_method="env://") scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS @@ -117,7 +118,7 @@ def run(*options, cfg=None, local_rank=0): basic_aug = Compose( [ Normalize( - mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1, + mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1 ), Resize( config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, @@ -134,7 +135,7 @@ def run(*options, cfg=None, local_rank=0): ] ) if config.TRAIN.AUGMENTATION: - train_aug = Compose([basic_aug, HorizontalFlip(p=0.5),]) + train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)]) val_aug = basic_aug else: train_aug = val_aug = basic_aug @@ -198,7 +199,8 @@ def run(*options, cfg=None, local_rank=0): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - # weights are inversely proportional to the frequency of the classes in the training set + # weights are inversely proportional to the frequency of the classes in + # the training set class_weights = torch.tensor( config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False ) @@ -229,7 +231,7 @@ def run(*options, cfg=None, local_rank=0): ) scheduler = ConcatScheduler( - schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration], + schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] ) trainer = create_supervised_trainer( @@ -237,39 +239,37 @@ def run(*options, cfg=None, local_rank=0): ) trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - # Set to update the epoch parameter of our distributed data sampler so that we get different shuffles + # Set to update the epoch parameter of our distributed data sampler so that we get + # different shuffles trainer.add_event_handler(Events.EPOCH_STARTED, update_sampler_epoch(train_loader)) if silence_other_ranks & local_rank != 0: logging.getLogger("ignite.engine.engine.Engine").setLevel(logging.WARNING) def _select_pred_and_mask(model_out_dict): - return ( - model_out_dict["y_pred"].squeeze(), - model_out_dict["mask"].squeeze(), - ) + return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) evaluator = create_supervised_evaluator( model, prepare_batch, metrics={ - "IoU": apex.MeanIoU( - n_classes, device, output_transform=_select_pred_and_mask + "nll": Loss( + criterion, output_transform=_select_pred_and_mask, device=device ), - "nll": apex.LossMetric( - criterion, - world_size, - config.VALIDATION.BATCH_SIZE_PER_GPU, - output_transform=_select_pred_and_mask, + "pixa": pixelwise_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "mca": apex.MeanClassAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "cacc": class_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "fiou": apex.FrequencyWeightedIoU( - n_classes, device, output_transform=_select_pred_and_mask + "mca": mean_class_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "pixa": apex.PixelwiseAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "ciou": class_iou( + n_classes, output_transform=_select_pred_and_mask, device=device + ), + "mIoU": mean_iou( + n_classes, output_transform=_select_pred_and_mask, device=device ), }, device=device, @@ -312,11 +312,10 @@ def _select_pred_and_mask(model_out_dict): logging_handlers.log_metrics( "Validation results", metrics_dict={ - "IoU": "IoU :", "nll": "Avg loss :", + "mIoU": " Avg IoU :", "pixa": "Pixelwise Accuracy :", "mca": "Mean Class Accuracy :", - "fiou": "Freq Weighted IoU :", }, ), ) @@ -327,10 +326,9 @@ def _select_pred_and_mask(model_out_dict): trainer, "epoch", metrics_dict={ - "IoU": "Validation/IoU", + "mIoU": "Validation/IoU", "nll": "Validation/Loss", "mca": "Validation/MCA", - "fiou": "Validation/FIoU", }, ), ) @@ -354,10 +352,7 @@ def _tensor_to_numpy(pred_tensor): evaluator.add_event_handler( Events.EPOCH_COMPLETED, create_image_writer( - summary_writer, - "Validation/Mask", - "mask", - transform_func=transform_func, + summary_writer, "Validation/Mask", "mask", transform_func=transform_func ), ) evaluator.add_event_handler( @@ -376,7 +371,7 @@ def snapshot_function(): checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, - extract_metric_from("fiou"), + extract_metric_from("mIoU"), snapshot_function, ) evaluator.add_event_handler( diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index 4b1fb17f..7c43bc91 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -1,5 +1,5 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. # /* spell-checker: disable */ import logging @@ -10,8 +10,7 @@ import fire import numpy as np import torch -from albumentations import (Compose, HorizontalFlip, Normalize, PadIfNeeded, - Resize) +from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize from ignite.contrib.handlers import CosineAnnealingScheduler from ignite.engine import Events from ignite.metrics import Loss @@ -20,25 +19,41 @@ from torch.utils import data from deepseismic_interpretation.dutchf3.data import get_patch_loader, decode_segmap -from cv_lib.event_handlers import (SnapshotHandler, logging_handlers, - tensorboard_handlers) +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import (create_image_writer, - create_summary_writer) -from cv_lib.segmentation import models -from cv_lib.segmentation.dutchf3.engine import (create_supervised_evaluator, - create_supervised_trainer) -from cv_lib.segmentation.dutchf3.metrics import (FrequencyWeightedIoU, - MeanClassAccuracy, MeanIoU, - PixelwiseAccuracy) -from cv_lib.segmentation.dutchf3.utils import (current_datetime, generate_path, - git_branch, git_hash, np_to_tb) +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) +from cv_lib.segmentation import models, extract_metric_from +from cv_lib.segmentation.dutchf3.engine import ( + create_supervised_evaluator, + create_supervised_trainer, +) + +from cv_lib.segmentation.metrics import ( + pixelwise_accuracy, + class_accuracy, + mean_class_accuracy, + class_iou, + mean_iou, +) + +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) from default import _C as config from default import update_config -from cv_lib.segmentation import extract_metric_from - def prepare_batch(batch, device=None, non_blocking=False): x, y = batch @@ -53,12 +68,13 @@ def run(*options, cfg=None): Notes: Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file + Options from default.py will be overridden by options loaded from cfg file + Options passed in via options argument will override option loaded from cfg file Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py + *options (str,int ,optional): Options used to overide what is loaded from the + config. To see what options are available consult + default.py cfg (str, optional): Location of config file to load. Defaults to None. """ @@ -78,9 +94,7 @@ def run(*options, cfg=None): basic_aug = Compose( [ Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, + mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1 ), Resize( config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, @@ -97,12 +111,7 @@ def run(*options, cfg=None): ] ) if config.TRAIN.AUGMENTATION: - train_aug = Compose( - [ - basic_aug, - HorizontalFlip(p=0.5), - ] - ) + train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)]) val_aug = basic_aug else: train_aug = val_aug = basic_aug @@ -148,7 +157,6 @@ def run(*options, cfg=None): device = "cuda" model = model.to(device) # Send to GPU - optimizer = torch.optim.SGD( model.parameters(), lr=config.TRAIN.MAX_LR, @@ -163,19 +171,18 @@ def run(*options, cfg=None): config.MODEL.NAME, current_datetime(), ) + summary_writer = create_summary_writer( log_dir=path.join(output_dir, config.LOG_DIR) ) + snapshot_duration = scheduler_step * len(train_loader) scheduler = CosineAnnealingScheduler( - optimizer, - "lr", - config.TRAIN.MAX_LR, - config.TRAIN.MIN_LR, - snapshot_duration, + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration ) - # weights are inversely proportional to the frequency of the classes in the training set + # weights are inversely proportional to the frequency of the classes in the + # training set class_weights = torch.tensor( config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False ) @@ -194,9 +201,7 @@ def run(*options, cfg=None): Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) - ) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) trainer.add_event_handler( Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), @@ -207,50 +212,42 @@ def run(*options, cfg=None): ) def _select_pred_and_mask(model_out_dict): - return ( - model_out_dict["y_pred"].squeeze(), - model_out_dict["mask"].squeeze(), - ) + return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) evaluator = create_supervised_evaluator( model, prepare_batch, metrics={ - "IoU": MeanIoU( - n_classes, device, output_transform=_select_pred_and_mask - ), "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "mca": MeanClassAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "pixacc": pixelwise_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "fiou": FrequencyWeightedIoU( - n_classes, device, output_transform=_select_pred_and_mask - ), - "pixa": PixelwiseAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask), + "mca": mean_class_accuracy( + n_classes, output_transform=_select_pred_and_mask ), + "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask), + "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask), }, device=device, ) # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler( - Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) - ) + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) evaluator.add_event_handler( Events.EPOCH_COMPLETED, logging_handlers.log_metrics( "Validation results", metrics_dict={ - "IoU": "IoU :", "nll": "Avg loss :", - "pixa": "Pixelwise Accuracy :", - "mca": "Mean Class Accuracy :", - "fiou": "Freq Weighted IoU :", + "pixacc": "Pixelwise Accuracy :", + "mca": "Avg Class Accuracy :", + "mIoU": "Avg Class IoU :", }, ), ) + evaluator.add_event_handler( Events.EPOCH_COMPLETED, tensorboard_handlers.log_metrics( @@ -258,10 +255,10 @@ def _select_pred_and_mask(model_out_dict): trainer, "epoch", metrics_dict={ - "IoU": "Validation/IoU", + "mIoU": "Validation/mIoU", "nll": "Validation/Loss", "mca": "Validation/MCA", - "fiou": "Validation/FIoU", + "pixacc": "Validation/Pixel_Acc", }, ), ) @@ -285,19 +282,13 @@ def _tensor_to_numpy(pred_tensor): evaluator.add_event_handler( Events.EPOCH_COMPLETED, create_image_writer( - summary_writer, - "Validation/Mask", - "mask", - transform_func=transform_func, + summary_writer, "Validation/Mask", "mask", transform_func=transform_func ), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, create_image_writer( - summary_writer, - "Validation/Pred", - "y_pred", - transform_func=transform_pred, + summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred ), ) @@ -307,7 +298,7 @@ def snapshot_function(): checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, - extract_metric_from("fiou"), + extract_metric_from("mIoU"), snapshot_function, ) evaluator.add_event_handler( diff --git a/experiments/interpretation/dutchf3_section/local/train.py b/experiments/interpretation/dutchf3_section/local/train.py index 04db2b12..6d99c463 100644 --- a/experiments/interpretation/dutchf3_section/local/train.py +++ b/experiments/interpretation/dutchf3_section/local/train.py @@ -1,4 +1,4 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. +# Copyright (c) Microsoft Corporation. # # Licensed under the MIT License. # # /* spell-checker: disable */ @@ -10,10 +10,7 @@ import numpy as np import torch from albumentations import Compose, HorizontalFlip, Normalize -from deepseismic_interpretation.dutchf3.data import ( - decode_segmap, - get_section_loader, -) +from deepseismic_interpretation.dutchf3.data import decode_segmap, get_section_loader from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -24,16 +21,17 @@ create_image_writer, create_summary_writer, ) -from cv_lib.segmentation import models +from cv_lib.segmentation import models, extract_metric_from from cv_lib.segmentation.dutchf3.engine import ( create_supervised_evaluator, create_supervised_trainer, ) -from cv_lib.segmentation.dutchf3.metrics import ( - FrequencyWeightedIoU, - MeanClassAccuracy, - MeanIoU, - PixelwiseAccuracy, +from cv_lib.segmentation.metrics import ( + pixelwise_accuracy, + class_accuracy, + mean_class_accuracy, + class_iou, + mean_iou, ) from cv_lib.segmentation.dutchf3.utils import ( current_datetime, @@ -46,13 +44,11 @@ from default import update_config from ignite.contrib.handlers import CosineAnnealingScheduler from ignite.engine import Events -from ignite.metrics import Loss from ignite.utils import convert_tensor +from ignite.metrics import Loss from toolz import compose from torch.utils import data -from cv_lib.segmentation import extract_metric_from - def prepare_batch(batch, device="cuda", non_blocking=False): x, y = batch @@ -67,12 +63,13 @@ def run(*options, cfg=None): Notes: Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file + Options from default.py will be overridden by options loaded from cfg file + Options passed in via options argument will override option loaded from cfg file Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py + *options (str,int ,optional): Options used to overide what is loaded from the + config. To see what options are available consult + default.py cfg (str, optional): Location of config file to load. Defaults to None. """ @@ -92,9 +89,7 @@ def run(*options, cfg=None): basic_aug = Compose( [ Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, + mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1 ) ] ) @@ -127,9 +122,7 @@ def __init__(self, data_source): def __iter__(self): char = ["i" if np.random.randint(2) == 1 else "x"] self.indices = [ - idx - for (idx, name) in enumerate(self.data_source) - if char[0] in name + idx for (idx, name) in enumerate(self.data_source) if char[0] in name ] return (self.indices[i] for i in torch.randperm(len(self.indices))) @@ -184,14 +177,11 @@ def __len__(self): snapshot_duration = scheduler_step * len(train_loader) scheduler = CosineAnnealingScheduler( - optimizer, - "lr", - config.TRAIN.MAX_LR, - config.TRAIN.MIN_LR, - snapshot_duration, + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration ) - # weights are inversely proportional to the frequency of the classes in the training set + # weights are inversely proportional to the frequency of the classes in + # the training set class_weights = torch.tensor( config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False ) @@ -211,9 +201,7 @@ def __len__(self): logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) - ) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) trainer.add_event_handler( Events.EPOCH_STARTED, @@ -226,51 +214,58 @@ def __len__(self): ) def _select_pred_and_mask(model_out_dict): - return ( - model_out_dict["y_pred"].squeeze(), - model_out_dict["mask"].squeeze(), - ) + return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) evaluator = create_supervised_evaluator( model, prepare_batch, metrics={ - "IoU": MeanIoU( - n_classes, device, output_transform=_select_pred_and_mask + "nll": Loss( + criterion, output_transform=_select_pred_and_mask, device=device + ), + "pixacc": pixelwise_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "mca": MeanClassAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "cacc": class_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "fiou": FrequencyWeightedIoU( - n_classes, device, output_transform=_select_pred_and_mask + "mca": mean_class_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "pixa": PixelwiseAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "ciou": class_iou( + n_classes, output_transform=_select_pred_and_mask, device=device + ), + "mIoU": mean_iou( + n_classes, output_transform=_select_pred_and_mask, device=device ), }, device=device, ) # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler( - Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) - ) + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) evaluator.add_event_handler( Events.EPOCH_COMPLETED, logging_handlers.log_metrics( "Validation results", metrics_dict={ - "IoU": "IoU :", "nll": "Avg loss :", - "pixa": "Pixelwise Accuracy :", - "mca": "Mean Class Accuracy :", - "fiou": "Freq Weighted IoU :", + "pixacc": "Pixelwise Accuracy :", + "mca": "Avg Class Accuracy :", + "mIoU": "Avg Class IoU :", }, ), ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_class_metrics( + "Per class validation results", + metrics_dict={"ciou": "Class IoU :", "cacc": "Class Accuracy :"}, + ), + ) + evaluator.add_event_handler( Events.EPOCH_COMPLETED, tensorboard_handlers.log_metrics( @@ -278,10 +273,10 @@ def _select_pred_and_mask(model_out_dict): trainer, "epoch", metrics_dict={ - "IoU": "Validation/IoU", + "mIoU": "Validation/mIoU", "nll": "Validation/Loss", "mca": "Validation/MCA", - "fiou": "Validation/FIoU", + "pixacc": "Validation/Pixel_Acc", }, ), ) @@ -306,20 +301,14 @@ def _tensor_to_numpy(pred_tensor): evaluator.add_event_handler( Events.EPOCH_COMPLETED, create_image_writer( - summary_writer, - "Validation/Mask", - "mask", - transform_func=transform_func, + summary_writer, "Validation/Mask", "mask", transform_func=transform_func ), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, create_image_writer( - summary_writer, - "Validation/Pred", - "y_pred", - transform_func=transform_pred, + summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred ), ) @@ -329,7 +318,7 @@ def snapshot_function(): checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, - extract_metric_from("fiou"), + extract_metric_from("mIoU"), snapshot_function, ) diff --git a/experiments/interpretation/dutchf3_voxel/train.py b/experiments/interpretation/dutchf3_voxel/train.py index 7ea63254..71ae42f3 100644 --- a/experiments/interpretation/dutchf3_voxel/train.py +++ b/experiments/interpretation/dutchf3_voxel/train.py @@ -12,74 +12,55 @@ from torch.utils import data from ignite.engine import Events from ignite.handlers import ModelCheckpoint -from ignite.metrics import Accuracy, Loss -# TODO: get mertircs from Ignite -# from ignite.metrics import MIoU, MeanClassAccuracy, FrequencyWeightedIoU, PixelwiseAccuracy +from ignite.metrics import Loss from ignite.utils import convert_tensor -from ignite.engine.engine import Engine -from toolz import compose, curry from tqdm import tqdm -from deepseismic_interpretation.dutchf3.data import ( - get_voxel_loader, - decode_segmap, -) +from deepseismic_interpretation.dutchf3.data import get_voxel_loader +from deepseismic_interpretation.models.texture_net import TextureNet + from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, tensorboard_handlers, ) from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, +from cv_lib.event_handlers.tensorboard_handlers import create_summary_writer + +# TODO: replace with Ignite metrics +from cv_lib.segmentation.metrics import ( + pixelwise_accuracy, + class_accuracy, + mean_class_accuracy, + class_iou, + mean_iou, ) -from cv_lib.segmentation import models +from cv_lib.segmentation import extract_metric_from # from cv_lib.segmentation.dutchf3.engine import ( # create_supervised_evaluator, # create_supervised_trainer, # ) # Use ignite generic versions for now -from ignite.engine import ( - create_supervised_trainer, - create_supervised_evaluator, -) - -# TODO: replace with Ignite metrics -from cv_lib.segmentation.dutchf3.metrics import ( - FrequencyWeightedIoU, - MeanClassAccuracy, - MeanIoU, - PixelwiseAccuracy, -) - -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, - np_to_tb, -) - -from interpretation.deepseismic_interpretation.models.texture_net import ( - TextureNet, -) +from ignite.engine import create_supervised_trainer, create_supervised_evaluator from default import _C as config from default import update_config -def _prepare_batch( - batch, device=None, non_blocking=False, t_type=torch.FloatTensor -): +def _prepare_batch(batch, device=None, non_blocking=False, t_type=torch.FloatTensor): x, y = batch new_x = convert_tensor( torch.squeeze(x, 1), device=device, non_blocking=non_blocking ) - new_y = convert_tensor(torch.unsqueeze(y, 2), device=device, non_blocking=non_blocking) + new_y = convert_tensor( + torch.unsqueeze(y, 2), device=device, non_blocking=non_blocking + ) if device == "cuda": - return new_x.type(t_type).cuda(), torch.unsqueeze(new_y, 3).type(torch.LongTensor).cuda() + return ( + new_x.type(t_type).cuda(), + torch.unsqueeze(new_y, 3).type(torch.LongTensor).cuda(), + ) else: return new_x.type(t_type), torch.unsqueeze(new_y, 3).type(torch.LongTensor) @@ -89,12 +70,13 @@ def run(*options, cfg=None): Notes: Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file + Options from default.py will be overridden by options loaded from cfg file + Options passed in via options argument will override option loaded from cfg file Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py + *options (str,int ,optional): Options used to overide what is loaded from the + config. To see what options are available consult + default.py cfg (str, optional): Location of config file to load. Defaults to None. """ @@ -117,7 +99,7 @@ def run(*options, cfg=None): config.DATASET.FILENAME, split="train", window_size=config.WINDOW_SIZE, - len=config.TRAIN.BATCH_SIZE_PER_GPU*config.TRAIN.BATCH_PER_EPOCH, + len=config.TRAIN.BATCH_SIZE_PER_GPU * config.TRAIN.BATCH_PER_EPOCH, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, ) val_set = TrainVoxelLoader( @@ -125,28 +107,29 @@ def run(*options, cfg=None): config.DATASET.FILENAME, split="val", window_size=config.WINDOW_SIZE, - len=config.TRAIN.BATCH_SIZE_PER_GPU*config.TRAIN.BATCH_PER_EPOCH, + len=config.TRAIN.BATCH_SIZE_PER_GPU * config.TRAIN.BATCH_PER_EPOCH, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, ) n_classes = train_set.n_classes - # set dataset length to batch size to be consistent with 5000 iterations each of size - # 32 in the original Waldeland implementation + # set dataset length to batch size to be consistent with 5000 iterations + # each of size 32 in the original Waldeland implementation train_loader = data.DataLoader( train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - shuffle=False + num_workers=config.WORKERS, + shuffle=False, ) val_loader = data.DataLoader( val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, - shuffle=False + shuffle=False, ) - # this is how we import model for CV - here we're importing a seismic segmentation model + # this is how we import model for CV - here we're importing a seismic + # segmentation model model = TextureNet(n_classes=config.DATASET.NUM_CLASSES) optimizer = torch.optim.Adam( @@ -157,72 +140,74 @@ def run(*options, cfg=None): ) device = "cpu" - log_interval = 10 + if torch.cuda.is_available(): device = "cuda" model = model.cuda() - loss = torch.nn.CrossEntropyLoss() + loss = torch.nn.CrossEntropyLoss() trainer = create_supervised_trainer( - model, - optimizer, - loss, - prepare_batch=_prepare_batch, - device=device, + model, optimizer, loss, prepare_batch=_prepare_batch, device=device ) desc = "ITERATION - loss: {:.2f}" - pbar = tqdm( - initial=0, leave=False, total=len(train_loader), desc=desc.format(0) - ) + pbar = tqdm(initial=0, leave=False, total=len(train_loader), desc=desc.format(0)) # add model checkpointing output_dir = path.join(config.OUTPUT_DIR, config.TRAIN.MODEL_DIR) checkpoint_handler = ModelCheckpoint( - output_dir, "model", save_interval=1, n_saved=3, create_dir=True, require_empty=False) + output_dir, + "model", + save_interval=1, + n_saved=3, + create_dir=True, + require_empty=False, + ) criterion = torch.nn.CrossEntropyLoss(reduction="mean") - + # save model at each epoch trainer.add_event_handler( Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model} ) - + def _select_pred_and_mask(model_out): # receive a tuple of (x, y_pred), y - # so actually in line 51 of cv_lib/cv_lib/segmentation/dutch_f3/metrics/__init__.py - # we do the following line, so here we just select the model - #_, y_pred = torch.max(model_out[0].squeeze(), 1, keepdim=True) + # so actually in line 51 of + # cv_lib/cv_lib/segmentation/dutch_f3/metrics/__init__.py + # we do the following line, so here we just select the model + # _, y_pred = torch.max(model_out[0].squeeze(), 1, keepdim=True) y_pred = model_out[0].squeeze() y = model_out[1].squeeze() - return (y_pred.squeeze(), y,) - + return (y_pred.squeeze(), y) + evaluator = create_supervised_evaluator( - model, + model, metrics={ - "IoU": MeanIoU( - n_classes, device, output_transform=_select_pred_and_mask + "nll": Loss(criterion, device=device), + "pixa": pixelwise_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device + ), + "cacc": class_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "nll": Loss(criterion), - "mca": MeanClassAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "mca": mean_class_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "fiou": FrequencyWeightedIoU( - n_classes, device, output_transform=_select_pred_and_mask + "ciou": class_iou( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "pixa": PixelwiseAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "mIoU": mean_iou( + n_classes, output_transform=_select_pred_and_mask, device=device ), }, device=device, prepare_batch=_prepare_batch, - ) + ) # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler( - Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) - ) + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) summary_writer = create_summary_writer( log_dir=path.join(output_dir, config.LOG_DIR) @@ -233,11 +218,10 @@ def _select_pred_and_mask(model_out): logging_handlers.log_metrics( "Validation results", metrics_dict={ - "IoU": "IoU :", + "mIoU": "Avg IoU :", "nll": "Avg loss :", "pixa": "Pixelwise Accuracy :", "mca": "Mean Class Accuracy :", - "fiou": "Freq Weighted IoU :", }, ), ) @@ -248,26 +232,26 @@ def _select_pred_and_mask(model_out): trainer, "epoch", metrics_dict={ - "IoU": "Validation/IoU", + "mIoU": "Validation/IoU", "nll": "Validation/Loss", "mca": "Validation/MCA", - "fiou": "Validation/FIoU", }, ), ) - + summary_writer = create_summary_writer( log_dir=path.join(output_dir, config.LOG_DIR) - ) - + ) + + snapshot_duration = 1 - snapshot_duration=1 def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, + extract_metric_from("mIoU"), snapshot_function, ) evaluator.add_event_handler( @@ -275,9 +259,11 @@ def snapshot_function(): ) logger.info("Starting training") - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH//config.TRAIN.BATCH_PER_EPOCH) + trainer.run( + train_loader, max_epochs=config.TRAIN.END_EPOCH // config.TRAIN.BATCH_PER_EPOCH + ) pbar.close() + if __name__ == "__main__": fire.Fire(run) - diff --git a/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml b/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml index 36b315a7..cb61b6b6 100644 --- a/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml +++ b/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml @@ -54,11 +54,11 @@ VALIDATION: TEST: COMPLETE_PATCHES_ONLY: False - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + MODEL_PATH: "/data/home/vapaunic/repos/DeepSeismic/experiments/interpretation/penobscot/local/output/vapaunic/metrics/4120aa99152b6e4f92f8134b783ac63c8131e1ed/resnet_unet/Nov05_105704/models/resnet_unet_running_model_1.pth" AUGMENTATIONS: RESIZE: - HEIGHT: 200 - WIDTH: 200 + HEIGHT: 256 + WIDTH: 256 PAD: HEIGHT: 256 WIDTH: 256 diff --git a/experiments/interpretation/penobscot/local/test.py b/experiments/interpretation/penobscot/local/test.py index 91899519..9eafa2c5 100644 --- a/experiments/interpretation/penobscot/local/test.py +++ b/experiments/interpretation/penobscot/local/test.py @@ -12,26 +12,19 @@ import numpy as np import torch import torchvision -from albumentations import ( - Compose, - Normalize, - PadIfNeeded, - Resize, -) -from cv_lib.event_handlers import ( - logging_handlers, - tensorboard_handlers, -) +from albumentations import Compose, Normalize, PadIfNeeded, Resize +from cv_lib.event_handlers import logging_handlers, tensorboard_handlers from cv_lib.event_handlers.tensorboard_handlers import ( create_image_writer, create_summary_writer, ) from cv_lib.segmentation import models -from cv_lib.segmentation.dutchf3.metrics import ( - FrequencyWeightedIoU, - MeanClassAccuracy, - MeanIoU, - PixelwiseAccuracy, +from cv_lib.segmentation.metrics import ( + pixelwise_accuracy, + class_accuracy, + mean_class_accuracy, + class_iou, + mean_iou, ) from cv_lib.segmentation.dutchf3.utils import ( current_datetime, @@ -40,13 +33,9 @@ git_hash, np_to_tb, ) -from cv_lib.segmentation.penobscot.engine import ( - create_supervised_evaluator, -) +from cv_lib.segmentation.penobscot.engine import create_supervised_evaluator from deepseismic_interpretation.dutchf3.data import decode_segmap -from deepseismic_interpretation.penobscot.data import ( - get_patch_dataset, -) +from deepseismic_interpretation.penobscot.data import get_patch_dataset from deepseismic_interpretation.penobscot.metrics import InlineMeanIoU from default import _C as config from default import update_config @@ -70,12 +59,10 @@ def _prepare_batch(batch, device=None, non_blocking=False): def _padding_from(config): padding_height = ( - config.TEST.AUGMENTATIONS.PAD.HEIGHT - - config.TEST.AUGMENTATIONS.RESIZE.HEIGHT + config.TEST.AUGMENTATIONS.PAD.HEIGHT - config.TEST.AUGMENTATIONS.RESIZE.HEIGHT ) padding_width = ( - config.TEST.AUGMENTATIONS.PAD.WIDTH - - config.TEST.AUGMENTATIONS.RESIZE.WIDTH + config.TEST.AUGMENTATIONS.PAD.WIDTH - config.TEST.AUGMENTATIONS.RESIZE.WIDTH ) assert ( padding_height == padding_width @@ -84,19 +71,17 @@ def _padding_from(config): def _scale_from(config): - scale_height = ( - config.TEST.AUGMENTATIONS.PAD.HEIGHT / config.TRAIN.PATCH_SIZE - ) + scale_height = config.TEST.AUGMENTATIONS.PAD.HEIGHT / config.TRAIN.PATCH_SIZE scale_width = config.TEST.AUGMENTATIONS.PAD.WIDTH / config.TRAIN.PATCH_SIZE assert ( - config.TEST.AUGMENTATIONS.PAD.HEIGHT % config.TRAIN.PATCH_SIZE != 0 - ), "The scaling between the patch height and resized height needs to be whole number" + config.TEST.AUGMENTATIONS.PAD.HEIGHT % config.TRAIN.PATCH_SIZE == 0 + ), "The scaling between the patch height and resized height must be whole number" assert ( - config.TEST.AUGMENTATIONS.PAD.WIDTH % config.TRAIN.PATCH_SIZE != 0 - ), "The scaling between the patch width and resized height needs to be whole number" + config.TEST.AUGMENTATIONS.PAD.WIDTH % config.TRAIN.PATCH_SIZE == 0 + ), "The scaling between the patch width and resized height must be whole number" assert ( scale_height == scale_width - ), "The scaling for the height and width need to be the same" + ), "The scaling for the height and width must be the same" return int(scale_height) @@ -113,9 +98,7 @@ def _scale_from(config): ) -def _log_tensor_to_tensorboard( - images_tensor, identifier, summary_writer, evaluator -): +def _log_tensor_to_tensorboard(images_tensor, identifier, summary_writer, evaluator): image_grid = torchvision.utils.make_grid( images_tensor, normalize=False, scale_each=False, nrow=2 ) @@ -132,12 +115,13 @@ def run(*options, cfg=None): Notes: Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file + Options from default.py will be overridden by options loaded from cfg file + Options passed in via options argument will override option loaded from cfg file Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py + *options (str,int ,optional): Options used to overide what is loaded from the + config. To see what options are available consult + default.py cfg (str, optional): Location of config file to load. Defaults to None. """ @@ -145,7 +129,6 @@ def run(*options, cfg=None): logging.config.fileConfig(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK torch.manual_seed(config.SEED) @@ -226,7 +209,8 @@ def run(*options, cfg=None): log_dir=path.join(output_dir, config.LOG_DIR) ) - # weights are inversely proportional to the frequency of the classes in the training set + # weights are inversely proportional to the frequency of the classes in + # the training set class_weights = torch.tensor( config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False ) @@ -236,10 +220,7 @@ def run(*options, cfg=None): ) def _select_pred_and_mask(model_out_dict): - return ( - model_out_dict["y_pred"].squeeze(), - model_out_dict["mask"].squeeze(), - ) + return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) def _select_all(model_out_dict): return ( @@ -263,20 +244,25 @@ def _select_all(model_out_dict): model, _prepare_batch, metrics={ - "IoU": MeanIoU( - n_classes, device, output_transform=_select_pred_and_mask + "nll": Loss( + criterion, output_transform=_select_pred_and_mask, device=device ), - "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "mca": MeanClassAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "inIoU": inline_mean_iou, + "pixa": pixelwise_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "fiou": FrequencyWeightedIoU( - n_classes, device, output_transform=_select_pred_and_mask + "cacc": class_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "pixa": PixelwiseAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "mca": mean_class_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device + ), + "ciou": class_iou( + n_classes, output_transform=_select_pred_and_mask, device=device + ), + "mIoU": mean_iou( + n_classes, output_transform=_select_pred_and_mask, device=device ), - "inIoU": inline_mean_iou, }, device=device, ) @@ -286,11 +272,10 @@ def _select_all(model_out_dict): logging_handlers.log_metrics( "Test results", metrics_dict={ - "IoU": "IoU :", "nll": "Avg loss :", + "mIoU": "Avg IoU :", "pixa": "Pixelwise Accuracy :", "mca": "Mean Class Accuracy :", - "fiou": "Freq Weighted IoU :", "inIoU": "Mean Inline IoU :", }, ), @@ -302,10 +287,9 @@ def _select_all(model_out_dict): evaluator, "epoch", metrics_dict={ - "IoU": "Test/IoU", + "mIoU": "Test/IoU", "nll": "Test/Loss", "mca": "Test/MCA", - "fiou": "Test/FIoU", "inIoU": "Test/MeanInlineIoU", }, ), @@ -338,15 +322,12 @@ def _tensor_to_numpy(pred_tensor): evaluator.add_event_handler( Events.EPOCH_COMPLETED, create_image_writer( - summary_writer, - "Test/Pred", - "y_pred", - transform_func=transform_pred, + summary_writer, "Test/Pred", "y_pred", transform_func=transform_pred ), ) logger.info("Starting training") - evaluator.run(take(10, test_loader), max_epochs=1) + evaluator.run(test_loader, max_epochs=1) # Log top N and bottom N inlines in terms of IoU to tensorboard inline_ious = inline_mean_iou.iou_per_inline() diff --git a/experiments/interpretation/penobscot/local/train.py b/experiments/interpretation/penobscot/local/train.py index 82168244..58dffef9 100644 --- a/experiments/interpretation/penobscot/local/train.py +++ b/experiments/interpretation/penobscot/local/train.py @@ -10,13 +10,7 @@ import fire import numpy as np import torch -from albumentations import ( - Compose, - HorizontalFlip, - Normalize, - PadIfNeeded, - Resize, -) +from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize from ignite.contrib.handlers import CosineAnnealingScheduler from ignite.engine import Events from ignite.metrics import Loss @@ -25,9 +19,7 @@ from torch.utils import data from deepseismic_interpretation.dutchf3.data import decode_segmap -from deepseismic_interpretation.penobscot.data import ( - get_patch_dataset, -) +from deepseismic_interpretation.penobscot.data import get_patch_dataset from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -38,16 +30,17 @@ create_image_writer, create_summary_writer, ) -from cv_lib.segmentation import models +from cv_lib.segmentation import models, extract_metric_from from cv_lib.segmentation.penobscot.engine import ( create_supervised_evaluator, create_supervised_trainer, ) -from cv_lib.segmentation.dutchf3.metrics import ( - FrequencyWeightedIoU, - MeanClassAccuracy, - MeanIoU, - PixelwiseAccuracy, +from cv_lib.segmentation.metrics import ( + pixelwise_accuracy, + class_accuracy, + mean_class_accuracy, + class_iou, + mean_iou, ) from cv_lib.segmentation.dutchf3.utils import ( current_datetime, @@ -60,8 +53,6 @@ from default import _C as config from default import update_config -from cv_lib.segmentation import extract_metric_from - mask_value = 255 _SEG_COLOURS = np.asarray( [ @@ -91,12 +82,13 @@ def run(*options, cfg=None): Notes: Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file + Options loaded from default.py will be overridden by those loaded from cfg file + Options passed in via options argument will override those loaded from cfg file Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py + *options (str, int, optional): Options used to overide what is loaded from the + config. To see what options are available consult + default.py cfg (str, optional): Location of config file to load. Defaults to None. """ @@ -112,6 +104,10 @@ def run(*options, cfg=None): torch.cuda.manual_seed_all(config.SEED) np.random.seed(seed=config.SEED) + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + # Setup Augmentations basic_aug = Compose( [ @@ -189,9 +185,6 @@ def run(*options, cfg=None): model = getattr(models, config.MODEL.NAME).get_seg_model(config) - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" model = model.to(device) # Send to GPU optimizer = torch.optim.SGD( @@ -213,14 +206,11 @@ def run(*options, cfg=None): ) snapshot_duration = scheduler_step * len(train_loader) scheduler = CosineAnnealingScheduler( - optimizer, - "lr", - config.TRAIN.MAX_LR, - config.TRAIN.MIN_LR, - snapshot_duration, + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration ) - # weights are inversely proportional to the frequency of the classes in the training set + # weights are inversely proportional to the frequency of the classes in + # the training set class_weights = torch.tensor( config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False ) @@ -239,9 +229,7 @@ def run(*options, cfg=None): Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) - ) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) trainer.add_event_handler( Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), @@ -252,47 +240,38 @@ def run(*options, cfg=None): ) def _select_pred_and_mask(model_out_dict): - return ( - model_out_dict["y_pred"].squeeze(), - model_out_dict["mask"].squeeze(), - ) + return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) evaluator = create_supervised_evaluator( model, _prepare_batch, metrics={ - "IoU": MeanIoU( - n_classes, device, output_transform=_select_pred_and_mask + "pixacc": pixelwise_accuracy( + n_classes, output_transform=_select_pred_and_mask ), "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "mca": MeanClassAccuracy( - n_classes, device, output_transform=_select_pred_and_mask - ), - "fiou": FrequencyWeightedIoU( - n_classes, device, output_transform=_select_pred_and_mask - ), - "pixa": PixelwiseAccuracy( - n_classes, device, output_transform=_select_pred_and_mask + "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask), + "mca": mean_class_accuracy( + n_classes, output_transform=_select_pred_and_mask ), + "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask), + "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask), }, device=device, ) # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler( - Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader) - ) + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) evaluator.add_event_handler( Events.EPOCH_COMPLETED, logging_handlers.log_metrics( "Validation results", metrics_dict={ - "IoU": "IoU :", "nll": "Avg loss :", - "pixa": "Pixelwise Accuracy :", - "mca": "Mean Class Accuracy :", - "fiou": "Freq Weighted IoU :", + "pixacc": "Pixelwise Accuracy :", + "mca": "Avg Class Accuracy :", + "mIoU": "Avg Class IoU :", }, ), ) @@ -303,10 +282,10 @@ def _select_pred_and_mask(model_out_dict): trainer, "epoch", metrics_dict={ - "IoU": "Validation/IoU", + "mIoU": "Validation/mIoU", "nll": "Validation/Loss", "mca": "Validation/MCA", - "fiou": "Validation/FIoU", + "pixacc": "Validation/Pixel_Acc", }, ), ) @@ -332,19 +311,13 @@ def _tensor_to_numpy(pred_tensor): evaluator.add_event_handler( Events.EPOCH_COMPLETED, create_image_writer( - summary_writer, - "Validation/Mask", - "mask", - transform_func=transform_func, + summary_writer, "Validation/Mask", "mask", transform_func=transform_func ), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, create_image_writer( - summary_writer, - "Validation/Pred", - "y_pred", - transform_func=transform_pred, + summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred ), ) @@ -354,7 +327,7 @@ def snapshot_function(): checkpoint_handler = SnapshotHandler( path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, - extract_metric_from("IoU"), + extract_metric_from("mIoU"), snapshot_function, ) evaluator.add_event_handler( diff --git a/interpretation/deepseismic_interpretation/penobscot/metrics.py b/interpretation/deepseismic_interpretation/penobscot/metrics.py index 1be9a5a5..cb6e9ca9 100644 --- a/interpretation/deepseismic_interpretation/penobscot/metrics.py +++ b/interpretation/deepseismic_interpretation/penobscot/metrics.py @@ -2,12 +2,32 @@ # Licensed under the MIT License. from collections import defaultdict -from cv_lib.segmentation.dutchf3.metrics import _torch_hist from ignite.metrics import Metric import torch import numpy as np +def _torch_hist(label_true, label_pred, n_class): + """Calculates the confusion matrix for the labels + + Args: + label_true ([type]): [description] + label_pred ([type]): [description] + n_class ([type]): [description] + + Returns: + [type]: [description] + """ + # TODO Add exceptions + assert len(label_true.shape) == 1, "Labels need to be 1D" + assert len(label_pred.shape) == 1, "Predictions need to be 1D" + mask = (label_true >= 0) & (label_true < n_class) + hist = torch.bincount( + n_class * label_true[mask] + label_pred[mask], minlength=n_class ** 2 + ).reshape(n_class, n_class) + return hist + + def _default_tensor(image_height, image_width, pad_value=255): return torch.full((image_height, image_width), pad_value, dtype=torch.long) @@ -18,8 +38,8 @@ class InlineMeanIoU(Metric): """Compute Mean IoU for Inline Notes: - This metric collects all the patches and recomposes the predictions and masks into inlines - These are then used to calculate the mean IoU + This metric collects all the patches and recomposes the predictions and masks + into inlines. These are then used to calculate the mean IoU. """ def __init__( @@ -40,11 +60,16 @@ def __init__( image_width (int): width of inline patch_size (int): patch size num_classes (int): number of classes in dataset - padding (int, optional): the amount of padding to height and width, e.g 200 padded to 256 - padding=56. Defaults to 0 - scale (int, optional): the scale factor applied to the patch, e.g 100 scaled to 200 - scale=2. Defaults to 1 + padding (int, optional): the amount of padding to height and width, + e.g 200 padded to 256 - padding=56. Defaults to 0 + scale (int, optional): the scale factor applied to the patch, + e.g 100 scaled to 200 - scale=2. Defaults to 1 pad_value (int): the constant value used for padding Defaults to 255 - output_transform (callable, optional): a callable that is used to transform the ignite.engine.Engine's `process_function`'s output into the - form expected by the metric. This can be useful if, for example, you have a multi-output model and you want to compute the metric with respect to one of the outputs. + output_transform (callable, optional): a callable that is used to transform + the ignite.engine.Engine's `process_function`'s output into the form + expected by the metric. This can be useful if, for example, if you have + a multi-output model and you want to compute the metric with respect to + one of the outputs. """ self._image_height = image_height self._image_width = image_width @@ -78,7 +103,8 @@ def update(self, output): assert y.shape == max_prediction.shape, "Shape not the same" for pred, mask, id, patch_loc in zip(max_prediction, y, ids, patch_locations): - # ! With overlapping patches this does not aggregate the results it simply overwrites them + # ! With overlapping patches this does not aggregate the results, + # ! it simply overwrites them # If patch is padded ingore padding pad = int(self._padding // 2) pred = pred[pad : pred.shape[0] - pad, pad : pred.shape[1] - pad] From 47d407127f44c33dc69d8bce4fb064ead234de7d Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Thu, 7 Nov 2019 16:05:24 -0500 Subject: [PATCH 071/207] BUILD: added build setup files. (#5) --- tests/README.md | 8 ++-- .../cicd/component_governance.yml | 12 +++++- tests/cicd/main_build.yml | 37 +++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) rename azure-pipelines.yml => tests/cicd/component_governance.yml (61%) create mode 100644 tests/cicd/main_build.yml diff --git a/tests/README.md b/tests/README.md index dfe2ceca..3c873030 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,8 +2,10 @@ This project uses unit and integration tests only with Python files and notebooks: - * In the unit tests we just make sure the notebook runs. - * In the integration tests we use a bigger dataset for more epochs and we test that the metrics are what we expect. + * In the unit tests we just make sure our metrics are sane. + * In the integration tests we test that our models execute training and scoring scripts and that our notebooks all run to completion. - TODO: add more info + ## CI/CD + + You can find build configuration files in the `cicd` folder. diff --git a/azure-pipelines.yml b/tests/cicd/component_governance.yml similarity index 61% rename from azure-pipelines.yml rename to tests/cicd/component_governance.yml index 26cbc5b0..cae6b7a9 100644 --- a/azure-pipelines.yml +++ b/tests/cicd/component_governance.yml @@ -1,10 +1,19 @@ -# Starter pipeline +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Starter pipeline for legal clearance # Start with a minimal pipeline that you can customize to build and deploy your code. # Add steps that build, run tests, deploy, and more: # https://aka.ms/yaml +# Pull request against these branches will trigger this build +pr: +- master +- staging + trigger: - master +- staging pool: vmImage: 'ubuntu-latest' @@ -15,4 +24,3 @@ steps: scanType: 'Register' verbosity: 'Verbose' alertWarningLevel: 'High' - diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml new file mode 100644 index 00000000..2647defd --- /dev/null +++ b/tests/cicd/main_build.yml @@ -0,0 +1,37 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Pull request against these branches will trigger this build +pr: +- master +- staging + +# Any commit to this branch will trigger the build. +trigger: +- master +- staging + +jobs: + +- job: unit test job + timeoutInMinutes: 10 # how long to run the job before automatically cancelling + pool: + name: deepseismicagentpool + + steps: + - bash: | + echo "First step" + echo "Replace this with command" + echo "Dummy unit test job passed" + displayName: Unit Test Job + +- job: integration test job + timeoutInMinutes: 10 # how long to run the job before automatically cancelling + pool: + name: deepseismicagentpool + + steps: + - bash: | + echo "First step" + echo "Dummy integration test job passed" + displayName: Integration Test Job \ No newline at end of file From 2b57f8927fe759f0b056757ef817819b6d50e002 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Thu, 7 Nov 2019 16:09:51 -0500 Subject: [PATCH 072/207] Update main_build.yml for Azure Pipelines --- tests/cicd/main_build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 2647defd..511098df 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -13,7 +13,7 @@ trigger: jobs: -- job: unit test job +- job: uni_test_job timeoutInMinutes: 10 # how long to run the job before automatically cancelling pool: name: deepseismicagentpool @@ -25,7 +25,7 @@ jobs: echo "Dummy unit test job passed" displayName: Unit Test Job -- job: integration test job +- job: integration_test_job timeoutInMinutes: 10 # how long to run the job before automatically cancelling pool: name: deepseismicagentpool From 763c5371a5718edc0152637e1ea6095543ec3bcd Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Thu, 7 Nov 2019 16:28:41 -0500 Subject: [PATCH 073/207] BUILD: added build status badges (#6) --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 022076bc..58251629 100644 --- a/README.md +++ b/README.md @@ -150,3 +150,12 @@ This project welcomes contributions and suggestions. Most contributions require When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Build Status +| Build | Branch | Status | +| --- | --- | --- | +| **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.DeepSeismic?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=staging) | +| **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.DeepSeismic?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=master) | +| **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.DeepSeismic%20(1)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=staging) | +| **Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.DeepSeismic%20(1)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=master) | + From 145439a9742a587f4208e819a4a839818e33c482 Mon Sep 17 00:00:00 2001 From: Mat Date: Fri, 8 Nov 2019 14:45:55 +0000 Subject: [PATCH 074/207] Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py --- .gitignore | 7 +- LICENSE | 3 +- bin/ds | 6 - .../demo/local/configs/hrnet.yaml | 107 +++++ .../interpretation/demo/local/default.py | 115 ++++++ .../interpretation/demo/local/train.py | 348 +++++++++++++++++ .../interpretation/demo/local/train.sh | 2 + .../deepseismic_interpretation/data.py | 365 ++++++++++++++++++ setup.cfg | 2 - setup.py | 49 --- 10 files changed, 941 insertions(+), 63 deletions(-) delete mode 100644 bin/ds create mode 100644 experiments/interpretation/demo/local/configs/hrnet.yaml create mode 100644 experiments/interpretation/demo/local/default.py create mode 100644 experiments/interpretation/demo/local/train.py create mode 100755 experiments/interpretation/demo/local/train.sh create mode 100644 interpretation/deepseismic_interpretation/data.py delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.gitignore b/.gitignore index afd4f078..a675ed36 100644 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,7 @@ env.bak/ venv.bak/ wheels/ + .dev_env .azureml @@ -114,8 +115,4 @@ interpretation/environment/anaconda/local/src/cv-lib # Rope project settings .ropeproject -# mkdocs documentation -/site - -# mypy -.mypy_cache/ +*.pth \ No newline at end of file diff --git a/LICENSE b/LICENSE index 9e841e7a..cbf2717d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License - Copyright (c) Microsoft Corporation. + Copyright (c) Microsoft Corporation. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,3 +19,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE + diff --git a/bin/ds b/bin/ds deleted file mode 100644 index 3bd01081..00000000 --- a/bin/ds +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -from deepseismic import cli - -if __name__ == "__main__": - cli.main() diff --git a/experiments/interpretation/demo/local/configs/hrnet.yaml b/experiments/interpretation/demo/local/configs/hrnet.yaml new file mode 100644 index 00000000..5102fc31 --- /dev/null +++ b/experiments/interpretation/demo/local/configs/hrnet.yaml @@ -0,0 +1,107 @@ +CUDNN: + BENCHMARK: true + DETERMINISTIC: false + ENABLED: true +GPUS: (0,) +OUTPUT_DIR: 'outputs' +LOG_DIR: 'log' +WORKERS: 4 +PRINT_FREQ: 10 +LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf +SEED: 2019 + + +DATASET: + NUM_CLASSES: 2 + ROOT: /data/msft/seam + CLASS_WEIGHTS: [] + INLINE_HEIGHT: 876 + INLINE_WIDTH: 751 + +MODEL: + NAME: seg_hrnet + IN_CHANNELS: 3 + PRETRAINED: '/data/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + EXTRA: + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + FUSE_METHOD: SUM + STAGE4: + NUM_MODULES: 3 + NUM_BRANCHES: 4 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 48 + - 96 + - 192 + - 384 + FUSE_METHOD: SUM + +TRAIN: + COMPLETE_PATCHES_ONLY: True + BATCH_SIZE_PER_GPU: 32 + BEGIN_EPOCH: 0 + END_EPOCH: 300 + MIN_LR: 0.0001 + MAX_LR: 0.02 + MOMENTUM: 0.9 + WEIGHT_DECAY: 0.0001 + SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "none" #"patch" # Options are none, patch and section + STRIDE: 64 + PATCH_SIZE: 128 + AUGMENTATIONS: + RESIZE: + HEIGHT: 256 + WIDTH: 256 + PAD: + HEIGHT: 256 + WIDTH: 256 + MEAN: [-0.0001777, 0.49, -0.0000688] # First value is for images, second for depth and then combination of both + STD: [0.14076 , 0.2717, 0.06286] + MAX: 1 + MODEL_DIR: "models" + + +VALIDATION: + BATCH_SIZE_PER_GPU: 128 + COMPLETE_PATCHES_ONLY: True + +TEST: + COMPLETE_PATCHES_ONLY: False + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + AUGMENTATIONS: + RESIZE: + HEIGHT: 256 + WIDTH: 256 + PAD: + HEIGHT: 256 + WIDTH: 256 diff --git a/experiments/interpretation/demo/local/default.py b/experiments/interpretation/demo/local/default.py new file mode 100644 index 00000000..f4999678 --- /dev/null +++ b/experiments/interpretation/demo/local/default.py @@ -0,0 +1,115 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# ------------------------------------------------------------------------------ + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from yacs.config import CfgNode as CN + +_C = CN() + +_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models +_C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR +_C.GPUS = (0,) +_C.WORKERS = 4 +_C.PRINT_FREQ = 20 +_C.AUTO_RESUME = False +_C.PIN_MEMORY = True +# TODO: this should be loaded by automatically figuring out the file path location +_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" +_C.SEED = 42 + +# size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only +_C.WINDOW_SIZE = 65 + +# Cudnn related params +_C.CUDNN = CN() +_C.CUDNN.BENCHMARK = True +_C.CUDNN.DETERMINISTIC = False +_C.CUDNN.ENABLED = True + +# DATASET related params +_C.DATASET = CN() +_C.DATASET.ROOT = "" +_C.DATASET.NUM_CLASSES = 7 +_C.DATASET.CLASS_WEIGHTS = [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] +_C.DATASET.INLINE_HEIGHT = 1501 +_C.DATASET.INLINE_WIDTH = 481 + +# common params for NETWORK +_C.MODEL = CN() +_C.MODEL.NAME = "patch_deconvnet" +_C.MODEL.IN_CHANNELS = 1 +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) + + +# training +_C.TRAIN = CN() +_C.TRAIN.COMPLETE_PATCHES_ONLY = False +_C.TRAIN.MIN_LR = 0.001 +_C.TRAIN.MAX_LR = 0.01 +_C.TRAIN.MOMENTUM = 0.9 +_C.TRAIN.BEGIN_EPOCH = 0 +_C.TRAIN.END_EPOCH = 484 +_C.TRAIN.BATCH_SIZE_PER_GPU = 32 +_C.TRAIN.WEIGHT_DECAY = 0.0001 +_C.TRAIN.SNAPSHOTS = 5 +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR +_C.TRAIN.AUGMENTATION = True +_C.TRAIN.STRIDE = 50 +_C.TRAIN.PATCH_SIZE = 99 +_C.TRAIN.MEAN = [0.0009997] # 0.0009996710808862074 +_C.TRAIN.STD = [0.20977] # 0.20976548783479299 +_C.TRAIN.MAX = 255 +_C.TRAIN.DEPTH = 'no' # Options are None, Patch and Section +# None adds no depth information and the num of channels remains at 1 +# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 +# Section adds depth per section so contains depth information for the whole section, channels=3 +_C.TRAIN.AUGMENTATIONS = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE = CN() +_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 +_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TRAIN.AUGMENTATIONS.PAD = CN() +_C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 +_C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 + +# validation +_C.VALIDATION = CN() +_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 +_C.VALIDATION.COMPLETE_PATCHES_ONLY = False + +# TEST +_C.TEST = CN() +_C.TEST.MODEL_PATH = "" +_C.TEST.COMPLETE_PATCHES_ONLY = True +_C.TEST.AUGMENTATIONS = CN() +_C.TEST.AUGMENTATIONS.RESIZE = CN() +_C.TEST.AUGMENTATIONS.RESIZE.HEIGHT = 200 +_C.TEST.AUGMENTATIONS.RESIZE.WIDTH = 200 +_C.TEST.AUGMENTATIONS.PAD = CN() +_C.TEST.AUGMENTATIONS.PAD.HEIGHT = 256 +_C.TEST.AUGMENTATIONS.PAD.WIDTH = 256 + + +def update_config(cfg, options=None, config_file=None): + cfg.defrost() + + if config_file: + cfg.merge_from_file(config_file) + + if options: + cfg.merge_from_list(options) + + cfg.freeze() + + +if __name__ == "__main__": + import sys + + with open(sys.argv[1], "w") as f: + print(_C, file=f) + diff --git a/experiments/interpretation/demo/local/train.py b/experiments/interpretation/demo/local/train.py new file mode 100644 index 00000000..99a62011 --- /dev/null +++ b/experiments/interpretation/demo/local/train.py @@ -0,0 +1,348 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# /* spell-checker: disable */ + +import logging +import logging.config +from os import path + +import cv2 +import fire +import numpy as np +import torch +from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize +from cv_lib.event_handlers import ( + SnapshotHandler, + logging_handlers, + tensorboard_handlers, +) +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import ( + create_image_writer, + create_summary_writer, +) +from cv_lib.segmentation import models, extract_metric_from +from cv_lib.segmentation.metrics import ( + pixelwise_accuracy, + class_accuracy, + mean_class_accuracy, + class_iou, + mean_iou, +) +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, + np_to_tb, +) +from cv_lib.segmentation.penobscot.engine import ( + create_supervised_evaluator, + create_supervised_trainer, +) +from deepseismic_interpretation.data import InlinePatchDataset +from deepseismic_interpretation.dutchf3.data import decode_segmap +from default import _C as config +from default import update_config +from ignite.contrib.handlers import CosineAnnealingScheduler +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from toolz import compose +from torch.nn import functional as F +from torch.utils import data + +mask_value = 255 +_SEG_COLOURS = np.asarray( + [ + [241, 238, 246], + [208, 209, 230], + [166, 189, 219], + [116, 169, 207], + [54, 144, 192], + [5, 112, 176], + [3, 78, 123], + ] +) + + +def _prepare_batch(batch, device=None, non_blocking=False): + x, y, ids, patch_locations = batch + return ( + convert_tensor(x, device=device, non_blocking=non_blocking), + convert_tensor(y, device=device, non_blocking=non_blocking), + ids, + patch_locations, + ) + + +def run(*options, cfg=None): + """Run training and validation of model + + Notes: + Options can be passed in via the options argument and loaded from the cfg file + Options loaded from default.py will be overridden by options loaded from cfg file + Options passed in through options argument will override option loaded from cfg file + + Args: + *options (str,int ,optional): Options used to overide what is loaded from the config. + To see what options are available consult default.py + cfg (str, optional): Location of config file to load. Defaults to None. + """ + + update_config(config, options=options, config_file=cfg) + logging.config.fileConfig(config.LOG_CONFIG) + logger = logging.getLogger(__name__) + logger.debug(config.WORKERS) + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + + torch.manual_seed(config.SEED) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(config.SEED) + np.random.seed(seed=config.SEED) + + # Setup Augmentations + basic_aug = Compose( + [ + Normalize( + mean=(config.TRAIN.MEAN,), + std=(config.TRAIN.STD,), + max_pixel_value=config.TRAIN.MAX, + ), + PadIfNeeded( + min_height=config.TRAIN.PATCH_SIZE, + min_width=config.TRAIN.PATCH_SIZE, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=mask_value, + value=0, + ), + Resize( + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, + ), + PadIfNeeded( + min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, + min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=mask_value, + value=0, + ), + ] + ) + if config.TRAIN.AUGMENTATION: + train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)]) + val_aug = basic_aug + else: + train_aug = val_aug = basic_aug + + ny = 1001 + nx = 876 + nz = 751 + + img_name = "rtm3d.bin" + seam_image_name = path.join(config.DATASET.ROOT, img_name) + # load RTM image, note it is type np.float32 + img = ( + np.fromfile(seam_image_name, dtype=np.float32).reshape(ny, nx, nz) + / 71892250000.0 + ) + + salt_name = "salt_mask.bin" + salt_mask_name = path.join(config.DATASET.ROOT, salt_name) + # load salt mask, note it is type np.int16 + mask = np.fromfile(salt_mask_name, dtype=np.int16).reshape(ny, nx, nz) + + train_set = InlinePatchDataset( + img, + mask, + config.TRAIN.PATCH_SIZE, + config.TRAIN.STRIDE, + split="train", + transforms=train_aug, + n_channels=config.MODEL.IN_CHANNELS, + complete_patches_only=config.TRAIN.COMPLETE_PATCHES_ONLY, + ) + + val_set = InlinePatchDataset( + img, + mask, + config.TRAIN.PATCH_SIZE, + config.TRAIN.STRIDE, + split="val", + transforms=val_aug, + n_channels=config.MODEL.IN_CHANNELS, + complete_patches_only=config.VALIDATION.COMPLETE_PATCHES_ONLY, + ) + logger.info(train_set) + logger.info(val_set) + n_classes = train_set.n_classes + + train_loader = data.DataLoader( + train_set, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + shuffle=True, + ) + + val_loader = data.DataLoader( + val_set, + batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + ) + + model = getattr(models, config.MODEL.NAME).get_seg_model(config) + + device = "cpu" + if torch.cuda.is_available(): + device = "cuda" + model = model.to(device) # Send to GPU + + optimizer = torch.optim.SGD( + model.parameters(), + lr=config.TRAIN.MAX_LR, + momentum=config.TRAIN.MOMENTUM, + weight_decay=config.TRAIN.WEIGHT_DECAY, + ) + + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) + summary_writer = create_summary_writer( + log_dir=path.join(output_dir, config.LOG_DIR) + ) + snapshot_duration = scheduler_step * len(train_loader) + scheduler = CosineAnnealingScheduler( + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration + ) + + criterion = torch.nn.CrossEntropyLoss(ignore_index=mask_value, reduction="mean") + + trainer = create_supervised_trainer( + model, optimizer, criterion, _prepare_batch, device=device + ) + + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + ) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + trainer.add_event_handler( + Events.EPOCH_STARTED, + tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + ) + trainer.add_event_handler( + Events.ITERATION_COMPLETED, + tensorboard_handlers.log_training_output(summary_writer), + ) + + def _select_pred_and_mask(model_out_dict): + return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) + + evaluator = create_supervised_evaluator( + model, + _prepare_batch, + metrics={ + "pixacc": pixelwise_accuracy( + n_classes, output_transform=_select_pred_and_mask + ), + "nll": Loss(criterion, output_transform=_select_pred_and_mask), + "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask), + "mca": mean_class_accuracy( + n_classes, output_transform=_select_pred_and_mask + ), + "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask), + "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask), + }, + device=device, + ) + + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + logging_handlers.log_metrics( + "Validation results", + metrics_dict={ + "nll": "Avg loss :", + "pixacc": "Pixelwise Accuracy :", + "mca": "Avg Class Accuracy :", + "mIoU": "Avg Class IoU :", + }, + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + tensorboard_handlers.log_metrics( + summary_writer, + trainer, + "epoch", + metrics_dict={ + "mIoU": "Validation/mIoU", + "nll": "Validation/Loss", + "mca": "Validation/MCA", + "pixacc": "Validation/Pixel_Acc", + }, + ), + ) + + def _select_max(pred_tensor): + return pred_tensor.max(1)[1] + + def _tensor_to_numpy(pred_tensor): + return pred_tensor.squeeze().cpu().numpy() + + transform_func = compose( + np_to_tb, + decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), + _tensor_to_numpy, + ) + + transform_pred = compose(transform_func, _select_max) + + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer(summary_writer, "Validation/Image", "image"), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, "Validation/Mask", "mask", transform_func=transform_func + ), + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, + create_image_writer( + summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred + ), + ) + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + checkpoint_handler = SnapshotHandler( + path.join(output_dir, config.TRAIN.MODEL_DIR), + config.MODEL.NAME, + extract_metric_from("mIoU"), + snapshot_function, + ) + evaluator.add_event_handler( + Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} + ) + + logger.info("Starting training") + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + +if __name__ == "__main__": + fire.Fire(run) diff --git a/experiments/interpretation/demo/local/train.sh b/experiments/interpretation/demo/local/train.sh new file mode 100755 index 00000000..b9bb9338 --- /dev/null +++ b/experiments/interpretation/demo/local/train.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python train.py --cfg "configs/hrnet.yaml" \ No newline at end of file diff --git a/interpretation/deepseismic_interpretation/data.py b/interpretation/deepseismic_interpretation/data.py new file mode 100644 index 00000000..ae4f1a31 --- /dev/null +++ b/interpretation/deepseismic_interpretation/data.py @@ -0,0 +1,365 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import itertools +import math +from collections import defaultdict + +import numpy as np +import torch +from PIL import Image +from toolz import compose, curry +from toolz import partition_all +from torch.utils.data import Dataset +from torchvision.datasets.utils import iterable_to_str, verify_str_arg + +_open_to_array = compose(np.array, Image.open) + + +class DataNotSplitException(Exception): + pass + + +def _get_classes_and_counts(mask_list): + class_counts_dict = defaultdict(int) + for mask in mask_list: + for class_label, class_count in zip(*np.unique(mask, return_counts=True)): + class_counts_dict[class_label] += class_count + return list(class_counts_dict.keys()), list(class_counts_dict.values()) + + +def _combine(mask_array): + """Combine classes 2 and 3. Reduce all classes above 3 by one + """ + mask_array[np.logical_or(mask_array == 2, mask_array == 3)] = 2 + for i in filter(lambda x: x > 3, np.unique(mask_array)): + mask_array[mask_array == i] = i - 1 + return mask_array + + +def _combine_classes(mask_array_list): + """Combine classes + + Segmentation implementations using this dataset seem to combine + classes 2 and 3 so we are doing the same here and then relabeling the rest + + Args: + mask_array_list (list): list of mask (numpy.Array) + """ + return [_combine(mask_array.copy()) for mask_array in mask_array_list] + + +def _replicate_channels(image_array, n_channels): + new_image_array = np.zeros((n_channels, image_array.shape[0], image_array.shape[1])) + for i in range(n_channels): + new_image_array[i] = image_array + return new_image_array + + +def _number_patches_in(height_or_width, patch_size, stride, complete_patches_only=True): + strides_in_hw = (height_or_width - patch_size) / stride + if complete_patches_only: + return int(np.floor(strides_in_hw)) + else: + return int(np.ceil(strides_in_hw)) + + +def _is_2D(numpy_array): + return len(numpy_array.shape) == 2 + + +def _is_3D(numpy_array): + return len(numpy_array.shape) == 3 + + +@curry +def _extract_patches(patch_size, stride, complete_patches_only, img_array, mask_array): + height, width = img_array.shape[-2], img_array.shape[-1] + num_h_patches = _number_patches_in( + height, patch_size, stride, complete_patches_only=complete_patches_only + ) + num_w_patches = _number_patches_in( + width, patch_size, stride, complete_patches_only=complete_patches_only + ) + height_iter = range(0, stride * (num_h_patches + 1), stride) + width_iter = range(0, stride * (num_w_patches + 1), stride) + patch_locations = list(itertools.product(height_iter, width_iter)) + + image_patch_generator = _generate_patches_for( + img_array, patch_locations, patch_size + ) + mask_patch_generator = _generate_patches_for( + mask_array, patch_locations, patch_size + ) + return image_patch_generator, mask_patch_generator, patch_locations + + +def _generate_patches_for(numpy_array, patch_locations, patch_size): + if _is_2D(numpy_array): + generate = _generate_patches_from_2D + elif _is_3D(numpy_array): + generate = _generate_patches_from_3D + else: + raise ValueError("Array is not 2D or 3D") + return generate(numpy_array, patch_locations, patch_size) + + +def _generate_patches_from_2D(numpy_array, patch_locations, patch_size): + return ( + numpy_array[h : h + patch_size, w : w + patch_size].copy() + for h, w in patch_locations + ) + + +def _generate_patches_from_3D(numpy_array, patch_locations, patch_size): + return ( + numpy_array[:, h : h + patch_size, w : w + patch_size].copy() + for h, w in patch_locations + ) + + +_STATS_FUNCS = {"mean": np.mean, "std": np.std, "max": np.max} + + +def _transform_CHW_to_HWC(numpy_array): + return np.moveaxis(numpy_array, 0, -1) + + +def _transform_HWC_to_CHW(numpy_array): + return np.moveaxis(numpy_array, -1, 0) + + +def _rescale(numpy_array): + """ Rescale the numpy array by 10000. The maximum value achievable is 32737 + This will bring the values between -n and n + """ + return numpy_array / 10000 + + +def _split_train_val_test(partition, val_ratio, test_ratio): + total_samples = len(partition) + val_samples = math.floor(val_ratio * total_samples) + test_samples = math.floor(test_ratio * total_samples) + train_samples = total_samples - (val_samples + test_samples) + train_list = partition[:train_samples] + val_list = partition[train_samples : train_samples + val_samples] + test_list = partition[ + train_samples + val_samples : train_samples + val_samples + test_samples + ] + return train_list, val_list, test_list + + +class InlinePatchDataset(Dataset): + """Dataset that returns patches from the numpy dataset + + Notes: + Loads inlines only and splits into patches + """ + + _repr_indent = 4 + + def __init__( + self, + data_array, + mask_array, + patch_size, + stride, + split="train", + transforms=None, + max_inlines=None, + n_channels=1, + complete_patches_only=True, + val_ratio=0.1, + test_ratio=0.2, + ): + """Initialise Numpy Dataset + + Args: + data_array (numpy.Array): a 3D numpy array that contain the seismic info + mask_array (numpy.Array): a 3D numpy array that contains the labels + patch_size (int): the size of the patch in pixels + stride (int): the stride applied when extracting patches + split (str, optional): what split to load, (train, val, test). Defaults to `train` + transforms (albumentations.augmentations.transforms, optional): albumentation transforms to apply to patches. Defaults to None + exclude_files (list[str], optional): list of files to exclude. Defaults to None + max_inlines (int, optional): maximum number of inlines to load. Defaults to None + n_channels (int, optional): number of channels that the output should contain. Defaults to 3 + complete_patches_only (bool, optional): whether to load incomplete patches that are padded to patch_size. Defaults to True + val_ratio (float): ratio to use for validation. Defaults to 0.1 + test_ratio (float): ratio to use for test. Defaults to 0.2 + """ + + super(InlinePatchDataset, self).__init__() + self._data_array = data_array + self._slice_mask_array = mask_array + self._split = split + self._max_inlines = max_inlines + self._n_channels = n_channels + self._complete_patches_only = complete_patches_only + self._patch_size = patch_size + self._stride = stride + self._image_array = [] + self._mask_array = [] + self._ids = [] + self._patch_locations = [] + + self.transforms = transforms + + valid_modes = ("train", "test", "val") + msg = "Unknown value '{}' for argument split. " "Valid values are {{{}}}." + msg = msg.format(split, iterable_to_str(valid_modes)) + verify_str_arg(split, "split", valid_modes, msg) + + # Set the patch and stride for the patch extractor + _extract_patches_from = _extract_patches( + patch_size, stride, self._complete_patches_only + ) + num_partitions = 5 + indexes = self._data_array.shape[0] + num_elements = math.ceil(indexes / num_partitions) + train_indexes_list = [] + test_indexes_list = [] + val_indexes_list = [] + + for partition in partition_all( + num_elements, range(indexes) + ): # Partition files into N partitions + train_indexes, val_indexes, test_indexes = _split_train_val_test( + partition, val_ratio, test_ratio + ) + train_indexes_list.extend(train_indexes) + test_indexes_list.extend(test_indexes) + val_indexes_list.extend(val_indexes) + + if split == "train": + indexes = train_indexes_list + elif split == "val": + indexes = val_indexes_list + elif split == "test": + indexes = test_indexes_list + + # Extract patches + for index in indexes: + img_array = self._data_array[index] + mask_array = self._slice_mask_array[index] + self._ids.append(index) + image_generator, mask_generator, patch_locations = _extract_patches_from( + img_array, mask_array + ) + self._patch_locations.extend(patch_locations) + + self._image_array.extend(image_generator) + + self._mask_array.extend(mask_generator) + + assert len(self._image_array) == len( + self._patch_locations + ), "The shape is not the same" + + assert ( + len(self._patch_locations) % len(self._ids) == 0 + ), "Something is wrong with the patches" + + self._patches_per_image = int(len(self._patch_locations) / len(self._ids)) + + self._classes, self._class_counts = _get_classes_and_counts(self._mask_array) + + def __len__(self): + return len(self._image_array) + + @property + def n_classes(self): + return len(self._classes) + + @property + def class_proportions(self): + total = np.sum(self._class_counts) + return [(i, w / total) for i, w in zip(self._classes, self._class_counts)] + + def _add_extra_channels(self, image): + if self._n_channels > 1: + image = _replicate_channels(image, self._n_channels) + return image + + def __getitem__(self, index): + image, target, ids, patch_locations = ( + self._image_array[index], + self._mask_array[index], + self._ids[index // self._patches_per_image], + self._patch_locations[index], + ) + + image = self._add_extra_channels(image) + if _is_2D(image): + image = np.expand_dims(image, 0) + + if self.transforms is not None: + image = _transform_CHW_to_HWC(image) + augmented_dict = self.transforms(image=image, mask=target) + image, target = augmented_dict["image"], augmented_dict["mask"] + image = _transform_HWC_to_CHW(image) + + target = np.expand_dims(target, 0) + + return ( + torch.from_numpy(image).float(), + torch.from_numpy(target).long(), + ids, + np.array(patch_locations), + ) + + @property + def statistics(self): + flat_image_array = np.concatenate([i.flatten() for i in self._image_array]) + stats = { + stat: statfunc(flat_image_array) for stat, statfunc in _STATS_FUNCS.items() + } + return "Mean: {mean} Std: {std} Max: {max}".format(**stats) + + def __repr__(self): + head = "Dataset " + self.__class__.__name__ + body = ["Number of datapoints: {}".format(self.__len__())] + body += self.extra_repr().splitlines() + if hasattr(self, "transforms") and self.transforms is not None: + body += [repr(self.transforms)] + lines = [head] + [" " * self._repr_indent + line for line in body] + return "\n".join(lines) + + def _format_transform_repr(self, transform, head): + lines = transform.__repr__().splitlines() + return ["{}{}".format(head, lines[0])] + [ + "{}{}".format(" " * len(head), line) for line in lines[1:] + ] + + def extra_repr(self): + lines = [ + "Split: {_split}", + "Patch size: {_patch_size}", + "Stride: {_stride}", + "Max inlines: {_max_inlines}", + "Num channels: {_n_channels}", + f"Num classes: {self.n_classes}", + f"Class proportions: {self.class_proportions}", + "Complete patches only: {_complete_patches_only}", + f"Dataset statistics: {self.statistics}", + ] + return "\n".join(lines).format(**self.__dict__) + + +_TRAIN_PATCH_DATASETS = {"none": InlinePatchDataset} + + +def get_patch_dataset(cfg): + """ Return the Dataset class for Numpy Array + + Args: + cfg: yacs config + + Returns: + InlinePatchDataset + """ + assert str(cfg.TRAIN.DEPTH).lower() in [ + "none" + ], f"Depth {cfg.TRAIN.DEPTH} not supported for patch data. \ + Valid values: section, patch, none." + return _TRAIN_PATCH_DATASETS.get(cfg.TRAIN.DEPTH, InlinePatchDataset) diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b7e47898..00000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[aliases] -test=pytest diff --git a/setup.py b/setup.py deleted file mode 100644 index 8c3f73af..00000000 --- a/setup.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -import setuptools - -with open("README.md", "r") as f: - long_description = f.read() - -setuptools.setup( - author="DeepSeismic Maintainers", - author_email="deepseismic@microsoft.com", - classifiers=[ - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Topic :: Scientific/Engineering", - "Topic :: Software Development", - ], - dependency_links=[ - "https://github.com/opesci/devito/archive/v3.5.tar.gz#egg=devito-3.5" - ], - description="DeepSeismic", - install_requires=[ - "click==7.0", - "devito==3.5", - "h5py==2.9.0", - "numpy==1.17.0", - "scipy==1.3.0", - "sympy==1.4", - ], - license="MIT", - long_description=long_description, - long_description_content_type="text/markdown", - name="deepseismic", - packages=setuptools.find_packages(include=["deepseismic", "deepseismic.*"]), - platforms="any", - python_requires=">= 3.5", - scripts=["bin/ds"], - setup_requires=["pytest-runner"], - tests_require=["pytest"], - url="https://github.com/microsoft/deepseismic", - version="0.1.0", - zip_safe=False, -) From 246e88ebb3c09a2395b937776422d128043c028e Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Sat, 9 Nov 2019 14:06:34 +0000 Subject: [PATCH 075/207] Log config file now experiment specific (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments --- WORKERS | 2 +- cv_lib/cv_lib/utils.py | 23 ++++++ ..._block_training_and_evaluation_local.ipynb | 3 +- .../notebooks/configs/hrnet.yaml | 2 +- .../configs/patch_deconvnet_skip.yaml | 2 +- .../notebooks/configs/unet.yaml | 2 +- .../demo/local/configs/hrnet.yaml | 2 +- .../interpretation/demo/local/default.py | 25 ++++--- .../interpretation/demo/local/logging.conf | 0 .../interpretation/demo/local/train.py | 5 +- .../distributed/configs/hrnet.yaml | 2 +- .../distributed/configs/patch_deconvnet.yaml | 2 +- .../configs/patch_deconvnet_skip.yaml | 2 +- .../distributed/configs/seresnet_unet.yaml | 2 +- .../distributed/configs/unet.yaml | 2 +- .../dutchf3_patch/distributed/default.py | 19 ++--- .../dutchf3_patch/distributed/logging.conf | 34 +++++++++ .../dutchf3_patch/distributed/train.py | 5 +- .../dutchf3_patch/local/configs/hrnet.yaml | 2 +- .../local/configs/patch_deconvnet.yaml | 2 +- .../local/configs/patch_deconvnet_skip.yaml | 2 +- .../local/configs/seresnet_unet.yaml | 2 +- .../local/configs/texture_net.yaml | 2 +- .../dutchf3_patch/local/configs/unet.yaml | 2 +- .../dutchf3_patch/local/default.py | 25 ++++--- .../dutchf3_patch/local/logging.conf | 34 +++++++++ .../dutchf3_patch/local/test.py | 75 +++++-------------- .../dutchf3_patch/local/train.py | 5 +- .../local/configs/section_deconvnet_skip.yaml | 4 +- .../dutchf3_section/local/default.py | 15 ++-- .../dutchf3_section/local/logging.conf | 34 +++++++++ .../dutchf3_section/local/test.py | 62 +++++---------- .../dutchf3_section/local/train.py | 6 +- .../dutchf3_voxel/configs/texture_net.yaml | 2 +- .../interpretation/dutchf3_voxel/default.py | 7 +- .../interpretation/dutchf3_voxel/logging.conf | 34 +++++++++ .../interpretation/dutchf3_voxel/train.py | 6 +- .../penobscot/local/configs/hrnet.yaml | 2 +- .../local/configs/seresnet_unet.yaml | 2 +- .../interpretation/penobscot/local/default.py | 7 +- .../penobscot/local/logging.conf | 34 +++++++++ .../interpretation/penobscot/local/test.py | 5 +- .../interpretation/penobscot/local/train.py | 5 +- .../interpretation/voxel2pixel/data.py | 35 +++------ 44 files changed, 346 insertions(+), 199 deletions(-) create mode 100644 cv_lib/cv_lib/utils.py rename logging.conf => experiments/interpretation/demo/local/logging.conf (100%) create mode 100644 experiments/interpretation/dutchf3_patch/distributed/logging.conf create mode 100644 experiments/interpretation/dutchf3_patch/local/logging.conf create mode 100644 experiments/interpretation/dutchf3_section/local/logging.conf create mode 100644 experiments/interpretation/dutchf3_voxel/logging.conf create mode 100644 experiments/interpretation/penobscot/local/logging.conf diff --git a/WORKERS b/WORKERS index 10439679..633ed717 100644 --- a/WORKERS +++ b/WORKERS @@ -8,7 +8,7 @@ DATASET: NUM_CLASSES: 6 ROOT: GPUS: (0,) -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf LOG_DIR: MODEL: IN_CHANNELS: 1 diff --git a/cv_lib/cv_lib/utils.py b/cv_lib/cv_lib/utils.py new file mode 100644 index 00000000..e06ea5cf --- /dev/null +++ b/cv_lib/cv_lib/utils.py @@ -0,0 +1,23 @@ +import os +import logging + + +def load_log_configuration(log_config_file): + """ + Loads logging configuration from the given configuration file. + """ + if not os.path.exists(log_config_file) or not os.path.isfile(log_config_file): + msg = "%s configuration file does not exist!", log_config_file + logging.getLogger(__name__).error(msg) + raise ValueError(msg) + try: + logging.config.fileConfig(log_config_file, disable_existing_loggers=False) + logging.getLogger(__name__).info( + "%s configuration file was loaded.", log_config_file + ) + except Exception as e: + logging.getLogger(__name__).error( + "Failed to load configuration from %s!", log_config_file + ) + logging.getLogger(__name__).debug(str(e), exc_info=True) + raise e diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index e6cfd60b..afcaf36d 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -78,6 +78,7 @@ "from toolz import compose\n", "from torch.utils import data\n", "\n", + "from cv_lib.utils import load_log_configuration\n", "from cv_lib.event_handlers import SnapshotHandler, logging_handlers\n", "from cv_lib.segmentation import models, tgs_salt\n", "from cv_lib.segmentation.dutchf3.engine import create_supervised_trainer\n", @@ -259,7 +260,7 @@ "outputs": [], "source": [ "# Set up logging\n", - "logging.config.fileConfig(config.LOG_CONFIG)\n", + "load_log_configuration(config.LOG_CONFIG)\n", "logger = logging.getLogger(__name__)\n", "logger.debug(config.WORKERS)\n", "torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK\n" diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index b5c1f7f9..813a9797 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 50 -LOG_CONFIG: /home/vapaunic/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml index d671dce8..8eda77c3 100644 --- a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml +++ b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 50 -LOG_CONFIG: /data/home/vapaunic/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 DATASET: diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml index 06f3cbf6..8e5e5203 100644 --- a/examples/interpretation/notebooks/configs/unet.yaml +++ b/examples/interpretation/notebooks/configs/unet.yaml @@ -9,7 +9,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 50 -LOG_CONFIG: /home/vapaunic/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/demo/local/configs/hrnet.yaml b/experiments/interpretation/demo/local/configs/hrnet.yaml index 5102fc31..db735b56 100644 --- a/experiments/interpretation/demo/local/configs/hrnet.yaml +++ b/experiments/interpretation/demo/local/configs/hrnet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'outputs' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/demo/local/default.py b/experiments/interpretation/demo/local/default.py index f4999678..ceec54c1 100644 --- a/experiments/interpretation/demo/local/default.py +++ b/experiments/interpretation/demo/local/default.py @@ -11,15 +11,14 @@ _C = CN() -_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models +_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.GPUS = (0,) _C.WORKERS = 4 _C.PRINT_FREQ = 20 _C.AUTO_RESUME = False _C.PIN_MEMORY = True -# TODO: this should be loaded by automatically figuring out the file path location -_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" +_C.LOG_CONFIG = "logging.conf" _C.SEED = 42 # size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only @@ -35,7 +34,15 @@ _C.DATASET = CN() _C.DATASET.ROOT = "" _C.DATASET.NUM_CLASSES = 7 -_C.DATASET.CLASS_WEIGHTS = [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] +_C.DATASET.CLASS_WEIGHTS = [ + 0.02630481, + 0.05448931, + 0.0811898, + 0.01866496, + 0.15868563, + 0.0875993, + 0.5730662, +] _C.DATASET.INLINE_HEIGHT = 1501 _C.DATASET.INLINE_WIDTH = 481 @@ -58,14 +65,14 @@ _C.TRAIN.BATCH_SIZE_PER_GPU = 32 _C.TRAIN.WEIGHT_DECAY = 0.0001 _C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR _C.TRAIN.AUGMENTATION = True _C.TRAIN.STRIDE = 50 _C.TRAIN.PATCH_SIZE = 99 -_C.TRAIN.MEAN = [0.0009997] # 0.0009996710808862074 -_C.TRAIN.STD = [0.20977] # 0.20976548783479299 -_C.TRAIN.MAX = 255 -_C.TRAIN.DEPTH = 'no' # Options are None, Patch and Section +_C.TRAIN.MEAN = [0.0009997] # 0.0009996710808862074 +_C.TRAIN.STD = [0.20977] # 0.20976548783479299 +_C.TRAIN.MAX = 255 +_C.TRAIN.DEPTH = "no" # Options are None, Patch and Section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 diff --git a/logging.conf b/experiments/interpretation/demo/local/logging.conf similarity index 100% rename from logging.conf rename to experiments/interpretation/demo/local/logging.conf diff --git a/experiments/interpretation/demo/local/train.py b/experiments/interpretation/demo/local/train.py index 99a62011..37e8f0a8 100644 --- a/experiments/interpretation/demo/local/train.py +++ b/experiments/interpretation/demo/local/train.py @@ -11,6 +11,7 @@ import numpy as np import torch from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize +from cv_lib.utils import load_log_configuration from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -91,7 +92,9 @@ def run(*options, cfg=None): """ update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) + + # Start logging + load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml index 2fdf6d9d..04ad6479 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml +++ b/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml index ca56f9b4..7f33b7f7 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml +++ b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml index d0450cc5..c6c06481 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml +++ b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 DATASET: diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml index 24b8ee36..d0b8126f 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml +++ b/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml index 74fdc13a..2843e62c 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml +++ b/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml @@ -9,7 +9,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/dutchf3_patch/distributed/default.py b/experiments/interpretation/dutchf3_patch/distributed/default.py index 1b218be9..d42e4716 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/default.py +++ b/experiments/interpretation/dutchf3_patch/distributed/default.py @@ -18,7 +18,7 @@ _C.PRINT_FREQ = 20 _C.AUTO_RESUME = False _C.PIN_MEMORY = True -_C.LOG_CONFIG = "/data/home/mat/repos/DeepSeismic/logging.conf" +_C.LOG_CONFIG = "logging.conf" _C.SEED = 42 # Cudnn related params @@ -56,9 +56,9 @@ _C.TRAIN.AUGMENTATION = True _C.TRAIN.STRIDE = 50 _C.TRAIN.PATCH_SIZE = 99 -_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 -_C.TRAIN.STD = 0.21 # 0.20976548783479299 -_C.TRAIN.DEPTH = 'None' # Options are None, Patch and Section +_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 +_C.TRAIN.STD = 0.21 # 0.20976548783479299 +_C.TRAIN.DEPTH = "None" # Options are None, Patch and Section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 @@ -79,12 +79,14 @@ _C.TEST = CN() _C.TEST.MODEL_PATH = "" _C.TEST.TEST_STRIDE = 10 -_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 +_C.TEST.SPLIT = "Both" # Can be Both, Test1, Test2 _C.TEST.INLINE = True _C.TEST.CROSSLINE = True -_C.TEST.POST_PROCESSING = CN() # Model output postprocessing -_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels -_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right +_C.TEST.POST_PROCESSING = CN() # Model output postprocessing +_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels +_C.TEST.POST_PROCESSING.CROP_PIXELS = ( + 14 # Number of pixels to crop top, bottom, left and right +) def update_config(cfg, options=None, config_file=None): @@ -105,4 +107,3 @@ def update_config(cfg, options=None, config_file=None): with open(sys.argv[1], "w") as f: print(_C, file=f) - diff --git a/experiments/interpretation/dutchf3_patch/distributed/logging.conf b/experiments/interpretation/dutchf3_patch/distributed/logging.conf new file mode 100644 index 00000000..56334fc4 --- /dev/null +++ b/experiments/interpretation/dutchf3_patch/distributed/logging.conf @@ -0,0 +1,34 @@ +[loggers] +keys=root,__main__,event_handlers + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[logger___main__] +level=INFO +handlers=consoleHandler +qualname=__main__ +propagate=0 + +[logger_event_handlers] +level=INFO +handlers=consoleHandler +qualname=event_handlers +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py index 3eab5dfb..51b07d6f 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -17,6 +17,7 @@ import numpy as np import torch from albumentations import Compose, HorizontalFlip, Normalize, Resize, PadIfNeeded +from cv_lib.utils import load_log_configuration from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -92,7 +93,9 @@ def run(*options, cfg=None, local_rank=0): cfg (str, optional): Location of config file to load. Defaults to None. """ update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) + + # Start logging + load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) silence_other_ranks = True diff --git a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml index 01f37893..c1964c98 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml index ca56f9b4..7f33b7f7 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml index cf03ff37..d82231e9 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 DATASET: diff --git a/experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml index 24b8ee36..d0b8126f 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /data/home/mat/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml b/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml index bf370c9e..e218d854 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml @@ -9,7 +9,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 WINDOW_SIZE: 65 diff --git a/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml index e3c2e312..c31157bf 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml @@ -9,7 +9,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/dutchf3_patch/local/default.py b/experiments/interpretation/dutchf3_patch/local/default.py index 15d355d6..d28cc268 100644 --- a/experiments/interpretation/dutchf3_patch/local/default.py +++ b/experiments/interpretation/dutchf3_patch/local/default.py @@ -11,15 +11,16 @@ _C = CN() -_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models +_C.OUTPUT_DIR = ( + "output" # The base directory for all output, such as logs and saved models +) _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.GPUS = (0,) _C.WORKERS = 4 _C.PRINT_FREQ = 20 _C.AUTO_RESUME = False _C.PIN_MEMORY = True -# TODO: this should be loaded by automatically figuring out the file path location -_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" +_C.LOG_CONFIG = "logging.conf" _C.SEED = 42 @@ -53,13 +54,13 @@ _C.TRAIN.BATCH_SIZE_PER_GPU = 32 _C.TRAIN.WEIGHT_DECAY = 0.0001 _C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR _C.TRAIN.AUGMENTATION = True _C.TRAIN.STRIDE = 50 _C.TRAIN.PATCH_SIZE = 99 -_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 -_C.TRAIN.STD = 0.20977 # 0.20976548783479299 # TODO: Should we apply std scaling? -_C.TRAIN.DEPTH = 'no' # Options are None, Patch and Section +_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 +_C.TRAIN.STD = 0.20977 # 0.20976548783479299 # TODO: Should we apply std scaling? +_C.TRAIN.DEPTH = "no" # Options are None, Patch and Section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 @@ -79,12 +80,14 @@ _C.TEST = CN() _C.TEST.MODEL_PATH = "" _C.TEST.TEST_STRIDE = 10 -_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 +_C.TEST.SPLIT = "Both" # Can be Both, Test1, Test2 _C.TEST.INLINE = True _C.TEST.CROSSLINE = True -_C.TEST.POST_PROCESSING = CN() # Model output postprocessing -_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels -_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right +_C.TEST.POST_PROCESSING = CN() # Model output postprocessing +_C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels +_C.TEST.POST_PROCESSING.CROP_PIXELS = ( + 14 # Number of pixels to crop top, bottom, left and right +) def update_config(cfg, options=None, config_file=None): diff --git a/experiments/interpretation/dutchf3_patch/local/logging.conf b/experiments/interpretation/dutchf3_patch/local/logging.conf new file mode 100644 index 00000000..56334fc4 --- /dev/null +++ b/experiments/interpretation/dutchf3_patch/local/logging.conf @@ -0,0 +1,34 @@ +[loggers] +keys=root,__main__,event_handlers + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[logger___main__] +level=INFO +handlers=consoleHandler +qualname=__main__ +propagate=0 + +[logger_event_handlers] +level=INFO +handlers=consoleHandler +qualname=event_handlers +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index a68c3618..86ceceed 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -20,6 +20,7 @@ import torch import torch.nn.functional as F from albumentations import Compose, Normalize, PadIfNeeded, Resize +from cv_lib.utils import load_log_configuration from cv_lib.segmentation import models from deepseismic_interpretation.dutchf3.data import ( add_patch_depth_channels, @@ -71,9 +72,7 @@ def get_scores(self): acc = np.diag(hist).sum() / hist.sum() acc_cls = np.diag(hist) / hist.sum(axis=1) mean_acc_cls = np.nanmean(acc_cls) - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) + iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) mean_iu = np.nanmean(iu) freq = ( hist.sum(axis=1) / hist.sum() @@ -156,14 +155,10 @@ def _expand_dims_if_necessary(torch_tensor): @curry def _extract_patch(hdx, wdx, ps, patch_size, img_p): if len(img_p.shape) == 2: # 2D - return img_p[ - hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size - ] + return img_p[hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size] else: # 3D return img_p[ - :, - hdx + ps : hdx + ps + patch_size, - wdx + ps : wdx + ps + patch_size, + :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size, ] @@ -183,13 +178,10 @@ def _compose_processing_pipeline(depth, aug=None): def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): hdc_wdx_generator = itertools.product( - range(0, h - patch_size + ps, stride), - range(0, w - patch_size + ps, stride), + range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride), ) - for batch_indexes in itertoolz.partition_all( - batch_size, hdc_wdx_generator - ): + for batch_indexes in itertoolz.partition_all(batch_size, hdc_wdx_generator): yield batch_indexes @@ -197,16 +189,10 @@ def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): def _output_processing_pipeline(config, output): output = output.unsqueeze(0) _, _, h, w = output.shape - if ( - config.TEST.POST_PROCESSING.SIZE != h - or config.TEST.POST_PROCESSING.SIZE != w - ): + if config.TEST.POST_PROCESSING.SIZE != h or config.TEST.POST_PROCESSING.SIZE != w: output = F.interpolate( output, - size=( - config.TEST.POST_PROCESSING.SIZE, - config.TEST.POST_PROCESSING.SIZE, - ), + size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE,), mode="bilinear", ) @@ -250,20 +236,14 @@ def _patch_label_2d( ): batch = torch.stack( [ - pipe( - img_p, - _extract_patch(hdx, wdx, ps, patch_size), - pre_processing, - ) + pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing,) for hdx, wdx in batch_indexes ], dim=0, ) model_output = model(batch.to(device)) - for (hdx, wdx), output in zip( - batch_indexes, model_output.detach().cpu() - ): + for (hdx, wdx), output in zip(batch_indexes, model_output.detach().cpu()): output = output_processing(output) output_p[ :, @@ -287,9 +267,7 @@ def to_image(label_mask, n_classes=6): r[label_mask == ll] = label_colours[ll, 0] g[label_mask == ll] = label_colours[ll, 1] b[label_mask == ll] = label_colours[ll, 2] - rgb = np.zeros( - (label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3) - ) + rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3)) rgb[:, :, :, 0] = r rgb[:, :, :, 1] = g rgb[:, :, :, 2] = b @@ -310,10 +288,7 @@ def _evaluate_split( TestSectionLoader = get_test_loader(config) test_set = TestSectionLoader( - config.DATASET.ROOT, - split=split, - is_transform=True, - augmentations=section_aug, + config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug, ) n_classes = test_set.n_classes @@ -354,9 +329,7 @@ def _evaluate_split( # Log split results logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') for cdx, class_name in enumerate(_CLASS_NAMES): - logger.info( - f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' - ) + logger.info(f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}') logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') @@ -390,7 +363,9 @@ def _write_section_file(labels, section_file): def test(*options, cfg=None): update_config(config, options=options, config_file=cfg) n_classes = config.DATASET.NUM_CLASSES - logging.config.fileConfig(config.LOG_CONFIG) + + # Start logging + load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") log_dir, model_name = os.path.split(config.TEST.MODEL_PATH) @@ -406,9 +381,7 @@ def test(*options, cfg=None): section_aug = Compose( [ Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, + mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1, ) ] ) @@ -430,16 +403,10 @@ def test(*options, cfg=None): ] ) - pre_processing = _compose_processing_pipeline( - config.TRAIN.DEPTH, aug=patch_aug - ) + pre_processing = _compose_processing_pipeline(config.TRAIN.DEPTH, aug=patch_aug) output_processing = _output_processing_pipeline(config) - splits = ( - ["test1", "test2"] - if "Both" in config.TEST.SPLIT - else [config.TEST.SPLIT] - ) + splits = ["test1", "test2"] if "Both" in config.TEST.SPLIT else [config.TEST.SPLIT] for sdx, split in enumerate(splits): labels = np.load( path.join(config.DATASEST.ROOT, "test_once", split + "_labels.npy") @@ -465,9 +432,7 @@ def test(*options, cfg=None): logger.info("--------------- FINAL RESULTS -----------------") logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') for cdx, class_name in enumerate(_CLASS_NAMES): - logger.info( - f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' - ) + logger.info(f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}') logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index 7c43bc91..cefb5974 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -19,6 +19,7 @@ from torch.utils import data from deepseismic_interpretation.dutchf3.data import get_patch_loader, decode_segmap +from cv_lib.utils import load_log_configuration from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -79,7 +80,9 @@ def run(*options, cfg=None): """ update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) + + # Start logging + load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS diff --git a/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml index cbab8c60..9ce3937e 100644 --- a/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml +++ b/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 DATASET: @@ -20,7 +20,7 @@ MODEL: IN_CHANNELS: 1 TRAIN: - BATCH_SIZE_PER_GPU: 32 + BATCH_SIZE_PER_GPU: 16 BEGIN_EPOCH: 0 END_EPOCH: 300 MIN_LR: 0.001 diff --git a/experiments/interpretation/dutchf3_section/local/default.py b/experiments/interpretation/dutchf3_section/local/default.py index 94a8cd7f..d57a3c72 100644 --- a/experiments/interpretation/dutchf3_section/local/default.py +++ b/experiments/interpretation/dutchf3_section/local/default.py @@ -11,14 +11,14 @@ _C = CN() -_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models +_C.OUTPUT_DIR = "output" # Base directory for all output (logs, models, etc) _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.GPUS = (0,) _C.WORKERS = 4 _C.PRINT_FREQ = 20 _C.AUTO_RESUME = False _C.PIN_MEMORY = True -_C.LOG_CONFIG = "/data/home/vapaunic/repos/DeepSeismic/logging.conf" +_C.LOG_CONFIG = "./logging.conf" # Logging config file relative to the experiment _C.SEED = 42 # Cudnn related params @@ -50,11 +50,11 @@ _C.TRAIN.BATCH_SIZE_PER_GPU = 16 _C.TRAIN.WEIGHT_DECAY = 0.0001 _C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR +_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR _C.TRAIN.AUGMENTATION = True -_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 -_C.TRAIN.STD = 0.20977 # 0.20976548783479299 -_C.TRAIN.DEPTH = 'none' # Options are 'none', 'patch' and 'section' +_C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 +_C.TRAIN.STD = 0.20977 # 0.20976548783479299 +_C.TRAIN.DEPTH = "none" # Options are 'none', 'patch' and 'section' # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 @@ -67,10 +67,11 @@ _C.TEST = CN() _C.TEST.MODEL_PATH = "" _C.TEST.TEST_STRIDE = 10 -_C.TEST.SPLIT = 'Both' # Can be Both, Test1, Test2 +_C.TEST.SPLIT = "Both" # Can be Both, Test1, Test2 _C.TEST.INLINE = True _C.TEST.CROSSLINE = True + def update_config(cfg, options=None, config_file=None): cfg.defrost() diff --git a/experiments/interpretation/dutchf3_section/local/logging.conf b/experiments/interpretation/dutchf3_section/local/logging.conf new file mode 100644 index 00000000..56334fc4 --- /dev/null +++ b/experiments/interpretation/dutchf3_section/local/logging.conf @@ -0,0 +1,34 @@ +[loggers] +keys=root,__main__,event_handlers + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[logger___main__] +level=INFO +handlers=consoleHandler +qualname=__main__ +propagate=0 + +[logger_event_handlers] +level=INFO +handlers=consoleHandler +qualname=event_handlers +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + diff --git a/experiments/interpretation/dutchf3_section/local/test.py b/experiments/interpretation/dutchf3_section/local/test.py index 40374182..cf964705 100644 --- a/experiments/interpretation/dutchf3_section/local/test.py +++ b/experiments/interpretation/dutchf3_section/local/test.py @@ -1,34 +1,28 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. +# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 # url: https://github.com/olivesgatech/facies_classification_benchmark """ Modified version of the Alaudah testing script -#TODO: Needs to be improved. Needs to be able to run across multiple GPUs and better factoring around the loader +# TODO: Needs to be improved. Needs to be able to run across multiple GPUs and better +# factoring around the loader """ -import itertools import logging import logging.config import os from os import path -import cv2 import fire import numpy as np import torch -import torch.nn.functional as F from albumentations import Compose, Normalize +from cv_lib.utils import load_log_configuration from cv_lib.segmentation import models -from deepseismic_interpretation.dutchf3.data import ( - add_patch_depth_channels, - get_seismic_labels, - get_test_loader, -) +from deepseismic_interpretation.dutchf3.data import get_test_loader from default import _C as config from default import update_config -from toolz import compose, curry, itertoolz, pipe from torch.utils import data _CLASS_NAMES = [ @@ -73,9 +67,7 @@ def get_scores(self): acc = np.diag(hist).sum() / hist.sum() acc_cls = np.diag(hist) / hist.sum(axis=1) mean_acc_cls = np.nanmean(acc_cls) - iu = np.diag(hist) / ( - hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) - ) + iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) mean_iu = np.nanmean(iu) freq = ( hist.sum(axis=1) / hist.sum() @@ -100,21 +92,13 @@ def reset(self): def _evaluate_split( - split, - section_aug, - model, - device, - running_metrics_overall, - config, + split, section_aug, model, device, running_metrics_overall, config, ): logger = logging.getLogger(__name__) TestSectionLoader = get_test_loader(config) test_set = TestSectionLoader( - data_dir=DATA_ROOT, - split=split, - is_transform=True, - augmentations=section_aug, + data_dir=DATA_ROOT, split=split, is_transform=True, augmentations=section_aug, ) n_classes = test_set.n_classes @@ -145,9 +129,7 @@ def _evaluate_split( # Log split results logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') for cdx, class_name in enumerate(_CLASS_NAMES): - logger.info( - f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' - ) + logger.info(f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}') logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') @@ -181,7 +163,9 @@ def _write_section_file(labels, section_file): def test(*options, cfg=None): update_config(config, options=options, config_file=cfg) n_classes = config.DATASET.NUM_CLASSES - logging.config.fileConfig(config.LOG_CONFIG) + + # Start logging + load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") log_dir, _ = os.path.split(config.TEST.MODEL_PATH) @@ -197,26 +181,16 @@ def test(*options, cfg=None): section_aug = Compose( [ Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=1, + mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1, ) ] ) - splits = ( - ["test1", "test2"] - if "Both" in config.TEST.SPLIT - else [config.TEST.SPLIT] - ) + splits = ["test1", "test2"] if "Both" in config.TEST.SPLIT else [config.TEST.SPLIT] for sdx, split in enumerate(splits): - labels = np.load( - path.join(DATA_ROOT, "test_once", split + "_labels.npy") - ) - section_file = path.join( - DATA_ROOT, "splits", "section_" + split + ".txt" - ) + labels = np.load(path.join(DATA_ROOT, "test_once", split + "_labels.npy")) + section_file = path.join(DATA_ROOT, "splits", "section_" + split + ".txt") _write_section_file(labels, section_file) _evaluate_split( split, section_aug, model, device, running_metrics_overall, config @@ -228,9 +202,7 @@ def test(*options, cfg=None): logger.info("--------------- FINAL RESULTS -----------------") logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') for cdx, class_name in enumerate(_CLASS_NAMES): - logger.info( - f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}' - ) + logger.info(f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}') logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') diff --git a/experiments/interpretation/dutchf3_section/local/train.py b/experiments/interpretation/dutchf3_section/local/train.py index 6d99c463..e8dd6225 100644 --- a/experiments/interpretation/dutchf3_section/local/train.py +++ b/experiments/interpretation/dutchf3_section/local/train.py @@ -10,7 +10,9 @@ import numpy as np import torch from albumentations import Compose, HorizontalFlip, Normalize + from deepseismic_interpretation.dutchf3.data import decode_segmap, get_section_loader +from cv_lib.utils import load_log_configuration from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -74,7 +76,9 @@ def run(*options, cfg=None): """ update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) + + # Start logging + load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS diff --git a/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml b/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml index 76358ef1..bf08ca7e 100644 --- a/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml +++ b/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml @@ -9,7 +9,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/maxkaz/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 WINDOW_SIZE: 65 diff --git a/experiments/interpretation/dutchf3_voxel/default.py b/experiments/interpretation/dutchf3_voxel/default.py index a79761f4..288a9ac4 100644 --- a/experiments/interpretation/dutchf3_voxel/default.py +++ b/experiments/interpretation/dutchf3_voxel/default.py @@ -19,13 +19,12 @@ _C.GPUS = (0,) _C.OUTPUT_DIR = ( - "output" -) # This will be the base directory for all output, such as logs and saved models + "output" # The base directory for all output, such as logs and saved models +) _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.WORKERS = 4 _C.PRINT_FREQ = 20 -# TODO: this should be loaded by automatically figuring out the file path location -_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" +_C.LOG_CONFIG = "logging.conf" _C.SEED = 42 # size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only _C.WINDOW_SIZE = 65 diff --git a/experiments/interpretation/dutchf3_voxel/logging.conf b/experiments/interpretation/dutchf3_voxel/logging.conf new file mode 100644 index 00000000..56334fc4 --- /dev/null +++ b/experiments/interpretation/dutchf3_voxel/logging.conf @@ -0,0 +1,34 @@ +[loggers] +keys=root,__main__,event_handlers + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[logger___main__] +level=INFO +handlers=consoleHandler +qualname=__main__ +propagate=0 + +[logger_event_handlers] +level=INFO +handlers=consoleHandler +qualname=event_handlers +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + diff --git a/experiments/interpretation/dutchf3_voxel/train.py b/experiments/interpretation/dutchf3_voxel/train.py index 71ae42f3..d8810442 100644 --- a/experiments/interpretation/dutchf3_voxel/train.py +++ b/experiments/interpretation/dutchf3_voxel/train.py @@ -19,6 +19,7 @@ from deepseismic_interpretation.dutchf3.data import get_voxel_loader from deepseismic_interpretation.models.texture_net import TextureNet +from cv_lib.utils import load_log_configuration from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -27,7 +28,6 @@ from cv_lib.event_handlers.logging_handlers import Evaluator from cv_lib.event_handlers.tensorboard_handlers import create_summary_writer -# TODO: replace with Ignite metrics from cv_lib.segmentation.metrics import ( pixelwise_accuracy, class_accuracy, @@ -81,7 +81,9 @@ def run(*options, cfg=None): """ update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) + + # Start logging + load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK diff --git a/experiments/interpretation/penobscot/local/configs/hrnet.yaml b/experiments/interpretation/penobscot/local/configs/hrnet.yaml index 0d42c1ad..4ebf6484 100644 --- a/experiments/interpretation/penobscot/local/configs/hrnet.yaml +++ b/experiments/interpretation/penobscot/local/configs/hrnet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/vapaunic/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml b/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml index cb61b6b6..29c61936 100644 --- a/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml +++ b/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml @@ -7,7 +7,7 @@ OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 -LOG_CONFIG: /home/vapaunic/repos/DeepSeismic/logging.conf +LOG_CONFIG: logging.conf SEED: 2019 diff --git a/experiments/interpretation/penobscot/local/default.py b/experiments/interpretation/penobscot/local/default.py index a15615f2..35a6dd47 100644 --- a/experiments/interpretation/penobscot/local/default.py +++ b/experiments/interpretation/penobscot/local/default.py @@ -12,16 +12,15 @@ _C = CN() _C.OUTPUT_DIR = ( - "output" -) # This will be the base directory for all output, such as logs and saved models + "output" # The base directory for all output, such as logs and saved models +) _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.GPUS = (0,) _C.WORKERS = 4 _C.PRINT_FREQ = 20 _C.AUTO_RESUME = False _C.PIN_MEMORY = True -# TODO: this should be loaded by automatically figuring out the file path location -_C.LOG_CONFIG = "/home/maxkaz/repos/DeepSeismic/logging.conf" +_C.LOG_CONFIG = "logging.conf" _C.SEED = 42 # size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only diff --git a/experiments/interpretation/penobscot/local/logging.conf b/experiments/interpretation/penobscot/local/logging.conf new file mode 100644 index 00000000..56334fc4 --- /dev/null +++ b/experiments/interpretation/penobscot/local/logging.conf @@ -0,0 +1,34 @@ +[loggers] +keys=root,__main__,event_handlers + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[logger___main__] +level=INFO +handlers=consoleHandler +qualname=__main__ +propagate=0 + +[logger_event_handlers] +level=INFO +handlers=consoleHandler +qualname=event_handlers +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + diff --git a/experiments/interpretation/penobscot/local/test.py b/experiments/interpretation/penobscot/local/test.py index 9eafa2c5..8cd55c4e 100644 --- a/experiments/interpretation/penobscot/local/test.py +++ b/experiments/interpretation/penobscot/local/test.py @@ -13,6 +13,7 @@ import torch import torchvision from albumentations import Compose, Normalize, PadIfNeeded, Resize +from cv_lib.utils import load_log_configuration from cv_lib.event_handlers import logging_handlers, tensorboard_handlers from cv_lib.event_handlers.tensorboard_handlers import ( create_image_writer, @@ -126,7 +127,9 @@ def run(*options, cfg=None): """ update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) + + # Start logging + load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK diff --git a/experiments/interpretation/penobscot/local/train.py b/experiments/interpretation/penobscot/local/train.py index 58dffef9..d62a74d3 100644 --- a/experiments/interpretation/penobscot/local/train.py +++ b/experiments/interpretation/penobscot/local/train.py @@ -20,6 +20,7 @@ from deepseismic_interpretation.dutchf3.data import decode_segmap from deepseismic_interpretation.penobscot.data import get_patch_dataset +from cv_lib.utils import load_log_configuration from cv_lib.event_handlers import ( SnapshotHandler, logging_handlers, @@ -93,7 +94,9 @@ def run(*options, cfg=None): """ update_config(config, options=options, config_file=cfg) - logging.config.fileConfig(config.LOG_CONFIG) + + # Start logging + load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS diff --git a/experiments/interpretation/voxel2pixel/data.py b/experiments/interpretation/voxel2pixel/data.py index b1dc6f7a..c416ab84 100644 --- a/experiments/interpretation/voxel2pixel/data.py +++ b/experiments/interpretation/voxel2pixel/data.py @@ -87,7 +87,7 @@ def write_segy(out_filename, in_filename, out_cube): iline = out_cube[i - iline_start, :, :] src.iline[i] = np.ascontiguousarray(iline.astype(dtype)) - # TODO: rewrite this whole function - this is terrible + # TODO: rewrite this whole function # Moving temporal axis first again - just in case the user want to keep working on it out_cube = np.moveaxis(out_cube, -1, 0) @@ -111,27 +111,21 @@ def read_labels(fname, data_info): Returns: list of labels and list of coordinates - """ + """ label_imgs = [] label_coordinates = {} # Find image files in folder - - tmp = fname.split('/')[-1].split("_") + + tmp = fname.split("/")[-1].split("_") slice_type = tmp[0].lower() tmp = tmp[1].split(".") slice_no = int(tmp[0]) - if ( - slice_type - not in inline_alias + crossline_alias + timeslice_alias - ): + if slice_type not in inline_alias + crossline_alias + timeslice_alias: print( - "File:", - fname, - "could not be loaded.", - "Unknown slice type", + "File:", fname, "could not be loaded.", "Unknown slice type", ) return None @@ -145,33 +139,24 @@ def read_labels(fname, data_info): # Read file print("Loading labels for", slice_type, slice_no, "with") img = scipy.misc.imread(fname) - img = interpolate_to_fit_data( - img, slice_type, slice_no, data_info - ) + img = interpolate_to_fit_data(img, slice_type, slice_no, data_info) label_img = parse_labels_in_image(img) # Get coordinates for slice - coords = get_coordinates_for_slice( - slice_type, slice_no, data_info - ) + coords = get_coordinates_for_slice(slice_type, slice_no, data_info) # Loop through labels in label_img and append to label_coordinates for cls in np.unique(label_img): if cls > -1: if str(cls) not in label_coordinates.keys(): - label_coordinates[str(cls)] = np.array( - np.zeros([3, 0]) - ) + label_coordinates[str(cls)] = np.array(np.zeros([3, 0])) inds_with_cls = label_img == cls cords_with_cls = coords[:, inds_with_cls.ravel()] label_coordinates[str(cls)] = np.concatenate( (label_coordinates[str(cls)], cords_with_cls), 1 ) print( - " ", - str(np.sum(inds_with_cls)), - "labels for class", - str(cls), + " ", str(np.sum(inds_with_cls)), "labels for class", str(cls), ) if len(np.unique(label_img)) == 1: print(" ", 0, "labels", str(cls)) From 9a57d535dba128d5ec9df72c34de908fec2dbdf3 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 11 Nov 2019 10:38:27 -0500 Subject: [PATCH 076/207] DOC: forking dislaimer and new build names. (#9) --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 58251629..56595219 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,10 @@ python scripts/prepare_penobscot.py split_inline --data-dir=/mnt/penobscot --val This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. +### Submitting a Pull Request + +We try to keep the repo in a clean state, which means that we only enable read access to the repo - read access still enables one to submit a PR or an issue. To do so, fork the repo, and submit a PR from a branch in your forked repo into our staging branch. + When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. @@ -154,8 +158,8 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope ## Build Status | Build | Branch | Status | | --- | --- | --- | -| **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.DeepSeismic?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=staging) | -| **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.DeepSeismic?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=master) | -| **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.DeepSeismic%20(1)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=staging) | -| **Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.DeepSeismic%20(1)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=master) | +| **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=staging) | +| **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=master) | +| **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=staging) | +| **Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=master) | From 2355c819781df689018284a0e51d8ab12d05ee42 Mon Sep 17 00:00:00 2001 From: Sharat Chikkerur Date: Mon, 11 Nov 2019 10:51:51 -0500 Subject: [PATCH 077/207] Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md --- DeepSeismicLogo.jpg | Bin 0 -> 154387 bytes README.md | 38 ++++++++++++-------------------------- 2 files changed, 12 insertions(+), 26 deletions(-) create mode 100644 DeepSeismicLogo.jpg diff --git a/DeepSeismicLogo.jpg b/DeepSeismicLogo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6c68879d4d7416d278f462f76068e159a84c9593 GIT binary patch literal 154387 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

CW+=ZK;liK`ayr|%7X(%@=XbY~P&emeMXNBH@F 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 train time (s), score time (s)| | -Alaudah base patch | Pixel Acc, IoU
train time (s), score time (s)| | -HRNet slice | | | -DeepLab V3 slice | | | -| TODO: add all models | | | -#### Scribble-Level Labels - -We present results of algorithms which are based on scribble-level annotations, where the annotator labels a large collection of consecutive pixels with no gaps, e.g. brushstroke label. - -#### Pixel-Level Labels - -We present results of algorithms which are based on pixel-level annotations, where the annotator labels individual pixels and gaps are allowed between pixels; the annotator can also label a small neighborhood of pixels, e.g. large dot of ~100 pixels. - ### Data #### Netherlands F3 - To download the F3 Netherlands dataset for 2D experiments, please follow the data download instructions at [this github repository](https://github.com/olivesgatech/facies_classification_benchmark). From 8b857d2863709ac26f2cbd9efef44e13dec2244a Mon Sep 17 00:00:00 2001 From: Mat Date: Mon, 11 Nov 2019 21:06:17 +0000 Subject: [PATCH 078/207] Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation --- .flake8 | 6 +- .pre-commit-config.yaml | 1 - cv_lib/cv_lib/__version__.py | 1 - cv_lib/cv_lib/event_handlers/__init__.py | 15 +- .../cv_lib/event_handlers/logging_handlers.py | 44 +-- .../event_handlers/tensorboard_handlers.py | 28 +- cv_lib/cv_lib/segmentation/__init__.py | 4 +- .../segmentation/dutchf3/augmentations.py | 6 +- cv_lib/cv_lib/segmentation/dutchf3/engine.py | 8 +- cv_lib/cv_lib/segmentation/metrics.py | 24 +- cv_lib/cv_lib/segmentation/models/__init__.py | 14 +- .../segmentation/models/patch_deconvnet.py | 7 +- .../models/patch_deconvnet_skip.py | 8 +- .../cv_lib/segmentation/models/resnet_unet.py | 59 +--- .../segmentation/models/section_deconvnet.py | 7 +- .../models/section_deconvnet_skip.py | 7 +- .../cv_lib/segmentation/models/seg_hrnet.py | 281 ++++++++---------- cv_lib/cv_lib/segmentation/models/unet.py | 4 - cv_lib/cv_lib/segmentation/models/utils.py | 28 +- .../cv_lib/segmentation/penobscot/engine.py | 15 +- cv_lib/cv_lib/segmentation/utils.py | 12 +- cv_lib/cv_lib/utils.py | 8 +- cv_lib/requirements.txt | 14 +- cv_lib/tests/test_metrics.py | 26 +- deepseismic/cli/forward.py | 29 +- deepseismic/cli/velocity.py | 29 +- deepseismic/forward/models.py | 34 +-- deepseismic/forward/sources.py | 12 +- deepseismic/forward/subdomains.py | 4 +- deepseismic/velocity/generator.py | 5 +- deepseismic/velocity/roeth_tarantola.py | 4 +- environment/anaconda/local/environment.yml | 2 +- ..._block_training_and_evaluation_local.ipynb | 31 +- .../notebooks/configs/hrnet.yaml | 4 +- .../configs/patch_deconvnet_skip.yaml | 4 +- .../notebooks/configs/unet.yaml | 2 +- .../interpretation/notebooks/logging.conf | 34 +++ .../interpretation/notebooks/utilities.py | 127 ++------ .../interpretation/demo/local/default.py | 1 - .../interpretation/demo/local/train.py | 92 ++---- .../dutchf3_patch/distributed/default.py | 5 +- .../dutchf3_patch/distributed/train.py | 129 ++------ .../dutchf3_patch/local/default.py | 9 +- .../dutchf3_patch/local/test.py | 102 ++----- .../dutchf3_patch/local/train.py | 83 ++---- .../dutchf3_section/local/default.py | 2 +- .../dutchf3_section/local/test.py | 36 +-- .../dutchf3_section/local/train.py | 109 ++----- .../interpretation/dutchf3_voxel/default.py | 4 +- .../interpretation/dutchf3_voxel/train.py | 75 ++--- .../interpretation/penobscot/local/default.py | 6 +- .../interpretation/penobscot/local/test.py | 127 ++------ .../interpretation/penobscot/local/train.py | 95 ++---- .../interpretation/voxel2pixel/batch.py | 4 +- .../interpretation/voxel2pixel/data.py | 8 +- .../interpretation/voxel2pixel/tb_logger.py | 61 +--- .../voxel2pixel/test_parallel.py | 79 ++--- .../interpretation/voxel2pixel/texture_net.py | 4 +- .../interpretation/voxel2pixel/train.py | 43 +-- .../interpretation/voxel2pixel/utils.py | 169 +++++------ imaging/deepseismic_imaging/cli/forward.py | 29 +- imaging/deepseismic_imaging/cli/velocity.py | 29 +- imaging/deepseismic_imaging/forward/models.py | 34 +-- .../deepseismic_imaging/forward/sources.py | 12 +- .../deepseismic_imaging/forward/subdomains.py | 4 +- .../deepseismic_imaging/velocity/generator.py | 5 +- .../velocity/roeth_tarantola.py | 4 +- imaging/setup.py | 8 +- .../deepseismic_interpretation/data.py | 62 +--- .../dutchf3/data.py | 153 ++-------- .../dutchf3/utils/batch.py | 4 +- .../models/texture_net.py | 7 +- .../penobscot/data.py | 85 ++---- .../penobscot/metrics.py | 20 +- interpretation/setup.py | 4 +- pyproject.toml | 2 +- scripts/prepare_dutchf3.py | 45 +-- scripts/prepare_penobscot.py | 20 +- 78 files changed, 772 insertions(+), 1946 deletions(-) create mode 100644 examples/interpretation/notebooks/logging.conf diff --git a/.flake8 b/.flake8 index 696bba44..f09cc27c 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,5 @@ [flake8] -max-line-length = 88 +max-line-length = 120 max-complexity = 18 select = B,C,E,F,W,T4,B9 ignore = @@ -9,9 +9,9 @@ ignore = E266 # module level import not at top of file E402 - # do not use bare except, specify exception instead - E722 # line break before binary operator W503 # blank line contains whitespace W293 + # line too long + E501 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cddba3d4..9002a3b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,6 @@ repos: rev: stable hooks: - id: black - language_version: python3.6 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v1.2.3 hooks: diff --git a/cv_lib/cv_lib/__version__.py b/cv_lib/cv_lib/__version__.py index 04ecc805..97b8b400 100644 --- a/cv_lib/cv_lib/__version__.py +++ b/cv_lib/cv_lib/__version__.py @@ -2,4 +2,3 @@ # Licensed under the MIT License. __version__ = "0.0.1" - diff --git a/cv_lib/cv_lib/event_handlers/__init__.py b/cv_lib/cv_lib/event_handlers/__init__.py index a2dab4ca..589bbd86 100644 --- a/cv_lib/cv_lib/event_handlers/__init__.py +++ b/cv_lib/cv_lib/event_handlers/__init__.py @@ -31,21 +31,12 @@ def _create_checkpoint_handler(self): def __call__(self, engine, to_save): self._checkpoint_handler(engine, to_save) if self._snapshot_function(): - files = glob.glob( - os.path.join( - self._model_save_location, self._running_model_prefix + "*" - ) - ) + files = glob.glob(os.path.join(self._model_save_location, self._running_model_prefix + "*")) print(files) name_postfix = os.path.basename(files[0]).lstrip(self._running_model_prefix) copyfile( files[0], - os.path.join( - self._model_save_location, - f"{self._snapshot_prefix}{self._snapshot_num}{name_postfix}", - ), + os.path.join(self._model_save_location, f"{self._snapshot_prefix}{self._snapshot_num}{name_postfix}",), ) - self._checkpoint_handler = ( - self._create_checkpoint_handler() - ) # Reset the checkpoint handler + self._checkpoint_handler = self._create_checkpoint_handler() # Reset the checkpoint handler self._snapshot_num += 1 diff --git a/cv_lib/cv_lib/event_handlers/logging_handlers.py b/cv_lib/cv_lib/event_handlers/logging_handlers.py index a1045de8..b7c41651 100644 --- a/cv_lib/cv_lib/event_handlers/logging_handlers.py +++ b/cv_lib/cv_lib/event_handlers/logging_handlers.py @@ -6,16 +6,16 @@ from toolz import curry import numpy as np + np.set_printoptions(precision=3) + @curry def log_training_output(engine, log_interval=100): logger = logging.getLogger(__name__) if engine.state.iteration % log_interval == 0: - logger.info( - f"Epoch: {engine.state.epoch} Iter: {engine.state.iteration} loss {engine.state.output['loss']}" - ) + logger.info(f"Epoch: {engine.state.epoch} Iter: {engine.state.iteration} loss {engine.state.output['loss']}") @curry @@ -32,28 +32,20 @@ def log_lr(optimizer, engine): def log_metrics(log_msg, engine, metrics_dict=_DEFAULT_METRICS): logger = logging.getLogger(__name__) metrics = engine.state.metrics - metrics_msg = " ".join( - [f"{metrics_dict[k]} {metrics[k]:.2f}" for k in metrics_dict] - ) - logger.info( - f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}] " - + metrics_msg - ) + metrics_msg = " ".join([f"{metrics_dict[k]} {metrics[k]:.2f}" for k in metrics_dict]) + logger.info(f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}] " + metrics_msg) + @curry def log_class_metrics(log_msg, engine, metrics_dict): logger = logging.getLogger(__name__) metrics = engine.state.metrics metrics_msg = "\n".join(f"{metrics_dict[k]} {metrics[k].numpy()}" for k in metrics_dict) - logger.info( - f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}]\n" - + metrics_msg - ) + logger.info(f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}]\n" + metrics_msg) class Evaluator: def __init__(self, evaluation_engine, data_loader): - logger = logging.getLogger(__name__) self._evaluation_engine = evaluation_engine self._data_loader = data_loader @@ -70,13 +62,7 @@ class HorovodLRScheduler: """ def __init__( - self, - base_lr, - warmup_epochs, - cluster_size, - data_loader, - optimizer, - batches_per_allreduce, + self, base_lr, warmup_epochs, cluster_size, data_loader, optimizer, batches_per_allreduce, ): self._warmup_epochs = warmup_epochs self._cluster_size = cluster_size @@ -90,11 +76,7 @@ def __call__(self, engine): epoch = engine.state.epoch if epoch < self._warmup_epochs: epoch += float(engine.state.iteration + 1) / len(self._data_loader) - lr_adj = ( - 1.0 - / self._cluster_size - * (epoch * (self._cluster_size - 1) / self._warmup_epochs + 1) - ) + lr_adj = 1.0 / self._cluster_size * (epoch * (self._cluster_size - 1) / self._warmup_epochs + 1) elif epoch < 30: lr_adj = 1.0 elif epoch < 60: @@ -104,11 +86,5 @@ def __call__(self, engine): else: lr_adj = 1e-3 for param_group in self._optimizer.param_groups: - param_group["lr"] = ( - self._base_lr - * self._cluster_size - * self._batches_per_allreduce - * lr_adj - ) + param_group["lr"] = self._base_lr * self._cluster_size * self._batches_per_allreduce * lr_adj self._logger.debug(f"Adjust learning rate {param_group['lr']}") - diff --git a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py index 1c3a574a..654c9b4d 100644 --- a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py +++ b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py @@ -9,9 +9,7 @@ try: from tensorboardX import SummaryWriter except ImportError: - raise RuntimeError( - "No tensorboardX package is found. Please install with the command: \npip install tensorboardX" - ) + raise RuntimeError("No tensorboardX package is found. Please install with the command: \npip install tensorboardX") def create_summary_writer(log_dir): @@ -20,9 +18,7 @@ def create_summary_writer(log_dir): def _log_model_output(log_label, summary_writer, engine): - summary_writer.add_scalar( - log_label, engine.state.output["loss"], engine.state.iteration - ) + summary_writer.add_scalar(log_label, engine.state.output["loss"], engine.state.iteration) @curry @@ -53,31 +49,21 @@ def log_lr(summary_writer, optimizer, log_interval, engine): @curry -def log_metrics( - summary_writer, train_engine, log_interval, engine, metrics_dict=_DEFAULT_METRICS -): +def log_metrics(summary_writer, train_engine, log_interval, engine, metrics_dict=_DEFAULT_METRICS): metrics = engine.state.metrics for m in metrics_dict: - summary_writer.add_scalar( - metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval) - ) + summary_writer.add_scalar(metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval)) -def create_image_writer( - summary_writer, label, output_variable, normalize=False, transform_func=lambda x: x -): +def create_image_writer(summary_writer, label, output_variable, normalize=False, transform_func=lambda x: x): logger = logging.getLogger(__name__) def write_to(engine): try: data_tensor = transform_func(engine.state.output[output_variable]) - image_grid = torchvision.utils.make_grid( - data_tensor, normalize=normalize, scale_each=True - ) + image_grid = torchvision.utils.make_grid(data_tensor, normalize=normalize, scale_each=True) summary_writer.add_image(label, image_grid, engine.state.epoch) except KeyError: - logger.warning( - "Predictions and or ground truth labels not available to report" - ) + logger.warning("Predictions and or ground truth labels not available to report") return write_to diff --git a/cv_lib/cv_lib/segmentation/__init__.py b/cv_lib/cv_lib/segmentation/__init__.py index 4473ef65..4306a4e0 100644 --- a/cv_lib/cv_lib/segmentation/__init__.py +++ b/cv_lib/cv_lib/segmentation/__init__.py @@ -13,7 +13,5 @@ def extract_metric_from(metric, engine): @curry def padded_val_transform(pad_left, fine_size, x, y, y_pred): - y_pred = y_pred[ - :, :, pad_left : pad_left + fine_size, pad_left : pad_left + fine_size - ].contiguous() + y_pred = y_pred[:, :, pad_left : pad_left + fine_size, pad_left : pad_left + fine_size].contiguous() return {"image": x, "y_pred": F.sigmoid(y_pred).detach(), "mask": y.detach()} diff --git a/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py b/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py index d14cc8c8..e4df608f 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/augmentations.py @@ -6,7 +6,7 @@ import random import numpy as np -from PIL import Image, ImageOps, ImageChops +from PIL import Image, ImageOps class Compose(object): @@ -181,8 +181,8 @@ def __init__(self, degree): def __call__(self, img, mask): """ - PIL automatically adds zeros to the borders of images that rotated. To fix this - issue, the code in the botton sets anywhere in the labels (mask) that is zero to + PIL automatically adds zeros to the borders of images that rotated. To fix this + issue, the code in the botton sets anywhere in the labels (mask) that is zero to 255 (the value used for ignore_index). """ rotate_degree = random.random() * 2 * self.degree - self.degree diff --git a/cv_lib/cv_lib/segmentation/dutchf3/engine.py b/cv_lib/cv_lib/segmentation/dutchf3/engine.py index 5d3afbbf..c137af5c 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/engine.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/engine.py @@ -51,12 +51,7 @@ def val_transform(x, y, y_pred): def create_supervised_evaluator( - model, - prepare_batch, - metrics=None, - device=None, - non_blocking=False, - output_transform=val_transform, + model, prepare_batch, metrics=None, device=None, non_blocking=False, output_transform=val_transform, ): metrics = metrics or {} @@ -133,4 +128,3 @@ def _update(engine, batch): # metric.attach(engine, name) # return engine - diff --git a/cv_lib/cv_lib/segmentation/metrics.py b/cv_lib/cv_lib/segmentation/metrics.py index 02230fa9..2d28a954 100644 --- a/cv_lib/cv_lib/segmentation/metrics.py +++ b/cv_lib/cv_lib/segmentation/metrics.py @@ -17,9 +17,7 @@ def pixelwise_accuracy(num_classes, output_transform=lambda x: x, device=None): MetricsLambda """ - cm = ignite.metrics.ConfusionMatrix( - num_classes=num_classes, output_transform=output_transform, device=device - ) + cm = ignite.metrics.ConfusionMatrix(num_classes=num_classes, output_transform=output_transform, device=device) # Increase floating point precision and pass to CPU cm = cm.type(torch.DoubleTensor) @@ -40,9 +38,7 @@ def class_accuracy(num_classes, output_transform=lambda x: x, device=None): MetricsLambda """ - cm = ignite.metrics.ConfusionMatrix( - num_classes=num_classes, output_transform=output_transform, device=device - ) + cm = ignite.metrics.ConfusionMatrix(num_classes=num_classes, output_transform=output_transform, device=device) # Increase floating point precision and pass to CPU cm = cm.type(torch.DoubleTensor) @@ -63,14 +59,10 @@ def mean_class_accuracy(num_classes, output_transform=lambda x: x, device=None): MetricsLambda """ - return class_accuracy( - num_classes=num_classes, output_transform=output_transform, device=device - ).mean() + return class_accuracy(num_classes=num_classes, output_transform=output_transform, device=device).mean() -def class_iou( - num_classes, output_transform=lambda x: x, device=None, ignore_index=None -): +def class_iou(num_classes, output_transform=lambda x: x, device=None, ignore_index=None): """Calculates per-class intersection-over-union Args: @@ -82,9 +74,7 @@ def class_iou( MetricsLambda """ - cm = ignite.metrics.ConfusionMatrix( - num_classes=num_classes, output_transform=output_transform, device=device - ) + cm = ignite.metrics.ConfusionMatrix(num_classes=num_classes, output_transform=output_transform, device=device) return ignite.metrics.IoU(cm, ignore_index=ignore_index) @@ -100,7 +90,5 @@ def mean_iou(num_classes, output_transform=lambda x: x, device=None, ignore_inde MetricsLambda """ - cm = ignite.metrics.ConfusionMatrix( - num_classes=num_classes, output_transform=output_transform, device=device - ) + cm = ignite.metrics.ConfusionMatrix(num_classes=num_classes, output_transform=output_transform, device=device) return ignite.metrics.mIoU(cm, ignore_index=ignore_index) diff --git a/cv_lib/cv_lib/segmentation/models/__init__.py b/cv_lib/cv_lib/segmentation/models/__init__.py index 499803f5..11d443e4 100644 --- a/cv_lib/cv_lib/segmentation/models/__init__.py +++ b/cv_lib/cv_lib/segmentation/models/__init__.py @@ -1,10 +1,10 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -import cv_lib.segmentation.models.seg_hrnet -import cv_lib.segmentation.models.resnet_unet -import cv_lib.segmentation.models.unet -import cv_lib.segmentation.models.section_deconvnet -import cv_lib.segmentation.models.patch_deconvnet -import cv_lib.segmentation.models.patch_deconvnet_skip -import cv_lib.segmentation.models.section_deconvnet_skip +import cv_lib.segmentation.models.seg_hrnet # noqa: F401 +import cv_lib.segmentation.models.resnet_unet # noqa: F401 +import cv_lib.segmentation.models.unet # noqa: F401 +import cv_lib.segmentation.models.section_deconvnet # noqa: F401 +import cv_lib.segmentation.models.patch_deconvnet # noqa: F401 +import cv_lib.segmentation.models.patch_deconvnet_skip # noqa: F401 +import cv_lib.segmentation.models.section_deconvnet_skip # noqa: F401 diff --git a/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py b/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py index 1d274446..4ee1ed59 100644 --- a/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py +++ b/cv_lib/cv_lib/segmentation/models/patch_deconvnet.py @@ -287,12 +287,7 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): if i_layer == 0: l2.weight.data = ( - ( - l1.weight.data[:, 0, :, :] - + l1.weight.data[:, 1, :, :] - + l1.weight.data[:, 2, :, :] - ) - / 3.0 + (l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, 2, :, :]) / 3.0 ).view(l2.weight.size()) l2.bias.data = l1.bias.data i_layer = i_layer + 1 diff --git a/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py b/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py index 1b42a19d..d5506b84 100644 --- a/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py +++ b/cv_lib/cv_lib/segmentation/models/patch_deconvnet_skip.py @@ -287,12 +287,7 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): if i_layer == 0: l2.weight.data = ( - ( - l1.weight.data[:, 0, :, :] - + l1.weight.data[:, 1, :, :] - + l1.weight.data[:, 2, :, :] - ) - / 3.0 + (l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, 2, :, :]) / 3.0 ).view(l2.weight.size()) l2.bias.data = l1.bias.data i_layer = i_layer + 1 @@ -310,4 +305,3 @@ def get_seg_model(cfg, **kwargs): ), f"Patch deconvnet is not implemented to accept {cfg.MODEL.IN_CHANNELS} channels. Please only pass 1 for cfg.MODEL.IN_CHANNELS" model = patch_deconvnet_skip(n_classes=cfg.DATASET.NUM_CLASSES) return model - diff --git a/cv_lib/cv_lib/segmentation/models/resnet_unet.py b/cv_lib/cv_lib/segmentation/models/resnet_unet.py index dbf6eb13..05badb64 100644 --- a/cv_lib/cv_lib/segmentation/models/resnet_unet.py +++ b/cv_lib/cv_lib/segmentation/models/resnet_unet.py @@ -10,15 +10,10 @@ class FPAv2(nn.Module): def __init__(self, input_dim, output_dim): super(FPAv2, self).__init__() - self.glob = nn.Sequential( - nn.AdaptiveAvgPool2d(1), - nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False), - ) + self.glob = nn.Sequential(nn.AdaptiveAvgPool2d(1), nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False),) self.down2_1 = nn.Sequential( - nn.Conv2d( - input_dim, input_dim, kernel_size=5, stride=2, padding=2, bias=False - ), + nn.Conv2d(input_dim, input_dim, kernel_size=5, stride=2, padding=2, bias=False), nn.BatchNorm2d(input_dim), nn.ELU(True), ) @@ -29,9 +24,7 @@ def __init__(self, input_dim, output_dim): ) self.down3_1 = nn.Sequential( - nn.Conv2d( - input_dim, input_dim, kernel_size=3, stride=2, padding=1, bias=False - ), + nn.Conv2d(input_dim, input_dim, kernel_size=3, stride=2, padding=1, bias=False), nn.BatchNorm2d(input_dim), nn.ELU(True), ) @@ -42,17 +35,13 @@ def __init__(self, input_dim, output_dim): ) self.conv1 = nn.Sequential( - nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False), - nn.BatchNorm2d(output_dim), - nn.ELU(True), + nn.Conv2d(input_dim, output_dim, kernel_size=1, bias=False), nn.BatchNorm2d(output_dim), nn.ELU(True), ) def forward(self, x): # x shape: 512, 16, 16 x_glob = self.glob(x) # 256, 1, 1 - x_glob = F.upsample( - x_glob, scale_factor=16, mode="bilinear", align_corners=True - ) # 256, 16, 16 + x_glob = F.upsample(x_glob, scale_factor=16, mode="bilinear", align_corners=True) # 256, 16, 16 d2 = self.down2_1(x) # 512, 8, 8 d3 = self.down3_1(d2) # 512, 4, 4 @@ -60,14 +49,10 @@ def forward(self, x): d2 = self.down2_2(d2) # 256, 8, 8 d3 = self.down3_2(d3) # 256, 4, 4 - d3 = F.upsample( - d3, scale_factor=2, mode="bilinear", align_corners=True - ) # 256, 8, 8 + d3 = F.upsample(d3, scale_factor=2, mode="bilinear", align_corners=True) # 256, 8, 8 d2 = d2 + d3 - d2 = F.upsample( - d2, scale_factor=2, mode="bilinear", align_corners=True - ) # 256, 16, 16 + d2 = F.upsample(d2, scale_factor=2, mode="bilinear", align_corners=True) # 256, 16, 16 x = self.conv1(x) # 256, 16, 16 x = x * d2 @@ -78,14 +63,7 @@ def forward(self, x): def conv3x3(input_dim, output_dim, rate=1): return nn.Sequential( - nn.Conv2d( - input_dim, - output_dim, - kernel_size=3, - dilation=rate, - padding=rate, - bias=False, - ), + nn.Conv2d(input_dim, output_dim, kernel_size=3, dilation=rate, padding=rate, bias=False,), nn.BatchNorm2d(output_dim), nn.ELU(True), ) @@ -107,12 +85,8 @@ class GAB(nn.Module): def __init__(self, input_dim, reduction=4): super(GAB, self).__init__() self.global_avgpool = nn.AdaptiveAvgPool2d(1) - self.conv1 = nn.Conv2d( - input_dim, input_dim // reduction, kernel_size=1, stride=1 - ) - self.conv2 = nn.Conv2d( - input_dim // reduction, input_dim, kernel_size=1, stride=1 - ) + self.conv1 = nn.Conv2d(input_dim, input_dim // reduction, kernel_size=1, stride=1) + self.conv2 = nn.Conv2d(input_dim // reduction, input_dim, kernel_size=1, stride=1) self.relu = nn.ReLU(inplace=True) self.sigmoid = nn.Sigmoid() @@ -316,10 +290,7 @@ def forward(self, x): [ fuse_pixel, F.upsample( - fuse_image.view(batch_size, -1, 1, 1), - scale_factor=256, - mode="bilinear", - align_corners=True, + fuse_image.view(batch_size, -1, 1, 1), scale_factor=256, mode="bilinear", align_corners=True, ), ], 1, @@ -336,9 +307,7 @@ def __init__(self): self.resnet = torchvision.models.resnet34(True) self.conv1 = nn.Sequential( - nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False), - self.resnet.bn1, - self.resnet.relu, + nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False), self.resnet.bn1, self.resnet.relu, ) self.encode2 = nn.Sequential(self.resnet.layer1, SCse(64)) @@ -354,9 +323,7 @@ def __init__(self): self.decode2 = Decoderv2(64, 64, 64) self.logit = nn.Sequential( - nn.Conv2d(256, 32, kernel_size=3, padding=1), - nn.ELU(True), - nn.Conv2d(32, 1, kernel_size=1, bias=False), + nn.Conv2d(256, 32, kernel_size=3, padding=1), nn.ELU(True), nn.Conv2d(32, 1, kernel_size=1, bias=False), ) def forward(self, x): diff --git a/cv_lib/cv_lib/segmentation/models/section_deconvnet.py b/cv_lib/cv_lib/segmentation/models/section_deconvnet.py index b8cf73e8..7234b1ee 100644 --- a/cv_lib/cv_lib/segmentation/models/section_deconvnet.py +++ b/cv_lib/cv_lib/segmentation/models/section_deconvnet.py @@ -287,12 +287,7 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): if i_layer == 0: l2.weight.data = ( - ( - l1.weight.data[:, 0, :, :] - + l1.weight.data[:, 1, :, :] - + l1.weight.data[:, 2, :, :] - ) - / 3.0 + (l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, 2, :, :]) / 3.0 ).view(l2.weight.size()) l2.bias.data = l1.bias.data i_layer = i_layer + 1 diff --git a/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py b/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py index cfcfbcd1..cb8b2ecb 100644 --- a/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py +++ b/cv_lib/cv_lib/segmentation/models/section_deconvnet_skip.py @@ -287,12 +287,7 @@ def init_vgg16_params(self, vgg16, copy_fc8=True): if isinstance(l1, nn.Conv2d) and isinstance(l2, nn.Conv2d): if i_layer == 0: l2.weight.data = ( - ( - l1.weight.data[:, 0, :, :] - + l1.weight.data[:, 1, :, :] - + l1.weight.data[:, 2, :, :] - ) - / 3.0 + (l1.weight.data[:, 0, :, :] + l1.weight.data[:, 1, :, :] + l1.weight.data[:, 2, :, :]) / 3.0 ).view(l2.weight.size()) l2.bias.data = l1.bias.data i_layer = i_layer + 1 diff --git a/cv_lib/cv_lib/segmentation/models/seg_hrnet.py b/cv_lib/cv_lib/segmentation/models/seg_hrnet.py index 200e118d..dd06118e 100644 --- a/cv_lib/cv_lib/segmentation/models/seg_hrnet.py +++ b/cv_lib/cv_lib/segmentation/models/seg_hrnet.py @@ -26,10 +26,10 @@ BN_MOMENTUM = 0.1 logger = logging.getLogger(__name__) + def conv3x3(in_planes, out_planes, stride=1): """3x3 convolution with padding""" - return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, - padding=1, bias=False) + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False) class BasicBlock(nn.Module): @@ -71,13 +71,10 @@ def __init__(self, inplanes, planes, stride=1, downsample=None): super(Bottleneck, self).__init__() self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) self.bn1 = BatchNorm2d(planes, momentum=BN_MOMENTUM) - self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, - padding=1, bias=False) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = BatchNorm2d(planes, momentum=BN_MOMENTUM) - self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, - bias=False) - self.bn3 = BatchNorm2d(planes * self.expansion, - momentum=BN_MOMENTUM) + self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False) + self.bn3 = BatchNorm2d(planes * self.expansion, momentum=BN_MOMENTUM) self.relu = nn.ReLU(inplace=True) self.downsample = downsample self.stride = stride @@ -106,11 +103,11 @@ def forward(self, x): class HighResolutionModule(nn.Module): - def __init__(self, num_branches, blocks, num_blocks, num_inchannels, - num_channels, fuse_method, multi_scale_output=True): + def __init__( + self, num_branches, blocks, num_blocks, num_inchannels, num_channels, fuse_method, multi_scale_output=True, + ): super(HighResolutionModule, self).__init__() - self._check_branches( - num_branches, blocks, num_blocks, num_inchannels, num_channels) + self._check_branches(num_branches, blocks, num_blocks, num_inchannels, num_channels) self.num_inchannels = num_inchannels self.fuse_method = fuse_method @@ -118,52 +115,45 @@ def __init__(self, num_branches, blocks, num_blocks, num_inchannels, self.multi_scale_output = multi_scale_output - self.branches = self._make_branches( - num_branches, blocks, num_blocks, num_channels) + self.branches = self._make_branches(num_branches, blocks, num_blocks, num_channels) self.fuse_layers = self._make_fuse_layers() self.relu = nn.ReLU(inplace=True) - def _check_branches(self, num_branches, blocks, num_blocks, - num_inchannels, num_channels): + def _check_branches(self, num_branches, blocks, num_blocks, num_inchannels, num_channels): if num_branches != len(num_blocks): - error_msg = 'NUM_BRANCHES({}) <> NUM_BLOCKS({})'.format( - num_branches, len(num_blocks)) + error_msg = "NUM_BRANCHES({}) <> NUM_BLOCKS({})".format(num_branches, len(num_blocks)) logger.error(error_msg) raise ValueError(error_msg) if num_branches != len(num_channels): - error_msg = 'NUM_BRANCHES({}) <> NUM_CHANNELS({})'.format( - num_branches, len(num_channels)) + error_msg = "NUM_BRANCHES({}) <> NUM_CHANNELS({})".format(num_branches, len(num_channels)) logger.error(error_msg) raise ValueError(error_msg) if num_branches != len(num_inchannels): - error_msg = 'NUM_BRANCHES({}) <> NUM_INCHANNELS({})'.format( - num_branches, len(num_inchannels)) + error_msg = "NUM_BRANCHES({}) <> NUM_INCHANNELS({})".format(num_branches, len(num_inchannels)) logger.error(error_msg) raise ValueError(error_msg) - def _make_one_branch(self, branch_index, block, num_blocks, num_channels, - stride=1): + def _make_one_branch(self, branch_index, block, num_blocks, num_channels, stride=1): downsample = None - if stride != 1 or \ - self.num_inchannels[branch_index] != num_channels[branch_index] * block.expansion: + if stride != 1 or self.num_inchannels[branch_index] != num_channels[branch_index] * block.expansion: downsample = nn.Sequential( - nn.Conv2d(self.num_inchannels[branch_index], - num_channels[branch_index] * block.expansion, - kernel_size=1, stride=stride, bias=False), - BatchNorm2d(num_channels[branch_index] * block.expansion, - momentum=BN_MOMENTUM), + nn.Conv2d( + self.num_inchannels[branch_index], + num_channels[branch_index] * block.expansion, + kernel_size=1, + stride=stride, + bias=False, + ), + BatchNorm2d(num_channels[branch_index] * block.expansion, momentum=BN_MOMENTUM), ) layers = [] - layers.append(block(self.num_inchannels[branch_index], - num_channels[branch_index], stride, downsample)) - self.num_inchannels[branch_index] = \ - num_channels[branch_index] * block.expansion + layers.append(block(self.num_inchannels[branch_index], num_channels[branch_index], stride, downsample,)) + self.num_inchannels[branch_index] = num_channels[branch_index] * block.expansion for i in range(1, num_blocks[branch_index]): - layers.append(block(self.num_inchannels[branch_index], - num_channels[branch_index])) + layers.append(block(self.num_inchannels[branch_index], num_channels[branch_index])) return nn.Sequential(*layers) @@ -171,8 +161,7 @@ def _make_branches(self, num_branches, block, num_blocks, num_channels): branches = [] for i in range(num_branches): - branches.append( - self._make_one_branch(i, block, num_blocks, num_channels)) + branches.append(self._make_one_branch(i, block, num_blocks, num_channels)) return nn.ModuleList(branches) @@ -187,36 +176,34 @@ def _make_fuse_layers(self): fuse_layer = [] for j in range(num_branches): if j > i: - fuse_layer.append(nn.Sequential( - nn.Conv2d(num_inchannels[j], - num_inchannels[i], - 1, - 1, - 0, - bias=False), - BatchNorm2d(num_inchannels[i], momentum=BN_MOMENTUM))) + fuse_layer.append( + nn.Sequential( + nn.Conv2d(num_inchannels[j], num_inchannels[i], 1, 1, 0, bias=False,), + BatchNorm2d(num_inchannels[i], momentum=BN_MOMENTUM), + ) + ) elif j == i: fuse_layer.append(None) else: conv3x3s = [] - for k in range(i-j): + for k in range(i - j): if k == i - j - 1: num_outchannels_conv3x3 = num_inchannels[i] - conv3x3s.append(nn.Sequential( - nn.Conv2d(num_inchannels[j], - num_outchannels_conv3x3, - 3, 2, 1, bias=False), - BatchNorm2d(num_outchannels_conv3x3, - momentum=BN_MOMENTUM))) + conv3x3s.append( + nn.Sequential( + nn.Conv2d(num_inchannels[j], num_outchannels_conv3x3, 3, 2, 1, bias=False,), + BatchNorm2d(num_outchannels_conv3x3, momentum=BN_MOMENTUM), + ) + ) else: num_outchannels_conv3x3 = num_inchannels[j] - conv3x3s.append(nn.Sequential( - nn.Conv2d(num_inchannels[j], - num_outchannels_conv3x3, - 3, 2, 1, bias=False), - BatchNorm2d(num_outchannels_conv3x3, - momentum=BN_MOMENTUM), - nn.ReLU(inplace=True))) + conv3x3s.append( + nn.Sequential( + nn.Conv2d(num_inchannels[j], num_outchannels_conv3x3, 3, 2, 1, bias=False,), + BatchNorm2d(num_outchannels_conv3x3, momentum=BN_MOMENTUM), + nn.ReLU(inplace=True), + ) + ) fuse_layer.append(nn.Sequential(*conv3x3s)) fuse_layers.append(nn.ModuleList(fuse_layer)) @@ -242,9 +229,8 @@ def forward(self, x): width_output = x[i].shape[-1] height_output = x[i].shape[-2] y = y + F.interpolate( - self.fuse_layers[i][j](x[j]), - size=[height_output, width_output], - mode='bilinear') + self.fuse_layers[i][j](x[j]), size=[height_output, width_output], mode="bilinear", + ) else: y = y + self.fuse_layers[i][j](x[j]) x_fuse.append(self.relu(y)) @@ -252,67 +238,50 @@ def forward(self, x): return x_fuse -blocks_dict = { - 'BASIC': BasicBlock, - 'BOTTLENECK': Bottleneck -} +blocks_dict = {"BASIC": BasicBlock, "BOTTLENECK": Bottleneck} class HighResolutionNet(nn.Module): - def __init__(self, config, **kwargs): extra = config.MODEL.EXTRA super(HighResolutionNet, self).__init__() # stem net - self.conv1 = nn.Conv2d(config.MODEL.IN_CHANNELS, 64, kernel_size=3, stride=2, padding=1, - bias=False) + self.conv1 = nn.Conv2d(config.MODEL.IN_CHANNELS, 64, kernel_size=3, stride=2, padding=1, bias=False) self.bn1 = BatchNorm2d(64, momentum=BN_MOMENTUM) - self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, - bias=False) + self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, bias=False) self.bn2 = BatchNorm2d(64, momentum=BN_MOMENTUM) self.relu = nn.ReLU(inplace=True) self.layer1 = self._make_layer(Bottleneck, 64, 64, 4) - self.stage2_cfg = extra['STAGE2'] - num_channels = self.stage2_cfg['NUM_CHANNELS'] - block = blocks_dict[self.stage2_cfg['BLOCK']] - num_channels = [ - num_channels[i] * block.expansion for i in range(len(num_channels))] + self.stage2_cfg = extra["STAGE2"] + num_channels = self.stage2_cfg["NUM_CHANNELS"] + block = blocks_dict[self.stage2_cfg["BLOCK"]] + num_channels = [num_channels[i] * block.expansion for i in range(len(num_channels))] self.transition1 = self._make_transition_layer([256], num_channels) - self.stage2, pre_stage_channels = self._make_stage( - self.stage2_cfg, num_channels) - - self.stage3_cfg = extra['STAGE3'] - num_channels = self.stage3_cfg['NUM_CHANNELS'] - block = blocks_dict[self.stage3_cfg['BLOCK']] - num_channels = [ - num_channels[i] * block.expansion for i in range(len(num_channels))] - self.transition2 = self._make_transition_layer( - pre_stage_channels, num_channels) - self.stage3, pre_stage_channels = self._make_stage( - self.stage3_cfg, num_channels) - - self.stage4_cfg = extra['STAGE4'] - num_channels = self.stage4_cfg['NUM_CHANNELS'] - block = blocks_dict[self.stage4_cfg['BLOCK']] - num_channels = [ - num_channels[i] * block.expansion for i in range(len(num_channels))] - self.transition3 = self._make_transition_layer( - pre_stage_channels, num_channels) - self.stage4, pre_stage_channels = self._make_stage( - self.stage4_cfg, num_channels, multi_scale_output=True) - + self.stage2, pre_stage_channels = self._make_stage(self.stage2_cfg, num_channels) + + self.stage3_cfg = extra["STAGE3"] + num_channels = self.stage3_cfg["NUM_CHANNELS"] + block = blocks_dict[self.stage3_cfg["BLOCK"]] + num_channels = [num_channels[i] * block.expansion for i in range(len(num_channels))] + self.transition2 = self._make_transition_layer(pre_stage_channels, num_channels) + self.stage3, pre_stage_channels = self._make_stage(self.stage3_cfg, num_channels) + + self.stage4_cfg = extra["STAGE4"] + num_channels = self.stage4_cfg["NUM_CHANNELS"] + block = blocks_dict[self.stage4_cfg["BLOCK"]] + num_channels = [num_channels[i] * block.expansion for i in range(len(num_channels))] + self.transition3 = self._make_transition_layer(pre_stage_channels, num_channels) + self.stage4, pre_stage_channels = self._make_stage(self.stage4_cfg, num_channels, multi_scale_output=True) + last_inp_channels = np.int(np.sum(pre_stage_channels)) self.last_layer = nn.Sequential( nn.Conv2d( - in_channels=last_inp_channels, - out_channels=last_inp_channels, - kernel_size=1, - stride=1, - padding=0), + in_channels=last_inp_channels, out_channels=last_inp_channels, kernel_size=1, stride=1, padding=0, + ), BatchNorm2d(last_inp_channels, momentum=BN_MOMENTUM), nn.ReLU(inplace=True), nn.Conv2d( @@ -320,11 +289,11 @@ def __init__(self, config, **kwargs): out_channels=config.DATASET.NUM_CLASSES, kernel_size=extra.FINAL_CONV_KERNEL, stride=1, - padding=1 if extra.FINAL_CONV_KERNEL == 3 else 0) + padding=1 if extra.FINAL_CONV_KERNEL == 3 else 0, + ), ) - def _make_transition_layer( - self, num_channels_pre_layer, num_channels_cur_layer): + def _make_transition_layer(self, num_channels_pre_layer, num_channels_cur_layer): num_branches_cur = len(num_channels_cur_layer) num_branches_pre = len(num_channels_pre_layer) @@ -332,29 +301,27 @@ def _make_transition_layer( for i in range(num_branches_cur): if i < num_branches_pre: if num_channels_cur_layer[i] != num_channels_pre_layer[i]: - transition_layers.append(nn.Sequential( - nn.Conv2d(num_channels_pre_layer[i], - num_channels_cur_layer[i], - 3, - 1, - 1, - bias=False), - BatchNorm2d( - num_channels_cur_layer[i], momentum=BN_MOMENTUM), - nn.ReLU(inplace=True))) + transition_layers.append( + nn.Sequential( + nn.Conv2d(num_channels_pre_layer[i], num_channels_cur_layer[i], 3, 1, 1, bias=False,), + BatchNorm2d(num_channels_cur_layer[i], momentum=BN_MOMENTUM), + nn.ReLU(inplace=True), + ) + ) else: transition_layers.append(None) else: conv3x3s = [] - for j in range(i+1-num_branches_pre): + for j in range(i + 1 - num_branches_pre): inchannels = num_channels_pre_layer[-1] - outchannels = num_channels_cur_layer[i] \ - if j == i-num_branches_pre else inchannels - conv3x3s.append(nn.Sequential( - nn.Conv2d( - inchannels, outchannels, 3, 2, 1, bias=False), - BatchNorm2d(outchannels, momentum=BN_MOMENTUM), - nn.ReLU(inplace=True))) + outchannels = num_channels_cur_layer[i] if j == i - num_branches_pre else inchannels + conv3x3s.append( + nn.Sequential( + nn.Conv2d(inchannels, outchannels, 3, 2, 1, bias=False), + BatchNorm2d(outchannels, momentum=BN_MOMENTUM), + nn.ReLU(inplace=True), + ) + ) transition_layers.append(nn.Sequential(*conv3x3s)) return nn.ModuleList(transition_layers) @@ -363,8 +330,7 @@ def _make_layer(self, block, inplanes, planes, blocks, stride=1): downsample = None if stride != 1 or inplanes != planes * block.expansion: downsample = nn.Sequential( - nn.Conv2d(inplanes, planes * block.expansion, - kernel_size=1, stride=stride, bias=False), + nn.Conv2d(inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False,), BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), ) @@ -376,14 +342,13 @@ def _make_layer(self, block, inplanes, planes, blocks, stride=1): return nn.Sequential(*layers) - def _make_stage(self, layer_config, num_inchannels, - multi_scale_output=True): - num_modules = layer_config['NUM_MODULES'] - num_branches = layer_config['NUM_BRANCHES'] - num_blocks = layer_config['NUM_BLOCKS'] - num_channels = layer_config['NUM_CHANNELS'] - block = blocks_dict[layer_config['BLOCK']] - fuse_method = layer_config['FUSE_METHOD'] + def _make_stage(self, layer_config, num_inchannels, multi_scale_output=True): + num_modules = layer_config["NUM_MODULES"] + num_branches = layer_config["NUM_BRANCHES"] + num_blocks = layer_config["NUM_BLOCKS"] + num_channels = layer_config["NUM_CHANNELS"] + block = blocks_dict[layer_config["BLOCK"]] + fuse_method = layer_config["FUSE_METHOD"] modules = [] for i in range(num_modules): @@ -393,13 +358,15 @@ def _make_stage(self, layer_config, num_inchannels, else: reset_multi_scale_output = True modules.append( - HighResolutionModule(num_branches, - block, - num_blocks, - num_inchannels, - num_channels, - fuse_method, - reset_multi_scale_output) + HighResolutionModule( + num_branches, + block, + num_blocks, + num_inchannels, + num_channels, + fuse_method, + reset_multi_scale_output, + ) ) num_inchannels = modules[-1].get_num_inchannels() @@ -415,7 +382,7 @@ def forward(self, x): x = self.layer1(x) x_list = [] - for i in range(self.stage2_cfg['NUM_BRANCHES']): + for i in range(self.stage2_cfg["NUM_BRANCHES"]): if self.transition1[i] is not None: x_list.append(self.transition1[i](x)) else: @@ -423,7 +390,7 @@ def forward(self, x): y_list = self.stage2(x_list) x_list = [] - for i in range(self.stage3_cfg['NUM_BRANCHES']): + for i in range(self.stage3_cfg["NUM_BRANCHES"]): if self.transition2[i] is not None: x_list.append(self.transition2[i](y_list[-1])) else: @@ -431,7 +398,7 @@ def forward(self, x): y_list = self.stage3(x_list) x_list = [] - for i in range(self.stage4_cfg['NUM_BRANCHES']): + for i in range(self.stage4_cfg["NUM_BRANCHES"]): if self.transition3[i] is not None: x_list.append(self.transition3[i](y_list[-1])) else: @@ -440,9 +407,9 @@ def forward(self, x): # Upsampling x0_h, x0_w = x[0].size(2), x[0].size(3) - x1 = F.upsample(x[1], size=(x0_h, x0_w), mode='bilinear') - x2 = F.upsample(x[2], size=(x0_h, x0_w), mode='bilinear') - x3 = F.upsample(x[3], size=(x0_h, x0_w), mode='bilinear') + x1 = F.upsample(x[1], size=(x0_h, x0_w), mode="bilinear") + x2 = F.upsample(x[2], size=(x0_h, x0_w), mode="bilinear") + x3 = F.upsample(x[3], size=(x0_h, x0_w), mode="bilinear") x = torch.cat([x[0], x1, x2, x3], 1) @@ -450,8 +417,10 @@ def forward(self, x): return x - def init_weights(self, pretrained='',): - logger.info('=> init weights from normal distribution') + def init_weights( + self, pretrained="", + ): + logger.info("=> init weights from normal distribution") for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.normal_(m.weight, std=0.001) @@ -460,16 +429,16 @@ def init_weights(self, pretrained='',): nn.init.constant_(m.bias, 0) if os.path.isfile(pretrained): pretrained_dict = torch.load(pretrained) - logger.info('=> loading pretrained model {}'.format(pretrained)) + logger.info("=> loading pretrained model {}".format(pretrained)) model_dict = self.state_dict() - pretrained_dict = {k: v for k, v in pretrained_dict.items() - if k in model_dict.keys()} - #for k, _ in pretrained_dict.items(): + pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict.keys()} + # for k, _ in pretrained_dict.items(): # logger.info( # '=> loading {} pretrained model {}'.format(k, pretrained)) model_dict.update(pretrained_dict) self.load_state_dict(model_dict) + def get_seg_model(cfg, **kwargs): model = HighResolutionNet(cfg, **kwargs) model.init_weights(cfg.MODEL.PRETRAINED) diff --git a/cv_lib/cv_lib/segmentation/models/unet.py b/cv_lib/cv_lib/segmentation/models/unet.py index 95143856..c6ae6813 100644 --- a/cv_lib/cv_lib/segmentation/models/unet.py +++ b/cv_lib/cv_lib/segmentation/models/unet.py @@ -4,12 +4,9 @@ """ Taken from https://github.com/milesial/Pytorch-UNet """ -import numpy as np import torch import torch.nn as nn import torch.nn.functional as F -from torch.autograd import Variable -from torch.nn import init class double_conv(nn.Module): @@ -117,4 +114,3 @@ def forward(self, x): def get_seg_model(cfg, **kwargs): model = UNet(cfg.MODEL.IN_CHANNELS, cfg.DATASET.NUM_CLASSES) return model - diff --git a/cv_lib/cv_lib/segmentation/models/utils.py b/cv_lib/cv_lib/segmentation/models/utils.py index 591ddd60..70b4805f 100644 --- a/cv_lib/cv_lib/segmentation/models/utils.py +++ b/cv_lib/cv_lib/segmentation/models/utils.py @@ -1,15 +1,11 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -import torch import torch.nn as nn -import torch.nn.functional as F class conv2DBatchNorm(nn.Module): - def __init__( - self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1 - ): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): super(conv2DBatchNorm, self).__init__() if dilation > 1: @@ -47,12 +43,7 @@ def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): self.dcb_unit = nn.Sequential( nn.ConvTranspose2d( - int(in_channels), - int(n_filters), - kernel_size=k_size, - padding=padding, - stride=stride, - bias=bias, + int(in_channels), int(n_filters), kernel_size=k_size, padding=padding, stride=stride, bias=bias, ), nn.BatchNorm2d(int(n_filters)), ) @@ -63,9 +54,7 @@ def forward(self, inputs): class conv2DBatchNormRelu(nn.Module): - def __init__( - self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1 - ): + def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True, dilation=1): super(conv2DBatchNormRelu, self).__init__() if dilation > 1: @@ -90,9 +79,7 @@ def __init__( dilation=1, ) - self.cbr_unit = nn.Sequential( - conv_mod, nn.BatchNorm2d(int(n_filters)), nn.ReLU(inplace=True), - ) + self.cbr_unit = nn.Sequential(conv_mod, nn.BatchNorm2d(int(n_filters)), nn.ReLU(inplace=True),) def forward(self, inputs): outputs = self.cbr_unit(inputs) @@ -105,12 +92,7 @@ def __init__(self, in_channels, n_filters, k_size, stride, padding, bias=True): self.dcbr_unit = nn.Sequential( nn.ConvTranspose2d( - int(in_channels), - int(n_filters), - kernel_size=k_size, - padding=padding, - stride=stride, - bias=bias, + int(in_channels), int(n_filters), kernel_size=k_size, padding=padding, stride=stride, bias=bias, ), nn.BatchNorm2d(int(n_filters)), nn.ReLU(inplace=True), diff --git a/cv_lib/cv_lib/segmentation/penobscot/engine.py b/cv_lib/cv_lib/segmentation/penobscot/engine.py index 634fd6ce..0b1273bb 100644 --- a/cv_lib/cv_lib/segmentation/penobscot/engine.py +++ b/cv_lib/cv_lib/segmentation/penobscot/engine.py @@ -52,9 +52,7 @@ def create_supervised_trainer( def _update(engine, batch): model.train() optimizer.zero_grad() - x, y, ids, patch_locations = prepare_batch( - batch, device=device, non_blocking=non_blocking - ) + x, y, ids, patch_locations = prepare_batch(batch, device=device, non_blocking=non_blocking) y_pred = model(x) y_pred = _upscale_model_output(y_pred, y) loss = loss_fn(y_pred.squeeze(1), y.squeeze(1)) @@ -77,12 +75,7 @@ def val_transform(x, y, y_pred, ids, patch_locations): def create_supervised_evaluator( - model, - prepare_batch, - metrics=None, - device=None, - non_blocking=False, - output_transform=val_transform, + model, prepare_batch, metrics=None, device=None, non_blocking=False, output_transform=val_transform, ): """Factory function for creating an evaluator for supervised segmentation models. @@ -113,9 +106,7 @@ def create_supervised_evaluator( def _inference(engine, batch): model.eval() with torch.no_grad(): - x, y, ids, patch_locations = prepare_batch( - batch, device=device, non_blocking=non_blocking - ) + x, y, ids, patch_locations = prepare_batch(batch, device=device, non_blocking=non_blocking) y_pred = model(x) y_pred = _upscale_model_output(y_pred, x) return output_transform(x, y, y_pred, ids, patch_locations) diff --git a/cv_lib/cv_lib/segmentation/utils.py b/cv_lib/cv_lib/segmentation/utils.py index 2410fa9a..07951e88 100644 --- a/cv_lib/cv_lib/segmentation/utils.py +++ b/cv_lib/cv_lib/segmentation/utils.py @@ -23,9 +23,7 @@ def save_images(pred_dict, output_dir, num_classes, colours, extra_identifier="" ) -def save_image( - image_numpy_array, output_dir, num_classes, colours, extra_identifier="" -): +def save_image(image_numpy_array, output_dir, num_classes, colours, extra_identifier=""): """Save segmentation map as image Args: @@ -35,11 +33,7 @@ def save_image( colours ([type]): [description] extra_identifier (str, optional): [description]. Defaults to "". """ - im_array = decode_segmap( - image_numpy_array, n_classes=num_classes, label_colours=colours, - ) - im = pipe( - (im_array * 255).astype(np.uint8).squeeze(), _chw_to_hwc, Image.fromarray, - ) + im_array = decode_segmap(image_numpy_array, n_classes=num_classes, label_colours=colours,) + im = pipe((im_array * 255).astype(np.uint8).squeeze(), _chw_to_hwc, Image.fromarray,) filename = path.join(output_dir, f"{id}_{extra_identifier}.png") im.save(filename) diff --git a/cv_lib/cv_lib/utils.py b/cv_lib/cv_lib/utils.py index e06ea5cf..d3e41aeb 100644 --- a/cv_lib/cv_lib/utils.py +++ b/cv_lib/cv_lib/utils.py @@ -12,12 +12,8 @@ def load_log_configuration(log_config_file): raise ValueError(msg) try: logging.config.fileConfig(log_config_file, disable_existing_loggers=False) - logging.getLogger(__name__).info( - "%s configuration file was loaded.", log_config_file - ) + logging.getLogger(__name__).info("%s configuration file was loaded.", log_config_file) except Exception as e: - logging.getLogger(__name__).error( - "Failed to load configuration from %s!", log_config_file - ) + logging.getLogger(__name__).error("Failed to load configuration from %s!", log_config_file) logging.getLogger(__name__).debug(str(e), exc_info=True) raise e diff --git a/cv_lib/requirements.txt b/cv_lib/requirements.txt index 5c472c45..e543e89a 100644 --- a/cv_lib/requirements.txt +++ b/cv_lib/requirements.txt @@ -1,9 +1,9 @@ numpy>=1.16.4 -toolz==0.9.0 -pandas==0.24.2 -ignite==1.1.0 -scikit_learn==0.21.3 -tensorboardX==1.8 -torch==1.2.0 -torchvision==0.4.0 +toolz>=0.9.0 +pandas>=0.24.2 +ignite>=1.1.0 +scikit_learn>=0.21.3 +tensorboardX>=1.8 +torch>=1.2.0 +torchvision>=0.4.0 tqdm>=4.33.0 diff --git a/cv_lib/tests/test_metrics.py b/cv_lib/tests/test_metrics.py index 83cf57cf..23a671eb 100644 --- a/cv_lib/tests/test_metrics.py +++ b/cv_lib/tests/test_metrics.py @@ -63,18 +63,13 @@ def test_class_accuracy(): metric_cm = _get_cm(acc_metric) # assert confusion matrix exists and is all zeroes assert metric_cm is not None - assert ( - torch.min(metric_cm.confusion_matrix) == 0.0 - and torch.max(metric_cm.confusion_matrix) == 0.0 - ) + assert torch.min(metric_cm.confusion_matrix) == 0.0 and torch.max(metric_cm.confusion_matrix) == 0.0 metric_cm.update(output) # Expected result true_res = [1.0, 1.0, 1.0] res = acc_metric.compute().numpy() - assert np.all(res == true_res), "Result {} vs. expected values {}".format( - res, true_res - ) + assert np.all(res == true_res), "Result {} vs. expected values {}".format(res, true_res) ## Imperfect prediction th_y_true, th_y_logits = _compute_th_y_true_y_logits(y_true, y_pred) @@ -86,18 +81,13 @@ def test_class_accuracy(): # Retrieve and update confusion matrix metric_cm = _get_cm(acc_metric) assert metric_cm is not None - assert ( - torch.min(metric_cm.confusion_matrix) == 0.0 - and torch.max(metric_cm.confusion_matrix) == 0.0 - ) + assert torch.min(metric_cm.confusion_matrix) == 0.0 and torch.max(metric_cm.confusion_matrix) == 0.0 metric_cm.update(output) # Expected result true_res = [0.75, 0.0, 0.25] res = acc_metric.compute().numpy() - assert np.all(res == true_res), "Result {} vs. expected values {}".format( - res, true_res - ) + assert np.all(res == true_res), "Result {} vs. expected values {}".format(res, true_res) def test_mean_class_accuracy(): @@ -117,9 +107,7 @@ def test_mean_class_accuracy(): # Expected result true_res = 1.0 res = acc_metric.compute().numpy() - assert res == approx(true_res), "Result {} vs. expected value {}".format( - res, true_res - ) + assert res == approx(true_res), "Result {} vs. expected value {}".format(res, true_res) ## Imperfect prediction th_y_true, th_y_logits = _compute_th_y_true_y_logits(y_true, y_pred) @@ -135,6 +123,4 @@ def test_mean_class_accuracy(): # Expected result true_res = 1 / 3 res = acc_metric.compute().numpy() - assert res == approx(true_res), "Result {} vs. expected value {}".format( - res, true_res - ) + assert res == approx(true_res), "Result {} vs. expected value {}".format(res, true_res) diff --git a/deepseismic/cli/forward.py b/deepseismic/cli/forward.py index 77b94ef7..16881f41 100644 --- a/deepseismic/cli/forward.py +++ b/deepseismic/cli/forward.py @@ -21,10 +21,7 @@ @click.option("-dt", default=2.0, type=float, help="Time increment (in ms)") @click.option("--n-pml", default=10, type=int, help="PML size (in grid points)") @click.option( - "--n-receivers", - default=11, - type=int, - help="Number of receivers per horizontal dimension", + "--n-receivers", default=11, type=int, help="Number of receivers per horizontal dimension", ) @click.option("--space-order", default=2, type=int, help="Space order") @click.option("--spacing", default=10.0, type=float, help="Spacing between grid points") @@ -73,27 +70,15 @@ def ricker(ctx, f0: float): space_order=ctx.obj["space_order"], n_pml=ctx.obj["n_pml"], ) - time_range = TimeAxis( - start=0.0, stop=ctx.obj["duration"], step=ctx.obj["dt"] - ) - source = RickerSource( - name="source", grid=model.grid, f0=f0, npoint=1, time_range=time_range, - ) + time_range = TimeAxis(start=0.0, stop=ctx.obj["duration"], step=ctx.obj["dt"]) + source = RickerSource(name="source", grid=model.grid, f0=f0, npoint=1, time_range=time_range,) source.coordinates.data[0, :] = np.array(model.domain_size) * 0.5 source.coordinates.data[0, -1] = 0.0 n_receivers = ctx.obj["n_receivers"] total_receivers = n_receivers ** (len(model.shape) - 1) - receivers = Receiver( - name="receivers", - grid=model.grid, - npoint=total_receivers, - time_range=time_range, - ) + receivers = Receiver(name="receivers", grid=model.grid, npoint=total_receivers, time_range=time_range,) receivers_coords = np.meshgrid( - *( - np.linspace(start=0, stop=s, num=n_receivers + 2)[1:-1] - for s in model.domain_size[:-1] - ) + *(np.linspace(start=0, stop=s, num=n_receivers + 2)[1:-1] for s in model.domain_size[:-1]) ) for d in range(len(receivers_coords)): receivers.coordinates.data[:, d] = receivers_coords[d].flatten() @@ -101,8 +86,6 @@ def ricker(ctx, f0: float): output_group = output_file.create_group(input_group_name) for input_dataset_name, vp in input_group.items(): model.vp = vp[()] - seismograms = model.solve( - source=source, receivers=receivers, time_range=time_range - ) + seismograms = model.solve(source=source, receivers=receivers, time_range=time_range) output_group.create_dataset(input_dataset_name, data=seismograms) bar.update(1) diff --git a/deepseismic/cli/velocity.py b/deepseismic/cli/velocity.py index c340c7d9..c5ee0d3c 100644 --- a/deepseismic/cli/velocity.py +++ b/deepseismic/cli/velocity.py @@ -20,16 +20,10 @@ ) @click.option("-n", default=1, type=int, help="Number of simulations") @click.option( - "-nx", - default=100, - type=int, - help="Number of grid points along the first dimension", + "-nx", default=100, type=int, help="Number of grid points along the first dimension", ) @click.option( - "-ny", - default=100, - type=int, - help="Number of grid points along the second dimension", + "-ny", default=100, type=int, help="Number of grid points along the second dimension", ) @click.option("-nz", type=int, help="Number of grid points along the third dimension") @click.option("-s", "--seed", default=42, type=int, help="Random seed") @@ -42,9 +36,7 @@ def vp( if nz is not None: shape += (nz,) output_file = h5py.File(output, mode=("a" if append else "w")) - output_group = output_file.create_group( - str(max((int(x) for x in output_file.keys()), default=-1) + 1) - ) + output_group = output_file.create_group(str(max((int(x) for x in output_file.keys()), default=-1) + 1)) ctx.obj["n"] = n ctx.obj["output_file"] = output_file ctx.obj["output_group"] = output_group @@ -55,23 +47,14 @@ def vp( @vp.command() @click.option("--n-layers", default=8, type=int, help="Number of layers") @click.option( - "--initial-vp", - default=(1350.0, 1650.0), - type=(float, float), - help="Initial Vp (in km/s)", + "--initial-vp", default=(1350.0, 1650.0), type=(float, float), help="Initial Vp (in km/s)", ) @click.option( - "--vp-perturbation", - default=(-190.0, 570.0), - type=(float, float), - help="Per-layer Vp perturbation (in km/s)", + "--vp-perturbation", default=(-190.0, 570.0), type=(float, float), help="Per-layer Vp perturbation (in km/s)", ) @click.pass_context def rt( - ctx, - initial_vp: Tuple[float, float], - n_layers: int, - vp_perturbation: Tuple[float, float], + ctx, initial_vp: Tuple[float, float], n_layers: int, vp_perturbation: Tuple[float, float], ): """Röth-Tarantola model""" model = RoethTarantolaGenerator( diff --git a/deepseismic/forward/models.py b/deepseismic/forward/models.py index eaa0e56d..e4e850f7 100644 --- a/deepseismic/forward/models.py +++ b/deepseismic/forward/models.py @@ -41,20 +41,10 @@ def __init__( shape_pml = tuple(x + 2 * n_pml for x in shape) extent_pml = tuple(s * (d - 1) for s, d in zip(spacing, shape_pml)) origin_pml = tuple(dtype(o - s * n_pml) for o, s in zip(origin, spacing)) - self.grid = Grid( - shape=shape_pml, - extent=extent_pml, - origin=origin_pml, - dtype=dtype, - subdomains=subdomains, - ) + self.grid = Grid(shape=shape_pml, extent=extent_pml, origin=origin_pml, dtype=dtype, subdomains=subdomains,) self.n_pml = n_pml self.pml = Function(name="pml", grid=self.grid) - pml_data = np.pad( - np.zeros(shape, dtype=dtype), - [(n_pml,) * 2 for _ in range(self.pml.ndim)], - mode="edge", - ) + pml_data = np.pad(np.zeros(shape, dtype=dtype), [(n_pml,) * 2 for _ in range(self.pml.ndim)], mode="edge",) pml_coef = 1.5 * np.log(1000.0) / 40.0 for d in range(self.pml.ndim): for i in range(n_pml): @@ -65,9 +55,7 @@ def __init__( pml_data[tuple(idx)] += val / self.grid.spacing[d] idx[d] = slice(pml_data.shape[d] - i, pml_data.shape[d] - i + 1) pml_data[tuple(idx)] += val / self.grid.spacing[d] - pml_data = np.pad( - pml_data, [(i.left, i.right) for i in self.pml._size_halo], mode="edge", - ) + pml_data = np.pad(pml_data, [(i.left, i.right) for i in self.pml._size_halo], mode="edge",) self.pml.data_with_halo[:] = pml_data self.shape = shape @@ -120,12 +108,8 @@ def vp(self) -> Union[float, np.ndarray]: def vp(self, vp: Union[float, np.ndarray]) -> None: self._vp = vp if isinstance(vp, np.ndarray): - pad_widths = [ - (self.n_pml + i.left, self.n_pml + i.right) for i in self.m._size_halo - ] - self.m.data_with_halo[:] = np.pad( - 1.0 / self.vp ** 2.0, pad_widths, mode="edge" - ) + pad_widths = [(self.n_pml + i.left, self.n_pml + i.right) for i in self.m._size_halo] + self.m.data_with_halo[:] = np.pad(1.0 / self.vp ** 2.0, pad_widths, mode="edge") else: self.m.data = 1.0 / float(vp) ** 2.0 @@ -138,16 +122,12 @@ def solve( kernel: Optional[Kernel] = Kernel.OT2, ) -> np.ndarray: assert isinstance(kernel, Kernel) - u = TimeFunction( - name="u", grid=self.grid, time_order=2, space_order=space_order - ) + u = TimeFunction(name="u", grid=self.grid, time_order=2, space_order=space_order) H = u.laplace if kernel is Kernel.OT4: H += self.time_spacing ** 2 / 12 * u.laplace2(1 / self.m) eq = Eq(u.forward, solve(self.m * u.dt2 - H + self.pml * u.dt, u.forward)) - src_term = source.inject( - field=u.forward, expr=source * self.time_spacing ** 2 / self.m - ) + src_term = source.inject(field=u.forward, expr=source * self.time_spacing ** 2 / self.m) rec_term = receivers.interpolate(expr=u) op = Operator([eq] + src_term + rec_term, subs=self.spacing_map) op(time=time_range.num - 1, dt=time_range.step) diff --git a/deepseismic/forward/sources.py b/deepseismic/forward/sources.py index 576e3934..4d1e72ea 100644 --- a/deepseismic/forward/sources.py +++ b/deepseismic/forward/sources.py @@ -27,9 +27,7 @@ def __new__(cls, *args, **kwargs): npoint = kwargs.pop("npoint", None) coordinates = kwargs.pop("coordinates", kwargs.pop("coordinates_data", None)) if npoint is None: - assert ( - coordinates is not None - ), "Either `npoint` or `coordinates` must be provided" + assert coordinates is not None, "Either `npoint` or `coordinates` must be provided" npoint = coordinates.shape[0] obj = SparseTimeFunction.__new__( cls, @@ -63,9 +61,7 @@ def resample( rtol: Optional[float] = 1.0e-5, order: Optional[int] = 3, ): - assert (dt is not None) ^ ( - num is not None - ), "Exactly one of `dt` or `num` must be provided" + assert (dt is not None) ^ (num is not None), "Exactly one of `dt` or `num` must be provided" start = self._time_range.start stop = self._time_range.stop dt0 = self._time_range.step @@ -79,9 +75,7 @@ def resample( n_traces = self.data.shape[1] new_traces = np.zeros((new_time_range.num, n_traces), dtype=self.data.dtype) for j in range(n_traces): - tck = interpolate.splrep( - self._time_range.time_values, self.data[:, j], k=order - ) + tck = interpolate.splrep(self._time_range.time_values, self.data[:, j], k=order) new_traces[:, j] = interpolate.splev(new_time_range.time_values, tck) return PointSource( name=self.name, diff --git a/deepseismic/forward/subdomains.py b/deepseismic/forward/subdomains.py index fcabe59b..de209494 100644 --- a/deepseismic/forward/subdomains.py +++ b/deepseismic/forward/subdomains.py @@ -13,7 +13,5 @@ def __init__(self, n_pml: int): super().__init__() self.n_pml = n_pml - def define( - self, dimensions: Iterable[Dimension] - ) -> Dict[Dimension, Tuple[str, int, int]]: + def define(self, dimensions: Iterable[Dimension]) -> Dict[Dimension, Tuple[str, int, int]]: return {d: ("middle", self.n_pml, self.n_pml) for d in dimensions} diff --git a/deepseismic/velocity/generator.py b/deepseismic/velocity/generator.py index 5b28721c..fd4deba2 100644 --- a/deepseismic/velocity/generator.py +++ b/deepseismic/velocity/generator.py @@ -8,10 +8,7 @@ class Generator(object): def __init__( - self, - shape: Tuple[int, ...], - dtype: Optional[type] = np.float32, - seed: Optional[int] = None, + self, shape: Tuple[int, ...], dtype: Optional[type] = np.float32, seed: Optional[int] = None, ): self.shape = shape self.dtype = dtype diff --git a/deepseismic/velocity/roeth_tarantola.py b/deepseismic/velocity/roeth_tarantola.py index 89f860ae..78c04536 100644 --- a/deepseismic/velocity/roeth_tarantola.py +++ b/deepseismic/velocity/roeth_tarantola.py @@ -28,9 +28,7 @@ def __init__( def generate(self) -> np.ndarray: vp = np.zeros(self.shape, dtype=self.dtype) dim = self.depth_dim - layer_idx = np.round(np.linspace(0, self.shape[dim], self.n_layers + 1)).astype( - np.int - ) + layer_idx = np.round(np.linspace(0, self.shape[dim], self.n_layers + 1)).astype(np.int) vp_idx = [slice(0, x) for x in vp.shape] layer_vp = None for i in range(self.n_layers): diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 77099297..d3351017 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -12,8 +12,8 @@ dependencies: - pandas - opencv - scikit-learn - - tensorflow-gpu=1.14 - tqdm + - itkwidgets - pip: - segyio - pytorch-ignite==0.3.0.dev20191105 # pre-release until stable available diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index afcaf36d..0c248e0c 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -80,7 +80,7 @@ "\n", "from cv_lib.utils import load_log_configuration\n", "from cv_lib.event_handlers import SnapshotHandler, logging_handlers\n", - "from cv_lib.segmentation import models, tgs_salt\n", + "from cv_lib.segmentation import models\n", "from cv_lib.segmentation.dutchf3.engine import create_supervised_trainer\n", "\n", "from cv_lib.segmentation.dutchf3.utils import (\n", @@ -97,14 +97,15 @@ " get_test_loader,\n", ")\n", "\n", + "from itkwidgets import view\n", + "\n", "from utilities import (\n", " plot_aline,\n", - " plot_f3block_interactive,\n", " prepare_batch,\n", - " _patch_label_2d,\n", - " _compose_processing_pipeline,\n", - " _output_processing_pipeline,\n", - " _write_section_file,\n", + " patch_label_2d,\n", + " compose_processing_pipeline,\n", + " output_processing_pipeline,\n", + " write_section_file,\n", " runningScore,\n", ")\n", "\n", @@ -204,7 +205,7 @@ "metadata": {}, "outputs": [], "source": [ - "plot_f3block_interactive(train_labels)" + "view(train_labels, slicing_planes=True)" ] }, { @@ -503,6 +504,16 @@ "Let's first load the model saved previously." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with open(CONFIG_FILE, \"rt\") as f_read:\n", + " config = yacs.config.load_cfg(f_read)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -708,9 +719,9 @@ ], "metadata": { "kernelspec": { - "display_name": "seismic-interpretation", + "display_name": "Python 3", "language": "python", - "name": "seismic-interpretation" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -722,7 +733,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.7.3" } }, "nbformat": 4, diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index 813a9797..f6bf580c 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -13,14 +13,14 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/dutchf3 + ROOT: /data/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: NAME: seg_hrnet IN_CHANNELS: 3 - PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + PRETRAINED: '/data/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml index 8eda77c3..52657ea0 100644 --- a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml +++ b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml @@ -12,7 +12,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/dutchf3 + ROOT: /data/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: @@ -48,7 +48,7 @@ VALIDATION: BATCH_SIZE_PER_GPU: 512 TEST: - MODEL_PATH: '/data/home/vapaunic/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_100.pth' + MODEL_PATH: '/data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth' TEST_STRIDE: 10 SPLIT: 'test1' # Can be both, test1, test2 INLINE: True diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml index 8e5e5203..d1efc6b5 100644 --- a/examples/interpretation/notebooks/configs/unet.yaml +++ b/examples/interpretation/notebooks/configs/unet.yaml @@ -15,7 +15,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: '/mnt/dutchf3' + ROOT: '/data/dutchf3' CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/examples/interpretation/notebooks/logging.conf b/examples/interpretation/notebooks/logging.conf new file mode 100644 index 00000000..56334fc4 --- /dev/null +++ b/examples/interpretation/notebooks/logging.conf @@ -0,0 +1,34 @@ +[loggers] +keys=root,__main__,event_handlers + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[logger___main__] +level=INFO +handlers=consoleHandler +qualname=__main__ +propagate=0 + +[logger_event_handlers] +level=INFO +handlers=consoleHandler +qualname=event_handlers +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index 135273f3..0ef72f02 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -2,17 +2,14 @@ # Licensed under the MIT License. import itertools + +import matplotlib.pyplot as plt import numpy as np import torch import torch.nn.functional as F - from ignite.utils import convert_tensor - -from toolz import compose, curry, itertoolz, pipe - -import matplotlib.pyplot as plt -import plotly.graph_objects as go from scipy.ndimage import zoom +from toolz import compose, curry, itertoolz, pipe class runningScore(object): @@ -22,17 +19,14 @@ def __init__(self, n_classes): def _fast_hist(self, label_true, label_pred, n_class): mask = (label_true >= 0) & (label_true < n_class) - hist = np.bincount( - n_class * label_true[mask].astype(int) + label_pred[mask], - minlength=n_class ** 2, - ).reshape(n_class, n_class) + hist = np.bincount(n_class * label_true[mask].astype(int) + label_pred[mask], minlength=n_class ** 2,).reshape( + n_class, n_class + ) return hist def update(self, label_trues, label_preds): for lt, lp in zip(label_trues, label_preds): - self.confusion_matrix += self._fast_hist( - lt.flatten(), lp.flatten(), self.n_classes - ) + self.confusion_matrix += self._fast_hist(lt.flatten(), lp.flatten(), self.n_classes) def get_scores(self): """Returns accuracy score evaluation result. @@ -47,9 +41,7 @@ def get_scores(self): mean_acc_cls = np.nanmean(acc_cls) iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) mean_iu = np.nanmean(iu) - freq = ( - hist.sum(axis=1) / hist.sum() - ) # fraction of the pixels that come from each class + freq = hist.sum(axis=1) / hist.sum() # fraction of the pixels that come from each class fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() cls_iu = dict(zip(range(self.n_classes), iu)) @@ -138,12 +130,10 @@ def _extract_patch(hdx, wdx, ps, patch_size, img_p): if len(img_p.shape) == 2: # 2D return img_p[hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size] else: # 3D - return img_p[ - :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size - ] + return img_p[:, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size] -def _compose_processing_pipeline(depth, aug=None): +def compose_processing_pipeline(depth, aug=None): steps = [] if aug is not None: steps.append(_apply_augmentation(aug)) @@ -158,23 +148,19 @@ def _compose_processing_pipeline(depth, aug=None): def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): - hdc_wdx_generator = itertools.product( - range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride) - ) + hdc_wdx_generator = itertools.product(range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride)) for batch_indexes in itertoolz.partition_all(batch_size, hdc_wdx_generator): yield batch_indexes @curry -def _output_processing_pipeline(config, output): +def output_processing_pipeline(config, output): output = output.unsqueeze(0) _, _, h, w = output.shape if config.TEST.POST_PROCESSING.SIZE != h or config.TEST.POST_PROCESSING.SIZE != w: output = F.interpolate( - output, - size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE), - mode="bilinear", + output, size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE), mode="bilinear", ) if config.TEST.POST_PROCESSING.CROP_PIXELS > 0: @@ -182,24 +168,14 @@ def _output_processing_pipeline(config, output): output = output[ :, :, - config.TEST.POST_PROCESSING.CROP_PIXELS : h - - config.TEST.POST_PROCESSING.CROP_PIXELS, - config.TEST.POST_PROCESSING.CROP_PIXELS : w - - config.TEST.POST_PROCESSING.CROP_PIXELS, + config.TEST.POST_PROCESSING.CROP_PIXELS : h - config.TEST.POST_PROCESSING.CROP_PIXELS, + config.TEST.POST_PROCESSING.CROP_PIXELS : w - config.TEST.POST_PROCESSING.CROP_PIXELS, ] return output.squeeze() -def _patch_label_2d( - model, - img, - pre_processing, - output_processing, - patch_size, - stride, - batch_size, - device, - num_classes, +def patch_label_2d( + model, img, pre_processing, output_processing, patch_size, stride, batch_size, device, num_classes, ): """Processes a whole section""" img = torch.squeeze(img) @@ -211,47 +187,23 @@ def _patch_label_2d( output_p = torch.zeros([1, num_classes, h + 2 * ps, w + 2 * ps]) # generate output: - for batch_indexes in _generate_batches( - h, w, ps, patch_size, stride, batch_size=batch_size - ): + for batch_indexes in _generate_batches(h, w, ps, patch_size, stride, batch_size=batch_size): batch = torch.stack( - [ - pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing) - for hdx, wdx in batch_indexes - ], + [pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing) for hdx, wdx in batch_indexes], dim=0, ) model_output = model(batch.to(device)) for (hdx, wdx), output in zip(batch_indexes, model_output.detach().cpu()): output = output_processing(output) - output_p[ - :, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size - ] += output + output_p[:, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size] += output # crop the output_p in the middle output = output_p[:, :, ps:-ps, ps:-ps] return output -@curry -def to_image(label_mask, n_classes=6): - label_colours = get_seismic_labels() - r = label_mask.copy() - g = label_mask.copy() - b = label_mask.copy() - for ll in range(0, n_classes): - r[label_mask == ll] = label_colours[ll, 0] - g[label_mask == ll] = label_colours[ll, 1] - b[label_mask == ll] = label_colours[ll, 2] - rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3)) - rgb[:, :, :, 0] = r - rgb[:, :, :, 1] = g - rgb[:, :, :, 2] = b - return rgb - - -def _write_section_file(labels, section_file, config): +def write_section_file(labels, section_file, config): # define indices of the array irange, xrange, depth = labels.shape @@ -288,40 +240,3 @@ def plot_aline(aline, labels, xlabel, ylabel="depth"): plt.imshow(labels) plt.xlabel(xlabel) plt.title("Label") - - -def plot_f3block_interactive(data, x_slice_locations=[0.25], y_slice_locations=[0.8]): - """Plot interactive graph of F3 block""" - values = zoom(data, 0.2) - values = values[:, :, ::-1] - - x, y, z = values.shape - X, Y, Z = np.mgrid[0:x, 0:y, 0:z] - - fig = go.Figure( - data=go.Volume( - x=X.flatten(), - y=Y.flatten(), - z=Z.flatten(), - value=values.flatten(), - slices_x=dict(show=True, locations=[i * x for i in x_slice_locations]), - slices_y=dict(show=True, locations=[i * y for i in y_slice_locations]), - opacity=0.5, # needs to be small to see through all surfaces - showscale=False, - caps=dict(x_show=True, y_show=True, z_show=True), - colorscale="Viridis", - ) - ) - - fig.update_layout( - scene_xaxis_showticklabels=False, - scene_yaxis_showticklabels=False, - scene_zaxis_showticklabels=False, - height=800, - width=1000, - title="F3 Block Netherlands", - scene=dict( - xaxis_title="Inlines", yaxis_title="Crosslines", zaxis_title="Depth" - ), - ) - fig.show() diff --git a/experiments/interpretation/demo/local/default.py b/experiments/interpretation/demo/local/default.py index ceec54c1..24f7671a 100644 --- a/experiments/interpretation/demo/local/default.py +++ b/experiments/interpretation/demo/local/default.py @@ -119,4 +119,3 @@ def update_config(cfg, options=None, config_file=None): with open(sys.argv[1], "w") as f: print(_C, file=f) - diff --git a/experiments/interpretation/demo/local/train.py b/experiments/interpretation/demo/local/train.py index 37e8f0a8..d896f84b 100644 --- a/experiments/interpretation/demo/local/train.py +++ b/experiments/interpretation/demo/local/train.py @@ -55,15 +55,7 @@ mask_value = 255 _SEG_COLOURS = np.asarray( - [ - [241, 238, 246], - [208, 209, 230], - [166, 189, 219], - [116, 169, 207], - [54, 144, 192], - [5, 112, 176], - [3, 78, 123], - ] + [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123],] ) @@ -108,11 +100,7 @@ def run(*options, cfg=None): # Setup Augmentations basic_aug = Compose( [ - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=config.TRAIN.MAX, - ), + Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=config.TRAIN.MAX,), PadIfNeeded( min_height=config.TRAIN.PATCH_SIZE, min_width=config.TRAIN.PATCH_SIZE, @@ -122,9 +110,7 @@ def run(*options, cfg=None): value=0, ), Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, @@ -149,10 +135,7 @@ def run(*options, cfg=None): img_name = "rtm3d.bin" seam_image_name = path.join(config.DATASET.ROOT, img_name) # load RTM image, note it is type np.float32 - img = ( - np.fromfile(seam_image_name, dtype=np.float32).reshape(ny, nx, nz) - / 71892250000.0 - ) + img = np.fromfile(seam_image_name, dtype=np.float32).reshape(ny, nx, nz) / 71892250000.0 salt_name = "salt_mask.bin" salt_mask_name = path.join(config.DATASET.ROOT, salt_name) @@ -185,17 +168,10 @@ def run(*options, cfg=None): n_classes = train_set.n_classes train_loader = data.DataLoader( - train_set, - batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - shuffle=True, + train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True, ) - val_loader = data.DataLoader( - val_set, - batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - ) + val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -211,41 +187,26 @@ def run(*options, cfg=None): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration - ) + scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) criterion = torch.nn.CrossEntropyLoss(ignore_index=mask_value, reduction="mean") - trainer = create_supervised_trainer( - model, optimizer, criterion, _prepare_batch, device=device - ) + trainer = create_supervised_trainer(model, optimizer, criterion, _prepare_batch, device=device) trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), ) trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), + Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer), ) def _select_pred_and_mask(model_out_dict): @@ -255,14 +216,10 @@ def _select_pred_and_mask(model_out_dict): model, _prepare_batch, metrics={ - "pixacc": pixelwise_accuracy( - n_classes, output_transform=_select_pred_and_mask - ), + "pixacc": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask), "nll": Loss(criterion, output_transform=_select_pred_and_mask), "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask), - "mca": mean_class_accuracy( - n_classes, output_transform=_select_pred_and_mask - ), + "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask), "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask), "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask), }, @@ -306,28 +263,21 @@ def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() transform_func = compose( - np_to_tb, - decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), - _tensor_to_numpy, + np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy, ) transform_pred = compose(transform_func, _select_max) evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), + Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Validation/Image", "image"), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Mask", "mask", transform_func=transform_func - ), + create_image_writer(summary_writer, "Validation/Mask", "mask", transform_func=transform_func), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred - ), + create_image_writer(summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred), ) def snapshot_function(): @@ -339,9 +289,7 @@ def snapshot_function(): extract_metric_from("mIoU"), snapshot_function, ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) + evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/experiments/interpretation/dutchf3_patch/distributed/default.py b/experiments/interpretation/dutchf3_patch/distributed/default.py index d42e4716..bf23527b 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/default.py +++ b/experiments/interpretation/dutchf3_patch/distributed/default.py @@ -84,9 +84,7 @@ _C.TEST.CROSSLINE = True _C.TEST.POST_PROCESSING = CN() # Model output postprocessing _C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels -_C.TEST.POST_PROCESSING.CROP_PIXELS = ( - 14 # Number of pixels to crop top, bottom, left and right -) +_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right def update_config(cfg, options=None, config_file=None): @@ -106,4 +104,3 @@ def update_config(cfg, options=None, config_file=None): with open(sys.argv[1], "w") as f: print(_C, file=f) - diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py index 51b07d6f..94940f21 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -120,13 +120,9 @@ def run(*options, cfg=None, local_rank=0): # Setup Augmentations basic_aug = Compose( [ - Normalize( - mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1 - ), + Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1), Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, @@ -166,26 +162,16 @@ def run(*options, cfg=None, local_rank=0): logger.info(f"Validation examples {len(val_set)}") n_classes = train_set.n_classes - train_sampler = torch.utils.data.distributed.DistributedSampler( - train_set, num_replicas=world_size, rank=local_rank - ) + train_sampler = torch.utils.data.distributed.DistributedSampler(train_set, num_replicas=world_size, rank=local_rank) train_loader = data.DataLoader( - train_set, - batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - sampler=train_sampler, + train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, sampler=train_sampler, ) - val_sampler = torch.utils.data.distributed.DistributedSampler( - val_set, num_replicas=world_size, rank=local_rank - ) + val_sampler = torch.utils.data.distributed.DistributedSampler(val_set, num_replicas=world_size, rank=local_rank) val_loader = data.DataLoader( - val_set, - batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - sampler=val_sampler, + val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, sampler=val_sampler, ) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -204,17 +190,11 @@ def run(*options, cfg=None, local_rank=0): # weights are inversely proportional to the frequency of the classes in # the training set - class_weights = torch.tensor( - config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False - ) + class_weights = torch.tensor(config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False) - criterion = torch.nn.CrossEntropyLoss( - weight=class_weights, ignore_index=255, reduction="mean" - ) + criterion = torch.nn.CrossEntropyLoss(weight=class_weights, ignore_index=255, reduction="mean") - model = torch.nn.parallel.DistributedDataParallel( - model, device_ids=[device], find_unused_parameters=True - ) + model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[device], find_unused_parameters=True) snapshot_duration = scheduler_step * len(train_loader) warmup_duration = 5 * len(train_loader) @@ -226,20 +206,12 @@ def run(*options, cfg=None, local_rank=0): cycle_size=10 * len(train_loader), ) cosine_scheduler = CosineAnnealingScheduler( - optimizer, - "lr", - config.TRAIN.MAX_LR * world_size, - config.TRAIN.MIN_LR * world_size, - snapshot_duration, + optimizer, "lr", config.TRAIN.MAX_LR * world_size, config.TRAIN.MIN_LR * world_size, snapshot_duration, ) - scheduler = ConcatScheduler( - schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration] - ) + scheduler = ConcatScheduler(schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration]) - trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_batch, device=device - ) + trainer = create_supervised_trainer(model, optimizer, criterion, prepare_batch, device=device) trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) # Set to update the epoch parameter of our distributed data sampler so that we get @@ -256,24 +228,12 @@ def _select_pred_and_mask(model_out_dict): model, prepare_batch, metrics={ - "nll": Loss( - criterion, output_transform=_select_pred_and_mask, device=device - ), - "pixa": pixelwise_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "cacc": class_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "mca": mean_class_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "ciou": class_iou( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "mIoU": mean_iou( - n_classes, output_transform=_select_pred_and_mask, device=device - ), + "nll": Loss(criterion, output_transform=_select_pred_and_mask, device=device), + "pixa": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask, device=device), + "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask, device=device), }, device=device, ) @@ -284,31 +244,18 @@ def _select_pred_and_mask(model_out_dict): if local_rank == 0: # Run only on master process trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler( - Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer) + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) logger.info(f"Logging Tensorboard to {path.join(output_dir, config.LOG_DIR)}") trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), ) trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), + Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, @@ -328,11 +275,7 @@ def _select_pred_and_mask(model_out_dict): summary_writer, trainer, "epoch", - metrics_dict={ - "mIoU": "Validation/IoU", - "nll": "Validation/Loss", - "mca": "Validation/MCA", - }, + metrics_dict={"mIoU": "Validation/IoU", "nll": "Validation/Loss", "mca": "Validation/MCA",}, ), ) @@ -342,30 +285,20 @@ def _select_max(pred_tensor): def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() - transform_func = compose( - np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy - ) + transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy) transform_pred = compose(transform_func, _select_max) evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), + Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Validation/Image", "image"), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Mask", "mask", transform_func=transform_func - ), + create_image_writer(summary_writer, "Validation/Mask", "mask", transform_func=transform_func), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, - "Validation/Pred", - "y_pred", - transform_func=transform_pred, - ), + create_image_writer(summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred,), ) def snapshot_function(): @@ -377,9 +310,7 @@ def snapshot_function(): extract_metric_from("mIoU"), snapshot_function, ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) + evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") diff --git a/experiments/interpretation/dutchf3_patch/local/default.py b/experiments/interpretation/dutchf3_patch/local/default.py index d28cc268..e34627a8 100644 --- a/experiments/interpretation/dutchf3_patch/local/default.py +++ b/experiments/interpretation/dutchf3_patch/local/default.py @@ -11,9 +11,7 @@ _C = CN() -_C.OUTPUT_DIR = ( - "output" # The base directory for all output, such as logs and saved models -) +_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.GPUS = (0,) _C.WORKERS = 4 @@ -85,9 +83,7 @@ _C.TEST.CROSSLINE = True _C.TEST.POST_PROCESSING = CN() # Model output postprocessing _C.TEST.POST_PROCESSING.SIZE = 128 # Size to interpolate to in pixels -_C.TEST.POST_PROCESSING.CROP_PIXELS = ( - 14 # Number of pixels to crop top, bottom, left and right -) +_C.TEST.POST_PROCESSING.CROP_PIXELS = 14 # Number of pixels to crop top, bottom, left and right def update_config(cfg, options=None, config_file=None): @@ -107,4 +103,3 @@ def update_config(cfg, options=None, config_file=None): with open(sys.argv[1], "w") as f: print(_C, file=f) - diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index 86ceceed..6e34e54e 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -49,17 +49,14 @@ def __init__(self, n_classes): def _fast_hist(self, label_true, label_pred, n_class): mask = (label_true >= 0) & (label_true < n_class) - hist = np.bincount( - n_class * label_true[mask].astype(int) + label_pred[mask], - minlength=n_class ** 2, - ).reshape(n_class, n_class) + hist = np.bincount(n_class * label_true[mask].astype(int) + label_pred[mask], minlength=n_class ** 2,).reshape( + n_class, n_class + ) return hist def update(self, label_trues, label_preds): for lt, lp in zip(label_trues, label_preds): - self.confusion_matrix += self._fast_hist( - lt.flatten(), lp.flatten(), self.n_classes - ) + self.confusion_matrix += self._fast_hist(lt.flatten(), lp.flatten(), self.n_classes) def get_scores(self): """Returns accuracy score evaluation result. @@ -74,9 +71,7 @@ def get_scores(self): mean_acc_cls = np.nanmean(acc_cls) iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) mean_iu = np.nanmean(iu) - freq = ( - hist.sum(axis=1) / hist.sum() - ) # fraction of the pixels that come from each class + freq = hist.sum(axis=1) / hist.sum() # fraction of the pixels that come from each class fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() cls_iu = dict(zip(range(self.n_classes), iu)) @@ -177,10 +172,7 @@ def _compose_processing_pipeline(depth, aug=None): def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): - hdc_wdx_generator = itertools.product( - range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride), - ) - + hdc_wdx_generator = itertools.product(range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride),) for batch_indexes in itertoolz.partition_all(batch_size, hdc_wdx_generator): yield batch_indexes @@ -191,9 +183,7 @@ def _output_processing_pipeline(config, output): _, _, h, w = output.shape if config.TEST.POST_PROCESSING.SIZE != h or config.TEST.POST_PROCESSING.SIZE != w: output = F.interpolate( - output, - size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE,), - mode="bilinear", + output, size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE,), mode="bilinear", ) if config.TEST.POST_PROCESSING.CROP_PIXELS > 0: @@ -201,24 +191,14 @@ def _output_processing_pipeline(config, output): output = output[ :, :, - config.TEST.POST_PROCESSING.CROP_PIXELS : h - - config.TEST.POST_PROCESSING.CROP_PIXELS, - config.TEST.POST_PROCESSING.CROP_PIXELS : w - - config.TEST.POST_PROCESSING.CROP_PIXELS, + config.TEST.POST_PROCESSING.CROP_PIXELS : h - config.TEST.POST_PROCESSING.CROP_PIXELS, + config.TEST.POST_PROCESSING.CROP_PIXELS : w - config.TEST.POST_PROCESSING.CROP_PIXELS, ] return output.squeeze() def _patch_label_2d( - model, - img, - pre_processing, - output_processing, - patch_size, - stride, - batch_size, - device, - num_classes, + model, img, pre_processing, output_processing, patch_size, stride, batch_size, device, num_classes, ): """Processes a whole section """ @@ -231,26 +211,16 @@ def _patch_label_2d( output_p = torch.zeros([1, num_classes, h + 2 * ps, w + 2 * ps]) # generate output: - for batch_indexes in _generate_batches( - h, w, ps, patch_size, stride, batch_size=batch_size - ): + for batch_indexes in _generate_batches(h, w, ps, patch_size, stride, batch_size=batch_size): batch = torch.stack( - [ - pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing,) - for hdx, wdx in batch_indexes - ], + [pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing,) for hdx, wdx in batch_indexes], dim=0, ) model_output = model(batch.to(device)) for (hdx, wdx), output in zip(batch_indexes, model_output.detach().cpu()): output = output_processing(output) - output_p[ - :, - :, - hdx + ps : hdx + ps + patch_size, - wdx + ps : wdx + ps + patch_size, - ] += output + output_p[:, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size,] += output # crop the output_p in the middle output = output_p[:, :, ps:-ps, ps:-ps] @@ -275,27 +245,16 @@ def to_image(label_mask, n_classes=6): def _evaluate_split( - split, - section_aug, - model, - pre_processing, - output_processing, - device, - running_metrics_overall, - config, + split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, ): logger = logging.getLogger(__name__) TestSectionLoader = get_test_loader(config) - test_set = TestSectionLoader( - config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug, - ) + test_set = TestSectionLoader(config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug,) n_classes = test_set.n_classes - test_loader = data.DataLoader( - test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False - ) + test_loader = data.DataLoader(test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False) running_metrics_split = runningScore(n_classes) # testing mode: @@ -378,20 +337,12 @@ def test(*options, cfg=None): running_metrics_overall = runningScore(n_classes) # Augmentation - section_aug = Compose( - [ - Normalize( - mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1, - ) - ] - ) + section_aug = Compose([Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1,)]) patch_aug = Compose( [ Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, @@ -408,22 +359,11 @@ def test(*options, cfg=None): splits = ["test1", "test2"] if "Both" in config.TEST.SPLIT else [config.TEST.SPLIT] for sdx, split in enumerate(splits): - labels = np.load( - path.join(config.DATASEST.ROOT, "test_once", split + "_labels.npy") - ) - section_file = path.join( - config.DATASEST.ROOT, "splits", "section_" + split + ".txt" - ) + labels = np.load(path.join(config.DATASEST.ROOT, "test_once", split + "_labels.npy")) + section_file = path.join(config.DATASEST.ROOT, "splits", "section_" + split + ".txt") _write_section_file(labels, section_file) _evaluate_split( - split, - section_aug, - model, - pre_processing, - output_processing, - device, - running_metrics_overall, - config, + split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, ) # FINAL TEST RESULTS: diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index cefb5974..ebc128f1 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -96,13 +96,9 @@ def run(*options, cfg=None): # Setup Augmentations basic_aug = Compose( [ - Normalize( - mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1 - ), + Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1), Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, @@ -142,16 +138,9 @@ def run(*options, cfg=None): n_classes = train_set.n_classes train_loader = data.DataLoader( - train_set, - batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - shuffle=True, - ) - val_loader = data.DataLoader( - val_set, - batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, + train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True, ) + val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -167,51 +156,32 @@ def run(*options, cfg=None): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration - ) + scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) # weights are inversely proportional to the frequency of the classes in the # training set - class_weights = torch.tensor( - config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False - ) + class_weights = torch.tensor(config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False) - criterion = torch.nn.CrossEntropyLoss( - weight=class_weights, ignore_index=255, reduction="mean" - ) + criterion = torch.nn.CrossEntropyLoss(weight=class_weights, ignore_index=255, reduction="mean") - trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_batch, device=device - ) + trainer = create_supervised_trainer(model, optimizer, criterion, prepare_batch, device=device) trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), ) trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), + Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer), ) def _select_pred_and_mask(model_out_dict): @@ -222,13 +192,9 @@ def _select_pred_and_mask(model_out_dict): prepare_batch, metrics={ "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "pixacc": pixelwise_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), + "pixacc": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask), - "mca": mean_class_accuracy( - n_classes, output_transform=_select_pred_and_mask - ), + "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask), "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask), "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask), }, @@ -272,27 +238,20 @@ def _select_max(pred_tensor): def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() - transform_func = compose( - np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy - ) + transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy) transform_pred = compose(transform_func, _select_max) evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), + Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Validation/Image", "image"), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Mask", "mask", transform_func=transform_func - ), + create_image_writer(summary_writer, "Validation/Mask", "mask", transform_func=transform_func), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred - ), + create_image_writer(summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred), ) def snapshot_function(): @@ -304,9 +263,7 @@ def snapshot_function(): extract_metric_from("mIoU"), snapshot_function, ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) + evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/experiments/interpretation/dutchf3_section/local/default.py b/experiments/interpretation/dutchf3_section/local/default.py index d57a3c72..5e296295 100644 --- a/experiments/interpretation/dutchf3_section/local/default.py +++ b/experiments/interpretation/dutchf3_section/local/default.py @@ -11,6 +11,7 @@ _C = CN() + _C.OUTPUT_DIR = "output" # Base directory for all output (logs, models, etc) _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.GPUS = (0,) @@ -89,4 +90,3 @@ def update_config(cfg, options=None, config_file=None): with open(sys.argv[1], "w") as f: print(_C, file=f) - diff --git a/experiments/interpretation/dutchf3_section/local/test.py b/experiments/interpretation/dutchf3_section/local/test.py index cf964705..e7968418 100644 --- a/experiments/interpretation/dutchf3_section/local/test.py +++ b/experiments/interpretation/dutchf3_section/local/test.py @@ -20,6 +20,7 @@ from albumentations import Compose, Normalize from cv_lib.utils import load_log_configuration from cv_lib.segmentation import models + from deepseismic_interpretation.dutchf3.data import get_test_loader from default import _C as config from default import update_config @@ -44,17 +45,14 @@ def __init__(self, n_classes): def _fast_hist(self, label_true, label_pred, n_class): mask = (label_true >= 0) & (label_true < n_class) - hist = np.bincount( - n_class * label_true[mask].astype(int) + label_pred[mask], - minlength=n_class ** 2, - ).reshape(n_class, n_class) + hist = np.bincount(n_class * label_true[mask].astype(int) + label_pred[mask], minlength=n_class ** 2,).reshape( + n_class, n_class + ) return hist def update(self, label_trues, label_preds): for lt, lp in zip(label_trues, label_preds): - self.confusion_matrix += self._fast_hist( - lt.flatten(), lp.flatten(), self.n_classes - ) + self.confusion_matrix += self._fast_hist(lt.flatten(), lp.flatten(), self.n_classes) def get_scores(self): """Returns accuracy score evaluation result. @@ -69,9 +67,7 @@ def get_scores(self): mean_acc_cls = np.nanmean(acc_cls) iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist)) mean_iu = np.nanmean(iu) - freq = ( - hist.sum(axis=1) / hist.sum() - ) # fraction of the pixels that come from each class + freq = hist.sum(axis=1) / hist.sum() # fraction of the pixels that come from each class fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() cls_iu = dict(zip(range(self.n_classes), iu)) @@ -97,15 +93,11 @@ def _evaluate_split( logger = logging.getLogger(__name__) TestSectionLoader = get_test_loader(config) - test_set = TestSectionLoader( - data_dir=DATA_ROOT, split=split, is_transform=True, augmentations=section_aug, - ) + test_set = TestSectionLoader(data_dir=DATA_ROOT, split=split, is_transform=True, augmentations=section_aug,) n_classes = test_set.n_classes - test_loader = data.DataLoader( - test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False - ) + test_loader = data.DataLoader(test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False) running_metrics_split = runningScore(n_classes) # testing mode: @@ -178,13 +170,7 @@ def test(*options, cfg=None): running_metrics_overall = runningScore(n_classes) # Augmentation - section_aug = Compose( - [ - Normalize( - mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1, - ) - ] - ) + section_aug = Compose([Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1,)]) splits = ["test1", "test2"] if "Both" in config.TEST.SPLIT else [config.TEST.SPLIT] @@ -192,9 +178,7 @@ def test(*options, cfg=None): labels = np.load(path.join(DATA_ROOT, "test_once", split + "_labels.npy")) section_file = path.join(DATA_ROOT, "splits", "section_" + split + ".txt") _write_section_file(labels, section_file) - _evaluate_split( - split, section_aug, model, device, running_metrics_overall, config - ) + _evaluate_split(split, section_aug, model, device, running_metrics_overall, config) # FINAL TEST RESULTS: score, class_iou = running_metrics_overall.get_scores() diff --git a/experiments/interpretation/dutchf3_section/local/train.py b/experiments/interpretation/dutchf3_section/local/train.py index e8dd6225..99bf4279 100644 --- a/experiments/interpretation/dutchf3_section/local/train.py +++ b/experiments/interpretation/dutchf3_section/local/train.py @@ -90,13 +90,7 @@ def run(*options, cfg=None): np.random.seed(seed=config.SEED) # Setup Augmentations - basic_aug = Compose( - [ - Normalize( - mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1 - ) - ] - ) + basic_aug = Compose([Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1)]) if config.TRAIN.AUGMENTATION: train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)]) val_aug = basic_aug @@ -105,19 +99,9 @@ def run(*options, cfg=None): TrainLoader = get_section_loader(config) - train_set = TrainLoader( - data_dir=config.DATASET.ROOT, - split="train", - is_transform=True, - augmentations=train_aug, - ) + train_set = TrainLoader(data_dir=config.DATASET.ROOT, split="train", is_transform=True, augmentations=train_aug,) - val_set = TrainLoader( - data_dir=config.DATASET.ROOT, - split="val", - is_transform=True, - augmentations=val_aug, - ) + val_set = TrainLoader(data_dir=config.DATASET.ROOT, split="val", is_transform=True, augmentations=val_aug,) class CustomSampler(torch.utils.data.Sampler): def __init__(self, data_source): @@ -125,9 +109,7 @@ def __init__(self, data_source): def __iter__(self): char = ["i" if np.random.randint(2) == 1 else "x"] - self.indices = [ - idx for (idx, name) in enumerate(self.data_source) if char[0] in name - ] + self.indices = [idx for (idx, name) in enumerate(self.data_source) if char[0] in name] return (self.indices[i] for i in torch.randperm(len(self.indices))) def __len__(self): @@ -167,54 +149,35 @@ def __len__(self): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration - ) + scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) # weights are inversely proportional to the frequency of the classes in # the training set - class_weights = torch.tensor( - config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False - ) + class_weights = torch.tensor(config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False) - criterion = torch.nn.CrossEntropyLoss( - weight=class_weights, ignore_index=255, reduction="mean" - ) + criterion = torch.nn.CrossEntropyLoss(weight=class_weights, ignore_index=255, reduction="mean") - trainer = create_supervised_trainer( - model, optimizer, criterion, prepare_batch, device=device - ) + trainer = create_supervised_trainer(model, optimizer, criterion, prepare_batch, device=device) trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), ) trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), + Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer), ) def _select_pred_and_mask(model_out_dict): @@ -224,24 +187,12 @@ def _select_pred_and_mask(model_out_dict): model, prepare_batch, metrics={ - "nll": Loss( - criterion, output_transform=_select_pred_and_mask, device=device - ), - "pixacc": pixelwise_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "cacc": class_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "mca": mean_class_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "ciou": class_iou( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "mIoU": mean_iou( - n_classes, output_transform=_select_pred_and_mask, device=device - ), + "nll": Loss(criterion, output_transform=_select_pred_and_mask, device=device), + "pixacc": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask, device=device), + "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask, device=device), }, device=device, ) @@ -265,8 +216,7 @@ def _select_pred_and_mask(model_out_dict): evaluator.add_event_handler( Events.EPOCH_COMPLETED, logging_handlers.log_class_metrics( - "Per class validation results", - metrics_dict={"ciou": "Class IoU :", "cacc": "Class Accuracy :"}, + "Per class validation results", metrics_dict={"ciou": "Class IoU :", "cacc": "Class Accuracy :"}, ), ) @@ -291,29 +241,22 @@ def _select_max(pred_tensor): def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() - transform_func = compose( - np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy - ) + transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy) transform_pred = compose(transform_func, _select_max) evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), + Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Validation/Image", "image"), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Mask", "mask", transform_func=transform_func - ), + create_image_writer(summary_writer, "Validation/Mask", "mask", transform_func=transform_func), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred - ), + create_image_writer(summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred), ) def snapshot_function(): @@ -326,9 +269,7 @@ def snapshot_function(): snapshot_function, ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) + evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/experiments/interpretation/dutchf3_voxel/default.py b/experiments/interpretation/dutchf3_voxel/default.py index 288a9ac4..100da598 100644 --- a/experiments/interpretation/dutchf3_voxel/default.py +++ b/experiments/interpretation/dutchf3_voxel/default.py @@ -18,9 +18,7 @@ _C.CUDNN.ENABLED = True _C.GPUS = (0,) -_C.OUTPUT_DIR = ( - "output" # The base directory for all output, such as logs and saved models -) +_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.WORKERS = 4 _C.PRINT_FREQ = 20 diff --git a/experiments/interpretation/dutchf3_voxel/train.py b/experiments/interpretation/dutchf3_voxel/train.py index d8810442..bd8cdf4b 100644 --- a/experiments/interpretation/dutchf3_voxel/train.py +++ b/experiments/interpretation/dutchf3_voxel/train.py @@ -50,12 +50,8 @@ def _prepare_batch(batch, device=None, non_blocking=False, t_type=torch.FloatTensor): x, y = batch - new_x = convert_tensor( - torch.squeeze(x, 1), device=device, non_blocking=non_blocking - ) - new_y = convert_tensor( - torch.unsqueeze(y, 2), device=device, non_blocking=non_blocking - ) + new_x = convert_tensor(torch.squeeze(x, 1), device=device, non_blocking=non_blocking) + new_y = convert_tensor(torch.unsqueeze(y, 2), device=device, non_blocking=non_blocking) if device == "cuda": return ( new_x.type(t_type).cuda(), @@ -118,16 +114,10 @@ def run(*options, cfg=None): # set dataset length to batch size to be consistent with 5000 iterations # each of size 32 in the original Waldeland implementation train_loader = data.DataLoader( - train_set, - batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - shuffle=False, + train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=False, ) val_loader = data.DataLoader( - val_set, - batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - shuffle=False, + val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=False, ) # this is how we import model for CV - here we're importing a seismic @@ -149,9 +139,7 @@ def run(*options, cfg=None): loss = torch.nn.CrossEntropyLoss() - trainer = create_supervised_trainer( - model, optimizer, loss, prepare_batch=_prepare_batch, device=device - ) + trainer = create_supervised_trainer(model, optimizer, loss, prepare_batch=_prepare_batch, device=device) desc = "ITERATION - loss: {:.2f}" pbar = tqdm(initial=0, leave=False, total=len(train_loader), desc=desc.format(0)) @@ -159,20 +147,13 @@ def run(*options, cfg=None): # add model checkpointing output_dir = path.join(config.OUTPUT_DIR, config.TRAIN.MODEL_DIR) checkpoint_handler = ModelCheckpoint( - output_dir, - "model", - save_interval=1, - n_saved=3, - create_dir=True, - require_empty=False, + output_dir, "model", save_interval=1, n_saved=3, create_dir=True, require_empty=False, ) criterion = torch.nn.CrossEntropyLoss(reduction="mean") # save model at each epoch - trainer.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model} - ) + trainer.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model}) def _select_pred_and_mask(model_out): # receive a tuple of (x, y_pred), y @@ -188,21 +169,11 @@ def _select_pred_and_mask(model_out): model, metrics={ "nll": Loss(criterion, device=device), - "pixa": pixelwise_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "cacc": class_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "mca": mean_class_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "ciou": class_iou( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "mIoU": mean_iou( - n_classes, output_transform=_select_pred_and_mask, device=device - ), + "pixa": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask, device=device), + "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask, device=device), }, device=device, prepare_batch=_prepare_batch, @@ -211,9 +182,7 @@ def _select_pred_and_mask(model_out): # Set the validation run to start on the epoch completion of the training run trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) evaluator.add_event_handler( Events.EPOCH_COMPLETED, @@ -233,17 +202,11 @@ def _select_pred_and_mask(model_out): summary_writer, trainer, "epoch", - metrics_dict={ - "mIoU": "Validation/IoU", - "nll": "Validation/Loss", - "mca": "Validation/MCA", - }, + metrics_dict={"mIoU": "Validation/IoU", "nll": "Validation/Loss", "mca": "Validation/MCA",}, ), ) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) snapshot_duration = 1 @@ -256,14 +219,10 @@ def snapshot_function(): extract_metric_from("mIoU"), snapshot_function, ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) + evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") - trainer.run( - train_loader, max_epochs=config.TRAIN.END_EPOCH // config.TRAIN.BATCH_PER_EPOCH - ) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH // config.TRAIN.BATCH_PER_EPOCH) pbar.close() diff --git a/experiments/interpretation/penobscot/local/default.py b/experiments/interpretation/penobscot/local/default.py index 35a6dd47..0d63a530 100644 --- a/experiments/interpretation/penobscot/local/default.py +++ b/experiments/interpretation/penobscot/local/default.py @@ -11,9 +11,8 @@ _C = CN() -_C.OUTPUT_DIR = ( - "output" # The base directory for all output, such as logs and saved models -) +_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models + _C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR _C.GPUS = (0,) _C.WORKERS = 4 @@ -118,4 +117,3 @@ def update_config(cfg, options=None, config_file=None): with open(sys.argv[1], "w") as f: print(_C, file=f) - diff --git a/experiments/interpretation/penobscot/local/test.py b/experiments/interpretation/penobscot/local/test.py index 8cd55c4e..7b244296 100644 --- a/experiments/interpretation/penobscot/local/test.py +++ b/experiments/interpretation/penobscot/local/test.py @@ -59,15 +59,9 @@ def _prepare_batch(batch, device=None, non_blocking=False): def _padding_from(config): - padding_height = ( - config.TEST.AUGMENTATIONS.PAD.HEIGHT - config.TEST.AUGMENTATIONS.RESIZE.HEIGHT - ) - padding_width = ( - config.TEST.AUGMENTATIONS.PAD.WIDTH - config.TEST.AUGMENTATIONS.RESIZE.WIDTH - ) - assert ( - padding_height == padding_width - ), "The padding for the height and width need to be the same" + padding_height = config.TEST.AUGMENTATIONS.PAD.HEIGHT - config.TEST.AUGMENTATIONS.RESIZE.HEIGHT + padding_width = config.TEST.AUGMENTATIONS.PAD.WIDTH - config.TEST.AUGMENTATIONS.RESIZE.WIDTH + assert padding_height == padding_width, "The padding for the height and width need to be the same" return int(padding_height) @@ -80,29 +74,17 @@ def _scale_from(config): assert ( config.TEST.AUGMENTATIONS.PAD.WIDTH % config.TRAIN.PATCH_SIZE == 0 ), "The scaling between the patch width and resized height must be whole number" - assert ( - scale_height == scale_width - ), "The scaling for the height and width must be the same" + assert scale_height == scale_width, "The scaling for the height and width must be the same" return int(scale_height) _SEG_COLOURS = np.asarray( - [ - [241, 238, 246], - [208, 209, 230], - [166, 189, 219], - [116, 169, 207], - [54, 144, 192], - [5, 112, 176], - [3, 78, 123], - ] + [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123],] ) def _log_tensor_to_tensorboard(images_tensor, identifier, summary_writer, evaluator): - image_grid = torchvision.utils.make_grid( - images_tensor, normalize=False, scale_each=False, nrow=2 - ) + image_grid = torchvision.utils.make_grid(images_tensor, normalize=False, scale_each=False, nrow=2) summary_writer.add_image(identifier, image_grid, evaluator.state.epoch) @@ -142,11 +124,7 @@ def run(*options, cfg=None): # Setup Augmentations test_aug = Compose( [ - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=config.TRAIN.MAX, - ), + Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=config.TRAIN.MAX,), PadIfNeeded( min_height=config.TRAIN.PATCH_SIZE, min_width=config.TRAIN.PATCH_SIZE, @@ -156,9 +134,7 @@ def run(*options, cfg=None): value=0, ), Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, @@ -187,9 +163,7 @@ def run(*options, cfg=None): n_classes = test_set.n_classes test_loader = data.DataLoader( - test_set, - batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, + test_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, ) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -201,26 +175,14 @@ def run(*options, cfg=None): device = "cuda" model = model.to(device) # Send to GPU - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) # weights are inversely proportional to the frequency of the classes in # the training set - class_weights = torch.tensor( - config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False - ) + class_weights = torch.tensor(config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False) - criterion = torch.nn.CrossEntropyLoss( - weight=class_weights, ignore_index=mask_value, reduction="mean" - ) + criterion = torch.nn.CrossEntropyLoss(weight=class_weights, ignore_index=mask_value, reduction="mean") def _select_pred_and_mask(model_out_dict): return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) @@ -247,25 +209,13 @@ def _select_all(model_out_dict): model, _prepare_batch, metrics={ - "nll": Loss( - criterion, output_transform=_select_pred_and_mask, device=device - ), + "nll": Loss(criterion, output_transform=_select_pred_and_mask, device=device), "inIoU": inline_mean_iou, - "pixa": pixelwise_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "cacc": class_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "mca": mean_class_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "ciou": class_iou( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "mIoU": mean_iou( - n_classes, output_transform=_select_pred_and_mask, device=device - ), + "pixa": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask, device=device), + "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask, device=device), }, device=device, ) @@ -289,12 +239,7 @@ def _select_all(model_out_dict): summary_writer, evaluator, "epoch", - metrics_dict={ - "mIoU": "Test/IoU", - "nll": "Test/Loss", - "mca": "Test/MCA", - "inIoU": "Test/MeanInlineIoU", - }, + metrics_dict={"mIoU": "Test/IoU", "nll": "Test/Loss", "mca": "Test/MCA", "inIoU": "Test/MeanInlineIoU",}, ), ) @@ -305,28 +250,20 @@ def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() transform_func = compose( - np_to_tb, - decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), - _tensor_to_numpy, + np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy, ) transform_pred = compose(transform_func, _select_max) evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Test/Image", "image"), + Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Test/Image", "image"), ) evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Test/Mask", "mask", transform_func=transform_func - ), + Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Test/Mask", "mask", transform_func=transform_func), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Test/Pred", "y_pred", transform_func=transform_pred - ), + create_image_writer(summary_writer, "Test/Pred", "y_pred", transform_func=transform_pred), ) logger.info("Starting training") @@ -335,24 +272,16 @@ def _tensor_to_numpy(pred_tensor): # Log top N and bottom N inlines in terms of IoU to tensorboard inline_ious = inline_mean_iou.iou_per_inline() sorted_ious = sorted(inline_ious.items(), key=lambda x: x[1], reverse=True) - topk = ( - (inline_mean_iou.predictions[key], inline_mean_iou.masks[key]) - for key, iou in take(_TOP_K, sorted_ious) - ) + topk = ((inline_mean_iou.predictions[key], inline_mean_iou.masks[key]) for key, iou in take(_TOP_K, sorted_ious)) bottomk = ( - (inline_mean_iou.predictions[key], inline_mean_iou.masks[key]) - for key, iou in tail(_BOTTOM_K, sorted_ious) + (inline_mean_iou.predictions[key], inline_mean_iou.masks[key]) for key, iou in tail(_BOTTOM_K, sorted_ious) ) stack_and_decode = compose(transform_func, torch.stack) predictions, masks = unzip(chain(topk, bottomk)) predictions_tensor = stack_and_decode(list(predictions)) masks_tensor = stack_and_decode(list(masks)) - _log_tensor_to_tensorboard( - predictions_tensor, "Test/InlinePredictions", summary_writer, evaluator - ) - _log_tensor_to_tensorboard( - masks_tensor, "Test/InlineMasks", summary_writer, evaluator - ) + _log_tensor_to_tensorboard(predictions_tensor, "Test/InlinePredictions", summary_writer, evaluator) + _log_tensor_to_tensorboard(masks_tensor, "Test/InlineMasks", summary_writer, evaluator) summary_writer.close() diff --git a/experiments/interpretation/penobscot/local/train.py b/experiments/interpretation/penobscot/local/train.py index d62a74d3..9f7c57c5 100644 --- a/experiments/interpretation/penobscot/local/train.py +++ b/experiments/interpretation/penobscot/local/train.py @@ -56,15 +56,7 @@ mask_value = 255 _SEG_COLOURS = np.asarray( - [ - [241, 238, 246], - [208, 209, 230], - [166, 189, 219], - [116, 169, 207], - [54, 144, 192], - [5, 112, 176], - [3, 78, 123], - ] + [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123],] ) @@ -114,11 +106,7 @@ def run(*options, cfg=None): # Setup Augmentations basic_aug = Compose( [ - Normalize( - mean=(config.TRAIN.MEAN,), - std=(config.TRAIN.STD,), - max_pixel_value=config.TRAIN.MAX, - ), + Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=config.TRAIN.MAX,), PadIfNeeded( min_height=config.TRAIN.PATCH_SIZE, min_width=config.TRAIN.PATCH_SIZE, @@ -128,9 +116,7 @@ def run(*options, cfg=None): value=0, ), Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, @@ -174,17 +160,10 @@ def run(*options, cfg=None): n_classes = train_set.n_classes train_loader = data.DataLoader( - train_set, - batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - shuffle=True, + train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True, ) - val_loader = data.DataLoader( - val_set, - batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - ) + val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -197,49 +176,30 @@ def run(*options, cfg=None): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), - ) - summary_writer = create_summary_writer( - log_dir=path.join(output_dir, config.LOG_DIR) - ) + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration - ) + scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) # weights are inversely proportional to the frequency of the classes in # the training set - class_weights = torch.tensor( - config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False - ) + class_weights = torch.tensor(config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False) - criterion = torch.nn.CrossEntropyLoss( - weight=class_weights, ignore_index=mask_value, reduction="mean" - ) + criterion = torch.nn.CrossEntropyLoss(weight=class_weights, ignore_index=mask_value, reduction="mean") - trainer = create_supervised_trainer( - model, optimizer, criterion, _prepare_batch, device=device - ) + trainer = create_supervised_trainer(model, optimizer, criterion, _prepare_batch, device=device) trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) trainer.add_event_handler( - Events.EPOCH_STARTED, - tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), + Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), ) trainer.add_event_handler( - Events.ITERATION_COMPLETED, - tensorboard_handlers.log_training_output(summary_writer), + Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer), ) def _select_pred_and_mask(model_out_dict): @@ -249,14 +209,10 @@ def _select_pred_and_mask(model_out_dict): model, _prepare_batch, metrics={ - "pixacc": pixelwise_accuracy( - n_classes, output_transform=_select_pred_and_mask - ), + "pixacc": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask), "nll": Loss(criterion, output_transform=_select_pred_and_mask), "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask), - "mca": mean_class_accuracy( - n_classes, output_transform=_select_pred_and_mask - ), + "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask), "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask), "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask), }, @@ -300,28 +256,21 @@ def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() transform_func = compose( - np_to_tb, - decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), - _tensor_to_numpy, + np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy, ) transform_pred = compose(transform_func, _select_max) evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Image", "image"), + Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Validation/Image", "image"), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Mask", "mask", transform_func=transform_func - ), + create_image_writer(summary_writer, "Validation/Mask", "mask", transform_func=transform_func), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred - ), + create_image_writer(summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred), ) def snapshot_function(): @@ -333,9 +282,7 @@ def snapshot_function(): extract_metric_from("mIoU"), snapshot_function, ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model} - ) + evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/experiments/interpretation/voxel2pixel/batch.py b/experiments/interpretation/voxel2pixel/batch.py index 4947a08a..d53f79e8 100644 --- a/experiments/interpretation/voxel2pixel/batch.py +++ b/experiments/interpretation/voxel2pixel/batch.py @@ -81,9 +81,7 @@ def get_random_batch( # Insert in output arrays ret_labels[i] = class_ind - batch[i, 0, :, :, :] = np.reshape( - sample, (im_size[0], im_size[1], im_size[2]) - ) + batch[i, 0, :, :, :] = np.reshape(sample, (im_size[0], im_size[1], im_size[2])) # We seek to have a balanced batch with equally many samples from each class. n_for_class += 1 diff --git a/experiments/interpretation/voxel2pixel/data.py b/experiments/interpretation/voxel2pixel/data.py index c416ab84..bdcad76a 100644 --- a/experiments/interpretation/voxel2pixel/data.py +++ b/experiments/interpretation/voxel2pixel/data.py @@ -152,9 +152,7 @@ def read_labels(fname, data_info): label_coordinates[str(cls)] = np.array(np.zeros([3, 0])) inds_with_cls = label_img == cls cords_with_cls = coords[:, inds_with_cls.ravel()] - label_coordinates[str(cls)] = np.concatenate( - (label_coordinates[str(cls)], cords_with_cls), 1 - ) + label_coordinates[str(cls)] = np.concatenate((label_coordinates[str(cls)], cords_with_cls), 1) print( " ", str(np.sum(inds_with_cls)), "labels for class", str(cls), ) @@ -208,9 +206,7 @@ def parse_labels_in_image(img): for color in class_color_coding: # Find pixels with these labels inds = ( - (np.abs(r - color[0]) < tolerance) - & (np.abs(g - color[1]) < tolerance) - & (np.abs(b - color[2]) < tolerance) + (np.abs(r - color[0]) < tolerance) & (np.abs(g - color[1]) < tolerance) & (np.abs(b - color[2]) < tolerance) ) label_img[inds] = cls cls += 1 diff --git a/experiments/interpretation/voxel2pixel/tb_logger.py b/experiments/interpretation/voxel2pixel/tb_logger.py index 64af80f0..c6a894dc 100644 --- a/experiments/interpretation/voxel2pixel/tb_logger.py +++ b/experiments/interpretation/voxel2pixel/tb_logger.py @@ -10,9 +10,7 @@ try: import tensorflow as tf except: - print( - "Tensorflow could not be imported, therefore tensorboard cannot be used." - ) + print("Tensorflow could not be imported, therefore tensorboard cannot be used.") from io import BytesIO import matplotlib.pyplot as plt @@ -28,12 +26,7 @@ class TBLogger(object): def __init__(self, log_dir, folder_name=""): - self.log_dir = join( - log_dir, - folder_name - + " " - + datetime.datetime.now().strftime("%I%M%p, %B %d, %Y"), - ) + self.log_dir = join(log_dir, folder_name + " " + datetime.datetime.now().strftime("%I%M%p, %B %d, %Y"),) self.log_dir = self.log_dir.replace("//", "/") self.writer = tf.summary.FileWriter(self.log_dir) @@ -47,9 +40,7 @@ def log_scalar(self, tag, value, step=0): step: step """ - summary = tf.Summary( - value=[tf.Summary.Value(tag=tag, simple_value=value)] - ) + summary = tf.Summary(value=[tf.Summary.Value(tag=tag, simple_value=value)]) self.writer.add_summary(summary, step) # TODO: this should probably be a static method - take care of this when re-writing the whole thing @@ -104,16 +95,10 @@ def log_images(self, tag, images, step=0, dim=2, max_imgs=50, cm="jet"): # If 3D we make one list for each slice-type if dim == 3: - new_images_ts, new_images_il, new_images_cl = self.get_slices_from_3d( - images - ) - self.log_images( - tag + "_timeslice", new_images_ts, step, 2, max_imgs - ) + new_images_ts, new_images_il, new_images_cl = self.get_slices_from_3d(images) + self.log_images(tag + "_timeslice", new_images_ts, step, 2, max_imgs) self.log_images(tag + "_inline", new_images_il, step, 2, max_imgs) - self.log_images( - tag + "_crossline", new_images_cl, step, 2, max_imgs - ) + self.log_images(tag + "_crossline", new_images_cl, step, 2, max_imgs) return im_summaries = [] @@ -134,15 +119,9 @@ def log_images(self, tag, images, step=0, dim=2, max_imgs=50, cm="jet"): plt.imsave(s, img, format="png") # Create an Image object - img_sum = tf.Summary.Image( - encoded_image_string=s.getvalue(), - height=img.shape[0], - width=img.shape[1], - ) + img_sum = tf.Summary.Image(encoded_image_string=s.getvalue(), height=img.shape[0], width=img.shape[1],) # Create a Summary value - im_summaries.append( - tf.Summary.Value(tag="%s/%d" % (tag, nr), image=img_sum) - ) + im_summaries.append(tf.Summary.Value(tag="%s/%d" % (tag, nr), image=img_sum)) # if nr == max_imgs-1: # break @@ -172,27 +151,15 @@ def get_slices_from_3d(self, img): elif len(img.shape) == 4: for i in range(img.shape[0]): - new_images_ts.append( - np.squeeze(img[i, img.shape[1] / 2, :, :]) - ) - new_images_il.append( - np.squeeze(img[i, :, img.shape[2] / 2, :]) - ) - new_images_cl.append( - np.squeeze(img[i, :, :, img.shape[3] / 2]) - ) + new_images_ts.append(np.squeeze(img[i, img.shape[1] / 2, :, :])) + new_images_il.append(np.squeeze(img[i, :, img.shape[2] / 2, :])) + new_images_cl.append(np.squeeze(img[i, :, :, img.shape[3] / 2])) elif len(img.shape) == 5: for i in range(img.shape[0]): - new_images_ts.append( - np.squeeze(img[i, 0, img.shape[2] / 2, :, :]) - ) - new_images_il.append( - np.squeeze(img[i, 0, :, img.shape[3] / 2, :]) - ) - new_images_cl.append( - np.squeeze(img[i, 0, :, :, img.shape[4] / 2]) - ) + new_images_ts.append(np.squeeze(img[i, 0, img.shape[2] / 2, :, :])) + new_images_il.append(np.squeeze(img[i, 0, :, img.shape[3] / 2, :])) + new_images_cl.append(np.squeeze(img[i, 0, :, :, img.shape[4] / 2])) return new_images_ts, new_images_il, new_images_cl diff --git a/experiments/interpretation/voxel2pixel/test_parallel.py b/experiments/interpretation/voxel2pixel/test_parallel.py index becebec0..cd7bfbc2 100644 --- a/experiments/interpretation/voxel2pixel/test_parallel.py +++ b/experiments/interpretation/voxel2pixel/test_parallel.py @@ -77,9 +77,9 @@ def __getitem__(self, index): x, y, z = pixel # TODO: current bottleneck - can we slice out voxels any faster small_cube = self.data[ - x - self.window: x + self.window + 1, - y - self.window: y + self.window + 1, - z - self.window: z + self.window + 1, + x - self.window : x + self.window + 1, + y - self.window : y + self.window + 1, + z - self.window : z + self.window + 1, ] return small_cube[np.newaxis, :, :, :], pixel @@ -108,18 +108,10 @@ def main_worker(gpu, ngpus_per_node, args): # initialize the distributed process and join the group print( - "setting rank", - args.rank, - "world size", - args.world_size, - args.dist_backend, - args.dist_url, + "setting rank", args.rank, "world size", args.world_size, args.dist_backend, args.dist_url, ) dist.init_process_group( - backend=args.dist_backend, - init_method=args.dist_url, - world_size=args.world_size, - rank=args.rank, + backend=args.dist_backend, init_method=args.dist_url, world_size=args.world_size, rank=args.rank, ) # set default GPU device for this worker @@ -129,9 +121,7 @@ def main_worker(gpu, ngpus_per_node, args): # Load trained model (run train.py to create trained network = TextureNet(n_classes=N_CLASSES) - model_state_dict = torch.load( - join(args.data, "saved_model.pt"), map_location=local_device - ) + model_state_dict = torch.load(join(args.data, "saved_model.pt"), map_location=local_device) network.load_state_dict(model_state_dict) network.eval() network.cuda(args.gpu) @@ -165,7 +155,7 @@ def main_worker(gpu, ngpus_per_node, args): # reduce data size for debugging if args.debug: - data = data[0: 3 * window] + data = data[0 : 3 * window] # generate full list of coordinates # memory footprint of this isn't large yet, so not need to wrap as a generator @@ -180,14 +170,12 @@ def main_worker(gpu, ngpus_per_node, args): # we need to map the data manually to each rank - DistributedDataParallel doesn't do this at score time print("take a subset of coord_list by chunk") - coord_list = list( - np.array_split(np.array(coord_list), args.world_size)[args.rank] - ) + coord_list = list(np.array_split(np.array(coord_list), args.world_size)[args.rank]) coord_list = [tuple(x) for x in coord_list] # we only score first batch in debug mode if args.debug: - coord_list = coord_list[0: args.batch_size] + coord_list = coord_list[0 : args.batch_size] # prepare the data print("setup dataset") @@ -257,9 +245,7 @@ def main_worker(gpu, ngpus_per_node, args): parser = argparse.ArgumentParser(description="Seismic Distributed Scoring") -parser.add_argument( - "-d", "--data", default="/mnt/dutchf3", type=str, help="default dataset folder name" -) +parser.add_argument("-d", "--data", default="/mnt/dutchf3", type=str, help="default dataset folder name") parser.add_argument( "-s", "--slice", @@ -269,42 +255,21 @@ def main_worker(gpu, ngpus_per_node, args): help="slice type which we want to score on", ) parser.add_argument( - "-n", - "--slice-num", - default=339, - type=int, - help="slice number which we want to score", + "-n", "--slice-num", default=339, type=int, help="slice number which we want to score", ) parser.add_argument( - "-b", - "--batch-size", - default=2 ** 11, - type=int, - help="batch size which we use for scoring", + "-b", "--batch-size", default=2 ** 11, type=int, help="batch size which we use for scoring", ) parser.add_argument( - "-p", - "--n-proc-per-gpu", - default=1, - type=int, - help="number of multiple processes to run per each GPU", + "-p", "--n-proc-per-gpu", default=1, type=int, help="number of multiple processes to run per each GPU", ) parser.add_argument( - "--dist-url", - default="tcp://127.0.0.1:12345", - type=str, - help="url used to set up distributed training", + "--dist-url", default="tcp://127.0.0.1:12345", type=str, help="url used to set up distributed training", ) +parser.add_argument("--dist-backend", default="nccl", type=str, help="distributed backend") +parser.add_argument("--seed", default=0, type=int, help="default random number seed") parser.add_argument( - "--dist-backend", default="nccl", type=str, help="distributed backend" -) -parser.add_argument( - "--seed", default=0, type=int, help="default random number seed" -) -parser.add_argument( - "--debug", - action="store_true", - help="debug flag - if on we will only process one batch", + "--debug", action="store_true", help="debug flag - if on we will only process one batch", ) @@ -377,9 +342,7 @@ def main(): processes = [] for i in range(args.world_size): # error_queue = mp.SimpleQueue() - process = mp.Process( - target=main_worker, args=(i, ngpus_per_node, args), daemon=False - ) + process = mp.Process(target=main_worker, args=(i, ngpus_per_node, args), daemon=False) process.start() # error_queues.append(error_queue) processes.append(process) @@ -396,9 +359,7 @@ def main(): # Log to tensorboard - input slice logger = tb_logger.TBLogger("log", "Test") logger.log_images( - args.slice + "_" + str(args.slice_num), - get_slice(data, data_info, args.slice, args.slice_num), - cm="gray", + args.slice + "_" + str(args.slice_num), get_slice(data, data_info, args.slice, args.slice_num), cm="gray", ) x_coords = [] @@ -438,7 +399,7 @@ def worker(classified_cube, coord): delayed(worker)(classified_cube, coord) for coord in tqdm(pixels) ) - We do this: + We do this: """ # placeholder for results diff --git a/experiments/interpretation/voxel2pixel/texture_net.py b/experiments/interpretation/voxel2pixel/texture_net.py index 4935a647..f19fda96 100644 --- a/experiments/interpretation/voxel2pixel/texture_net.py +++ b/experiments/interpretation/voxel2pixel/texture_net.py @@ -16,9 +16,7 @@ def __init__(self, n_classes=2, n_filters=50): # Network definition # Parameters #in_channels, #out_channels, filter_size, stride (downsampling factor) self.net = nn.Sequential( - nn.Conv3d( - 1, n_filters, 5, 4, padding=2 - ), + nn.Conv3d(1, n_filters, 5, 4, padding=2), nn.BatchNorm3d(n_filters), # nn.Dropout3d() #Droput can be added like this ... nn.ReLU(), diff --git a/experiments/interpretation/voxel2pixel/train.py b/experiments/interpretation/voxel2pixel/train.py index 6cf73417..8d5d3ddc 100644 --- a/experiments/interpretation/voxel2pixel/train.py +++ b/experiments/interpretation/voxel2pixel/train.py @@ -46,22 +46,17 @@ # Load the data cube and labels data, data_info = read_segy(join(ROOT_PATH, INPUT_VOXEL)) -train_class_imgs, train_coordinates = read_labels( - join(ROOT_PATH, TRAIN_MASK), data_info -) +train_class_imgs, train_coordinates = read_labels(join(ROOT_PATH, TRAIN_MASK), data_info) val_class_imgs, _ = read_labels(join(ROOT_PATH, VAL_MASK), data_info) # Plot training/validation data with labels if LOG_TENSORBOARD: for class_img in train_class_imgs + val_class_imgs: logger.log_images( - class_img[1] + "_" + str(class_img[2]), - get_slice(data, data_info, class_img[1], class_img[2]), - cm="gray", + class_img[1] + "_" + str(class_img[2]), get_slice(data, data_info, class_img[1], class_img[2]), cm="gray", ) logger.log_images( - class_img[1] + "_" + str(class_img[2]) + "_true_class", - class_img[0], + class_img[1] + "_" + str(class_img[2]) + "_true_class", class_img[0], ) # Training loop @@ -112,9 +107,7 @@ print("Iteration:", i, "Training loss:", utils.var_to_np(loss)) if LOG_TENSORBOARD: logger.log_scalar("training_loss", utils.var_to_np(loss), i) - for k, v in utils.compute_accuracy( - torch.argmax(output, 1), labels - ).items(): + for k, v in utils.compute_accuracy(torch.argmax(output, 1), labels).items(): if LOG_TENSORBOARD: logger.log_scalar("training_" + k, v, i) print(" -", k, v, "%") @@ -130,34 +123,14 @@ slice_no = class_img[2] class_img = utils.interpret( - network.classify, - data, - data_info, - slice, - slice_no, - IM_SIZE, - 16, - return_full_size=True, - use_gpu=USE_GPU, - ) - logger.log_images( - slice + "_" + str(slice_no) + "_pred_class", class_img[0], step=i + network.classify, data, data_info, slice, slice_no, IM_SIZE, 16, return_full_size=True, use_gpu=USE_GPU, ) + logger.log_images(slice + "_" + str(slice_no) + "_pred_class", class_img[0], step=i) class_img = utils.interpret( - network, - data, - data_info, - slice, - slice_no, - IM_SIZE, - 16, - return_full_size=True, - use_gpu=USE_GPU, - ) - logger.log_images( - slice + "_" + str(slice_no) + "_pred_prob", class_img[0], i + network, data, data_info, slice, slice_no, IM_SIZE, 16, return_full_size=True, use_gpu=USE_GPU, ) + logger.log_images(slice + "_" + str(slice_no) + "_pred_prob", class_img[0], i) # Store trained network torch.save(network.state_dict(), join(ROOT_PATH, "saved_model.pt")) diff --git a/experiments/interpretation/voxel2pixel/utils.py b/experiments/interpretation/voxel2pixel/utils.py index e700ec5b..31db6b55 100644 --- a/experiments/interpretation/voxel2pixel/utils.py +++ b/experiments/interpretation/voxel2pixel/utils.py @@ -18,15 +18,7 @@ def interpret( - network, - data, - data_info, - slice, - slice_no, - im_size, - subsampl, - return_full_size=True, - use_gpu=True, + network, data, data_info, slice, slice_no, im_size, subsampl, return_full_size=True, use_gpu=True, ): """ Down-samples a slice from the classified image and upsamples to full resolution if needed. Basically @@ -53,59 +45,58 @@ def interpret( # Wrap np.linspace in compact function call ls = lambda N: np.linspace(0, N - 1, N, dtype="int") - #Size of cube + # Size of cube N0, N1, N2 = data.shape - #Coords for full cube + # Coords for full cube x0_range = ls(N0) x1_range = ls(N1) x2_range = ls(N2) - #Coords for subsampled cube + # Coords for subsampled cube pred_points = (x0_range[::subsampl], x1_range[::subsampl], x2_range[::subsampl]) - #Select slice - if slice == 'full': + # Select slice + if slice == "full": class_cube = data[::subsampl, ::subsampl, ::subsampl] * 0 - elif slice == 'inline': - slice_no = slice_no - data_info['inline_start'] + elif slice == "inline": + slice_no = slice_no - data_info["inline_start"] class_cube = data[::subsampl, 0:1, ::subsampl] * 0 x1_range = np.array([slice_no]) - pred_points = (pred_points[0],pred_points[2]) + pred_points = (pred_points[0], pred_points[2]) - elif slice == 'crossline': - slice_no = slice_no - data_info['crossline_start'] + elif slice == "crossline": + slice_no = slice_no - data_info["crossline_start"] class_cube = data[::subsampl, ::subsampl, 0:1,] * 0 x2_range = np.array([slice_no]) pred_points = (pred_points[0], pred_points[1]) - elif slice == 'timeslice': - slice_no = slice_no - data_info['timeslice_start'] + elif slice == "timeslice": + slice_no = slice_no - data_info["timeslice_start"] class_cube = data[0:1, ::subsampl, ::subsampl] * 0 x0_range = np.array([slice_no]) pred_points = (pred_points[1], pred_points[2]) + # Grid for small class slice/cube + n0, n1, n2 = class_cube.shape + x0_grid, x1_grid, x2_grid = np.meshgrid(ls(n0,), ls(n1), ls(n2), indexing="ij") - #Grid for small class slice/cube - n0,n1,n2 = class_cube.shape - x0_grid, x1_grid, x2_grid = np.meshgrid(ls(n0,), ls(n1), ls(n2), indexing='ij') + # Grid for full slice/cube + X0_grid, X1_grid, X2_grid = np.meshgrid(x0_range, x1_range, x2_range, indexing="ij") - #Grid for full slice/cube - X0_grid, X1_grid, X2_grid = np.meshgrid(x0_range, x1_range, x2_range, indexing='ij') - - #Indexes for large cube at small cube pixels + # Indexes for large cube at small cube pixels X0_grid_sub = X0_grid[::subsampl, ::subsampl, ::subsampl] X1_grid_sub = X1_grid[::subsampl, ::subsampl, ::subsampl] X2_grid_sub = X2_grid[::subsampl, ::subsampl, ::subsampl] - #Get half window size - w = im_size//2 + # Get half window size + w = im_size // 2 - #Loop through center pixels in output cube + # Loop through center pixels in output cube for i in range(X0_grid_sub.size): - #Get coordinates in small and large cube + # Get coordinates in small and large cube x0 = x0_grid.ravel()[i] x1 = x1_grid.ravel()[i] x2 = x2_grid.ravel()[i] @@ -114,74 +105,90 @@ def interpret( X1 = X1_grid_sub.ravel()[i] X2 = X2_grid_sub.ravel()[i] + # Only compute when a full 65x65x65 cube can be extracted around center pixel + if X0 > w and X1 > w and X2 > w and X0 < N0 - w + 1 and X1 < N1 - w + 1 and X2 < N2 - w + 1: - #Only compute when a full 65x65x65 cube can be extracted around center pixel - if X0>w and X1>w and X2>w and X0= 0: - accuracies["accuracy_class_" + str(cls)] = int( - np.mean(predicted_class[labels == cls] == cls) * 100 - ) - accuracies["average_class_accuracy"] = np.mean( - [acc for acc in accuracies.values()] - ) + accuracies["accuracy_class_" + str(cls)] = int(np.mean(predicted_class[labels == cls] == cls) * 100) + accuracies["average_class_accuracy"] = np.mean([acc for acc in accuracies.values()]) return accuracies diff --git a/imaging/deepseismic_imaging/cli/forward.py b/imaging/deepseismic_imaging/cli/forward.py index 3e0658c5..32c05d16 100644 --- a/imaging/deepseismic_imaging/cli/forward.py +++ b/imaging/deepseismic_imaging/cli/forward.py @@ -21,10 +21,7 @@ @click.option("-dt", default=2.0, type=float, help="Time increment (in ms)") @click.option("--n-pml", default=10, type=int, help="PML size (in grid points)") @click.option( - "--n-receivers", - default=11, - type=int, - help="Number of receivers per horizontal dimension", + "--n-receivers", default=11, type=int, help="Number of receivers per horizontal dimension", ) @click.option("--space-order", default=2, type=int, help="Space order") @click.option("--spacing", default=10.0, type=float, help="Spacing between grid points") @@ -73,27 +70,15 @@ def ricker(ctx, f0: float): space_order=ctx.obj["space_order"], n_pml=ctx.obj["n_pml"], ) - time_range = TimeAxis( - start=0.0, stop=ctx.obj["duration"], step=ctx.obj["dt"] - ) - source = RickerSource( - name="source", grid=model.grid, f0=f0, npoint=1, time_range=time_range, - ) + time_range = TimeAxis(start=0.0, stop=ctx.obj["duration"], step=ctx.obj["dt"]) + source = RickerSource(name="source", grid=model.grid, f0=f0, npoint=1, time_range=time_range,) source.coordinates.data[0, :] = np.array(model.domain_size) * 0.5 source.coordinates.data[0, -1] = 0.0 n_receivers = ctx.obj["n_receivers"] total_receivers = n_receivers ** (len(model.shape) - 1) - receivers = Receiver( - name="receivers", - grid=model.grid, - npoint=total_receivers, - time_range=time_range, - ) + receivers = Receiver(name="receivers", grid=model.grid, npoint=total_receivers, time_range=time_range,) receivers_coords = np.meshgrid( - *( - np.linspace(start=0, stop=s, num=n_receivers + 2)[1:-1] - for s in model.domain_size[:-1] - ) + *(np.linspace(start=0, stop=s, num=n_receivers + 2)[1:-1] for s in model.domain_size[:-1]) ) for d in range(len(receivers_coords)): receivers.coordinates.data[:, d] = receivers_coords[d].flatten() @@ -101,8 +86,6 @@ def ricker(ctx, f0: float): output_group = output_file.create_group(input_group_name) for input_dataset_name, vp in input_group.items(): model.vp = vp[()] - seismograms = model.solve( - source=source, receivers=receivers, time_range=time_range - ) + seismograms = model.solve(source=source, receivers=receivers, time_range=time_range) output_group.create_dataset(input_dataset_name, data=seismograms) bar.update(1) diff --git a/imaging/deepseismic_imaging/cli/velocity.py b/imaging/deepseismic_imaging/cli/velocity.py index c805d6af..f596289b 100644 --- a/imaging/deepseismic_imaging/cli/velocity.py +++ b/imaging/deepseismic_imaging/cli/velocity.py @@ -20,16 +20,10 @@ ) @click.option("-n", default=1, type=int, help="Number of simulations") @click.option( - "-nx", - default=100, - type=int, - help="Number of grid points along the first dimension", + "-nx", default=100, type=int, help="Number of grid points along the first dimension", ) @click.option( - "-ny", - default=100, - type=int, - help="Number of grid points along the second dimension", + "-ny", default=100, type=int, help="Number of grid points along the second dimension", ) @click.option("-nz", type=int, help="Number of grid points along the third dimension") @click.option("-s", "--seed", default=42, type=int, help="Random seed") @@ -42,9 +36,7 @@ def vp( if nz is not None: shape += (nz,) output_file = h5py.File(output, mode=("a" if append else "w")) - output_group = output_file.create_group( - str(max((int(x) for x in output_file.keys()), default=-1) + 1) - ) + output_group = output_file.create_group(str(max((int(x) for x in output_file.keys()), default=-1) + 1)) ctx.obj["n"] = n ctx.obj["output_file"] = output_file ctx.obj["output_group"] = output_group @@ -55,23 +47,14 @@ def vp( @vp.command() @click.option("--n-layers", default=8, type=int, help="Number of layers") @click.option( - "--initial-vp", - default=(1350.0, 1650.0), - type=(float, float), - help="Initial Vp (in km/s)", + "--initial-vp", default=(1350.0, 1650.0), type=(float, float), help="Initial Vp (in km/s)", ) @click.option( - "--vp-perturbation", - default=(-190.0, 570.0), - type=(float, float), - help="Per-layer Vp perturbation (in km/s)", + "--vp-perturbation", default=(-190.0, 570.0), type=(float, float), help="Per-layer Vp perturbation (in km/s)", ) @click.pass_context def rt( - ctx, - initial_vp: Tuple[float, float], - n_layers: int, - vp_perturbation: Tuple[float, float], + ctx, initial_vp: Tuple[float, float], n_layers: int, vp_perturbation: Tuple[float, float], ): """Röth-Tarantola model""" model = RoethTarantolaGenerator( diff --git a/imaging/deepseismic_imaging/forward/models.py b/imaging/deepseismic_imaging/forward/models.py index eaa0e56d..e4e850f7 100644 --- a/imaging/deepseismic_imaging/forward/models.py +++ b/imaging/deepseismic_imaging/forward/models.py @@ -41,20 +41,10 @@ def __init__( shape_pml = tuple(x + 2 * n_pml for x in shape) extent_pml = tuple(s * (d - 1) for s, d in zip(spacing, shape_pml)) origin_pml = tuple(dtype(o - s * n_pml) for o, s in zip(origin, spacing)) - self.grid = Grid( - shape=shape_pml, - extent=extent_pml, - origin=origin_pml, - dtype=dtype, - subdomains=subdomains, - ) + self.grid = Grid(shape=shape_pml, extent=extent_pml, origin=origin_pml, dtype=dtype, subdomains=subdomains,) self.n_pml = n_pml self.pml = Function(name="pml", grid=self.grid) - pml_data = np.pad( - np.zeros(shape, dtype=dtype), - [(n_pml,) * 2 for _ in range(self.pml.ndim)], - mode="edge", - ) + pml_data = np.pad(np.zeros(shape, dtype=dtype), [(n_pml,) * 2 for _ in range(self.pml.ndim)], mode="edge",) pml_coef = 1.5 * np.log(1000.0) / 40.0 for d in range(self.pml.ndim): for i in range(n_pml): @@ -65,9 +55,7 @@ def __init__( pml_data[tuple(idx)] += val / self.grid.spacing[d] idx[d] = slice(pml_data.shape[d] - i, pml_data.shape[d] - i + 1) pml_data[tuple(idx)] += val / self.grid.spacing[d] - pml_data = np.pad( - pml_data, [(i.left, i.right) for i in self.pml._size_halo], mode="edge", - ) + pml_data = np.pad(pml_data, [(i.left, i.right) for i in self.pml._size_halo], mode="edge",) self.pml.data_with_halo[:] = pml_data self.shape = shape @@ -120,12 +108,8 @@ def vp(self) -> Union[float, np.ndarray]: def vp(self, vp: Union[float, np.ndarray]) -> None: self._vp = vp if isinstance(vp, np.ndarray): - pad_widths = [ - (self.n_pml + i.left, self.n_pml + i.right) for i in self.m._size_halo - ] - self.m.data_with_halo[:] = np.pad( - 1.0 / self.vp ** 2.0, pad_widths, mode="edge" - ) + pad_widths = [(self.n_pml + i.left, self.n_pml + i.right) for i in self.m._size_halo] + self.m.data_with_halo[:] = np.pad(1.0 / self.vp ** 2.0, pad_widths, mode="edge") else: self.m.data = 1.0 / float(vp) ** 2.0 @@ -138,16 +122,12 @@ def solve( kernel: Optional[Kernel] = Kernel.OT2, ) -> np.ndarray: assert isinstance(kernel, Kernel) - u = TimeFunction( - name="u", grid=self.grid, time_order=2, space_order=space_order - ) + u = TimeFunction(name="u", grid=self.grid, time_order=2, space_order=space_order) H = u.laplace if kernel is Kernel.OT4: H += self.time_spacing ** 2 / 12 * u.laplace2(1 / self.m) eq = Eq(u.forward, solve(self.m * u.dt2 - H + self.pml * u.dt, u.forward)) - src_term = source.inject( - field=u.forward, expr=source * self.time_spacing ** 2 / self.m - ) + src_term = source.inject(field=u.forward, expr=source * self.time_spacing ** 2 / self.m) rec_term = receivers.interpolate(expr=u) op = Operator([eq] + src_term + rec_term, subs=self.spacing_map) op(time=time_range.num - 1, dt=time_range.step) diff --git a/imaging/deepseismic_imaging/forward/sources.py b/imaging/deepseismic_imaging/forward/sources.py index 576e3934..4d1e72ea 100644 --- a/imaging/deepseismic_imaging/forward/sources.py +++ b/imaging/deepseismic_imaging/forward/sources.py @@ -27,9 +27,7 @@ def __new__(cls, *args, **kwargs): npoint = kwargs.pop("npoint", None) coordinates = kwargs.pop("coordinates", kwargs.pop("coordinates_data", None)) if npoint is None: - assert ( - coordinates is not None - ), "Either `npoint` or `coordinates` must be provided" + assert coordinates is not None, "Either `npoint` or `coordinates` must be provided" npoint = coordinates.shape[0] obj = SparseTimeFunction.__new__( cls, @@ -63,9 +61,7 @@ def resample( rtol: Optional[float] = 1.0e-5, order: Optional[int] = 3, ): - assert (dt is not None) ^ ( - num is not None - ), "Exactly one of `dt` or `num` must be provided" + assert (dt is not None) ^ (num is not None), "Exactly one of `dt` or `num` must be provided" start = self._time_range.start stop = self._time_range.stop dt0 = self._time_range.step @@ -79,9 +75,7 @@ def resample( n_traces = self.data.shape[1] new_traces = np.zeros((new_time_range.num, n_traces), dtype=self.data.dtype) for j in range(n_traces): - tck = interpolate.splrep( - self._time_range.time_values, self.data[:, j], k=order - ) + tck = interpolate.splrep(self._time_range.time_values, self.data[:, j], k=order) new_traces[:, j] = interpolate.splev(new_time_range.time_values, tck) return PointSource( name=self.name, diff --git a/imaging/deepseismic_imaging/forward/subdomains.py b/imaging/deepseismic_imaging/forward/subdomains.py index fcabe59b..de209494 100644 --- a/imaging/deepseismic_imaging/forward/subdomains.py +++ b/imaging/deepseismic_imaging/forward/subdomains.py @@ -13,7 +13,5 @@ def __init__(self, n_pml: int): super().__init__() self.n_pml = n_pml - def define( - self, dimensions: Iterable[Dimension] - ) -> Dict[Dimension, Tuple[str, int, int]]: + def define(self, dimensions: Iterable[Dimension]) -> Dict[Dimension, Tuple[str, int, int]]: return {d: ("middle", self.n_pml, self.n_pml) for d in dimensions} diff --git a/imaging/deepseismic_imaging/velocity/generator.py b/imaging/deepseismic_imaging/velocity/generator.py index 5b28721c..fd4deba2 100644 --- a/imaging/deepseismic_imaging/velocity/generator.py +++ b/imaging/deepseismic_imaging/velocity/generator.py @@ -8,10 +8,7 @@ class Generator(object): def __init__( - self, - shape: Tuple[int, ...], - dtype: Optional[type] = np.float32, - seed: Optional[int] = None, + self, shape: Tuple[int, ...], dtype: Optional[type] = np.float32, seed: Optional[int] = None, ): self.shape = shape self.dtype = dtype diff --git a/imaging/deepseismic_imaging/velocity/roeth_tarantola.py b/imaging/deepseismic_imaging/velocity/roeth_tarantola.py index 89f860ae..78c04536 100644 --- a/imaging/deepseismic_imaging/velocity/roeth_tarantola.py +++ b/imaging/deepseismic_imaging/velocity/roeth_tarantola.py @@ -28,9 +28,7 @@ def __init__( def generate(self) -> np.ndarray: vp = np.zeros(self.shape, dtype=self.dtype) dim = self.depth_dim - layer_idx = np.round(np.linspace(0, self.shape[dim], self.n_layers + 1)).astype( - np.int - ) + layer_idx = np.round(np.linspace(0, self.shape[dim], self.n_layers + 1)).astype(np.int) vp_idx = [slice(0, x) for x in vp.shape] layer_vp = None for i in range(self.n_layers): diff --git a/imaging/setup.py b/imaging/setup.py index 5b67c692..4b198c96 100644 --- a/imaging/setup.py +++ b/imaging/setup.py @@ -24,18 +24,14 @@ "Topic :: Scientific/Engineering", "Topic :: Software Development", ], - dependency_links=[ - "https://github.com/opesci/devito/archive/v3.5.tar.gz#egg=devito-3.5" - ], + dependency_links=["https://github.com/opesci/devito/archive/v3.5.tar.gz#egg=devito-3.5"], description="DeepSeismic", install_requires=requirements, license="MIT", long_description=long_description, long_description_content_type="text/markdown", name="deepseismic_imaging", - packages=setuptools.find_packages( - include=["deepseismic_imaging", "deepseismic_imaging.*"] - ), + packages=setuptools.find_packages(include=["deepseismic_imaging", "deepseismic_imaging.*"]), platforms="any", python_requires=">= 3.6", scripts=["bin/ds"], diff --git a/interpretation/deepseismic_interpretation/data.py b/interpretation/deepseismic_interpretation/data.py index ae4f1a31..53ec7f9f 100644 --- a/interpretation/deepseismic_interpretation/data.py +++ b/interpretation/deepseismic_interpretation/data.py @@ -75,22 +75,14 @@ def _is_3D(numpy_array): @curry def _extract_patches(patch_size, stride, complete_patches_only, img_array, mask_array): height, width = img_array.shape[-2], img_array.shape[-1] - num_h_patches = _number_patches_in( - height, patch_size, stride, complete_patches_only=complete_patches_only - ) - num_w_patches = _number_patches_in( - width, patch_size, stride, complete_patches_only=complete_patches_only - ) + num_h_patches = _number_patches_in(height, patch_size, stride, complete_patches_only=complete_patches_only) + num_w_patches = _number_patches_in(width, patch_size, stride, complete_patches_only=complete_patches_only) height_iter = range(0, stride * (num_h_patches + 1), stride) width_iter = range(0, stride * (num_w_patches + 1), stride) patch_locations = list(itertools.product(height_iter, width_iter)) - image_patch_generator = _generate_patches_for( - img_array, patch_locations, patch_size - ) - mask_patch_generator = _generate_patches_for( - mask_array, patch_locations, patch_size - ) + image_patch_generator = _generate_patches_for(img_array, patch_locations, patch_size) + mask_patch_generator = _generate_patches_for(mask_array, patch_locations, patch_size) return image_patch_generator, mask_patch_generator, patch_locations @@ -105,17 +97,11 @@ def _generate_patches_for(numpy_array, patch_locations, patch_size): def _generate_patches_from_2D(numpy_array, patch_locations, patch_size): - return ( - numpy_array[h : h + patch_size, w : w + patch_size].copy() - for h, w in patch_locations - ) + return (numpy_array[h : h + patch_size, w : w + patch_size].copy() for h, w in patch_locations) def _generate_patches_from_3D(numpy_array, patch_locations, patch_size): - return ( - numpy_array[:, h : h + patch_size, w : w + patch_size].copy() - for h, w in patch_locations - ) + return (numpy_array[:, h : h + patch_size, w : w + patch_size].copy() for h, w in patch_locations) _STATS_FUNCS = {"mean": np.mean, "std": np.std, "max": np.max} @@ -143,9 +129,7 @@ def _split_train_val_test(partition, val_ratio, test_ratio): train_samples = total_samples - (val_samples + test_samples) train_list = partition[:train_samples] val_list = partition[train_samples : train_samples + val_samples] - test_list = partition[ - train_samples + val_samples : train_samples + val_samples + test_samples - ] + test_list = partition[train_samples + val_samples : train_samples + val_samples + test_samples] return train_list, val_list, test_list @@ -211,9 +195,7 @@ def __init__( verify_str_arg(split, "split", valid_modes, msg) # Set the patch and stride for the patch extractor - _extract_patches_from = _extract_patches( - patch_size, stride, self._complete_patches_only - ) + _extract_patches_from = _extract_patches(patch_size, stride, self._complete_patches_only) num_partitions = 5 indexes = self._data_array.shape[0] num_elements = math.ceil(indexes / num_partitions) @@ -221,12 +203,8 @@ def __init__( test_indexes_list = [] val_indexes_list = [] - for partition in partition_all( - num_elements, range(indexes) - ): # Partition files into N partitions - train_indexes, val_indexes, test_indexes = _split_train_val_test( - partition, val_ratio, test_ratio - ) + for partition in partition_all(num_elements, range(indexes)): # Partition files into N partitions + train_indexes, val_indexes, test_indexes = _split_train_val_test(partition, val_ratio, test_ratio) train_indexes_list.extend(train_indexes) test_indexes_list.extend(test_indexes) val_indexes_list.extend(val_indexes) @@ -243,22 +221,16 @@ def __init__( img_array = self._data_array[index] mask_array = self._slice_mask_array[index] self._ids.append(index) - image_generator, mask_generator, patch_locations = _extract_patches_from( - img_array, mask_array - ) + image_generator, mask_generator, patch_locations = _extract_patches_from(img_array, mask_array) self._patch_locations.extend(patch_locations) self._image_array.extend(image_generator) self._mask_array.extend(mask_generator) - assert len(self._image_array) == len( - self._patch_locations - ), "The shape is not the same" + assert len(self._image_array) == len(self._patch_locations), "The shape is not the same" - assert ( - len(self._patch_locations) % len(self._ids) == 0 - ), "Something is wrong with the patches" + assert len(self._patch_locations) % len(self._ids) == 0, "Something is wrong with the patches" self._patches_per_image = int(len(self._patch_locations) / len(self._ids)) @@ -311,9 +283,7 @@ def __getitem__(self, index): @property def statistics(self): flat_image_array = np.concatenate([i.flatten() for i in self._image_array]) - stats = { - stat: statfunc(flat_image_array) for stat, statfunc in _STATS_FUNCS.items() - } + stats = {stat: statfunc(flat_image_array) for stat, statfunc in _STATS_FUNCS.items()} return "Mean: {mean} Std: {std} Max: {max}".format(**stats) def __repr__(self): @@ -327,9 +297,7 @@ def __repr__(self): def _format_transform_repr(self, transform, head): lines = transform.__repr__().splitlines() - return ["{}{}".format(head, lines[0])] + [ - "{}{}".format(" " * len(head), line) for line in lines[1:] - ] + return ["{}{}".format(head, lines[0])] + ["{}{}".format(" " * len(head), line) for line in lines[1:]] def extra_repr(self): lines = [ diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index a227c178..36d69f21 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -6,6 +6,7 @@ import segyio from os import path import scipy + # bugfix for scipy imports import scipy.misc import numpy as np @@ -145,9 +146,7 @@ def read_labels(fname, data_info): label_coordinates[str(cls)] = np.array(np.zeros([3, 0])) inds_with_cls = label_img == cls cords_with_cls = coords[:, inds_with_cls.ravel()] - label_coordinates[str(cls)] = np.concatenate( - (label_coordinates[str(cls)], cords_with_cls), 1 - ) + label_coordinates[str(cls)] = np.concatenate((label_coordinates[str(cls)], cords_with_cls), 1) print(" ", str(np.sum(inds_with_cls)), "labels for class", str(cls)) if len(np.unique(label_img)) == 1: print(" ", 0, "labels", str(cls)) @@ -291,14 +290,7 @@ def transform(self, img, lbl): class VoxelLoader(data.Dataset): def __init__( - self, - root_path, - filename, - window_size=65, - split="train", - n_classes=2, - gen_coord_list=False, - len=None, + self, root_path, filename, window_size=65, split="train", n_classes=2, gen_coord_list=False, len=None, ): assert split == "train" or split == "val" @@ -365,10 +357,7 @@ def transform(self, img, lbl): class TrainSectionLoader(SectionLoader): def __init__(self, data_dir, split="train", is_transform=True, augmentations=None): super(TrainSectionLoader, self).__init__( - data_dir, - split=split, - is_transform=is_transform, - augmentations=augmentations, + data_dir, split=split, is_transform=is_transform, augmentations=augmentations, ) self.seismic = np.load(_train_data_for(self.data_dir)) @@ -384,10 +373,7 @@ def __init__(self, data_dir, split="train", is_transform=True, augmentations=Non class TrainSectionLoaderWithDepth(TrainSectionLoader): def __init__(self, data_dir, split="train", is_transform=True, augmentations=None): super(TrainSectionLoaderWithDepth, self).__init__( - data_dir, - split=split, - is_transform=is_transform, - augmentations=augmentations, + data_dir, split=split, is_transform=is_transform, augmentations=augmentations, ) self.seismic = add_section_depth_channels(self.seismic) # NCWH @@ -421,13 +407,7 @@ def __getitem__(self, index): class TrainVoxelWaldelandLoader(VoxelLoader): def __init__( - self, - root_path, - filename, - split="train", - window_size=65, - batch_size=None, - len=None, + self, root_path, filename, split="train", window_size=65, batch_size=None, len=None, ): super(TrainVoxelWaldelandLoader, self).__init__( root_path, filename, split=split, window_size=window_size, len=len @@ -469,10 +449,7 @@ def __getitem__(self, index): class TestSectionLoader(SectionLoader): def __init__(self, data_dir, split="test1", is_transform=True, augmentations=None): super(TestSectionLoader, self).__init__( - data_dir, - split=split, - is_transform=is_transform, - augmentations=augmentations, + data_dir, split=split, is_transform=is_transform, augmentations=augmentations, ) if "test1" in self.split: @@ -493,10 +470,7 @@ def __init__(self, data_dir, split="test1", is_transform=True, augmentations=Non class TestSectionLoaderWithDepth(TestSectionLoader): def __init__(self, data_dir, split="test1", is_transform=True, augmentations=None): super(TestSectionLoaderWithDepth, self).__init__( - data_dir, - split=split, - is_transform=is_transform, - augmentations=augmentations, + data_dir, split=split, is_transform=is_transform, augmentations=augmentations, ) self.seismic = add_section_depth_channels(self.seismic) # NCWH @@ -547,9 +521,7 @@ class PatchLoader(data.Dataset): Data loader for the patch-based deconvnet """ - def __init__( - self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None - ): + def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None): self.data_dir = data_dir self.is_transform = is_transform self.augmentations = augmentations @@ -562,9 +534,7 @@ def pad_volume(self, volume): """ Only used for train/val!! Not test. """ - return np.pad( - volume, pad_width=self.patch_size, mode="constant", constant_values=255 - ) + return np.pad(volume, pad_width=self.patch_size, mode="constant", constant_values=255) def __len__(self): return len(self.patches) @@ -581,19 +551,11 @@ def __getitem__(self, index): idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift if direction == "i": - im = self.seismic[ - idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size - ] - lbl = self.labels[ - idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size - ] + im = self.seismic[idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size] + lbl = self.labels[idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size] elif direction == "x": - im = self.seismic[ - idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size - ] - lbl = self.labels[ - idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size - ] + im = self.seismic[idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size] + lbl = self.labels[idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size] im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) @@ -614,15 +576,9 @@ def transform(self, img, lbl): class TestPatchLoader(PatchLoader): - def __init__( - self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None - ): + def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None): super(TestPatchLoader, self).__init__( - data_dir, - stride=stride, - patch_size=patch_size, - is_transform=is_transform, - augmentations=augmentations, + data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, ) ## Warning: this is not used or tested raise NotImplementedError("This class is not correctly implemented.") @@ -640,20 +596,10 @@ def __init__( class TrainPatchLoader(PatchLoader): def __init__( - self, - data_dir, - split="train", - stride=30, - patch_size=99, - is_transform=True, - augmentations=None, + self, data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, ): super(TrainPatchLoader, self).__init__( - data_dir, - stride=stride, - patch_size=patch_size, - is_transform=is_transform, - augmentations=augmentations, + data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, ) # self.seismic = self.pad_volume(np.load(seismic_path)) # self.labels = self.pad_volume(np.load(labels_path)) @@ -672,20 +618,10 @@ def __init__( class TrainPatchLoaderWithDepth(TrainPatchLoader): def __init__( - self, - data_dir, - split="train", - stride=30, - patch_size=99, - is_transform=True, - augmentations=None, + self, data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, ): super(TrainPatchLoaderWithDepth, self).__init__( - data_dir, - stride=stride, - patch_size=patch_size, - is_transform=is_transform, - augmentations=augmentations, + data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, ) def __getitem__(self, index): @@ -700,19 +636,11 @@ def __getitem__(self, index): idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift if direction == "i": - im = self.seismic[ - idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size - ] - lbl = self.labels[ - idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size - ] + im = self.seismic[idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size] + lbl = self.labels[idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size] elif direction == "x": - im = self.seismic[ - idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size - ] - lbl = self.labels[ - idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size - ] + im = self.seismic[idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size] + lbl = self.labels[idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size] im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) @@ -738,13 +666,7 @@ def _transform_HWC_to_CHW(numpy_array): class TrainPatchLoaderWithSectionDepth(TrainPatchLoader): def __init__( - self, - data_dir, - split="train", - stride=30, - patch_size=99, - is_transform=True, - augmentations=None, + self, data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, ): super(TrainPatchLoaderWithSectionDepth, self).__init__( data_dir, @@ -767,19 +689,11 @@ def __getitem__(self, index): shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift if direction == "i": - im = self.seismic[ - idx, :, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size - ] - lbl = self.labels[ - idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size - ] + im = self.seismic[idx, :, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size] + lbl = self.labels[idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size] elif direction == "x": - im = self.seismic[ - idx : idx + self.patch_size, :, xdx, ddx : ddx + self.patch_size - ] - lbl = self.labels[ - idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size - ] + im = self.seismic[idx : idx + self.patch_size, :, xdx, ddx : ddx + self.patch_size] + lbl = self.labels[idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size] im = np.swapaxes(im, 0, 1) # From WCH to CWH im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) @@ -882,14 +796,7 @@ def add_section_depth_channels(sections_numpy): def get_seismic_labels(): return np.asarray( - [ - [69, 117, 180], - [145, 191, 219], - [224, 243, 248], - [254, 224, 144], - [252, 141, 89], - [215, 48, 39], - ] + [[69, 117, 180], [145, 191, 219], [224, 243, 248], [254, 224, 144], [252, 141, 89], [215, 48, 39]] ) diff --git a/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py b/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py index 41048def..8ebc6790 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py +++ b/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py @@ -99,9 +99,7 @@ def parse_labels_in_image(img): for color in class_color_coding: # Find pixels with these labels inds = ( - (np.abs(r - color[0]) < tolerance) - & (np.abs(g - color[1]) < tolerance) - & (np.abs(b - color[2]) < tolerance) + (np.abs(r - color[0]) < tolerance) & (np.abs(g - color[1]) < tolerance) & (np.abs(b - color[2]) < tolerance) ) label_img[inds] = cls cls += 1 diff --git a/interpretation/deepseismic_interpretation/models/texture_net.py b/interpretation/deepseismic_interpretation/models/texture_net.py index 418b7ae4..da5371d5 100644 --- a/interpretation/deepseismic_interpretation/models/texture_net.py +++ b/interpretation/deepseismic_interpretation/models/texture_net.py @@ -14,9 +14,7 @@ def __init__(self, n_classes=2): # Network definition # Parameters #in_channels, #out_channels, filter_size, stride (downsampling factor) self.net = nn.Sequential( - nn.Conv3d( - 1, 50, 5, 4, padding=2 - ), + nn.Conv3d(1, 50, 5, 4, padding=2), nn.BatchNorm3d(50), # nn.Dropout3d() #Droput can be added like this ... nn.ReLU(), @@ -156,6 +154,5 @@ def getFeatures(self, x, layer_no): def get_seg_model(cfg, **kwargs): - model = TextureNet(n_classes = cfg.DATASET.NUM_CLASSES) + model = TextureNet(n_classes=cfg.DATASET.NUM_CLASSES) return model - diff --git a/interpretation/deepseismic_interpretation/penobscot/data.py b/interpretation/deepseismic_interpretation/penobscot/data.py index ca25c0a3..7afebf57 100644 --- a/interpretation/deepseismic_interpretation/penobscot/data.py +++ b/interpretation/deepseismic_interpretation/penobscot/data.py @@ -30,9 +30,7 @@ def _pad_right_and_bottom(pad_size, numpy_array, pad_value=255): assert ( len(numpy_array.shape) == 2 ), f"_pad_right_and_bottom only accepts 2D arrays. Input is {len(numpy_array.shape)}D" - return np.pad( - numpy_array, pad_width=[(0, pad_size), (0, pad_size)], constant_values=pad_value - ) + return np.pad(numpy_array, pad_width=[(0, pad_size), (0, pad_size)], constant_values=pad_value) def _get_classes_and_counts(mask_list): @@ -55,7 +53,7 @@ def _combine(mask_array): def _combine_classes(mask_array_list): """Combine classes - Segmentation implementations using this dataset seem to combine + Segmentation implementations using this dataset seem to combine classes 2 and 3 so we are doing the same here and then relabeling the rest Args: @@ -72,9 +70,7 @@ def _replicate_channels(image_array, n_channels): def _extract_filename(filepath): - return os.path.splitext(os.path.split(filepath)[-1].strip())[ - 0 - ] # extract filename without extension + return os.path.splitext(os.path.split(filepath)[-1].strip())[0] # extract filename without extension def _generate_images_and_masks(images_iter, mask_dir): @@ -84,9 +80,7 @@ def _generate_images_and_masks(images_iter, mask_dir): if os.path.exists(mask_file): yield image_file, mask_file else: - raise FileNotFoundError( - f"Could not find mask {mask_file} corresponding to {image_file}" - ) + raise FileNotFoundError(f"Could not find mask {mask_file} corresponding to {image_file}") def _number_patches_in(height_or_width, patch_size, stride, complete_patches_only=True): @@ -108,22 +102,14 @@ def _is_3D(numpy_array): @curry def _extract_patches(patch_size, stride, complete_patches_only, img_array, mask_array): height, width = img_array.shape[-2], img_array.shape[-1] - num_h_patches = _number_patches_in( - height, patch_size, stride, complete_patches_only=complete_patches_only - ) - num_w_patches = _number_patches_in( - width, patch_size, stride, complete_patches_only=complete_patches_only - ) + num_h_patches = _number_patches_in(height, patch_size, stride, complete_patches_only=complete_patches_only) + num_w_patches = _number_patches_in(width, patch_size, stride, complete_patches_only=complete_patches_only) height_iter = range(0, stride * (num_h_patches + 1), stride) width_iter = range(0, stride * (num_w_patches + 1), stride) patch_locations = list(itertools.product(height_iter, width_iter)) - image_patch_generator = _generate_patches_for( - img_array, patch_locations, patch_size - ) - mask_patch_generator = _generate_patches_for( - mask_array, patch_locations, patch_size - ) + image_patch_generator = _generate_patches_for(img_array, patch_locations, patch_size) + mask_patch_generator = _generate_patches_for(mask_array, patch_locations, patch_size) return image_patch_generator, mask_patch_generator, patch_locations @@ -138,17 +124,11 @@ def _generate_patches_for(numpy_array, patch_locations, patch_size): def _generate_patches_from_2D(numpy_array, patch_locations, patch_size): - return ( - numpy_array[h : h + patch_size, w : w + patch_size].copy() - for h, w in patch_locations - ) + return (numpy_array[h : h + patch_size, w : w + patch_size].copy() for h, w in patch_locations) def _generate_patches_from_3D(numpy_array, patch_locations, patch_size): - return ( - numpy_array[:, h : h + patch_size, w : w + patch_size].copy() - for h, w in patch_locations - ) + return (numpy_array[:, h : h + patch_size, w : w + patch_size].copy() for h, w in patch_locations) @curry @@ -264,33 +244,23 @@ def __init__( ) # Set the patch and stride for the patch extractor - _extract_patches_from = _extract_patches( - patch_size, stride, self._complete_patches_only - ) + _extract_patches_from = _extract_patches(patch_size, stride, self._complete_patches_only) # Extract patches - for image_path, mask_path in _generate_images_and_masks( - images_iter, self._mask_dir - ): + for image_path, mask_path in _generate_images_and_masks(images_iter, self._mask_dir): img_array = self._open_image(image_path) mask_array = self._open_mask(mask_path) self._file_ids.append(_extract_filename(image_path)) - image_generator, mask_generator, patch_locations = _extract_patches_from( - img_array, mask_array - ) + image_generator, mask_generator, patch_locations = _extract_patches_from(img_array, mask_array) self._patch_locations.extend(patch_locations) self._image_array.extend(image_generator) self._mask_array.extend(mask_generator) - assert len(self._image_array) == len( - self._patch_locations - ), "The shape is not the same" + assert len(self._image_array) == len(self._patch_locations), "The shape is not the same" - assert ( - len(self._patch_locations) % len(self._file_ids) == 0 - ), "Something is wrong with the patches" + assert len(self._patch_locations) % len(self._file_ids) == 0, "Something is wrong with the patches" self._patches_per_image = int(len(self._patch_locations) / len(self._file_ids)) @@ -352,9 +322,7 @@ def __getitem__(self, index): @property def statistics(self): flat_image_array = np.concatenate([i.flatten() for i in self._image_array]) - stats = { - stat: statfunc(flat_image_array) for stat, statfunc in _STATS_FUNCS.items() - } + stats = {stat: statfunc(flat_image_array) for stat, statfunc in _STATS_FUNCS.items()} return "Mean: {mean} Std: {std} Max: {max}".format(**stats) def extra_repr(self): @@ -422,16 +390,20 @@ def __init__( patch_size (int): the size of the patch in pixels stride (int): the stride applied when extracting patches split (str, optional): what split to load, (train, val, test). Defaults to `train` - transforms (albumentations.augmentations.transforms, optional): albumentation transforms to apply to patches. Defaults to None + transforms (albumentations.augmentations.transforms, optional): albumentation transforms + to apply to patches. + Defaults to None exclude_files (list[str], optional): list of files to exclude. Defaults to None max_inlines (int, optional): maximum number of inlines to load. Defaults to None n_channels (int, optional): number of channels that the output should contain. Defaults to 3 - complete_patches_only (bool, optional): whether to load incomplete patches that are padded to patch_size. Defaults to True + complete_patches_only (bool, optional): whether to load incomplete patches + that are padded to patch_size. Defaults to True """ - assert ( - n_channels == 3 - ), f"For the Section Depth based dataset the number of channels can only be 3. Currently n_channels={n_channels}" + assert n_channels == 3, ( + f"For the Section Depth based dataset the number of channels can only be 3." + f"Currently n_channels={n_channels}" + ) super(PenobscotInlinePatchSectionDepthDataset, self).__init__( root, patch_size, @@ -478,11 +450,14 @@ def __init__( patch_size (int): the size of the patch in pixels stride (int): the stride applied when extracting patches split (str, optional): what split to load, (train, val, test). Defaults to `train` - transforms (albumentations.augmentations.transforms, optional): albumentation transforms to apply to patches. Defaults to None + transforms (albumentations.augmentations.transforms, optional): albumentation transforms + to apply to patches. + Defaults to None exclude_files (list[str], optional): list of files to exclude. Defaults to None max_inlines (int, optional): maximum number of inlines to load. Defaults to None n_channels (int, optional): number of channels that the output should contain. Defaults to 3 - complete_patches_only (bool, optional): whether to load incomplete patches that are padded to patch_size. Defaults to True + complete_patches_only (bool, optional): whether to load incomplete patches that are + padded to patch_size. Defaults to True """ assert ( n_channels == 3 diff --git a/interpretation/deepseismic_interpretation/penobscot/metrics.py b/interpretation/deepseismic_interpretation/penobscot/metrics.py index cb6e9ca9..846faacc 100644 --- a/interpretation/deepseismic_interpretation/penobscot/metrics.py +++ b/interpretation/deepseismic_interpretation/penobscot/metrics.py @@ -22,9 +22,9 @@ def _torch_hist(label_true, label_pred, n_class): assert len(label_true.shape) == 1, "Labels need to be 1D" assert len(label_pred.shape) == 1, "Predictions need to be 1D" mask = (label_true >= 0) & (label_true < n_class) - hist = torch.bincount( - n_class * label_true[mask] + label_pred[mask], minlength=n_class ** 2 - ).reshape(n_class, n_class) + hist = torch.bincount(n_class * label_true[mask] + label_pred[mask], minlength=n_class ** 2).reshape( + n_class, n_class + ) return hist @@ -83,16 +83,12 @@ def __init__( def reset(self): self._pred_dict = defaultdict( lambda: _default_tensor( - self._image_height * self._scale, - self._image_width * self._scale, - pad_value=self._pad_value, + self._image_height * self._scale, self._image_width * self._scale, pad_value=self._pad_value, ) ) self._mask_dict = defaultdict( lambda: _default_tensor( - self._image_height * self._scale, - self._image_width * self._scale, - pad_value=self._pad_value, + self._image_height * self._scale, self._image_width * self._scale, pad_value=self._pad_value, ) ) @@ -120,13 +116,11 @@ def update(self, output): width = (x_end + 1) - x_start self._pred_dict[id][ - patch_loc[0] * 2 : patch_loc[0] * 2 + height, - patch_loc[1] * 2 : patch_loc[1] * 2 + width, + patch_loc[0] * 2 : patch_loc[0] * 2 + height, patch_loc[1] * 2 : patch_loc[1] * 2 + width, ] = pred[y_start : y_end + 1, x_start : x_end + 1] self._mask_dict[id][ - patch_loc[0] * 2 : patch_loc[0] * 2 + height, - patch_loc[1] * 2 : patch_loc[1] * 2 + width, + patch_loc[0] * 2 : patch_loc[0] * 2 + height, patch_loc[1] * 2 : patch_loc[1] * 2 + width, ] = mask[y_start : y_end + 1, x_start : x_end + 1] def iou_per_inline(self): diff --git a/interpretation/setup.py b/interpretation/setup.py index 132d8044..38be7a20 100644 --- a/interpretation/setup.py +++ b/interpretation/setup.py @@ -30,9 +30,7 @@ long_description=long_description, long_description_content_type="text/markdown", name="deepseismic_interpretation", - packages=setuptools.find_packages( - include=["deepseismic_interpretation", "deepseismic_interpretation.*"] - ), + packages=setuptools.find_packages(include=["deepseismic_interpretation", "deepseismic_interpretation.*"]), platforms="any", python_requires=">=3.6", setup_requires=["pytest-runner"], diff --git a/pyproject.toml b/pyproject.toml index fed6c975..446077b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.black] -line-length = 88 +line-length = 120 include = '\.pyi?$' exclude = ''' /( diff --git a/scripts/prepare_dutchf3.py b/scripts/prepare_dutchf3.py index 4ac7018a..40d9f4e6 100644 --- a/scripts/prepare_dutchf3.py +++ b/scripts/prepare_dutchf3.py @@ -39,9 +39,7 @@ def _write_split_files(splits_path, train_list, test_list, loader_type): def _get_aline_range(aline, per_val): # Inline sections test_aline = math.floor(aline * per_val / 2) - test_aline_range = itertools.chain( - range(0, test_aline), range(aline - test_aline, aline) - ) + test_aline_range = itertools.chain(range(0, test_aline), range(aline - test_aline, aline)) train_aline_range = range(test_aline, aline - test_aline) return train_aline_range, test_aline_range @@ -133,12 +131,8 @@ def _i_extract_patches(iline_range, horz_locations, vert_locations): for j, k in locations: yield "i_" + str(i) + "_" + str(j) + "_" + str(k) - test_i_list = list( - _i_extract_patches(test_iline_range, horz_locations, vert_locations) - ) - train_i_list = list( - _i_extract_patches(train_iline_range, horz_locations, vert_locations) - ) + test_i_list = list(_i_extract_patches(test_iline_range, horz_locations, vert_locations)) + train_i_list = list(_i_extract_patches(train_iline_range, horz_locations, vert_locations)) # Process crosslines horz_locations = range(0, iline - patch, stride) @@ -150,12 +144,8 @@ def _x_extract_patches(xline_range, horz_locations, vert_locations): for i, k in locations: yield "x_" + str(i) + "_" + str(j) + "_" + str(k) - test_x_list = list( - _x_extract_patches(test_xline_range, horz_locations, vert_locations) - ) - train_x_list = list( - _x_extract_patches(train_xline_range, horz_locations, vert_locations) - ) + test_x_list = list(_x_extract_patches(test_xline_range, horz_locations, vert_locations)) + train_x_list = list(_x_extract_patches(train_xline_range, horz_locations, vert_locations)) train_list = train_x_list + train_i_list test_list = test_x_list + test_i_list @@ -178,9 +168,7 @@ def run_split_func(loader_type, *args, **kwargs): split_func(*args, **kwargs) -def split_alaudah_et_al_19( - data_dir, stride, fraction_validation=0.2, loader_type="patch", log_config=None -): +def split_alaudah_et_al_19(data_dir, stride, fraction_validation=0.2, loader_type="patch", log_config=None): """Generate train and validation files (with overlap) for Netherlands F3 dataset. The original split method from https://github.com/olivesgatech/facies_classification_benchmark DON'T USE, SEE NOTES BELOW @@ -235,9 +223,7 @@ def split_alaudah_et_al_19( # for every inline: # images are references by top-left corner: locations = [[j, k] for j in horz_locations for k in vert_locations] - patches_list = [ - "i_" + str(i) + "_" + str(j) + "_" + str(k) for j, k in locations - ] + patches_list = ["i_" + str(i) + "_" + str(j) + "_" + str(k) for j, k in locations] i_list.append(patches_list) # flatten the list @@ -250,9 +236,7 @@ def split_alaudah_et_al_19( # for every xline: # images are references by top-left corner: locations = [[i, k] for i in horz_locations for k in vert_locations] - patches_list = [ - "x_" + str(i) + "_" + str(j) + "_" + str(k) for i, k in locations - ] + patches_list = ["x_" + str(i) + "_" + str(j) + "_" + str(k) for i, k in locations] x_list.append(patches_list) # flatten the list @@ -261,9 +245,7 @@ def split_alaudah_et_al_19( list_train_val = i_list + x_list # create train and test splits: - train_list, test_list = train_test_split( - list_train_val, test_size=fraction_validation, shuffle=True - ) + train_list, test_list = train_test_split(list_train_val, test_size=fraction_validation, shuffle=True) # write to files to disk: splits_path = _get_splits_path(data_dir) @@ -294,9 +276,7 @@ def patch(self, data_dir, stride, patch, per_val=0.2, log_config=None): Defaults to 0.2. log_config (str): path to log configurations """ - return split_patch_train_val( - data_dir, stride, patch, per_val=per_val, log_config=log_config - ) + return split_patch_train_val(data_dir, stride, patch, per_val=per_val, log_config=log_config) if __name__ == "__main__": @@ -307,8 +287,5 @@ def patch(self, data_dir, stride, patch, per_val=0.2, log_config=None): """ fire.Fire( - { - "split_train_val": SplitTrainValCLI, - "split_alaudah_et_al_19": split_alaudah_et_al_19, - } + {"split_train_val": SplitTrainValCLI, "split_alaudah_et_al_19": split_alaudah_et_al_19,} ) diff --git a/scripts/prepare_penobscot.py b/scripts/prepare_penobscot.py index c5557f6a..754993be 100644 --- a/scripts/prepare_penobscot.py +++ b/scripts/prepare_penobscot.py @@ -47,9 +47,7 @@ def _split_train_val_test(partition, val_ratio, test_ratio): train_samples = total_samples - (val_samples + test_samples) train_list = partition[:train_samples] val_list = partition[train_samples : train_samples + val_samples] - test_list = partition[ - train_samples + val_samples : train_samples + val_samples + test_samples - ] + test_list = partition[train_samples + val_samples : train_samples + val_samples + test_samples] return train_list, val_list, test_list @@ -66,26 +64,18 @@ def split_inline(data_dir, val_ratio, test_ratio, overwrite=False, exclude_files num_partitions = 5 image_dir = os.path.join(data_dir, "inlines") dir_paths = (os.path.join(image_dir, ddir) for ddir in ("train", "val", "test")) - locations_list = [ - _create_directory(d, overwrite=overwrite) for d in dir_paths - ] # train, val, test + locations_list = [_create_directory(d, overwrite=overwrite) for d in dir_paths] # train, val, test images_iter = glob.iglob(os.path.join(image_dir, "*.tiff")) if exclude_files is not None: - images_list = list( - itertools.filterfalse(lambda x: x in exclude_files, images_iter) - ) + images_list = list(itertools.filterfalse(lambda x: x in exclude_files, images_iter)) else: images_list = list(images_iter) num_elements = math.ceil(len(images_list) / num_partitions) - for partition in partition_all( - num_elements, images_list - ): # Partition files into N partitions - for files_list, dest_dir in zip( - _split_train_val_test(partition, val_ratio, test_ratio), locations_list - ): + for partition in partition_all(num_elements, images_list): # Partition files into N partitions + for files_list, dest_dir in zip(_split_train_val_test(partition, val_ratio, test_ratio), locations_list): _copy_files(files_list, dest_dir) From bc06c75409942e20a968e70262d28d0fe273616b Mon Sep 17 00:00:00 2001 From: Mat Date: Mon, 11 Nov 2019 21:07:49 +0000 Subject: [PATCH 079/207] Adds jupytext to pre-commit (#12) * Add jupytext --- .pre-commit-config.yaml | 7 +++++++ environment/anaconda/local/environment.yml | 1 + 2 files changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9002a3b4..43a6b718 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,3 +7,10 @@ repos: rev: v1.2.3 hooks: - id: flake8 +- repo: local + hooks: + - id: jupytext + name: jupytext + entry: jupytext --sync --pipe black --check flake8 --pre-commit + pass_filenames: false + language: python diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index d3351017..c550ea91 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -31,4 +31,5 @@ dependencies: - black - pylint - scipy==1.1.0 + - jupytext From a3202afae9448c76d86a5decffd76e7509fe40ee Mon Sep 17 00:00:00 2001 From: Mat Date: Mon, 11 Nov 2019 21:09:34 +0000 Subject: [PATCH 080/207] Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 --- environment/anaconda/local/environment.yml | 1 + ..._block_training_and_evaluation_local.ipynb | 7 - .../notebooks/HRNet_demo_notebook.ipynb | 598 ++++++++++++++++++ .../notebooks/configs/hrnet.yaml | 57 +- 4 files changed, 632 insertions(+), 31 deletions(-) create mode 100644 examples/interpretation/notebooks/HRNet_demo_notebook.ipynb diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index c550ea91..3e7ecfa6 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -12,6 +12,7 @@ dependencies: - pandas - opencv - scikit-learn + - tensorflow==2.0 - tqdm - itkwidgets - pip: diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 0c248e0c..03d061ad 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -708,13 +708,6 @@ "_ = f_axes[0].set_title('Actual')\n", "_ = f_axes[1].set_title('Predicted') " ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb new file mode 100644 index 00000000..2b250cfd --- /dev/null +++ b/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb @@ -0,0 +1,598 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# HRNet training and validation on numpy dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this notebook, we demonstrate how to train an HRNet model for facies prediction a numpy dataset. The data expected in this notebook needs to be in the form of two 3D arrays. One array will contain the seismic information the other the mask. The network will be trained to take a 2D patch of data from the seismic block and learn to predict the 2D mask patch associated with it. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Environment setup\n", + "\n", + "To set up the conda environment, please follow the instructions in the top-level [README.md](../../../README.md) file.\n", + "\n", + "__Note__: To register the conda environment in Jupyter, run:\n", + "`python -m ipykernel install --user --name envname`\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Library imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "import logging.config\n", + "from os import path\n", + "\n", + "import cv2\n", + "import numpy as np\n", + "import yacs.config\n", + "import torch\n", + "from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize\n", + "from cv_lib.utils import load_log_configuration\n", + "from cv_lib.event_handlers import (\n", + " SnapshotHandler,\n", + " logging_handlers,\n", + " tensorboard_handlers,\n", + ")\n", + "from cv_lib.event_handlers.logging_handlers import Evaluator\n", + "from cv_lib.event_handlers.tensorboard_handlers import (\n", + " create_image_writer,\n", + " create_summary_writer,\n", + ")\n", + "from cv_lib.segmentation import models, extract_metric_from\n", + "from cv_lib.segmentation.metrics import (\n", + " pixelwise_accuracy,\n", + " class_accuracy,\n", + " mean_class_accuracy,\n", + " class_iou,\n", + " mean_iou,\n", + ")\n", + "from cv_lib.segmentation.dutchf3.utils import (\n", + " current_datetime,\n", + " generate_path,\n", + " np_to_tb,\n", + ")\n", + "from cv_lib.segmentation.penobscot.engine import (\n", + " create_supervised_evaluator,\n", + " create_supervised_trainer,\n", + ")\n", + "from deepseismic_interpretation.data import InlinePatchDataset\n", + "from deepseismic_interpretation.dutchf3.data import decode_segmap\n", + "from ignite.contrib.handlers import CosineAnnealingScheduler\n", + "from ignite.engine import Events\n", + "from ignite.metrics import Loss\n", + "from ignite.utils import convert_tensor\n", + "from toolz import compose\n", + "from torch.utils import data\n", + "from itkwidgets import view\n", + "from utilities import plot_aline\n", + "\n", + "mask_value = 255\n", + "_SEG_COLOURS = np.asarray(\n", + " [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123],]\n", + ")\n", + "\n", + "# experiment configuration file\n", + "CONFIG_FILE = \"./configs/hrnet.yaml\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def _prepare_batch(batch, device=None, non_blocking=False):\n", + " x, y, ids, patch_locations = batch\n", + " return (\n", + " convert_tensor(x, device=device, non_blocking=non_blocking),\n", + " convert_tensor(y, device=device, non_blocking=non_blocking),\n", + " ids,\n", + " patch_locations,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Experiment configuration file\n", + "We use configuration files to specify experiment configuration, such as hyperparameters used in training and evaluation, as well as other experiment settings. We provide several configuration files for this notebook, under `./configs`, mainly differing in the DNN architecture used for defining the model.\n", + "\n", + "Modify the `CONFIG_FILE` variable above if you would like to run the experiment using a different configuration file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with open(CONFIG_FILE, \"rt\") as f_read:\n", + " config = yacs.config.load_cfg(f_read)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ny = 1001\n", + "nx = 876\n", + "nz = 751\n", + "\n", + "img_name = \"rtm3d.bin\"\n", + "seam_image_name = path.join(config.DATASET.ROOT, img_name)\n", + "# load RTM image, note it is type np.float32\n", + "img = np.fromfile(seam_image_name, dtype=np.float32).reshape(ny, nx, nz) / 71892250000.0\n", + "\n", + "salt_name = \"salt_mask.bin\"\n", + "salt_mask_name = path.join(config.DATASET.ROOT, salt_name)\n", + "# load salt mask, note it is type np.int16\n", + "mask = np.fromfile(salt_mask_name, dtype=np.int16).reshape(ny, nx, nz)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize the dataset. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "view(mask, slicing_planes=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's view slices of the data along inline and crossline directions. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "idx = 100\n", + "x_in = img[idx, :, :].swapaxes(0, 1)\n", + "x_inl = mask[idx, :, :].swapaxes(0, 1)\n", + "\n", + "plot_aline(x_in, x_inl, xlabel=\"inline\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x_cr = img[:, idx, :].swapaxes(0, 1)\n", + "x_crl = mask[:, idx, :].swapaxes(0, 1)\n", + "\n", + "plot_aline(x_cr, x_crl, xlabel=\"crossline\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model training" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Setup logging\n", + "load_log_configuration(config.LOG_CONFIG)\n", + "logger = logging.getLogger(__name__)\n", + "logger.debug(config.WORKERS)\n", + "scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS\n", + "torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK\n", + "\n", + "torch.manual_seed(config.SEED)\n", + "if torch.cuda.is_available():\n", + " torch.cuda.manual_seed_all(config.SEED)\n", + "np.random.seed(seed=config.SEED)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up data augmentation\n", + "\n", + "Let's define our data augmentation pipeline, which includes basic transformations, such as _data normalization, resizing, and padding_ if necessary. \n", + "The padding is carried out twice becuase if we split the inline or crossline slice into multiple patches then some of these patches will be at the edge of the slice and may not contain a full patch worth of data. To compensate to this and have same size patches in the batch (a requirement) we need to pad them. \n", + "So our basic augmentation is: \n", + "- Normalize\n", + "- Pad if needed to initial size\n", + "- Resize to a larger size\n", + "- Pad further if necessary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Setup Augmentations\n", + "basic_aug = Compose(\n", + " [\n", + " Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=config.TRAIN.MAX,),\n", + " PadIfNeeded(\n", + " min_height=config.TRAIN.PATCH_SIZE,\n", + " min_width=config.TRAIN.PATCH_SIZE,\n", + " border_mode=cv2.BORDER_CONSTANT,\n", + " always_apply=True,\n", + " mask_value=mask_value,\n", + " value=0,\n", + " ),\n", + " Resize(\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True,\n", + " ),\n", + " PadIfNeeded(\n", + " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", + " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", + " border_mode=cv2.BORDER_CONSTANT,\n", + " always_apply=True,\n", + " mask_value=mask_value,\n", + " value=0,\n", + " ),\n", + " ]\n", + ")\n", + "if config.TRAIN.AUGMENTATION:\n", + " train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)])\n", + " val_aug = basic_aug\n", + "else:\n", + " train_aug = val_aug = basic_aug" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load the data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For training the model, we will use a patch-based approach. Rather than using entire sections (crosslines or inlines) of the data, we extract a large number of small patches from the sections, and use the patches as our data. This allows us to generate larger set of images for training, but is also a more feasible approach for large seismic volumes. \n", + "\n", + "We are using a custom patch data loader from our __`deepseismic_interpretation`__ library for generating and loading patches from seismic section data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train_set = InlinePatchDataset(\n", + " img,\n", + " mask,\n", + " config.TRAIN.PATCH_SIZE,\n", + " config.TRAIN.STRIDE,\n", + " split=\"train\",\n", + " transforms=train_aug,\n", + " n_channels=config.MODEL.IN_CHANNELS,\n", + " complete_patches_only=config.TRAIN.COMPLETE_PATCHES_ONLY,\n", + ")\n", + "\n", + "val_set = InlinePatchDataset(\n", + " img,\n", + " mask,\n", + " config.TRAIN.PATCH_SIZE,\n", + " config.TRAIN.STRIDE,\n", + " split=\"val\",\n", + " transforms=val_aug,\n", + " n_channels=config.MODEL.IN_CHANNELS,\n", + " complete_patches_only=config.VALIDATION.COMPLETE_PATCHES_ONLY,\n", + ")\n", + "\n", + "logger.info(train_set)\n", + "logger.info(val_set)\n", + "\n", + "n_classes = train_set.n_classes\n", + "train_loader = data.DataLoader(\n", + " train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True,\n", + ")\n", + "\n", + "val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up model training\n", + "Next, let's define a model to train, an optimization algorithm, and a loss function. \n", + "\n", + "Note that the model is loaded from our __`cv_lib`__ library, using the name of the model as specified in the configuration file. To load a different model, either change the `MODEL.NAME` field in the configuration file, or create a new one corresponding to the model you wish to train." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", + "\n", + "device = \"cpu\"\n", + "if torch.cuda.is_available():\n", + " device = \"cuda\"\n", + "model = model.to(device) # Send to GPU\n", + "\n", + "optimizer = torch.optim.SGD(\n", + " model.parameters(),\n", + " lr=config.TRAIN.MAX_LR,\n", + " momentum=config.TRAIN.MOMENTUM,\n", + " weight_decay=config.TRAIN.WEIGHT_DECAY,\n", + ")\n", + "\n", + "output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),)\n", + "summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR))\n", + "snapshot_duration = scheduler_step * len(train_loader)\n", + "scheduler = CosineAnnealingScheduler(optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration)\n", + "\n", + "criterion = torch.nn.CrossEntropyLoss(ignore_index=mask_value, reduction=\"mean\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Training the model\n", + "We use [ignite](https://pytorch.org/ignite/index.html) framework to create training and validation loops in our codebase. Ignite provides an easy way to create compact training/validation loops without too much boilerplate code.\n", + "\n", + "In this notebook, we demonstrate the use of ignite on the training loop only. We create a training engine `trainer` that loops multiple times over the training dataset and updates model parameters. In addition, we add various events to the trainer, using an event system, that allows us to interact with the engine on each step of the run, such as, when the trainer is started/completed, when the epoch is started/completed and so on. \n", + "\n", + "In the cell below, we use event handlers to add the following events to the training loop:\n", + "- log training output\n", + "- log and schedule learning rate and\n", + "- periodically save model to disk." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "trainer = create_supervised_trainer(model, optimizer, criterion, _prepare_batch, device=device)\n", + "\n", + "trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)\n", + "\n", + "trainer.add_event_handler(\n", + " Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ),\n", + ")\n", + "trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer))\n", + "trainer.add_event_handler(\n", + " Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, \"epoch\"),\n", + ")\n", + "trainer.add_event_handler(\n", + " Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def _select_pred_and_mask(model_out_dict):\n", + " return (model_out_dict[\"y_pred\"].squeeze(), model_out_dict[\"mask\"].squeeze())\n", + "\n", + "evaluator = create_supervised_evaluator(\n", + " model,\n", + " _prepare_batch,\n", + " metrics={\n", + " \"pixacc\": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", + " \"nll\": Loss(criterion, output_transform=_select_pred_and_mask),\n", + " \"cacc\": class_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", + " \"mca\": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", + " \"ciou\": class_iou(n_classes, output_transform=_select_pred_and_mask),\n", + " \"mIoU\": mean_iou(n_classes, output_transform=_select_pred_and_mask),\n", + " },\n", + " device=device,\n", + ")\n", + "\n", + "# Set the validation run to start on the epoch completion of the training run\n", + "trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader))\n", + "\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED,\n", + " logging_handlers.log_metrics(\n", + " \"Validation results\",\n", + " metrics_dict={\n", + " \"nll\": \"Avg loss :\",\n", + " \"pixacc\": \"Pixelwise Accuracy :\",\n", + " \"mca\": \"Avg Class Accuracy :\",\n", + " \"mIoU\": \"Avg Class IoU :\",\n", + " },\n", + " ),\n", + ")\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED,\n", + " tensorboard_handlers.log_metrics(\n", + " summary_writer,\n", + " trainer,\n", + " \"epoch\",\n", + " metrics_dict={\n", + " \"mIoU\": \"Validation/mIoU\",\n", + " \"nll\": \"Validation/Loss\",\n", + " \"mca\": \"Validation/MCA\",\n", + " \"pixacc\": \"Validation/Pixel_Acc\",\n", + " },\n", + " ),\n", + ")\n", + "\n", + "def _select_max(pred_tensor):\n", + " return pred_tensor.max(1)[1]\n", + "\n", + "def _tensor_to_numpy(pred_tensor):\n", + " return pred_tensor.squeeze().cpu().numpy()\n", + "\n", + "transform_func = compose(\n", + " np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy,\n", + ")\n", + "\n", + "transform_pred = compose(transform_func, _select_max)\n", + "\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED, create_image_writer(summary_writer, \"Validation/Image\", \"image\"),\n", + ")\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED,\n", + " create_image_writer(summary_writer, \"Validation/Mask\", \"mask\", transform_func=transform_func),\n", + ")\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED,\n", + " create_image_writer(summary_writer, \"Validation/Pred\", \"y_pred\", transform_func=transform_pred),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Checkpointing\n", + "Below we define the function that will save the best performing models based on mean IoU. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def snapshot_function():\n", + " return (trainer.state.iteration % snapshot_duration) == 0\n", + "\n", + "checkpoint_handler = SnapshotHandler(\n", + " path.join(output_dir, config.TRAIN.MODEL_DIR),\n", + " config.MODEL.NAME,\n", + " extract_metric_from(\"mIoU\"),\n", + " snapshot_function,\n", + ")\n", + "evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {\"model\": model})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Start the training engine run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "logger.info(\"Starting training\")\n", + "trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tensorboard\n", + "Using tensorboard for monitoring runs can be quite enlightening. Just ensure that the appropriate port is open on the VM so you can access it. Below we have the command for running tensorboard in your notebook. You can as easily view it in a seperate browser window by pointing the browser to the appropriate location and port." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext tensorboard" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%tensorboard --logdir outputs --port 6007 --host 0.0.0.0" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index f6bf580c..db735b56 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -3,19 +3,20 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' +OUTPUT_DIR: 'outputs' LOG_DIR: 'log' WORKERS: 4 -PRINT_FREQ: 50 +PRINT_FREQ: 10 LOG_CONFIG: logging.conf SEED: 2019 DATASET: - NUM_CLASSES: 6 - ROOT: /data/dutchf3 - CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - + NUM_CLASSES: 2 + ROOT: /data/msft/seam + CLASS_WEIGHTS: [] + INLINE_HEIGHT: 876 + INLINE_WIDTH: 751 MODEL: NAME: seg_hrnet @@ -64,35 +65,43 @@ MODEL: FUSE_METHOD: SUM TRAIN: - BATCH_SIZE_PER_GPU: 16 + COMPLETE_PATCHES_ONLY: True + BATCH_SIZE_PER_GPU: 32 BEGIN_EPOCH: 0 - END_EPOCH: 10 - MIN_LR: 0.001 + END_EPOCH: 300 + MIN_LR: 0.0001 MAX_LR: 0.02 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "section" #"patch" # Options are No, Patch and Section - STRIDE: 50 - PATCH_SIZE: 100 + DEPTH: "none" #"patch" # Options are none, patch and section + STRIDE: 64 + PATCH_SIZE: 128 AUGMENTATIONS: RESIZE: - HEIGHT: 200 - WIDTH: 200 + HEIGHT: 256 + WIDTH: 256 PAD: HEIGHT: 256 WIDTH: 256 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 + MEAN: [-0.0001777, 0.49, -0.0000688] # First value is for images, second for depth and then combination of both + STD: [0.14076 , 0.2717, 0.06286] + MAX: 1 MODEL_DIR: "models" + +VALIDATION: + BATCH_SIZE_PER_GPU: 128 + COMPLETE_PATCHES_ONLY: True + TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/dutchf3/local/output/mat/exp/237c16780794800631c3f1895cacc475e15aca99/seg_hrnet/Sep17_115731/models/seg_hrnet_running_model_33.pth" - TEST_STRIDE: 10 - SPLIT: 'Both' # Can be Both, Test1, Test2 - INLINE: True - CROSSLINE: True - POST_PROCESSING: - SIZE: 128 # - CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right + COMPLETE_PATCHES_ONLY: False + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + AUGMENTATIONS: + RESIZE: + HEIGHT: 256 + WIDTH: 256 + PAD: + HEIGHT: 256 + WIDTH: 256 From e2ee4e4b9e9fe17ac8c8c32e6ed8c8908e0c2515 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 12 Nov 2019 11:14:25 -0500 Subject: [PATCH 081/207] removed my username references (#15) --- TODO.md | 2 -- scripts/data_symlink.sh | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 TODO.md diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 4d20c8b2..00000000 --- a/TODO.md +++ /dev/null @@ -1,2 +0,0 @@ -1. recode to add dynamically LOG_CONFIG: /home/maxkaz/repos/DeepSeismic/logging.conf -1. other diff --git a/scripts/data_symlink.sh b/scripts/data_symlink.sh index 6f70378c..7f629a6c 100755 --- a/scripts/data_symlink.sh +++ b/scripts/data_symlink.sh @@ -3,6 +3,7 @@ # Azure VMs lose mounts after restart - this symlinks the data folder from user's # home directory after VM restart -sudo chown -R maxkaz /mnt -sudo chgrp -R maxkaz /mnt +user=$(whoami) +sudo chown -R $user /mnt +sudo chgrp -R $user /mnt ln -s ~/dutchf3 /mnt From a9e4ed40acfece8b780646d1d7f805c28ef220e0 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 12 Nov 2019 11:38:39 -0500 Subject: [PATCH 082/207] moving 3D models into contrib folder (#16) --- .../experiments}/interpretation/dutchf3_voxel/README.md | 0 .../interpretation/dutchf3_voxel/configs/texture_net.yaml | 0 .../experiments}/interpretation/dutchf3_voxel/default.py | 0 .../experiments}/interpretation/dutchf3_voxel/logging.conf | 0 .../experiments}/interpretation/dutchf3_voxel/train.py | 0 .../interpretation => contrib/experiments}/voxel2pixel/README.md | 0 .../interpretation => contrib/experiments}/voxel2pixel/batch.py | 0 .../interpretation => contrib/experiments}/voxel2pixel/data.py | 0 .../experiments}/voxel2pixel/tb_logger.py | 0 .../experiments}/voxel2pixel/test_parallel.py | 0 .../experiments}/voxel2pixel/texture_net.py | 0 .../interpretation => contrib/experiments}/voxel2pixel/train.py | 0 .../interpretation => contrib/experiments}/voxel2pixel/utils.py | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename {experiments => contrib/experiments}/interpretation/dutchf3_voxel/README.md (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_voxel/configs/texture_net.yaml (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_voxel/default.py (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_voxel/logging.conf (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_voxel/train.py (100%) rename {experiments/interpretation => contrib/experiments}/voxel2pixel/README.md (100%) rename {experiments/interpretation => contrib/experiments}/voxel2pixel/batch.py (100%) rename {experiments/interpretation => contrib/experiments}/voxel2pixel/data.py (100%) rename {experiments/interpretation => contrib/experiments}/voxel2pixel/tb_logger.py (100%) rename {experiments/interpretation => contrib/experiments}/voxel2pixel/test_parallel.py (100%) rename {experiments/interpretation => contrib/experiments}/voxel2pixel/texture_net.py (100%) rename {experiments/interpretation => contrib/experiments}/voxel2pixel/train.py (100%) rename {experiments/interpretation => contrib/experiments}/voxel2pixel/utils.py (100%) diff --git a/experiments/interpretation/dutchf3_voxel/README.md b/contrib/experiments/interpretation/dutchf3_voxel/README.md similarity index 100% rename from experiments/interpretation/dutchf3_voxel/README.md rename to contrib/experiments/interpretation/dutchf3_voxel/README.md diff --git a/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml b/contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml similarity index 100% rename from experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml rename to contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml diff --git a/experiments/interpretation/dutchf3_voxel/default.py b/contrib/experiments/interpretation/dutchf3_voxel/default.py similarity index 100% rename from experiments/interpretation/dutchf3_voxel/default.py rename to contrib/experiments/interpretation/dutchf3_voxel/default.py diff --git a/experiments/interpretation/dutchf3_voxel/logging.conf b/contrib/experiments/interpretation/dutchf3_voxel/logging.conf similarity index 100% rename from experiments/interpretation/dutchf3_voxel/logging.conf rename to contrib/experiments/interpretation/dutchf3_voxel/logging.conf diff --git a/experiments/interpretation/dutchf3_voxel/train.py b/contrib/experiments/interpretation/dutchf3_voxel/train.py similarity index 100% rename from experiments/interpretation/dutchf3_voxel/train.py rename to contrib/experiments/interpretation/dutchf3_voxel/train.py diff --git a/experiments/interpretation/voxel2pixel/README.md b/contrib/experiments/voxel2pixel/README.md similarity index 100% rename from experiments/interpretation/voxel2pixel/README.md rename to contrib/experiments/voxel2pixel/README.md diff --git a/experiments/interpretation/voxel2pixel/batch.py b/contrib/experiments/voxel2pixel/batch.py similarity index 100% rename from experiments/interpretation/voxel2pixel/batch.py rename to contrib/experiments/voxel2pixel/batch.py diff --git a/experiments/interpretation/voxel2pixel/data.py b/contrib/experiments/voxel2pixel/data.py similarity index 100% rename from experiments/interpretation/voxel2pixel/data.py rename to contrib/experiments/voxel2pixel/data.py diff --git a/experiments/interpretation/voxel2pixel/tb_logger.py b/contrib/experiments/voxel2pixel/tb_logger.py similarity index 100% rename from experiments/interpretation/voxel2pixel/tb_logger.py rename to contrib/experiments/voxel2pixel/tb_logger.py diff --git a/experiments/interpretation/voxel2pixel/test_parallel.py b/contrib/experiments/voxel2pixel/test_parallel.py similarity index 100% rename from experiments/interpretation/voxel2pixel/test_parallel.py rename to contrib/experiments/voxel2pixel/test_parallel.py diff --git a/experiments/interpretation/voxel2pixel/texture_net.py b/contrib/experiments/voxel2pixel/texture_net.py similarity index 100% rename from experiments/interpretation/voxel2pixel/texture_net.py rename to contrib/experiments/voxel2pixel/texture_net.py diff --git a/experiments/interpretation/voxel2pixel/train.py b/contrib/experiments/voxel2pixel/train.py similarity index 100% rename from experiments/interpretation/voxel2pixel/train.py rename to contrib/experiments/voxel2pixel/train.py diff --git a/experiments/interpretation/voxel2pixel/utils.py b/contrib/experiments/voxel2pixel/utils.py similarity index 100% rename from experiments/interpretation/voxel2pixel/utils.py rename to contrib/experiments/voxel2pixel/utils.py From 21b98e27206dd175d69ffc44183e54b5d2a33513 Mon Sep 17 00:00:00 2001 From: Wee Hyong Tok Date: Tue, 12 Nov 2019 14:11:24 -0500 Subject: [PATCH 083/207] Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md --- README.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 360e7197..635fff60 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # DeepSeismic ![DeepSeismic](./DeepSeismicLogo.jpg ) -This repository shows you how to perform seismic imaging and interpretation on Azure. It empowers geophysicists and data scientists to run seismic experiments using state-of-art DSL-based PDE solvers and segmentation algorithms on Azure. The repository provides sample notebooks, data loaders for seismic data, utility codes, and out-of-the box ML pipelines +This repository shows you how to perform seismic imaging and interpretation on Azure. It empowers geophysicists and data scientists to run seismic experiments using state-of-art DSL-based PDE solvers and segmentation algorithms on Azure. -For seismic imaging, the repository shows how you can leverage open-source PDE solvers (e.g. Devito), and perform Full-Waveform Inversion (FWI) at scale on Azure, using Azure Machine Learning (Azure ML), and Azure Batch. It demonnstrates examples on how to run Devito easily on a single machine, as well as multiple machines using Azure ML managed compute. +The repository provides sample notebooks, data loaders for seismic data, utility codes, and out-of-the box ML pipelines. -For seismic interpretation, the repository consists of extensible machine learning pipelines, that shows how you can leverage state-of-art segmentation algorithms (UNet, SEResNET, HRNet) for seismic interpretation, and also benchmarking results from running these algorithms using various seismic datasets (Dutch F3, and Penobscot). ## Interpretation +For seismic interpretation, the repository consists of extensible machine learning pipelines, that shows how you can leverage state-of-art segmentation algorithms (UNet, SEResNET, HRNet) for seismic interpretation, and also benchmarking results from running these algorithms using various seismic datasets (Dutch F3, and Penobscot). + ### Setting up Environment Navigate to the folder where you pulled the DeepSeismic repo to @@ -54,15 +55,15 @@ conda install -c anaconda pyqt=4.11.4 pip install segyviewer ``` -## Benchmarks +### Benchmarks -### Dense Labels +#### Dense Labels This section contains benchmarks of different algorithms for seismic interpretation on 3D seismic datasets with densely-annotated data. Below are the results from the models contained in this repo. To run them check the instructions in folder. Alternatively take a look in for how to run them on your own dataset -### Netherlands F3 +#### Netherlands F3 | Source | Experiment | PA | FW IoU | MCA | |------------------|-----------------------------------|-------------|--------------|------------| @@ -73,7 +74,7 @@ Below are the results from the models contained in this repo. To run them check | | HRNet(patch)+patch_depth | .908 | .843 | .837 | | | HRNet(patch)+section_depth | .928 | .871 | .871 | -### Penobscot +#### Penobscot Trained and tested on full dataset. Inlines with artefacts were left in for training, validation and testing. The dataset was split 70% training, 10% validation and 20% test. The results below are from the test set @@ -88,8 +89,8 @@ The dataset was split 70% training, 10% validation and 20% test. The results bel -### Data -#### Netherlands F3 +#### Data +##### Netherlands F3 To download the F3 Netherlands dataset for 2D experiments, please follow the data download instructions at [this github repository](https://github.com/olivesgatech/facies_classification_benchmark). @@ -107,7 +108,7 @@ python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutchf3 Refer to the script itself for more argument options. -#### Penobscot +##### Penobscot To download the Penobscot dataset run the [download_penobscot.sh](scripts/download_penobscot.sh) script, e.g. ``` @@ -123,12 +124,20 @@ python scripts/prepare_penobscot.py split_inline --data-dir=/mnt/penobscot --val ``` -### Scripts +#### Scripts - [parallel_training.sh](scripts/parallel_training.sh): Script to launch multiple jobs in parallel. Used mainly for local hyperparameter tuning. Look at the script for further instructions - [kill_windows.sh](scripts/kill_windows.sh): Script to kill multiple tmux windows. Used to kill jobs that parallel_training.sh might have started. +## Seismic Imaging +For seismic imaging, the repository shows how you can leverage open-source PDE solvers (e.g. Devito), and perform Full-Waveform Inversion (FWI) at scale on Azure, using Azure Machine Learning (Azure ML), and Azure Batch. The repository provides a collection of sample notebooks that shows + +* How you can create customized Docker containers with Devito and use this on Azure +* How you can create Azure ML estimators for performing FWI using Devito. +This enable the Devito code to easily run on a single machine, as well as multiple machines using Azure ML managed computes. + + ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. From f848d79325f02971cd4fdf64522bbe6a80e519ed Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Tue, 19 Nov 2019 10:06:47 -0800 Subject: [PATCH 084/207] added pytest to environmetn, and pytest job to the main build (#18) --- environment/anaconda/local/environment.yml | 1 + tests/cicd/main_build.yml | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 3e7ecfa6..5a118697 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -33,4 +33,5 @@ dependencies: - pylint - scipy==1.1.0 - jupytext + - pytest diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 511098df..3c5b5cc7 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -20,9 +20,10 @@ jobs: steps: - bash: | - echo "First step" - echo "Replace this with command" - echo "Dummy unit test job passed" + echo "Starting unit tests" + conda activate seismic-interpretation + pytest --durations=0 cv_lib/tests/ + echo "Unit test job passed" displayName: Unit Test Job - job: integration_test_job From f9af75147aacd0585b427835e75b3674157d0858 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 19 Nov 2019 16:51:07 -0500 Subject: [PATCH 085/207] Update main_build.yml for Azure Pipelines --- tests/cicd/main_build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 3c5b5cc7..7972d6a3 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -35,4 +35,8 @@ jobs: - bash: | echo "First step" echo "Dummy integration test job passed" + pwd + ls + git branch + uname -ra displayName: Integration Test Job \ No newline at end of file From 128750ff735f6fdb371abe13b01165ec9d58875f Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 19 Nov 2019 19:42:08 -0500 Subject: [PATCH 086/207] minor stylistic changes (#19) --- README.md | 12 +++++++----- DeepSeismicLogo.jpg => assets/DeepSeismicLogo.jpg | Bin {images => assets}/penobscot_seresnet_best.png | Bin {images => assets}/penobscot_seresnet_worst.png | Bin 4 files changed, 7 insertions(+), 5 deletions(-) rename DeepSeismicLogo.jpg => assets/DeepSeismicLogo.jpg (100%) rename {images => assets}/penobscot_seresnet_best.png (100%) rename {images => assets}/penobscot_seresnet_worst.png (100%) diff --git a/README.md b/README.md index 635fff60..29674216 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # DeepSeismic -![DeepSeismic](./DeepSeismicLogo.jpg ) +![DeepSeismic](./assets/DeepSeismicLogo.jpg ) This repository shows you how to perform seismic imaging and interpretation on Azure. It empowers geophysicists and data scientists to run seismic experiments using state-of-art DSL-based PDE solvers and segmentation algorithms on Azure. @@ -84,8 +84,8 @@ The dataset was split 70% training, 10% validation and 20% test. The results bel | DeepSeismic | SEResNet UNet + section depth | 1.0 | .98 | .99 | | | HRNet(patch) + section depth | 1.0 | .97 | .98 | -![Best Penobscot SEResNet](images/penobscot_seresnet_best.png "Best performing inlines, Mask and Predictions from SEResNet") -![Worst Penobscot SEResNet](images/penobscot_seresnet_worst.png "Worst performing inlines Mask and Predictions from SEResNet") +![Best Penobscot SEResNet](assets/penobscot_seresnet_best.png "Best performing inlines, Mask and Predictions from SEResNet") +![Worst Penobscot SEResNet](assets/penobscot_seresnet_worst.png "Worst performing inlines Mask and Predictions from SEResNet") @@ -112,7 +112,9 @@ Refer to the script itself for more argument options. To download the Penobscot dataset run the [download_penobscot.sh](scripts/download_penobscot.sh) script, e.g. ``` -./download_penobscot.sh /data/penobscot +data_dir='/data/penobscot' +mkdir "$data_dir" +./scripts/download_penobscot.sh "$data_dir" ``` Note that the specified download location (e.g `/data/penobscot`) should be created beforehand, and configured appropriate `write` pemissions. @@ -120,7 +122,7 @@ Note that the specified download location (e.g `/data/penobscot`) should be crea To prepare the data for the experiments (e.g. split into train/val/test), please run the following script (modifying arguments as desired): ``` -python scripts/prepare_penobscot.py split_inline --data-dir=/mnt/penobscot --val-ratio=.1 --test-ratio=.2 +python scripts/prepare_penobscot.py split_inline --data-dir=/data/penobscot --val-ratio=.1 --test-ratio=.2 ``` diff --git a/DeepSeismicLogo.jpg b/assets/DeepSeismicLogo.jpg similarity index 100% rename from DeepSeismicLogo.jpg rename to assets/DeepSeismicLogo.jpg diff --git a/images/penobscot_seresnet_best.png b/assets/penobscot_seresnet_best.png similarity index 100% rename from images/penobscot_seresnet_best.png rename to assets/penobscot_seresnet_best.png diff --git a/images/penobscot_seresnet_worst.png b/assets/penobscot_seresnet_worst.png similarity index 100% rename from images/penobscot_seresnet_worst.png rename to assets/penobscot_seresnet_worst.png From 7f84a3706eb478c14d9513ac2ed112c47a5e9f06 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Wed, 20 Nov 2019 20:44:44 -0500 Subject: [PATCH 087/207] Update main_build.yml for Azure Pipelines Added template for integration tests for scripts and experiments Added setup and env Increased job timeout added complete set of tests --- tests/cicd/main_build.yml | 244 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 232 insertions(+), 12 deletions(-) diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 7972d6a3..eedbc813 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -13,30 +13,250 @@ trigger: jobs: -- job: uni_test_job - timeoutInMinutes: 10 # how long to run the job before automatically cancelling +# partially disable setup for now - done manually on build VM +- job: setup + timeoutInMinutes: 5 + displayName: Setup pool: name: deepseismicagentpool steps: + - bash: | + echo "Running setup..." + + pwd + ls + git branch + uname -ra + + # make sure we have the latest and greatest + # yes | conda env update -f environment/anaconda/local/environment.yml + conda activate seismic-interpretation + pip install -e interpretation + pip install -e cv_lib + # add this if pytorch stops detecting GPU + # conda install pytorch torchvision cudatoolkit=9.2 -c pytorch + + # copy your model files like so - using dummy file to illustrate + azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name + +- job: unit_tests_job + timeoutInMinutes: 20 + pool: + name: deepseismicagentpool + steps: - bash: | echo "Starting unit tests" conda activate seismic-interpretation pytest --durations=0 cv_lib/tests/ echo "Unit test job passed" - displayName: Unit Test Job + displayName: Unit Tests Job -- job: integration_test_job - timeoutInMinutes: 10 # how long to run the job before automatically cancelling +################################################################################################### +# LOCAL PATCH JOBS +################################################################################################### + +- job: hrnet_patch + timeoutInMinutes: 5 + displayName: hrnet patch pool: name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_patch/local + python train.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + # find the latest model which we just trained + model=$(ls -td */*/*/hrnet/* | head -1) + # try running the test script + python test.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/hrnet_running_model_1.pth +- job: unet_patch + timeoutInMinutes: 5 + displayName: unet patch + pool: + name: deepseismicagentpool steps: - bash: | - echo "First step" - echo "Dummy integration test job passed" - pwd - ls - git branch - uname -ra - displayName: Integration Test Job \ No newline at end of file + conda activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_patch/local + python train.py --cfg=configs/unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + # find the latest model which we just trained + model=$(ls -td */*/*/unet/* | head -1) + # try running the test script + python test.py --cfg=configs/unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/unet_running_model_1.pth + +- job: seresnet_unet_patch + timeoutInMinutes: 5 + displayName: seresnet unet patch + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_patch/local + python train.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + # find the latest model which we just trained + model=$(ls -td */*/*/seresnet_unet/* | head -1) + # try running the test script + python test.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/seresnet_unet_running_model_1.pth + +- job: patch_deconvnet + timeoutInMinutes: 5 + displayName: patch deconvnet + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_patch/local + python train.py --cfg=configs/patch_deconvnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + # find the latest model which we just trained + model=$(ls -td */*/*/patch_deconvnet/* | head -1) + # try running the test script + python test.py --cfg=configs/patch_deconvnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/patch_deconvnet_running_model_1.pth + +- job: patch_deconvnet_skip + timeoutInMinutes: 5 + displayName: patch deconvnet skip + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_patch/local + python train.py --cfg=configs/patch_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + # find the latest model which we just trained + model=$(ls -td */*/*/patch_deconvnet_skip/* | head -1) + # try running the test script + python test.py --cfg=configs/patch_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/patch_deconvnet_skip_running_model_1.pth + +# Penobscot dataset + +- job: seresnet_unet + timeoutInMinutes: 5 + displayName: seresnet unet + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + # run the tests + cd experiments/interpretation/penobscot/local + python train.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + # find the latest model which we just trained + model=$(ls -td */*/*/seresnet_unet/* | head -1) + # try running the test script + python test.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/models/seresnet_unet_running_model_1.pth + +- job: hrnet_penobscot + timeoutInMinutes: 5 + displayName: hrnet penobscot + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + # run the tests + cd experiments/interpretation/penobscot/local + python train.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + # find the latest model which we just trained + model=$(ls -td */*/*/hrnet/* | head -1) + # try running the test script + python test.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/models/hrnet_running_model_1.pth + +################################################################################################### +# DISTRIBUTED PATCH JOBS +################################################################################################### + +- job: hrnet_patch + timeoutInMinutes: 5 + displayName: hrnet patch + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" + # run the tests + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + +- job: unet_patch + timeoutInMinutes: 5 + displayName: unet patch + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" + # run the tests + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + +- job: seresnet_unet_patch + timeoutInMinutes: 5 + displayName: seresnet unet patch + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" + # run the tests + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + +- job: patch_deconvnet + timeoutInMinutes: 5 + displayName: patch deconvnet + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" + # run the tests + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/patch_deconvnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + +- job: patch_deconvnet_skip + timeoutInMinutes: 5 + displayName: patch deconvnet skip + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" + # run the tests + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/patch_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + + +################################################################################################### +# LOCAL SECTION JOBS +################################################################################################### + +- job: section_deconvnet_skip + timeoutInMinutes: 5 + displayName: section deconvnet skip + pool: + name: deepseismicagentpool + steps: + - bash: | + conda activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_section/local + python train.py --cfg=configs/section_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + # find the latest model which we just trained + model=$(ls -td */*/*/section_deconvnet_skip/* | head -1) + # try running the test script + python test.py --cfg=configs/section_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/section_deconvnet_skip_running_model_1.pth + From 47961154f7d677b1d490edb19f86b9b7079d72bc Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Fri, 22 Nov 2019 04:07:17 +0000 Subject: [PATCH 088/207] BUILD: placeholder for Azure pipelines for notebooks build. BUILD: added notebooks job placeholders. BUILD: added github badgets for notebook builds --- README.md | 2 ++ tests/cicd/notebooks_build.yml | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 tests/cicd/notebooks_build.yml diff --git a/README.md b/README.md index 29674216..29dc93fa 100644 --- a/README.md +++ b/README.md @@ -159,4 +159,6 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=master) | | **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=staging) | | **Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=master) | +| **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | +| **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | diff --git a/tests/cicd/notebooks_build.yml b/tests/cicd/notebooks_build.yml new file mode 100644 index 00000000..003cec03 --- /dev/null +++ b/tests/cicd/notebooks_build.yml @@ -0,0 +1,62 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Pull request against these branches will trigger this build +pr: +- master +- staging + +# Any commit to this branch will trigger the build. +trigger: +- master +- staging + +jobs: + +# partially disable setup for now - done manually on build VM +- job: setup + timeoutInMinutes: 5 + displayName: Setup + pool: + name: deepseismicagentpool + + steps: + - bash: | + echo "Running setup..." + + pwd + ls + git branch + uname -ra + + # make sure we have the latest and greatest + # yes | conda env update -f environment/anaconda/local/environment.yml + conda activate seismic-interpretation + pip install -e interpretation + pip install -e cv_lib + # add this if pytorch stops detecting GPU + # conda install pytorch torchvision cudatoolkit=9.2 -c pytorch + + # copy your model files like so - using dummy file to illustrate + azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name + +- job: HRNET_demo + timeoutInMinutes: 5 + displayName: HRNET demo + pool: + name: deepseismicagentpool + steps: + - bash: | + echo "TODO: HRNET_demo placeholder" + conda activate seismic-interpretation + +- job: F3_block_training_and_evaluation_local + timeoutInMinutes: 5 + displayName: F3 block training and evaluation local + pool: + name: deepseismicagentpool + steps: + - bash: | + echo "TODO: F3_block_training_and_evaluation_local placeholder" + conda activate seismic-interpretation + From b95ea81e92eb5f6bf42a9121fb7f937fc4fd45ab Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Fri, 22 Nov 2019 12:44:29 -0500 Subject: [PATCH 089/207] CLEANUP: moved non-release items to contrib (#20) --- {benchmarks => contrib/benchmarks}/README.md | 0 .../benchmarks}/benchmark_utils.py | 0 {imaging => contrib/imaging}/bin/ds | 0 .../imaging}/deepseismic_imaging/__init__.py | 0 .../deepseismic_imaging/cli/__init__.py | 0 .../deepseismic_imaging/cli/forward.py | 0 .../deepseismic_imaging/cli/velocity.py | 0 .../deepseismic_imaging/forward/__init__.py | 0 .../deepseismic_imaging/forward/models.py | 0 .../deepseismic_imaging/forward/sources.py | 0 .../deepseismic_imaging/forward/subdomains.py | 0 .../deepseismic_imaging/forward/time.py | 0 .../deepseismic_imaging/forward/types.py | 0 .../deepseismic_imaging/velocity/__init__.py | 0 .../deepseismic_imaging/velocity/generator.py | 0 .../velocity/roeth_tarantola.py | 0 {imaging => contrib/imaging}/requirements.txt | 0 {imaging => contrib/imaging}/setup.cfg | 0 {imaging => contrib/imaging}/setup.py | 0 .../local/configs/texture_net.yaml | 64 ------------------- 20 files changed, 64 deletions(-) rename {benchmarks => contrib/benchmarks}/README.md (100%) rename {benchmarks => contrib/benchmarks}/benchmark_utils.py (100%) rename {imaging => contrib/imaging}/bin/ds (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/__init__.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/cli/__init__.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/cli/forward.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/cli/velocity.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/forward/__init__.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/forward/models.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/forward/sources.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/forward/subdomains.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/forward/time.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/forward/types.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/velocity/__init__.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/velocity/generator.py (100%) rename {imaging => contrib/imaging}/deepseismic_imaging/velocity/roeth_tarantola.py (100%) rename {imaging => contrib/imaging}/requirements.txt (100%) rename {imaging => contrib/imaging}/setup.cfg (100%) rename {imaging => contrib/imaging}/setup.py (100%) delete mode 100644 experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml diff --git a/benchmarks/README.md b/contrib/benchmarks/README.md similarity index 100% rename from benchmarks/README.md rename to contrib/benchmarks/README.md diff --git a/benchmarks/benchmark_utils.py b/contrib/benchmarks/benchmark_utils.py similarity index 100% rename from benchmarks/benchmark_utils.py rename to contrib/benchmarks/benchmark_utils.py diff --git a/imaging/bin/ds b/contrib/imaging/bin/ds similarity index 100% rename from imaging/bin/ds rename to contrib/imaging/bin/ds diff --git a/imaging/deepseismic_imaging/__init__.py b/contrib/imaging/deepseismic_imaging/__init__.py similarity index 100% rename from imaging/deepseismic_imaging/__init__.py rename to contrib/imaging/deepseismic_imaging/__init__.py diff --git a/imaging/deepseismic_imaging/cli/__init__.py b/contrib/imaging/deepseismic_imaging/cli/__init__.py similarity index 100% rename from imaging/deepseismic_imaging/cli/__init__.py rename to contrib/imaging/deepseismic_imaging/cli/__init__.py diff --git a/imaging/deepseismic_imaging/cli/forward.py b/contrib/imaging/deepseismic_imaging/cli/forward.py similarity index 100% rename from imaging/deepseismic_imaging/cli/forward.py rename to contrib/imaging/deepseismic_imaging/cli/forward.py diff --git a/imaging/deepseismic_imaging/cli/velocity.py b/contrib/imaging/deepseismic_imaging/cli/velocity.py similarity index 100% rename from imaging/deepseismic_imaging/cli/velocity.py rename to contrib/imaging/deepseismic_imaging/cli/velocity.py diff --git a/imaging/deepseismic_imaging/forward/__init__.py b/contrib/imaging/deepseismic_imaging/forward/__init__.py similarity index 100% rename from imaging/deepseismic_imaging/forward/__init__.py rename to contrib/imaging/deepseismic_imaging/forward/__init__.py diff --git a/imaging/deepseismic_imaging/forward/models.py b/contrib/imaging/deepseismic_imaging/forward/models.py similarity index 100% rename from imaging/deepseismic_imaging/forward/models.py rename to contrib/imaging/deepseismic_imaging/forward/models.py diff --git a/imaging/deepseismic_imaging/forward/sources.py b/contrib/imaging/deepseismic_imaging/forward/sources.py similarity index 100% rename from imaging/deepseismic_imaging/forward/sources.py rename to contrib/imaging/deepseismic_imaging/forward/sources.py diff --git a/imaging/deepseismic_imaging/forward/subdomains.py b/contrib/imaging/deepseismic_imaging/forward/subdomains.py similarity index 100% rename from imaging/deepseismic_imaging/forward/subdomains.py rename to contrib/imaging/deepseismic_imaging/forward/subdomains.py diff --git a/imaging/deepseismic_imaging/forward/time.py b/contrib/imaging/deepseismic_imaging/forward/time.py similarity index 100% rename from imaging/deepseismic_imaging/forward/time.py rename to contrib/imaging/deepseismic_imaging/forward/time.py diff --git a/imaging/deepseismic_imaging/forward/types.py b/contrib/imaging/deepseismic_imaging/forward/types.py similarity index 100% rename from imaging/deepseismic_imaging/forward/types.py rename to contrib/imaging/deepseismic_imaging/forward/types.py diff --git a/imaging/deepseismic_imaging/velocity/__init__.py b/contrib/imaging/deepseismic_imaging/velocity/__init__.py similarity index 100% rename from imaging/deepseismic_imaging/velocity/__init__.py rename to contrib/imaging/deepseismic_imaging/velocity/__init__.py diff --git a/imaging/deepseismic_imaging/velocity/generator.py b/contrib/imaging/deepseismic_imaging/velocity/generator.py similarity index 100% rename from imaging/deepseismic_imaging/velocity/generator.py rename to contrib/imaging/deepseismic_imaging/velocity/generator.py diff --git a/imaging/deepseismic_imaging/velocity/roeth_tarantola.py b/contrib/imaging/deepseismic_imaging/velocity/roeth_tarantola.py similarity index 100% rename from imaging/deepseismic_imaging/velocity/roeth_tarantola.py rename to contrib/imaging/deepseismic_imaging/velocity/roeth_tarantola.py diff --git a/imaging/requirements.txt b/contrib/imaging/requirements.txt similarity index 100% rename from imaging/requirements.txt rename to contrib/imaging/requirements.txt diff --git a/imaging/setup.cfg b/contrib/imaging/setup.cfg similarity index 100% rename from imaging/setup.cfg rename to contrib/imaging/setup.cfg diff --git a/imaging/setup.py b/contrib/imaging/setup.py similarity index 100% rename from imaging/setup.py rename to contrib/imaging/setup.py diff --git a/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml b/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml deleted file mode 100644 index e218d854..00000000 --- a/experiments/interpretation/dutchf3_patch/local/configs/texture_net.yaml +++ /dev/null @@ -1,64 +0,0 @@ -# UNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: logging.conf -SEED: 2019 -WINDOW_SIZE: 65 - - -DATASET: - NUM_CLASSES: 2 - ROOT: /mnt/dutchf3 - CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - -MODEL: - NAME: texture_net - IN_CHANNELS: 1 - - -TRAIN: - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "voxel" # Options are No, Patch, Section and Voxel - STRIDE: 50 - PATCH_SIZE: 100 - AUGMENTATIONS: - RESIZE: - HEIGHT: 200 - WIDTH: 200 - PAD: - HEIGHT: 256 - WIDTH: 256 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 - MODEL_DIR: "models" - - -VALIDATION: - BATCH_SIZE_PER_GPU: 32 - -TEST: - MODEL_PATH: "" - TEST_STRIDE: 10 - SPLIT: 'Both' # Can be Both, Test1, Test2 - INLINE: True - CROSSLINE: True - POST_PROCESSING: - SIZE: 128 - CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right - From 07c9d84ae0ae91489d816606b9cf16f3ef5efaad Mon Sep 17 00:00:00 2001 From: Mat Date: Mon, 25 Nov 2019 10:26:51 -0500 Subject: [PATCH 090/207] =?UTF-8?q?Updates=20HRNet=20notebook=20?= =?UTF-8?q?=F0=9F=9A=80=20=20(#25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Modifies pre-commit hook to modify output * Modifies the HRNet notebook to use Penobscot dataset Adds parameters to limit iterations Adds parameters meta tag for papermil * Fixing merge peculiarities --- .pre-commit-config.yaml | 5 +- README.md | 2 + .../notebooks/HRNet_demo_notebook.ipynb | 158 +++++++++++------- .../notebooks/configs/hrnet.yaml | 13 +- tests/cicd/main_build.yml | 3 +- 5 files changed, 113 insertions(+), 68 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43a6b718..d852a98b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,7 @@ repos: hooks: - id: jupytext name: jupytext - entry: jupytext --sync --pipe black --check flake8 --pre-commit - pass_filenames: false + entry: jupytext --from ipynb --pipe black --check flake8 + pass_filenames: true + files: .ipynb language: python diff --git a/README.md b/README.md index 29dc93fa..3867f411 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope ## Build Status | Build | Branch | Status | | --- | --- | --- | + | **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=staging) | | **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=master) | | **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=staging) | @@ -162,3 +163,4 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | | **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | + diff --git a/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb index 2b250cfd..1a8ec834 100644 --- a/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb @@ -20,7 +20,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this notebook, we demonstrate how to train an HRNet model for facies prediction a numpy dataset. The data expected in this notebook needs to be in the form of two 3D arrays. One array will contain the seismic information the other the mask. The network will be trained to take a 2D patch of data from the seismic block and learn to predict the 2D mask patch associated with it. " + "In this notebook, we demonstrate how to train an HRNet model for facies prediction a numpy dataset. The data expected in this notebook needs to be in the form of two 3D arrays. One array will contain the seismic information the other the mask. The network will be trained to take a 2D patch of data from the seismic block and learn to predict the 2D mask patch associated with it." ] }, { @@ -85,7 +85,7 @@ " create_supervised_evaluator,\n", " create_supervised_trainer,\n", ")\n", - "from deepseismic_interpretation.data import InlinePatchDataset\n", + "from deepseismic_interpretation.penobscot.data import PenobscotInlinePatchDataset\n", "from deepseismic_interpretation.dutchf3.data import decode_segmap\n", "from ignite.contrib.handlers import CosineAnnealingScheduler\n", "from ignite.engine import Events\n", @@ -95,10 +95,12 @@ "from torch.utils import data\n", "from itkwidgets import view\n", "from utilities import plot_aline\n", + "from toolz import take\n", + "\n", "\n", "mask_value = 255\n", "_SEG_COLOURS = np.asarray(\n", - " [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123],]\n", + " [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123]]\n", ")\n", "\n", "# experiment configuration file\n", @@ -145,35 +147,31 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Load Dataset" + "## Parameters" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "tags": [ + "parameters" + ] + }, "outputs": [], "source": [ - "ny = 1001\n", - "nx = 876\n", - "nz = 751\n", - "\n", - "img_name = \"rtm3d.bin\"\n", - "seam_image_name = path.join(config.DATASET.ROOT, img_name)\n", - "# load RTM image, note it is type np.float32\n", - "img = np.fromfile(seam_image_name, dtype=np.float32).reshape(ny, nx, nz) / 71892250000.0\n", - "\n", - "salt_name = \"salt_mask.bin\"\n", - "salt_mask_name = path.join(config.DATASET.ROOT, salt_name)\n", - "# load salt mask, note it is type np.int16\n", - "mask = np.fromfile(salt_mask_name, dtype=np.int16).reshape(ny, nx, nz)\n" + "max_iterations = None # The number of iterations you want to run in training or validation\n", + "# Setting to None will run whole dataset\n", + "# Use only if you want to check things are running and don't want to run\n", + "# through whole dataset\n", + "max_epochs = config.TRAIN.END_EPOCH # The number of epochs to run in training" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's visualize the dataset. " + "## Load Dataset" ] }, { @@ -182,14 +180,46 @@ "metadata": {}, "outputs": [], "source": [ - "view(mask, slicing_planes=True)" + "import os\n", + "from toolz import pipe\n", + "import glob\n", + "from PIL import Image" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "image_dir = os.path.join(config.DATASET.ROOT, \"inlines\")\n", + "mask_dir = os.path.join(config.DATASET.ROOT, \"masks\")\n", + "\n", + "image_iter = pipe(os.path.join(image_dir, \"*.tiff\"), glob.iglob,)\n", + "\n", + "_open_to_array = compose(np.array, Image.open)\n", + "\n", + "\n", + "def open_image_mask(image_path):\n", + " return pipe(image_path, _open_to_array)\n", + "\n", + "\n", + "def _mask_filename(imagepath):\n", + " file_part = os.path.splitext(os.path.split(imagepath)[-1].strip())[0]\n", + " return os.path.join(mask_dir, file_part + \"_mask.png\")\n", + "\n", + "\n", + "image_list = sorted(list(image_iter))\n", + "image_list_array = [_open_to_array(i) for i in image_list]\n", + "mask_list_array = [pipe(i, _mask_filename, _open_to_array) for i in image_list]\n", + "mask = np.stack(mask_list_array, axis=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's view slices of the data along inline and crossline directions. " + "Let's visualize the dataset." ] }, { @@ -198,11 +228,14 @@ "metadata": {}, "outputs": [], "source": [ - "idx = 100\n", - "x_in = img[idx, :, :].swapaxes(0, 1)\n", - "x_inl = mask[idx, :, :].swapaxes(0, 1)\n", - "\n", - "plot_aline(x_in, x_inl, xlabel=\"inline\")" + "view(mask, slicing_planes=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's view slices of the data along inline and crossline directions." ] }, { @@ -211,10 +244,11 @@ "metadata": {}, "outputs": [], "source": [ - "x_cr = img[:, idx, :].swapaxes(0, 1)\n", - "x_crl = mask[:, idx, :].swapaxes(0, 1)\n", + "idx = 100\n", + "x_in = image_list_array[idx]\n", + "x_inl = mask_list_array[idx]\n", "\n", - "plot_aline(x_cr, x_crl, xlabel=\"crossline\")" + "plot_aline(x_in, x_inl, xlabel=\"inline\")" ] }, { @@ -249,9 +283,9 @@ "source": [ "### Set up data augmentation\n", "\n", - "Let's define our data augmentation pipeline, which includes basic transformations, such as _data normalization, resizing, and padding_ if necessary. \n", - "The padding is carried out twice becuase if we split the inline or crossline slice into multiple patches then some of these patches will be at the edge of the slice and may not contain a full patch worth of data. To compensate to this and have same size patches in the batch (a requirement) we need to pad them. \n", - "So our basic augmentation is: \n", + "Let's define our data augmentation pipeline, which includes basic transformations, such as _data normalization, resizing, and padding_ if necessary.\n", + "The padding is carried out twice becuase if we split the inline or crossline slice into multiple patches then some of these patches will be at the edge of the slice and may not contain a full patch worth of data. To compensate to this and have same size patches in the batch (a requirement) we need to pad them.\n", + "So our basic augmentation is:\n", "- Normalize\n", "- Pad if needed to initial size\n", "- Resize to a larger size\n", @@ -276,9 +310,7 @@ " mask_value=mask_value,\n", " value=0,\n", " ),\n", - " Resize(\n", - " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True,\n", - " ),\n", + " Resize(config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True,),\n", " PadIfNeeded(\n", " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", @@ -307,7 +339,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For training the model, we will use a patch-based approach. Rather than using entire sections (crosslines or inlines) of the data, we extract a large number of small patches from the sections, and use the patches as our data. This allows us to generate larger set of images for training, but is also a more feasible approach for large seismic volumes. \n", + "For training the model, we will use a patch-based approach. Rather than using entire sections (crosslines or inlines) of the data, we extract a large number of small patches from the sections, and use the patches as our data. This allows us to generate larger set of images for training, but is also a more feasible approach for large seismic volumes.\n", "\n", "We are using a custom patch data loader from our __`deepseismic_interpretation`__ library for generating and loading patches from seismic section data." ] @@ -315,12 +347,13 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [ - "train_set = InlinePatchDataset(\n", - " img,\n", - " mask,\n", + "train_set = PenobscotInlinePatchDataset(\n", + " config.DATASET.ROOT,\n", " config.TRAIN.PATCH_SIZE,\n", " config.TRAIN.STRIDE,\n", " split=\"train\",\n", @@ -329,9 +362,8 @@ " complete_patches_only=config.TRAIN.COMPLETE_PATCHES_ONLY,\n", ")\n", "\n", - "val_set = InlinePatchDataset(\n", - " img,\n", - " mask,\n", + "val_set = PenobscotInlinePatchDataset(\n", + " config.DATASET.ROOT,\n", " config.TRAIN.PATCH_SIZE,\n", " config.TRAIN.STRIDE,\n", " split=\"val\",\n", @@ -348,7 +380,7 @@ " train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True,\n", ")\n", "\n", - "val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,)\n" + "val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,)" ] }, { @@ -356,7 +388,7 @@ "metadata": {}, "source": [ "### Set up model training\n", - "Next, let's define a model to train, an optimization algorithm, and a loss function. \n", + "Next, let's define a model to train, an optimization algorithm, and a loss function.\n", "\n", "Note that the model is loaded from our __`cv_lib`__ library, using the name of the model as specified in the configuration file. To load a different model, either change the `MODEL.NAME` field in the configuration file, or create a new one corresponding to the model you wish to train." ] @@ -375,10 +407,7 @@ "model = model.to(device) # Send to GPU\n", "\n", "optimizer = torch.optim.SGD(\n", - " model.parameters(),\n", - " lr=config.TRAIN.MAX_LR,\n", - " momentum=config.TRAIN.MOMENTUM,\n", - " weight_decay=config.TRAIN.WEIGHT_DECAY,\n", + " model.parameters(), lr=config.TRAIN.MAX_LR, momentum=config.TRAIN.MOMENTUM, weight_decay=config.TRAIN.WEIGHT_DECAY,\n", ")\n", "\n", "output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),)\n", @@ -396,7 +425,7 @@ "### Training the model\n", "We use [ignite](https://pytorch.org/ignite/index.html) framework to create training and validation loops in our codebase. Ignite provides an easy way to create compact training/validation loops without too much boilerplate code.\n", "\n", - "In this notebook, we demonstrate the use of ignite on the training loop only. We create a training engine `trainer` that loops multiple times over the training dataset and updates model parameters. In addition, we add various events to the trainer, using an event system, that allows us to interact with the engine on each step of the run, such as, when the trainer is started/completed, when the epoch is started/completed and so on. \n", + "In this notebook, we demonstrate the use of ignite on the training loop only. We create a training engine `trainer` that loops multiple times over the training dataset and updates model parameters. In addition, we add various events to the trainer, using an event system, that allows us to interact with the engine on each step of the run, such as, when the trainer is started/completed, when the epoch is started/completed and so on.\n", "\n", "In the cell below, we use event handlers to add the following events to the training loop:\n", "- log training output\n", @@ -435,6 +464,7 @@ "def _select_pred_and_mask(model_out_dict):\n", " return (model_out_dict[\"y_pred\"].squeeze(), model_out_dict[\"mask\"].squeeze())\n", "\n", + "\n", "evaluator = create_supervised_evaluator(\n", " model,\n", " _prepare_batch,\n", @@ -449,6 +479,9 @@ " device=device,\n", ")\n", "\n", + "if max_iterations is not None:\n", + " val_loader = take(max_iterations, val_loader)\n", + "\n", "# Set the validation run to start on the epoch completion of the training run\n", "trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader))\n", "\n", @@ -479,15 +512,16 @@ " ),\n", ")\n", "\n", + "\n", "def _select_max(pred_tensor):\n", " return pred_tensor.max(1)[1]\n", "\n", + "\n", "def _tensor_to_numpy(pred_tensor):\n", " return pred_tensor.squeeze().cpu().numpy()\n", "\n", - "transform_func = compose(\n", - " np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy,\n", - ")\n", + "\n", + "transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy,)\n", "\n", "transform_pred = compose(transform_func, _select_max)\n", "\n", @@ -509,7 +543,7 @@ "metadata": {}, "source": [ "### Checkpointing\n", - "Below we define the function that will save the best performing models based on mean IoU. " + "Below we define the function that will save the best performing models based on mean IoU." ] }, { @@ -521,11 +555,9 @@ "def snapshot_function():\n", " return (trainer.state.iteration % snapshot_duration) == 0\n", "\n", + "\n", "checkpoint_handler = SnapshotHandler(\n", - " path.join(output_dir, config.TRAIN.MODEL_DIR),\n", - " config.MODEL.NAME,\n", - " extract_metric_from(\"mIoU\"),\n", - " snapshot_function,\n", + " path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, extract_metric_from(\"mIoU\"), snapshot_function,\n", ")\n", "evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {\"model\": model})" ] @@ -537,6 +569,16 @@ "Start the training engine run." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if max_iterations is not None:\n", + " train_loader = take(max_iterations, train_loader)" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index db735b56..ffcbd72d 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -6,17 +6,18 @@ GPUS: (0,) OUTPUT_DIR: 'outputs' LOG_DIR: 'log' WORKERS: 4 -PRINT_FREQ: 10 +PRINT_FREQ: 50 LOG_CONFIG: logging.conf SEED: 2019 DATASET: - NUM_CLASSES: 2 - ROOT: /data/msft/seam - CLASS_WEIGHTS: [] - INLINE_HEIGHT: 876 - INLINE_WIDTH: 751 + NUM_CLASSES: 7 + ROOT: /mnt/penobscot + CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] + INLINE_HEIGHT: 1501 + INLINE_WIDTH: 481 + MODEL: NAME: seg_hrnet diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index eedbc813..361934f1 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -12,18 +12,17 @@ trigger: - staging jobs: - # partially disable setup for now - done manually on build VM - job: setup timeoutInMinutes: 5 displayName: Setup + pool: name: deepseismicagentpool steps: - bash: | echo "Running setup..." - pwd ls git branch From 5f9df2232cf781602578397bcb461d54967b9426 Mon Sep 17 00:00:00 2001 From: Mat Date: Mon, 25 Nov 2019 10:39:42 -0500 Subject: [PATCH 091/207] Updates environment.yaml (#21) * Pins main libraries Adds cudatoolkit version based on issues faced during workshop * removing files --- environment/anaconda/local/environment.yml | 37 ++++++++++--------- .../notebooks/configs/hrnet.yaml | 1 + 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 5a118697..fad5040e 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -5,33 +5,34 @@ channels: dependencies: - python=3.6 - pip - - pytorch + - pytorch==1.2.0 + - cudatoolkit==9.2 - jupyter - ipykernel - - torchvision - - pandas - - opencv - - scikit-learn + - torchvision==0.4.0 + - pandas==0.24.2 + - opencv==4.1.1 + - scikit-learn==0.21.3 - tensorflow==2.0 - - tqdm - - itkwidgets + - tqdm==4.38.0 + - itkwidgets==0.22.0 - pip: - - segyio + - segyio==1.8.8 - pytorch-ignite==0.3.0.dev20191105 # pre-release until stable available - - fire - - toolz + - fire==0.2.1 + - toolz==0.9.0 - tabulate==0.8.2 - - Jinja2 - - gitpython - - tensorboard - - tensorboardx - - tqdm + - jinja2==2.10.3 + - gitpython==3.0.4 + - tensortensorboard==2.0.0 + - tensorboardx==1.8 + - tqdm==4.38.0 - invoke - - yacs - - albumentations + - yacs==0.1.6 + - albumentations==0.4.2 - black - pylint - scipy==1.1.0 - - jupytext + - jupytext==1.2.4 - pytest diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index ffcbd72d..112df1b3 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -19,6 +19,7 @@ DATASET: INLINE_WIDTH: 481 + MODEL: NAME: seg_hrnet IN_CHANNELS: 3 From 62289f4920b4586b93f09dc30f1fba0fb0ca3107 Mon Sep 17 00:00:00 2001 From: Mat Date: Mon, 25 Nov 2019 10:40:12 -0500 Subject: [PATCH 092/207] Updates Readme (#22) * Adds model instructions to readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 3867f411..8a613902 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,9 @@ To prepare the data for the experiments (e.g. split into train/val/test), please python scripts/prepare_penobscot.py split_inline --data-dir=/data/penobscot --val-ratio=.1 --test-ratio=.2 ``` +### Pretrained Models +#### HRNet +To achieve the same results as the benchmarks above you will need to download the HRNet model pretrained on Imagenet. This can be found [here](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk). Download this to your local drive and make sure you add the path to the Yacs configuration script. #### Scripts - [parallel_training.sh](scripts/parallel_training.sh): Script to launch multiple jobs in parallel. Used mainly for local hyperparameter tuning. Look at the script for further instructions From 4aeeda3971eee91b24ee76c2e8eba562fd9b03d5 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Mon, 25 Nov 2019 21:50:04 -0500 Subject: [PATCH 093/207] Update README.md (#24) I have collected points to all of our BP repos into this central place. We are trying to create links between everything to draw people from one to the other. Can we please add a pointer here to the readme? I have spoken with Max and will be adding Deep Seismic there once you have gone public. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8a613902..7375711d 100644 --- a/README.md +++ b/README.md @@ -166,4 +166,6 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | | **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | +# Related projects +[Microsoft AI Labs Github](https://aka.ms/ai-labs) Find other Best Practice projects, and Azure AI design patterns in our central repository. From 439c5c2a8a4e21299a6d46623130d25159745e6d Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 25 Nov 2019 22:07:34 -0500 Subject: [PATCH 094/207] CONTRIB: cleanup for imaging. (#28) --- contrib/README.md | 3 + ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 0 ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 0 ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 0 ...zureml_buildexperimentationdockerimage.log | 0 contrib/imaging/bin/ds | 6 - .../imaging/deepseismic_imaging/__init__.py | 6 - .../deepseismic_imaging/cli/__init__.py | 24 ---- .../deepseismic_imaging/cli/forward.py | 91 ------------ .../deepseismic_imaging/cli/velocity.py | 71 ---------- .../deepseismic_imaging/forward/__init__.py | 17 --- .../deepseismic_imaging/forward/models.py | 134 ------------------ .../deepseismic_imaging/forward/sources.py | 123 ---------------- .../deepseismic_imaging/forward/subdomains.py | 17 --- .../deepseismic_imaging/forward/time.py | 37 ----- .../deepseismic_imaging/forward/types.py | 9 -- .../deepseismic_imaging/velocity/__init__.py | 7 - .../deepseismic_imaging/velocity/generator.py | 22 --- .../velocity/roeth_tarantola.py | 42 ------ contrib/imaging/requirements.txt | 6 - contrib/imaging/setup.cfg | 2 - contrib/imaging/setup.py | 43 ------ deepseismic/__init__.py | 6 - deepseismic/cli/__init__.py | 24 ---- deepseismic/cli/forward.py | 91 ------------ deepseismic/cli/velocity.py | 71 ---------- deepseismic/forward/__init__.py | 17 --- deepseismic/forward/models.py | 134 ------------------ deepseismic/forward/sources.py | 123 ---------------- deepseismic/forward/subdomains.py | 17 --- deepseismic/forward/time.py | 37 ----- deepseismic/forward/types.py | 9 -- deepseismic/velocity/__init__.py | 7 - deepseismic/velocity/generator.py | 22 --- deepseismic/velocity/roeth_tarantola.py | 42 ------ 35 files changed, 3 insertions(+), 1257 deletions(-) create mode 100644 contrib/README.md rename {examples => contrib/examples}/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb (100%) rename {examples => contrib/examples}/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb (100%) rename {examples => contrib/examples}/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb (100%) rename {examples => contrib/examples}/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log (100%) delete mode 100644 contrib/imaging/bin/ds delete mode 100644 contrib/imaging/deepseismic_imaging/__init__.py delete mode 100644 contrib/imaging/deepseismic_imaging/cli/__init__.py delete mode 100644 contrib/imaging/deepseismic_imaging/cli/forward.py delete mode 100644 contrib/imaging/deepseismic_imaging/cli/velocity.py delete mode 100644 contrib/imaging/deepseismic_imaging/forward/__init__.py delete mode 100644 contrib/imaging/deepseismic_imaging/forward/models.py delete mode 100644 contrib/imaging/deepseismic_imaging/forward/sources.py delete mode 100644 contrib/imaging/deepseismic_imaging/forward/subdomains.py delete mode 100644 contrib/imaging/deepseismic_imaging/forward/time.py delete mode 100644 contrib/imaging/deepseismic_imaging/forward/types.py delete mode 100644 contrib/imaging/deepseismic_imaging/velocity/__init__.py delete mode 100644 contrib/imaging/deepseismic_imaging/velocity/generator.py delete mode 100644 contrib/imaging/deepseismic_imaging/velocity/roeth_tarantola.py delete mode 100644 contrib/imaging/requirements.txt delete mode 100644 contrib/imaging/setup.cfg delete mode 100644 contrib/imaging/setup.py delete mode 100644 deepseismic/__init__.py delete mode 100644 deepseismic/cli/__init__.py delete mode 100644 deepseismic/cli/forward.py delete mode 100644 deepseismic/cli/velocity.py delete mode 100644 deepseismic/forward/__init__.py delete mode 100644 deepseismic/forward/models.py delete mode 100644 deepseismic/forward/sources.py delete mode 100644 deepseismic/forward/subdomains.py delete mode 100644 deepseismic/forward/time.py delete mode 100644 deepseismic/forward/types.py delete mode 100644 deepseismic/velocity/__init__.py delete mode 100644 deepseismic/velocity/generator.py delete mode 100644 deepseismic/velocity/roeth_tarantola.py diff --git a/contrib/README.md b/contrib/README.md new file mode 100644 index 00000000..b88cecec --- /dev/null +++ b/contrib/README.md @@ -0,0 +1,3 @@ +This is a contrib folder - software located here is not guarateed to run or be useful - it is not tested in any way, shape or form. + +You can use software located in this folder as a starting point at your own risk. You might find certain software items located in this folder to be valuable. diff --git a/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb similarity index 100% rename from examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb rename to contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb diff --git a/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb similarity index 100% rename from examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb rename to contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb diff --git a/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb similarity index 100% rename from examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb rename to contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb diff --git a/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log b/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log similarity index 100% rename from examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log rename to contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log diff --git a/contrib/imaging/bin/ds b/contrib/imaging/bin/ds deleted file mode 100644 index 5171b1a7..00000000 --- a/contrib/imaging/bin/ds +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -from deepseismic.imaging import cli - -if __name__ == "__main__": - cli.main() diff --git a/contrib/imaging/deepseismic_imaging/__init__.py b/contrib/imaging/deepseismic_imaging/__init__.py deleted file mode 100644 index b063f14b..00000000 --- a/contrib/imaging/deepseismic_imaging/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from deepseismic_imaging import velocity, forward, cli - -__all__ = ["cli", "forward", "velocity"] diff --git a/contrib/imaging/deepseismic_imaging/cli/__init__.py b/contrib/imaging/deepseismic_imaging/cli/__init__.py deleted file mode 100644 index 1497eac6..00000000 --- a/contrib/imaging/deepseismic_imaging/cli/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from functools import partial - -import click - -from . import forward, velocity - -click.option = partial(click.option, show_default=True) - - -@click.group() -@click.pass_context -def cli(ctx): - ctx.ensure_object(dict) - - -cli.add_command(forward.fwd) -cli.add_command(velocity.vp) - - -def main(): - cli(obj={}) diff --git a/contrib/imaging/deepseismic_imaging/cli/forward.py b/contrib/imaging/deepseismic_imaging/cli/forward.py deleted file mode 100644 index 32c05d16..00000000 --- a/contrib/imaging/deepseismic_imaging/cli/forward.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from functools import partial - -import click -import h5py -import numpy as np - -from deepseismic_imaging.forward import Receiver, RickerSource, TimeAxis, VelocityModel - -click.option = partial(click.option, show_default=True) - - -@click.group() -@click.argument("input", type=click.Path()) -@click.argument("output", type=click.Path()) -@click.option( - "-d", "--duration", default=1000.0, type=float, help="Simulation duration (in ms)", -) -@click.option("-dt", default=2.0, type=float, help="Time increment (in ms)") -@click.option("--n-pml", default=10, type=int, help="PML size (in grid points)") -@click.option( - "--n-receivers", default=11, type=int, help="Number of receivers per horizontal dimension", -) -@click.option("--space-order", default=2, type=int, help="Space order") -@click.option("--spacing", default=10.0, type=float, help="Spacing between grid points") -@click.pass_context -def fwd( - ctx, - dt: float, - duration: float, - input: str, - n_pml: int, - n_receivers: int, - output: str, - space_order: int, - spacing: float, -): - """Forward modelling""" - if dt: - ctx.obj["dt"] = dt - ctx.obj["duration"] = duration - ctx.obj["input_file"] = h5py.File(input, mode="r") - ctx.obj["n_pml"] = n_pml - ctx.obj["n_receivers"] = n_receivers - ctx.obj["output_file"] = h5py.File(output, mode="w") - ctx.obj["space_order"] = space_order - ctx.obj["spacing"] = spacing - - -@fwd.command() -@click.option("-f0", default=0.01, type=float, help="Source peak frequency (in kHz)") -@click.pass_context -def ricker(ctx, f0: float): - """Ricker source""" - input_file = ctx.obj["input_file"] - output_file = ctx.obj["output_file"] - n = sum(len(x.values()) for x in input_file.values()) - with click.progressbar(length=n) as bar: - for input_group_name, input_group in input_file.items(): - for dataset in input_group.values(): - first_dataset = dataset - break - model = VelocityModel( - shape=first_dataset.shape, - origin=tuple(0.0 for _ in first_dataset.shape), - spacing=tuple(ctx.obj["spacing"] for _ in first_dataset.shape), - vp=first_dataset[()], - space_order=ctx.obj["space_order"], - n_pml=ctx.obj["n_pml"], - ) - time_range = TimeAxis(start=0.0, stop=ctx.obj["duration"], step=ctx.obj["dt"]) - source = RickerSource(name="source", grid=model.grid, f0=f0, npoint=1, time_range=time_range,) - source.coordinates.data[0, :] = np.array(model.domain_size) * 0.5 - source.coordinates.data[0, -1] = 0.0 - n_receivers = ctx.obj["n_receivers"] - total_receivers = n_receivers ** (len(model.shape) - 1) - receivers = Receiver(name="receivers", grid=model.grid, npoint=total_receivers, time_range=time_range,) - receivers_coords = np.meshgrid( - *(np.linspace(start=0, stop=s, num=n_receivers + 2)[1:-1] for s in model.domain_size[:-1]) - ) - for d in range(len(receivers_coords)): - receivers.coordinates.data[:, d] = receivers_coords[d].flatten() - receivers.coordinates.data[:, -1] = 0.0 - output_group = output_file.create_group(input_group_name) - for input_dataset_name, vp in input_group.items(): - model.vp = vp[()] - seismograms = model.solve(source=source, receivers=receivers, time_range=time_range) - output_group.create_dataset(input_dataset_name, data=seismograms) - bar.update(1) diff --git a/contrib/imaging/deepseismic_imaging/cli/velocity.py b/contrib/imaging/deepseismic_imaging/cli/velocity.py deleted file mode 100644 index f596289b..00000000 --- a/contrib/imaging/deepseismic_imaging/cli/velocity.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from functools import partial -from itertools import islice -from typing import Tuple - -import click -import h5py - -from deepseismic_imaging.velocity import RoethTarantolaGenerator - -click.option = partial(click.option, show_default=True) - - -@click.group() -@click.argument("output", type=click.Path()) -@click.option( - "--append/--no-append", default=False, help="Whether to append to output file", -) -@click.option("-n", default=1, type=int, help="Number of simulations") -@click.option( - "-nx", default=100, type=int, help="Number of grid points along the first dimension", -) -@click.option( - "-ny", default=100, type=int, help="Number of grid points along the second dimension", -) -@click.option("-nz", type=int, help="Number of grid points along the third dimension") -@click.option("-s", "--seed", default=42, type=int, help="Random seed") -@click.pass_context -def vp( - ctx, append: bool, n: int, nx: int, ny: int, nz: int, output: str, seed: int, -): - """Vp simulation""" - shape = (nx, ny) - if nz is not None: - shape += (nz,) - output_file = h5py.File(output, mode=("a" if append else "w")) - output_group = output_file.create_group(str(max((int(x) for x in output_file.keys()), default=-1) + 1)) - ctx.obj["n"] = n - ctx.obj["output_file"] = output_file - ctx.obj["output_group"] = output_group - ctx.obj["seed"] = seed - ctx.obj["shape"] = shape - - -@vp.command() -@click.option("--n-layers", default=8, type=int, help="Number of layers") -@click.option( - "--initial-vp", default=(1350.0, 1650.0), type=(float, float), help="Initial Vp (in km/s)", -) -@click.option( - "--vp-perturbation", default=(-190.0, 570.0), type=(float, float), help="Per-layer Vp perturbation (in km/s)", -) -@click.pass_context -def rt( - ctx, initial_vp: Tuple[float, float], n_layers: int, vp_perturbation: Tuple[float, float], -): - """Röth-Tarantola model""" - model = RoethTarantolaGenerator( - shape=ctx.obj["shape"], - seed=ctx.obj["seed"], - n_layers=n_layers, - initial_vp=initial_vp, - vp_perturbation=vp_perturbation, - ) - group = ctx.obj["output_group"] - with click.progressbar(length=ctx.obj["n"]) as bar: - for i, data in enumerate(islice(model.generate_many(), ctx.obj["n"])): - group.create_dataset(str(i), data=data, compression="gzip") - bar.update(1) diff --git a/contrib/imaging/deepseismic_imaging/forward/__init__.py b/contrib/imaging/deepseismic_imaging/forward/__init__.py deleted file mode 100644 index 8aaed73e..00000000 --- a/contrib/imaging/deepseismic_imaging/forward/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from .models import Model, VelocityModel -from .sources import Receiver, RickerSource, WaveletSource -from .time import TimeAxis -from .types import Kernel - -__all__ = [ - "Kernel", - "Model", - "Receiver", - "RickerSource", - "TimeAxis", - "VelocityModel", - "WaveletSource", -] diff --git a/contrib/imaging/deepseismic_imaging/forward/models.py b/contrib/imaging/deepseismic_imaging/forward/models.py deleted file mode 100644 index e4e850f7..00000000 --- a/contrib/imaging/deepseismic_imaging/forward/models.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional, Tuple, Union - -import numpy as np -from devito import ( - Constant, - Eq, - Function, - Grid, - Operator, - SubDomain, - TimeFunction, - logger, - solve, -) - -from .sources import PointSource -from .subdomains import PhysicalDomain -from .time import TimeAxis -from .types import Kernel - -logger.set_log_level("WARNING") - - -class Model(object): - def __init__( - self, - shape: Tuple[int, ...], - origin: Tuple[float, ...], - spacing: Tuple[float, ...], - n_pml: Optional[int] = 0, - dtype: Optional[type] = np.float32, - subdomains: Optional[Tuple[SubDomain]] = (), - ): - shape = tuple(int(x) for x in shape) - origin = tuple(dtype(x) for x in origin) - n_pml = int(n_pml) - subdomains = tuple(subdomains) + (PhysicalDomain(n_pml),) - shape_pml = tuple(x + 2 * n_pml for x in shape) - extent_pml = tuple(s * (d - 1) for s, d in zip(spacing, shape_pml)) - origin_pml = tuple(dtype(o - s * n_pml) for o, s in zip(origin, spacing)) - self.grid = Grid(shape=shape_pml, extent=extent_pml, origin=origin_pml, dtype=dtype, subdomains=subdomains,) - self.n_pml = n_pml - self.pml = Function(name="pml", grid=self.grid) - pml_data = np.pad(np.zeros(shape, dtype=dtype), [(n_pml,) * 2 for _ in range(self.pml.ndim)], mode="edge",) - pml_coef = 1.5 * np.log(1000.0) / 40.0 - for d in range(self.pml.ndim): - for i in range(n_pml): - pos = np.abs((n_pml - i + 1) / n_pml) - val = pml_coef * (pos - np.sin(2 * np.pi * pos) / (2 * np.pi)) - idx = [slice(0, x) for x in pml_data.shape] - idx[d] = slice(i, i + 1) - pml_data[tuple(idx)] += val / self.grid.spacing[d] - idx[d] = slice(pml_data.shape[d] - i, pml_data.shape[d] - i + 1) - pml_data[tuple(idx)] += val / self.grid.spacing[d] - pml_data = np.pad(pml_data, [(i.left, i.right) for i in self.pml._size_halo], mode="edge",) - self.pml.data_with_halo[:] = pml_data - self.shape = shape - - @property - def domain_size(self) -> Tuple[float, ...]: - return tuple((d - 1) * s for d, s in zip(self.shape, self.spacing)) - - @property - def dtype(self) -> type: - return self.grid.dtype - - @property - def spacing(self): - return self.grid.spacing - - @property - def spacing_map(self): - return self.grid.spacing_map - - @property - def time_spacing(self): - return self.grid.stepping_dim.spacing - - -class VelocityModel(Model): - def __init__( - self, - shape: Tuple[int, ...], - origin: Tuple[float, ...], - spacing: Tuple[float, ...], - vp: Union[float, np.ndarray], - space_order: Optional[int] = None, - n_pml: Optional[int] = 0, - dtype: Optional[type] = np.float32, - subdomains: Optional[Tuple[SubDomain]] = (), - ): - super().__init__(shape, origin, spacing, n_pml, dtype, subdomains) - if isinstance(vp, np.ndarray): - assert space_order is not None - self.m = Function(name="m", grid=self.grid, space_order=int(space_order)) - else: - self.m = Constant(name="m", value=1.0 / float(vp) ** 2.0) - self.vp = vp - - @property - def vp(self) -> Union[float, np.ndarray]: - return self._vp - - @vp.setter - def vp(self, vp: Union[float, np.ndarray]) -> None: - self._vp = vp - if isinstance(vp, np.ndarray): - pad_widths = [(self.n_pml + i.left, self.n_pml + i.right) for i in self.m._size_halo] - self.m.data_with_halo[:] = np.pad(1.0 / self.vp ** 2.0, pad_widths, mode="edge") - else: - self.m.data = 1.0 / float(vp) ** 2.0 - - def solve( - self, - source: PointSource, - receivers: PointSource, - time_range: TimeAxis, - space_order: Optional[int] = 4, - kernel: Optional[Kernel] = Kernel.OT2, - ) -> np.ndarray: - assert isinstance(kernel, Kernel) - u = TimeFunction(name="u", grid=self.grid, time_order=2, space_order=space_order) - H = u.laplace - if kernel is Kernel.OT4: - H += self.time_spacing ** 2 / 12 * u.laplace2(1 / self.m) - eq = Eq(u.forward, solve(self.m * u.dt2 - H + self.pml * u.dt, u.forward)) - src_term = source.inject(field=u.forward, expr=source * self.time_spacing ** 2 / self.m) - rec_term = receivers.interpolate(expr=u) - op = Operator([eq] + src_term + rec_term, subs=self.spacing_map) - op(time=time_range.num - 1, dt=time_range.step) - return receivers.data diff --git a/contrib/imaging/deepseismic_imaging/forward/sources.py b/contrib/imaging/deepseismic_imaging/forward/sources.py deleted file mode 100644 index 4d1e72ea..00000000 --- a/contrib/imaging/deepseismic_imaging/forward/sources.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional - -import numpy as np -import sympy -from devito.types import Dimension, SparseTimeFunction -from devito.types.basic import _SymbolCache -from scipy import interpolate - -from .time import TimeAxis - - -class PointSource(SparseTimeFunction): - def __new__(cls, *args, **kwargs): - if cls in _SymbolCache: - options = kwargs.get("options", {}) - obj = sympy.Function.__new__(cls, *args, **options) - obj._cached_init() - return obj - name = kwargs.pop("name") - grid = kwargs.pop("grid") - time_range = kwargs.pop("time_range") - time_order = kwargs.pop("time_order", 2) - p_dim = kwargs.pop("dimension", Dimension(name="p_%s" % name)) - npoint = kwargs.pop("npoint", None) - coordinates = kwargs.pop("coordinates", kwargs.pop("coordinates_data", None)) - if npoint is None: - assert coordinates is not None, "Either `npoint` or `coordinates` must be provided" - npoint = coordinates.shape[0] - obj = SparseTimeFunction.__new__( - cls, - name=name, - grid=grid, - dimensions=(grid.time_dim, p_dim), - npoint=npoint, - nt=time_range.num, - time_order=time_order, - coordinates=coordinates, - **kwargs - ) - obj._time_range = time_range - data = kwargs.get("data") - if data is not None: - obj.data[:] = data - return obj - - @property - def time_range(self) -> TimeAxis: - return self._time_range - - @property - def time_values(self) -> np.ndarray: - return self._time_range.time_values - - def resample( - self, - dt: Optional[float] = None, - num: Optional[int] = None, - rtol: Optional[float] = 1.0e-5, - order: Optional[int] = 3, - ): - assert (dt is not None) ^ (num is not None), "Exactly one of `dt` or `num` must be provided" - start = self._time_range.start - stop = self._time_range.stop - dt0 = self._time_range.step - if dt is not None: - new_time_range = TimeAxis(start=start, stop=stop, step=dt) - else: - new_time_range = TimeAxis(start=start, stop=stop, num=num) - dt = new_time_range.step - if np.isclose(dt0, dt, rtol=rtol): - return self - n_traces = self.data.shape[1] - new_traces = np.zeros((new_time_range.num, n_traces), dtype=self.data.dtype) - for j in range(n_traces): - tck = interpolate.splrep(self._time_range.time_values, self.data[:, j], k=order) - new_traces[:, j] = interpolate.splev(new_time_range.time_values, tck) - return PointSource( - name=self.name, - grid=self.grid, - time_range=new_time_range, - coordinates=self.coordinates.data, - data=new_traces, - ) - - _pickle_kwargs = SparseTimeFunction._pickle_kwargs + ["time_range"] - _pickle_kwargs.remove("nt") # Inferred from time_range - - -class Receiver(PointSource): - pass - - -class WaveletSource(PointSource): - def __new__(cls, *args, **kwargs): - if cls in _SymbolCache: - options = kwargs.get("options", {}) - obj = sympy.Function.__new__(cls, *args, **options) - obj._cached_init() - return obj - npoint = kwargs.pop("npoint", 1) - obj = PointSource.__new__(cls, npoint=npoint, **kwargs) - obj.f0 = kwargs.get("f0") - for p in range(npoint): - obj.data[:, p] = obj.wavelet(obj.f0, obj.time_values) - return obj - - def __init__(self, *args, **kwargs): - if not self._cached(): - super(WaveletSource, self).__init__(*args, **kwargs) - - def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: - raise NotImplementedError - - _pickle_kwargs = PointSource._pickle_kwargs + ["f0"] - - -class RickerSource(WaveletSource): - def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: - r = np.pi * f0 * (t - 1.0 / f0) - return (1.0 - 2.0 * r ** 2.0) * np.exp(-(r ** 2.0)) diff --git a/contrib/imaging/deepseismic_imaging/forward/subdomains.py b/contrib/imaging/deepseismic_imaging/forward/subdomains.py deleted file mode 100644 index de209494..00000000 --- a/contrib/imaging/deepseismic_imaging/forward/subdomains.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Dict, Iterable, Tuple - -from devito import Dimension, SubDomain - - -class PhysicalDomain(SubDomain): - name = "physical_domain" - - def __init__(self, n_pml: int): - super().__init__() - self.n_pml = n_pml - - def define(self, dimensions: Iterable[Dimension]) -> Dict[Dimension, Tuple[str, int, int]]: - return {d: ("middle", self.n_pml, self.n_pml) for d in dimensions} diff --git a/contrib/imaging/deepseismic_imaging/forward/time.py b/contrib/imaging/deepseismic_imaging/forward/time.py deleted file mode 100644 index 7ce51d5d..00000000 --- a/contrib/imaging/deepseismic_imaging/forward/time.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional - -import numpy as np - - -class TimeAxis(object): - def __init__( - self, - start: Optional[float] = None, - stop: Optional[float] = None, - num: Optional[int] = None, - step: Optional[float] = None, - dtype: Optional[type] = np.float32, - ): - if start is None: - start = step * (1 - num) + stop - elif stop is None: - stop = step * (num - 1) + start - elif num is None: - num = int(np.ceil((stop - start + step) / step)) - stop = step * (num - 1) + start - elif step is None: - step = (stop - start) / (num - 1) - else: - raise ValueError - self.start = start - self.stop = stop - self.num = num - self.step = step - self.dtype = dtype - - @property - def time_values(self) -> np.ndarray: - return np.linspace(self.start, self.stop, self.num, dtype=self.dtype) diff --git a/contrib/imaging/deepseismic_imaging/forward/types.py b/contrib/imaging/deepseismic_imaging/forward/types.py deleted file mode 100644 index 3b4fe2ac..00000000 --- a/contrib/imaging/deepseismic_imaging/forward/types.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from enum import Enum, auto - - -class Kernel(Enum): - OT2 = auto() - OT4 = auto() diff --git a/contrib/imaging/deepseismic_imaging/velocity/__init__.py b/contrib/imaging/deepseismic_imaging/velocity/__init__.py deleted file mode 100644 index 34ef3009..00000000 --- a/contrib/imaging/deepseismic_imaging/velocity/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from .generator import Generator -from .roeth_tarantola import RoethTarantolaGenerator - -__all__ = ["Generator", "RoethTarantolaGenerator"] diff --git a/contrib/imaging/deepseismic_imaging/velocity/generator.py b/contrib/imaging/deepseismic_imaging/velocity/generator.py deleted file mode 100644 index fd4deba2..00000000 --- a/contrib/imaging/deepseismic_imaging/velocity/generator.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional, Tuple - -import numpy as np - - -class Generator(object): - def __init__( - self, shape: Tuple[int, ...], dtype: Optional[type] = np.float32, seed: Optional[int] = None, - ): - self.shape = shape - self.dtype = dtype - self._prng = np.random.RandomState(seed) - - def generate(self) -> np.ndarray: - raise NotImplementedError - - def generate_many(self) -> np.ndarray: - while True: - yield self.generate() diff --git a/contrib/imaging/deepseismic_imaging/velocity/roeth_tarantola.py b/contrib/imaging/deepseismic_imaging/velocity/roeth_tarantola.py deleted file mode 100644 index 78c04536..00000000 --- a/contrib/imaging/deepseismic_imaging/velocity/roeth_tarantola.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional, Tuple - -import numpy as np - -from .generator import Generator - - -class RoethTarantolaGenerator(Generator): - def __init__( - self, - shape: Tuple[int, ...], - dtype: Optional[type] = np.float32, - seed: Optional[int] = None, - depth_dim: Optional[int] = -1, - n_layers: Optional[int] = 8, - initial_vp: Optional[Tuple[float, float]] = (1.35, 1.65), - vp_perturbation: Optional[Tuple[float, float]] = (-0.19, 0.57), - ): - super().__init__(shape, dtype, seed) - self.depth_dim = depth_dim - self.n_layers = n_layers - self.initial_vp = initial_vp - self.vp_perturbation = vp_perturbation - - def generate(self) -> np.ndarray: - vp = np.zeros(self.shape, dtype=self.dtype) - dim = self.depth_dim - layer_idx = np.round(np.linspace(0, self.shape[dim], self.n_layers + 1)).astype(np.int) - vp_idx = [slice(0, x) for x in vp.shape] - layer_vp = None - for i in range(self.n_layers): - vp_idx[dim] = slice(layer_idx[i], layer_idx[i + 1]) - layer_vp = ( - self._prng.uniform(*self.initial_vp) - if layer_vp is None - else layer_vp + self._prng.uniform(*self.vp_perturbation) - ) - vp[tuple(vp_idx)] = layer_vp - return vp diff --git a/contrib/imaging/requirements.txt b/contrib/imaging/requirements.txt deleted file mode 100644 index 9983a4db..00000000 --- a/contrib/imaging/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -click==7.0 -devito==3.5 -h5py==2.9.0 -numpy==1.17.0 -scipy==1.3.0 -sympy==1.4 \ No newline at end of file diff --git a/contrib/imaging/setup.cfg b/contrib/imaging/setup.cfg deleted file mode 100644 index b7e47898..00000000 --- a/contrib/imaging/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[aliases] -test=pytest diff --git a/contrib/imaging/setup.py b/contrib/imaging/setup.py deleted file mode 100644 index 4b198c96..00000000 --- a/contrib/imaging/setup.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -import setuptools - -with open("../README.md", "r") as f: - long_description = f.read() - -with open("requirements.txt") as f: - requirements = f.read().splitlines() - -setuptools.setup( - author="DeepSeismic Maintainers", - author_email="deepseismic@microsoft.com", - classifiers=[ - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Topic :: Scientific/Engineering", - "Topic :: Software Development", - ], - dependency_links=["https://github.com/opesci/devito/archive/v3.5.tar.gz#egg=devito-3.5"], - description="DeepSeismic", - install_requires=requirements, - license="MIT", - long_description=long_description, - long_description_content_type="text/markdown", - name="deepseismic_imaging", - packages=setuptools.find_packages(include=["deepseismic_imaging", "deepseismic_imaging.*"]), - platforms="any", - python_requires=">= 3.6", - scripts=["bin/ds"], - setup_requires=["pytest-runner"], - tests_require=["pytest"], - url="https://github.com/microsoft/deepseismic", - version="0.1.0", - zip_safe=False, -) diff --git a/deepseismic/__init__.py b/deepseismic/__init__.py deleted file mode 100644 index 0f06c3a5..00000000 --- a/deepseismic/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from . import cli, forward, velocity - -__all__ = ["cli", "forward", "velocity"] diff --git a/deepseismic/cli/__init__.py b/deepseismic/cli/__init__.py deleted file mode 100644 index 1497eac6..00000000 --- a/deepseismic/cli/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from functools import partial - -import click - -from . import forward, velocity - -click.option = partial(click.option, show_default=True) - - -@click.group() -@click.pass_context -def cli(ctx): - ctx.ensure_object(dict) - - -cli.add_command(forward.fwd) -cli.add_command(velocity.vp) - - -def main(): - cli(obj={}) diff --git a/deepseismic/cli/forward.py b/deepseismic/cli/forward.py deleted file mode 100644 index 16881f41..00000000 --- a/deepseismic/cli/forward.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from functools import partial - -import click -import h5py -import numpy as np - -from ..forward import Receiver, RickerSource, TimeAxis, VelocityModel - -click.option = partial(click.option, show_default=True) - - -@click.group() -@click.argument("input", type=click.Path()) -@click.argument("output", type=click.Path()) -@click.option( - "-d", "--duration", default=1000.0, type=float, help="Simulation duration (in ms)", -) -@click.option("-dt", default=2.0, type=float, help="Time increment (in ms)") -@click.option("--n-pml", default=10, type=int, help="PML size (in grid points)") -@click.option( - "--n-receivers", default=11, type=int, help="Number of receivers per horizontal dimension", -) -@click.option("--space-order", default=2, type=int, help="Space order") -@click.option("--spacing", default=10.0, type=float, help="Spacing between grid points") -@click.pass_context -def fwd( - ctx, - dt: float, - duration: float, - input: str, - n_pml: int, - n_receivers: int, - output: str, - space_order: int, - spacing: float, -): - """Forward modelling""" - if dt: - ctx.obj["dt"] = dt - ctx.obj["duration"] = duration - ctx.obj["input_file"] = h5py.File(input, mode="r") - ctx.obj["n_pml"] = n_pml - ctx.obj["n_receivers"] = n_receivers - ctx.obj["output_file"] = h5py.File(output, mode="w") - ctx.obj["space_order"] = space_order - ctx.obj["spacing"] = spacing - - -@fwd.command() -@click.option("-f0", default=0.01, type=float, help="Source peak frequency (in kHz)") -@click.pass_context -def ricker(ctx, f0: float): - """Ricker source""" - input_file = ctx.obj["input_file"] - output_file = ctx.obj["output_file"] - n = sum(len(x.values()) for x in input_file.values()) - with click.progressbar(length=n) as bar: - for input_group_name, input_group in input_file.items(): - for dataset in input_group.values(): - first_dataset = dataset - break - model = VelocityModel( - shape=first_dataset.shape, - origin=tuple(0.0 for _ in first_dataset.shape), - spacing=tuple(ctx.obj["spacing"] for _ in first_dataset.shape), - vp=first_dataset[()], - space_order=ctx.obj["space_order"], - n_pml=ctx.obj["n_pml"], - ) - time_range = TimeAxis(start=0.0, stop=ctx.obj["duration"], step=ctx.obj["dt"]) - source = RickerSource(name="source", grid=model.grid, f0=f0, npoint=1, time_range=time_range,) - source.coordinates.data[0, :] = np.array(model.domain_size) * 0.5 - source.coordinates.data[0, -1] = 0.0 - n_receivers = ctx.obj["n_receivers"] - total_receivers = n_receivers ** (len(model.shape) - 1) - receivers = Receiver(name="receivers", grid=model.grid, npoint=total_receivers, time_range=time_range,) - receivers_coords = np.meshgrid( - *(np.linspace(start=0, stop=s, num=n_receivers + 2)[1:-1] for s in model.domain_size[:-1]) - ) - for d in range(len(receivers_coords)): - receivers.coordinates.data[:, d] = receivers_coords[d].flatten() - receivers.coordinates.data[:, -1] = 0.0 - output_group = output_file.create_group(input_group_name) - for input_dataset_name, vp in input_group.items(): - model.vp = vp[()] - seismograms = model.solve(source=source, receivers=receivers, time_range=time_range) - output_group.create_dataset(input_dataset_name, data=seismograms) - bar.update(1) diff --git a/deepseismic/cli/velocity.py b/deepseismic/cli/velocity.py deleted file mode 100644 index c5ee0d3c..00000000 --- a/deepseismic/cli/velocity.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from functools import partial -from itertools import islice -from typing import Tuple - -import click -import h5py - -from ..velocity import RoethTarantolaGenerator - -click.option = partial(click.option, show_default=True) - - -@click.group() -@click.argument("output", type=click.Path()) -@click.option( - "--append/--no-append", default=False, help="Whether to append to output file", -) -@click.option("-n", default=1, type=int, help="Number of simulations") -@click.option( - "-nx", default=100, type=int, help="Number of grid points along the first dimension", -) -@click.option( - "-ny", default=100, type=int, help="Number of grid points along the second dimension", -) -@click.option("-nz", type=int, help="Number of grid points along the third dimension") -@click.option("-s", "--seed", default=42, type=int, help="Random seed") -@click.pass_context -def vp( - ctx, append: bool, n: int, nx: int, ny: int, nz: int, output: str, seed: int, -): - """Vp simulation""" - shape = (nx, ny) - if nz is not None: - shape += (nz,) - output_file = h5py.File(output, mode=("a" if append else "w")) - output_group = output_file.create_group(str(max((int(x) for x in output_file.keys()), default=-1) + 1)) - ctx.obj["n"] = n - ctx.obj["output_file"] = output_file - ctx.obj["output_group"] = output_group - ctx.obj["seed"] = seed - ctx.obj["shape"] = shape - - -@vp.command() -@click.option("--n-layers", default=8, type=int, help="Number of layers") -@click.option( - "--initial-vp", default=(1350.0, 1650.0), type=(float, float), help="Initial Vp (in km/s)", -) -@click.option( - "--vp-perturbation", default=(-190.0, 570.0), type=(float, float), help="Per-layer Vp perturbation (in km/s)", -) -@click.pass_context -def rt( - ctx, initial_vp: Tuple[float, float], n_layers: int, vp_perturbation: Tuple[float, float], -): - """Röth-Tarantola model""" - model = RoethTarantolaGenerator( - shape=ctx.obj["shape"], - seed=ctx.obj["seed"], - n_layers=n_layers, - initial_vp=initial_vp, - vp_perturbation=vp_perturbation, - ) - group = ctx.obj["output_group"] - with click.progressbar(length=ctx.obj["n"]) as bar: - for i, data in enumerate(islice(model.generate_many(), ctx.obj["n"])): - group.create_dataset(str(i), data=data, compression="gzip") - bar.update(1) diff --git a/deepseismic/forward/__init__.py b/deepseismic/forward/__init__.py deleted file mode 100644 index 8aaed73e..00000000 --- a/deepseismic/forward/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from .models import Model, VelocityModel -from .sources import Receiver, RickerSource, WaveletSource -from .time import TimeAxis -from .types import Kernel - -__all__ = [ - "Kernel", - "Model", - "Receiver", - "RickerSource", - "TimeAxis", - "VelocityModel", - "WaveletSource", -] diff --git a/deepseismic/forward/models.py b/deepseismic/forward/models.py deleted file mode 100644 index e4e850f7..00000000 --- a/deepseismic/forward/models.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional, Tuple, Union - -import numpy as np -from devito import ( - Constant, - Eq, - Function, - Grid, - Operator, - SubDomain, - TimeFunction, - logger, - solve, -) - -from .sources import PointSource -from .subdomains import PhysicalDomain -from .time import TimeAxis -from .types import Kernel - -logger.set_log_level("WARNING") - - -class Model(object): - def __init__( - self, - shape: Tuple[int, ...], - origin: Tuple[float, ...], - spacing: Tuple[float, ...], - n_pml: Optional[int] = 0, - dtype: Optional[type] = np.float32, - subdomains: Optional[Tuple[SubDomain]] = (), - ): - shape = tuple(int(x) for x in shape) - origin = tuple(dtype(x) for x in origin) - n_pml = int(n_pml) - subdomains = tuple(subdomains) + (PhysicalDomain(n_pml),) - shape_pml = tuple(x + 2 * n_pml for x in shape) - extent_pml = tuple(s * (d - 1) for s, d in zip(spacing, shape_pml)) - origin_pml = tuple(dtype(o - s * n_pml) for o, s in zip(origin, spacing)) - self.grid = Grid(shape=shape_pml, extent=extent_pml, origin=origin_pml, dtype=dtype, subdomains=subdomains,) - self.n_pml = n_pml - self.pml = Function(name="pml", grid=self.grid) - pml_data = np.pad(np.zeros(shape, dtype=dtype), [(n_pml,) * 2 for _ in range(self.pml.ndim)], mode="edge",) - pml_coef = 1.5 * np.log(1000.0) / 40.0 - for d in range(self.pml.ndim): - for i in range(n_pml): - pos = np.abs((n_pml - i + 1) / n_pml) - val = pml_coef * (pos - np.sin(2 * np.pi * pos) / (2 * np.pi)) - idx = [slice(0, x) for x in pml_data.shape] - idx[d] = slice(i, i + 1) - pml_data[tuple(idx)] += val / self.grid.spacing[d] - idx[d] = slice(pml_data.shape[d] - i, pml_data.shape[d] - i + 1) - pml_data[tuple(idx)] += val / self.grid.spacing[d] - pml_data = np.pad(pml_data, [(i.left, i.right) for i in self.pml._size_halo], mode="edge",) - self.pml.data_with_halo[:] = pml_data - self.shape = shape - - @property - def domain_size(self) -> Tuple[float, ...]: - return tuple((d - 1) * s for d, s in zip(self.shape, self.spacing)) - - @property - def dtype(self) -> type: - return self.grid.dtype - - @property - def spacing(self): - return self.grid.spacing - - @property - def spacing_map(self): - return self.grid.spacing_map - - @property - def time_spacing(self): - return self.grid.stepping_dim.spacing - - -class VelocityModel(Model): - def __init__( - self, - shape: Tuple[int, ...], - origin: Tuple[float, ...], - spacing: Tuple[float, ...], - vp: Union[float, np.ndarray], - space_order: Optional[int] = None, - n_pml: Optional[int] = 0, - dtype: Optional[type] = np.float32, - subdomains: Optional[Tuple[SubDomain]] = (), - ): - super().__init__(shape, origin, spacing, n_pml, dtype, subdomains) - if isinstance(vp, np.ndarray): - assert space_order is not None - self.m = Function(name="m", grid=self.grid, space_order=int(space_order)) - else: - self.m = Constant(name="m", value=1.0 / float(vp) ** 2.0) - self.vp = vp - - @property - def vp(self) -> Union[float, np.ndarray]: - return self._vp - - @vp.setter - def vp(self, vp: Union[float, np.ndarray]) -> None: - self._vp = vp - if isinstance(vp, np.ndarray): - pad_widths = [(self.n_pml + i.left, self.n_pml + i.right) for i in self.m._size_halo] - self.m.data_with_halo[:] = np.pad(1.0 / self.vp ** 2.0, pad_widths, mode="edge") - else: - self.m.data = 1.0 / float(vp) ** 2.0 - - def solve( - self, - source: PointSource, - receivers: PointSource, - time_range: TimeAxis, - space_order: Optional[int] = 4, - kernel: Optional[Kernel] = Kernel.OT2, - ) -> np.ndarray: - assert isinstance(kernel, Kernel) - u = TimeFunction(name="u", grid=self.grid, time_order=2, space_order=space_order) - H = u.laplace - if kernel is Kernel.OT4: - H += self.time_spacing ** 2 / 12 * u.laplace2(1 / self.m) - eq = Eq(u.forward, solve(self.m * u.dt2 - H + self.pml * u.dt, u.forward)) - src_term = source.inject(field=u.forward, expr=source * self.time_spacing ** 2 / self.m) - rec_term = receivers.interpolate(expr=u) - op = Operator([eq] + src_term + rec_term, subs=self.spacing_map) - op(time=time_range.num - 1, dt=time_range.step) - return receivers.data diff --git a/deepseismic/forward/sources.py b/deepseismic/forward/sources.py deleted file mode 100644 index 4d1e72ea..00000000 --- a/deepseismic/forward/sources.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional - -import numpy as np -import sympy -from devito.types import Dimension, SparseTimeFunction -from devito.types.basic import _SymbolCache -from scipy import interpolate - -from .time import TimeAxis - - -class PointSource(SparseTimeFunction): - def __new__(cls, *args, **kwargs): - if cls in _SymbolCache: - options = kwargs.get("options", {}) - obj = sympy.Function.__new__(cls, *args, **options) - obj._cached_init() - return obj - name = kwargs.pop("name") - grid = kwargs.pop("grid") - time_range = kwargs.pop("time_range") - time_order = kwargs.pop("time_order", 2) - p_dim = kwargs.pop("dimension", Dimension(name="p_%s" % name)) - npoint = kwargs.pop("npoint", None) - coordinates = kwargs.pop("coordinates", kwargs.pop("coordinates_data", None)) - if npoint is None: - assert coordinates is not None, "Either `npoint` or `coordinates` must be provided" - npoint = coordinates.shape[0] - obj = SparseTimeFunction.__new__( - cls, - name=name, - grid=grid, - dimensions=(grid.time_dim, p_dim), - npoint=npoint, - nt=time_range.num, - time_order=time_order, - coordinates=coordinates, - **kwargs - ) - obj._time_range = time_range - data = kwargs.get("data") - if data is not None: - obj.data[:] = data - return obj - - @property - def time_range(self) -> TimeAxis: - return self._time_range - - @property - def time_values(self) -> np.ndarray: - return self._time_range.time_values - - def resample( - self, - dt: Optional[float] = None, - num: Optional[int] = None, - rtol: Optional[float] = 1.0e-5, - order: Optional[int] = 3, - ): - assert (dt is not None) ^ (num is not None), "Exactly one of `dt` or `num` must be provided" - start = self._time_range.start - stop = self._time_range.stop - dt0 = self._time_range.step - if dt is not None: - new_time_range = TimeAxis(start=start, stop=stop, step=dt) - else: - new_time_range = TimeAxis(start=start, stop=stop, num=num) - dt = new_time_range.step - if np.isclose(dt0, dt, rtol=rtol): - return self - n_traces = self.data.shape[1] - new_traces = np.zeros((new_time_range.num, n_traces), dtype=self.data.dtype) - for j in range(n_traces): - tck = interpolate.splrep(self._time_range.time_values, self.data[:, j], k=order) - new_traces[:, j] = interpolate.splev(new_time_range.time_values, tck) - return PointSource( - name=self.name, - grid=self.grid, - time_range=new_time_range, - coordinates=self.coordinates.data, - data=new_traces, - ) - - _pickle_kwargs = SparseTimeFunction._pickle_kwargs + ["time_range"] - _pickle_kwargs.remove("nt") # Inferred from time_range - - -class Receiver(PointSource): - pass - - -class WaveletSource(PointSource): - def __new__(cls, *args, **kwargs): - if cls in _SymbolCache: - options = kwargs.get("options", {}) - obj = sympy.Function.__new__(cls, *args, **options) - obj._cached_init() - return obj - npoint = kwargs.pop("npoint", 1) - obj = PointSource.__new__(cls, npoint=npoint, **kwargs) - obj.f0 = kwargs.get("f0") - for p in range(npoint): - obj.data[:, p] = obj.wavelet(obj.f0, obj.time_values) - return obj - - def __init__(self, *args, **kwargs): - if not self._cached(): - super(WaveletSource, self).__init__(*args, **kwargs) - - def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: - raise NotImplementedError - - _pickle_kwargs = PointSource._pickle_kwargs + ["f0"] - - -class RickerSource(WaveletSource): - def wavelet(self, f0: float, t: np.ndarray) -> np.ndarray: - r = np.pi * f0 * (t - 1.0 / f0) - return (1.0 - 2.0 * r ** 2.0) * np.exp(-(r ** 2.0)) diff --git a/deepseismic/forward/subdomains.py b/deepseismic/forward/subdomains.py deleted file mode 100644 index de209494..00000000 --- a/deepseismic/forward/subdomains.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Dict, Iterable, Tuple - -from devito import Dimension, SubDomain - - -class PhysicalDomain(SubDomain): - name = "physical_domain" - - def __init__(self, n_pml: int): - super().__init__() - self.n_pml = n_pml - - def define(self, dimensions: Iterable[Dimension]) -> Dict[Dimension, Tuple[str, int, int]]: - return {d: ("middle", self.n_pml, self.n_pml) for d in dimensions} diff --git a/deepseismic/forward/time.py b/deepseismic/forward/time.py deleted file mode 100644 index 7ce51d5d..00000000 --- a/deepseismic/forward/time.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional - -import numpy as np - - -class TimeAxis(object): - def __init__( - self, - start: Optional[float] = None, - stop: Optional[float] = None, - num: Optional[int] = None, - step: Optional[float] = None, - dtype: Optional[type] = np.float32, - ): - if start is None: - start = step * (1 - num) + stop - elif stop is None: - stop = step * (num - 1) + start - elif num is None: - num = int(np.ceil((stop - start + step) / step)) - stop = step * (num - 1) + start - elif step is None: - step = (stop - start) / (num - 1) - else: - raise ValueError - self.start = start - self.stop = stop - self.num = num - self.step = step - self.dtype = dtype - - @property - def time_values(self) -> np.ndarray: - return np.linspace(self.start, self.stop, self.num, dtype=self.dtype) diff --git a/deepseismic/forward/types.py b/deepseismic/forward/types.py deleted file mode 100644 index 3b4fe2ac..00000000 --- a/deepseismic/forward/types.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from enum import Enum, auto - - -class Kernel(Enum): - OT2 = auto() - OT4 = auto() diff --git a/deepseismic/velocity/__init__.py b/deepseismic/velocity/__init__.py deleted file mode 100644 index 34ef3009..00000000 --- a/deepseismic/velocity/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from .generator import Generator -from .roeth_tarantola import RoethTarantolaGenerator - -__all__ = ["Generator", "RoethTarantolaGenerator"] diff --git a/deepseismic/velocity/generator.py b/deepseismic/velocity/generator.py deleted file mode 100644 index fd4deba2..00000000 --- a/deepseismic/velocity/generator.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional, Tuple - -import numpy as np - - -class Generator(object): - def __init__( - self, shape: Tuple[int, ...], dtype: Optional[type] = np.float32, seed: Optional[int] = None, - ): - self.shape = shape - self.dtype = dtype - self._prng = np.random.RandomState(seed) - - def generate(self) -> np.ndarray: - raise NotImplementedError - - def generate_many(self) -> np.ndarray: - while True: - yield self.generate() diff --git a/deepseismic/velocity/roeth_tarantola.py b/deepseismic/velocity/roeth_tarantola.py deleted file mode 100644 index 78c04536..00000000 --- a/deepseismic/velocity/roeth_tarantola.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from typing import Optional, Tuple - -import numpy as np - -from .generator import Generator - - -class RoethTarantolaGenerator(Generator): - def __init__( - self, - shape: Tuple[int, ...], - dtype: Optional[type] = np.float32, - seed: Optional[int] = None, - depth_dim: Optional[int] = -1, - n_layers: Optional[int] = 8, - initial_vp: Optional[Tuple[float, float]] = (1.35, 1.65), - vp_perturbation: Optional[Tuple[float, float]] = (-0.19, 0.57), - ): - super().__init__(shape, dtype, seed) - self.depth_dim = depth_dim - self.n_layers = n_layers - self.initial_vp = initial_vp - self.vp_perturbation = vp_perturbation - - def generate(self) -> np.ndarray: - vp = np.zeros(self.shape, dtype=self.dtype) - dim = self.depth_dim - layer_idx = np.round(np.linspace(0, self.shape[dim], self.n_layers + 1)).astype(np.int) - vp_idx = [slice(0, x) for x in vp.shape] - layer_vp = None - for i in range(self.n_layers): - vp_idx[dim] = slice(layer_idx[i], layer_idx[i + 1]) - layer_vp = ( - self._prng.uniform(*self.initial_vp) - if layer_vp is None - else layer_vp + self._prng.uniform(*self.vp_perturbation) - ) - vp[tuple(vp_idx)] = layer_vp - return vp From 9f3e80015dda65be1ca45929f5f7436f38f0fd7d Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Tue, 26 Nov 2019 16:04:10 -0500 Subject: [PATCH 095/207] Create Unit Test Build.yml (#29) Adding Unit Test Build. --- .ci/steps/setup_step.yml | 22 ++++++++++++++ .ci/steps/unit_test_steps.yml | 18 ++++++++++++ .ci/unit_test_build.yml | 28 ++++++++++++++++++ environment/anaconda/local/environment.yml | 34 ++++++++++------------ 4 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 .ci/steps/setup_step.yml create mode 100644 .ci/steps/unit_test_steps.yml create mode 100644 .ci/unit_test_build.yml diff --git a/.ci/steps/setup_step.yml b/.ci/steps/setup_step.yml new file mode 100644 index 00000000..6a51a9d0 --- /dev/null +++ b/.ci/steps/setup_step.yml @@ -0,0 +1,22 @@ + +parameters: + storagename: # + storagekey: # + conda: seismic-interpretation + +steps: +- bash: | + echo "Running setup..." + + # make sure we have the latest and greatest + conda env create -f environment/anaconda/local/environment.yml --force + source activate ${{parameters.conda}} + pip install -e interpretation + pip install -e cv_lib + # add this if pytorch stops detecting GPU + # conda install pytorch torchvision cudatoolkit=9.2 -c pytorch + + # copy your model files like so - using dummy file to illustrate + azcopy --quiet --source:https://${{parameters.storagename}}.blob.core.windows.net/models/model --source-key ${{parameters.storagekey}} --destination ./models/your_model_name + displayName: Setup + failOnStderr: True diff --git a/.ci/steps/unit_test_steps.yml b/.ci/steps/unit_test_steps.yml new file mode 100644 index 00000000..ea06da5c --- /dev/null +++ b/.ci/steps/unit_test_steps.yml @@ -0,0 +1,18 @@ +parameters: + conda: seismic-interpretation + +steps: + - bash: | + echo "Starting unit tests" + source activate ${{parameters.conda}} + pytest --durations=0 --junitxml 'reports/test-unit.xml' cv_lib/tests/ + echo "Unit test job passed" + displayName: Unit Tests Job + failOnStderr: True + + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/test-*.xml' + inputs: + testResultsFiles: '**/test-*.xml' + failTaskOnFailedTests: true + condition: succeededOrFailed() diff --git a/.ci/unit_test_build.yml b/.ci/unit_test_build.yml new file mode 100644 index 00000000..ea7701de --- /dev/null +++ b/.ci/unit_test_build.yml @@ -0,0 +1,28 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Pull request against these branches will trigger this build +pr: +- master +- staging + +# Any commit to this branch will trigger the build. +trigger: +- master +- staging + +jobs: +# partially disable setup for now - done manually on build VM +- job: DeepSeismic + + displayName: Deep Seismic Main Build + pool: + name: $(AgentName) + + steps: + - template: steps/setup_step.yml + parameters: + storagename: $(storageaccoutname) + storagekey: $(storagekey) + + - template: steps/unit_test_steps.yml diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index fad5040e..29a5b3b4 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -5,34 +5,32 @@ channels: dependencies: - python=3.6 - pip - - pytorch==1.2.0 - - cudatoolkit==9.2 + - pytorch==1.3.1 + - cudatoolkit==10.1.243 - jupyter - ipykernel - - torchvision==0.4.0 - - pandas==0.24.2 - - opencv==4.1.1 - - scikit-learn==0.21.3 + - torchvision==0.4.2 + - pandas==0.25.3 + - opencv==4.1.2 + - scikit-learn=0.21.3 - tensorflow==2.0 - - tqdm==4.38.0 - - itkwidgets==0.22.0 + - tqdm==4.39.0 + - itkwidgets==0.23.1 - pip: - segyio==1.8.8 - pytorch-ignite==0.3.0.dev20191105 # pre-release until stable available - fire==0.2.1 - - toolz==0.9.0 + - toolz==0.10.0 - tabulate==0.8.2 - - jinja2==2.10.3 - - gitpython==3.0.4 - - tensortensorboard==2.0.0 - - tensorboardx==1.8 - - tqdm==4.38.0 - - invoke + - Jinja2==2.10.3 + - gitpython==3.0.5 + - tensorboard==2.0.1 + - tensorboardx==1.9 + - invoke==1.3.0 - yacs==0.1.6 - - albumentations==0.4.2 + - albumentations==0.4.3 - black - pylint - scipy==1.1.0 - - jupytext==1.2.4 + - jupytext==1.3.0 - pytest - From 7065e9c8878d1b25ed5adb4371278c3089441f25 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Tue, 26 Nov 2019 18:28:13 -0500 Subject: [PATCH 096/207] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7375711d..7f3d7b2a 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | | **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | -# Related projects +## Related projects + +[Microsoft AI Github](https://github.com/microsoft/ai) Find other Best Practice projects, and Azure AI Designed patterns in our central repository. -[Microsoft AI Labs Github](https://aka.ms/ai-labs) Find other Best Practice projects, and Azure AI design patterns in our central repository. From d173630ba96e06bdb99f008030e473ee959809b2 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Tue, 26 Nov 2019 18:28:42 -0500 Subject: [PATCH 097/207] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 7f3d7b2a..694713ae 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,6 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope ## Build Status | Build | Branch | Status | | --- | --- | --- | - | **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=staging) | | **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=master) | | **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=staging) | From 99d78ecf700f0bcd3725da99766c597c6dc3a4dc Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Tue, 26 Nov 2019 16:04:10 -0500 Subject: [PATCH 098/207] Create Unit Test Build.yml (#29) Adding Unit Test Build. Update README.md Update README.md --- .ci/steps/setup_step.yml | 22 ++++++++++++++ .ci/steps/unit_test_steps.yml | 18 ++++++++++++ .ci/unit_test_build.yml | 28 ++++++++++++++++++ README.md | 6 ++-- environment/anaconda/local/environment.yml | 34 ++++++++++------------ 5 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 .ci/steps/setup_step.yml create mode 100644 .ci/steps/unit_test_steps.yml create mode 100644 .ci/unit_test_build.yml diff --git a/.ci/steps/setup_step.yml b/.ci/steps/setup_step.yml new file mode 100644 index 00000000..6a51a9d0 --- /dev/null +++ b/.ci/steps/setup_step.yml @@ -0,0 +1,22 @@ + +parameters: + storagename: # + storagekey: # + conda: seismic-interpretation + +steps: +- bash: | + echo "Running setup..." + + # make sure we have the latest and greatest + conda env create -f environment/anaconda/local/environment.yml --force + source activate ${{parameters.conda}} + pip install -e interpretation + pip install -e cv_lib + # add this if pytorch stops detecting GPU + # conda install pytorch torchvision cudatoolkit=9.2 -c pytorch + + # copy your model files like so - using dummy file to illustrate + azcopy --quiet --source:https://${{parameters.storagename}}.blob.core.windows.net/models/model --source-key ${{parameters.storagekey}} --destination ./models/your_model_name + displayName: Setup + failOnStderr: True diff --git a/.ci/steps/unit_test_steps.yml b/.ci/steps/unit_test_steps.yml new file mode 100644 index 00000000..ea06da5c --- /dev/null +++ b/.ci/steps/unit_test_steps.yml @@ -0,0 +1,18 @@ +parameters: + conda: seismic-interpretation + +steps: + - bash: | + echo "Starting unit tests" + source activate ${{parameters.conda}} + pytest --durations=0 --junitxml 'reports/test-unit.xml' cv_lib/tests/ + echo "Unit test job passed" + displayName: Unit Tests Job + failOnStderr: True + + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/test-*.xml' + inputs: + testResultsFiles: '**/test-*.xml' + failTaskOnFailedTests: true + condition: succeededOrFailed() diff --git a/.ci/unit_test_build.yml b/.ci/unit_test_build.yml new file mode 100644 index 00000000..ea7701de --- /dev/null +++ b/.ci/unit_test_build.yml @@ -0,0 +1,28 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Pull request against these branches will trigger this build +pr: +- master +- staging + +# Any commit to this branch will trigger the build. +trigger: +- master +- staging + +jobs: +# partially disable setup for now - done manually on build VM +- job: DeepSeismic + + displayName: Deep Seismic Main Build + pool: + name: $(AgentName) + + steps: + - template: steps/setup_step.yml + parameters: + storagename: $(storageaccoutname) + storagekey: $(storagekey) + + - template: steps/unit_test_steps.yml diff --git a/README.md b/README.md index 7375711d..694713ae 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,6 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope ## Build Status | Build | Branch | Status | | --- | --- | --- | - | **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=staging) | | **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=master) | | **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=staging) | @@ -166,6 +165,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | | **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | -# Related projects +## Related projects + +[Microsoft AI Github](https://github.com/microsoft/ai) Find other Best Practice projects, and Azure AI Designed patterns in our central repository. -[Microsoft AI Labs Github](https://aka.ms/ai-labs) Find other Best Practice projects, and Azure AI design patterns in our central repository. diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index fad5040e..29a5b3b4 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -5,34 +5,32 @@ channels: dependencies: - python=3.6 - pip - - pytorch==1.2.0 - - cudatoolkit==9.2 + - pytorch==1.3.1 + - cudatoolkit==10.1.243 - jupyter - ipykernel - - torchvision==0.4.0 - - pandas==0.24.2 - - opencv==4.1.1 - - scikit-learn==0.21.3 + - torchvision==0.4.2 + - pandas==0.25.3 + - opencv==4.1.2 + - scikit-learn=0.21.3 - tensorflow==2.0 - - tqdm==4.38.0 - - itkwidgets==0.22.0 + - tqdm==4.39.0 + - itkwidgets==0.23.1 - pip: - segyio==1.8.8 - pytorch-ignite==0.3.0.dev20191105 # pre-release until stable available - fire==0.2.1 - - toolz==0.9.0 + - toolz==0.10.0 - tabulate==0.8.2 - - jinja2==2.10.3 - - gitpython==3.0.4 - - tensortensorboard==2.0.0 - - tensorboardx==1.8 - - tqdm==4.38.0 - - invoke + - Jinja2==2.10.3 + - gitpython==3.0.5 + - tensorboard==2.0.1 + - tensorboardx==1.9 + - invoke==1.3.0 - yacs==0.1.6 - - albumentations==0.4.2 + - albumentations==0.4.3 - black - pylint - scipy==1.1.0 - - jupytext==1.2.4 + - jupytext==1.3.0 - pytest - From 0e4e3a20d9e756f30ed5c885d3529ec3708657c7 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Thu, 28 Nov 2019 01:45:24 +0000 Subject: [PATCH 099/207] azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing --- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 32 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 478 +++---- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 35 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1272 +++++++++++++++++ .../azureml_devito/notebooks/README.md | 28 + ...zureml_buildexperimentationdockerimage.log | 202 +-- 6 files changed, 1573 insertions(+), 474 deletions(-) create mode 100755 contrib/examples/imaging/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb create mode 100644 contrib/examples/imaging/azureml_devito/notebooks/README.md diff --git a/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index af554269..b38369a3 100755 --- a/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -1,5 +1,13 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -55,7 +63,8 @@ "from azureml.core import Workspace, Experiment\n", "from azureml.core.compute import ComputeTarget, AmlCompute\n", "from azureml.core.compute_target import ComputeTargetException\n", - "import platform, dotenv" + "import platform, dotenv\n", + "import pathlib" ] }, { @@ -67,13 +76,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.69\n" + "Azure ML SDK Version: 1.0.74\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1061-azure-x86_64-with-debian-10.0'" + "'Linux-4.15.0-1063-azure-x86_64-with-debian-10.1'" ] }, "execution_count": 3, @@ -83,7 +92,7 @@ { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks'" + "'/workspace/contrib/examples/imaging/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -141,7 +150,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./src/project_utils.py\n" + "Overwriting /workspace/contrib/examples/imaging/azureml_devito/notebooks/./src/project_utils.py\n" ] } ], @@ -358,7 +367,7 @@ "\n", "# experimentation_docker_image_name = \"fwi01_azureml\"\n", "# experimentation_docker_image_tag = \"sdk.v1.0.60\"\n", - "# docker_container_mount_point = '/datadrive01/prj/DeepSeismic/fwi' # use project directory or a subdirectory\n", + "# docker_container_mount_point = os.getcwd() # use project directory or a subdirectory\n", "\n", "# docker_login = \"georgedockeraccount\"\n", "# docker_pwd = \"\"\n", @@ -418,6 +427,7 @@ "%load_ext dotenv\n", "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH)) \n", "os.makedirs(os.path.join(*(prj_consts.DOTENV_FILE_PATH[:-1])), exist_ok=True)\n", + "pathlib.Path(dotenv_file_path).touch()\n", "\n", "# # show .env file path\n", "# !pwd\n", @@ -649,23 +659,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "azure-cli 2.0.75\r\n", + "azure-cli 2.0.76 *\r\n", "\r\n", "command-modules-nspkg 2.0.3\r\n", - "core 2.0.75\r\n", + "core 2.0.76 *\r\n", "nspkg 3.0.4\r\n", "telemetry 1.0.4\r\n", "\r\n", "Python location '/opt/az/bin/python3'\r\n", "Extensions directory '/root/.azure/cliextensions'\r\n", "\r\n", - "Python (Linux) 3.6.5 (default, Oct 11 2019, 09:04:03) \r\n", + "Python (Linux) 3.6.5 (default, Oct 30 2019, 06:31:53) \r\n", "[GCC 6.3.0 20170516]\r\n", "\r\n", "Legal docs and information: aka.ms/AzureCliLegal\r\n", "\r\n", "\r\n", - "Your CLI is up-to-date.\r\n" + "\u001b[33mYou have 2 updates available. Consider updating your CLI installation.\u001b[0m\r\n" ] } ], @@ -827,7 +837,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb index 07c16572..c19e34fa 100755 --- a/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -4,6 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License.\n", + "\n", "# FWI in Azure project\n", "\n", "## Create Experimentation Docker image\n", @@ -62,7 +65,7 @@ { "data": { "text/plain": [ - "'Linux-4.15.0-1061-azure-x86_64-with-debian-10.0'" + "'Linux-4.15.0-1063-azure-x86_64-with-debian-10.1'" ] }, "execution_count": 3, @@ -72,7 +75,7 @@ { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks'" + "'/workspace/contrib/examples/imaging/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -105,10 +108,7 @@ "outputs": [], "source": [ "docker_build_no_cache = '' # '--no-cache' # or '' #\n", - "docker_test_run_devito_tests = False # True # False\n", - "\n", - "# azureml_sdk_version set here must match azureml sdk version pinned in conda env file written to conda_common_file_path below\n", - "azureml_sdk_version = '1.0.69' " + "docker_test_run_devito_tests = False # True # False" ] }, { @@ -184,7 +184,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "/workspace/examples/imaging/azureml_devito/notebooks\r\n" + "/workspace/contrib/examples/imaging/azureml_devito/notebooks\r\n" ] } ], @@ -192,6 +192,16 @@ "!pwd" ] }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# azureml_sdk_version set here must match azureml sdk version pinned in conda env file written to conda_common_file_path below\n", + "azureml_sdk_version = '1.0.74' " + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -206,118 +216,88 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(True, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', 'sdk.v1.0.69')" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "(True, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "(True,\n", - " 'DOCKER_CONTAINER_MOUNT_POINT',\n", - " '/datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks')" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# SDK changes often, so we'll keep its version transparent \n", "import dotenv\n", "\n", - "# EXPERIMENTATION_IMAGE_VERSION should:\n", - "# - match sdk version in fwi01_conda_env01 environmnet in conda_env_fwi01_azureml_sdk.v1.0.XX.yml file below\n", - "# - match the conda env yml file name, e.g. conda_env_fwi01_azureml_sdk.v1.0.62.yml referenced in \n", - "# Dockerfile_fwi01_azureml_sdk.v1.0.62\n", - "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', ('sdk.v'+azureml_sdk_version))\n", - "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')\n", - "\n", - "dotenv.set_key(dotenv_file_path, 'DOCKER_CONTAINER_MOUNT_POINT', '/datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks')" + "if 0:\n", + " # EXPERIMENTATION_IMAGE_VERSION should:\n", + " # - match sdk version in fwi01_conda_env01 environmnet in conda_env_fwi01_azureml_sdk.v1.0.XX.yml file below\n", + " # - match the conda env yml file name, e.g. conda_env_fwi01_azureml_sdk.v1.0.62.yml referenced in \n", + " # Dockerfile_fwi01_azureml_sdk.v1.0.62\n", + " dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', ('sdk.v'+azureml_sdk_version))\n", + " dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')\n", + "\n", + " docker_container_mount_point = os.getcwd()\n", + " # or something like \"/datadrive01/prj/DeepSeismic/contrib/examples/imaging/azureml_devito/notebooks'\n", + " dotenv.set_key(dotenv_file_path, 'DOCKER_CONTAINER_MOUNT_POINT', docker_container_mount_point)" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ - "'conda_env_fwi01_azureml_sdk.v1.0.69.yml'" + "'conda_env_fwi01_azureml_sdk.v1.0.74.yml'" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.69.yml'" + "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.74.yml'" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml'" + "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml'" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build'" + "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build'" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.69'" + "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.74'" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -364,14 +344,14 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" + "Overwriting /workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" ] } ], @@ -399,7 +379,7 @@ " - py-cpuinfo # all required by devito or dask-tutorial\n", " - pytables\n", " - python-graphviz\n", - " - requests>=2.19.1\n", + " - requests\n", " - pillow\n", " - scipy\n", " - snakeviz\n", @@ -407,7 +387,7 @@ " - toolz\n", " - pip:\n", " - anytree # required by devito\n", - " - azureml-sdk[notebooks,automl]==1.0.69\n", + " - azureml-sdk[notebooks,automl]==1.0.74\n", " - codepy # required by devito\n", " - papermill[azure]\n", " - pyrevolve # required by devito" @@ -415,14 +395,14 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.69\n" + "Overwriting /workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.74\n" ] } ], @@ -469,16 +449,16 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.69.yml'" + "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.74.yml'" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, @@ -486,16 +466,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "total 36\r\n", - "-rw-r--r-- 1 root root 1073 Oct 30 05:29 Dockerfile_fwi01_azureml_sdk.v1.0.60\r\n", - "-rw-r--r-- 1 root root 1098 Sep 26 19:04 Dockerfile_fwi01_azureml_sdk.v1.0.62\r\n", - "-rw-r--r-- 1 root root 1085 Oct 24 01:05 Dockerfile_fwi01_azureml_sdk.v1.0.65\r\n", - "-rw-r--r-- 1 root root 1073 Nov 3 14:43 Dockerfile_fwi01_azureml_sdk.v1.0.69\r\n", - "-rw-r--r-- 1 root root 733 Nov 3 14:43 conda_env_fwi01_azureml.yml\r\n", - "-rw-r--r-- 1 root root 733 Oct 30 05:29 conda_env_fwi01_azureml_sdk.v1.0.60.yml\r\n", - "-rw-r--r-- 1 root root 713 Sep 26 19:04 conda_env_fwi01_azureml_sdk.v1.0.62.yml\r\n", - "-rw-r--r-- 1 root root 733 Oct 24 01:05 conda_env_fwi01_azureml_sdk.v1.0.65.yml\r\n", - "-rw-r--r-- 1 root root 733 Nov 3 14:43 conda_env_fwi01_azureml_sdk.v1.0.69.yml\r\n" + "total 20\r\n", + "-rw-r--r-- 1 root root 1073 Nov 26 16:35 Dockerfile_fwi01_azureml_sdk.v1.0.69\r\n", + "-rw-r--r-- 1 root root 1073 Nov 27 12:53 Dockerfile_fwi01_azureml_sdk.v1.0.74\r\n", + "-rw-r--r-- 1 root root 725 Nov 27 12:53 conda_env_fwi01_azureml.yml\r\n", + "-rw-r--r-- 1 root root 733 Nov 26 16:35 conda_env_fwi01_azureml_sdk.v1.0.69.yml\r\n", + "-rw-r--r-- 1 root root 725 Nov 27 12:53 conda_env_fwi01_azureml_sdk.v1.0.74.yml\r\n" ] } ], @@ -507,30 +483,30 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69 -f /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.69 /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build '" + "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74 -f /workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.74 /workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build '" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ - "['Sending build context to Docker daemon 16.9kB',\n", + "['Sending build context to Docker daemon 9.728kB',\n", " '',\n", " 'Step 1/15 : FROM continuumio/miniconda3:4.7.10',\n", " ' ---> 4a51de2367be',\n", " 'Step 2/15 : MAINTAINER George Iordanescu ']" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, @@ -539,12 +515,12 @@ "text/plain": [ "['Step 15/15 : CMD /bin/bash',\n", " ' ---> Using cache',\n", - " ' ---> 3dc3d5d871a4',\n", - " 'Successfully built 3dc3d5d871a4',\n", - " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69']" + " ' ---> 3b4b53971bed',\n", + " 'Successfully built 3b4b53971bed',\n", + " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74']" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -572,16 +548,16 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" }, @@ -591,17 +567,17 @@ "'bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ - "b'/\\n1.0.69\\n'" + "b'/\\n1.0.74\\n'" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -631,7 +607,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -640,7 +616,7 @@ "'./fwi01_azureml_buildexperimentationdockerimage.log'" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -659,7 +635,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -668,7 +644,7 @@ "''" ] }, - "execution_count": 16, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -692,16 +668,16 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; \"'" + "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/contrib/examples/imaging/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; \"'" ] }, - "execution_count": 17, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" }, @@ -714,193 +690,95 @@ "base /opt/conda\n", "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", "\n", - "total 536\n", - "-rw-r--r-- 1 root root 11521 Oct 30 05:30 conftest.py\n", - "-rw-r--r-- 1 root root 6006 Oct 30 05:30 test_adjoint.py\n", - "-rw-r--r-- 1 root root 13882 Oct 30 05:30 test_autotuner.py\n", - "-rw-r--r-- 1 root root 5902 Oct 30 05:30 test_builtins.py\n", - "-rw-r--r-- 1 root root 21885 Oct 30 05:30 test_caching.py\n", - "-rw-r--r-- 1 root root 9721 Oct 30 05:30 test_checkpointing.py\n", - "-rw-r--r-- 1 root root 1095 Oct 30 05:30 test_constant.py\n", - "-rw-r--r-- 1 root root 54642 Oct 30 05:30 test_data.py\n", - "-rw-r--r-- 1 root root 481 Oct 30 05:30 test_dependency_bugs.py\n", - "-rw-r--r-- 1 root root 16506 Oct 30 05:30 test_derivatives.py\n", - "-rw-r--r-- 1 root root 1473 Oct 30 05:30 test_differentiable.py\n", - "-rw-r--r-- 1 root root 30846 Oct 30 05:30 test_dimension.py\n", - "-rw-r--r-- 1 root root 21233 Oct 30 05:30 test_dle.py\n", - "-rw-r--r-- 1 root root 1169 Oct 30 05:30 test_docstrings.py\n", - "-rw-r--r-- 1 root root 26928 Oct 30 05:30 test_dse.py\n", - "-rw-r--r-- 1 root root 8205 Oct 30 05:30 test_gradient.py\n", - "-rw-r--r-- 1 root root 15227 Oct 30 05:30 test_interpolation.py\n", - "-rw-r--r-- 1 root root 31514 Oct 30 05:30 test_ir.py\n", - "-rw-r--r-- 1 root root 62102 Oct 30 05:30 test_mpi.py\n", - "-rw-r--r-- 1 root root 61208 Oct 30 05:30 test_operator.py\n", - "-rw-r--r-- 1 root root 13006 Oct 30 05:30 test_ops.py\n", - "-rw-r--r-- 1 root root 12228 Oct 30 05:30 test_pickle.py\n", - "-rw-r--r-- 1 root root 1809 Oct 30 05:30 test_resample.py\n", - "-rw-r--r-- 1 root root 1754 Oct 30 05:30 test_save.py\n", - "-rw-r--r-- 1 root root 5711 Oct 30 05:30 test_subdomains.py\n", - "-rw-r--r-- 1 root root 3320 Oct 30 05:30 test_symbolic_coefficients.py\n", - "-rw-r--r-- 1 root root 3186 Oct 30 05:30 test_timestepping.py\n", - "-rw-r--r-- 1 root root 603 Oct 30 05:30 test_tools.py\n", - "-rw-r--r-- 1 root root 3296 Oct 30 05:30 test_tti.py\n", - "-rw-r--r-- 1 root root 8835 Oct 30 05:30 test_visitors.py\n", - "-rw-r--r-- 1 root root 21800 Oct 30 05:30 test_yask.py\n", - "1.0.69\n", + "total 556\n", + "-rw-r--r-- 1 root root 11521 Nov 27 03:12 conftest.py\n", + "-rw-r--r-- 1 root root 6006 Nov 27 03:12 test_adjoint.py\n", + "-rw-r--r-- 1 root root 14586 Nov 27 03:12 test_autotuner.py\n", + "-rw-r--r-- 1 root root 7538 Nov 27 03:12 test_builtins.py\n", + "-rw-r--r-- 1 root root 24415 Nov 27 03:12 test_caching.py\n", + "-rw-r--r-- 1 root root 9721 Nov 27 03:12 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Nov 27 03:12 test_constant.py\n", + "-rw-r--r-- 1 root root 55954 Nov 27 03:12 test_data.py\n", + "-rw-r--r-- 1 root root 481 Nov 27 03:12 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16331 Nov 27 03:12 test_derivatives.py\n", + "-rw-r--r-- 1 root root 1473 Nov 27 03:12 test_differentiable.py\n", + "-rw-r--r-- 1 root root 30846 Nov 27 03:12 test_dimension.py\n", + "-rw-r--r-- 1 root root 24838 Nov 27 03:12 test_dle.py\n", + "-rw-r--r-- 1 root root 1169 Nov 27 03:12 test_docstrings.py\n", + "-rw-r--r-- 1 root root 32134 Nov 27 03:12 test_dse.py\n", + "-rw-r--r-- 1 root root 8205 Nov 27 03:12 test_gradient.py\n", + "-rw-r--r-- 1 root root 15227 Nov 27 03:12 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31816 Nov 27 03:12 test_ir.py\n", + "-rw-r--r-- 1 root root 62176 Nov 27 03:12 test_mpi.py\n", + "-rw-r--r-- 1 root root 65255 Nov 27 03:12 test_operator.py\n", + "-rw-r--r-- 1 root root 14208 Nov 27 03:12 test_ops.py\n", + "-rw-r--r-- 1 root root 12228 Nov 27 03:12 test_pickle.py\n", + "-rw-r--r-- 1 root root 1809 Nov 27 03:12 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Nov 27 03:12 test_save.py\n", + "-rw-r--r-- 1 root root 2115 Nov 27 03:12 test_staggered_utils.py\n", + "-rw-r--r-- 1 root root 5711 Nov 27 03:12 test_subdomains.py\n", + "-rw-r--r-- 1 root root 3320 Nov 27 03:12 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 7277 Nov 27 03:12 test_tensors.py\n", + "-rw-r--r-- 1 root root 3186 Nov 27 03:12 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Nov 27 03:12 test_tools.py\n", + "-rw-r--r-- 1 root root 3296 Nov 27 03:12 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Nov 27 03:12 test_visitors.py\n", + "-rw-r--r-- 1 root root 21802 Nov 27 03:12 test_yask.py\n", + "1.0.74\n", "\n", "content of devito tests log file after testing:\n", "============================= test session starts ==============================\n", - "platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0\n", + "platform linux -- Python 3.6.9, pytest-5.3.0, py-1.8.0, pluggy-0.13.1\n", "rootdir: /devito, inifile: setup.cfg\n", - "plugins: nbval-0.9.3, cov-2.7.1\n", - "collected 912 items / 2 skipped / 910 selected\n", + "plugins: nbval-0.9.3, cov-2.8.1\n", + "collected 1053 items / 2 skipped / 1051 selected\n", "\n", "tests/test_adjoint.py .......................... [ 2%]\n", - "tests/test_autotuner.py ..........s.... [ 4%]\n", - "tests/test_builtins.py ....s.s.s [ 5%]\n", - "tests/test_caching.py .................................. [ 9%]\n", - "tests/test_checkpointing.py ....... [ 9%]\n", - "tests/test_constant.py . [ 10%]\n", - "tests/test_data.py ..........................sssssssssssssss.ss.. [ 15%]\n", - "tests/test_dependency_bugs.py . [ 15%]\n", + "tests/test_autotuner.py ..........s..... [ 3%]\n", + "tests/test_builtins.py ....s...............s..s [ 6%]\n", + "tests/test_caching.py .................................................. [ 11%]\n", + " [ 11%]\n", + "tests/test_checkpointing.py ....... [ 11%]\n", + "tests/test_constant.py . [ 11%]\n", + "tests/test_data.py ..........................ssssssssssssssssss.ss.. [ 16%]\n", + "tests/test_dependency_bugs.py . [ 16%]\n", "tests/test_derivatives.py .............................................. [ 20%]\n", - "........................................................................ [ 28%]\n", - ".........................................................F...F.......... [ 36%]\n", - "...... [ 36%]\n", - "tests/test_differentiable.py .. [ 36%]\n", - "tests/test_dimension.py ............................... [ 40%]\n", - "tests/test_dle.py ...................................................... [ 46%]\n", - "......................................... [ 50%]\n", - "tests/test_docstrings.py ................ [ 52%]\n", - "tests/test_dse.py ......x............................................... [ 58%]\n", - ".......................s.... [ 61%]\n", - "tests/test_gradient.py .... [ 61%]\n", - "tests/test_interpolation.py ........................ [ 64%]\n", - "tests/test_ir.py ....................................................... [ 70%]\n", - "................ [ 72%]\n", - "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 78%]\n", - "ss [ 78%]\n", - "tests/test_operator.py ................................................. [ 83%]\n", - "........................................................................ [ 91%]\n", - ".................. [ 93%]\n", - "tests/test_pickle.py .................ss. [ 95%]\n", - "tests/test_resample.py . [ 96%]\n", - "tests/test_save.py .. [ 96%]\n", - "tests/test_subdomains.py ... [ 96%]\n", - "tests/test_symbolic_coefficients.py .....F [ 97%]\n", + "........................................................................ [ 27%]\n", + "........................................................................ [ 34%]\n", + "...... [ 35%]\n", + "tests/test_differentiable.py .. [ 35%]\n", + "tests/test_dimension.py ............................... [ 38%]\n", + "tests/test_dle.py ...................................................... [ 43%]\n", + "........................................... [ 47%]\n", + "tests/test_docstrings.py ................ [ 49%]\n", + "tests/test_dse.py ......x............................................... [ 54%]\n", + "................x..........s.... [ 57%]\n", + "tests/test_gradient.py .... [ 57%]\n", + "tests/test_interpolation.py ........................ [ 59%]\n", + "tests/test_ir.py ....................................................... [ 65%]\n", + "................ [ 66%]\n", + "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 71%]\n", + "ss [ 71%]\n", + "tests/test_operator.py ................................................. [ 76%]\n", + "........................................................................ [ 83%]\n", + "................................ [ 86%]\n", + "tests/test_pickle.py .................ss. [ 88%]\n", + "tests/test_resample.py . [ 88%]\n", + "tests/test_save.py .. [ 88%]\n", + "tests/test_staggered_utils.py ......... [ 89%]\n", + "tests/test_subdomains.py ... [ 89%]\n", + "tests/test_symbolic_coefficients.py .....F [ 90%]\n", + "tests/test_tensors.py .................................................. [ 95%]\n", + "........................... [ 97%]\n", "tests/test_timestepping.py ....... [ 98%]\n", "tests/test_tools.py ..... [ 98%]\n", "tests/test_tti.py .... [ 99%]\n", "tests/test_visitors.py ......... [100%]\n", "\n", "=================================== FAILURES ===================================\n", - "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________\n", - "\n", - "self = , so = 12, ndim = 1\n", - "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", - "\n", - " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", - " @pytest.mark.parametrize('ndim', [1, 2])\n", - " @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [\n", - " ('dx', 'dx', -1),\n", - " ('dx2', 'dx2', 1),\n", - " ('dxl', 'dxr', -1),\n", - " ('dxr', 'dxl', -1)])\n", - " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", - " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", - " x = grid.dimensions[0]\n", - " f = Function(name='f', grid=grid, space_order=so)\n", - " f_deriv = Function(name='f_deriv', grid=grid, space_order=so)\n", - " g = Function(name='g', grid=grid, space_order=so)\n", - " g_deriv = Function(name='g_deriv', grid=grid, space_order=so)\n", - " \n", - " # Fill f and g with smooth cos/sin\n", - " Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply()\n", - " # Check symbolic expression are expected ones for the adjoint .T\n", - " deriv = getattr(f, derivative)\n", - " expected = adjoint_coeff * getattr(f, adjoint_name).evaluate\n", - " assert deriv.T.evaluate == expected\n", - " \n", - " # Compute numerical derivatives and verify dot test\n", - " # i.e = \n", - " \n", - " eq_f = Eq(f_deriv, deriv)\n", - " eq_g = Eq(g_deriv, getattr(g, derivative).T)\n", - " \n", - " op = Operator([eq_f, eq_g])\n", - " op()\n", - " \n", - " a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1))\n", - " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", - "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", - "E assert False\n", - "E + where False = ((1 - (0.22231627 / 0.2223134)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", - "\n", - "tests/test_derivatives.py:394: AssertionError\n", - "----------------------------- Captured stderr call -----------------------------\n", - "Operator `Kernel` run in 0.01 s\n", - "Operator `Kernel` run in 0.01 s\n", - "------------------------------ Captured log call -------------------------------\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________\n", - "\n", - "self = , so = 12, ndim = 2\n", - "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", - "\n", - " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", - " @pytest.mark.parametrize('ndim', [1, 2])\n", - " @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [\n", - " ('dx', 'dx', -1),\n", - " ('dx2', 'dx2', 1),\n", - " ('dxl', 'dxr', -1),\n", - " ('dxr', 'dxl', -1)])\n", - " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", - " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", - " x = grid.dimensions[0]\n", - " f = Function(name='f', grid=grid, space_order=so)\n", - " f_deriv = Function(name='f_deriv', grid=grid, space_order=so)\n", - " g = Function(name='g', grid=grid, space_order=so)\n", - " g_deriv = Function(name='g_deriv', grid=grid, space_order=so)\n", - " \n", - " # Fill f and g with smooth cos/sin\n", - " Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply()\n", - " # Check symbolic expression are expected ones for the adjoint .T\n", - " deriv = getattr(f, derivative)\n", - " expected = adjoint_coeff * getattr(f, adjoint_name).evaluate\n", - " assert deriv.T.evaluate == expected\n", - " \n", - " # Compute numerical derivatives and verify dot test\n", - " # i.e = \n", - " \n", - " eq_f = Eq(f_deriv, deriv)\n", - " eq_g = Eq(g_deriv, getattr(g, derivative).T)\n", - " \n", - " op = Operator([eq_f, eq_g])\n", - " op()\n", - " \n", - " a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1))\n", - " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", - "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", - "E assert False\n", - "E + where False = ((1 - (11.3381815 / 11.338006)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", - "\n", - "tests/test_derivatives.py:394: AssertionError\n", - "----------------------------- Captured stderr call -----------------------------\n", - "Operator `Kernel` run in 0.01 s\n", - "/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c: In function ‘Kernel’:\n", - "/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", - " #pragma omp simd aligned(f,f_deriv,g,g_deriv:32)\n", - " \n", - "Operator `Kernel` run in 0.01 s\n", - "------------------------------ Captured log call -------------------------------\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", "______________________ TestSC.test_function_coefficients _______________________\n", "\n", - "self = \n", + "self = \n", "\n", " def test_function_coefficients(self):\n", " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", @@ -922,9 +800,9 @@ " wdims = as_tuple(wdims)\n", " \n", " w = Function(name='w', dimensions=wdims, shape=wshape)\n", - " w.data[:, :, 0] = -0.5/grid.spacing[0]\n", - " w.data[:, :, 1] = 0.0\n", - " w.data[:, :, 2] = 0.5/grid.spacing[0]\n", + " w.data[:, :, 0] = 0.0\n", + " w.data[:, :, 1] = -1.0/grid.spacing[0]\n", + " w.data[:, :, 2] = 1.0/grid.spacing[0]\n", " \n", " f_x_coeffs = Coefficient(1, f0, x, w)\n", " \n", @@ -944,10 +822,10 @@ " \n", "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", "E assert Data(False)\n", - "E + where Data(False) = (Data([[[ True, True, True, True],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...True],\\n [ True, True, True, True],\\n [ True, True, True, True],\\n [ True, True, True, True]]]))\n", - "E + where = np.all\n", - "E + and Data([[[ True, True, True, True],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...True],\\n [ True, True, True, True],\\n [ True, True, True, True],\\n [ True, True, True, True]]]) = ((Data([[[-11.4375 , -11.4375 , -11.4375 , -11.4375 ],\\n [ 49.59375, 49.59375, 49.59375, 49.59375],\\n [ -6.... [-24.25 , -24.25 , -24.25 , -24.25 ],\\n [ -1.9375 , -1.9375 , -1.9375 , -1.9375 ]]], dtype=float32) - Data([[[-11.437502 , -11.437502 , -11.437502 , -11.437502 ],\\n [ 49.59374 , 49.59374 , 49.59374 , 49.59374 ....249996 , -24.249996 , -24.249996 ],\\n [ -1.9375012, -1.9375012, -1.9375012, -1.9375012]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", - "E + where = np.isclose\n", + "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", + "E + where = np.all\n", + "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", + "E + where = np.isclose\n", "\n", "tests/test_symbolic_coefficients.py:96: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", @@ -956,15 +834,15 @@ " #pragma omp simd aligned(f0,w:32)\n", " \n", "Operator `Kernel` run in 0.01 s\n", - "/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c: In function ‘Kernel’:\n", - "/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + "/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c: In function ‘Kernel’:\n", + "/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", " #pragma omp simd aligned(f1:32)\n", " \n", "Operator `Kernel` run in 0.01 s\n", "------------------------------ Captured log call -------------------------------\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "======= 3 failed, 828 passed, 82 skipped, 1 xfailed in 830.53s (0:13:50) =======\n" + "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", + "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", + "====== 1 failed, 967 passed, 85 skipped, 2 xfailed in 1058.91s (0:17:38) =======\n" ] } ], @@ -994,7 +872,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -1003,7 +881,7 @@ "'az acr login --name fwi01acr'" ] }, - "execution_count": 18, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" }, @@ -1038,16 +916,16 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" + "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" ] }, - "execution_count": 19, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -1059,7 +937,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -1068,13 +946,13 @@ "text": [ "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", "\n", - "\u001b[1B8b071694: Preparing \n", - "\u001b[1B5b417f99: Preparing \n", - "\u001b[1Bd24aa1b4: Preparing \n", + "\u001b[1Bff8f35a1: Preparing \n", + "\u001b[1Ba2d0572b: Preparing \n", + "\u001b[1B9c751d4d: Preparing \n", "\u001b[1Be41d536b: Preparing \n", "\u001b[1Bf8fc4c9a: Preparing \n", "\u001b[1Bba47210e: Preparing \n", - "\u001b[2Bba47210e: Layer already exists \u001b[3A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[Ksdk.v1.0.69: digest: sha256:9acb10b41bf2e750519b303c365870f67a64c059b90d8868ff139e7d54446948 size: 1800\n" + "\u001b[2Bba47210e: Layer already exists \u001b[1A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[Ksdk.v1.0.74: digest: sha256:c07f66d82fb26972c2bbc4cbf2914c3d2c8b50bcad3925fdb6d77bac7ef5779a size: 1800\n" ] } ], @@ -1084,7 +962,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -1124,7 +1002,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb index 59843f6e..1f14d9b3 100755 --- a/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -4,6 +4,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License. \n", + " \n", + " \n", "# FWI demo based on: \n", "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", @@ -71,13 +75,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.69\n" + "Azure ML SDK Version: 1.0.74\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1061-azure-x86_64-with-debian-10.0'" + "'Linux-4.15.0-1063-azure-x86_64-with-debian-10.1'" ] }, "execution_count": 3, @@ -87,7 +91,7 @@ { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks'" + "'/workspace/contrib/examples/imaging/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -477,7 +481,7 @@ { "data": { "text/plain": [ - "'fwi01_azureml:sdk.v1.0.69'" + "'fwi01_azureml:sdk.v1.0.74'" ] }, "execution_count": 9, @@ -487,7 +491,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" ] }, "execution_count": 9, @@ -497,9 +501,9 @@ ], "source": [ "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", - "docker_image_name = os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", + "docker_image_name = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", "\n", - "image_version = os.getenv('EXPERIMENTATION_IMAGE_VERSION')\n", + "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", "if image_version!=\"\":\n", " docker_image_name = docker_image_name +':'+ image_version\n", "\n", @@ -524,7 +528,7 @@ { "data": { "text/plain": [ - "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69 /bin/bash -c \"which python\" '" + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74 /bin/bash -c \"which python\" '" ] }, "execution_count": 10, @@ -820,7 +824,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "2066a8170a4e4c11ada50b453920959b", + "model_id": "087f9fbd5cf344cbb55ab4821081324d", "version_major": 2, "version_minor": 0 }, @@ -830,6 +834,13 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574860552_cc296d9b?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1574860552_cc296d9b\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1574860552_cc296d9b\", \"created_utc\": \"2019-11-27T13:15:54.231937Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"mlflow.source.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-11-27T13:19:50.61051Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/55_azureml-execution-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt?sv=2019-02-02&sr=b&sig=%2FF76BbosxuAzbk%2B2sMTd5U8AGl7sfr72xR5KtQVHeAM%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/65_job_prep-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt?sv=2019-02-02&sr=b&sig=mGIIQyUJfiU0CzMCjT7sn5NjpZYAJrbGA1uvdhvsR%2Fo%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=O08hKv6f%2BBx8fMwC0e8DF4VhgqpYd72I8A3rlUglIco%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/75_job_post-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/75_job_post-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt?sv=2019-02-02&sr=b&sig=f2OaM4d2ISojn8LNdS%2Be30xACXpoz0%2BOmr7DIk9QdEA%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=u1Dza2X5QhAOj14DUl7wMHMIwyexsRwc8Va3WzX7tHk%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=faGC2hFaEbNo%2Fun8X37DOvgUw9IOoeOe8BTbre31EVo%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"logs/azureml/728_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/logs/azureml/728_azureml.log?sv=2019-02-02&sr=b&sig=mZ56mvBlObajZ0AgKpAdeNp1lhDNOjN6j6xLg0%2BZkjY%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=HvgJaNtCgSUhoBo7wKxr9e5JUqTf8UfYHDAckVpWiZ8%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\"], [\"logs/azureml/728_azureml.log\"]], \"run_duration\": \"0:03:56\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1574860552_cc296d9b\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-11-27 13:18:09,635|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-11-27 13:18:09,635|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-11-27 13:18:09,635|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-11-27 13:18:09,636|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-11-27 13:18:09,960|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-11-27 13:18:09,961|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-11-27 13:18:10,335|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:18:10,335|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:18:10,335|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:18:10,335|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:18:10,335|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-11-27 13:18:10,997|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-11-27 13:18:11,003|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-11-27 13:18:11,008|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-11-27 13:18:11,014|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-11-27 13:18:11,015|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:18:11,022|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,022|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-11-27 13:18:11,023|azureml.core.authentication|DEBUG|Time to expire 1814262.976988 seconds\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,024|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,053|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,058|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,067|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,072|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,077|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,082|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,083|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:18:11,083|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:18:11,083|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574860552_cc296d9b'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0aa686f0-3fcf-4102-ab74-8a6e9c373cb1'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'request-id': '0aa686f0-3fcf-4102-ab74-8a6e9c373cb1'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:18:11,085|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:18:11,085|msrest.http_logger|DEBUG|None\\n2019-11-27 13:18:11,085|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:18:11,085|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:18:11,085|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:18:11,085|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:18:11,161|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:18:11 GMT'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0aa686f0-3fcf-4102-ab74-8a6e9c373cb1'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:18:11,163|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:18:11,163|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:18:11,163|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:18:11,163|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1398,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:15:54.2319371+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:16:06.348303+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574860552_cc296d9b/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574860552_cc296d9b/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574860552_cc296d9b/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:18:11,169|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:18:11,170|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:18:11,170|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:18:11,170|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-11-27 13:18:11,170|azureml.WorkerPool|DEBUG|[START]\\n2019-11-27 13:18:11,170|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574860552_cc296d9b/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574860552_cc296d9b\\n2019-11-27 13:18:11,171|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:18:11,171|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574860552_cc296d9b/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574860552_cc296d9b\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,218|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,218|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,218|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,224|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,225|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-11-27 13:18:13,230|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,235|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,241|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,246|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,247|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:18:13,247|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574860552_cc296d9b'\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'c9e7dc9f-a9ff-4449-87ef-489a13628a79'\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG| 'request-id': 'c9e7dc9f-a9ff-4449-87ef-489a13628a79'\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG|None\\n2019-11-27 13:18:13,248|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:18:13,248|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:18:13,248|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:18:13,248|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:18:13 GMT'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'c9e7dc9f-a9ff-4449-87ef-489a13628a79'\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1398,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:15:54.2319371+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:16:06.348303+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574860552_cc296d9b/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574860552_cc296d9b/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574860552_cc296d9b/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:18:13,304|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:18:13,304|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:18:13,304|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:18:41,020|azureml.core.authentication|DEBUG|Time to expire 1814232.979555 seconds\\n2019-11-27 13:19:11,020|azureml.core.authentication|DEBUG|Time to expire 1814202.979107 seconds\\n2019-11-27 13:19:29,411|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:19:29,412|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:19:29,412|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574860552_cc296d9b/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574860552_cc296d9b\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574860552_cc296d9b/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574860552_cc296d9b\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-11-27 13:19:29,484|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b|INFO|complete is not setting status for submitted runs.\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,485|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,486|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,486|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-11-27 13:19:29,486|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-11-27 13:19:29,486|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-11-27 13:19:29,486|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:19:29,486|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-11-27 13:19:29,486|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-11-27 13:19:29,487|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:19:29,487|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-11-27 13:19:29,487|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-11-27 13:19:29,487|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-11-27 13:19:29,488|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:19:29,489|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-11-27 13:19:29,489|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:19:29,489|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574860552_cc296d9b/batch/metrics'\\n2019-11-27 13:19:29,490|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-11-27 13:19:29,490|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:19:29,490|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:19:29,490|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-11-27 13:19:29,491|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '119f863f-8271-4a91-94eb-227d47a35c46'\\n2019-11-27 13:19:29,491|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG| 'request-id': '119f863f-8271-4a91-94eb-227d47a35c46'\\n2019-11-27 13:19:29,491|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-11-27 13:19:29,491|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74 sdk_run'\\n2019-11-27 13:19:29,491|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:19:29,492|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"9eac8088-e7ee-4218-a267-72559639c64d\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-11-27T13:19:29.411376Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-11-27 13:19:29,492|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:19:29,492|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:19:29,492|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:19:29,492|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:19:29 GMT'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '119f863f-8271-4a91-94eb-227d47a35c46'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:19:29,632|msrest.http_logger|DEBUG|\\n2019-11-27 13:19:29,632|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00022172927856445312 seconds.\\n\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,743|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-11-27 13:19:29,743|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-11-27 13:19:29,743|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-11-27 13:19:29,743|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.0.74\"}, \"loading\": false}" + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -889,7 +900,7 @@ { "data": { "text/plain": [ - "'runId= 020_AzureMLEstimator_1572793621_e35441c0'" + "'runId= 020_AzureMLEstimator_1574860552_cc296d9b'" ] }, "execution_count": 18, @@ -899,7 +910,7 @@ { "data": { "text/plain": [ - "'experimentation baseImage: fwi01_azureml:sdk.v1.0.69'" + "'experimentation baseImage: fwi01_azureml:sdk.v1.0.74'" ] }, "execution_count": 18, @@ -949,7 +960,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/contrib/examples/imaging/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb new file mode 100755 index 00000000..df9405d4 --- /dev/null +++ b/contrib/examples/imaging/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -0,0 +1,1272 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License. \n", + " \n", + "\n", + "# FWI demo based on: \n", + "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", + "\n", + "\n", + "\n", + "In this notebook we run the devito demo [notebooks](https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/) mentioned above by using an [AzureML estimator](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator.estimator?view=azure-ml-py) with custom docker image. The docker image and associated docker file were created in previous notebook.\n", + "\n", + "
\n", + "#### This notebook is used as a control plane to submit experimentation jobs running devito in Azure in two modes (see [remote run azureml python script file invoking devito](#devito_demo_mode)):\n", + " - [Mode 1](#devito_demo_mode_1):\n", + " - uses custom code (slightly modified graphing functions save images to files too) \n", + " - experimentation job is defined by the devito code that is packaged as a py file to be run on an Azure remote compute target\n", + " - experimentation job can be used to track metrics or other artifacts (images)\n", + " \n", + " - Mode 2:\n", + " - papermill is invoked via cli or via its Python API to run unedited devito demo notebooks (https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) on the remote compute target and get back the results as saved notebooks that are then Available in Azure portal. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Allow multiple displays per cell\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\" " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "import shutil\n", + "import urllib\n", + "import azureml.core\n", + "from azureml.core import Workspace, Experiment\n", + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "from azureml.core.runconfig import MpiConfiguration\n", + "\n", + "\n", + "# from azureml.core.datastore import Datastore\n", + "# from azureml.data.data_reference import DataReference\n", + "# from azureml.pipeline.steps import HyperDriveStep\n", + "# from azureml.pipeline.core import Pipeline, PipelineData\n", + "# from azureml.train.dnn import TensorFlow\n", + "\n", + "from azureml.train.estimator import Estimator\n", + "from azureml.widgets import RunDetails\n", + "\n", + "import platform" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Azure ML SDK Version: 1.0.74\n" + ] + }, + { + "data": { + "text/plain": [ + "'Linux-4.15.0-1063-azure-x86_64-with-debian-10.1'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/workspace/contrib/examples/imaging/azureml_devito/notebooks'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", + "platform.platform()\n", + "os.getcwd()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../not_shared/general.env'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_path_to_sys_path(path_to_append):\n", + " if not (any(path_to_append in paths for paths in sys.path)):\n", + " sys.path.append(path_to_append)\n", + " \n", + "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", + "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", + "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", + "\n", + "import project_utils\n", + "prj_consts = project_utils.project_consts()\n", + "\n", + "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", + "dotenv_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../not_shared'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "workspace_config_dir = os.path.join(*(prj_consts.AML_WORKSPACE_CONFIG_DIR))\n", + "workspace_config_file = prj_consts.AML_WORKSPACE_CONFIG_FILE_NAME\n", + "workspace_config_dir" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/azureml_01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%dotenv $dotenv_file_path\n", + "\n", + "script_folder = prj_consts.AML_EXPERIMENT_DIR + ['devito_tutorial']\n", + "\n", + "devito_training_script_file = '01_modelling.py' # hardcoded in file azureml_training_script_full_file_name below\n", + "azureml_training_script_file = 'azureml_'+devito_training_script_file\n", + "experimentName = '020_AzureMLEstimator'\n", + "\n", + "os.makedirs(os.path.join(*(script_folder)), exist_ok=True)\n", + "script_path = os.path.join(*(script_folder))\n", + "training_script_full_file_name = os.path.join(script_path, devito_training_script_file)\n", + "azureml_training_script_full_file_name = os.path.join(script_path, azureml_training_script_file)\n", + "\n", + "training_script_full_file_name\n", + "azureml_training_script_full_file_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " \n", + "##### devito in Azure ML demo mode 1\n", + "Create devito demo script based on \n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/01_modelling.ipynb\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to summary of modes od demoing devito in AzureML.\n", + "\n", + "Main purpose of this script is to extend _plot_velocity()_ and _plot_shotrecord()_ devito [plotting functions](https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py) to allow the mto work in batch mode, i.e. save output to a file." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $training_script_full_file_name\n", + "\n", + "import numpy as np\n", + "import os, argparse\n", + "\n", + "from examples.seismic import Model\n", + "from examples.seismic import TimeAxis\n", + "from examples.seismic import Receiver\n", + "from devito import TimeFunction\n", + "from devito import Eq, solve\n", + "from devito import Operator\n", + "\n", + "\n", + "# try:\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib import cm\n", + "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", + "\n", + "mpl.rc('font', size=16)\n", + "mpl.rc('figure', figsize=(8, 6))\n", + "# except:\n", + "# plt = None\n", + "# cm = None\n", + " \n", + "\n", + "\n", + "# \"all\" plotting utils in devito do not save to file, so we extend them here\n", + "# https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py\n", + "def plot_velocity(model, source=None, receiver=None, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a two-dimensional velocity field from a seismic `Model`\n", + " object. Optionally also includes point markers for sources and receivers.\n", + "\n", + " Parameters\n", + " ----------\n", + " model : Model\n", + " Object that holds the velocity model.\n", + " source : array_like or float\n", + " Coordinates of the source point.\n", + " receiver : array_like or float\n", + " Coordinates of the receiver points.\n", + " colorbar : bool\n", + " Option to plot the colorbar.\n", + " \"\"\"\n", + " domain_size = 1.e-3 * np.array(model.domain_size)\n", + " extent = [model.origin[0], model.origin[0] + domain_size[0],\n", + " model.origin[1] + domain_size[1], model.origin[1]]\n", + "\n", + " plot = plt.imshow(np.transpose(model.vp.data), animated=True, cmap=cm.jet,\n", + " vmin=np.min(model.vp.data), vmax=np.max(model.vp.data),\n", + " extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Depth (km)')\n", + "\n", + " # Plot source points, if provided\n", + " if receiver is not None:\n", + " plt.scatter(1e-3*receiver[:, 0], 1e-3*receiver[:, 1],\n", + " s=25, c='green', marker='D')\n", + "\n", + " # Plot receiver points, if provided\n", + " if source is not None:\n", + " plt.scatter(1e-3*source[:, 0], 1e-3*source[:, 1],\n", + " s=25, c='red', marker='o')\n", + "\n", + " # Ensure axis limits\n", + " plt.xlim(model.origin[0], model.origin[0] + domain_size[0])\n", + " plt.ylim(model.origin[1] + domain_size[1], model.origin[1])\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " cbar = plt.colorbar(plot, cax=cax)\n", + " cbar.set_label('Velocity (km/s)')\n", + " plt.show()\n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def plot_shotrecord(rec, model, t0, tn, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a shot record (receiver values over time).\n", + "\n", + " Parameters\n", + " ----------\n", + " rec :\n", + " Receiver data with shape (time, points).\n", + " model : Model\n", + " object that holds the velocity model.\n", + " t0 : int\n", + " Start of time dimension to plot.\n", + " tn : int\n", + " End of time dimension to plot.\n", + " \"\"\"\n", + " scale = np.max(rec) / 10.\n", + " extent = [model.origin[0], model.origin[0] + 1e-3*model.domain_size[0],\n", + " 1e-3*tn, t0]\n", + "\n", + " plot = plt.imshow(rec, vmin=-scale, vmax=scale, cmap=cm.gray, extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Time (s)')\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " plt.colorbar(plot, cax=cax)\n", + " plt.show() \n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def main(output_folder): \n", + " # 1. Define the physical problem\n", + " # The first step is to define the physical model:\n", + " # - physical dimensions of interest\n", + " # - velocity profile of this physical domain\n", + "\n", + " # Define a physical size\n", + " shape = (101, 101) # Number of grid point (nx, nz)\n", + " spacing = (10., 10.) # Grid spacing in m. The domain size is now 1km by 1km\n", + " origin = (0., 0.) # What is the location of the top left corner. This is necessary to define\n", + " # the absolute location of the source and receivers\n", + "\n", + " # Define a velocity profile. The velocity is in km/s\n", + " v = np.empty(shape, dtype=np.float32)\n", + " v[:, :51] = 1.5\n", + " v[:, 51:] = 2.5\n", + "\n", + " # With the velocity and model size defined, we can create the seismic model that\n", + " # encapsulates this properties. We also define the size of the absorbing layer as 10 grid points\n", + " model = Model(vp=v, origin=origin, shape=shape, spacing=spacing,\n", + " space_order=2, nbpml=10)\n", + "\n", + " plot_velocity(model, \n", + " file= os.path.join(*( [output_folder,'output000.png'])))\n", + " \n", + " # 2. Acquisition geometry\n", + " t0 = 0. # Simulation starts a t=0\n", + " tn = 1000. # Simulation last 1 second (1000 ms)\n", + " dt = model.critical_dt # Time step from model grid spacing\n", + "\n", + " time_range = TimeAxis(start=t0, stop=tn, step=dt)\n", + " from examples.seismic import RickerSource\n", + "\n", + " f0 = 0.010 # Source peak frequency is 10Hz (0.010 kHz)\n", + " src = RickerSource(name='src', grid=model.grid, f0=f0,\n", + " npoint=1, time_range=time_range)\n", + "\n", + " # First, position source centrally in all dimensions, then set depth\n", + " src.coordinates.data[0, :] = np.array(model.domain_size) * .5\n", + " src.coordinates.data[0, -1] = 20. # Depth is 20m\n", + "\n", + " # We can plot the time signature to see the wavelet\n", + "# src.show()\n", + "\n", + " # Create symbol for 101 receivers\n", + " rec = Receiver(name='rec', grid=model.grid, npoint=101, time_range=time_range)\n", + "\n", + " # Prescribe even spacing for receivers along the x-axis\n", + " rec.coordinates.data[:, 0] = np.linspace(0, model.domain_size[0], num=101)\n", + " rec.coordinates.data[:, 1] = 20. # Depth is 20m\n", + "\n", + " # We can now show the source and receivers within our domain:\n", + " # Red dot: Source location\n", + " # Green dots: Receiver locations (every 4th point)\n", + " plot_velocity(model, source=src.coordinates.data,\n", + " receiver=rec.coordinates.data[::4, :], \n", + " file= os.path.join(*( [output_folder,'output010.png'])))\n", + " \n", + " # Define the wavefield with the size of the model and the time dimension\n", + " u = TimeFunction(name=\"u\", grid=model.grid, time_order=2, space_order=2)\n", + "\n", + " # We can now write the PDE\n", + " pde = model.m * u.dt2 - u.laplace + model.damp * u.dt\n", + "\n", + " # The PDE representation is as on paper\n", + " pde\n", + " \n", + " # This discrete PDE can be solved in a time-marching way updating u(t+dt) from the previous time step\n", + " # Devito as a shortcut for u(t+dt) which is u.forward. We can then rewrite the PDE as \n", + " # a time marching updating equation known as a stencil using customized SymPy functions\n", + "\n", + " stencil = Eq(u.forward, solve(pde, u.forward))\n", + " # Finally we define the source injection and receiver read function to generate the corresponding code\n", + " src_term = src.inject(field=u.forward, expr=src * dt**2 / model.m)\n", + "\n", + " # Create interpolation expression for receivers\n", + " rec_term = rec.interpolate(expr=u.forward)\n", + "\n", + " op = Operator([stencil] + src_term + rec_term, subs=model.spacing_map)\n", + " \n", + " op(time=time_range.num-1, dt=model.critical_dt)\n", + " plot_shotrecord(rec.data, model, t0, tn, \n", + " file= os.path.join(*( [output_folder,'output020.png'])))\n", + "\n", + "if __name__ == \"__main__\":\n", + " parser = argparse.ArgumentParser()\n", + " parser.add_argument('--output_folder', type=str, nargs='?', \\\n", + " dest='output_folder', help='ouput artifacts location',\\\n", + " default='.')\n", + " args = parser.parse_args()\n", + " \n", + " main(args.output_folder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Get experimentation docker image for devito" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'fwi01_azureml:sdk.v1.0.74'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", + "\n", + "docker_image_name = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", + "if image_version!=\"\":\n", + " docker_image_name = docker_image_name +':'+ image_version\n", + "\n", + "full_docker_image_name = docker_repo_name + '/' + docker_image_name\n", + " \n", + "docker_image_name\n", + "full_docker_image_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Extract/decide the python path in custom docker image that corresponds to desired conda environment. Without this, AzureML tries to create a separate environment." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74 /bin/bash -c \"which python\" '" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/opt/conda/envs/fwi01_conda_env/bin/python'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_Python_path_command='docker run -i --rm --name fwi01_azureml_container02 '+ \\\n", + "full_docker_image_name + \\\n", + "' /bin/bash -c \"which python\" '\n", + "get_Python_path_command\n", + "\n", + "\n", + "import subprocess\n", + "python_path_in_docker_image = subprocess.check_output(get_Python_path_command,shell=True,stderr=subprocess.STDOUT).\\\n", + "decode('utf-8').strip()\n", + "python_path_in_docker_image" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "#### Create azureml_script_file that invokes:\n", + " - devito exclusive custom edited training_script_file\n", + " - unedited devito notebooks via papermill (invoked via cli and via ppapermill python API)\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to notebook summary." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/azureml_01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $azureml_training_script_full_file_name\n", + "\n", + "import argparse\n", + "import os\n", + "os.system('conda env list')\n", + "\n", + "import azureml.core;\n", + "from azureml.core.run import Run\n", + "\n", + "print(azureml.core.VERSION)\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "parser.add_argument('--output_folder', type=str, dest='output_folder', help='ouput artifacts location')\n", + "\n", + "args = parser.parse_args()\n", + "print('args.output_folder is {} but it will be ignored since AzureML_tracked ./outputs will be used'.format(args.output_folder))\n", + "\n", + "# get the Azure ML run object\n", + "run = Run.get_context()\n", + "\n", + "# ./outputs/ folder is autotracked so should get uploaded at the end of the run\n", + "output_dir_AzureML_tracked = './outputs'\n", + "\n", + "crt_dir = os.getcwd()\n", + "\n", + "cli_command= \\\n", + "'cd /devito; /opt/conda/envs/fwi01_conda_env/bin/python '+ crt_dir +'/01_modelling.py' + \\\n", + "' --output_folder '+ crt_dir + output_dir_AzureML_tracked+ '/' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/01_modelling.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/devito_cli_py.log'\n", + "print('Running devito from cli on 01_modelling.py----BEGIN-----:') \n", + "print(cli_command); print('\\n');os.system(cli_command)\n", + "print('Running devito from cli on 01_modelling.py----END-----:\\n\\n')\n", + "\n", + "cli_command= \\\n", + "'cd /devito; papermill ' + \\\n", + "'./examples/seismic/tutorials/02_rtm.ipynb '+\\\n", + "crt_dir +'/outputs/02_rtm_output.ipynb ' + \\\n", + "'--log-output --no-progress-bar --kernel python3 ' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/02_rtm_output.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/papermill_cli.log'\n", + "\n", + "# FIXME - activate right conda env for running papermill from cli\n", + "activate_right_conda_env_fixed = False\n", + "if activate_right_conda_env_fixed:\n", + " print('Running papermill from cli on 02_rtm.ipynb----BEGIN-----:') \n", + " print(cli_command); print('\\n');os.system(cli_command)\n", + " print('Running papermill from cli on 02_rtm.ipynb----END-----:\\n\\n') \n", + "\n", + "\n", + "print('Running papermill from Python API on 03_fwi.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/03_fwi.ipynb',\n", + " crt_dir +'/outputs/03_fwi_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 03_fwi.ipynb----END-----:') \n", + "\n", + "print('Running papermill from Python API on 04_dask.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/04_dask.ipynb',\n", + " crt_dir +'/outputs/04_dask_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 04_dask.ipynb----END-----:') \n", + " \n", + "\n", + "os.system('pwd')\n", + "os.system('ls -l /')\n", + "os.system('ls -l ./')\n", + "os.system('ls -l ' +crt_dir + output_dir_AzureML_tracked)\n", + "run.log('training_message01: ', 'finished experiment')\n", + "print('\\n')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['azureml_01_modelling.py', '01_modelling.py']" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "script_path=os.path.join(*(script_folder))\n", + "os.listdir(script_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize workspace\n", + "\n", + "Initialize a workspace object from persisted configuration. If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, make sure the config file is present at .\\config.json" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Workspace name: ghiordanfwiws\n", + "Azure region: eastus2\n", + "Subscription id: 7899\n" + ] + } + ], + "source": [ + "ws = Workspace.from_config(\n", + " path=os.path.join(os.getcwd(),\n", + " os.path.join(*([workspace_config_dir, '.azureml', workspace_config_file]))))\n", + "print('Workspace name: ' + ws.name, \n", + " 'Azure region: ' + ws.location, \n", + " 'Subscription id: ' + ws.subscription_id[0:4], sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create an Azure ML experiment\n", + "Let's create an experiment named \"tf-mnist\" and a folder to hold the training scripts. The script runs will be recorded under the experiment in Azure." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "exp = Experiment(workspace=ws, name=experimentName)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve or create a Azure Machine Learning compute\n", + "Azure Machine Learning Compute is a service for provisioning and managing clusters of Azure virtual machines for running machine learning workloads. Let's create a new Azure Machine Learning Compute in the current workspace, if it doesn't already exist. We will then run the training script on this compute target.\n", + "\n", + "If we could not find the compute with the given name in the previous cell, then we will create a new compute here. This process is broken down into the following steps:\n", + "\n", + "1. Create the configuration\n", + "2. Create the Azure Machine Learning compute\n", + "\n", + "**This process will take a few minutes and is providing only sparse output in the process. Please make sure to wait until the call returns before moving to the next cell.**" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gpuclstfwi08'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", + "gpu_cluster_name = 'gpuclstfwi08'\n", + "gpu_cluster_name" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found existing gpu cluster\n" + ] + } + ], + "source": [ + "# Verify that cluster does not exist already\n", + "max_nodes_value = 5\n", + "try:\n", + " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", + " print(\"Found existing gpu cluster\")\n", + "except ComputeTargetException:\n", + " print(\"Could not find ComputeTarget cluster!\")\n", + " \n", + "# # Create a new gpucluster using code below\n", + "# # Specify the configuration for the new cluster\n", + "# compute_config = AmlCompute.provisioning_configuration(vm_size=\"Standard_NC6\",\n", + "# min_nodes=0,\n", + "# max_nodes=max_nodes_value)\n", + "# # Create the cluster with the specified name and configuration\n", + "# gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, compute_config)\n", + "\n", + "# # Wait for the cluster to complete, show the output log\n", + "# gpu_cluster.wait_for_completion(show_output=True)\n", + " \n", + " \n", + "# for demo purposes, show how clsuter properties can be altered post-creation\n", + "gpu_cluster.update(min_nodes=0, max_nodes=max_nodes_value, idle_seconds_before_scaledown=1200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create an Azure ML SDK estimator with custom docker image " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "80ea5d0977e248919b48c668a7f5b6ad", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574861911_2e9e0637?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1574861911_2e9e0637\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1574861911_2e9e0637\", \"created_utc\": \"2019-11-27T13:38:35.066726Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"mlflow.source.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-11-27T13:40:51.446761Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/55_azureml-execution-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt?sv=2019-02-02&sr=b&sig=TT5sa3P1MRW7fI4fRLIGTMoqSPvlGUOBqAYmM%2ByqpjY%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/65_job_prep-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt?sv=2019-02-02&sr=b&sig=PmGsvNZXAPsPIBpIk6p03sDvRsh3y5FTqGJlqLFQ7k8%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=pc1Jnlu3XArHtoScX9eVTC9ScVC4U6BIVfUDN4WzrLs%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/75_job_post-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/75_job_post-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt?sv=2019-02-02&sr=b&sig=rogM18tpVxhJaKR0et6UjN7dLTTKVoQIk1d7KXF76KU%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=MjKESRWtWOyWQH5B%2F8FaMdTSXN1bMmorNEuLFjIyRkM%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=Fq18rEAa1tUnD1URPEi2GwMROmqnhpZRK3mL4wrctUo%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"logs/azureml/729_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/logs/azureml/729_azureml.log?sv=2019-02-02&sr=b&sig=RaCw0b35z0EqgOjs1YgpCFSVw2TJ9anLbbhl4Ff6ua4%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=ROpfnTuQup%2BJz%2B9wDl9ByxbA5T4NDdzbUJp9635gk4A%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\"], [\"azureml-logs/65_job_prep-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\"], [\"logs/azureml/729_azureml.log\"]], \"run_duration\": \"0:02:16\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1574861911_2e9e0637\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-11-27 13:39:14,199|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-11-27 13:39:14,200|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-11-27 13:39:14,200|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-11-27 13:39:14,200|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-11-27 13:39:14,460|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-11-27 13:39:14,461|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-11-27 13:39:14,780|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:39:14,780|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:39:14,780|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:39:14,780|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:39:14,780|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-11-27 13:39:15,303|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-11-27 13:39:15,307|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-11-27 13:39:15,311|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-11-27 13:39:15,315|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-11-27 13:39:15,316|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:39:15,321|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,322|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-11-27 13:39:15,322|azureml.core.authentication|DEBUG|Time to expire 1814359.677606 seconds\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,323|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,323|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,349|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,354|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,359|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,364|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,368|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,372|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,372|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:39:15,373|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574861911_2e9e0637'\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'bef4b830-fd2f-4981-8d88-8e5b6456fcd5'\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG| 'request-id': 'bef4b830-fd2f-4981-8d88-8e5b6456fcd5'\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG|None\\n2019-11-27 13:39:15,374|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:39:15,374|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:39:15,374|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:39:15,374|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:39:15,431|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:39:15,431|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:39:15 GMT'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'bef4b830-fd2f-4981-8d88-8e5b6456fcd5'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:39:15,433|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1419,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:38:35.0667261+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:39:02.8734776+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574861911_2e9e0637/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574861911_2e9e0637/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574861911_2e9e0637/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:39:15,437|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:39:15,438|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:39:15,438|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:39:15,438|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-11-27 13:39:15,438|azureml.WorkerPool|DEBUG|[START]\\n2019-11-27 13:39:15,438|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-11-27 13:39:15,438|azureml.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:39:15,438|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:39:15,439|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-11-27 13:39:15,439|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-11-27 13:39:15,439|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574861911_2e9e0637/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574861911_2e9e0637\\n2019-11-27 13:39:15,439|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:39:15,439|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574861911_2e9e0637/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574861911_2e9e0637\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,335|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,339|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,340|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-11-27 13:39:17,344|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,349|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,353|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,357|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,358|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:39:17,358|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574861911_2e9e0637'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a0d8a26-00f5-4a0c-b9ed-080c13576b5f'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'request-id': '2a0d8a26-00f5-4a0c-b9ed-080c13576b5f'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:39:17,359|msrest.http_logger|DEBUG|None\\n2019-11-27 13:39:17,359|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:39:17,359|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:39:17,359|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:39:17,359|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:39:17 GMT'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a0d8a26-00f5-4a0c-b9ed-080c13576b5f'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1419,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:38:35.0667261+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:39:02.8734776+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574861911_2e9e0637/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574861911_2e9e0637/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574861911_2e9e0637/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:39:17,412|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:39:17,412|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:39:17,412|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:39:45,317|azureml.core.authentication|DEBUG|Time to expire 1814329.682784 seconds\\n2019-11-27 13:40:15,317|azureml.core.authentication|DEBUG|Time to expire 1814299.682403 seconds\\n2019-11-27 13:40:31,838|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:40:31,838|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:40:31,838|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:40:31,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:40:31,911|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-11-27 13:40:31,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-11-27 13:40:31,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574861911_2e9e0637/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574861911_2e9e0637\\n2019-11-27 13:40:31,912|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574861911_2e9e0637/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574861911_2e9e0637\\n2019-11-27 13:40:31,912|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-11-27 13:40:31,912|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637|INFO|complete is not setting status for submitted runs.\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-11-27 13:40:31,914|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-11-27 13:40:31,914|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-11-27 13:40:31,914|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:40:31,914|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-11-27 13:40:31,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-11-27 13:40:31,915|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-11-27 13:40:31,915|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:40:31,915|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-11-27 13:40:31,916|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:40:31,916|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-11-27 13:40:31,916|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-11-27 13:40:31,916|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:40:31,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-11-27 13:40:31,917|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574861911_2e9e0637/batch/metrics'\\n2019-11-27 13:40:31,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-11-27 13:40:31,917|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-11-27 13:40:31,917|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:40:31,917|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:40:31,917|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:40:31,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-11-27 13:40:31,918|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'fd06b231-4412-4943-bb03-c10b2b321592'\\n2019-11-27 13:40:31,918|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'request-id': 'fd06b231-4412-4943-bb03-c10b2b321592'\\n2019-11-27 13:40:31,918|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-11-27 13:40:31,918|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74 sdk_run'\\n2019-11-27 13:40:31,919|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-11-27 13:40:31,919|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:40:31,919|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"b5de25fb-c539-4fb1-892f-894e0843e150\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-11-27T13:40:31.837846Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-11-27 13:40:31,919|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:40:31,919|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:40:31,919|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:40:31,919|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:40:32 GMT'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'fd06b231-4412-4943-bb03-c10b2b321592'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:40:32,048|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:40:32,048|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:40:32,048|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:40:32,048|msrest.http_logger|DEBUG|\\n2019-11-27 13:40:32,048|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00026988983154296875 seconds.\\n\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:32,170|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:32,170|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-11-27 13:40:32,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-11-27 13:40:32,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-11-27 13:40:32,170|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.0.74\"}, \"loading\": false}" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# use a custom Docker image\n", + "from azureml.core.container_registry import ContainerRegistry\n", + "\n", + "image_name = docker_image_name\n", + "\n", + "# you can also point to an image in a private ACR\n", + "image_registry_details = ContainerRegistry()\n", + "image_registry_details.address = docker_repo_name\n", + "image_registry_details.username = os.getenv('ACR_USERNAME')\n", + "image_registry_details.password = os.getenv('ACR_PASSWORD') \n", + "\n", + "# don't let the system build a new conda environment\n", + "user_managed_dependencies = True\n", + "\n", + "# submit to a local Docker container. if you don't have Docker engine running locally, you can set compute_target to cpu_cluster.\n", + "script_params = {\n", + " '--output_folder': 'some_folder'\n", + "}\n", + "\n", + "\n", + "# distributed_training_conf = MpiConfiguration()\n", + "# distributed_training_conf.process_count_per_node = 2\n", + "\n", + "est = Estimator(source_directory=script_path, \n", + " compute_target=gpu_cluster,#'local', #gpu_cluster, \n", + " entry_script=azureml_training_script_file,\n", + " script_params=script_params,\n", + " use_docker=True,\n", + " custom_docker_image=image_name,\n", + " # uncomment below line to use your private ACR\n", + " image_registry_details=image_registry_details, \n", + " user_managed=user_managed_dependencies,\n", + " distributed_training=None,\n", + " node_count=1\n", + " )\n", + "est.run_config.environment.python.interpreter_path = python_path_in_docker_image\n", + "\n", + "run = exp.submit(est)\n", + "RunDetails(run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One can use the above link to currrent experiment run in Azure Portal to see tracked metrics, and images and output notebooks saved by azureml_training_script_full_file_name in {run_dir}/outputs on the remote compute target that are automatically saved by AzureML in the run history Azure portal pages." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "response = run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final print 7, time 15.769 seconds: Counter({'Completed': 1})\r" + ] + } + ], + "source": [ + "import time\n", + "from collections import Counter\n", + "#wait till all jobs finished\n", + "\n", + "def wait_for_run_list_to_finish(the_run_list):\n", + " finished_status_list = ['Completed', 'Failed']\n", + " printing_counter = 0\n", + " start_time = time.time()\n", + " while (not all((crt_queried_job.get_status() in finished_status_list) for crt_queried_job in the_run_list)):\n", + " time.sleep(2)\n", + " printing_counter+= 1\n", + " print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\")\n", + "# final status\n", + " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", + "wait_for_run_list_to_finish([run])" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "run_duration in seconds 108.573284\n", + "run_duration= 1m 48.573s\n" + ] + } + ], + "source": [ + "import datetime, math\n", + "def get_run_duration(azureml_exp_run):\n", + " run_details = azureml_exp_run.get_details()\n", + " run_duration = datetime.datetime.strptime(run_details['endTimeUtc'], \"%Y-%m-%dT%H:%M:%S.%fZ\") - \\\n", + " datetime.datetime.strptime(run_details['startTimeUtc'], \"%Y-%m-%dT%H:%M:%S.%fZ\")\n", + " return run_duration.total_seconds()\n", + "run_duration = get_run_duration(run)\n", + "\n", + "run_seconds, run_minutes = math.modf(run_duration/60)\n", + "print('run_duration in seconds {}'.format(run_duration))\n", + "print('run_duration= {0:.0f}m {1:.3f}s'.format(run_minutes, run_seconds*60))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Showing details for run 15\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4c927a9a365c46c89659cac33fb5b242", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574862227_525f5932?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1574862227_525f5932\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1574862227_525f5932\", \"created_utc\": \"2019-11-27T13:43:50.112507Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"mlflow.source.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":0,\\\"RunningNodeCount\\\":10,\\\"CurrentNodeCount\\\":10}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-11-27T13:46:12.593103Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/55_azureml-execution-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt?sv=2019-02-02&sr=b&sig=svX0XzF6dV2Q%2BStMcT%2BgFfppPMiJD8qOKfEydMeMFMI%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/65_job_prep-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt?sv=2019-02-02&sr=b&sig=0Bsb8vX2yn1Hll1IHHcShRbS53YyFHma%2FfZj0A1j3%2FA%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=dwKEImPP5vJC8sx7NZgeln%2F392uvl0VFmuBRMdHVoqA%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/75_job_post-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/75_job_post-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt?sv=2019-02-02&sr=b&sig=nh7cg3qW12z%2FgH%2Fmd1TtxWkWXB1egCHAnaxSluEyKt8%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=TyDPhQzs6GRrPDAPRabyZoYbK1NxlyPWTb7llH1OQ0I%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=cVTzsFqFGHhh14TdeBhKGHlFNkkpI6ezUhwx2JAacZc%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"logs/azureml/729_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/logs/azureml/729_azureml.log?sv=2019-02-02&sr=b&sig=S4Bbzo2wIG5HWCNTw2akX5exlL3LpHPAsArMA4b5kOY%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=brICMUePTeIK8l2uHAiQOv5J2mGLlkbmh%2FIt5IUqC28%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\"], [\"azureml-logs/65_job_prep-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\"], [\"logs/azureml/729_azureml.log\"]], \"run_duration\": \"0:02:22\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1574862227_525f5932\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-11-27 13:44:35,626|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-11-27 13:44:35,627|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-11-27 13:44:35,627|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-11-27 13:44:35,627|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-11-27 13:44:35,894|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-11-27 13:44:35,895|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-11-27 13:44:36,211|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:44:36,211|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:44:36,211|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:44:36,211|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:44:36,211|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-11-27 13:44:36,737|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-11-27 13:44:36,741|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-11-27 13:44:36,745|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-11-27 13:44:36,749|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-11-27 13:44:36,750|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:44:36,755|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,755|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-11-27 13:44:36,756|azureml.core.authentication|DEBUG|Time to expire 1814353.243919 seconds\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,785|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,789|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,795|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,799|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,803|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,807|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,808|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:44:36,808|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574862227_525f5932'\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '42fd0f32-95de-47fb-832b-4fe844f16942'\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG| 'request-id': '42fd0f32-95de-47fb-832b-4fe844f16942'\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG|None\\n2019-11-27 13:44:36,809|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:44:36,809|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:44:36,809|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:44:36,809|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:44:36 GMT'\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '42fd0f32-95de-47fb-832b-4fe844f16942'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1434,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:43:50.1125076+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 8,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:44:18.8607892+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":0,\\\\\\\"RunningNodeCount\\\\\\\":10,\\\\\\\"CurrentNodeCount\\\\\\\":10}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574862227_525f5932/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574862227_525f5932/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574862227_525f5932/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:44:36,862|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:44:36,862|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:44:36,862|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:44:36,863|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-11-27 13:44:36,863|azureml.WorkerPool|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574862227_525f5932/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574862227_525f5932\\n2019-11-27 13:44:36,863|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:44:36,863|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574862227_525f5932/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574862227_525f5932\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,717|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,721|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,722|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-11-27 13:44:38,727|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,731|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,735|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,739|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,740|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:44:38,740|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574862227_525f5932'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '289c750b-cea3-41df-b422-7eddd50f2daf'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG| 'request-id': '289c750b-cea3-41df-b422-7eddd50f2daf'\\n2019-11-27 13:44:38,741|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:44:38,741|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:44:38,741|msrest.http_logger|DEBUG|None\\n2019-11-27 13:44:38,741|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:44:38,741|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:44:38,741|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:44:38,741|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:44:38 GMT'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '289c750b-cea3-41df-b422-7eddd50f2daf'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:44:38,794|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1434,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:43:50.1125076+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 8,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:44:18.8607892+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":0,\\\\\\\"RunningNodeCount\\\\\\\":10,\\\\\\\"CurrentNodeCount\\\\\\\":10}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574862227_525f5932/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574862227_525f5932/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574862227_525f5932/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:44:38,795|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:44:38,796|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:44:38,796|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:45:06,751|azureml.core.authentication|DEBUG|Time to expire 1814323.248956 seconds\\n2019-11-27 13:45:36,754|azureml.core.authentication|DEBUG|Time to expire 1814293.246046 seconds\\n2019-11-27 13:45:52,153|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:45:52,153|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:45:52,153|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:45:52,230|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:45:52,230|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-11-27 13:45:52,230|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-11-27 13:45:52,231|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574862227_525f5932/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574862227_525f5932\\n2019-11-27 13:45:52,231|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574862227_525f5932/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574862227_525f5932\\n2019-11-27 13:45:52,231|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-11-27 13:45:52,231|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932|INFO|complete is not setting status for submitted runs.\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,232|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,233|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,233|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,233|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-11-27 13:45:52,233|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-11-27 13:45:52,233|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-11-27 13:45:52,233|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:45:52,233|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-11-27 13:45:52,233|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-11-27 13:45:52,234|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-11-27 13:45:52,234|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:45:52,234|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-11-27 13:45:52,235|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-11-27 13:45:52,235|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-11-27 13:45:52,235|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:45:52,235|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-11-27 13:45:52,236|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:45:52,236|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-11-27 13:45:52,236|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574862227_525f5932/batch/metrics'\\n2019-11-27 13:45:52,236|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:45:52,236|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-11-27 13:45:52,237|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-11-27 13:45:52,237|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:45:52,237|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:45:52,237|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:45:52,237|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-11-27 13:45:52,237|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-11-27 13:45:52,237|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-11-27 13:45:52,237|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0bb73131-a79c-4272-a377-1223fa4f9232'\\n2019-11-27 13:45:52,237|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG| 'request-id': '0bb73131-a79c-4272-a377-1223fa4f9232'\\n2019-11-27 13:45:52,238|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-11-27 13:45:52,238|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74 sdk_run'\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"f4ff6c5a-d4ce-4443-8009-e2ae8ccf4068\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-11-27T13:45:52.152964Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-11-27 13:45:52,238|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:45:52,238|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:45:52,238|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:45:52,238|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:45:52 GMT'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0bb73131-a79c-4272-a377-1223fa4f9232'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:45:52,365|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:45:52,365|msrest.http_logger|DEBUG|\\n2019-11-27 13:45:52,365|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-11-27 13:45:52,488|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002124309539794922 seconds.\\n\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-11-27 13:45:52,489|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.0.74\"}, \"loading\": false}" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Counter16: submission of job 16 on 10 nodes took 8.287715435028076 seconds \n", + "run list length 16\n", + "Counter17: submission of job 17 on 10 nodes took 7.936461687088013 seconds \n", + "run list length 17\n", + "Counter18: submission of job 18 on 10 nodes took 8.747953414916992 seconds \n", + "run list length 18\n", + "Counter19: submission of job 19 on 10 nodes took 7.859047889709473 seconds \n", + "run list length 19\n" + ] + } + ], + "source": [ + "import time\n", + "from IPython.display import clear_output\n", + "\n", + "no_of_jobs = 20\n", + "no_of_nodes = 10\n", + "\n", + "job_counter = 0\n", + "print_cycle = 7\n", + "run_list = []\n", + "submit_time_list = []\n", + "for crt_nodes in range(no_of_nodes, (no_of_nodes+1)):\n", + " gpu_cluster.update(min_nodes=0, max_nodes=crt_nodes, idle_seconds_before_scaledown=1200)\n", + " clust_start_time = time.time()\n", + " for crt_job in range(1, no_of_jobs):\n", + " job_counter+= 1\n", + " start_time = time.time()\n", + " run = exp.submit(est)\n", + " end_time = time.time()\n", + " run_time = end_time - start_time\n", + " run_list.append(run)\n", + " submit_time_list.append(run_time)\n", + " print('Counter{}: submission of job {} on {} nodes took {} seconds '.format(job_counter, crt_job, crt_nodes, run_time))\n", + " print('run list length {}'.format(len(run_list)))\n", + " if ((job_counter-1) % print_cycle) == 0:\n", + " clear_output()\n", + " print('Showing details for run {}'.format(job_counter))\n", + " RunDetails(run).show()\n", + "# [all_jobs_done = True if (('Completed'==crt_queried_job.get_status()) for crt_queried_job in run_list)]" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 9.95614195, 11.63660598, 11.59176159, 8.24005198, 39.15067554,\n", + " 7.82959986, 10.54416084, 7.90288782, 9.45449305, 8.52095628,\n", + " 12.79820418, 8.11050415, 19.89081311, 7.72854948, 8.98733926,\n", + " 8.28771544, 7.93646169, 8.74795341, 7.85904789])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "(array([0, 0, 0, 1, 5, 3, 2, 1, 1]),\n", + " array([ 6. , 6.44444444, 6.88888889, 7.33333333, 7.77777778,\n", + " 8.22222222, 8.66666667, 9.11111111, 9.55555556, 10. ]))" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "np.asarray(submit_time_list)\n", + "np.histogram(np.asarray(submit_time_list), bins=np.linspace(6.0, 10.0, num=10), density=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final print 87, time 224.009 seconds: Counter({'Completed': 13, 'Failed': 6})izing': 1})'Running': 1})ueued': 1})\r" + ] + } + ], + "source": [ + "def wait_for_run_list_to_finish(the_run_list, plot_results=True):\n", + " finished_status_list = ['Completed', 'Failed']\n", + " printing_counter = 0\n", + " start_time = time.time()\n", + " while (not all((crt_queried_job.get_status() in finished_status_list) for crt_queried_job in the_run_list)):\n", + " time.sleep(2)\n", + " printing_counter+= 1\n", + " crt_status = Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list])\n", + " print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(crt_status)), end=\"\\r\")\n", + " if plot_results:\n", + "# import numpy as np\n", + " import matplotlib.pyplot as plt\n", + " plt.bar(crt_status.keys(), crt_status.values())\n", + " plt.show()\n", + " \n", + "# indexes = np.arange(len(labels))\n", + "# width = 1\n", + "\n", + "# plt.bar(indexes, values, width)\n", + "# plt.xticks(indexes + width * 0.5, labels)\n", + "# plt.show()\n", + "\n", + "# from pandas import Series\n", + "# crt_status = Series([crt_queried_job.get_status() for crt_queried_job in the_run_list])\n", + "# status_counts = crt_status.value_counts().sort_index()\n", + "# print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + "# str(status_counts)), end=\"\\r\")\n", + "# final status\n", + " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", + "\n", + " \n", + " \n", + "wait_for_run_list_to_finish(run_list, plot_results=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "run_durations = [get_run_duration(crt_queried_job) for crt_queried_job in run_list]\n", + "run_statuses = [crt_queried_job.get_status() for crt_queried_job in run_list]" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 9, 4, 13, 10, 5, 14, 18, 7, 8, 16, 11, 6, 1, 12, 15, 3,\n", + " 2, 17])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 4.819322 101.946523 106.5569 107.866548 109.776871 111.461206\n", + " 113.732314 117.302445 122.746341 135.938763 156.116081 157.700891\n", + " 158.001392 162.059693 163.769164 164.909099 166.151158 175.955566\n", + " 185.333479]\n", + "['Failed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Failed' 'Failed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Failed' 'Failed'\n", + " 'Failed']\n" + ] + }, + { + "data": { + "text/plain": [ + "array([ 0, 9, 4, 13, 10, 5, 14, 18, 7, 8, 16, 11, 6, 1, 12, 15, 3,\n", + " 2, 17])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 4.819322 101.946523 106.5569 107.866548 109.776871 111.461206\n", + " 113.732314 117.302445 122.746341 135.938763 156.116081 157.700891\n", + " 158.001392 162.059693 163.769164 164.909099 166.151158 175.955566\n", + " 185.333479]\n", + "['Failed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Failed' 'Failed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Failed' 'Failed'\n", + " 'Failed']\n" + ] + }, + { + "data": { + "text/plain": [ + "(array([0, 0, 0, 6, 2, 1, 7, 1, 1]),\n", + " array([ 50. , 66.66666667, 83.33333333, 100. ,\n", + " 116.66666667, 133.33333333, 150. , 166.66666667,\n", + " 183.33333333, 200. ]))" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_durations = np.asarray(run_durations)\n", + "run_statuses = np.asarray(run_statuses)\n", + "\n", + "extreme_k = 20\n", + "#longest runs\n", + "indices = np.argsort(run_durations)[-extreme_k:]\n", + "indices\n", + "print(run_durations[indices])\n", + "print(run_statuses[indices])\n", + "#shortest runs\n", + "indices = np.argsort(run_durations)[0:extreme_k]\n", + "indices\n", + "print(run_durations[indices])\n", + "print(run_statuses[indices])\n", + "\n", + "#run_durations histogram - counts and bins\n", + "np.histogram(run_durations, bins=np.linspace(50, 200, num=10), density=False)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finished running 030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito!\n" + ] + } + ], + "source": [ + "print('Finished running 030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito!')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/contrib/examples/imaging/azureml_devito/notebooks/README.md b/contrib/examples/imaging/azureml_devito/notebooks/README.md new file mode 100644 index 00000000..2de32570 --- /dev/null +++ b/contrib/examples/imaging/azureml_devito/notebooks/README.md @@ -0,0 +1,28 @@ +# DeepSeismic + +## Imaging + +This tutorial shows how to run devito tutorial notebooks in Azure. For best experience use a linux Azure DSVM and Jupyter notebook to run the notebooks. + +### Setting up Environment + +This conda .yml file should be used to create the conda environment, before starting Jupyter: + +``` +channels: + - anaconda +dependencies: + - python=3.6 + - numpy + - cython + - notebook + - nb_conda + - scikit-learn + - pip + - pip: + - python-dotenv + - papermill[azure] + - azureml-sdk[notebooks,automl,explain] +``` + +You may also need az cli to create an ACR in notebook 000_Setup_GeophysicsTutorial_FWI_Azure_devito, and then push and pull images. One can also create the ACR via Azure [portal](https://azure.microsoft.com/). \ No newline at end of file diff --git a/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log b/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log index 22b30a9b..01c1af62 100644 --- a/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log +++ b/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log @@ -1,155 +1,55 @@ ============================= test session starts ============================== -platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 +platform linux -- Python 3.6.9, pytest-5.3.0, py-1.8.0, pluggy-0.13.1 rootdir: /devito, inifile: setup.cfg -plugins: nbval-0.9.3, cov-2.7.1 -collected 912 items / 2 skipped / 910 selected +plugins: nbval-0.9.3, cov-2.8.1 +collected 1053 items / 2 skipped / 1051 selected tests/test_adjoint.py .......................... [ 2%] -tests/test_autotuner.py ..........s.... [ 4%] -tests/test_builtins.py ....s.s.s [ 5%] -tests/test_caching.py .................................. [ 9%] -tests/test_checkpointing.py ....... [ 9%] -tests/test_constant.py . [ 10%] -tests/test_data.py ..........................sssssssssssssss.ss.. [ 15%] -tests/test_dependency_bugs.py . [ 15%] +tests/test_autotuner.py ..........s..... [ 3%] +tests/test_builtins.py ....s...............s..s [ 6%] +tests/test_caching.py .................................................. [ 11%] + [ 11%] +tests/test_checkpointing.py ....... [ 11%] +tests/test_constant.py . [ 11%] +tests/test_data.py ..........................ssssssssssssssssss.ss.. [ 16%] +tests/test_dependency_bugs.py . [ 16%] tests/test_derivatives.py .............................................. [ 20%] -........................................................................ [ 28%] -.........................................................F...F.......... [ 36%] -...... [ 36%] -tests/test_differentiable.py .. [ 36%] -tests/test_dimension.py ............................... [ 40%] -tests/test_dle.py ...................................................... [ 46%] -......................................... [ 50%] -tests/test_docstrings.py ................ [ 52%] -tests/test_dse.py ......x............................................... [ 58%] -.......................s.... [ 61%] -tests/test_gradient.py .... [ 61%] -tests/test_interpolation.py ........................ [ 64%] -tests/test_ir.py ....................................................... [ 70%] -................ [ 72%] -tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 78%] -ss [ 78%] -tests/test_operator.py ................................................. [ 83%] -........................................................................ [ 91%] -.................. [ 93%] -tests/test_pickle.py .................ss. [ 95%] -tests/test_resample.py . [ 96%] -tests/test_save.py .. [ 96%] -tests/test_subdomains.py ... [ 96%] -tests/test_symbolic_coefficients.py .....F [ 97%] +........................................................................ [ 27%] +........................................................................ [ 34%] +...... [ 35%] +tests/test_differentiable.py .. [ 35%] +tests/test_dimension.py ............................... [ 38%] +tests/test_dle.py ...................................................... [ 43%] +........................................... [ 47%] +tests/test_docstrings.py ................ [ 49%] +tests/test_dse.py ......x............................................... [ 54%] +................x..........s.... [ 57%] +tests/test_gradient.py .... [ 57%] +tests/test_interpolation.py ........................ [ 59%] +tests/test_ir.py ....................................................... [ 65%] +................ [ 66%] +tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 71%] +ss [ 71%] +tests/test_operator.py ................................................. [ 76%] +........................................................................ [ 83%] +................................ [ 86%] +tests/test_pickle.py .................ss. [ 88%] +tests/test_resample.py . [ 88%] +tests/test_save.py .. [ 88%] +tests/test_staggered_utils.py ......... [ 89%] +tests/test_subdomains.py ... [ 89%] +tests/test_symbolic_coefficients.py .....F [ 90%] +tests/test_tensors.py .................................................. [ 95%] +........................... [ 97%] tests/test_timestepping.py ....... [ 98%] tests/test_tools.py ..... [ 98%] tests/test_tti.py .... [ 99%] tests/test_visitors.py ......... [100%] =================================== FAILURES =================================== -____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________ - -self = , so = 12, ndim = 1 -derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 - - @pytest.mark.parametrize('so', [2, 4, 8, 12]) - @pytest.mark.parametrize('ndim', [1, 2]) - @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [ - ('dx', 'dx', -1), - ('dx2', 'dx2', 1), - ('dxl', 'dxr', -1), - ('dxr', 'dxl', -1)]) - def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): - grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) - x = grid.dimensions[0] - f = Function(name='f', grid=grid, space_order=so) - f_deriv = Function(name='f_deriv', grid=grid, space_order=so) - g = Function(name='g', grid=grid, space_order=so) - g_deriv = Function(name='g_deriv', grid=grid, space_order=so) - - # Fill f and g with smooth cos/sin - Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply() - # Check symbolic expression are expected ones for the adjoint .T - deriv = getattr(f, derivative) - expected = adjoint_coeff * getattr(f, adjoint_name).evaluate - assert deriv.T.evaluate == expected - - # Compute numerical derivatives and verify dot test - # i.e = - - eq_f = Eq(f_deriv, deriv) - eq_g = Eq(g_deriv, getattr(g, derivative).T) - - op = Operator([eq_f, eq_g]) - op() - - a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1)) - b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) -> assert np.isclose(1 - a/b, 0, atol=1e-5) -E assert False -E + where False = ((1 - (0.22231627 / 0.2223134)), 0, atol=1e-05) -E + where = np.isclose - -tests/test_derivatives.py:394: AssertionError ------------------------------ Captured stderr call ----------------------------- -Operator `Kernel` run in 0.01 s -Operator `Kernel` run in 0.01 s ------------------------------- Captured log call ------------------------------- -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________ - -self = , so = 12, ndim = 2 -derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 - - @pytest.mark.parametrize('so', [2, 4, 8, 12]) - @pytest.mark.parametrize('ndim', [1, 2]) - @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [ - ('dx', 'dx', -1), - ('dx2', 'dx2', 1), - ('dxl', 'dxr', -1), - ('dxr', 'dxl', -1)]) - def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): - grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) - x = grid.dimensions[0] - f = Function(name='f', grid=grid, space_order=so) - f_deriv = Function(name='f_deriv', grid=grid, space_order=so) - g = Function(name='g', grid=grid, space_order=so) - g_deriv = Function(name='g_deriv', grid=grid, space_order=so) - - # Fill f and g with smooth cos/sin - Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply() - # Check symbolic expression are expected ones for the adjoint .T - deriv = getattr(f, derivative) - expected = adjoint_coeff * getattr(f, adjoint_name).evaluate - assert deriv.T.evaluate == expected - - # Compute numerical derivatives and verify dot test - # i.e = - - eq_f = Eq(f_deriv, deriv) - eq_g = Eq(g_deriv, getattr(g, derivative).T) - - op = Operator([eq_f, eq_g]) - op() - - a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1)) - b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) -> assert np.isclose(1 - a/b, 0, atol=1e-5) -E assert False -E + where False = ((1 - (11.3381815 / 11.338006)), 0, atol=1e-05) -E + where = np.isclose - -tests/test_derivatives.py:394: AssertionError ------------------------------ Captured stderr call ----------------------------- -Operator `Kernel` run in 0.01 s -/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c: In function ‘Kernel’: -/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas] - #pragma omp simd aligned(f,f_deriv,g,g_deriv:32) - -Operator `Kernel` run in 0.01 s ------------------------------- Captured log call ------------------------------- -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s ______________________ TestSC.test_function_coefficients _______________________ -self = +self = def test_function_coefficients(self): """Test that custom function coefficients return the expected result""" @@ -171,9 +71,9 @@ self = wdims = as_tuple(wdims) w = Function(name='w', dimensions=wdims, shape=wshape) - w.data[:, :, 0] = -0.5/grid.spacing[0] - w.data[:, :, 1] = 0.0 - w.data[:, :, 2] = 0.5/grid.spacing[0] + w.data[:, :, 0] = 0.0 + w.data[:, :, 1] = -1.0/grid.spacing[0] + w.data[:, :, 2] = 1.0/grid.spacing[0] f_x_coeffs = Coefficient(1, f0, x, w) @@ -193,10 +93,10 @@ self = > assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0)) E assert Data(False) -E + where Data(False) = (Data([[[ True, True, True, True],\n [False, False, False, False],\n [ True, True, True, True],\n ...True],\n [ True, True, True, True],\n [ True, True, True, True],\n [ True, True, True, True]]])) -E + where = np.all -E + and Data([[[ True, True, True, True],\n [False, False, False, False],\n [ True, True, True, True],\n ...True],\n [ True, True, True, True],\n [ True, True, True, True],\n [ True, True, True, True]]]) = ((Data([[[-11.4375 , -11.4375 , -11.4375 , -11.4375 ],\n [ 49.59375, 49.59375, 49.59375, 49.59375],\n [ -6.... [-24.25 , -24.25 , -24.25 , -24.25 ],\n [ -1.9375 , -1.9375 , -1.9375 , -1.9375 ]]], dtype=float32) - Data([[[-11.437502 , -11.437502 , -11.437502 , -11.437502 ],\n [ 49.59374 , 49.59374 , 49.59374 , 49.59374 ....249996 , -24.249996 , -24.249996 ],\n [ -1.9375012, -1.9375012, -1.9375012, -1.9375012]]],\n dtype=float32)), 0.0, atol=1e-05, rtol=0) -E + where = np.isclose +E + where Data(False) = (Data([[[False, False, False, False],\n [False, False, False, False],\n [ True, True, True, True],\n ...alse],\n [False, False, False, False],\n [False, False, False, False],\n [ True, True, True, True]]])) +E + where = np.all +E + and Data([[[False, False, False, False],\n [False, False, False, False],\n [ True, True, True, True],\n ...alse],\n [False, False, False, False],\n [False, False, False, False],\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\n [ 3327., 3327., 3327., 3327.],\n [-3414., -3414., -3414., -341...3., 383., 383.],\n [ -598., -598., -598., -598.],\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\n [ 341. , 341. , 341. , 341. ]]],\n dtype=float32)), 0.0, atol=1e-05, rtol=0) +E + where = np.isclose tests/test_symbolic_coefficients.py:96: AssertionError ----------------------------- Captured stderr call ----------------------------- @@ -205,12 +105,12 @@ tests/test_symbolic_coefficients.py:96: AssertionError #pragma omp simd aligned(f0,w:32) Operator `Kernel` run in 0.01 s -/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c: In function ‘Kernel’: -/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas] +/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c: In function ‘Kernel’: +/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas] #pragma omp simd aligned(f1:32) Operator `Kernel` run in 0.01 s ------------------------------ Captured log call ------------------------------- -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -======= 3 failed, 828 passed, 82 skipped, 1 xfailed in 830.53s (0:13:50) ======= +INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s +INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s +====== 1 failed, 967 passed, 85 skipped, 2 xfailed in 1058.91s (0:17:38) ======= From e70935fa9406956ba776408df8a02ff9a63a5084 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Thu, 28 Nov 2019 01:46:54 +0000 Subject: [PATCH 100/207] azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing --- ...0_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index b38369a3..42d4ca96 100755 --- a/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -804,18 +804,8 @@ ] }, { - "cell_type": "code", - "execution_count": 23, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finished running 000_Setup_GeophysicsTutorial_FWI_Azure_devito!\n" - ] - } - ], "source": [ "print('Finished running 000_Setup_GeophysicsTutorial_FWI_Azure_devito!')" ] From ecb91b139837947743c7061ee20591a62c170554 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Thu, 28 Nov 2019 09:57:06 -0500 Subject: [PATCH 101/207] TESTS: added notebook integreation tests. (#65) * TESTS: added notebook integreation tests. * TEST: typo in env name --- environment/anaconda/local/environment.yml | 11 ++++++-- ..._block_training_and_evaluation_local.ipynb | 28 +++++++++++++++++-- .../notebooks/HRNet_demo_notebook.ipynb | 8 ++++-- tests/__init__.py | 0 tests/cicd/notebooks_build.yml | 12 +++----- tests/cicd/src/conftest.py | 12 ++++++++ tests/cicd/src/notebook_integration_tests.py | 19 +++++++++++++ tests/notebooks_common.py | 9 ++++++ 8 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/cicd/src/conftest.py create mode 100644 tests/cicd/src/notebook_integration_tests.py create mode 100644 tests/notebooks_common.py diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 29a5b3b4..f24f2799 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -15,7 +15,13 @@ dependencies: - scikit-learn=0.21.3 - tensorflow==2.0 - tqdm==4.39.0 - - itkwidgets==0.23.1 + - itkwidgets==0.23.1 + - pytest + - papermill>=1.0.1 + #- jsonschema + #- pyrsistent + #- jupyter_core + #- pygments - pip: - segyio==1.8.8 - pytorch-ignite==0.3.0.dev20191105 # pre-release until stable available @@ -32,5 +38,4 @@ dependencies: - black - pylint - scipy==1.1.0 - - jupytext==1.3.0 - - pytest + - jupytext==1.3.0 diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 03d061ad..3675f826 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -170,6 +170,27 @@ " config = yacs.config.load_cfg(f_read)\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Override parameters in case we use papermill" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "parameters" + ] + }, + "outputs": [], + "source": [ + "max_epochs = config.TRAIN.END_EPOCH \n", + "max_snaphsots = config.TRAIN.SNAPSHOTS" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -388,7 +409,7 @@ ")\n", "\n", "# learning rate scheduler\n", - "scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS\n", + "scheduler_step = max_epochs // max_snapshots\n", "snapshot_duration = scheduler_step * len(train_loader)\n", "scheduler = CosineAnnealingScheduler(\n", " optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration\n", @@ -472,7 +493,7 @@ "metadata": {}, "outputs": [], "source": [ - "trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH)" + "trainer.run(train_loader, max_epochs=max_epochs)" ] }, { @@ -711,6 +732,7 @@ } ], "metadata": { + "celltoolbar": "Tags", "kernelspec": { "display_name": "Python 3", "language": "python", @@ -726,7 +748,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb index 1a8ec834..2fb58ec8 100644 --- a/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb @@ -164,7 +164,8 @@ "# Setting to None will run whole dataset\n", "# Use only if you want to check things are running and don't want to run\n", "# through whole dataset\n", - "max_epochs = config.TRAIN.END_EPOCH # The number of epochs to run in training" + "max_epochs = config.TRAIN.END_EPOCH # The number of epochs to run in training\n", + "max_snapshots = config.TRAIN.SNAPSHOTS" ] }, { @@ -268,7 +269,7 @@ "load_log_configuration(config.LOG_CONFIG)\n", "logger = logging.getLogger(__name__)\n", "logger.debug(config.WORKERS)\n", - "scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS\n", + "scheduler_step = max_epochs // max_snapshots\n", "torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK\n", "\n", "torch.manual_seed(config.SEED)\n", @@ -617,6 +618,7 @@ } ], "metadata": { + "celltoolbar": "Tags", "kernelspec": { "display_name": "Python 3", "language": "python", @@ -632,7 +634,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/cicd/notebooks_build.yml b/tests/cicd/notebooks_build.yml index 003cec03..d52ba42e 100644 --- a/tests/cicd/notebooks_build.yml +++ b/tests/cicd/notebooks_build.yml @@ -23,12 +23,10 @@ jobs: steps: - bash: | echo "Running setup..." - pwd ls git branch uname -ra - # make sure we have the latest and greatest # yes | conda env update -f environment/anaconda/local/environment.yml conda activate seismic-interpretation @@ -39,7 +37,6 @@ jobs: # copy your model files like so - using dummy file to illustrate azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name - - job: HRNET_demo timeoutInMinutes: 5 displayName: HRNET demo @@ -47,8 +44,8 @@ jobs: name: deepseismicagentpool steps: - bash: | - echo "TODO: HRNET_demo placeholder" conda activate seismic-interpretation + pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/HRNet_demo_notebook.ipynb - job: F3_block_training_and_evaluation_local timeoutInMinutes: 5 @@ -56,7 +53,6 @@ jobs: pool: name: deepseismicagentpool steps: - - bash: | - echo "TODO: F3_block_training_and_evaluation_local placeholder" - conda activate seismic-interpretation - + - bash: | + conda activate seismic-interpretation + pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb diff --git a/tests/cicd/src/conftest.py b/tests/cicd/src/conftest.py new file mode 100644 index 00000000..65b257b3 --- /dev/null +++ b/tests/cicd/src/conftest.py @@ -0,0 +1,12 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +def pytest_addoption(parser): + parser.addoption("--nbname", action="store", type=str, default=None) + +def pytest_generate_tests(metafunc): + # This is called for every test. Only get/set command line arguments + # if the argument is specified in the list of test "fixturenames". + option_value = metafunc.config.option.nbname + if 'nbname' in metafunc.fixturenames and option_value is not None: + metafunc.parametrize("nbname", [option_value]) diff --git a/tests/cicd/src/notebook_integration_tests.py b/tests/cicd/src/notebook_integration_tests.py new file mode 100644 index 00000000..07c49687 --- /dev/null +++ b/tests/cicd/src/notebook_integration_tests.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import pytest +import papermill as pm + +from tests.notebooks_common import OUTPUT_NOTEBOOK, KERNEL_NAME + +# don't add any markup as this just runs any notebook which name is supplied +# @pytest.mark.integration +# @pytest.mark.notebooks +def test_notebook_run(nbname): + pm.execute_notebook( + nbname, + OUTPUT_NOTEBOOK, + kernel_name=KERNEL_NAME, + parameters={"max_epochs":1, "max_snapshots":1}, + cwd="examples/interpretation/notebooks" + ) diff --git a/tests/notebooks_common.py b/tests/notebooks_common.py new file mode 100644 index 00000000..e9f632dd --- /dev/null +++ b/tests/notebooks_common.py @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os + +# Unless manually modified, python3 should be the name of the current jupyter kernel +# that runs on the activated conda environment +KERNEL_NAME = "python3" +OUTPUT_NOTEBOOK = "output.ipynb" From bb28247018840e6a2142480524a354457e458171 Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Thu, 28 Nov 2019 18:21:40 +0000 Subject: [PATCH 102/207] Addressing a number of minor issues with README and broken links (#67) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fix for seyviewer and mkdir splits in README + broken link in F3 notebook * issue edits to README * download complete message --- DeepSeismicLogo.jpg | Bin 0 -> 154387 bytes README.md | 160 +++++++++++------- ..._block_training_and_evaluation_local.ipynb | 4 +- scripts/download_penobscot.sh | 4 +- 4 files changed, 108 insertions(+), 60 deletions(-) create mode 100644 DeepSeismicLogo.jpg diff --git a/DeepSeismicLogo.jpg b/DeepSeismicLogo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6c68879d4d7416d278f462f76068e159a84c9593 GIT binary patch literal 154387 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

CW+=ZK;liK`ayr|%7X(%@=XbY~P&emeMXNBH@F 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 Date: Sat, 30 Nov 2019 20:47:18 +0000 Subject: [PATCH 103/207] Added Yacs info to README.md (#69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added info on yacs files --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c92ae8d6..a6e02929 100644 --- a/README.md +++ b/README.md @@ -114,12 +114,31 @@ python -m ipykernel install --user --name seismic-interpretation #### Experiments -We also provide scripts for a number of experiments we conducted using different segmentation approaches. These experiments are available under `experiments/interpretation`, and can be used as examples. Please refer to individual experiment README files for more information. +We also provide scripts for a number of experiments we conducted using different segmentation approaches. These experiments are available under `experiments/interpretation`, and can be used as examples. Within each experiment start from the `train.sh` and `test.sh` scripts under the `local/` (single GPU) and `distributed/` (multiple GPUs) directories, which invoke the corresponding python scripts, `train.py` and `test.py`. Take a look at the experiment configurations (see Experiment Configuration Files section below) for experiment options and modify if necessary. + +Please refer to individual experiment README files for more information. + +#### Configuration Files +We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manage configuration options for the experiments. There are three ways to pass arguments to the experiment scripts (e.g. train.py or test.py): + +- __default.py__ - A project config file `default.py` is a one-stop reference point for all configurable options, and provides sensible defaults for all arguments. If no arguments are passed to `train.py` or `test.py` script (e.g. `python train.py`), the arguments are by default loaded from `default.py`. Please take a look at `default.py` to familiarize yourself with the experiment arguments the script you run uses. + +- __yml config files__ - YAML configuration files under `configs/` are typically created one for each experiment. These are meant to be used for repeatable experiment runs and reproducible settings. Each configuration file only overrides the options that are changing in that experiment (e.g. options loaded from `defaults.py` during an experiment run will be overridden by arguments loaded from the yaml file). As an example, to use yml configuration file with the training script, run: + + ``` + python train.py --cfg "configs/hrnet.yaml" + ``` + +- __command line__ - Finally, options can be passed in through `options` argument, and those will override arguments loaded from the configuration file. We created CLIs for all our scripts (using Python Fire library), so you can pass these options via command-line arguments, like so: + + ``` + python train.py DATASET.ROOT "/mnt/dutchf3" TRAIN.END_EPOCH 10 + ``` ### Pretrained Models #### HRNet -To achieve the same results as the benchmarks above you will need to download the HRNet model pretrained on ImageNet. This can be found [here](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk). Download this to your local drive and make sure you add the path to the Yacs configuration script. +To achieve the same results as the benchmarks above you will need to download the HRNet model pretrained on ImageNet. This can be found [here](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk). Download this to your local drive and make sure you add the path to the experiment (or notebook) configuration file. ### Viewers (optional) From da0831617ef535778fe30cc8f9d605a33ceb5f7f Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Mon, 2 Dec 2019 13:33:03 +0000 Subject: [PATCH 104/207] MODEL.PRETRAINED key missing in default.py (#70) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added MODEL.PRETRAINED key to default.py --- experiments/interpretation/penobscot/local/default.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/experiments/interpretation/penobscot/local/default.py b/experiments/interpretation/penobscot/local/default.py index 0d63a530..fa8e540e 100644 --- a/experiments/interpretation/penobscot/local/default.py +++ b/experiments/interpretation/penobscot/local/default.py @@ -51,6 +51,8 @@ _C.MODEL = CN() _C.MODEL.NAME = "resnet_unet" _C.MODEL.IN_CHANNELS = 1 +_C.MODEL.PRETRAINED = "" +_C.MODEL.EXTRA = CN(new_allowed=True) # training _C.TRAIN = CN() From 35d2a14310c983fc0e4b50f4da7112a0685d7c8c Mon Sep 17 00:00:00 2001 From: Wee Hyong Tok Date: Mon, 2 Dec 2019 14:26:57 -0500 Subject: [PATCH 105/207] Update README.md (#59) --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index a6e02929..4a51c9a0 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,4 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | | **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | -## Related projects - -[Microsoft AI Github](https://github.com/microsoft/ai) Find other Best Practice projects, and Azure AI Designed patterns in our central repository. From d79b7a0058702d74c2e83f90589dd22650e4b0a3 Mon Sep 17 00:00:00 2001 From: Wee Hyong Tok Date: Mon, 2 Dec 2019 14:27:39 -0500 Subject: [PATCH 106/207] Update README.md (#58) --- contrib/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contrib/README.md b/contrib/README.md index b88cecec..cd6e0024 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -1,3 +1,7 @@ -This is a contrib folder - software located here is not guarateed to run or be useful - it is not tested in any way, shape or form. +Contrib folder +Code in this folder has not been tested, and are meant for exploratory work only. + +We encourage submissiosn to the contrib folder, and once it is well-tested, do submit a pull request and work with the repository owners to graduate it to the main DeepSeismic repository. + +Thank you. -You can use software located in this folder as a starting point at your own risk. You might find certain software items located in this folder to be valuable. From 7c1c12a6d043806a53afc8f61cf0073edf6f8c35 Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Tue, 3 Dec 2019 11:58:52 +0000 Subject: [PATCH 107/207] MINOR: addressing broken F3 download link (#73) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4a51c9a0..2bbc2f96 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ To download the Penobscot dataset run the [download_penobscot.sh](scripts/downlo ``` data_dir='/data/penobscot' -mkdir "$data_dir" -./scripts/download_penobscot.sh "$data_dir" +mkdir $data_dir +./scripts/download_penobscot.sh $data_dir ``` -Note that the specified download location (e.g `/data/penobscot`) should be created beforehand, and configured with appropriate `write` pemissions. +Note that the specified download location (e.g `/data/penobscot`) should be configured with appropriate `write` pemissions. To prepare the data for the experiments (e.g. split into train/val/test), please run the following script (modifying arguments as desired): @@ -61,11 +61,11 @@ To prepare the data for the experiments (e.g. split into train/val/test), please python scripts/prepare_penobscot.py split_inline --data-dir=/data/penobscot --val-ratio=.1 --test-ratio=.2 ``` -#### Netherlands F3 +#### F3 Netherlands To download the F3 Netherlands dataset for 2D experiments, please follow the data download instructions at -[this github repository](https://github.com/olivesgatech/facies_classification_benchmark). +[this github repository](https://github.com/yalaudah/facies_classification_benchmark) (section Dataset). -Once you've downloaded the data set, create an empty `splits` directory, under the downloaded `data` directory. This is where your training/test/validation splits will be saved. +Once you've downloaded the data set, make sure to create an empty `splits` directory, under the downloaded `data` directory. This is where your training/test/validation splits will be saved. ``` cd data From 85a02c4964cc8604f29c513668977ba12da4a7a8 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 3 Dec 2019 09:02:46 -0500 Subject: [PATCH 108/207] MINOR: python version fix to 3.6.7 (#72) --- environment/anaconda/local/environment.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index f24f2799..9c533880 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -3,7 +3,7 @@ channels: - conda-forge - pytorch dependencies: - - python=3.6 + - python=3.6.7 - pip - pytorch==1.3.1 - cudatoolkit==10.1.243 @@ -18,10 +18,6 @@ dependencies: - itkwidgets==0.23.1 - pytest - papermill>=1.0.1 - #- jsonschema - #- pyrsistent - #- jupyter_core - #- pygments - pip: - segyio==1.8.8 - pytorch-ignite==0.3.0.dev20191105 # pre-release until stable available From 65b5cf90388b8d8d0839ec509c1449ba88bcd4af Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Tue, 3 Dec 2019 16:01:58 +0000 Subject: [PATCH 109/207] Adding system requirements in README (#74) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2bbc2f96..3f810425 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,24 @@ To run examples available on the repo, please follow instructions below to: 3) [Run example notebooks and scripts](#run-examples) ### Setting up Environment -Navigate to the folder where you pulled the DeepSeismic repo to run: + +Follow the instruction bellow to read about compute requirements and install required libraries. + +

+ Compute environment + +We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on Linux only. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DSVM)](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/). This VM will come installed with all the system requirements that are needed to run the notebooks in this repository. + +For this repo, we recommend selecting an Ubuntu VM of type [Standard_NC6_v3](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series). The machine is powered by NVIDIA Tesla V100 GPU which can be found in most Azure regions. + +> NOTE: For users new to Azure, your subscription may not come with a quota for GPUs. You may need to go into the Azure portal to increase your quota for GPU VMs. Learn more about how to do this here: https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits. + +
+ +
+ Package Installation + +To install packages contained in this repository, navigate to the directory where you pulled the DeepSeismic repo to run: ```bash conda env create -f environment/anaconda/local/environment.yml ``` @@ -40,6 +57,8 @@ conda env update --file environment/anaconda/local/environment.yml ``` from the root of DeepSeismic repo. +
+ ### Dataset download and preparation This repository provides examples on how to run seismic interpretation on two publicly available annotated seismic datasets: [Penobscot](https://zenodo.org/record/1341774) and [F3 Netherlands](https://github.com/olivesgatech/facies_classification_benchmark). From f912d6d88e3890e87b829679d70ce4f86cf619dd Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Tue, 3 Dec 2019 21:05:59 -0500 Subject: [PATCH 110/207] merge upstream into my fork (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme --- README.md | 33 +++++++++++++++++----- environment/anaconda/local/environment.yml | 6 +--- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 4a51c9a0..3f810425 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,24 @@ To run examples available on the repo, please follow instructions below to: 3) [Run example notebooks and scripts](#run-examples) ### Setting up Environment -Navigate to the folder where you pulled the DeepSeismic repo to run: + +Follow the instruction bellow to read about compute requirements and install required libraries. + +
+ Compute environment + +We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on Linux only. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DSVM)](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/). This VM will come installed with all the system requirements that are needed to run the notebooks in this repository. + +For this repo, we recommend selecting an Ubuntu VM of type [Standard_NC6_v3](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series). The machine is powered by NVIDIA Tesla V100 GPU which can be found in most Azure regions. + +> NOTE: For users new to Azure, your subscription may not come with a quota for GPUs. You may need to go into the Azure portal to increase your quota for GPU VMs. Learn more about how to do this here: https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits. + +
+ +
+ Package Installation + +To install packages contained in this repository, navigate to the directory where you pulled the DeepSeismic repo to run: ```bash conda env create -f environment/anaconda/local/environment.yml ``` @@ -40,6 +57,8 @@ conda env update --file environment/anaconda/local/environment.yml ``` from the root of DeepSeismic repo. +
+ ### Dataset download and preparation This repository provides examples on how to run seismic interpretation on two publicly available annotated seismic datasets: [Penobscot](https://zenodo.org/record/1341774) and [F3 Netherlands](https://github.com/olivesgatech/facies_classification_benchmark). @@ -49,11 +68,11 @@ To download the Penobscot dataset run the [download_penobscot.sh](scripts/downlo ``` data_dir='/data/penobscot' -mkdir "$data_dir" -./scripts/download_penobscot.sh "$data_dir" +mkdir $data_dir +./scripts/download_penobscot.sh $data_dir ``` -Note that the specified download location (e.g `/data/penobscot`) should be created beforehand, and configured with appropriate `write` pemissions. +Note that the specified download location (e.g `/data/penobscot`) should be configured with appropriate `write` pemissions. To prepare the data for the experiments (e.g. split into train/val/test), please run the following script (modifying arguments as desired): @@ -61,11 +80,11 @@ To prepare the data for the experiments (e.g. split into train/val/test), please python scripts/prepare_penobscot.py split_inline --data-dir=/data/penobscot --val-ratio=.1 --test-ratio=.2 ``` -#### Netherlands F3 +#### F3 Netherlands To download the F3 Netherlands dataset for 2D experiments, please follow the data download instructions at -[this github repository](https://github.com/olivesgatech/facies_classification_benchmark). +[this github repository](https://github.com/yalaudah/facies_classification_benchmark) (section Dataset). -Once you've downloaded the data set, create an empty `splits` directory, under the downloaded `data` directory. This is where your training/test/validation splits will be saved. +Once you've downloaded the data set, make sure to create an empty `splits` directory, under the downloaded `data` directory. This is where your training/test/validation splits will be saved. ``` cd data diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index f24f2799..9c533880 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -3,7 +3,7 @@ channels: - conda-forge - pytorch dependencies: - - python=3.6 + - python=3.6.7 - pip - pytorch==1.3.1 - cudatoolkit==10.1.243 @@ -18,10 +18,6 @@ dependencies: - itkwidgets==0.23.1 - pytest - papermill>=1.0.1 - #- jsonschema - #- pyrsistent - #- jupyter_core - #- pygments - pip: - segyio==1.8.8 - pytorch-ignite==0.3.0.dev20191105 # pre-release until stable available From b457097a5d09b5d4587c9a145db82248a0841566 Mon Sep 17 00:00:00 2001 From: Mat Date: Wed, 4 Dec 2019 12:50:05 +0000 Subject: [PATCH 111/207] Adds premium storage (#79) * Adds premium storage method --- README.md | 4 ++ .../notebooks/configs/hrnet.yaml | 2 + .../azureml_tools/storage.py | 60 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 interpretation/deepseismic_interpretation/azureml_tools/storage.py diff --git a/README.md b/README.md index 3f810425..d17a1c65 100644 --- a/README.md +++ b/README.md @@ -249,4 +249,8 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | | **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | +# Related projects + +[Microsoft AI Labs Github](https://aka.ms/ai-labs) Find other Best Practice projects, and Azure AI design patterns in our central repository. + diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index 112df1b3..544870e2 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -7,6 +7,7 @@ OUTPUT_DIR: 'outputs' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 50 + LOG_CONFIG: logging.conf SEED: 2019 @@ -20,6 +21,7 @@ DATASET: + MODEL: NAME: seg_hrnet IN_CHANNELS: 3 diff --git a/interpretation/deepseismic_interpretation/azureml_tools/storage.py b/interpretation/deepseismic_interpretation/azureml_tools/storage.py new file mode 100644 index 00000000..29a3fbba --- /dev/null +++ b/interpretation/deepseismic_interpretation/azureml_tools/storage.py @@ -0,0 +1,60 @@ +from azure.mgmt.storage import StorageManagementClient +from azure.mgmt.storage.models import StorageAccountCreateParameters +from azure.mgmt.storage.v2019_04_01.models import Kind, Sku, SkuName + +from deepseismic_interpretation.azureml_tools.resource_group import \ + create_resource_group + + +class StorageAccountCreateFailure(Exception): + pass + + +def create_premium_storage( + profile_credentials, subscription_id, location, resource_group_name, storage_name, +): + """Create premium blob storage + + Args: + profile_credentials : credentials from Azure login (see example below for details) + subscription_id (str): subscription you wish to use + location (str): location you wish the strage to be created in + resource_group_name (str): the name of the resource group you want the storage to be created under + storage_name (str): the name of the storage account + + Raises: + Exception: [description] + + Returns: + [type]: [description] + + Example: + >>> from azure.common.credentials import get_cli_profile + >>> profile = get_cli_profile() + >>> profile.set_active_subscription("YOUR-ACCOUNT") + >>> cred, subscription_id, _ = profile.get_login_credentials() + >>> storage = create_premium_storage(cred, subscription_id, "eastus", "testrg", "teststr", wait=False) + """ + storage_client = StorageManagementClient(profile_credentials, subscription_id) + create_resource_group(profile_credentials, subscription_id, location, resource_group_name) + if storage_client.storage_accounts.check_name_availability(storage_name).name_available == False: + storage_account = storage_client.storage_accounts.get_properties(resource_group_name, storage_name) + else: + storage_async_operation = storage_client.storage_accounts.create( + resource_group_name, + storage_name, + StorageAccountCreateParameters( + sku=Sku(name=SkuName.premium_lrs), kind=Kind.block_blob_storage, location="eastus", + ), + ) + storage_account = storage_async_operation.result() + + if "Succeeded" not in storage_account.provisioning_state: + raise StorageAccountCreateFailure( + f"Storage account not created successfully | State {storage_account.provisioning_state}" + ) + + storage_keys = storage_client.storage_accounts.list_keys(resource_group_name, storage_name) + storage_keys = {v.key_name: v.value for v in storage_keys.keys} + + return storage_account, storage_keys From 5f9a3b43468a87aaa60038b97144ed6deed3fa3f Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Wed, 4 Dec 2019 09:06:43 -0500 Subject: [PATCH 112/207] update test.py for section based approach to use command line arguments (#76) --- .../interpretation/dutchf3_section/local/test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/experiments/interpretation/dutchf3_section/local/test.py b/experiments/interpretation/dutchf3_section/local/test.py index e7968418..e4585f83 100644 --- a/experiments/interpretation/dutchf3_section/local/test.py +++ b/experiments/interpretation/dutchf3_section/local/test.py @@ -35,8 +35,6 @@ "zechstein", ] -DATA_ROOT = config.DATASET.ROOT - class runningScore(object): def __init__(self, n_classes): @@ -93,7 +91,9 @@ def _evaluate_split( logger = logging.getLogger(__name__) TestSectionLoader = get_test_loader(config) - test_set = TestSectionLoader(data_dir=DATA_ROOT, split=split, is_transform=True, augmentations=section_aug,) + test_set = TestSectionLoader( + data_dir=config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug, + ) n_classes = test_set.n_classes @@ -175,8 +175,8 @@ def test(*options, cfg=None): splits = ["test1", "test2"] if "Both" in config.TEST.SPLIT else [config.TEST.SPLIT] for sdx, split in enumerate(splits): - labels = np.load(path.join(DATA_ROOT, "test_once", split + "_labels.npy")) - section_file = path.join(DATA_ROOT, "splits", "section_" + split + ".txt") + labels = np.load(path.join(config.DATASET.ROOT, "test_once", split + "_labels.npy")) + section_file = path.join(config.DATASET.ROOT, "splits", "section_" + split + ".txt") _write_section_file(labels, section_file) _evaluate_split(split, section_aug, model, device, running_metrics_overall, config) From dce495fc1e4e44524f64b40d1e8a074b35d9e170 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Wed, 4 Dec 2019 09:12:49 -0500 Subject: [PATCH 113/207] added README documentation per bug bush feedback (#78) --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d17a1c65..2404f554 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,18 @@ This repository shows you how to perform seismic imaging and interpretation on Azure. It empowers geophysicists and data scientists to run seismic experiments using state-of-art DSL-based PDE solvers and segmentation algorithms on Azure. -The repository provides sample notebooks, data loaders for seismic data, utility codes, and out-of-the box ML pipelines. +The repository provides sample notebooks, data loaders for seismic data, utilities, and out-of-the box ML pipelines, organized as follows: +- **sample notebooks**: these can be found in the `examples` folder - they are standard Jupyter notebooks which highlight how to use the codebase by walking the user through a set of pre-made examples +- **experiments**: the goal is to provide runnable Python scripts which train and test (score) our machine learning models in `experiments` folder. The models themselves are swappable, meaning a single train script can be used to run a different model on the same dataset by simply swapping out the configuration file which defines the model. Experiments are organized by model types and datasets - for example, "2D segmentation on Dutch F3 dataset", "2D segmentation on Penobscot dataset" and "3D segmentation on Penobscot dataset" are all different experiments. As another example, if one is swapping 2D segmentation models on Dutch F3 dataset, one would just point the train and test scripts to a different configuration file within the same experiment. +- **pip installable utilities**: we provide `cv_lib` and `deepseismic_interpretation` utilities (more info below) which are used by both sample notebooks and experiments mentioned above +DeepSeismic currently focuses on Seismic Interpretation (3D segmentation aka facies classification) with experimental code provided around Seismic Imaging. + +### Quick Start + +There are two ways to get started with the DeepSeismic codebase, which currently focuses on Interpretation: +- if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/staging/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb) +- to actually run the code, you'll need to set up a compute environment (which includes setting up a GPU-enabled Linux VM and downloading the appropriate Anaconda Python packages) and download the datasets which you'd like to work with - detailed steps for doing this are provided in the next `Interpretation` section below. ## Interpretation For seismic interpretation, the repository consists of extensible machine learning pipelines, that shows how you can leverage state-of-the-art segmentation algorithms (UNet, SEResNET, HRNet) for seismic interpretation, and also benchmarking results from running these algorithms using various seismic datasets (Dutch F3, and Penobscot). From 5c476430d75c91170de2fd3e576e81e393513859 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 4 Dec 2019 19:10:20 +0000 Subject: [PATCH 114/207] sdk 1.0.76; tested conda env vs docker image; extented readme --- contrib/README.md | 5 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1010 ------------- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 968 ------------- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1272 ---------------- .../azureml_devito/notebooks/README.md | 28 - ...zureml_buildexperimentationdockerimage.log | 116 -- contrib/fwi/azureml_devito/README.md | 48 + .../fwi_dev_conda_environment.yml | 16 + ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 123 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1064 ++++++++++++++ ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1003 +++++++++++++ ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1281 +++++++++++++++++ 12 files changed, 3520 insertions(+), 3414 deletions(-) delete mode 100755 contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb delete mode 100755 contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb delete mode 100755 contrib/examples/imaging/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb delete mode 100644 contrib/examples/imaging/azureml_devito/notebooks/README.md delete mode 100644 contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log create mode 100755 contrib/fwi/azureml_devito/README.md create mode 100755 contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml rename contrib/{examples/imaging => fwi}/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb (84%) create mode 100755 contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb create mode 100755 contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb create mode 100755 contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb diff --git a/contrib/README.md b/contrib/README.md index cd6e0024..a286b0f3 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -1,7 +1,8 @@ -Contrib folder +### Contrib folder + Code in this folder has not been tested, and are meant for exploratory work only. -We encourage submissiosn to the contrib folder, and once it is well-tested, do submit a pull request and work with the repository owners to graduate it to the main DeepSeismic repository. +We encourage submissions to the contrib folder, and once they are well-tested, do submit a pull request and work with the repository owners to graduate it to the main DeepSeismic repository. Thank you. diff --git a/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb deleted file mode 100755 index c19e34fa..00000000 --- a/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ /dev/null @@ -1,1010 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. \n", - "Licensed under the MIT License.\n", - "\n", - "# FWI in Azure project\n", - "\n", - "## Create Experimentation Docker image\n", - "\n", - "FWI demo based on: \n", - "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", - "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", - "\n", - "\n", - "\n", - "In this notebook we create a custom docker image that will be used to run the devito demo notebooks in AzureML. \n", - "\n", - " - We transparently create a docker file, a conda environment .yml file, build the docker image and push it into dockerhub. Azure ACR could also be used for storing docker images. \n", - " - The conda environment .yml file lists conda and pip installs, and separates all python dependencies from the docker installs. \n", - " - The dockerfile is generic. The only AzureML depedency is azureml-sdk pip installable package in conda environment .yml file\n", - " - The created docer image will be run in following notebook in a container on the local AzureVM or on a remote AzureML compute cluster. This AzureML pattern decouples experimentation (or training) job definition (experimentation script, data location, dependencies and docker image) happening on the control plane machine that runs this notebook, from the elastically allocated and Azure managed VM/cluster that does the actual training/experimentation computation.\n", - " \n", - "
\n", - "User input requiring steps:\n", - " - [Fill in and save docker image name settings, if needed. ](#docker_image_settings)\n", - " - [Update DOCKER_CONTAINER_MOUNT_POINT to match our local path](#docker_image_settings)\n", - " - [Set docker build and test flags](#docker_build_test_settings) \n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Allow multiple displays per cell\n", - "from IPython.core.interactiveshell import InteractiveShell\n", - "InteractiveShell.ast_node_interactivity = \"all\" " - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import sys, os\n", - "import shutil\n", - "import urllib\n", - "\n", - "import platform\n", - "import math\n", - "import docker" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Linux-4.15.0-1063-azure-x86_64-with-debian-10.1'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/contrib/examples/imaging/azureml_devito/notebooks'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "platform.platform()\n", - "os.getcwd()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "#### Setup docker image build and test process. \n", - " - devito tests take abou 15 mins (981.41 seconds). When running this notebook for first time make:\n", - " > docker_build_no_cache = '--no-cache' \n", - " > docker_test_run_devito_tests = True\n", - " \n", - "[Back](#user_input_requiring_steps) to summary of user input requiring steps." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "docker_build_no_cache = '' # '--no-cache' # or '' #\n", - "docker_test_run_devito_tests = False # True # False" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### Import utilities functions" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[None]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def add_path_to_sys_path(path_to_append):\n", - " if not (any(path_to_append in paths for paths in sys.path)):\n", - " sys.path.append(path_to_append)\n", - " \n", - "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", - "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", - "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", - "\n", - "import project_utils\n", - "prj_consts = project_utils.project_consts()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### Create experimentation docker file" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'./../not_shared/general.env'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", - "dotenv_file_path" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/workspace/contrib/examples/imaging/azureml_devito/notebooks\r\n" - ] - } - ], - "source": [ - "!pwd" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "# azureml_sdk_version set here must match azureml sdk version pinned in conda env file written to conda_common_file_path below\n", - "azureml_sdk_version = '1.0.74' " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "##### Input here docker image settings \n", - "in cell below we use [dotenv](https://github.com/theskumar/python-dotenv) to overwrite docker image properties already save in dotenv_file_path. Change as needed, e.g. update azureml_sdk version if using a different version.\n", - "\n", - "[Back](#user_input_requiring_steps) to summary of user input requiring steps." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "# SDK changes often, so we'll keep its version transparent \n", - "import dotenv\n", - "\n", - "if 0:\n", - " # EXPERIMENTATION_IMAGE_VERSION should:\n", - " # - match sdk version in fwi01_conda_env01 environmnet in conda_env_fwi01_azureml_sdk.v1.0.XX.yml file below\n", - " # - match the conda env yml file name, e.g. conda_env_fwi01_azureml_sdk.v1.0.62.yml referenced in \n", - " # Dockerfile_fwi01_azureml_sdk.v1.0.62\n", - " dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', ('sdk.v'+azureml_sdk_version))\n", - " dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')\n", - "\n", - " docker_container_mount_point = os.getcwd()\n", - " # or something like \"/datadrive01/prj/DeepSeismic/contrib/examples/imaging/azureml_devito/notebooks'\n", - " dotenv.set_key(dotenv_file_path, 'DOCKER_CONTAINER_MOUNT_POINT', docker_container_mount_point)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'conda_env_fwi01_azureml_sdk.v1.0.74.yml'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.74.yml'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.74'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%reload_ext dotenv\n", - "%dotenv $dotenv_file_path\n", - "\n", - "docker_file_location = os.path.join(*(prj_consts.AML_EXPERIMENT_DIR + ['docker_build']))\n", - "\n", - "docker_file_name = 'Dockerfile'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", - "\n", - "conda_dependency_file_name = 'conda_env'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", - "conda_dependency_common_file_name = conda_dependency_file_name\n", - "\n", - "devito_conda_dependency_file_name = 'devito_conda_env'+'.yml'\n", - "\n", - "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", - "docker_image_name = docker_repo_name + '/' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", - "\n", - "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", - "if image_version!=\"\":\n", - " docker_file_name = docker_file_name +'_'+ image_version\n", - " conda_dependency_file_name = conda_dependency_file_name+'_'+ image_version\n", - " docker_image_name = docker_image_name +':'+ image_version\n", - "conda_dependency_file_name=conda_dependency_file_name+'.yml'\n", - "conda_dependency_common_file_name = conda_dependency_common_file_name+'.yml'\n", - "\n", - "docker_file_dir = os.path.join(*([os.getcwd(), docker_file_location]))\n", - "os.makedirs(docker_file_dir, exist_ok=True)\n", - "docker_file_path = os.path.join(*([docker_file_dir]+[docker_file_name]))\n", - "conda_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_file_name]))\n", - "conda_common_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_common_file_name]))\n", - "\n", - "docker_image_name\n", - "\n", - "conda_dependency_file_name\n", - "conda_file_path\n", - "conda_common_file_path\n", - "\n", - "docker_file_dir\n", - "docker_file_path" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting /workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" - ] - } - ], - "source": [ - "%%writefile $conda_common_file_path\n", - "name: fwi01_conda_env01\n", - " \n", - "#https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.13.1-cp37-cp37m-linux_x86_64.whl \n", - "# https://github.com/dask/dask-tutorial\n", - "\n", - "channels:\n", - " - anaconda\n", - " - conda-forge\n", - "dependencies:\n", - " - python=3.6 # 3.6 req by tf, not 3.7.2 \n", - " - dask\n", - " - distributed\n", - " - h5py\n", - " - matplotlib\n", - " - nb_conda\n", - " - notebook \n", - " - numpy \n", - " - pandas\n", - " - pip\n", - " - py-cpuinfo # all required by devito or dask-tutorial\n", - " - pytables\n", - " - python-graphviz\n", - " - requests\n", - " - pillow\n", - " - scipy\n", - " - snakeviz\n", - " - scikit-image\n", - " - toolz\n", - " - pip:\n", - " - anytree # required by devito\n", - " - azureml-sdk[notebooks,automl]==1.0.74\n", - " - codepy # required by devito\n", - " - papermill[azure]\n", - " - pyrevolve # required by devito" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting /workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.74\n" - ] - } - ], - "source": [ - "%%writefile $docker_file_path \n", - "\n", - "FROM continuumio/miniconda3:4.7.10 \n", - "MAINTAINER George Iordanescu \n", - "\n", - "RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \\\n", - " gcc g++ \\\n", - " wget bzip2 \\\n", - " curl \\\n", - " git make \\\n", - " mpich \\ \n", - " libmpich-dev && \\\n", - " apt-get clean && \\\n", - " rm -rf /var/lib/apt/lists/*\n", - "\n", - "ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml.yml\n", - "ADD $CONDA_ENV_FILE_NAME /tmp/$CONDA_ENV_FILE_NAME\n", - "ENV CONDA_DIR /opt/conda\n", - "ENV CONDA_ENV_NAME fwi01_conda_env\n", - "\n", - "RUN git clone https://github.com/opesci/devito.git && \\\n", - " cd devito && \\\n", - " /opt/conda/bin/conda env create -q --name $CONDA_ENV_NAME -f environment.yml && \\\n", - " pip install -e . \n", - " \n", - "ENV CONDA_AUTO_UPDATE_CONDA=false\n", - "ENV CONDA_DEFAULT_ENV=$CONDA_ENV_NAME\n", - "ENV CONDA_PREFIX=$CONDA_DIR/envs/$CONDA_DEFAULT_ENV\n", - "ENV PATH=$CONDA_PREFIX/bin:/opt/conda/bin:$PATH \n", - "\n", - "RUN /opt/conda/bin/conda env update --name $CONDA_ENV_NAME -f /tmp/$CONDA_ENV_FILE_NAME && \\\n", - " /opt/conda/bin/conda clean --yes --all\n", - "\n", - "ENV PYTHONPATH=$PYTHONPATH:devito/app\n", - "\n", - "# WORKDIR /devito \n", - " \n", - "CMD /bin/bash" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'/workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.74.yml'" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "total 20\r\n", - "-rw-r--r-- 1 root root 1073 Nov 26 16:35 Dockerfile_fwi01_azureml_sdk.v1.0.69\r\n", - "-rw-r--r-- 1 root root 1073 Nov 27 12:53 Dockerfile_fwi01_azureml_sdk.v1.0.74\r\n", - "-rw-r--r-- 1 root root 725 Nov 27 12:53 conda_env_fwi01_azureml.yml\r\n", - "-rw-r--r-- 1 root root 733 Nov 26 16:35 conda_env_fwi01_azureml_sdk.v1.0.69.yml\r\n", - "-rw-r--r-- 1 root root 725 Nov 27 12:53 conda_env_fwi01_azureml_sdk.v1.0.74.yml\r\n" - ] - } - ], - "source": [ - "shutil.copyfile(conda_common_file_path, conda_file_path)\n", - "\n", - "! ls -l $docker_file_dir" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74 -f /workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.74 /workspace/contrib/examples/imaging/azureml_devito/notebooks/./../temp/docker_build '" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "['Sending build context to Docker daemon 9.728kB',\n", - " '',\n", - " 'Step 1/15 : FROM continuumio/miniconda3:4.7.10',\n", - " ' ---> 4a51de2367be',\n", - " 'Step 2/15 : MAINTAINER George Iordanescu ']" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "['Step 15/15 : CMD /bin/bash',\n", - " ' ---> Using cache',\n", - " ' ---> 3b4b53971bed',\n", - " 'Successfully built 3b4b53971bed',\n", - " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74']" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cli_command='docker build -t '+ docker_image_name + \\\n", - "' -f ' + docker_file_path + \\\n", - "' ' + docker_file_dir + ' ' +\\\n", - "docker_build_no_cache #'' #' --no-cache'\n", - "\n", - "\n", - "cli_command\n", - "docker_build_response = ! $cli_command\n", - "\n", - "docker_build_response[0:5] \n", - "docker_build_response[-5:] " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Docker containers can be run using python docker sdk" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "b'/\\n1.0.74\\n'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "docker_image_name\n", - "\n", - "sh_command='bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'\n", - "sh_command\n", - "client = docker.from_env()\n", - "client.containers.run(docker_image_name, \n", - " remove=True,\n", - " volumes={os.getenv('DOCKER_CONTAINER_MOUNT_POINT'): {'bind': '/workspace', 'mode': 'rw'}},\n", - " working_dir='/',\n", - " command=sh_command)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Docker containers can also be run in cli \n", - "\n", - "Here we also create a log file to capture commands execution in container. If flag docker_test_run_devito_tests is True, we run \n", - "and capture test commands output. Tests take abou 15 minutes to run. If flag docker_test_run_devito_tests is False, we show the results of a previous session. " - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'./fwi01_azureml_buildexperimentationdockerimage.log'" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "fwi01_log_file = os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log']))\n", - "fwi01_log_file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Create command for running devito tests, capture output in a log file, save log file outside container" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "''" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "if docker_test_run_devito_tests:\n", - " run_devito_tests_command = ' python -m pytest tests/ ' + \\\n", - "'> ' + fwi01_log_file +' 2>&1; ' + \\\n", - "' mv ' + fwi01_log_file + ' /workspace/' \n", - " \n", - " with open(os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log'])), \"w\") as crt_log_file:\n", - " print('Before running e13n container... ', file=crt_log_file)\n", - " print('\\ncontent of devito tests log file before testing:')\n", - " !cat $fwi01_log_file\n", - "else:\n", - " run_devito_tests_command = '' \n", - "\n", - "# run_devito_tests_command = 'ls -l > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'\n", - "run_devito_tests_command" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/contrib/examples/imaging/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; \"'" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "# conda environments:\n", - "#\n", - "base /opt/conda\n", - "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", - "\n", - "total 556\n", - "-rw-r--r-- 1 root root 11521 Nov 27 03:12 conftest.py\n", - "-rw-r--r-- 1 root root 6006 Nov 27 03:12 test_adjoint.py\n", - "-rw-r--r-- 1 root root 14586 Nov 27 03:12 test_autotuner.py\n", - "-rw-r--r-- 1 root root 7538 Nov 27 03:12 test_builtins.py\n", - "-rw-r--r-- 1 root root 24415 Nov 27 03:12 test_caching.py\n", - "-rw-r--r-- 1 root root 9721 Nov 27 03:12 test_checkpointing.py\n", - "-rw-r--r-- 1 root root 1095 Nov 27 03:12 test_constant.py\n", - "-rw-r--r-- 1 root root 55954 Nov 27 03:12 test_data.py\n", - "-rw-r--r-- 1 root root 481 Nov 27 03:12 test_dependency_bugs.py\n", - "-rw-r--r-- 1 root root 16331 Nov 27 03:12 test_derivatives.py\n", - "-rw-r--r-- 1 root root 1473 Nov 27 03:12 test_differentiable.py\n", - "-rw-r--r-- 1 root root 30846 Nov 27 03:12 test_dimension.py\n", - "-rw-r--r-- 1 root root 24838 Nov 27 03:12 test_dle.py\n", - "-rw-r--r-- 1 root root 1169 Nov 27 03:12 test_docstrings.py\n", - "-rw-r--r-- 1 root root 32134 Nov 27 03:12 test_dse.py\n", - "-rw-r--r-- 1 root root 8205 Nov 27 03:12 test_gradient.py\n", - "-rw-r--r-- 1 root root 15227 Nov 27 03:12 test_interpolation.py\n", - "-rw-r--r-- 1 root root 31816 Nov 27 03:12 test_ir.py\n", - "-rw-r--r-- 1 root root 62176 Nov 27 03:12 test_mpi.py\n", - "-rw-r--r-- 1 root root 65255 Nov 27 03:12 test_operator.py\n", - "-rw-r--r-- 1 root root 14208 Nov 27 03:12 test_ops.py\n", - "-rw-r--r-- 1 root root 12228 Nov 27 03:12 test_pickle.py\n", - "-rw-r--r-- 1 root root 1809 Nov 27 03:12 test_resample.py\n", - "-rw-r--r-- 1 root root 1754 Nov 27 03:12 test_save.py\n", - "-rw-r--r-- 1 root root 2115 Nov 27 03:12 test_staggered_utils.py\n", - "-rw-r--r-- 1 root root 5711 Nov 27 03:12 test_subdomains.py\n", - "-rw-r--r-- 1 root root 3320 Nov 27 03:12 test_symbolic_coefficients.py\n", - "-rw-r--r-- 1 root root 7277 Nov 27 03:12 test_tensors.py\n", - "-rw-r--r-- 1 root root 3186 Nov 27 03:12 test_timestepping.py\n", - "-rw-r--r-- 1 root root 603 Nov 27 03:12 test_tools.py\n", - "-rw-r--r-- 1 root root 3296 Nov 27 03:12 test_tti.py\n", - "-rw-r--r-- 1 root root 8835 Nov 27 03:12 test_visitors.py\n", - "-rw-r--r-- 1 root root 21802 Nov 27 03:12 test_yask.py\n", - "1.0.74\n", - "\n", - "content of devito tests log file after testing:\n", - "============================= test session starts ==============================\n", - "platform linux -- Python 3.6.9, pytest-5.3.0, py-1.8.0, pluggy-0.13.1\n", - "rootdir: /devito, inifile: setup.cfg\n", - "plugins: nbval-0.9.3, cov-2.8.1\n", - "collected 1053 items / 2 skipped / 1051 selected\n", - "\n", - "tests/test_adjoint.py .......................... [ 2%]\n", - "tests/test_autotuner.py ..........s..... [ 3%]\n", - "tests/test_builtins.py ....s...............s..s [ 6%]\n", - "tests/test_caching.py .................................................. [ 11%]\n", - " [ 11%]\n", - "tests/test_checkpointing.py ....... [ 11%]\n", - "tests/test_constant.py . [ 11%]\n", - "tests/test_data.py ..........................ssssssssssssssssss.ss.. [ 16%]\n", - "tests/test_dependency_bugs.py . [ 16%]\n", - "tests/test_derivatives.py .............................................. [ 20%]\n", - "........................................................................ [ 27%]\n", - "........................................................................ [ 34%]\n", - "...... [ 35%]\n", - "tests/test_differentiable.py .. [ 35%]\n", - "tests/test_dimension.py ............................... [ 38%]\n", - "tests/test_dle.py ...................................................... [ 43%]\n", - "........................................... [ 47%]\n", - "tests/test_docstrings.py ................ [ 49%]\n", - "tests/test_dse.py ......x............................................... [ 54%]\n", - "................x..........s.... [ 57%]\n", - "tests/test_gradient.py .... [ 57%]\n", - "tests/test_interpolation.py ........................ [ 59%]\n", - "tests/test_ir.py ....................................................... [ 65%]\n", - "................ [ 66%]\n", - "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 71%]\n", - "ss [ 71%]\n", - "tests/test_operator.py ................................................. [ 76%]\n", - "........................................................................ [ 83%]\n", - "................................ [ 86%]\n", - "tests/test_pickle.py .................ss. [ 88%]\n", - "tests/test_resample.py . [ 88%]\n", - "tests/test_save.py .. [ 88%]\n", - "tests/test_staggered_utils.py ......... [ 89%]\n", - "tests/test_subdomains.py ... [ 89%]\n", - "tests/test_symbolic_coefficients.py .....F [ 90%]\n", - "tests/test_tensors.py .................................................. [ 95%]\n", - "........................... [ 97%]\n", - "tests/test_timestepping.py ....... [ 98%]\n", - "tests/test_tools.py ..... [ 98%]\n", - "tests/test_tti.py .... [ 99%]\n", - "tests/test_visitors.py ......... [100%]\n", - "\n", - "=================================== FAILURES ===================================\n", - "______________________ TestSC.test_function_coefficients _______________________\n", - "\n", - "self = \n", - "\n", - " def test_function_coefficients(self):\n", - " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", - " so = 2\n", - " grid = Grid(shape=(4, 4))\n", - " f0 = TimeFunction(name='f0', grid=grid, space_order=so, coefficients='symbolic')\n", - " f1 = TimeFunction(name='f1', grid=grid, space_order=so)\n", - " x, y = grid.dimensions\n", - " \n", - " s = Dimension(name='s')\n", - " ncoeffs = so+1\n", - " \n", - " wshape = list(grid.shape)\n", - " wshape.append(ncoeffs)\n", - " wshape = as_tuple(wshape)\n", - " \n", - " wdims = list(grid.dimensions)\n", - " wdims.append(s)\n", - " wdims = as_tuple(wdims)\n", - " \n", - " w = Function(name='w', dimensions=wdims, shape=wshape)\n", - " w.data[:, :, 0] = 0.0\n", - " w.data[:, :, 1] = -1.0/grid.spacing[0]\n", - " w.data[:, :, 2] = 1.0/grid.spacing[0]\n", - " \n", - " f_x_coeffs = Coefficient(1, f0, x, w)\n", - " \n", - " subs = Substitutions(f_x_coeffs)\n", - " \n", - " eq0 = Eq(f0.dt + f0.dx, 1, coefficients=subs)\n", - " eq1 = Eq(f1.dt + f1.dx, 1)\n", - " \n", - " stencil0 = solve(eq0.evaluate, f0.forward)\n", - " stencil1 = solve(eq1.evaluate, f1.forward)\n", - " \n", - " op0 = Operator(Eq(f0.forward, stencil0))\n", - " op1 = Operator(Eq(f1.forward, stencil1))\n", - " \n", - " op0(time_m=0, time_M=5, dt=1.0)\n", - " op1(time_m=0, time_M=5, dt=1.0)\n", - " \n", - "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", - "E assert Data(False)\n", - "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", - "E + where = np.all\n", - "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", - "E + where = np.isclose\n", - "\n", - "tests/test_symbolic_coefficients.py:96: AssertionError\n", - "----------------------------- Captured stderr call -----------------------------\n", - "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c: In function ‘Kernel’:\n", - "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", - " #pragma omp simd aligned(f0,w:32)\n", - " \n", - "Operator `Kernel` run in 0.01 s\n", - "/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c: In function ‘Kernel’:\n", - "/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", - " #pragma omp simd aligned(f1:32)\n", - " \n", - "Operator `Kernel` run in 0.01 s\n", - "------------------------------ Captured log call -------------------------------\n", - "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", - "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", - "====== 1 failed, 967 passed, 85 skipped, 2 xfailed in 1058.91s (0:17:38) =======\n" - ] - } - ], - "source": [ - "cli_command='docker run -it --rm --name fwi01_azureml_container ' +\\\n", - "' -v '+os.getenv('DOCKER_CONTAINER_MOUNT_POINT')+':/workspace:rw ' + \\\n", - "docker_image_name + \\\n", - "' /bin/bash -c \"conda env list ; ls -l /devito/tests; ' + \\\n", - "'python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; ' + \\\n", - "'cd /devito; ' + \\\n", - "run_devito_tests_command +\\\n", - "' \"'\n", - "\n", - "cli_command\n", - "! $cli_command\n", - "# # ============= 774 passed, 70 skipped, 1 xfailed in 1106.76 seconds =============\n", - "print('\\ncontent of devito tests log file after testing:')\n", - "!cat $fwi01_log_file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "###### Use the ACR created in previous notebook or docker hub to push your image" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'az acr login --name fwi01acr'" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Login Succeeded\r\n", - "WARNING! Your password will be stored unencrypted in /root/.docker/config.json.\r\n", - "Configure a credential helper to remove this warning. See\r\n", - "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", - "\r\n", - "\u001b[0m" - ] - } - ], - "source": [ - "# docker_pwd = os.getenv('DOCKER_PWD')\n", - "# docker_login = os.getenv('DOCKER_LOGIN')\n", - "# !docker login -u=$docker_login -p=$docker_pwd\n", - "# !docker push {docker_image_name}\n", - "\n", - "%dotenv $dotenv_file_path\n", - "cli_command='az acr login --name '+os.getenv('ACR_NAME')\n", - "# print cli command\n", - "cli_command\n", - "\n", - "# run cli command\n", - "cli_command = cli_command+' --username '+os.getenv('ACR_USERNAME') + ' --password ' + os.getenv('ACR_PASSWORD')\n", - "! $cli_command" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cli_command='docker push '+docker_image_name\n", - "cli_command" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", - "\n", - "\u001b[1Bff8f35a1: Preparing \n", - "\u001b[1Ba2d0572b: Preparing \n", - "\u001b[1B9c751d4d: Preparing \n", - "\u001b[1Be41d536b: Preparing \n", - "\u001b[1Bf8fc4c9a: Preparing \n", - "\u001b[1Bba47210e: Preparing \n", - "\u001b[2Bba47210e: Layer already exists \u001b[1A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[Ksdk.v1.0.74: digest: sha256:c07f66d82fb26972c2bbc4cbf2914c3d2c8b50bcad3925fdb6d77bac7ef5779a size: 1800\n" - ] - } - ], - "source": [ - "! $cli_command" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!\n" - ] - } - ], - "source": [ - "# !jupyter nbconvert 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito --to html\n", - "print('Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb deleted file mode 100755 index 1f14d9b3..00000000 --- a/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ /dev/null @@ -1,968 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. \n", - "Licensed under the MIT License. \n", - " \n", - " \n", - "# FWI demo based on: \n", - "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", - "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", - "\n", - "\n", - "\n", - "In this notebook we run the devito demo [notebooks](https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/) mentioned above by using an [AzureML estimator](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator.estimator?view=azure-ml-py) with custom docker image. The docker image and associated docker file were created in previous notebook.\n", - "\n", - "\n", - "#### This notebook is used as a control plane to submit experimentation jobs running devito in Azure in two modes (see [remote run azureml python script file invoking devito](#devito_demo_mode)):\n", - " - [Mode 1](#devito_demo_mode_1):\n", - " - uses custom code (slightly modified graphing functions save images to files too) \n", - " - experimentation job is defined by the devito code that is packaged as a py file to be run on an Azure remote compute target\n", - " - experimentation job can be used to track metrics or other artifacts (images)\n", - " \n", - " - Mode 2:\n", - " - papermill is invoked via cli or via its Python API to run unedited devito demo notebooks (https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) on the remote compute target and get back the results as saved notebooks that are then Available in Azure portal. \n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Allow multiple displays per cell\n", - "from IPython.core.interactiveshell import InteractiveShell\n", - "InteractiveShell.ast_node_interactivity = \"all\" " - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import sys, os\n", - "import shutil\n", - "import urllib\n", - "import azureml.core\n", - "from azureml.core import Workspace, Experiment\n", - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "from azureml.core.runconfig import MpiConfiguration\n", - "\n", - "\n", - "# from azureml.core.datastore import Datastore\n", - "# from azureml.data.data_reference import DataReference\n", - "# from azureml.pipeline.steps import HyperDriveStep\n", - "# from azureml.pipeline.core import Pipeline, PipelineData\n", - "# from azureml.train.dnn import TensorFlow\n", - "\n", - "from azureml.train.estimator import Estimator\n", - "from azureml.widgets import RunDetails\n", - "\n", - "import platform" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Azure ML SDK Version: 1.0.74\n" - ] - }, - { - "data": { - "text/plain": [ - "'Linux-4.15.0-1063-azure-x86_64-with-debian-10.1'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/contrib/examples/imaging/azureml_devito/notebooks'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", - "platform.platform()\n", - "os.getcwd()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[None]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'./../not_shared/general.env'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def add_path_to_sys_path(path_to_append):\n", - " if not (any(path_to_append in paths for paths in sys.path)):\n", - " sys.path.append(path_to_append)\n", - " \n", - "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", - "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", - "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", - "\n", - "import project_utils\n", - "prj_consts = project_utils.project_consts()\n", - "\n", - "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", - "dotenv_file_path" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext dotenv" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'./../not_shared'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "workspace_config_dir = os.path.join(*(prj_consts.AML_WORKSPACE_CONFIG_DIR))\n", - "workspace_config_file = prj_consts.AML_WORKSPACE_CONFIG_FILE_NAME\n", - "workspace_config_dir" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'./../temp/devito_tutorial/01_modelling.py'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'./../temp/devito_tutorial/azureml_01_modelling.py'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%dotenv $dotenv_file_path\n", - "\n", - "script_folder = prj_consts.AML_EXPERIMENT_DIR + ['devito_tutorial']\n", - "\n", - "devito_training_script_file = '01_modelling.py' # hardcoded in file azureml_training_script_full_file_name below\n", - "azureml_training_script_file = 'azureml_'+devito_training_script_file\n", - "experimentName = '020_AzureMLEstimator'\n", - "\n", - "os.makedirs(os.path.join(*(script_folder)), exist_ok=True)\n", - "script_path = os.path.join(*(script_folder))\n", - "training_script_full_file_name = os.path.join(script_path, devito_training_script_file)\n", - "azureml_training_script_full_file_name = os.path.join(script_path, azureml_training_script_file)\n", - "\n", - "training_script_full_file_name\n", - "azureml_training_script_full_file_name" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - " \n", - "##### devito in Azure ML demo mode 1\n", - "Create devito demo script based on \n", - "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/01_modelling.ipynb\n", - "\n", - "[Back](#devito_in_AzureML_demoing_modes) to summary of modes od demoing devito in AzureML.\n", - "\n", - "Main purpose of this script is to extend _plot_velocity()_ and _plot_shotrecord()_ devito [plotting functions](https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py) to allow the mto work in batch mode, i.e. save output to a file." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting ./../temp/devito_tutorial/01_modelling.py\n" - ] - } - ], - "source": [ - "%%writefile $training_script_full_file_name\n", - "\n", - "import numpy as np\n", - "import os, argparse\n", - "\n", - "from examples.seismic import Model\n", - "from examples.seismic import TimeAxis\n", - "from examples.seismic import Receiver\n", - "from devito import TimeFunction\n", - "from devito import Eq, solve\n", - "from devito import Operator\n", - "\n", - "\n", - "# try:\n", - "import matplotlib as mpl\n", - "import matplotlib.pyplot as plt\n", - "from matplotlib import cm\n", - "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", - "\n", - "mpl.rc('font', size=16)\n", - "mpl.rc('figure', figsize=(8, 6))\n", - "# except:\n", - "# plt = None\n", - "# cm = None\n", - " \n", - "\n", - "\n", - "# \"all\" plotting utils in devito do not save to file, so we extend them here\n", - "# https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py\n", - "def plot_velocity(model, source=None, receiver=None, colorbar=True, file=None):\n", - " \"\"\"\n", - " Plot a two-dimensional velocity field from a seismic `Model`\n", - " object. Optionally also includes point markers for sources and receivers.\n", - "\n", - " Parameters\n", - " ----------\n", - " model : Model\n", - " Object that holds the velocity model.\n", - " source : array_like or float\n", - " Coordinates of the source point.\n", - " receiver : array_like or float\n", - " Coordinates of the receiver points.\n", - " colorbar : bool\n", - " Option to plot the colorbar.\n", - " \"\"\"\n", - " domain_size = 1.e-3 * np.array(model.domain_size)\n", - " extent = [model.origin[0], model.origin[0] + domain_size[0],\n", - " model.origin[1] + domain_size[1], model.origin[1]]\n", - "\n", - " plot = plt.imshow(np.transpose(model.vp.data), animated=True, cmap=cm.jet,\n", - " vmin=np.min(model.vp.data), vmax=np.max(model.vp.data),\n", - " extent=extent)\n", - " plt.xlabel('X position (km)')\n", - " plt.ylabel('Depth (km)')\n", - "\n", - " # Plot source points, if provided\n", - " if receiver is not None:\n", - " plt.scatter(1e-3*receiver[:, 0], 1e-3*receiver[:, 1],\n", - " s=25, c='green', marker='D')\n", - "\n", - " # Plot receiver points, if provided\n", - " if source is not None:\n", - " plt.scatter(1e-3*source[:, 0], 1e-3*source[:, 1],\n", - " s=25, c='red', marker='o')\n", - "\n", - " # Ensure axis limits\n", - " plt.xlim(model.origin[0], model.origin[0] + domain_size[0])\n", - " plt.ylim(model.origin[1] + domain_size[1], model.origin[1])\n", - "\n", - " # Create aligned colorbar on the right\n", - " if colorbar:\n", - " ax = plt.gca()\n", - " divider = make_axes_locatable(ax)\n", - " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", - " cbar = plt.colorbar(plot, cax=cax)\n", - " cbar.set_label('Velocity (km/s)')\n", - " plt.show()\n", - " \n", - " if file is not None:\n", - " plt.savefig(file)\n", - " print('plotted image saved as {} file'.format(file))\n", - " \n", - " plt.clf()\n", - "\n", - "def plot_shotrecord(rec, model, t0, tn, colorbar=True, file=None):\n", - " \"\"\"\n", - " Plot a shot record (receiver values over time).\n", - "\n", - " Parameters\n", - " ----------\n", - " rec :\n", - " Receiver data with shape (time, points).\n", - " model : Model\n", - " object that holds the velocity model.\n", - " t0 : int\n", - " Start of time dimension to plot.\n", - " tn : int\n", - " End of time dimension to plot.\n", - " \"\"\"\n", - " scale = np.max(rec) / 10.\n", - " extent = [model.origin[0], model.origin[0] + 1e-3*model.domain_size[0],\n", - " 1e-3*tn, t0]\n", - "\n", - " plot = plt.imshow(rec, vmin=-scale, vmax=scale, cmap=cm.gray, extent=extent)\n", - " plt.xlabel('X position (km)')\n", - " plt.ylabel('Time (s)')\n", - "\n", - " # Create aligned colorbar on the right\n", - " if colorbar:\n", - " ax = plt.gca()\n", - " divider = make_axes_locatable(ax)\n", - " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", - " plt.colorbar(plot, cax=cax)\n", - " plt.show() \n", - " \n", - " if file is not None:\n", - " plt.savefig(file)\n", - " print('plotted image saved as {} file'.format(file))\n", - " \n", - " plt.clf()\n", - "\n", - "def main(output_folder): \n", - " # 1. Define the physical problem\n", - " # The first step is to define the physical model:\n", - " # - physical dimensions of interest\n", - " # - velocity profile of this physical domain\n", - "\n", - " # Define a physical size\n", - " shape = (101, 101) # Number of grid point (nx, nz)\n", - " spacing = (10., 10.) # Grid spacing in m. The domain size is now 1km by 1km\n", - " origin = (0., 0.) # What is the location of the top left corner. This is necessary to define\n", - " # the absolute location of the source and receivers\n", - "\n", - " # Define a velocity profile. The velocity is in km/s\n", - " v = np.empty(shape, dtype=np.float32)\n", - " v[:, :51] = 1.5\n", - " v[:, 51:] = 2.5\n", - "\n", - " # With the velocity and model size defined, we can create the seismic model that\n", - " # encapsulates this properties. We also define the size of the absorbing layer as 10 grid points\n", - " model = Model(vp=v, origin=origin, shape=shape, spacing=spacing,\n", - " space_order=2, nbpml=10)\n", - "\n", - " plot_velocity(model, \n", - " file= os.path.join(*( [output_folder,'output000.png'])))\n", - " \n", - " # 2. Acquisition geometry\n", - " t0 = 0. # Simulation starts a t=0\n", - " tn = 1000. # Simulation last 1 second (1000 ms)\n", - " dt = model.critical_dt # Time step from model grid spacing\n", - "\n", - " time_range = TimeAxis(start=t0, stop=tn, step=dt)\n", - " from examples.seismic import RickerSource\n", - "\n", - " f0 = 0.010 # Source peak frequency is 10Hz (0.010 kHz)\n", - " src = RickerSource(name='src', grid=model.grid, f0=f0,\n", - " npoint=1, time_range=time_range)\n", - "\n", - " # First, position source centrally in all dimensions, then set depth\n", - " src.coordinates.data[0, :] = np.array(model.domain_size) * .5\n", - " src.coordinates.data[0, -1] = 20. # Depth is 20m\n", - "\n", - " # We can plot the time signature to see the wavelet\n", - "# src.show()\n", - "\n", - " # Create symbol for 101 receivers\n", - " rec = Receiver(name='rec', grid=model.grid, npoint=101, time_range=time_range)\n", - "\n", - " # Prescribe even spacing for receivers along the x-axis\n", - " rec.coordinates.data[:, 0] = np.linspace(0, model.domain_size[0], num=101)\n", - " rec.coordinates.data[:, 1] = 20. # Depth is 20m\n", - "\n", - " # We can now show the source and receivers within our domain:\n", - " # Red dot: Source location\n", - " # Green dots: Receiver locations (every 4th point)\n", - " plot_velocity(model, source=src.coordinates.data,\n", - " receiver=rec.coordinates.data[::4, :], \n", - " file= os.path.join(*( [output_folder,'output010.png'])))\n", - " \n", - " # Define the wavefield with the size of the model and the time dimension\n", - " u = TimeFunction(name=\"u\", grid=model.grid, time_order=2, space_order=2)\n", - "\n", - " # We can now write the PDE\n", - " pde = model.m * u.dt2 - u.laplace + model.damp * u.dt\n", - "\n", - " # The PDE representation is as on paper\n", - " pde\n", - " \n", - " # This discrete PDE can be solved in a time-marching way updating u(t+dt) from the previous time step\n", - " # Devito as a shortcut for u(t+dt) which is u.forward. We can then rewrite the PDE as \n", - " # a time marching updating equation known as a stencil using customized SymPy functions\n", - "\n", - " stencil = Eq(u.forward, solve(pde, u.forward))\n", - " # Finally we define the source injection and receiver read function to generate the corresponding code\n", - " src_term = src.inject(field=u.forward, expr=src * dt**2 / model.m)\n", - "\n", - " # Create interpolation expression for receivers\n", - " rec_term = rec.interpolate(expr=u.forward)\n", - "\n", - " op = Operator([stencil] + src_term + rec_term, subs=model.spacing_map)\n", - " \n", - " op(time=time_range.num-1, dt=model.critical_dt)\n", - " plot_shotrecord(rec.data, model, t0, tn, \n", - " file= os.path.join(*( [output_folder,'output020.png'])))\n", - "\n", - "if __name__ == \"__main__\":\n", - " parser = argparse.ArgumentParser()\n", - " parser.add_argument('--output_folder', type=str, nargs='?', \\\n", - " dest='output_folder', help='ouput artifacts location',\\\n", - " default='.')\n", - " args = parser.parse_args()\n", - " \n", - " main(args.output_folder)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### Get experimentation docker image for devito" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'fwi01_azureml:sdk.v1.0.74'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", - "docker_image_name = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", - "\n", - "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", - "if image_version!=\"\":\n", - " docker_image_name = docker_image_name +':'+ image_version\n", - "\n", - "full_docker_image_name = docker_repo_name + '/' + docker_image_name\n", - " \n", - "docker_image_name\n", - "full_docker_image_name" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Extract/decide the python path in custom docker image that corresponds to desired conda environment. Without this, AzureML tries to create a separate environment." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74 /bin/bash -c \"which python\" '" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/opt/conda/envs/fwi01_conda_env/bin/python'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "get_Python_path_command='docker run -i --rm --name fwi01_azureml_container02 '+ \\\n", - "full_docker_image_name + \\\n", - "' /bin/bash -c \"which python\" '\n", - "get_Python_path_command\n", - "\n", - "\n", - "import subprocess\n", - "python_path_in_docker_image = subprocess.check_output(get_Python_path_command,shell=True,stderr=subprocess.STDOUT).\\\n", - "decode('utf-8').strip()\n", - "python_path_in_docker_image" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "#### Create azureml_script_file that invokes:\n", - " - devito exclusive custom edited training_script_file\n", - " - unedited devito notebooks via papermill (invoked via cli and via ppapermill python API)\n", - "\n", - "[Back](#devito_in_AzureML_demoing_modes) to notebook summary." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting ./../temp/devito_tutorial/azureml_01_modelling.py\n" - ] - } - ], - "source": [ - "%%writefile $azureml_training_script_full_file_name\n", - "\n", - "import argparse\n", - "import os\n", - "os.system('conda env list')\n", - "\n", - "import azureml.core;\n", - "from azureml.core.run import Run\n", - "\n", - "print(azureml.core.VERSION)\n", - "\n", - "parser = argparse.ArgumentParser()\n", - "parser.add_argument('--output_folder', type=str, dest='output_folder', help='ouput artifacts location')\n", - "\n", - "args = parser.parse_args()\n", - "print('args.output_folder is {} but it will be ignored since AzureML_tracked ./outputs will be used'.format(args.output_folder))\n", - "\n", - "# get the Azure ML run object\n", - "run = Run.get_context()\n", - "\n", - "# ./outputs/ folder is autotracked so should get uploaded at the end of the run\n", - "output_dir_AzureML_tracked = './outputs'\n", - "\n", - "crt_dir = os.getcwd()\n", - "\n", - "cli_command= \\\n", - "'cd /devito; /opt/conda/envs/fwi01_conda_env/bin/python '+ crt_dir +'/01_modelling.py' + \\\n", - "' --output_folder '+ crt_dir + output_dir_AzureML_tracked+ '/' + \\\n", - "' > '+ crt_dir + output_dir_AzureML_tracked + '/01_modelling.log' \n", - "# + \\\n", - "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/devito_cli_py.log'\n", - "print('Running devito from cli on 01_modelling.py----BEGIN-----:') \n", - "print(cli_command); print('\\n');os.system(cli_command)\n", - "print('Running devito from cli on 01_modelling.py----END-----:\\n\\n')\n", - "\n", - "cli_command= \\\n", - "'cd /devito; papermill ' + \\\n", - "'./examples/seismic/tutorials/02_rtm.ipynb '+\\\n", - "crt_dir +'/outputs/02_rtm_output.ipynb ' + \\\n", - "'--log-output --no-progress-bar --kernel python3 ' + \\\n", - "' > '+ crt_dir + output_dir_AzureML_tracked + '/02_rtm_output.log' \n", - "# + \\\n", - "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/papermill_cli.log'\n", - "\n", - "# FIXME - activate right conda env for running papermill from cli\n", - "activate_right_conda_env_fixed = False\n", - "if activate_right_conda_env_fixed:\n", - " print('Running papermill from cli on 02_rtm.ipynb----BEGIN-----:') \n", - " print(cli_command); print('\\n');os.system(cli_command)\n", - " print('Running papermill from cli on 02_rtm.ipynb----END-----:\\n\\n') \n", - "\n", - "\n", - "print('Running papermill from Python API on 03_fwi.ipynb----BEGIN-----:') \n", - "import papermill as pm\n", - "os.chdir('/devito')\n", - "pm.execute_notebook(\n", - " './examples/seismic/tutorials/03_fwi.ipynb',\n", - " crt_dir +'/outputs/03_fwi_output.ipynb'\n", - ")\n", - "print('Running papermill from Python API on 03_fwi.ipynb----END-----:') \n", - "\n", - "print('Running papermill from Python API on 04_dask.ipynb----BEGIN-----:') \n", - "import papermill as pm\n", - "os.chdir('/devito')\n", - "pm.execute_notebook(\n", - " './examples/seismic/tutorials/04_dask.ipynb',\n", - " crt_dir +'/outputs/04_dask_output.ipynb'\n", - ")\n", - "print('Running papermill from Python API on 04_dask.ipynb----END-----:') \n", - " \n", - "\n", - "os.system('pwd')\n", - "os.system('ls -l /')\n", - "os.system('ls -l ./')\n", - "os.system('ls -l ' +crt_dir + output_dir_AzureML_tracked)\n", - "run.log('training_message01: ', 'finished experiment')\n", - "print('\\n')" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['azureml_01_modelling.py', '01_modelling.py']" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "script_path=os.path.join(*(script_folder))\n", - "os.listdir(script_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initialize workspace\n", - "\n", - "Initialize a workspace object from persisted configuration. If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, make sure the config file is present at .\\config.json" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Workspace name: ghiordanfwiws\n", - "Azure region: eastus2\n", - "Subscription id: 7899\n" - ] - } - ], - "source": [ - "ws = Workspace.from_config(\n", - " path=os.path.join(os.getcwd(),\n", - " os.path.join(*([workspace_config_dir, '.azureml', workspace_config_file]))))\n", - "print('Workspace name: ' + ws.name, \n", - " 'Azure region: ' + ws.location, \n", - " 'Subscription id: ' + ws.subscription_id[0:4], sep = '\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create an Azure ML experiment\n", - "Let's create an experiment named \"tf-mnist\" and a folder to hold the training scripts. The script runs will be recorded under the experiment in Azure." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "exp = Experiment(workspace=ws, name=experimentName)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Retrieve or create a Azure Machine Learning compute\n", - "Azure Machine Learning Compute is a service for provisioning and managing clusters of Azure virtual machines for running machine learning workloads. Let's create a new Azure Machine Learning Compute in the current workspace, if it doesn't already exist. We will then run the training script on this compute target.\n", - "\n", - "If we could not find the compute with the given name in the previous cell, then we will create a new compute here. This process is broken down into the following steps:\n", - "\n", - "1. Create the configuration\n", - "2. Create the Azure Machine Learning compute\n", - "\n", - "**This process will take a few minutes and is providing only sparse output in the process. Please make sure to wait until the call returns before moving to the next cell.**" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'gpuclstfwi02'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", - "gpu_cluster_name" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Found existing gpu cluster\n" - ] - } - ], - "source": [ - "# Verify that cluster does not exist already\n", - "max_nodes_value = 5\n", - "try:\n", - " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", - " print(\"Found existing gpu cluster\")\n", - "except ComputeTargetException:\n", - " print(\"Could not find ComputeTarget cluster!\")\n", - " \n", - "# # Create a new gpucluster using code below\n", - "# # Specify the configuration for the new cluster\n", - "# compute_config = AmlCompute.provisioning_configuration(vm_size=\"Standard_NC6\",\n", - "# min_nodes=0,\n", - "# max_nodes=max_nodes_value)\n", - "# # Create the cluster with the specified name and configuration\n", - "# gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, compute_config)\n", - "\n", - "# # Wait for the cluster to complete, show the output log\n", - "# gpu_cluster.wait_for_completion(show_output=True)\n", - " \n", - " \n", - "# for demo purposes, show how clsuter properties can be altered post-creation\n", - "gpu_cluster.update(min_nodes=0, max_nodes=max_nodes_value, idle_seconds_before_scaledown=1200)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Create an Azure ML SDK estimator with custom docker image " - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "087f9fbd5cf344cbb55ab4821081324d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574860552_cc296d9b?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1574860552_cc296d9b\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1574860552_cc296d9b\", \"created_utc\": \"2019-11-27T13:15:54.231937Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"mlflow.source.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-11-27T13:19:50.61051Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/55_azureml-execution-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt?sv=2019-02-02&sr=b&sig=%2FF76BbosxuAzbk%2B2sMTd5U8AGl7sfr72xR5KtQVHeAM%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/65_job_prep-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt?sv=2019-02-02&sr=b&sig=mGIIQyUJfiU0CzMCjT7sn5NjpZYAJrbGA1uvdhvsR%2Fo%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=O08hKv6f%2BBx8fMwC0e8DF4VhgqpYd72I8A3rlUglIco%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/75_job_post-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/75_job_post-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt?sv=2019-02-02&sr=b&sig=f2OaM4d2ISojn8LNdS%2Be30xACXpoz0%2BOmr7DIk9QdEA%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=u1Dza2X5QhAOj14DUl7wMHMIwyexsRwc8Va3WzX7tHk%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=faGC2hFaEbNo%2Fun8X37DOvgUw9IOoeOe8BTbre31EVo%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"logs/azureml/728_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/logs/azureml/728_azureml.log?sv=2019-02-02&sr=b&sig=mZ56mvBlObajZ0AgKpAdeNp1lhDNOjN6j6xLg0%2BZkjY%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574860552_cc296d9b/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=HvgJaNtCgSUhoBo7wKxr9e5JUqTf8UfYHDAckVpWiZ8%3D&st=2019-11-27T13%3A09%3A53Z&se=2019-11-27T21%3A19%3A53Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_edc3100245884dedeea0979dd0d3230bc75bcaf720003306edf43f413e1d130b_d.txt\"], [\"logs/azureml/728_azureml.log\"]], \"run_duration\": \"0:03:56\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1574860552_cc296d9b\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-11-27 13:18:09,635|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-11-27 13:18:09,635|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-11-27 13:18:09,635|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-11-27 13:18:09,636|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-11-27 13:18:09,960|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-11-27 13:18:09,961|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-11-27 13:18:10,335|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:18:10,335|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:18:10,335|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:18:10,335|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:18:10,335|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-11-27 13:18:10,997|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-11-27 13:18:11,003|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-11-27 13:18:11,008|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-11-27 13:18:11,014|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-11-27 13:18:11,015|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:18:11,022|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,022|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-11-27 13:18:11,023|azureml.core.authentication|DEBUG|Time to expire 1814262.976988 seconds\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,023|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,024|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,053|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:11,058|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,067|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,072|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,077|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,082|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:11,083|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:18:11,083|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:18:11,083|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574860552_cc296d9b'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0aa686f0-3fcf-4102-ab74-8a6e9c373cb1'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'request-id': '0aa686f0-3fcf-4102-ab74-8a6e9c373cb1'\\n2019-11-27 13:18:11,084|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:18:11,085|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:18:11,085|msrest.http_logger|DEBUG|None\\n2019-11-27 13:18:11,085|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:18:11,085|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:18:11,085|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:18:11,085|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:18:11,161|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:18:11 GMT'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0aa686f0-3fcf-4102-ab74-8a6e9c373cb1'\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:18:11,162|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:18:11,163|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:18:11,163|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:18:11,163|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:18:11,163|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1398,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:15:54.2319371+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:16:06.348303+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574860552_cc296d9b/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574860552_cc296d9b/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574860552_cc296d9b/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:18:11,169|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:18:11,170|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:18:11,170|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:18:11,170|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-11-27 13:18:11,170|azureml.WorkerPool|DEBUG|[START]\\n2019-11-27 13:18:11,170|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-11-27 13:18:11,171|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574860552_cc296d9b/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574860552_cc296d9b\\n2019-11-27 13:18:11,171|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:18:11,171|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574860552_cc296d9b/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574860552_cc296d9b\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,217|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,218|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,218|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,218|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:18:13,224|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,225|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-11-27 13:18:13,230|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,235|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,241|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,246|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:18:13,247|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:18:13,247|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574860552_cc296d9b'\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:18:13,247|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'c9e7dc9f-a9ff-4449-87ef-489a13628a79'\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG| 'request-id': 'c9e7dc9f-a9ff-4449-87ef-489a13628a79'\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:18:13,248|msrest.http_logger|DEBUG|None\\n2019-11-27 13:18:13,248|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:18:13,248|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:18:13,248|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:18:13,248|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:18:13 GMT'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:18:13,301|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'c9e7dc9f-a9ff-4449-87ef-489a13628a79'\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:18:13,302|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1398,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:15:54.2319371+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:16:06.348303+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574860552_cc296d9b\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574860552_cc296d9b/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574860552_cc296d9b/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574860552_cc296d9b/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:18:13,304|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:18:13,304|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:18:13,304|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:18:41,020|azureml.core.authentication|DEBUG|Time to expire 1814232.979555 seconds\\n2019-11-27 13:19:11,020|azureml.core.authentication|DEBUG|Time to expire 1814202.979107 seconds\\n2019-11-27 13:19:29,411|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:19:29,412|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:19:29,412|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574860552_cc296d9b/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574860552_cc296d9b\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574860552_cc296d9b/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574860552_cc296d9b\\n2019-11-27 13:19:29,483|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-11-27 13:19:29,484|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b|INFO|complete is not setting status for submitted runs.\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:19:29,484|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,485|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,485|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,486|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,486|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-11-27 13:19:29,486|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-11-27 13:19:29,486|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-11-27 13:19:29,486|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:19:29,486|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-11-27 13:19:29,486|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-11-27 13:19:29,487|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:19:29,487|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-11-27 13:19:29,487|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-11-27 13:19:29,487|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-11-27 13:19:29,488|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:19:29,489|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-11-27 13:19:29,489|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:19:29,489|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574860552_cc296d9b/batch/metrics'\\n2019-11-27 13:19:29,490|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-11-27 13:19:29,490|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:19:29,490|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:19:29,490|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:19:29,490|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-11-27 13:19:29,491|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '119f863f-8271-4a91-94eb-227d47a35c46'\\n2019-11-27 13:19:29,491|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG| 'request-id': '119f863f-8271-4a91-94eb-227d47a35c46'\\n2019-11-27 13:19:29,491|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-11-27 13:19:29,491|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74 sdk_run'\\n2019-11-27 13:19:29,491|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-11-27 13:19:29,491|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:19:29,492|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"9eac8088-e7ee-4218-a267-72559639c64d\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-11-27T13:19:29.411376Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-11-27 13:19:29,492|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:19:29,492|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:19:29,492|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:19:29,492|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:19:29 GMT'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '119f863f-8271-4a91-94eb-227d47a35c46'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:19:29,631|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:19:29,632|msrest.http_logger|DEBUG|\\n2019-11-27 13:19:29,632|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00022172927856445312 seconds.\\n\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,742|azureml._SubmittedRun#020_AzureMLEstimator_1574860552_cc296d9b.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:19:29,743|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-11-27 13:19:29,743|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-11-27 13:19:29,743|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-11-27 13:19:29,743|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.0.74\"}, \"loading\": false}" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# use a custom Docker image\n", - "from azureml.core.container_registry import ContainerRegistry\n", - "\n", - "image_name = docker_image_name\n", - "\n", - "# you can also point to an image in a private ACR\n", - "image_registry_details = ContainerRegistry()\n", - "image_registry_details.address = docker_repo_name\n", - "image_registry_details.username = os.getenv('ACR_USERNAME')\n", - "image_registry_details.password = os.getenv('ACR_PASSWORD') \n", - "\n", - "# don't let the system build a new conda environment\n", - "user_managed_dependencies = True\n", - "\n", - "# submit to a local Docker container. if you don't have Docker engine running locally, you can set compute_target to cpu_cluster.\n", - "script_params = {\n", - " '--output_folder': 'some_folder'\n", - "}\n", - "\n", - "\n", - "# distributed_training_conf = MpiConfiguration()\n", - "# distributed_training_conf.process_count_per_node = 2\n", - "\n", - "est = Estimator(source_directory=script_path, \n", - " compute_target=gpu_cluster,#'local', #gpu_cluster, \n", - " entry_script=azureml_training_script_file,\n", - " script_params=script_params,\n", - " use_docker=True,\n", - " custom_docker_image=image_name,\n", - " # uncomment below line to use your private ACR\n", - " image_registry_details=image_registry_details, \n", - " user_managed=user_managed_dependencies,\n", - " distributed_training=None,\n", - " node_count=1\n", - " )\n", - "est.run_config.environment.python.interpreter_path = python_path_in_docker_image\n", - "\n", - "run = exp.submit(est)\n", - "RunDetails(run).show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "One can use the above link to currrent experiment run in Azure Portal to see tracked metrics, and images and output notebooks saved by azureml_training_script_full_file_name in {run_dir}/outputs on the remote compute target that are automatically saved by AzureML in the run history Azure portal pages." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'runId= 020_AzureMLEstimator_1574860552_cc296d9b'" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'experimentation baseImage: fwi01_azureml:sdk.v1.0.74'" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run_details = run.get_details()\n", - "\n", - "# print some details of job run\n", - "'runId= {}'.format(run_details['runId'])\n", - "'experimentation baseImage: {}'.format(run_details['runDefinition']['environment']['docker']['baseImage'])" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!\n" - ] - } - ], - "source": [ - "print('Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/contrib/examples/imaging/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb deleted file mode 100755 index df9405d4..00000000 --- a/contrib/examples/imaging/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ /dev/null @@ -1,1272 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. \n", - "Licensed under the MIT License. \n", - " \n", - "\n", - "# FWI demo based on: \n", - "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", - "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", - "\n", - "\n", - "\n", - "In this notebook we run the devito demo [notebooks](https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/) mentioned above by using an [AzureML estimator](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator.estimator?view=azure-ml-py) with custom docker image. The docker image and associated docker file were created in previous notebook.\n", - "\n", - "\n", - "#### This notebook is used as a control plane to submit experimentation jobs running devito in Azure in two modes (see [remote run azureml python script file invoking devito](#devito_demo_mode)):\n", - " - [Mode 1](#devito_demo_mode_1):\n", - " - uses custom code (slightly modified graphing functions save images to files too) \n", - " - experimentation job is defined by the devito code that is packaged as a py file to be run on an Azure remote compute target\n", - " - experimentation job can be used to track metrics or other artifacts (images)\n", - " \n", - " - Mode 2:\n", - " - papermill is invoked via cli or via its Python API to run unedited devito demo notebooks (https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) on the remote compute target and get back the results as saved notebooks that are then Available in Azure portal. \n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Allow multiple displays per cell\n", - "from IPython.core.interactiveshell import InteractiveShell\n", - "InteractiveShell.ast_node_interactivity = \"all\" " - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import sys, os\n", - "import shutil\n", - "import urllib\n", - "import azureml.core\n", - "from azureml.core import Workspace, Experiment\n", - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "from azureml.core.runconfig import MpiConfiguration\n", - "\n", - "\n", - "# from azureml.core.datastore import Datastore\n", - "# from azureml.data.data_reference import DataReference\n", - "# from azureml.pipeline.steps import HyperDriveStep\n", - "# from azureml.pipeline.core import Pipeline, PipelineData\n", - "# from azureml.train.dnn import TensorFlow\n", - "\n", - "from azureml.train.estimator import Estimator\n", - "from azureml.widgets import RunDetails\n", - "\n", - "import platform" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Azure ML SDK Version: 1.0.74\n" - ] - }, - { - "data": { - "text/plain": [ - "'Linux-4.15.0-1063-azure-x86_64-with-debian-10.1'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/contrib/examples/imaging/azureml_devito/notebooks'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", - "platform.platform()\n", - "os.getcwd()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[None]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'./../not_shared/general.env'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def add_path_to_sys_path(path_to_append):\n", - " if not (any(path_to_append in paths for paths in sys.path)):\n", - " sys.path.append(path_to_append)\n", - " \n", - "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", - "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", - "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", - "\n", - "import project_utils\n", - "prj_consts = project_utils.project_consts()\n", - "\n", - "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", - "dotenv_file_path" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext dotenv" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'./../not_shared'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "workspace_config_dir = os.path.join(*(prj_consts.AML_WORKSPACE_CONFIG_DIR))\n", - "workspace_config_file = prj_consts.AML_WORKSPACE_CONFIG_FILE_NAME\n", - "workspace_config_dir" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'./../temp/devito_tutorial/01_modelling.py'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'./../temp/devito_tutorial/azureml_01_modelling.py'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%dotenv $dotenv_file_path\n", - "\n", - "script_folder = prj_consts.AML_EXPERIMENT_DIR + ['devito_tutorial']\n", - "\n", - "devito_training_script_file = '01_modelling.py' # hardcoded in file azureml_training_script_full_file_name below\n", - "azureml_training_script_file = 'azureml_'+devito_training_script_file\n", - "experimentName = '020_AzureMLEstimator'\n", - "\n", - "os.makedirs(os.path.join(*(script_folder)), exist_ok=True)\n", - "script_path = os.path.join(*(script_folder))\n", - "training_script_full_file_name = os.path.join(script_path, devito_training_script_file)\n", - "azureml_training_script_full_file_name = os.path.join(script_path, azureml_training_script_file)\n", - "\n", - "training_script_full_file_name\n", - "azureml_training_script_full_file_name" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - " \n", - "##### devito in Azure ML demo mode 1\n", - "Create devito demo script based on \n", - "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/01_modelling.ipynb\n", - "\n", - "[Back](#devito_in_AzureML_demoing_modes) to summary of modes od demoing devito in AzureML.\n", - "\n", - "Main purpose of this script is to extend _plot_velocity()_ and _plot_shotrecord()_ devito [plotting functions](https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py) to allow the mto work in batch mode, i.e. save output to a file." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting ./../temp/devito_tutorial/01_modelling.py\n" - ] - } - ], - "source": [ - "%%writefile $training_script_full_file_name\n", - "\n", - "import numpy as np\n", - "import os, argparse\n", - "\n", - "from examples.seismic import Model\n", - "from examples.seismic import TimeAxis\n", - "from examples.seismic import Receiver\n", - "from devito import TimeFunction\n", - "from devito import Eq, solve\n", - "from devito import Operator\n", - "\n", - "\n", - "# try:\n", - "import matplotlib as mpl\n", - "import matplotlib.pyplot as plt\n", - "from matplotlib import cm\n", - "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", - "\n", - "mpl.rc('font', size=16)\n", - "mpl.rc('figure', figsize=(8, 6))\n", - "# except:\n", - "# plt = None\n", - "# cm = None\n", - " \n", - "\n", - "\n", - "# \"all\" plotting utils in devito do not save to file, so we extend them here\n", - "# https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py\n", - "def plot_velocity(model, source=None, receiver=None, colorbar=True, file=None):\n", - " \"\"\"\n", - " Plot a two-dimensional velocity field from a seismic `Model`\n", - " object. Optionally also includes point markers for sources and receivers.\n", - "\n", - " Parameters\n", - " ----------\n", - " model : Model\n", - " Object that holds the velocity model.\n", - " source : array_like or float\n", - " Coordinates of the source point.\n", - " receiver : array_like or float\n", - " Coordinates of the receiver points.\n", - " colorbar : bool\n", - " Option to plot the colorbar.\n", - " \"\"\"\n", - " domain_size = 1.e-3 * np.array(model.domain_size)\n", - " extent = [model.origin[0], model.origin[0] + domain_size[0],\n", - " model.origin[1] + domain_size[1], model.origin[1]]\n", - "\n", - " plot = plt.imshow(np.transpose(model.vp.data), animated=True, cmap=cm.jet,\n", - " vmin=np.min(model.vp.data), vmax=np.max(model.vp.data),\n", - " extent=extent)\n", - " plt.xlabel('X position (km)')\n", - " plt.ylabel('Depth (km)')\n", - "\n", - " # Plot source points, if provided\n", - " if receiver is not None:\n", - " plt.scatter(1e-3*receiver[:, 0], 1e-3*receiver[:, 1],\n", - " s=25, c='green', marker='D')\n", - "\n", - " # Plot receiver points, if provided\n", - " if source is not None:\n", - " plt.scatter(1e-3*source[:, 0], 1e-3*source[:, 1],\n", - " s=25, c='red', marker='o')\n", - "\n", - " # Ensure axis limits\n", - " plt.xlim(model.origin[0], model.origin[0] + domain_size[0])\n", - " plt.ylim(model.origin[1] + domain_size[1], model.origin[1])\n", - "\n", - " # Create aligned colorbar on the right\n", - " if colorbar:\n", - " ax = plt.gca()\n", - " divider = make_axes_locatable(ax)\n", - " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", - " cbar = plt.colorbar(plot, cax=cax)\n", - " cbar.set_label('Velocity (km/s)')\n", - " plt.show()\n", - " \n", - " if file is not None:\n", - " plt.savefig(file)\n", - " print('plotted image saved as {} file'.format(file))\n", - " \n", - " plt.clf()\n", - "\n", - "def plot_shotrecord(rec, model, t0, tn, colorbar=True, file=None):\n", - " \"\"\"\n", - " Plot a shot record (receiver values over time).\n", - "\n", - " Parameters\n", - " ----------\n", - " rec :\n", - " Receiver data with shape (time, points).\n", - " model : Model\n", - " object that holds the velocity model.\n", - " t0 : int\n", - " Start of time dimension to plot.\n", - " tn : int\n", - " End of time dimension to plot.\n", - " \"\"\"\n", - " scale = np.max(rec) / 10.\n", - " extent = [model.origin[0], model.origin[0] + 1e-3*model.domain_size[0],\n", - " 1e-3*tn, t0]\n", - "\n", - " plot = plt.imshow(rec, vmin=-scale, vmax=scale, cmap=cm.gray, extent=extent)\n", - " plt.xlabel('X position (km)')\n", - " plt.ylabel('Time (s)')\n", - "\n", - " # Create aligned colorbar on the right\n", - " if colorbar:\n", - " ax = plt.gca()\n", - " divider = make_axes_locatable(ax)\n", - " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", - " plt.colorbar(plot, cax=cax)\n", - " plt.show() \n", - " \n", - " if file is not None:\n", - " plt.savefig(file)\n", - " print('plotted image saved as {} file'.format(file))\n", - " \n", - " plt.clf()\n", - "\n", - "def main(output_folder): \n", - " # 1. Define the physical problem\n", - " # The first step is to define the physical model:\n", - " # - physical dimensions of interest\n", - " # - velocity profile of this physical domain\n", - "\n", - " # Define a physical size\n", - " shape = (101, 101) # Number of grid point (nx, nz)\n", - " spacing = (10., 10.) # Grid spacing in m. The domain size is now 1km by 1km\n", - " origin = (0., 0.) # What is the location of the top left corner. This is necessary to define\n", - " # the absolute location of the source and receivers\n", - "\n", - " # Define a velocity profile. The velocity is in km/s\n", - " v = np.empty(shape, dtype=np.float32)\n", - " v[:, :51] = 1.5\n", - " v[:, 51:] = 2.5\n", - "\n", - " # With the velocity and model size defined, we can create the seismic model that\n", - " # encapsulates this properties. We also define the size of the absorbing layer as 10 grid points\n", - " model = Model(vp=v, origin=origin, shape=shape, spacing=spacing,\n", - " space_order=2, nbpml=10)\n", - "\n", - " plot_velocity(model, \n", - " file= os.path.join(*( [output_folder,'output000.png'])))\n", - " \n", - " # 2. Acquisition geometry\n", - " t0 = 0. # Simulation starts a t=0\n", - " tn = 1000. # Simulation last 1 second (1000 ms)\n", - " dt = model.critical_dt # Time step from model grid spacing\n", - "\n", - " time_range = TimeAxis(start=t0, stop=tn, step=dt)\n", - " from examples.seismic import RickerSource\n", - "\n", - " f0 = 0.010 # Source peak frequency is 10Hz (0.010 kHz)\n", - " src = RickerSource(name='src', grid=model.grid, f0=f0,\n", - " npoint=1, time_range=time_range)\n", - "\n", - " # First, position source centrally in all dimensions, then set depth\n", - " src.coordinates.data[0, :] = np.array(model.domain_size) * .5\n", - " src.coordinates.data[0, -1] = 20. # Depth is 20m\n", - "\n", - " # We can plot the time signature to see the wavelet\n", - "# src.show()\n", - "\n", - " # Create symbol for 101 receivers\n", - " rec = Receiver(name='rec', grid=model.grid, npoint=101, time_range=time_range)\n", - "\n", - " # Prescribe even spacing for receivers along the x-axis\n", - " rec.coordinates.data[:, 0] = np.linspace(0, model.domain_size[0], num=101)\n", - " rec.coordinates.data[:, 1] = 20. # Depth is 20m\n", - "\n", - " # We can now show the source and receivers within our domain:\n", - " # Red dot: Source location\n", - " # Green dots: Receiver locations (every 4th point)\n", - " plot_velocity(model, source=src.coordinates.data,\n", - " receiver=rec.coordinates.data[::4, :], \n", - " file= os.path.join(*( [output_folder,'output010.png'])))\n", - " \n", - " # Define the wavefield with the size of the model and the time dimension\n", - " u = TimeFunction(name=\"u\", grid=model.grid, time_order=2, space_order=2)\n", - "\n", - " # We can now write the PDE\n", - " pde = model.m * u.dt2 - u.laplace + model.damp * u.dt\n", - "\n", - " # The PDE representation is as on paper\n", - " pde\n", - " \n", - " # This discrete PDE can be solved in a time-marching way updating u(t+dt) from the previous time step\n", - " # Devito as a shortcut for u(t+dt) which is u.forward. We can then rewrite the PDE as \n", - " # a time marching updating equation known as a stencil using customized SymPy functions\n", - "\n", - " stencil = Eq(u.forward, solve(pde, u.forward))\n", - " # Finally we define the source injection and receiver read function to generate the corresponding code\n", - " src_term = src.inject(field=u.forward, expr=src * dt**2 / model.m)\n", - "\n", - " # Create interpolation expression for receivers\n", - " rec_term = rec.interpolate(expr=u.forward)\n", - "\n", - " op = Operator([stencil] + src_term + rec_term, subs=model.spacing_map)\n", - " \n", - " op(time=time_range.num-1, dt=model.critical_dt)\n", - " plot_shotrecord(rec.data, model, t0, tn, \n", - " file= os.path.join(*( [output_folder,'output020.png'])))\n", - "\n", - "if __name__ == \"__main__\":\n", - " parser = argparse.ArgumentParser()\n", - " parser.add_argument('--output_folder', type=str, nargs='?', \\\n", - " dest='output_folder', help='ouput artifacts location',\\\n", - " default='.')\n", - " args = parser.parse_args()\n", - " \n", - " main(args.output_folder)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### Get experimentation docker image for devito" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'fwi01_azureml:sdk.v1.0.74'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", - "\n", - "docker_image_name = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", - "\n", - "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", - "if image_version!=\"\":\n", - " docker_image_name = docker_image_name +':'+ image_version\n", - "\n", - "full_docker_image_name = docker_repo_name + '/' + docker_image_name\n", - " \n", - "docker_image_name\n", - "full_docker_image_name" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Extract/decide the python path in custom docker image that corresponds to desired conda environment. Without this, AzureML tries to create a separate environment." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.74 /bin/bash -c \"which python\" '" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/opt/conda/envs/fwi01_conda_env/bin/python'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "get_Python_path_command='docker run -i --rm --name fwi01_azureml_container02 '+ \\\n", - "full_docker_image_name + \\\n", - "' /bin/bash -c \"which python\" '\n", - "get_Python_path_command\n", - "\n", - "\n", - "import subprocess\n", - "python_path_in_docker_image = subprocess.check_output(get_Python_path_command,shell=True,stderr=subprocess.STDOUT).\\\n", - "decode('utf-8').strip()\n", - "python_path_in_docker_image" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "#### Create azureml_script_file that invokes:\n", - " - devito exclusive custom edited training_script_file\n", - " - unedited devito notebooks via papermill (invoked via cli and via ppapermill python API)\n", - "\n", - "[Back](#devito_in_AzureML_demoing_modes) to notebook summary." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting ./../temp/devito_tutorial/azureml_01_modelling.py\n" - ] - } - ], - "source": [ - "%%writefile $azureml_training_script_full_file_name\n", - "\n", - "import argparse\n", - "import os\n", - "os.system('conda env list')\n", - "\n", - "import azureml.core;\n", - "from azureml.core.run import Run\n", - "\n", - "print(azureml.core.VERSION)\n", - "\n", - "parser = argparse.ArgumentParser()\n", - "parser.add_argument('--output_folder', type=str, dest='output_folder', help='ouput artifacts location')\n", - "\n", - "args = parser.parse_args()\n", - "print('args.output_folder is {} but it will be ignored since AzureML_tracked ./outputs will be used'.format(args.output_folder))\n", - "\n", - "# get the Azure ML run object\n", - "run = Run.get_context()\n", - "\n", - "# ./outputs/ folder is autotracked so should get uploaded at the end of the run\n", - "output_dir_AzureML_tracked = './outputs'\n", - "\n", - "crt_dir = os.getcwd()\n", - "\n", - "cli_command= \\\n", - "'cd /devito; /opt/conda/envs/fwi01_conda_env/bin/python '+ crt_dir +'/01_modelling.py' + \\\n", - "' --output_folder '+ crt_dir + output_dir_AzureML_tracked+ '/' + \\\n", - "' > '+ crt_dir + output_dir_AzureML_tracked + '/01_modelling.log' \n", - "# + \\\n", - "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/devito_cli_py.log'\n", - "print('Running devito from cli on 01_modelling.py----BEGIN-----:') \n", - "print(cli_command); print('\\n');os.system(cli_command)\n", - "print('Running devito from cli on 01_modelling.py----END-----:\\n\\n')\n", - "\n", - "cli_command= \\\n", - "'cd /devito; papermill ' + \\\n", - "'./examples/seismic/tutorials/02_rtm.ipynb '+\\\n", - "crt_dir +'/outputs/02_rtm_output.ipynb ' + \\\n", - "'--log-output --no-progress-bar --kernel python3 ' + \\\n", - "' > '+ crt_dir + output_dir_AzureML_tracked + '/02_rtm_output.log' \n", - "# + \\\n", - "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/papermill_cli.log'\n", - "\n", - "# FIXME - activate right conda env for running papermill from cli\n", - "activate_right_conda_env_fixed = False\n", - "if activate_right_conda_env_fixed:\n", - " print('Running papermill from cli on 02_rtm.ipynb----BEGIN-----:') \n", - " print(cli_command); print('\\n');os.system(cli_command)\n", - " print('Running papermill from cli on 02_rtm.ipynb----END-----:\\n\\n') \n", - "\n", - "\n", - "print('Running papermill from Python API on 03_fwi.ipynb----BEGIN-----:') \n", - "import papermill as pm\n", - "os.chdir('/devito')\n", - "pm.execute_notebook(\n", - " './examples/seismic/tutorials/03_fwi.ipynb',\n", - " crt_dir +'/outputs/03_fwi_output.ipynb'\n", - ")\n", - "print('Running papermill from Python API on 03_fwi.ipynb----END-----:') \n", - "\n", - "print('Running papermill from Python API on 04_dask.ipynb----BEGIN-----:') \n", - "import papermill as pm\n", - "os.chdir('/devito')\n", - "pm.execute_notebook(\n", - " './examples/seismic/tutorials/04_dask.ipynb',\n", - " crt_dir +'/outputs/04_dask_output.ipynb'\n", - ")\n", - "print('Running papermill from Python API on 04_dask.ipynb----END-----:') \n", - " \n", - "\n", - "os.system('pwd')\n", - "os.system('ls -l /')\n", - "os.system('ls -l ./')\n", - "os.system('ls -l ' +crt_dir + output_dir_AzureML_tracked)\n", - "run.log('training_message01: ', 'finished experiment')\n", - "print('\\n')" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['azureml_01_modelling.py', '01_modelling.py']" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "script_path=os.path.join(*(script_folder))\n", - "os.listdir(script_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initialize workspace\n", - "\n", - "Initialize a workspace object from persisted configuration. If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, make sure the config file is present at .\\config.json" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Workspace name: ghiordanfwiws\n", - "Azure region: eastus2\n", - "Subscription id: 7899\n" - ] - } - ], - "source": [ - "ws = Workspace.from_config(\n", - " path=os.path.join(os.getcwd(),\n", - " os.path.join(*([workspace_config_dir, '.azureml', workspace_config_file]))))\n", - "print('Workspace name: ' + ws.name, \n", - " 'Azure region: ' + ws.location, \n", - " 'Subscription id: ' + ws.subscription_id[0:4], sep = '\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create an Azure ML experiment\n", - "Let's create an experiment named \"tf-mnist\" and a folder to hold the training scripts. The script runs will be recorded under the experiment in Azure." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "exp = Experiment(workspace=ws, name=experimentName)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Retrieve or create a Azure Machine Learning compute\n", - "Azure Machine Learning Compute is a service for provisioning and managing clusters of Azure virtual machines for running machine learning workloads. Let's create a new Azure Machine Learning Compute in the current workspace, if it doesn't already exist. We will then run the training script on this compute target.\n", - "\n", - "If we could not find the compute with the given name in the previous cell, then we will create a new compute here. This process is broken down into the following steps:\n", - "\n", - "1. Create the configuration\n", - "2. Create the Azure Machine Learning compute\n", - "\n", - "**This process will take a few minutes and is providing only sparse output in the process. Please make sure to wait until the call returns before moving to the next cell.**" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'gpuclstfwi08'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", - "gpu_cluster_name = 'gpuclstfwi08'\n", - "gpu_cluster_name" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Found existing gpu cluster\n" - ] - } - ], - "source": [ - "# Verify that cluster does not exist already\n", - "max_nodes_value = 5\n", - "try:\n", - " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", - " print(\"Found existing gpu cluster\")\n", - "except ComputeTargetException:\n", - " print(\"Could not find ComputeTarget cluster!\")\n", - " \n", - "# # Create a new gpucluster using code below\n", - "# # Specify the configuration for the new cluster\n", - "# compute_config = AmlCompute.provisioning_configuration(vm_size=\"Standard_NC6\",\n", - "# min_nodes=0,\n", - "# max_nodes=max_nodes_value)\n", - "# # Create the cluster with the specified name and configuration\n", - "# gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, compute_config)\n", - "\n", - "# # Wait for the cluster to complete, show the output log\n", - "# gpu_cluster.wait_for_completion(show_output=True)\n", - " \n", - " \n", - "# for demo purposes, show how clsuter properties can be altered post-creation\n", - "gpu_cluster.update(min_nodes=0, max_nodes=max_nodes_value, idle_seconds_before_scaledown=1200)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Create an Azure ML SDK estimator with custom docker image " - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "80ea5d0977e248919b48c668a7f5b6ad", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574861911_2e9e0637?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1574861911_2e9e0637\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1574861911_2e9e0637\", \"created_utc\": \"2019-11-27T13:38:35.066726Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"mlflow.source.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-11-27T13:40:51.446761Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/55_azureml-execution-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt?sv=2019-02-02&sr=b&sig=TT5sa3P1MRW7fI4fRLIGTMoqSPvlGUOBqAYmM%2ByqpjY%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/65_job_prep-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt?sv=2019-02-02&sr=b&sig=PmGsvNZXAPsPIBpIk6p03sDvRsh3y5FTqGJlqLFQ7k8%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=pc1Jnlu3XArHtoScX9eVTC9ScVC4U6BIVfUDN4WzrLs%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/75_job_post-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/75_job_post-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt?sv=2019-02-02&sr=b&sig=rogM18tpVxhJaKR0et6UjN7dLTTKVoQIk1d7KXF76KU%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=MjKESRWtWOyWQH5B%2F8FaMdTSXN1bMmorNEuLFjIyRkM%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=Fq18rEAa1tUnD1URPEi2GwMROmqnhpZRK3mL4wrctUo%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"logs/azureml/729_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/logs/azureml/729_azureml.log?sv=2019-02-02&sr=b&sig=RaCw0b35z0EqgOjs1YgpCFSVw2TJ9anLbbhl4Ff6ua4%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574861911_2e9e0637/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=ROpfnTuQup%2BJz%2B9wDl9ByxbA5T4NDdzbUJp9635gk4A%3D&st=2019-11-27T13%3A30%3A53Z&se=2019-11-27T21%3A40%3A53Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\"], [\"azureml-logs/65_job_prep-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_e3349815f27090e53a9d07aeaf5f0cb2d52e876722921a40c4a47b5ba1f1883c_p.txt\"], [\"logs/azureml/729_azureml.log\"]], \"run_duration\": \"0:02:16\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1574861911_2e9e0637\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-11-27 13:39:14,199|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-11-27 13:39:14,200|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-11-27 13:39:14,200|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-11-27 13:39:14,200|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-11-27 13:39:14,460|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-11-27 13:39:14,461|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-11-27 13:39:14,780|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:39:14,780|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:39:14,780|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:39:14,780|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:39:14,780|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-11-27 13:39:15,303|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-11-27 13:39:15,307|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-11-27 13:39:15,311|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-11-27 13:39:15,315|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-11-27 13:39:15,316|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:39:15,321|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,322|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-11-27 13:39:15,322|azureml.core.authentication|DEBUG|Time to expire 1814359.677606 seconds\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,322|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,323|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,323|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,349|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:15,354|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,359|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,364|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,368|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,372|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:15,372|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:39:15,373|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574861911_2e9e0637'\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:39:15,373|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'bef4b830-fd2f-4981-8d88-8e5b6456fcd5'\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG| 'request-id': 'bef4b830-fd2f-4981-8d88-8e5b6456fcd5'\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:39:15,374|msrest.http_logger|DEBUG|None\\n2019-11-27 13:39:15,374|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:39:15,374|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:39:15,374|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:39:15,374|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:39:15,431|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:39:15,431|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:39:15 GMT'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'bef4b830-fd2f-4981-8d88-8e5b6456fcd5'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:39:15,432|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:39:15,433|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1419,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:38:35.0667261+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:39:02.8734776+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574861911_2e9e0637/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574861911_2e9e0637/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574861911_2e9e0637/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:39:15,437|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:39:15,438|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:39:15,438|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:39:15,438|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-11-27 13:39:15,438|azureml.WorkerPool|DEBUG|[START]\\n2019-11-27 13:39:15,438|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-11-27 13:39:15,438|azureml.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:39:15,438|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:39:15,439|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-11-27 13:39:15,439|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-11-27 13:39:15,439|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574861911_2e9e0637/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574861911_2e9e0637\\n2019-11-27 13:39:15,439|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:39:15,439|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574861911_2e9e0637/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574861911_2e9e0637\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,334|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,335|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:39:17,339|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,340|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-11-27 13:39:17,344|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,349|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,353|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,357|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:39:17,358|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:39:17,358|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574861911_2e9e0637'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a0d8a26-00f5-4a0c-b9ed-080c13576b5f'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'request-id': '2a0d8a26-00f5-4a0c-b9ed-080c13576b5f'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:39:17,358|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:39:17,359|msrest.http_logger|DEBUG|None\\n2019-11-27 13:39:17,359|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:39:17,359|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:39:17,359|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:39:17,359|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:39:17 GMT'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:39:17,409|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a0d8a26-00f5-4a0c-b9ed-080c13576b5f'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:39:17,410|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1419,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:38:35.0667261+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:39:02.8734776+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574861911_2e9e0637\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574861911_2e9e0637/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574861911_2e9e0637/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574861911_2e9e0637/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:39:17,412|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:39:17,412|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:39:17,412|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:39:45,317|azureml.core.authentication|DEBUG|Time to expire 1814329.682784 seconds\\n2019-11-27 13:40:15,317|azureml.core.authentication|DEBUG|Time to expire 1814299.682403 seconds\\n2019-11-27 13:40:31,838|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:40:31,838|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:40:31,838|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:40:31,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:40:31,911|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-11-27 13:40:31,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-11-27 13:40:31,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574861911_2e9e0637/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574861911_2e9e0637\\n2019-11-27 13:40:31,912|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574861911_2e9e0637/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574861911_2e9e0637\\n2019-11-27 13:40:31,912|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-11-27 13:40:31,912|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637|INFO|complete is not setting status for submitted runs.\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:40:31,912|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:31,913|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-11-27 13:40:31,914|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-11-27 13:40:31,914|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-11-27 13:40:31,914|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:40:31,914|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-11-27 13:40:31,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-11-27 13:40:31,915|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-11-27 13:40:31,915|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:40:31,915|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-11-27 13:40:31,916|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:40:31,916|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-11-27 13:40:31,916|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-11-27 13:40:31,916|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:40:31,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-11-27 13:40:31,917|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574861911_2e9e0637/batch/metrics'\\n2019-11-27 13:40:31,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-11-27 13:40:31,917|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-11-27 13:40:31,917|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:40:31,917|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:40:31,917|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:40:31,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-11-27 13:40:31,918|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'fd06b231-4412-4943-bb03-c10b2b321592'\\n2019-11-27 13:40:31,918|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'request-id': 'fd06b231-4412-4943-bb03-c10b2b321592'\\n2019-11-27 13:40:31,918|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-11-27 13:40:31,918|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:40:31,918|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74 sdk_run'\\n2019-11-27 13:40:31,919|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-11-27 13:40:31,919|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:40:31,919|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"b5de25fb-c539-4fb1-892f-894e0843e150\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-11-27T13:40:31.837846Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-11-27 13:40:31,919|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:40:31,919|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:40:31,919|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:40:31,919|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:40:32 GMT'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'fd06b231-4412-4943-bb03-c10b2b321592'\\n2019-11-27 13:40:32,047|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:40:32,048|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:40:32,048|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:40:32,048|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:40:32,048|msrest.http_logger|DEBUG|\\n2019-11-27 13:40:32,048|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00026988983154296875 seconds.\\n\\n2019-11-27 13:40:32,169|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:32,170|azureml._SubmittedRun#020_AzureMLEstimator_1574861911_2e9e0637.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:40:32,170|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-11-27 13:40:32,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-11-27 13:40:32,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-11-27 13:40:32,170|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.0.74\"}, \"loading\": false}" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# use a custom Docker image\n", - "from azureml.core.container_registry import ContainerRegistry\n", - "\n", - "image_name = docker_image_name\n", - "\n", - "# you can also point to an image in a private ACR\n", - "image_registry_details = ContainerRegistry()\n", - "image_registry_details.address = docker_repo_name\n", - "image_registry_details.username = os.getenv('ACR_USERNAME')\n", - "image_registry_details.password = os.getenv('ACR_PASSWORD') \n", - "\n", - "# don't let the system build a new conda environment\n", - "user_managed_dependencies = True\n", - "\n", - "# submit to a local Docker container. if you don't have Docker engine running locally, you can set compute_target to cpu_cluster.\n", - "script_params = {\n", - " '--output_folder': 'some_folder'\n", - "}\n", - "\n", - "\n", - "# distributed_training_conf = MpiConfiguration()\n", - "# distributed_training_conf.process_count_per_node = 2\n", - "\n", - "est = Estimator(source_directory=script_path, \n", - " compute_target=gpu_cluster,#'local', #gpu_cluster, \n", - " entry_script=azureml_training_script_file,\n", - " script_params=script_params,\n", - " use_docker=True,\n", - " custom_docker_image=image_name,\n", - " # uncomment below line to use your private ACR\n", - " image_registry_details=image_registry_details, \n", - " user_managed=user_managed_dependencies,\n", - " distributed_training=None,\n", - " node_count=1\n", - " )\n", - "est.run_config.environment.python.interpreter_path = python_path_in_docker_image\n", - "\n", - "run = exp.submit(est)\n", - "RunDetails(run).show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "One can use the above link to currrent experiment run in Azure Portal to see tracked metrics, and images and output notebooks saved by azureml_training_script_full_file_name in {run_dir}/outputs on the remote compute target that are automatically saved by AzureML in the run history Azure portal pages." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "response = run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Final print 7, time 15.769 seconds: Counter({'Completed': 1})\r" - ] - } - ], - "source": [ - "import time\n", - "from collections import Counter\n", - "#wait till all jobs finished\n", - "\n", - "def wait_for_run_list_to_finish(the_run_list):\n", - " finished_status_list = ['Completed', 'Failed']\n", - " printing_counter = 0\n", - " start_time = time.time()\n", - " while (not all((crt_queried_job.get_status() in finished_status_list) for crt_queried_job in the_run_list)):\n", - " time.sleep(2)\n", - " printing_counter+= 1\n", - " print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", - " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\")\n", - "# final status\n", - " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", - " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", - "wait_for_run_list_to_finish([run])" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "run_duration in seconds 108.573284\n", - "run_duration= 1m 48.573s\n" - ] - } - ], - "source": [ - "import datetime, math\n", - "def get_run_duration(azureml_exp_run):\n", - " run_details = azureml_exp_run.get_details()\n", - " run_duration = datetime.datetime.strptime(run_details['endTimeUtc'], \"%Y-%m-%dT%H:%M:%S.%fZ\") - \\\n", - " datetime.datetime.strptime(run_details['startTimeUtc'], \"%Y-%m-%dT%H:%M:%S.%fZ\")\n", - " return run_duration.total_seconds()\n", - "run_duration = get_run_duration(run)\n", - "\n", - "run_seconds, run_minutes = math.modf(run_duration/60)\n", - "print('run_duration in seconds {}'.format(run_duration))\n", - "print('run_duration= {0:.0f}m {1:.3f}s'.format(run_minutes, run_seconds*60))\n" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Showing details for run 15\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "4c927a9a365c46c89659cac33fb5b242", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574862227_525f5932?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1574862227_525f5932\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1574862227_525f5932\", \"created_utc\": \"2019-11-27T13:43:50.112507Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"mlflow.source.git.commit\": \"439c5c2a8a4e21299a6d46623130d25159745e6d\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":0,\\\"RunningNodeCount\\\":10,\\\"CurrentNodeCount\\\":10}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-11-27T13:46:12.593103Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/55_azureml-execution-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt?sv=2019-02-02&sr=b&sig=svX0XzF6dV2Q%2BStMcT%2BgFfppPMiJD8qOKfEydMeMFMI%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/65_job_prep-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt?sv=2019-02-02&sr=b&sig=0Bsb8vX2yn1Hll1IHHcShRbS53YyFHma%2FfZj0A1j3%2FA%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=dwKEImPP5vJC8sx7NZgeln%2F392uvl0VFmuBRMdHVoqA%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/75_job_post-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/75_job_post-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt?sv=2019-02-02&sr=b&sig=nh7cg3qW12z%2FgH%2Fmd1TtxWkWXB1egCHAnaxSluEyKt8%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=TyDPhQzs6GRrPDAPRabyZoYbK1NxlyPWTb7llH1OQ0I%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=cVTzsFqFGHhh14TdeBhKGHlFNkkpI6ezUhwx2JAacZc%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"logs/azureml/729_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/logs/azureml/729_azureml.log?sv=2019-02-02&sr=b&sig=S4Bbzo2wIG5HWCNTw2akX5exlL3LpHPAsArMA4b5kOY%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1574862227_525f5932/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=brICMUePTeIK8l2uHAiQOv5J2mGLlkbmh%2FIt5IUqC28%3D&st=2019-11-27T13%3A36%3A16Z&se=2019-11-27T21%3A46%3A16Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\"], [\"azureml-logs/65_job_prep-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_579c72e865d72e623288cb3c1143e1e72693de194964cd392b79e130963d6b47_p.txt\"], [\"logs/azureml/729_azureml.log\"]], \"run_duration\": \"0:02:22\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1574862227_525f5932\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-11-27 13:44:35,626|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-11-27 13:44:35,627|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-11-27 13:44:35,627|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-11-27 13:44:35,627|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-11-27 13:44:35,894|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-11-27 13:44:35,895|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-11-27 13:44:36,211|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:44:36,211|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:44:36,211|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-11-27 13:44:36,211|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-11-27 13:44:36,211|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-11-27 13:44:36,737|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-11-27 13:44:36,741|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-11-27 13:44:36,745|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-11-27 13:44:36,749|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-11-27 13:44:36,750|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:44:36,755|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,755|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-11-27 13:44:36,756|azureml.core.authentication|DEBUG|Time to expire 1814353.243919 seconds\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,756|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,785|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:36,789|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,795|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,799|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,803|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,807|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:36,808|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:44:36,808|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574862227_525f5932'\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:44:36,808|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '42fd0f32-95de-47fb-832b-4fe844f16942'\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG| 'request-id': '42fd0f32-95de-47fb-832b-4fe844f16942'\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:44:36,809|msrest.http_logger|DEBUG|None\\n2019-11-27 13:44:36,809|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:44:36,809|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:44:36,809|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:44:36,809|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:44:36 GMT'\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:44:36,856|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '42fd0f32-95de-47fb-832b-4fe844f16942'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:44:36,857|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1434,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:43:50.1125076+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 8,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:44:18.8607892+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":0,\\\\\\\"RunningNodeCount\\\\\\\":10,\\\\\\\"CurrentNodeCount\\\\\\\":10}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574862227_525f5932/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574862227_525f5932/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574862227_525f5932/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:44:36,862|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:44:36,862|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:44:36,862|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:44:36,863|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-11-27 13:44:36,863|azureml.WorkerPool|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-11-27 13:44:36,863|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574862227_525f5932/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574862227_525f5932\\n2019-11-27 13:44:36,863|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:44:36,863|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574862227_525f5932/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574862227_525f5932\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,716|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,717|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-11-27 13:44:38,721|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,722|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-11-27 13:44:38,727|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,731|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,735|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,739|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:44:38,740|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-11-27 13:44:38,740|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574862227_525f5932'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '289c750b-cea3-41df-b422-7eddd50f2daf'\\n2019-11-27 13:44:38,740|msrest.http_logger|DEBUG| 'request-id': '289c750b-cea3-41df-b422-7eddd50f2daf'\\n2019-11-27 13:44:38,741|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74'\\n2019-11-27 13:44:38,741|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:44:38,741|msrest.http_logger|DEBUG|None\\n2019-11-27 13:44:38,741|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:44:38,741|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:44:38,741|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:44:38,741|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:44:38 GMT'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '289c750b-cea3-41df-b422-7eddd50f2daf'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-11-27 13:44:38,793|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:44:38,794|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1434,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-11-27T13:43:50.1125076+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 8,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-11-27T13:44:18.8607892+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1574862227_525f5932\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"439c5c2a8a4e21299a6d46623130d25159745e6d\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":0,\\\\\\\"RunningNodeCount\\\\\\\":10,\\\\\\\"CurrentNodeCount\\\\\\\":10}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1574862227_525f5932/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574862227_525f5932/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1574862227_525f5932/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-11-27 13:44:38,795|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-11-27 13:44:38,796|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'mlflow.source.git.commit': '439c5c2a8a4e21299a6d46623130d25159745e6d', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-11-27 13:44:38,796|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-11-27 13:45:06,751|azureml.core.authentication|DEBUG|Time to expire 1814323.248956 seconds\\n2019-11-27 13:45:36,754|azureml.core.authentication|DEBUG|Time to expire 1814293.246046 seconds\\n2019-11-27 13:45:52,153|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:45:52,153|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:45:52,153|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:45:52,230|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-11-27 13:45:52,230|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-11-27 13:45:52,230|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-11-27 13:45:52,231|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574862227_525f5932/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574862227_525f5932\\n2019-11-27 13:45:52,231|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1574862227_525f5932/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1574862227_525f5932\\n2019-11-27 13:45:52,231|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-11-27 13:45:52,231|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932|INFO|complete is not setting status for submitted runs.\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-11-27 13:45:52,231|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,232|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-11-27 13:45:52,232|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,233|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,233|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,233|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-11-27 13:45:52,233|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-11-27 13:45:52,233|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-11-27 13:45:52,233|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:45:52,233|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-11-27 13:45:52,233|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-11-27 13:45:52,234|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-11-27 13:45:52,234|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-11-27 13:45:52,234|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-11-27 13:45:52,235|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-11-27 13:45:52,235|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-11-27 13:45:52,235|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-11-27 13:45:52,235|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-11-27 13:45:52,236|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-11-27 13:45:52,236|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-11-27 13:45:52,236|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1574862227_525f5932/batch/metrics'\\n2019-11-27 13:45:52,236|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:45:52,236|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-11-27 13:45:52,237|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-11-27 13:45:52,237|msrest.http_logger|DEBUG|Request headers:\\n2019-11-27 13:45:52,237|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:45:52,237|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-11-27 13:45:52,237|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-11-27 13:45:52,237|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-11-27 13:45:52,237|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-11-27 13:45:52,237|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0bb73131-a79c-4272-a377-1223fa4f9232'\\n2019-11-27 13:45:52,237|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG| 'request-id': '0bb73131-a79c-4272-a377-1223fa4f9232'\\n2019-11-27 13:45:52,238|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-11-27 13:45:52,238|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.74 sdk_run'\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG|Request body:\\n2019-11-27 13:45:52,238|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"f4ff6c5a-d4ce-4443-8009-e2ae8ccf4068\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-11-27T13:45:52.152964Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-11-27 13:45:52,238|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-11-27 13:45:52,238|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-11-27 13:45:52,238|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-11-27 13:45:52,238|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG|Response status: 200\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG|Response headers:\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Date': 'Wed, 27 Nov 2019 13:45:52 GMT'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0bb73131-a79c-4272-a377-1223fa4f9232'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-11-27 13:45:52,364|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-11-27 13:45:52,365|msrest.http_logger|DEBUG|Response content:\\n2019-11-27 13:45:52,365|msrest.http_logger|DEBUG|\\n2019-11-27 13:45:52,365|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-11-27 13:45:52,488|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002124309539794922 seconds.\\n\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml._SubmittedRun#020_AzureMLEstimator_1574862227_525f5932.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-11-27 13:45:52,489|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-11-27 13:45:52,489|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.0.74\"}, \"loading\": false}" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Counter16: submission of job 16 on 10 nodes took 8.287715435028076 seconds \n", - "run list length 16\n", - "Counter17: submission of job 17 on 10 nodes took 7.936461687088013 seconds \n", - "run list length 17\n", - "Counter18: submission of job 18 on 10 nodes took 8.747953414916992 seconds \n", - "run list length 18\n", - "Counter19: submission of job 19 on 10 nodes took 7.859047889709473 seconds \n", - "run list length 19\n" - ] - } - ], - "source": [ - "import time\n", - "from IPython.display import clear_output\n", - "\n", - "no_of_jobs = 20\n", - "no_of_nodes = 10\n", - "\n", - "job_counter = 0\n", - "print_cycle = 7\n", - "run_list = []\n", - "submit_time_list = []\n", - "for crt_nodes in range(no_of_nodes, (no_of_nodes+1)):\n", - " gpu_cluster.update(min_nodes=0, max_nodes=crt_nodes, idle_seconds_before_scaledown=1200)\n", - " clust_start_time = time.time()\n", - " for crt_job in range(1, no_of_jobs):\n", - " job_counter+= 1\n", - " start_time = time.time()\n", - " run = exp.submit(est)\n", - " end_time = time.time()\n", - " run_time = end_time - start_time\n", - " run_list.append(run)\n", - " submit_time_list.append(run_time)\n", - " print('Counter{}: submission of job {} on {} nodes took {} seconds '.format(job_counter, crt_job, crt_nodes, run_time))\n", - " print('run list length {}'.format(len(run_list)))\n", - " if ((job_counter-1) % print_cycle) == 0:\n", - " clear_output()\n", - " print('Showing details for run {}'.format(job_counter))\n", - " RunDetails(run).show()\n", - "# [all_jobs_done = True if (('Completed'==crt_queried_job.get_status()) for crt_queried_job in run_list)]" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 9.95614195, 11.63660598, 11.59176159, 8.24005198, 39.15067554,\n", - " 7.82959986, 10.54416084, 7.90288782, 9.45449305, 8.52095628,\n", - " 12.79820418, 8.11050415, 19.89081311, 7.72854948, 8.98733926,\n", - " 8.28771544, 7.93646169, 8.74795341, 7.85904789])" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "(array([0, 0, 0, 1, 5, 3, 2, 1, 1]),\n", - " array([ 6. , 6.44444444, 6.88888889, 7.33333333, 7.77777778,\n", - " 8.22222222, 8.66666667, 9.11111111, 9.55555556, 10. ]))" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import numpy as np\n", - "np.asarray(submit_time_list)\n", - "np.histogram(np.asarray(submit_time_list), bins=np.linspace(6.0, 10.0, num=10), density=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Final print 87, time 224.009 seconds: Counter({'Completed': 13, 'Failed': 6})izing': 1})'Running': 1})ueued': 1})\r" - ] - } - ], - "source": [ - "def wait_for_run_list_to_finish(the_run_list, plot_results=True):\n", - " finished_status_list = ['Completed', 'Failed']\n", - " printing_counter = 0\n", - " start_time = time.time()\n", - " while (not all((crt_queried_job.get_status() in finished_status_list) for crt_queried_job in the_run_list)):\n", - " time.sleep(2)\n", - " printing_counter+= 1\n", - " crt_status = Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list])\n", - " print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", - " str(crt_status)), end=\"\\r\")\n", - " if plot_results:\n", - "# import numpy as np\n", - " import matplotlib.pyplot as plt\n", - " plt.bar(crt_status.keys(), crt_status.values())\n", - " plt.show()\n", - " \n", - "# indexes = np.arange(len(labels))\n", - "# width = 1\n", - "\n", - "# plt.bar(indexes, values, width)\n", - "# plt.xticks(indexes + width * 0.5, labels)\n", - "# plt.show()\n", - "\n", - "# from pandas import Series\n", - "# crt_status = Series([crt_queried_job.get_status() for crt_queried_job in the_run_list])\n", - "# status_counts = crt_status.value_counts().sort_index()\n", - "# print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", - "# str(status_counts)), end=\"\\r\")\n", - "# final status\n", - " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", - " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", - "\n", - " \n", - " \n", - "wait_for_run_list_to_finish(run_list, plot_results=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "run_durations = [get_run_duration(crt_queried_job) for crt_queried_job in run_list]\n", - "run_statuses = [crt_queried_job.get_status() for crt_queried_job in run_list]" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0, 9, 4, 13, 10, 5, 14, 18, 7, 8, 16, 11, 6, 1, 12, 15, 3,\n", - " 2, 17])" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ 4.819322 101.946523 106.5569 107.866548 109.776871 111.461206\n", - " 113.732314 117.302445 122.746341 135.938763 156.116081 157.700891\n", - " 158.001392 162.059693 163.769164 164.909099 166.151158 175.955566\n", - " 185.333479]\n", - "['Failed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Failed' 'Failed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Failed' 'Failed'\n", - " 'Failed']\n" - ] - }, - { - "data": { - "text/plain": [ - "array([ 0, 9, 4, 13, 10, 5, 14, 18, 7, 8, 16, 11, 6, 1, 12, 15, 3,\n", - " 2, 17])" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ 4.819322 101.946523 106.5569 107.866548 109.776871 111.461206\n", - " 113.732314 117.302445 122.746341 135.938763 156.116081 157.700891\n", - " 158.001392 162.059693 163.769164 164.909099 166.151158 175.955566\n", - " 185.333479]\n", - "['Failed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Failed' 'Failed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Failed' 'Failed'\n", - " 'Failed']\n" - ] - }, - { - "data": { - "text/plain": [ - "(array([0, 0, 0, 6, 2, 1, 7, 1, 1]),\n", - " array([ 50. , 66.66666667, 83.33333333, 100. ,\n", - " 116.66666667, 133.33333333, 150. , 166.66666667,\n", - " 183.33333333, 200. ]))" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run_durations = np.asarray(run_durations)\n", - "run_statuses = np.asarray(run_statuses)\n", - "\n", - "extreme_k = 20\n", - "#longest runs\n", - "indices = np.argsort(run_durations)[-extreme_k:]\n", - "indices\n", - "print(run_durations[indices])\n", - "print(run_statuses[indices])\n", - "#shortest runs\n", - "indices = np.argsort(run_durations)[0:extreme_k]\n", - "indices\n", - "print(run_durations[indices])\n", - "print(run_statuses[indices])\n", - "\n", - "#run_durations histogram - counts and bins\n", - "np.histogram(run_durations, bins=np.linspace(50, 200, num=10), density=False)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finished running 030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito!\n" - ] - } - ], - "source": [ - "print('Finished running 030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito!')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/contrib/examples/imaging/azureml_devito/notebooks/README.md b/contrib/examples/imaging/azureml_devito/notebooks/README.md deleted file mode 100644 index 2de32570..00000000 --- a/contrib/examples/imaging/azureml_devito/notebooks/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# DeepSeismic - -## Imaging - -This tutorial shows how to run devito tutorial notebooks in Azure. For best experience use a linux Azure DSVM and Jupyter notebook to run the notebooks. - -### Setting up Environment - -This conda .yml file should be used to create the conda environment, before starting Jupyter: - -``` -channels: - - anaconda -dependencies: - - python=3.6 - - numpy - - cython - - notebook - - nb_conda - - scikit-learn - - pip - - pip: - - python-dotenv - - papermill[azure] - - azureml-sdk[notebooks,automl,explain] -``` - -You may also need az cli to create an ACR in notebook 000_Setup_GeophysicsTutorial_FWI_Azure_devito, and then push and pull images. One can also create the ACR via Azure [portal](https://azure.microsoft.com/). \ No newline at end of file diff --git a/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log b/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log deleted file mode 100644 index 01c1af62..00000000 --- a/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log +++ /dev/null @@ -1,116 +0,0 @@ -============================= test session starts ============================== -platform linux -- Python 3.6.9, pytest-5.3.0, py-1.8.0, pluggy-0.13.1 -rootdir: /devito, inifile: setup.cfg -plugins: nbval-0.9.3, cov-2.8.1 -collected 1053 items / 2 skipped / 1051 selected - -tests/test_adjoint.py .......................... [ 2%] -tests/test_autotuner.py ..........s..... [ 3%] -tests/test_builtins.py ....s...............s..s [ 6%] -tests/test_caching.py .................................................. [ 11%] - [ 11%] -tests/test_checkpointing.py ....... [ 11%] -tests/test_constant.py . [ 11%] -tests/test_data.py ..........................ssssssssssssssssss.ss.. [ 16%] -tests/test_dependency_bugs.py . [ 16%] -tests/test_derivatives.py .............................................. [ 20%] -........................................................................ [ 27%] -........................................................................ [ 34%] -...... [ 35%] -tests/test_differentiable.py .. [ 35%] -tests/test_dimension.py ............................... [ 38%] -tests/test_dle.py ...................................................... [ 43%] -........................................... [ 47%] -tests/test_docstrings.py ................ [ 49%] -tests/test_dse.py ......x............................................... [ 54%] -................x..........s.... [ 57%] -tests/test_gradient.py .... [ 57%] -tests/test_interpolation.py ........................ [ 59%] -tests/test_ir.py ....................................................... [ 65%] -................ [ 66%] -tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 71%] -ss [ 71%] -tests/test_operator.py ................................................. [ 76%] -........................................................................ [ 83%] -................................ [ 86%] -tests/test_pickle.py .................ss. [ 88%] -tests/test_resample.py . [ 88%] -tests/test_save.py .. [ 88%] -tests/test_staggered_utils.py ......... [ 89%] -tests/test_subdomains.py ... [ 89%] -tests/test_symbolic_coefficients.py .....F [ 90%] -tests/test_tensors.py .................................................. [ 95%] -........................... [ 97%] -tests/test_timestepping.py ....... [ 98%] -tests/test_tools.py ..... [ 98%] -tests/test_tti.py .... [ 99%] -tests/test_visitors.py ......... [100%] - -=================================== FAILURES =================================== -______________________ TestSC.test_function_coefficients _______________________ - -self = - - def test_function_coefficients(self): - """Test that custom function coefficients return the expected result""" - so = 2 - grid = Grid(shape=(4, 4)) - f0 = TimeFunction(name='f0', grid=grid, space_order=so, coefficients='symbolic') - f1 = TimeFunction(name='f1', grid=grid, space_order=so) - x, y = grid.dimensions - - s = Dimension(name='s') - ncoeffs = so+1 - - wshape = list(grid.shape) - wshape.append(ncoeffs) - wshape = as_tuple(wshape) - - wdims = list(grid.dimensions) - wdims.append(s) - wdims = as_tuple(wdims) - - w = Function(name='w', dimensions=wdims, shape=wshape) - w.data[:, :, 0] = 0.0 - w.data[:, :, 1] = -1.0/grid.spacing[0] - w.data[:, :, 2] = 1.0/grid.spacing[0] - - f_x_coeffs = Coefficient(1, f0, x, w) - - subs = Substitutions(f_x_coeffs) - - eq0 = Eq(f0.dt + f0.dx, 1, coefficients=subs) - eq1 = Eq(f1.dt + f1.dx, 1) - - stencil0 = solve(eq0.evaluate, f0.forward) - stencil1 = solve(eq1.evaluate, f1.forward) - - op0 = Operator(Eq(f0.forward, stencil0)) - op1 = Operator(Eq(f1.forward, stencil1)) - - op0(time_m=0, time_M=5, dt=1.0) - op1(time_m=0, time_M=5, dt=1.0) - -> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0)) -E assert Data(False) -E + where Data(False) = (Data([[[False, False, False, False],\n [False, False, False, False],\n [ True, True, True, True],\n ...alse],\n [False, False, False, False],\n [False, False, False, False],\n [ True, True, True, True]]])) -E + where = np.all -E + and Data([[[False, False, False, False],\n [False, False, False, False],\n [ True, True, True, True],\n ...alse],\n [False, False, False, False],\n [False, False, False, False],\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\n [ 3327., 3327., 3327., 3327.],\n [-3414., -3414., -3414., -341...3., 383., 383.],\n [ -598., -598., -598., -598.],\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\n [ 341. , 341. , 341. , 341. ]]],\n dtype=float32)), 0.0, atol=1e-05, rtol=0) -E + where = np.isclose - -tests/test_symbolic_coefficients.py:96: AssertionError ------------------------------ Captured stderr call ----------------------------- -/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c: In function ‘Kernel’: -/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas] - #pragma omp simd aligned(f0,w:32) - -Operator `Kernel` run in 0.01 s -/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c: In function ‘Kernel’: -/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas] - #pragma omp simd aligned(f1:32) - -Operator `Kernel` run in 0.01 s ------------------------------- Captured log call ------------------------------- -INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s -INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s -====== 1 failed, 967 passed, 85 skipped, 2 xfailed in 1058.91s (0:17:38) ======= diff --git a/contrib/fwi/azureml_devito/README.md b/contrib/fwi/azureml_devito/README.md new file mode 100755 index 00000000..1be375db --- /dev/null +++ b/contrib/fwi/azureml_devito/README.md @@ -0,0 +1,48 @@ +# DeepSeismic + +## Imaging + +This tutorial shows how to run [devito](https://www.devitoproject.org/) tutorial [notebooks](https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) in Azure Machine Learning ([Azure ML](https://docs.microsoft.com/en-us/azure/machine-learning/)) using [Azure Machine Learning Python SDK](https://docs.microsoft.com/en-us/azure/machine-learning/service/tutorial-1st-experiment-sdk-setup). + +For best experience use a Linux (Ubuntu) Azure [DSVM](https://docs.microsoft.com/en-us/azure/machine-learning/data-science-virtual-machine/dsvm-ubuntu-intro) and Jupyter Notebook with AzureML Python SDK and [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) to run the notebooks (see __Setting up Environment__ section below). + +Devito is a domain-specific Language (DSL) and code generation framework for the design of highly optimized finite difference kernels via symbolic computation for use in inversion methods. Here we show how ```devito``` can be openly used in the cloud by leveraging AzureML experimentation framework as a transparent and scalable platform for generic computation workloads. We focus on Full waveform inversion (__FWI__) problems where non-linear data-fitting procedures are applied for computing estimates of subsurface properties from seismic data. + + +### Setting up Environment + +The [conda environment](https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/environments.html) that encapsulates all the dependencies needed to run the notebooks described above can be created using the fwi_dev_conda_environment.yml file. See [here](https://github.com/Azure/MachineLearningNotebooks/blob/master/NBSETUP.md) generic instructions on how to install and run AzureML Python SDK in Jupyter Notebooks. + +To create the conda environment, run: +``` +conda env create -f fwi_dev_conda_environment.yml + +``` + +then, one can see the created environment within the list of available environments and activate it: +``` +conda env list +conda activate fwi_dev_conda_environment +``` + +Finally, start Jupyter notebook from within the activated environment: +``` +jupyter notebook +``` + +[Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) is also used to create an ACR in notebook 000_Setup_GeophysicsTutorial_FWI_Azure_devito, and then push and pull docker images. One can also create the ACR via Azure [portal](https://azure.microsoft.com/). + +### Run devito in Azure +The devito fwi examples are run in AzuremL using 4 notebooks: + - ```000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb```: sets up Azure resources (like resource groups, AzureML [workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-workspace)). + - ```010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb```: Creates a custom docker file and the associated image that contains ```devito``` [github repository](https://github.com/opesci/devito.git) (including devito fwi tutorial [notebooks](https://github.com/opesci/devito/tree/master/examples/seismic/tutorials)) and runs the official devito install [tests](https://github.com/opesci/devito/tree/master/tests). + - ```020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb```: shows how the devito fwi tutorial [notebooks](https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) can be run in AzureML using Azure Machine Learning [generic](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator?view=azure-ml-py) [estimators](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-train-ml-models) with custom docker images. FWI computation takes place on a managed AzureML [remote compute cluster](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets). + + ```Devito``` fwi computation artifacts (images and notebooks with data processing output results) are tracked under the AzureML workspace, and can be later downloaded and visualized. + + Two ways of running devito code are shown: + (1) using __custom code__ (slightly modified graphing functions that save images to files). The AzureML experimentation job is defined by the devito code packaged as a py file. The experimentation job (defined by [azureml.core.experiment.Experiment](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.experiment.experiment?view=azure-ml-py) class can be used to track metrics or other artifacts (images) that are available in Azure portal. + (2) using [__papermill__](https://github.com/nteract/papermill) invoked via its Python API to run unedited devito demo notebooks (including the [dask](https://dask.org/) local cluster [example](https://github.com/opesci/devito/blob/master/examples/seismic/tutorials/04_dask.ipynb) on the remote compute target and the results as saved notebooks that are available in Azure portal. + - ```030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb```: shows how the devito fwi tutorial notebooks can be run in parallel on the elastically allocated AzureML [remote compute cluster](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets) created before. By submitting multiple jobs via azureml.core.Experiment submit(azureml.train.estimator.Estimator) one can use the [portal](https://portal.azure.com) to visualize the elastic allocation of AzureML [remote compute cluster](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets) nodes. + + diff --git a/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml new file mode 100755 index 00000000..e79343d6 --- /dev/null +++ b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml @@ -0,0 +1,16 @@ + +name: fwi_dev_conda_environment + +channels: + - anaconda +dependencies: + - python=3.7 + - numpy + - notebook + - nb_conda + - scikit-learn + - pip + - pip: + - python-dotenv + - papermill[azure] + - azureml-sdk[notebooks,automl,explain]==1.0.76 diff --git a/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb similarity index 84% rename from contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb rename to contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index 42d4ca96..3040bd87 100755 --- a/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -76,13 +76,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.74\n" + "Azure ML SDK Version: 1.0.76\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1063-azure-x86_64-with-debian-10.1'" + "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" ] }, "execution_count": 3, @@ -92,7 +92,7 @@ { "data": { "text/plain": [ - "'/workspace/contrib/examples/imaging/azureml_devito/notebooks'" + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -150,7 +150,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /workspace/contrib/examples/imaging/azureml_devito/notebooks/./src/project_utils.py\n" + "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./src/project_utils.py\n" ] } ], @@ -507,6 +507,15 @@ "execution_count": 14, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING - Warning: Falling back to use azure cli login credentials.\n", + "If you run your code in unattended mode, i.e., where you can't give a user input, then we recommend to use ServicePrincipalAuthentication or MsiAuthentication.\n", + "Please refer to aka.ms/aml-notebook-auth for different authentication mechanisms in azureml-sdk.\n" + ] + }, { "name": "stdout", "output_type": "stream", @@ -659,23 +668,79 @@ "name": "stdout", "output_type": "stream", "text": [ - "azure-cli 2.0.76 *\r\n", + "azure-cli 2.0.58 *\r\n", "\r\n", - "command-modules-nspkg 2.0.3\r\n", - "core 2.0.76 *\r\n", - "nspkg 3.0.4\r\n", - "telemetry 1.0.4\r\n", + "acr 2.2.0 *\r\n", + "acs 2.3.17 *\r\n", + "advisor 2.0.0 *\r\n", + "ams 0.4.1 *\r\n", + "appservice 0.2.13 *\r\n", + "backup 1.2.1 *\r\n", + "batch 3.4.1 *\r\n", + "batchai 0.4.7 *\r\n", + "billing 0.2.0 *\r\n", + "botservice 0.1.6 *\r\n", + "cdn 0.2.0 *\r\n", + "cloud 2.1.0 *\r\n", + "cognitiveservices 0.2.4 *\r\n", + "command-modules-nspkg 2.0.2 *\r\n", + "configure 2.0.20 *\r\n", + "consumption 0.4.2 *\r\n", + "container 0.3.13 *\r\n", + "core 2.0.58 *\r\n", + "cosmosdb 0.2.7 *\r\n", + "dla 0.2.4 *\r\n", + "dls 0.1.8 *\r\n", + "dms 0.1.2 *\r\n", + "eventgrid 0.2.1 *\r\n", + "eventhubs 0.3.3 *\r\n", + "extension 0.2.3 *\r\n", + "feedback 2.1.4 *\r\n", + "find 0.2.13 *\r\n", + "hdinsight 0.3.0 *\r\n", + "interactive 0.4.1 *\r\n", + "iot 0.3.6 *\r\n", + "iotcentral 0.1.6 *\r\n", + "keyvault 2.2.11 *\r\n", + "kusto 0.1.0 *\r\n", + "lab 0.1.5 *\r\n", + "maps 0.3.3 *\r\n", + "monitor 0.2.10 *\r\n", + "network 2.3.2 *\r\n", + "nspkg 3.0.3 *\r\n", + "policyinsights 0.1.1 *\r\n", + "profile 2.1.3 *\r\n", + "rdbms 0.3.7 *\r\n", + "redis 0.4.0 *\r\n", + "relay 0.1.3 *\r\n", + "reservations 0.4.1 *\r\n", + "resource 2.1.10 *\r\n", + "role 2.4.0 *\r\n", + "search 0.1.1 *\r\n", + "security 0.1.0 *\r\n", + "servicebus 0.3.3 *\r\n", + "servicefabric 0.1.12 *\r\n", + "signalr 1.0.0 *\r\n", + "sql 2.1.9 *\r\n", + "sqlvm 0.1.0 *\r\n", + "storage 2.3.1 *\r\n", + "telemetry 1.0.1 *\r\n", + "vm 2.2.15 *\r\n", + "\r\n", + "Extensions:\r\n", + "azure-ml-admin-cli 0.0.1\r\n", + "azure-cli-ml Unknown\r\n", "\r\n", "Python location '/opt/az/bin/python3'\r\n", - "Extensions directory '/root/.azure/cliextensions'\r\n", + "Extensions directory '/opt/az/extensions'\r\n", "\r\n", - "Python (Linux) 3.6.5 (default, Oct 30 2019, 06:31:53) \r\n", - "[GCC 6.3.0 20170516]\r\n", + "Python (Linux) 3.6.5 (default, Feb 12 2019, 02:10:43) \r\n", + "[GCC 5.4.0 20160609]\r\n", "\r\n", "Legal docs and information: aka.ms/AzureCliLegal\r\n", "\r\n", "\r\n", - "\u001b[33mYou have 2 updates available. Consider updating your CLI installation.\u001b[0m\r\n" + "\u001b[33mYou have 57 updates available. Consider updating your CLI installation.\u001b[0m\r\n" ] } ], @@ -715,6 +780,29 @@ "execution_count": 19, "metadata": {}, "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "[' \"loginServer\": \"fwi01acr.azurecr.io\",',\n", + " ' \"name\": \"fwi01acr\",',\n", + " ' \"networkRuleSet\": null,',\n", + " ' \"provisioningState\": \"Succeeded\",',\n", + " ' \"resourceGroup\": \"ghiordanfwirsg01\",',\n", + " ' \"sku\": {',\n", + " ' \"name\": \"Basic\",',\n", + " ' \"tier\": \"Basic\"',\n", + " ' },',\n", + " ' \"status\": null,',\n", + " ' \"storageAccount\": null,',\n", + " ' \"tags\": {},',\n", + " ' \"type\": \"Microsoft.ContainerRegistry/registries\"',\n", + " '}']" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -724,8 +812,8 @@ "cli_command='az acr create --resource-group '+ crt_resource_group +' --name ' + acr_name + ' --sku Basic'\n", "cli_command\n", "\n", - "if create_ACR_FLAG:\n", - " !$cli_command" + "response = !$cli_command\n", + "response[-14:]" ] }, { @@ -756,9 +844,8 @@ "cli_command='az acr update -n '+acr_name+' --admin-enabled true'\n", "cli_command\n", "\n", - "if create_ACR_FLAG:\n", - " response = !$cli_command\n", - " response" + "response = !$cli_command\n", + "# response" ] }, { diff --git a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb new file mode 100755 index 00000000..78276433 --- /dev/null +++ b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -0,0 +1,1064 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License.\n", + "\n", + "# FWI in Azure project\n", + "\n", + "## Create Experimentation Docker image\n", + "\n", + "FWI demo based on: \n", + "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", + "\n", + "\n", + "\n", + "In this notebook we create a custom docker image that will be used to run the devito demo notebooks in AzureML. \n", + "\n", + " - We transparently create a docker file, a conda environment .yml file, build the docker image and push it into dockerhub. Azure ACR could also be used for storing docker images. \n", + " - The conda environment .yml file lists conda and pip installs, and separates all python dependencies from the docker installs. \n", + " - The dockerfile is generic. The only AzureML depedency is azureml-sdk pip installable package in conda environment .yml file\n", + " - The created docer image will be run in following notebook in a container on the local AzureVM or on a remote AzureML compute cluster. This AzureML pattern decouples experimentation (or training) job definition (experimentation script, data location, dependencies and docker image) happening on the control plane machine that runs this notebook, from the elastically allocated and Azure managed VM/cluster that does the actual training/experimentation computation.\n", + " \n", + "\n", + "User input requiring steps:\n", + " - [Fill in and save docker image name settings, if needed. ](#docker_image_settings)\n", + " - [Update DOCKER_CONTAINER_MOUNT_POINT to match our local path](#docker_image_settings)\n", + " - [Set docker build and test flags](#docker_build_test_settings) \n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Allow multiple displays per cell\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\" " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "import shutil\n", + "import urllib\n", + "\n", + "import platform\n", + "import math\n", + "import docker" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "platform.platform()\n", + "os.getcwd()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "#### Setup docker image build and test process. \n", + " - devito tests take abou 15 mins (981.41 seconds). When running this notebook for first time make:\n", + " > docker_build_no_cache = '--no-cache' \n", + " > docker_test_run_devito_tests = True\n", + " \n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "docker_build_no_cache = '' # '--no-cache' # or '' #\n", + "docker_test_run_devito_tests = True # True # False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Import utilities functions" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_path_to_sys_path(path_to_append):\n", + " if not (any(path_to_append in paths for paths in sys.path)):\n", + " sys.path.append(path_to_append)\n", + " \n", + "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", + "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", + "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", + "\n", + "import project_utils\n", + "prj_consts = project_utils.project_consts()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Create experimentation docker file" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../not_shared/general.env'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", + "dotenv_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks\r\n" + ] + } + ], + "source": [ + "!pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# azureml_sdk_version set here must match azureml sdk version pinned in conda env file written to conda_common_file_path below\n", + "azureml_sdk_version = '1.0.76' " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "##### Input here docker image settings \n", + "in cell below we use [dotenv](https://github.com/theskumar/python-dotenv) to overwrite docker image properties already save in dotenv_file_path. Change as needed, e.g. update azureml_sdk version if using a different version.\n", + "\n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(True, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', 'sdk.v1.0.76')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "(True,\n", + " 'DOCKER_CONTAINER_MOUNT_POINT',\n", + " '/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# SDK changes often, so we'll keep its version transparent \n", + "import dotenv\n", + "\n", + "# EXPERIMENTATION_IMAGE_VERSION should:\n", + "# - match sdk version in fwi01_conda_env01 environmnet in conda_env_fwi01_azureml_sdk.v1.0.XX.yml file below\n", + "# - match the conda env yml file name, e.g. conda_env_fwi01_azureml_sdk.v1.0.xx.yml referenced in \n", + "# Dockerfile_fwi01_azureml_sdk.v1.0.xx\n", + "# dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')\n", + "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', ('sdk.v'+azureml_sdk_version))\n", + "\n", + "\n", + "docker_container_mount_point = os.getcwd()\n", + "# or something like \"/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'\n", + "dotenv.set_key(dotenv_file_path, 'DOCKER_CONTAINER_MOUNT_POINT', docker_container_mount_point)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'conda_env_fwi01_azureml_sdk.v1.0.76.yml'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.76.yml'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%reload_ext dotenv\n", + "%dotenv $dotenv_file_path\n", + "\n", + "docker_file_location = os.path.join(*(prj_consts.AML_EXPERIMENT_DIR + ['docker_build']))\n", + "\n", + "docker_file_name = 'Dockerfile'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "conda_dependency_file_name = 'conda_env'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "conda_dependency_common_file_name = conda_dependency_file_name\n", + "\n", + "devito_conda_dependency_file_name = 'devito_conda_env'+'.yml'\n", + "\n", + "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", + "docker_image_name = docker_repo_name + '/' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", + "if image_version!=\"\":\n", + " docker_file_name = docker_file_name +'_'+ image_version\n", + " conda_dependency_file_name = conda_dependency_file_name+'_'+ image_version\n", + " docker_image_name = docker_image_name +':'+ image_version\n", + "conda_dependency_file_name=conda_dependency_file_name+'.yml'\n", + "conda_dependency_common_file_name = conda_dependency_common_file_name+'.yml'\n", + "\n", + "docker_file_dir = os.path.join(*([os.getcwd(), docker_file_location]))\n", + "os.makedirs(docker_file_dir, exist_ok=True)\n", + "docker_file_path = os.path.join(*([docker_file_dir]+[docker_file_name]))\n", + "conda_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_file_name]))\n", + "conda_common_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_common_file_name]))\n", + "\n", + "docker_image_name\n", + "\n", + "conda_dependency_file_name\n", + "conda_file_path\n", + "conda_common_file_path\n", + "\n", + "docker_file_dir\n", + "docker_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" + ] + } + ], + "source": [ + "%%writefile $conda_common_file_path\n", + "name: fwi01_conda_env01\n", + " \n", + "#https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.13.1-cp37-cp37m-linux_x86_64.whl \n", + "# https://github.com/dask/dask-tutorial\n", + "\n", + "channels:\n", + " - anaconda\n", + " - conda-forge\n", + "dependencies:\n", + " - python=3.6 # 3.6 req by tf, not 3.7.2 \n", + " - dask\n", + " - distributed\n", + " - h5py\n", + " - matplotlib\n", + " - nb_conda\n", + " - notebook \n", + " - numpy \n", + " - pandas\n", + " - pip\n", + " - py-cpuinfo # all required by devito or dask-tutorial\n", + " - pytables\n", + " - python-graphviz\n", + " - requests\n", + " - pillow\n", + " - scipy\n", + " - snakeviz\n", + " - scikit-image\n", + " - toolz\n", + " - pip:\n", + " - anytree # required by devito\n", + " - azureml-sdk[notebooks,automl]==1.0.76\n", + " - codepy # required by devito\n", + " - papermill[azure]\n", + " - pyrevolve # required by devito" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76\n" + ] + } + ], + "source": [ + "%%writefile $docker_file_path \n", + "\n", + "FROM continuumio/miniconda3:4.7.10 \n", + "MAINTAINER George Iordanescu \n", + "\n", + "RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \\\n", + " gcc g++ \\\n", + " wget bzip2 \\\n", + " curl \\\n", + " git make \\\n", + " mpich \\ \n", + " libmpich-dev && \\\n", + " apt-get clean && \\\n", + " rm -rf /var/lib/apt/lists/*\n", + "\n", + "ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml.yml\n", + "ADD $CONDA_ENV_FILE_NAME /tmp/$CONDA_ENV_FILE_NAME\n", + "ENV CONDA_DIR /opt/conda\n", + "ENV CONDA_ENV_NAME fwi01_conda_env\n", + "\n", + "RUN git clone https://github.com/opesci/devito.git && \\\n", + " cd devito && \\\n", + " /opt/conda/bin/conda env create -q --name $CONDA_ENV_NAME -f environment.yml && \\\n", + " pip install -e . \n", + " \n", + "ENV CONDA_AUTO_UPDATE_CONDA=false\n", + "ENV CONDA_DEFAULT_ENV=$CONDA_ENV_NAME\n", + "ENV CONDA_PREFIX=$CONDA_DIR/envs/$CONDA_DEFAULT_ENV\n", + "ENV PATH=$CONDA_PREFIX/bin:/opt/conda/bin:$PATH \n", + "\n", + "RUN /opt/conda/bin/conda env update --name $CONDA_ENV_NAME -f /tmp/$CONDA_ENV_FILE_NAME && \\\n", + " /opt/conda/bin/conda clean --yes --all\n", + "\n", + "ENV PYTHONPATH=$PYTHONPATH:devito/app\n", + "\n", + "# WORKDIR /devito \n", + " \n", + "CMD /bin/bash" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.76.yml'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 28\r\n", + "-rwxrwxrwx 1 root root 733 Dec 4 02:31 conda_env_fwi01_azureml_sdk.v1.0.69.yml\r\n", + "-rwxrwxrwx 1 root root 725 Dec 4 02:31 conda_env_fwi01_azureml_sdk.v1.0.74.yml\r\n", + "-rwxrwxrwx 1 root root 725 Dec 4 16:59 conda_env_fwi01_azureml_sdk.v1.0.76.yml\r\n", + "-rwxrwxrwx 1 root root 725 Dec 4 16:59 conda_env_fwi01_azureml.yml\r\n", + "-rwxrwxrwx 1 root root 1073 Dec 4 02:31 Dockerfile_fwi01_azureml_sdk.v1.0.69\r\n", + "-rwxrwxrwx 1 root root 1073 Dec 4 02:31 Dockerfile_fwi01_azureml_sdk.v1.0.74\r\n", + "-rwxrwxrwx 1 root root 1073 Dec 4 16:59 Dockerfile_fwi01_azureml_sdk.v1.0.76\r\n" + ] + } + ], + "source": [ + "shutil.copyfile(conda_common_file_path, conda_file_path)\n", + "\n", + "! ls -l $docker_file_dir" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 -f /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76 /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build '" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "['Sending build context to Docker daemon 13.31kB',\n", + " '',\n", + " 'Step 1/15 : FROM continuumio/miniconda3:4.7.10',\n", + " '4.7.10: Pulling from continuumio/miniconda3',\n", + " '1ab2bdfe9778: Pulling fs layer']" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "[' ---> Running in 64cc95908200',\n", + " 'Removing intermediate container 64cc95908200',\n", + " ' ---> 619ab5d20944',\n", + " 'Successfully built 619ab5d20944',\n", + " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76']" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cli_command='docker build -t '+ docker_image_name + \\\n", + "' -f ' + docker_file_path + \\\n", + "' ' + docker_file_dir + ' ' +\\\n", + "docker_build_no_cache #'' #' --no-cache'\n", + "\n", + "\n", + "cli_command\n", + "docker_build_response = ! $cli_command\n", + "\n", + "docker_build_response[0:5] \n", + "docker_build_response[-5:] " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Docker containers can be run using python docker sdk" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "b'/\\n1.0.76\\n'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docker_image_name\n", + "\n", + "sh_command='bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'\n", + "sh_command\n", + "client = docker.from_env()\n", + "client.containers.run(docker_image_name, \n", + " remove=True,\n", + " volumes={os.getenv('DOCKER_CONTAINER_MOUNT_POINT'): {'bind': '/workspace', 'mode': 'rw'}},\n", + " working_dir='/',\n", + " command=sh_command)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Docker containers can also be run in cli \n", + "\n", + "Here we also create a log file to capture commands execution in container. If flag docker_test_run_devito_tests is True, we run \n", + "and capture test commands output. Tests take abou 15 minutes to run. If flag docker_test_run_devito_tests is False, we show the results of a previous session. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./fwi01_azureml_buildexperimentationdockerimage.log'" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fwi01_log_file = os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log']))\n", + "fwi01_log_file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create command for running devito tests, capture output in a log file, save log file outside container" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "content of devito tests log file before testing:\n", + "Before running e13n container... \r\n" + ] + }, + { + "data": { + "text/plain": [ + "' python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "if docker_test_run_devito_tests:\n", + " run_devito_tests_command = ' python -m pytest tests/ ' + \\\n", + "'> ' + fwi01_log_file +' 2>&1; ' + \\\n", + "' mv ' + fwi01_log_file + ' /workspace/' \n", + " \n", + " with open(os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log'])), \"w\") as crt_log_file:\n", + " print('Before running e13n container... ', file=crt_log_file)\n", + " print('\\ncontent of devito tests log file before testing:')\n", + " !cat $fwi01_log_file\n", + "else:\n", + " run_devito_tests_command = '' \n", + "\n", + "# run_devito_tests_command = 'ls -l > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'\n", + "run_devito_tests_command" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/ \"'" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# conda environments:\n", + "#\n", + "base /opt/conda\n", + "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", + "\n", + "total 560\n", + "-rw-r--r-- 1 root root 11521 Dec 4 17:00 conftest.py\n", + "-rw-r--r-- 1 root root 6006 Dec 4 17:00 test_adjoint.py\n", + "-rw-r--r-- 1 root root 14586 Dec 4 17:00 test_autotuner.py\n", + "-rw-r--r-- 1 root root 7538 Dec 4 17:00 test_builtins.py\n", + "-rw-r--r-- 1 root root 24415 Dec 4 17:00 test_caching.py\n", + "-rw-r--r-- 1 root root 9721 Dec 4 17:00 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Dec 4 17:00 test_constant.py\n", + "-rw-r--r-- 1 root root 55954 Dec 4 17:00 test_data.py\n", + "-rw-r--r-- 1 root root 481 Dec 4 17:00 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16331 Dec 4 17:00 test_derivatives.py\n", + "-rw-r--r-- 1 root root 1473 Dec 4 17:00 test_differentiable.py\n", + "-rw-r--r-- 1 root root 30846 Dec 4 17:00 test_dimension.py\n", + "-rw-r--r-- 1 root root 24838 Dec 4 17:00 test_dle.py\n", + "-rw-r--r-- 1 root root 1169 Dec 4 17:00 test_docstrings.py\n", + "-rw-r--r-- 1 root root 32134 Dec 4 17:00 test_dse.py\n", + "-rw-r--r-- 1 root root 8205 Dec 4 17:00 test_gradient.py\n", + "-rw-r--r-- 1 root root 15227 Dec 4 17:00 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31816 Dec 4 17:00 test_ir.py\n", + "-rw-r--r-- 1 root root 63169 Dec 4 17:00 test_mpi.py\n", + "-rw-r--r-- 1 root root 67053 Dec 4 17:00 test_operator.py\n", + "-rw-r--r-- 1 root root 14875 Dec 4 17:00 test_ops.py\n", + "-rw-r--r-- 1 root root 12228 Dec 4 17:00 test_pickle.py\n", + "-rw-r--r-- 1 root root 1809 Dec 4 17:00 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Dec 4 17:00 test_save.py\n", + "-rw-r--r-- 1 root root 2115 Dec 4 17:00 test_staggered_utils.py\n", + "-rw-r--r-- 1 root root 5711 Dec 4 17:00 test_subdomains.py\n", + "-rw-r--r-- 1 root root 3320 Dec 4 17:00 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 7277 Dec 4 17:00 test_tensors.py\n", + "-rw-r--r-- 1 root root 3186 Dec 4 17:00 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Dec 4 17:00 test_tools.py\n", + "-rw-r--r-- 1 root root 3296 Dec 4 17:00 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Dec 4 17:00 test_visitors.py\n", + "-rw-r--r-- 1 root root 21802 Dec 4 17:00 test_yask.py\n", + "1.0.76\n", + "\n", + "content of devito tests log file after testing:\n", + "============================= test session starts ==============================\n", + "platform linux -- Python 3.6.9, pytest-5.3.1, py-1.8.0, pluggy-0.13.1\n", + "rootdir: /devito, inifile: setup.cfg\n", + "plugins: nbval-0.9.3, cov-2.8.1\n", + "collected 1056 items / 2 skipped / 1054 selected\n", + "\n", + "tests/test_adjoint.py .......................... [ 2%]\n", + "tests/test_autotuner.py ..........s..... [ 3%]\n", + "tests/test_builtins.py ....s...............s..s [ 6%]\n", + "tests/test_caching.py .................................................. [ 10%]\n", + " [ 10%]\n", + "tests/test_checkpointing.py ....... [ 11%]\n", + "tests/test_constant.py . [ 11%]\n", + "tests/test_data.py ..........................ssssssssssssssssss.ss.. [ 16%]\n", + "tests/test_dependency_bugs.py . [ 16%]\n", + "tests/test_derivatives.py .............................................. [ 20%]\n", + "........................................................................ [ 27%]\n", + "........................................................................ [ 34%]\n", + "...... [ 35%]\n", + "tests/test_differentiable.py .. [ 35%]\n", + "tests/test_dimension.py ............................... [ 38%]\n", + "tests/test_dle.py ...................................................... [ 43%]\n", + "........................................... [ 47%]\n", + "tests/test_docstrings.py ................ [ 48%]\n", + "tests/test_dse.py ......x............................................... [ 53%]\n", + "................x..........s.... [ 57%]\n", + "tests/test_gradient.py .... [ 57%]\n", + "tests/test_interpolation.py ........................ [ 59%]\n", + "tests/test_ir.py ....................................................... [ 64%]\n", + "................ [ 66%]\n", + "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 71%]\n", + "sss [ 71%]\n", + "tests/test_operator.py ................................................. [ 76%]\n", + "..............................................s......................... [ 83%]\n", + ".................................. [ 86%]\n", + "tests/test_pickle.py .................ss. [ 88%]\n", + "tests/test_resample.py . [ 88%]\n", + "tests/test_save.py .. [ 88%]\n", + "tests/test_staggered_utils.py ......... [ 89%]\n", + "tests/test_subdomains.py ... [ 89%]\n", + "tests/test_symbolic_coefficients.py .....F [ 90%]\n", + "tests/test_tensors.py .................................................. [ 95%]\n", + "........................... [ 97%]\n", + "tests/test_timestepping.py ....... [ 98%]\n", + "tests/test_tools.py ..... [ 98%]\n", + "tests/test_tti.py .... [ 99%]\n", + "tests/test_visitors.py ......... [100%]\n", + "\n", + "=================================== FAILURES ===================================\n", + "______________________ TestSC.test_function_coefficients _______________________\n", + "\n", + "self = \n", + "\n", + " def test_function_coefficients(self):\n", + " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", + " so = 2\n", + " grid = Grid(shape=(4, 4))\n", + " f0 = TimeFunction(name='f0', grid=grid, space_order=so, coefficients='symbolic')\n", + " f1 = TimeFunction(name='f1', grid=grid, space_order=so)\n", + " x, y = grid.dimensions\n", + " \n", + " s = Dimension(name='s')\n", + " ncoeffs = so+1\n", + " \n", + " wshape = list(grid.shape)\n", + " wshape.append(ncoeffs)\n", + " wshape = as_tuple(wshape)\n", + " \n", + " wdims = list(grid.dimensions)\n", + " wdims.append(s)\n", + " wdims = as_tuple(wdims)\n", + " \n", + " w = Function(name='w', dimensions=wdims, shape=wshape)\n", + " w.data[:, :, 0] = 0.0\n", + " w.data[:, :, 1] = -1.0/grid.spacing[0]\n", + " w.data[:, :, 2] = 1.0/grid.spacing[0]\n", + " \n", + " f_x_coeffs = Coefficient(1, f0, x, w)\n", + " \n", + " subs = Substitutions(f_x_coeffs)\n", + " \n", + " eq0 = Eq(f0.dt + f0.dx, 1, coefficients=subs)\n", + " eq1 = Eq(f1.dt + f1.dx, 1)\n", + " \n", + " stencil0 = solve(eq0.evaluate, f0.forward)\n", + " stencil1 = solve(eq1.evaluate, f1.forward)\n", + " \n", + " op0 = Operator(Eq(f0.forward, stencil0))\n", + " op1 = Operator(Eq(f1.forward, stencil1))\n", + " \n", + " op0(time_m=0, time_M=5, dt=1.0)\n", + " op1(time_m=0, time_M=5, dt=1.0)\n", + " \n", + "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", + "E assert Data(False)\n", + "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", + "E + where = np.all\n", + "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", + "E + where = np.isclose\n", + "\n", + "tests/test_symbolic_coefficients.py:96: AssertionError\n", + "----------------------------- Captured stderr call -----------------------------\n", + "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c: In function ‘Kernel’:\n", + "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + " #pragma omp simd aligned(f0,w:32)\n", + " \n", + "Operator `Kernel` run in 0.01 s\n", + "/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c: In function ‘Kernel’:\n", + "/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + " #pragma omp simd aligned(f1:32)\n", + " \n", + "Operator `Kernel` run in 0.01 s\n", + "------------------------------ Captured log call -------------------------------\n", + "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", + "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", + "====== 1 failed, 968 passed, 87 skipped, 2 xfailed in 1038.47s (0:17:18) =======\n" + ] + } + ], + "source": [ + "cli_command='docker run -it --rm --name fwi01_azureml_container ' +\\\n", + "' -v '+os.getenv('DOCKER_CONTAINER_MOUNT_POINT')+':/workspace:rw ' + \\\n", + "docker_image_name + \\\n", + "' /bin/bash -c \"conda env list ; ls -l /devito/tests; ' + \\\n", + "'python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; ' + \\\n", + "'cd /devito; ' + \\\n", + "run_devito_tests_command +\\\n", + "' \"'\n", + "\n", + "cli_command\n", + "! $cli_command\n", + "# # ============= 774 passed, 70 skipped, 1 xfailed in 1106.76 seconds =============\n", + "print('\\ncontent of devito tests log file after testing:')\n", + "!cat $fwi01_log_file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "###### Use the ACR created in previous notebook or docker hub to push your image" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'az acr login --name fwi01acr'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Login Succeeded\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\n", + "Configure a credential helper to remove this warning. See\n", + "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\n", + "\n", + "\u001b[0m" + ] + } + ], + "source": [ + "# docker_pwd = os.getenv('DOCKER_PWD')\n", + "# docker_login = os.getenv('DOCKER_LOGIN')\n", + "# !docker login -u=$docker_login -p=$docker_pwd\n", + "# !docker push {docker_image_name}\n", + "\n", + "%dotenv $dotenv_file_path\n", + "cli_command='az acr login --name '+os.getenv('ACR_NAME')\n", + "# print cli command\n", + "cli_command\n", + "\n", + "# run cli command\n", + "cli_command = cli_command+' --username '+os.getenv('ACR_USERNAME') + ' --password ' + os.getenv('ACR_PASSWORD')\n", + "! $cli_command" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cli_command='docker push '+docker_image_name\n", + "cli_command" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", + "\n", + "\u001b[1B01e8603a: Preparing \n", + "\u001b[1B83481e05: Preparing \n", + "\u001b[1Bf84794df: Preparing \n", + "\u001b[1B4559bd8a: Preparing \n", + "\u001b[1Bf8fc4c9a: Preparing \n", + "\u001b[1Bba47210e: Preparing \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[7B01e8603a: Pushing 1.055GB/2.967GBA\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 479.3MB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 910.8MB/2.967GB\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[6B83481e05: Pushing 2.253GB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 1.072GB/2.967GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 1.771GB/2.967GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[6B83481e05: Pushed 3.102GB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[Ksdk.v1.0.76: digest: sha256:25a4a6faad05e910366df62dc4ea0fbeb0d5fe912e56ad5bddb2325104653710 size: 1800\n" + ] + } + ], + "source": [ + "! $cli_command" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!\n" + ] + } + ], + "source": [ + "# !jupyter nbconvert 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito --to html\n", + "print('Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb new file mode 100755 index 00000000..c2902730 --- /dev/null +++ b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -0,0 +1,1003 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License. \n", + " \n", + " \n", + "# FWI demo based on: \n", + "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", + "\n", + "\n", + "\n", + "In this notebook we run the devito demo [notebooks](https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/) mentioned above by using an [AzureML estimator](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator.estimator?view=azure-ml-py) with custom docker image. The docker image and associated docker file were created in previous notebook.\n", + "\n", + "\n", + "#### This notebook is used as a control plane to submit experimentation jobs running devito in Azure in two modes (see [remote run azureml python script file invoking devito](#devito_demo_mode)):\n", + " - [Mode 1](#devito_demo_mode_1):\n", + " - uses custom code (slightly modified graphing functions save images to files too) \n", + " - experimentation job is defined by the devito code that is packaged as a py file to be run on an Azure remote compute target\n", + " - experimentation job can be used to track metrics or other artifacts (images)\n", + " \n", + " - Mode 2:\n", + " - papermill is invoked via its Python API to run unedited devito demo notebooks (https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) on the remote compute target and get back the results as saved notebooks that are then Available in Azure portal. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Allow multiple displays per cell\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\" " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "import shutil\n", + "import urllib\n", + "import azureml.core\n", + "from azureml.core import Workspace, Experiment\n", + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "from azureml.core.runconfig import MpiConfiguration\n", + "\n", + "\n", + "# from azureml.core.datastore import Datastore\n", + "# from azureml.data.data_reference import DataReference\n", + "# from azureml.pipeline.steps import HyperDriveStep\n", + "# from azureml.pipeline.core import Pipeline, PipelineData\n", + "# from azureml.train.dnn import TensorFlow\n", + "\n", + "from azureml.train.estimator import Estimator\n", + "from azureml.widgets import RunDetails\n", + "\n", + "import platform" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Azure ML SDK Version: 1.0.76\n" + ] + }, + { + "data": { + "text/plain": [ + "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", + "platform.platform()\n", + "os.getcwd()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../not_shared/general.env'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_path_to_sys_path(path_to_append):\n", + " if not (any(path_to_append in paths for paths in sys.path)):\n", + " sys.path.append(path_to_append)\n", + " \n", + "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", + "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", + "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", + "\n", + "import project_utils\n", + "prj_consts = project_utils.project_consts()\n", + "\n", + "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", + "dotenv_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../not_shared'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "workspace_config_dir = os.path.join(*(prj_consts.AML_WORKSPACE_CONFIG_DIR))\n", + "workspace_config_file = prj_consts.AML_WORKSPACE_CONFIG_FILE_NAME\n", + "workspace_config_dir" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/azureml_01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%dotenv $dotenv_file_path\n", + "\n", + "script_folder = prj_consts.AML_EXPERIMENT_DIR + ['devito_tutorial']\n", + "\n", + "devito_training_script_file = '01_modelling.py' # hardcoded in file azureml_training_script_full_file_name below\n", + "azureml_training_script_file = 'azureml_'+devito_training_script_file\n", + "experimentName = '020_AzureMLEstimator'\n", + "\n", + "os.makedirs(os.path.join(*(script_folder)), exist_ok=True)\n", + "script_path = os.path.join(*(script_folder))\n", + "training_script_full_file_name = os.path.join(script_path, devito_training_script_file)\n", + "azureml_training_script_full_file_name = os.path.join(script_path, azureml_training_script_file)\n", + "\n", + "training_script_full_file_name\n", + "azureml_training_script_full_file_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " \n", + "##### devito in Azure ML demo mode 1\n", + "Create devito demo script based on \n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/01_modelling.ipynb\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to summary of modes od demoing devito in AzureML.\n", + "\n", + "Main purpose of this script is to extend _plot_velocity()_ and _plot_shotrecord()_ devito [plotting functions](https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py) to allow the mto work in batch mode, i.e. save output to a file." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $training_script_full_file_name\n", + "\n", + "import numpy as np\n", + "import os, argparse\n", + "\n", + "from examples.seismic import Model\n", + "from examples.seismic import TimeAxis\n", + "from examples.seismic import Receiver\n", + "from devito import TimeFunction\n", + "from devito import Eq, solve\n", + "from devito import Operator\n", + "\n", + "\n", + "# try:\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib import cm\n", + "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", + "\n", + "mpl.rc('font', size=16)\n", + "mpl.rc('figure', figsize=(8, 6))\n", + "# except:\n", + "# plt = None\n", + "# cm = None\n", + " \n", + "\n", + "\n", + "# \"all\" plotting utils in devito do not save to file, so we extend them here\n", + "# https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py\n", + "def plot_velocity(model, source=None, receiver=None, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a two-dimensional velocity field from a seismic `Model`\n", + " object. Optionally also includes point markers for sources and receivers.\n", + "\n", + " Parameters\n", + " ----------\n", + " model : Model\n", + " Object that holds the velocity model.\n", + " source : array_like or float\n", + " Coordinates of the source point.\n", + " receiver : array_like or float\n", + " Coordinates of the receiver points.\n", + " colorbar : bool\n", + " Option to plot the colorbar.\n", + " \"\"\"\n", + " domain_size = 1.e-3 * np.array(model.domain_size)\n", + " extent = [model.origin[0], model.origin[0] + domain_size[0],\n", + " model.origin[1] + domain_size[1], model.origin[1]]\n", + "\n", + " plot = plt.imshow(np.transpose(model.vp.data), animated=True, cmap=cm.jet,\n", + " vmin=np.min(model.vp.data), vmax=np.max(model.vp.data),\n", + " extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Depth (km)')\n", + "\n", + " # Plot source points, if provided\n", + " if receiver is not None:\n", + " plt.scatter(1e-3*receiver[:, 0], 1e-3*receiver[:, 1],\n", + " s=25, c='green', marker='D')\n", + "\n", + " # Plot receiver points, if provided\n", + " if source is not None:\n", + " plt.scatter(1e-3*source[:, 0], 1e-3*source[:, 1],\n", + " s=25, c='red', marker='o')\n", + "\n", + " # Ensure axis limits\n", + " plt.xlim(model.origin[0], model.origin[0] + domain_size[0])\n", + " plt.ylim(model.origin[1] + domain_size[1], model.origin[1])\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " cbar = plt.colorbar(plot, cax=cax)\n", + " cbar.set_label('Velocity (km/s)')\n", + " plt.show()\n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def plot_shotrecord(rec, model, t0, tn, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a shot record (receiver values over time).\n", + "\n", + " Parameters\n", + " ----------\n", + " rec :\n", + " Receiver data with shape (time, points).\n", + " model : Model\n", + " object that holds the velocity model.\n", + " t0 : int\n", + " Start of time dimension to plot.\n", + " tn : int\n", + " End of time dimension to plot.\n", + " \"\"\"\n", + " scale = np.max(rec) / 10.\n", + " extent = [model.origin[0], model.origin[0] + 1e-3*model.domain_size[0],\n", + " 1e-3*tn, t0]\n", + "\n", + " plot = plt.imshow(rec, vmin=-scale, vmax=scale, cmap=cm.gray, extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Time (s)')\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " plt.colorbar(plot, cax=cax)\n", + " plt.show() \n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def main(output_folder): \n", + " # 1. Define the physical problem\n", + " # The first step is to define the physical model:\n", + " # - physical dimensions of interest\n", + " # - velocity profile of this physical domain\n", + "\n", + " # Define a physical size\n", + " shape = (101, 101) # Number of grid point (nx, nz)\n", + " spacing = (10., 10.) # Grid spacing in m. The domain size is now 1km by 1km\n", + " origin = (0., 0.) # What is the location of the top left corner. This is necessary to define\n", + " # the absolute location of the source and receivers\n", + "\n", + " # Define a velocity profile. The velocity is in km/s\n", + " v = np.empty(shape, dtype=np.float32)\n", + " v[:, :51] = 1.5\n", + " v[:, 51:] = 2.5\n", + "\n", + " # With the velocity and model size defined, we can create the seismic model that\n", + " # encapsulates this properties. We also define the size of the absorbing layer as 10 grid points\n", + " model = Model(vp=v, origin=origin, shape=shape, spacing=spacing,\n", + " space_order=2, nbpml=10)\n", + "\n", + " plot_velocity(model, \n", + " file= os.path.join(*( [output_folder,'output000.png'])))\n", + " \n", + " # 2. Acquisition geometry\n", + " t0 = 0. # Simulation starts a t=0\n", + " tn = 1000. # Simulation last 1 second (1000 ms)\n", + " dt = model.critical_dt # Time step from model grid spacing\n", + "\n", + " time_range = TimeAxis(start=t0, stop=tn, step=dt)\n", + " from examples.seismic import RickerSource\n", + "\n", + " f0 = 0.010 # Source peak frequency is 10Hz (0.010 kHz)\n", + " src = RickerSource(name='src', grid=model.grid, f0=f0,\n", + " npoint=1, time_range=time_range)\n", + "\n", + " # First, position source centrally in all dimensions, then set depth\n", + " src.coordinates.data[0, :] = np.array(model.domain_size) * .5\n", + " src.coordinates.data[0, -1] = 20. # Depth is 20m\n", + "\n", + " # We can plot the time signature to see the wavelet\n", + "# src.show()\n", + "\n", + " # Create symbol for 101 receivers\n", + " rec = Receiver(name='rec', grid=model.grid, npoint=101, time_range=time_range)\n", + "\n", + " # Prescribe even spacing for receivers along the x-axis\n", + " rec.coordinates.data[:, 0] = np.linspace(0, model.domain_size[0], num=101)\n", + " rec.coordinates.data[:, 1] = 20. # Depth is 20m\n", + "\n", + " # We can now show the source and receivers within our domain:\n", + " # Red dot: Source location\n", + " # Green dots: Receiver locations (every 4th point)\n", + " plot_velocity(model, source=src.coordinates.data,\n", + " receiver=rec.coordinates.data[::4, :], \n", + " file= os.path.join(*( [output_folder,'output010.png'])))\n", + " \n", + " # Define the wavefield with the size of the model and the time dimension\n", + " u = TimeFunction(name=\"u\", grid=model.grid, time_order=2, space_order=2)\n", + "\n", + " # We can now write the PDE\n", + " pde = model.m * u.dt2 - u.laplace + model.damp * u.dt\n", + "\n", + " # The PDE representation is as on paper\n", + " pde\n", + " \n", + " # This discrete PDE can be solved in a time-marching way updating u(t+dt) from the previous time step\n", + " # Devito as a shortcut for u(t+dt) which is u.forward. We can then rewrite the PDE as \n", + " # a time marching updating equation known as a stencil using customized SymPy functions\n", + "\n", + " stencil = Eq(u.forward, solve(pde, u.forward))\n", + " # Finally we define the source injection and receiver read function to generate the corresponding code\n", + " src_term = src.inject(field=u.forward, expr=src * dt**2 / model.m)\n", + "\n", + " # Create interpolation expression for receivers\n", + " rec_term = rec.interpolate(expr=u.forward)\n", + "\n", + " op = Operator([stencil] + src_term + rec_term, subs=model.spacing_map)\n", + " \n", + " op(time=time_range.num-1, dt=model.critical_dt)\n", + " plot_shotrecord(rec.data, model, t0, tn, \n", + " file= os.path.join(*( [output_folder,'output020.png'])))\n", + "\n", + "if __name__ == \"__main__\":\n", + " parser = argparse.ArgumentParser()\n", + " parser.add_argument('--output_folder', type=str, nargs='?', \\\n", + " dest='output_folder', help='ouput artifacts location',\\\n", + " default='.')\n", + " args = parser.parse_args()\n", + " \n", + " main(args.output_folder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Get experimentation docker image for devito" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", + "docker_image_name = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", + "if image_version!=\"\":\n", + " docker_image_name = docker_image_name +':'+ image_version\n", + "\n", + "full_docker_image_name = docker_repo_name + '/' + docker_image_name\n", + " \n", + "docker_image_name\n", + "full_docker_image_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Extract/decide the python path in custom docker image that corresponds to desired conda environment. Without this, AzureML tries to create a separate environment." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Login Succeeded\r\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\r\n", + "Configure a credential helper to remove this warning. See\r\n", + "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", + "\r\n", + "\u001b[0m" + ] + } + ], + "source": [ + "%dotenv $dotenv_file_path\n", + "cli_command='az acr login --name '+\\\n", + "os.getenv('ACR_NAME')+\\\n", + "' --username '+os.getenv('ACR_USERNAME') + ' --password ' + os.getenv('ACR_PASSWORD')\n", + "! $cli_command" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 /bin/bash -c \"which python\" '" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/opt/conda/envs/fwi01_conda_env/bin/python'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_Python_path_command='docker run -i --rm --name fwi01_azureml_container02 '+ \\\n", + "full_docker_image_name + \\\n", + "' /bin/bash -c \"which python\" '\n", + "get_Python_path_command\n", + "\n", + "\n", + "import subprocess\n", + "python_path_in_docker_image = subprocess.check_output(get_Python_path_command,shell=True,stderr=subprocess.STDOUT).\\\n", + "decode('utf-8').strip()\n", + "python_path_in_docker_image" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "#### Create azureml_script_file that invokes:\n", + " - devito exclusive custom edited training_script_file\n", + " - unedited devito notebooks via papermill (invoked via cli and via ppapermill python API)\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to notebook summary." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/azureml_01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $azureml_training_script_full_file_name\n", + "\n", + "import argparse\n", + "import os\n", + "os.system('conda env list')\n", + "\n", + "import azureml.core;\n", + "from azureml.core.run import Run\n", + "\n", + "print(azureml.core.VERSION)\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "parser.add_argument('--output_folder', type=str, dest='output_folder', help='ouput artifacts location')\n", + "\n", + "args = parser.parse_args()\n", + "print('args.output_folder is {} but it will be ignored since AzureML_tracked ./outputs will be used'.format(args.output_folder))\n", + "\n", + "# get the Azure ML run object\n", + "run = Run.get_context()\n", + "\n", + "# ./outputs/ folder is autotracked so should get uploaded at the end of the run\n", + "output_dir_AzureML_tracked = './outputs'\n", + "\n", + "crt_dir = os.getcwd()\n", + "\n", + "cli_command= \\\n", + "'cd /devito; /opt/conda/envs/fwi01_conda_env/bin/python '+ crt_dir +'/01_modelling.py' + \\\n", + "' --output_folder '+ crt_dir + output_dir_AzureML_tracked+ '/' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/01_modelling.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/devito_cli_py.log'\n", + "print('Running devito from cli on 01_modelling.py----BEGIN-----:') \n", + "print(cli_command); print('\\n');os.system(cli_command)\n", + "print('Running devito from cli on 01_modelling.py----END-----:\\n\\n')\n", + "\n", + "cli_command= \\\n", + "'cd /devito; papermill ' + \\\n", + "'./examples/seismic/tutorials/02_rtm.ipynb '+\\\n", + "crt_dir +'/outputs/02_rtm_output.ipynb ' + \\\n", + "'--log-output --no-progress-bar --kernel python3 ' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/02_rtm_output.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/papermill_cli.log'\n", + "\n", + "# FIXME - activate right conda env for running papermill from cli\n", + "activate_right_conda_env_fixed = False\n", + "if activate_right_conda_env_fixed:\n", + " print('Running papermill from cli on 02_rtm.ipynb----BEGIN-----:') \n", + " print(cli_command); print('\\n');os.system(cli_command)\n", + " print('Running papermill from cli on 02_rtm.ipynb----END-----:\\n\\n') \n", + "\n", + "\n", + "print('Running papermill from Python API on 03_fwi.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/03_fwi.ipynb',\n", + " crt_dir +'/outputs/03_fwi_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 03_fwi.ipynb----END-----:') \n", + "\n", + "print('Running papermill from Python API on 04_dask.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/04_dask.ipynb',\n", + " crt_dir +'/outputs/04_dask_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 04_dask.ipynb----END-----:') \n", + " \n", + "\n", + "os.system('pwd')\n", + "os.system('ls -l /')\n", + "os.system('ls -l ./')\n", + "os.system('ls -l ' +crt_dir + output_dir_AzureML_tracked)\n", + "run.log('training_message01: ', 'finished experiment')\n", + "print('\\n')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['azureml_01_modelling.py', '01_modelling.py']" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "script_path=os.path.join(*(script_folder))\n", + "os.listdir(script_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize workspace\n", + "\n", + "Initialize a workspace object from persisted configuration. If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, make sure the config file is present at .\\config.json" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING - Warning: Falling back to use azure cli login credentials.\n", + "If you run your code in unattended mode, i.e., where you can't give a user input, then we recommend to use ServicePrincipalAuthentication or MsiAuthentication.\n", + "Please refer to aka.ms/aml-notebook-auth for different authentication mechanisms in azureml-sdk.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Workspace name: ghiordanfwiws\n", + "Azure region: eastus2\n", + "Subscription id: 7899\n" + ] + } + ], + "source": [ + "ws = Workspace.from_config(\n", + " path=os.path.join(os.getcwd(),\n", + " os.path.join(*([workspace_config_dir, '.azureml', workspace_config_file]))))\n", + "print('Workspace name: ' + ws.name, \n", + " 'Azure region: ' + ws.location, \n", + " 'Subscription id: ' + ws.subscription_id[0:4], sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create an Azure ML experiment\n", + "Let's create an experiment named \"tf-mnist\" and a folder to hold the training scripts. The script runs will be recorded under the experiment in Azure." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "exp = Experiment(workspace=ws, name=experimentName)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve or create a Azure Machine Learning compute\n", + "Azure Machine Learning Compute is a service for provisioning and managing clusters of Azure virtual machines for running machine learning workloads. Let's create a new Azure Machine Learning Compute in the current workspace, if it doesn't already exist. We will then run the training script on this compute target.\n", + "\n", + "If we could not find the compute with the given name in the previous cell, then we will create a new compute here. This process is broken down into the following steps:\n", + "\n", + "1. Create the configuration\n", + "2. Create the Azure Machine Learning compute\n", + "\n", + "**This process will take a few minutes and is providing only sparse output in the process. Please make sure to wait until the call returns before moving to the next cell.**" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gpuclstfwi02'" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", + "gpu_cluster_name" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found existing gpu cluster\n" + ] + } + ], + "source": [ + "# Verify that cluster does not exist already\n", + "max_nodes_value = 5\n", + "try:\n", + " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", + " print(\"Found existing gpu cluster\")\n", + "except ComputeTargetException:\n", + " print(\"Could not find ComputeTarget cluster!\")\n", + " \n", + "# # Create a new gpucluster using code below\n", + "# # Specify the configuration for the new cluster\n", + "# compute_config = AmlCompute.provisioning_configuration(vm_size=\"Standard_NC6\",\n", + "# min_nodes=0,\n", + "# max_nodes=max_nodes_value)\n", + "# # Create the cluster with the specified name and configuration\n", + "# gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, compute_config)\n", + "\n", + "# # Wait for the cluster to complete, show the output log\n", + "# gpu_cluster.wait_for_completion(show_output=True)\n", + " \n", + " \n", + "# for demo purposes, show how clsuter properties can be altered post-creation\n", + "gpu_cluster.update(min_nodes=0, max_nodes=max_nodes_value, idle_seconds_before_scaledown=1200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create an Azure ML SDK estimator with custom docker image " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f94f884975d14f94977e26fb8e35745f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'NOTSET',…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"created_utc\": \"2019-12-04T17:21:06.014446Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-04T17:30:00.733408Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=XtrqU6IMV%2B48BKF%2BQtFwy6LRoQjGkEJ2ALnJqe4N86Y%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=Z5mXvd7qJNNv2tWMiT1C25Tc%2Bu%2BblrWqkN6UFUzge2U%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=dOviChtDoL5vak4hJd2Pw3Pjj%2BkN5BmxbcPiW6ezqK0%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=1rch4YL2wvgtosymal8WdNdNW6K34Om02P0TMcWcjyc%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=DlifA0Xe8zotaCGo56XNplmG4xCoY0eegb1kFvRaOXg%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=8myM2hR0Cph66FXGtG4YmjmYoP%2F5c3bDBXacYucjnzI%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"logs/azureml/730_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/logs/azureml/730_azureml.log?sv=2019-02-02&sr=b&sig=AE4JyiNXVvzRYF8nTY0BrwltFPiXXIwSga9AlzFDEd8%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=Wk8VGtb5XKlUzkOqIRVsnhvdT3MGSH%2B%2BJEuS63MP0%2BI%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"logs/azureml/730_azureml.log\"]], \"run_duration\": \"0:08:54\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-04 17:28:21,458|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-04 17:28:21,458|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-04 17:28:21,459|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-04 17:28:21,459|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-04 17:28:21,801|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-04 17:28:21,801|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-04 17:28:22,775|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-04 17:28:22,780|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-04 17:28:22,785|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-04 17:28:22,790|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-04 17:28:22,792|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:28:22,798|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,799|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-04 17:28:22,799|azureml.core.authentication|DEBUG|Time to expire 1813963.200661 seconds\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,961|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,967|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,975|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,980|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,985|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,990|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,991|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-04 17:28:22,991|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:28:22,991|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|None\\n2019-12-04 17:28:22,992|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:28:22,992|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:28:22,993|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:28:22,993|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:28:23 GMT'\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:28:23,584|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1460,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-04T17:21:06.014446+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-04T17:25:54.100925+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575480062_180862b6/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-04 17:28:23,589|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-04 17:28:23,590|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'mlflow.source.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-04 17:28:23,590|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-04 17:28:23,591|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-04 17:28:23,591|azureml.WorkerPool|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-04 17:28:23,592|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:28:26,647|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,649|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,654|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,655|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-04 17:28:26,660|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,666|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,672|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,677|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,677|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-04 17:28:26,677|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|None\\n2019-12-04 17:28:26,678|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:28:27 GMT'\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1460,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-04T17:21:06.014446+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-04T17:25:54.100925+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575480062_180862b6/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-04 17:28:27,361|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-04 17:28:27,362|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'mlflow.source.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-04 17:28:27,362|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-04 17:28:52,796|azureml.core.authentication|DEBUG|Time to expire 1813933.203089 seconds\\n2019-12-04 17:29:22,797|azureml.core.authentication|DEBUG|Time to expire 1813903.202753 seconds\\n2019-12-04 17:29:37,671|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-04 17:29:37,671|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:29:37,672|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:29:37,708|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-04 17:29:37,708|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|INFO|complete is not setting status for submitted runs.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-04 17:29:37,710|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-04 17:29:37,711|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-04 17:29:37,711|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-04 17:29:37,712|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:29:37,713|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-04 17:29:37,713|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:29:37,713|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-04 17:29:37,713|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6/batch/metrics'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,715|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,715|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-04 17:29:37,715|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-04 17:29:37,716|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"db9cd91b-ba34-41bf-aad8-b744d46471a4\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-04T17:29:37.671117Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:29:37 GMT'\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG|\\n2019-12-04 17:29:37,835|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00024700164794921875 seconds.\\n\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-04 17:29:37,967|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# use a custom Docker image\n", + "from azureml.core.container_registry import ContainerRegistry\n", + "\n", + "image_name = docker_image_name\n", + "\n", + "# you can also point to an image in a private ACR\n", + "image_registry_details = ContainerRegistry()\n", + "image_registry_details.address = docker_repo_name\n", + "image_registry_details.username = os.getenv('ACR_USERNAME')\n", + "image_registry_details.password = os.getenv('ACR_PASSWORD') \n", + "\n", + "# don't let the system build a new conda environment\n", + "user_managed_dependencies = True\n", + "\n", + "# submit to a local Docker container. if you don't have Docker engine running locally, you can set compute_target to cpu_cluster.\n", + "script_params = {\n", + " '--output_folder': 'some_folder'\n", + "}\n", + "\n", + "\n", + "# distributed_training_conf = MpiConfiguration()\n", + "# distributed_training_conf.process_count_per_node = 2\n", + "\n", + "est = Estimator(source_directory=script_path, \n", + " compute_target=gpu_cluster,#'local', #gpu_cluster, \n", + " entry_script=azureml_training_script_file,\n", + " script_params=script_params,\n", + " use_docker=True,\n", + " custom_docker_image=image_name,\n", + " # uncomment below line to use your private ACR\n", + " image_registry_details=image_registry_details, \n", + " user_managed=user_managed_dependencies,\n", + " distributed_training=None,\n", + " node_count=1\n", + " )\n", + "est.run_config.environment.python.interpreter_path = python_path_in_docker_image\n", + "\n", + "run = exp.submit(est)\n", + "RunDetails(run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One can use the above link to currrent experiment run in Azure Portal to see tracked metrics, and images and output notebooks saved by azureml_training_script_full_file_name in {run_dir}/outputs on the remote compute target that are automatically saved by AzureML in the run history Azure portal pages." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'runId= 020_AzureMLEstimator_1575480062_180862b6'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'experimentation baseImage: fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_details = run.get_details()\n", + "\n", + "# print some details of job run\n", + "'runId= {}'.format(run_details['runId'])\n", + "'experimentation baseImage: {}'.format(run_details['runDefinition']['environment']['docker']['baseImage'])" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!\n" + ] + } + ], + "source": [ + "print('Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb new file mode 100755 index 00000000..f6b40ef1 --- /dev/null +++ b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -0,0 +1,1281 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License. \n", + " \n", + "\n", + "# FWI demo based on: \n", + "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", + "\n", + "\n", + "\n", + "In this notebook we run the devito demo [notebooks](https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/) mentioned above by using an [AzureML estimator](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator.estimator?view=azure-ml-py) with custom docker image. The docker image and associated docker file were created in previous notebook.\n", + "\n", + "\n", + "#### This notebook is used as a control plane to submit experimentation jobs running devito in Azure in two modes (see [remote run azureml python script file invoking devito](#devito_demo_mode)):\n", + " - [Mode 1](#devito_demo_mode_1):\n", + " - uses custom code (slightly modified graphing functions save images to files too) \n", + " - experimentation job is defined by the devito code that is packaged as a py file to be run on an Azure remote compute target\n", + " - experimentation job can be used to track metrics or other artifacts (images)\n", + " \n", + " - Mode 2:\n", + " - papermill is invoked via cli or via its Python API to run unedited devito demo notebooks (https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) on the remote compute target and get back the results as saved notebooks that are then Available in Azure portal. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Allow multiple displays per cell\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\" " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "import shutil\n", + "import urllib\n", + "import azureml.core\n", + "from azureml.core import Workspace, Experiment\n", + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "from azureml.core.runconfig import MpiConfiguration\n", + "\n", + "\n", + "# from azureml.core.datastore import Datastore\n", + "# from azureml.data.data_reference import DataReference\n", + "# from azureml.pipeline.steps import HyperDriveStep\n", + "# from azureml.pipeline.core import Pipeline, PipelineData\n", + "# from azureml.train.dnn import TensorFlow\n", + "\n", + "from azureml.train.estimator import Estimator\n", + "from azureml.widgets import RunDetails\n", + "\n", + "import platform" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Azure ML SDK Version: 1.0.76\n" + ] + }, + { + "data": { + "text/plain": [ + "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", + "platform.platform()\n", + "os.getcwd()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../not_shared/general.env'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_path_to_sys_path(path_to_append):\n", + " if not (any(path_to_append in paths for paths in sys.path)):\n", + " sys.path.append(path_to_append)\n", + " \n", + "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", + "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", + "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", + "\n", + "import project_utils\n", + "prj_consts = project_utils.project_consts()\n", + "\n", + "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", + "dotenv_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../not_shared'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "workspace_config_dir = os.path.join(*(prj_consts.AML_WORKSPACE_CONFIG_DIR))\n", + "workspace_config_file = prj_consts.AML_WORKSPACE_CONFIG_FILE_NAME\n", + "workspace_config_dir" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/azureml_01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%dotenv $dotenv_file_path\n", + "\n", + "script_folder = prj_consts.AML_EXPERIMENT_DIR + ['devito_tutorial']\n", + "\n", + "devito_training_script_file = '01_modelling.py' # hardcoded in file azureml_training_script_full_file_name below\n", + "azureml_training_script_file = 'azureml_'+devito_training_script_file\n", + "experimentName = '020_AzureMLEstimator'\n", + "\n", + "os.makedirs(os.path.join(*(script_folder)), exist_ok=True)\n", + "script_path = os.path.join(*(script_folder))\n", + "training_script_full_file_name = os.path.join(script_path, devito_training_script_file)\n", + "azureml_training_script_full_file_name = os.path.join(script_path, azureml_training_script_file)\n", + "\n", + "training_script_full_file_name\n", + "azureml_training_script_full_file_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " \n", + "##### devito in Azure ML demo mode 1\n", + "Create devito demo script based on \n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/01_modelling.ipynb\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to summary of modes od demoing devito in AzureML.\n", + "\n", + "Main purpose of this script is to extend _plot_velocity()_ and _plot_shotrecord()_ devito [plotting functions](https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py) to allow the mto work in batch mode, i.e. save output to a file." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $training_script_full_file_name\n", + "\n", + "import numpy as np\n", + "import os, argparse\n", + "\n", + "from examples.seismic import Model\n", + "from examples.seismic import TimeAxis\n", + "from examples.seismic import Receiver\n", + "from devito import TimeFunction\n", + "from devito import Eq, solve\n", + "from devito import Operator\n", + "\n", + "\n", + "# try:\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib import cm\n", + "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", + "\n", + "mpl.rc('font', size=16)\n", + "mpl.rc('figure', figsize=(8, 6))\n", + "# except:\n", + "# plt = None\n", + "# cm = None\n", + " \n", + "\n", + "\n", + "# \"all\" plotting utils in devito do not save to file, so we extend them here\n", + "# https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py\n", + "def plot_velocity(model, source=None, receiver=None, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a two-dimensional velocity field from a seismic `Model`\n", + " object. Optionally also includes point markers for sources and receivers.\n", + "\n", + " Parameters\n", + " ----------\n", + " model : Model\n", + " Object that holds the velocity model.\n", + " source : array_like or float\n", + " Coordinates of the source point.\n", + " receiver : array_like or float\n", + " Coordinates of the receiver points.\n", + " colorbar : bool\n", + " Option to plot the colorbar.\n", + " \"\"\"\n", + " domain_size = 1.e-3 * np.array(model.domain_size)\n", + " extent = [model.origin[0], model.origin[0] + domain_size[0],\n", + " model.origin[1] + domain_size[1], model.origin[1]]\n", + "\n", + " plot = plt.imshow(np.transpose(model.vp.data), animated=True, cmap=cm.jet,\n", + " vmin=np.min(model.vp.data), vmax=np.max(model.vp.data),\n", + " extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Depth (km)')\n", + "\n", + " # Plot source points, if provided\n", + " if receiver is not None:\n", + " plt.scatter(1e-3*receiver[:, 0], 1e-3*receiver[:, 1],\n", + " s=25, c='green', marker='D')\n", + "\n", + " # Plot receiver points, if provided\n", + " if source is not None:\n", + " plt.scatter(1e-3*source[:, 0], 1e-3*source[:, 1],\n", + " s=25, c='red', marker='o')\n", + "\n", + " # Ensure axis limits\n", + " plt.xlim(model.origin[0], model.origin[0] + domain_size[0])\n", + " plt.ylim(model.origin[1] + domain_size[1], model.origin[1])\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " cbar = plt.colorbar(plot, cax=cax)\n", + " cbar.set_label('Velocity (km/s)')\n", + " plt.show()\n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def plot_shotrecord(rec, model, t0, tn, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a shot record (receiver values over time).\n", + "\n", + " Parameters\n", + " ----------\n", + " rec :\n", + " Receiver data with shape (time, points).\n", + " model : Model\n", + " object that holds the velocity model.\n", + " t0 : int\n", + " Start of time dimension to plot.\n", + " tn : int\n", + " End of time dimension to plot.\n", + " \"\"\"\n", + " scale = np.max(rec) / 10.\n", + " extent = [model.origin[0], model.origin[0] + 1e-3*model.domain_size[0],\n", + " 1e-3*tn, t0]\n", + "\n", + " plot = plt.imshow(rec, vmin=-scale, vmax=scale, cmap=cm.gray, extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Time (s)')\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " plt.colorbar(plot, cax=cax)\n", + " plt.show() \n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def main(output_folder): \n", + " # 1. Define the physical problem\n", + " # The first step is to define the physical model:\n", + " # - physical dimensions of interest\n", + " # - velocity profile of this physical domain\n", + "\n", + " # Define a physical size\n", + " shape = (101, 101) # Number of grid point (nx, nz)\n", + " spacing = (10., 10.) # Grid spacing in m. The domain size is now 1km by 1km\n", + " origin = (0., 0.) # What is the location of the top left corner. This is necessary to define\n", + " # the absolute location of the source and receivers\n", + "\n", + " # Define a velocity profile. The velocity is in km/s\n", + " v = np.empty(shape, dtype=np.float32)\n", + " v[:, :51] = 1.5\n", + " v[:, 51:] = 2.5\n", + "\n", + " # With the velocity and model size defined, we can create the seismic model that\n", + " # encapsulates this properties. We also define the size of the absorbing layer as 10 grid points\n", + " model = Model(vp=v, origin=origin, shape=shape, spacing=spacing,\n", + " space_order=2, nbpml=10)\n", + "\n", + " plot_velocity(model, \n", + " file= os.path.join(*( [output_folder,'output000.png'])))\n", + " \n", + " # 2. Acquisition geometry\n", + " t0 = 0. # Simulation starts a t=0\n", + " tn = 1000. # Simulation last 1 second (1000 ms)\n", + " dt = model.critical_dt # Time step from model grid spacing\n", + "\n", + " time_range = TimeAxis(start=t0, stop=tn, step=dt)\n", + " from examples.seismic import RickerSource\n", + "\n", + " f0 = 0.010 # Source peak frequency is 10Hz (0.010 kHz)\n", + " src = RickerSource(name='src', grid=model.grid, f0=f0,\n", + " npoint=1, time_range=time_range)\n", + "\n", + " # First, position source centrally in all dimensions, then set depth\n", + " src.coordinates.data[0, :] = np.array(model.domain_size) * .5\n", + " src.coordinates.data[0, -1] = 20. # Depth is 20m\n", + "\n", + " # We can plot the time signature to see the wavelet\n", + "# src.show()\n", + "\n", + " # Create symbol for 101 receivers\n", + " rec = Receiver(name='rec', grid=model.grid, npoint=101, time_range=time_range)\n", + "\n", + " # Prescribe even spacing for receivers along the x-axis\n", + " rec.coordinates.data[:, 0] = np.linspace(0, model.domain_size[0], num=101)\n", + " rec.coordinates.data[:, 1] = 20. # Depth is 20m\n", + "\n", + " # We can now show the source and receivers within our domain:\n", + " # Red dot: Source location\n", + " # Green dots: Receiver locations (every 4th point)\n", + " plot_velocity(model, source=src.coordinates.data,\n", + " receiver=rec.coordinates.data[::4, :], \n", + " file= os.path.join(*( [output_folder,'output010.png'])))\n", + " \n", + " # Define the wavefield with the size of the model and the time dimension\n", + " u = TimeFunction(name=\"u\", grid=model.grid, time_order=2, space_order=2)\n", + "\n", + " # We can now write the PDE\n", + " pde = model.m * u.dt2 - u.laplace + model.damp * u.dt\n", + "\n", + " # The PDE representation is as on paper\n", + " pde\n", + " \n", + " # This discrete PDE can be solved in a time-marching way updating u(t+dt) from the previous time step\n", + " # Devito as a shortcut for u(t+dt) which is u.forward. We can then rewrite the PDE as \n", + " # a time marching updating equation known as a stencil using customized SymPy functions\n", + "\n", + " stencil = Eq(u.forward, solve(pde, u.forward))\n", + " # Finally we define the source injection and receiver read function to generate the corresponding code\n", + " src_term = src.inject(field=u.forward, expr=src * dt**2 / model.m)\n", + "\n", + " # Create interpolation expression for receivers\n", + " rec_term = rec.interpolate(expr=u.forward)\n", + "\n", + " op = Operator([stencil] + src_term + rec_term, subs=model.spacing_map)\n", + " \n", + " op(time=time_range.num-1, dt=model.critical_dt)\n", + " plot_shotrecord(rec.data, model, t0, tn, \n", + " file= os.path.join(*( [output_folder,'output020.png'])))\n", + "\n", + "if __name__ == \"__main__\":\n", + " parser = argparse.ArgumentParser()\n", + " parser.add_argument('--output_folder', type=str, nargs='?', \\\n", + " dest='output_folder', help='ouput artifacts location',\\\n", + " default='.')\n", + " args = parser.parse_args()\n", + " \n", + " main(args.output_folder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Get experimentation docker image for devito" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", + "\n", + "docker_image_name = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", + "if image_version!=\"\":\n", + " docker_image_name = docker_image_name +':'+ image_version\n", + "\n", + "full_docker_image_name = docker_repo_name + '/' + docker_image_name\n", + " \n", + "docker_image_name\n", + "full_docker_image_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Extract/decide the python path in custom docker image that corresponds to desired conda environment. Without this, AzureML tries to create a separate environment." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 /bin/bash -c \"which python\" '" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/opt/conda/envs/fwi01_conda_env/bin/python'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_Python_path_command='docker run -i --rm --name fwi01_azureml_container02 '+ \\\n", + "full_docker_image_name + \\\n", + "' /bin/bash -c \"which python\" '\n", + "get_Python_path_command\n", + "\n", + "\n", + "import subprocess\n", + "python_path_in_docker_image = subprocess.check_output(get_Python_path_command,shell=True,stderr=subprocess.STDOUT).\\\n", + "decode('utf-8').strip()\n", + "python_path_in_docker_image" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "#### Create azureml_script_file that invokes:\n", + " - devito exclusive custom edited training_script_file\n", + " - unedited devito notebooks via papermill (invoked via cli and via ppapermill python API)\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to notebook summary." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/azureml_01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $azureml_training_script_full_file_name\n", + "\n", + "import argparse\n", + "import os\n", + "os.system('conda env list')\n", + "\n", + "import azureml.core;\n", + "from azureml.core.run import Run\n", + "\n", + "print(azureml.core.VERSION)\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "parser.add_argument('--output_folder', type=str, dest='output_folder', help='ouput artifacts location')\n", + "\n", + "args = parser.parse_args()\n", + "print('args.output_folder is {} but it will be ignored since AzureML_tracked ./outputs will be used'.format(args.output_folder))\n", + "\n", + "# get the Azure ML run object\n", + "run = Run.get_context()\n", + "\n", + "# ./outputs/ folder is autotracked so should get uploaded at the end of the run\n", + "output_dir_AzureML_tracked = './outputs'\n", + "\n", + "crt_dir = os.getcwd()\n", + "\n", + "cli_command= \\\n", + "'cd /devito; /opt/conda/envs/fwi01_conda_env/bin/python '+ crt_dir +'/01_modelling.py' + \\\n", + "' --output_folder '+ crt_dir + output_dir_AzureML_tracked+ '/' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/01_modelling.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/devito_cli_py.log'\n", + "print('Running devito from cli on 01_modelling.py----BEGIN-----:') \n", + "print(cli_command); print('\\n');os.system(cli_command)\n", + "print('Running devito from cli on 01_modelling.py----END-----:\\n\\n')\n", + "\n", + "cli_command= \\\n", + "'cd /devito; papermill ' + \\\n", + "'./examples/seismic/tutorials/02_rtm.ipynb '+\\\n", + "crt_dir +'/outputs/02_rtm_output.ipynb ' + \\\n", + "'--log-output --no-progress-bar --kernel python3 ' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/02_rtm_output.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/papermill_cli.log'\n", + "\n", + "# FIXME - activate right conda env for running papermill from cli\n", + "activate_right_conda_env_fixed = False\n", + "if activate_right_conda_env_fixed:\n", + " print('Running papermill from cli on 02_rtm.ipynb----BEGIN-----:') \n", + " print(cli_command); print('\\n');os.system(cli_command)\n", + " print('Running papermill from cli on 02_rtm.ipynb----END-----:\\n\\n') \n", + "\n", + "\n", + "print('Running papermill from Python API on 03_fwi.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/03_fwi.ipynb',\n", + " crt_dir +'/outputs/03_fwi_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 03_fwi.ipynb----END-----:') \n", + "\n", + "print('Running papermill from Python API on 04_dask.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/04_dask.ipynb',\n", + " crt_dir +'/outputs/04_dask_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 04_dask.ipynb----END-----:') \n", + " \n", + "\n", + "os.system('pwd')\n", + "os.system('ls -l /')\n", + "os.system('ls -l ./')\n", + "os.system('ls -l ' +crt_dir + output_dir_AzureML_tracked)\n", + "run.log('training_message01: ', 'finished experiment')\n", + "print('\\n')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['azureml_01_modelling.py', '01_modelling.py']" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "script_path=os.path.join(*(script_folder))\n", + "os.listdir(script_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize workspace\n", + "\n", + "Initialize a workspace object from persisted configuration. If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, make sure the config file is present at .\\config.json" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING - Warning: Falling back to use azure cli login credentials.\n", + "If you run your code in unattended mode, i.e., where you can't give a user input, then we recommend to use ServicePrincipalAuthentication or MsiAuthentication.\n", + "Please refer to aka.ms/aml-notebook-auth for different authentication mechanisms in azureml-sdk.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Workspace name: ghiordanfwiws\n", + "Azure region: eastus2\n", + "Subscription id: 7899\n" + ] + } + ], + "source": [ + "ws = Workspace.from_config(\n", + " path=os.path.join(os.getcwd(),\n", + " os.path.join(*([workspace_config_dir, '.azureml', workspace_config_file]))))\n", + "print('Workspace name: ' + ws.name, \n", + " 'Azure region: ' + ws.location, \n", + " 'Subscription id: ' + ws.subscription_id[0:4], sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create an Azure ML experiment\n", + "Let's create an experiment named \"tf-mnist\" and a folder to hold the training scripts. The script runs will be recorded under the experiment in Azure." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "exp = Experiment(workspace=ws, name=experimentName)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve or create a Azure Machine Learning compute\n", + "Azure Machine Learning Compute is a service for provisioning and managing clusters of Azure virtual machines for running machine learning workloads. Let's create a new Azure Machine Learning Compute in the current workspace, if it doesn't already exist. We will then run the training script on this compute target.\n", + "\n", + "If we could not find the compute with the given name in the previous cell, then we will create a new compute here. This process is broken down into the following steps:\n", + "\n", + "1. Create the configuration\n", + "2. Create the Azure Machine Learning compute\n", + "\n", + "**This process will take a few minutes and is providing only sparse output in the process. Please make sure to wait until the call returns before moving to the next cell.**" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gpuclstfwi08'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", + "gpu_cluster_name = 'gpuclstfwi08'\n", + "gpu_cluster_name" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found existing gpu cluster\n" + ] + } + ], + "source": [ + "# Verify that cluster does not exist already\n", + "max_nodes_value = 5\n", + "try:\n", + " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", + " print(\"Found existing gpu cluster\")\n", + "except ComputeTargetException:\n", + " print(\"Could not find ComputeTarget cluster!\")\n", + " \n", + "# # Create a new gpucluster using code below\n", + "# # Specify the configuration for the new cluster\n", + "# compute_config = AmlCompute.provisioning_configuration(vm_size=\"Standard_NC6\",\n", + "# min_nodes=0,\n", + "# max_nodes=max_nodes_value)\n", + "# # Create the cluster with the specified name and configuration\n", + "# gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, compute_config)\n", + "\n", + "# # Wait for the cluster to complete, show the output log\n", + "# gpu_cluster.wait_for_completion(show_output=True)\n", + " \n", + " \n", + "# for demo purposes, show how clsuter properties can be altered post-creation\n", + "gpu_cluster.update(min_nodes=0, max_nodes=max_nodes_value, idle_seconds_before_scaledown=1200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create an Azure ML SDK estimator with custom docker image " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "edb374a7e72648cd892b24fb0cc8c2b6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'NOTSET',…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575481417_8013866e?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575481417_8013866e\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575481417_8013866e\", \"created_utc\": \"2019-12-04T17:43:40.27579Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":1,\\\"CurrentNodeCount\\\":2}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575481417_8013866e/azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt?sv=2019-02-02&sr=b&sig=lVAX2nGZV7C62e8t0DZkL19PV8vYW8dKjDExkyOnHj0%3D&st=2019-12-04T17%3A38%3A58Z&se=2019-12-05T01%3A48%3A58Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt\"]], \"run_duration\": \"0:05:18\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2019-12-04T17:48:07Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.76: Pulling from fwi01_azureml\\n1ab2bdfe9778: Pulling fs layer\\ndd7d28bd8be5: Pulling fs layer\\naf998e3a361b: Pulling fs layer\\n1f706d9b369a: Pulling fs layer\\ne5000b763efd: Pulling fs layer\\n8c572c1c1101: Pulling fs layer\\n5988e7f62200: Pulling fs layer\\n8c572c1c1101: Waiting\\n5988e7f62200: Waiting\\ne5000b763efd: Waiting\\n1f706d9b369a: Waiting\\naf998e3a361b: Verifying Checksum\\naf998e3a361b: Download complete\\ndd7d28bd8be5: Verifying Checksum\\ndd7d28bd8be5: Download complete\\ne5000b763efd: Verifying Checksum\\ne5000b763efd: Download complete\\n1ab2bdfe9778: Verifying Checksum\\n1ab2bdfe9778: Download complete\\n1f706d9b369a: Verifying Checksum\\n1f706d9b369a: Download complete\\n1ab2bdfe9778: Pull complete\\n5988e7f62200: Verifying Checksum\\n5988e7f62200: Download complete\\n8c572c1c1101: Verifying Checksum\\n8c572c1c1101: Download complete\\ndd7d28bd8be5: Pull complete\\naf998e3a361b: Pull complete\\n1f706d9b369a: Pull complete\\ne5000b763efd: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# use a custom Docker image\n", + "from azureml.core.container_registry import ContainerRegistry\n", + "\n", + "image_name = docker_image_name\n", + "\n", + "# you can also point to an image in a private ACR\n", + "image_registry_details = ContainerRegistry()\n", + "image_registry_details.address = docker_repo_name\n", + "image_registry_details.username = os.getenv('ACR_USERNAME')\n", + "image_registry_details.password = os.getenv('ACR_PASSWORD') \n", + "\n", + "# don't let the system build a new conda environment\n", + "user_managed_dependencies = True\n", + "\n", + "# submit to a local Docker container. if you don't have Docker engine running locally, you can set compute_target to cpu_cluster.\n", + "script_params = {\n", + " '--output_folder': 'some_folder'\n", + "}\n", + "\n", + "\n", + "# distributed_training_conf = MpiConfiguration()\n", + "# distributed_training_conf.process_count_per_node = 2\n", + "\n", + "est = Estimator(source_directory=script_path, \n", + " compute_target=gpu_cluster,#'local', #gpu_cluster, \n", + " entry_script=azureml_training_script_file,\n", + " script_params=script_params,\n", + " use_docker=True,\n", + " custom_docker_image=image_name,\n", + " # uncomment below line to use your private ACR\n", + " image_registry_details=image_registry_details, \n", + " user_managed=user_managed_dependencies,\n", + " distributed_training=None,\n", + " node_count=1\n", + " )\n", + "est.run_config.environment.python.interpreter_path = python_path_in_docker_image\n", + "\n", + "run = exp.submit(est)\n", + "RunDetails(run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One can use the above link to currrent experiment run in Azure Portal to see tracked metrics, and images and output notebooks saved by azureml_training_script_full_file_name in {run_dir}/outputs on the remote compute target that are automatically saved by AzureML in the run history Azure portal pages." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "response = run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final print 6, time 13.138 seconds: Counter({'Completed': 1})\r" + ] + } + ], + "source": [ + "import time\n", + "from collections import Counter\n", + "#wait till all jobs finished\n", + "\n", + "def wait_for_run_list_to_finish(the_run_list):\n", + " finished_status_list = ['Completed', 'Failed']\n", + " printing_counter = 0\n", + " start_time = time.time()\n", + " while (not all((crt_queried_job.get_status() in finished_status_list) for crt_queried_job in the_run_list)):\n", + " time.sleep(2)\n", + " printing_counter+= 1\n", + " print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\")\n", + "# final status\n", + " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", + "wait_for_run_list_to_finish([run])" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "run_duration in seconds 241.125075\n", + "run_duration= 4m 1.125s\n" + ] + } + ], + "source": [ + "import datetime, math\n", + "def get_run_duration(azureml_exp_run):\n", + " run_details = azureml_exp_run.get_details()\n", + " run_duration = datetime.datetime.strptime(run_details['endTimeUtc'], \"%Y-%m-%dT%H:%M:%S.%fZ\") - \\\n", + " datetime.datetime.strptime(run_details['startTimeUtc'], \"%Y-%m-%dT%H:%M:%S.%fZ\")\n", + " return run_duration.total_seconds()\n", + "run_duration = get_run_duration(run)\n", + "\n", + "run_seconds, run_minutes = math.modf(run_duration/60)\n", + "print('run_duration in seconds {}'.format(run_duration))\n", + "print('run_duration= {0:.0f}m {1:.3f}s'.format(run_minutes, run_seconds*60))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Showing details for run 15\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "78a6832f42fc4bb1bece76520e305f54", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'NOTSET',…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/aml.mini.widget.v1": "{\"status\": \"Queued\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575482070_cc2fb88e?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575482070_cc2fb88e\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575482070_cc2fb88e\", \"created_utc\": \"2019-12-04T17:54:31.673448Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":0,\\\"RunningNodeCount\\\":10,\\\"CurrentNodeCount\\\":10}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Queued\", \"log_files\": {}, \"log_groups\": [], \"run_duration\": \"0:05:16\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"Your job is submitted in Azure cloud and we are monitoring to get logs...\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Counter16: submission of job 16 on 10 nodes took 9.082231044769287 seconds \n", + "run list length 16\n", + "Counter17: submission of job 17 on 10 nodes took 9.493095874786377 seconds \n", + "run list length 17\n", + "Counter18: submission of job 18 on 10 nodes took 11.00885009765625 seconds \n", + "run list length 18\n", + "Counter19: submission of job 19 on 10 nodes took 8.174627780914307 seconds \n", + "run list length 19\n" + ] + } + ], + "source": [ + "import time\n", + "from IPython.display import clear_output\n", + "\n", + "no_of_jobs = 20\n", + "no_of_nodes = 10\n", + "\n", + "job_counter = 0\n", + "print_cycle = 7\n", + "run_list = []\n", + "submit_time_list = []\n", + "for crt_nodes in range(no_of_nodes, (no_of_nodes+1)):\n", + " gpu_cluster.update(min_nodes=0, max_nodes=crt_nodes, idle_seconds_before_scaledown=1200)\n", + " clust_start_time = time.time()\n", + " for crt_job in range(1, no_of_jobs):\n", + " job_counter+= 1\n", + " start_time = time.time()\n", + " run = exp.submit(est)\n", + " end_time = time.time()\n", + " run_time = end_time - start_time\n", + " run_list.append(run)\n", + " submit_time_list.append(run_time)\n", + " print('Counter{}: submission of job {} on {} nodes took {} seconds '.format(job_counter, crt_job, crt_nodes, run_time))\n", + " print('run list length {}'.format(len(run_list)))\n", + " if ((job_counter-1) % print_cycle) == 0:\n", + " clear_output()\n", + " print('Showing details for run {}'.format(job_counter))\n", + " RunDetails(run).show()\n", + "# [all_jobs_done = True if (('Completed'==crt_queried_job.get_status()) for crt_queried_job in run_list)]" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 9.56427002, 10.400774 , 9.58173323, 10.15340447, 11.36087322,\n", + " 12.94680119, 10.28511715, 9.50729609, 10.37926507, 8.8516674 ,\n", + " 8.78578663, 10.88563776, 10.30812001, 8.43333387, 8.51853824,\n", + " 9.08223104, 9.49309587, 11.0088501 , 8.17462778])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "(array([0, 0, 0, 0, 1, 2, 3, 2, 2]),\n", + " array([ 6. , 6.44444444, 6.88888889, 7.33333333, 7.77777778,\n", + " 8.22222222, 8.66666667, 9.11111111, 9.55555556, 10. ]))" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "np.asarray(submit_time_list)\n", + "np.histogram(np.asarray(submit_time_list), bins=np.linspace(6.0, 10.0, num=10), density=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final print 130, time 478.619 seconds: Counter({'Completed': 19})izing': 1})'Running': 1})Queued': 1})\r" + ] + } + ], + "source": [ + "def wait_for_run_list_to_finish(the_run_list, plot_results=True):\n", + " finished_status_list = ['Completed', 'Failed']\n", + " printing_counter = 0\n", + " start_time = time.time()\n", + " while (not all((crt_queried_job.get_status() in finished_status_list) for crt_queried_job in the_run_list)):\n", + " time.sleep(2)\n", + " printing_counter+= 1\n", + " crt_status = Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list])\n", + " print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(crt_status)), end=\"\\r\")\n", + " if plot_results:\n", + "# import numpy as np\n", + " import matplotlib.pyplot as plt\n", + " plt.bar(crt_status.keys(), crt_status.values())\n", + " plt.show()\n", + " \n", + "# indexes = np.arange(len(labels))\n", + "# width = 1\n", + "\n", + "# plt.bar(indexes, values, width)\n", + "# plt.xticks(indexes + width * 0.5, labels)\n", + "# plt.show()\n", + "\n", + "# from pandas import Series\n", + "# crt_status = Series([crt_queried_job.get_status() for crt_queried_job in the_run_list])\n", + "# status_counts = crt_status.value_counts().sort_index()\n", + "# print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + "# str(status_counts)), end=\"\\r\")\n", + "# final status\n", + " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", + "\n", + " \n", + " \n", + "wait_for_run_list_to_finish(run_list, plot_results=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "run_durations = [get_run_duration(crt_queried_job) for crt_queried_job in run_list]\n", + "run_statuses = [crt_queried_job.get_status() for crt_queried_job in run_list]" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([18, 0, 12, 4, 13, 17, 16, 1, 14, 15, 9, 10, 2, 8, 3, 5, 7,\n", + " 11, 6])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 95.714705 97.874433 101.482868 104.059911 104.543689 104.735191\n", + " 109.463122 109.99054 110.348304 111.175483 116.1721 237.121394\n", + " 238.584713 239.419802 239.850993 242.752749 243.522224 246.953329\n", + " 250.967768]\n", + "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed']\n" + ] + }, + { + "data": { + "text/plain": [ + "array([18, 0, 12, 4, 13, 17, 16, 1, 14, 15, 9, 10, 2, 8, 3, 5, 7,\n", + " 11, 6])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 95.714705 97.874433 101.482868 104.059911 104.543689 104.735191\n", + " 109.463122 109.99054 110.348304 111.175483 116.1721 237.121394\n", + " 238.584713 239.419802 239.850993 242.752749 243.522224 246.953329\n", + " 250.967768]\n", + "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed']\n" + ] + }, + { + "data": { + "text/plain": [ + "(array([0, 0, 2, 9, 0, 0, 0, 0, 0]),\n", + " array([ 50. , 66.66666667, 83.33333333, 100. ,\n", + " 116.66666667, 133.33333333, 150. , 166.66666667,\n", + " 183.33333333, 200. ]))" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_durations = np.asarray(run_durations)\n", + "run_statuses = np.asarray(run_statuses)\n", + "\n", + "extreme_k = 20\n", + "#longest runs\n", + "indices = np.argsort(run_durations)[-extreme_k:]\n", + "indices\n", + "print(run_durations[indices])\n", + "print(run_statuses[indices])\n", + "#shortest runs\n", + "indices = np.argsort(run_durations)[0:extreme_k]\n", + "indices\n", + "print(run_durations[indices])\n", + "print(run_statuses[indices])\n", + "\n", + "#run_durations histogram - counts and bins\n", + "np.histogram(run_durations, bins=np.linspace(50, 200, num=10), density=False)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finished running 030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito!\n" + ] + } + ], + "source": [ + "print('Finished running 030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito!')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From d4aa098a5ce5af819e833864b0b1e626e686753c Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 4 Dec 2019 19:59:32 +0000 Subject: [PATCH 115/207] removed reference to imaging --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 2404f554..344c5a1d 100644 --- a/README.md +++ b/README.md @@ -229,14 +229,6 @@ The dataset was split 70% training, 10% validation and 20% test. The results bel - [kill_windows.sh](scripts/kill_windows.sh): Script to kill multiple tmux windows. Used to kill jobs that parallel_training.sh might have started. -## Seismic Imaging -For seismic imaging, the repository shows how you can leverage open-source PDE solvers (e.g. Devito), and perform Full-Waveform Inversion (FWI) at scale on Azure, using Azure Machine Learning (Azure ML), and Azure Batch. The repository provides a collection of sample notebooks that shows - -* How you can create customized Docker containers with Devito and use this on Azure -* How you can create Azure ML estimators for performing FWI using Devito. -This enable the Devito code to easily run on a single machine, as well as multiple machines using Azure ML managed computes. - - ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. From 5d4052b1d41ce110bb2059ce36b58f5ac268f4c4 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 4 Dec 2019 20:00:57 +0000 Subject: [PATCH 116/207] minor md formatting --- contrib/fwi/azureml_devito/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contrib/fwi/azureml_devito/README.md b/contrib/fwi/azureml_devito/README.md index 1be375db..d6bff9f4 100755 --- a/contrib/fwi/azureml_devito/README.md +++ b/contrib/fwi/azureml_devito/README.md @@ -40,9 +40,11 @@ The devito fwi examples are run in AzuremL using 4 notebooks: ```Devito``` fwi computation artifacts (images and notebooks with data processing output results) are tracked under the AzureML workspace, and can be later downloaded and visualized. - Two ways of running devito code are shown: - (1) using __custom code__ (slightly modified graphing functions that save images to files). The AzureML experimentation job is defined by the devito code packaged as a py file. The experimentation job (defined by [azureml.core.experiment.Experiment](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.experiment.experiment?view=azure-ml-py) class can be used to track metrics or other artifacts (images) that are available in Azure portal. - (2) using [__papermill__](https://github.com/nteract/papermill) invoked via its Python API to run unedited devito demo notebooks (including the [dask](https://dask.org/) local cluster [example](https://github.com/opesci/devito/blob/master/examples/seismic/tutorials/04_dask.ipynb) on the remote compute target and the results as saved notebooks that are available in Azure portal. + Two ways of running devito code are shown: + (1) using __custom code__ (slightly modified graphing functions that save images to files). The AzureML experimentation job is defined by the devito code packaged as a py file. The experimentation job (defined by [azureml.core.experiment.Experiment](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.experiment.experiment?view=azure-ml-py) class can be used to track metrics or other artifacts (images) that are available in Azure portal. + + (2) using [__papermill__](https://github.com/nteract/papermill) invoked via its Python API to run unedited devito demo notebooks (including the [dask](https://dask.org/) local cluster [example](https://github.com/opesci/devito/blob/master/examples/seismic/tutorials/04_dask.ipynb) on the remote compute target and the results as saved notebooks that are available in Azure portal. + - ```030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb```: shows how the devito fwi tutorial notebooks can be run in parallel on the elastically allocated AzureML [remote compute cluster](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets) created before. By submitting multiple jobs via azureml.core.Experiment submit(azureml.train.estimator.Estimator) one can use the [portal](https://portal.azure.com) to visualize the elastic allocation of AzureML [remote compute cluster](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets) nodes. From 71acb888125ea1c4117c4f026835f85539a69386 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 4 Dec 2019 20:01:45 +0000 Subject: [PATCH 117/207] minor md formatting --- contrib/fwi/azureml_devito/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/fwi/azureml_devito/README.md b/contrib/fwi/azureml_devito/README.md index d6bff9f4..af99ddc7 100755 --- a/contrib/fwi/azureml_devito/README.md +++ b/contrib/fwi/azureml_devito/README.md @@ -41,6 +41,7 @@ The devito fwi examples are run in AzuremL using 4 notebooks: ```Devito``` fwi computation artifacts (images and notebooks with data processing output results) are tracked under the AzureML workspace, and can be later downloaded and visualized. Two ways of running devito code are shown: + (1) using __custom code__ (slightly modified graphing functions that save images to files). The AzureML experimentation job is defined by the devito code packaged as a py file. The experimentation job (defined by [azureml.core.experiment.Experiment](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.experiment.experiment?view=azure-ml-py) class can be used to track metrics or other artifacts (images) that are available in Azure portal. (2) using [__papermill__](https://github.com/nteract/papermill) invoked via its Python API to run unedited devito demo notebooks (including the [dask](https://dask.org/) local cluster [example](https://github.com/opesci/devito/blob/master/examples/seismic/tutorials/04_dask.ipynb) on the remote compute target and the results as saved notebooks that are available in Azure portal. From 150c61442fffc76d02d9b784525d68136c53f7d5 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 4 Dec 2019 17:16:41 -0500 Subject: [PATCH 118/207] https://github.com/microsoft/DeepSeismic/issues/71 (#80) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * merge upstream into my fork (#1) * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme * sdk 1.0.76; tested conda env vs docker image; extented readme * removed reference to imaging * minor md formatting * minor md formatting --- README.md | 8 - contrib/README.md | 5 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1132 ----------------- ...zureml_buildexperimentationdockerimage.log | 216 ---- contrib/fwi/azureml_devito/README.md | 51 + .../fwi_dev_conda_environment.yml | 16 + ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 151 ++- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1064 ++++++++++++++++ ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 1003 +++++++++++++++ ...GeophysicsTutorial_FWI_Azure_devito.ipynb} | 370 +++++- 10 files changed, 2603 insertions(+), 1413 deletions(-) delete mode 100755 contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb delete mode 100644 contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log create mode 100755 contrib/fwi/azureml_devito/README.md create mode 100755 contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml rename contrib/{examples/imaging => fwi}/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb (83%) create mode 100755 contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb create mode 100755 contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb rename contrib/{examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb => fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb} (63%) diff --git a/README.md b/README.md index 2404f554..344c5a1d 100644 --- a/README.md +++ b/README.md @@ -229,14 +229,6 @@ The dataset was split 70% training, 10% validation and 20% test. The results bel - [kill_windows.sh](scripts/kill_windows.sh): Script to kill multiple tmux windows. Used to kill jobs that parallel_training.sh might have started. -## Seismic Imaging -For seismic imaging, the repository shows how you can leverage open-source PDE solvers (e.g. Devito), and perform Full-Waveform Inversion (FWI) at scale on Azure, using Azure Machine Learning (Azure ML), and Azure Batch. The repository provides a collection of sample notebooks that shows - -* How you can create customized Docker containers with Devito and use this on Azure -* How you can create Azure ML estimators for performing FWI using Devito. -This enable the Devito code to easily run on a single machine, as well as multiple machines using Azure ML managed computes. - - ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. diff --git a/contrib/README.md b/contrib/README.md index cd6e0024..a286b0f3 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -1,7 +1,8 @@ -Contrib folder +### Contrib folder + Code in this folder has not been tested, and are meant for exploratory work only. -We encourage submissiosn to the contrib folder, and once it is well-tested, do submit a pull request and work with the repository owners to graduate it to the main DeepSeismic repository. +We encourage submissions to the contrib folder, and once they are well-tested, do submit a pull request and work with the repository owners to graduate it to the main DeepSeismic repository. Thank you. diff --git a/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb deleted file mode 100755 index 07c16572..00000000 --- a/contrib/examples/imaging/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ /dev/null @@ -1,1132 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# FWI in Azure project\n", - "\n", - "## Create Experimentation Docker image\n", - "\n", - "FWI demo based on: \n", - "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", - "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", - "\n", - "\n", - "\n", - "In this notebook we create a custom docker image that will be used to run the devito demo notebooks in AzureML. \n", - "\n", - " - We transparently create a docker file, a conda environment .yml file, build the docker image and push it into dockerhub. Azure ACR could also be used for storing docker images. \n", - " - The conda environment .yml file lists conda and pip installs, and separates all python dependencies from the docker installs. \n", - " - The dockerfile is generic. The only AzureML depedency is azureml-sdk pip installable package in conda environment .yml file\n", - " - The created docer image will be run in following notebook in a container on the local AzureVM or on a remote AzureML compute cluster. This AzureML pattern decouples experimentation (or training) job definition (experimentation script, data location, dependencies and docker image) happening on the control plane machine that runs this notebook, from the elastically allocated and Azure managed VM/cluster that does the actual training/experimentation computation.\n", - " \n", - "\n", - "User input requiring steps:\n", - " - [Fill in and save docker image name settings, if needed. ](#docker_image_settings)\n", - " - [Update DOCKER_CONTAINER_MOUNT_POINT to match our local path](#docker_image_settings)\n", - " - [Set docker build and test flags](#docker_build_test_settings) \n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Allow multiple displays per cell\n", - "from IPython.core.interactiveshell import InteractiveShell\n", - "InteractiveShell.ast_node_interactivity = \"all\" " - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import sys, os\n", - "import shutil\n", - "import urllib\n", - "\n", - "import platform\n", - "import math\n", - "import docker" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Linux-4.15.0-1061-azure-x86_64-with-debian-10.0'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "platform.platform()\n", - "os.getcwd()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "#### Setup docker image build and test process. \n", - " - devito tests take abou 15 mins (981.41 seconds). When running this notebook for first time make:\n", - " > docker_build_no_cache = '--no-cache' \n", - " > docker_test_run_devito_tests = True\n", - " \n", - "[Back](#user_input_requiring_steps) to summary of user input requiring steps." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "docker_build_no_cache = '' # '--no-cache' # or '' #\n", - "docker_test_run_devito_tests = False # True # False\n", - "\n", - "# azureml_sdk_version set here must match azureml sdk version pinned in conda env file written to conda_common_file_path below\n", - "azureml_sdk_version = '1.0.69' " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### Import utilities functions" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[None]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def add_path_to_sys_path(path_to_append):\n", - " if not (any(path_to_append in paths for paths in sys.path)):\n", - " sys.path.append(path_to_append)\n", - " \n", - "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", - "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", - "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", - "\n", - "import project_utils\n", - "prj_consts = project_utils.project_consts()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### Create experimentation docker file" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'./../not_shared/general.env'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", - "dotenv_file_path" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/workspace/examples/imaging/azureml_devito/notebooks\r\n" - ] - } - ], - "source": [ - "!pwd" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "##### Input here docker image settings \n", - "in cell below we use [dotenv](https://github.com/theskumar/python-dotenv) to overwrite docker image properties already save in dotenv_file_path. Change as needed, e.g. update azureml_sdk version if using a different version.\n", - "\n", - "[Back](#user_input_requiring_steps) to summary of user input requiring steps." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(True, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', 'sdk.v1.0.69')" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "(True, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "(True,\n", - " 'DOCKER_CONTAINER_MOUNT_POINT',\n", - " '/datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks')" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# SDK changes often, so we'll keep its version transparent \n", - "import dotenv\n", - "\n", - "# EXPERIMENTATION_IMAGE_VERSION should:\n", - "# - match sdk version in fwi01_conda_env01 environmnet in conda_env_fwi01_azureml_sdk.v1.0.XX.yml file below\n", - "# - match the conda env yml file name, e.g. conda_env_fwi01_azureml_sdk.v1.0.62.yml referenced in \n", - "# Dockerfile_fwi01_azureml_sdk.v1.0.62\n", - "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', ('sdk.v'+azureml_sdk_version))\n", - "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')\n", - "\n", - "dotenv.set_key(dotenv_file_path, 'DOCKER_CONTAINER_MOUNT_POINT', '/datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks')" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'conda_env_fwi01_azureml_sdk.v1.0.69.yml'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.69.yml'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.69'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%reload_ext dotenv\n", - "%dotenv $dotenv_file_path\n", - "\n", - "docker_file_location = os.path.join(*(prj_consts.AML_EXPERIMENT_DIR + ['docker_build']))\n", - "\n", - "docker_file_name = 'Dockerfile'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", - "\n", - "conda_dependency_file_name = 'conda_env'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", - "conda_dependency_common_file_name = conda_dependency_file_name\n", - "\n", - "devito_conda_dependency_file_name = 'devito_conda_env'+'.yml'\n", - "\n", - "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", - "docker_image_name = docker_repo_name + '/' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", - "\n", - "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", - "if image_version!=\"\":\n", - " docker_file_name = docker_file_name +'_'+ image_version\n", - " conda_dependency_file_name = conda_dependency_file_name+'_'+ image_version\n", - " docker_image_name = docker_image_name +':'+ image_version\n", - "conda_dependency_file_name=conda_dependency_file_name+'.yml'\n", - "conda_dependency_common_file_name = conda_dependency_common_file_name+'.yml'\n", - "\n", - "docker_file_dir = os.path.join(*([os.getcwd(), docker_file_location]))\n", - "os.makedirs(docker_file_dir, exist_ok=True)\n", - "docker_file_path = os.path.join(*([docker_file_dir]+[docker_file_name]))\n", - "conda_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_file_name]))\n", - "conda_common_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_common_file_name]))\n", - "\n", - "docker_image_name\n", - "\n", - "conda_dependency_file_name\n", - "conda_file_path\n", - "conda_common_file_path\n", - "\n", - "docker_file_dir\n", - "docker_file_path" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" - ] - } - ], - "source": [ - "%%writefile $conda_common_file_path\n", - "name: fwi01_conda_env01\n", - " \n", - "#https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.13.1-cp37-cp37m-linux_x86_64.whl \n", - "# https://github.com/dask/dask-tutorial\n", - "\n", - "channels:\n", - " - anaconda\n", - " - conda-forge\n", - "dependencies:\n", - " - python=3.6 # 3.6 req by tf, not 3.7.2 \n", - " - dask\n", - " - distributed\n", - " - h5py\n", - " - matplotlib\n", - " - nb_conda\n", - " - notebook \n", - " - numpy \n", - " - pandas\n", - " - pip\n", - " - py-cpuinfo # all required by devito or dask-tutorial\n", - " - pytables\n", - " - python-graphviz\n", - " - requests>=2.19.1\n", - " - pillow\n", - " - scipy\n", - " - snakeviz\n", - " - scikit-image\n", - " - toolz\n", - " - pip:\n", - " - anytree # required by devito\n", - " - azureml-sdk[notebooks,automl]==1.0.69\n", - " - codepy # required by devito\n", - " - papermill[azure]\n", - " - pyrevolve # required by devito" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.69\n" - ] - } - ], - "source": [ - "%%writefile $docker_file_path \n", - "\n", - "FROM continuumio/miniconda3:4.7.10 \n", - "MAINTAINER George Iordanescu \n", - "\n", - "RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \\\n", - " gcc g++ \\\n", - " wget bzip2 \\\n", - " curl \\\n", - " git make \\\n", - " mpich \\ \n", - " libmpich-dev && \\\n", - " apt-get clean && \\\n", - " rm -rf /var/lib/apt/lists/*\n", - "\n", - "ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml.yml\n", - "ADD $CONDA_ENV_FILE_NAME /tmp/$CONDA_ENV_FILE_NAME\n", - "ENV CONDA_DIR /opt/conda\n", - "ENV CONDA_ENV_NAME fwi01_conda_env\n", - "\n", - "RUN git clone https://github.com/opesci/devito.git && \\\n", - " cd devito && \\\n", - " /opt/conda/bin/conda env create -q --name $CONDA_ENV_NAME -f environment.yml && \\\n", - " pip install -e . \n", - " \n", - "ENV CONDA_AUTO_UPDATE_CONDA=false\n", - "ENV CONDA_DEFAULT_ENV=$CONDA_ENV_NAME\n", - "ENV CONDA_PREFIX=$CONDA_DIR/envs/$CONDA_DEFAULT_ENV\n", - "ENV PATH=$CONDA_PREFIX/bin:/opt/conda/bin:$PATH \n", - "\n", - "RUN /opt/conda/bin/conda env update --name $CONDA_ENV_NAME -f /tmp/$CONDA_ENV_FILE_NAME && \\\n", - " /opt/conda/bin/conda clean --yes --all\n", - "\n", - "ENV PYTHONPATH=$PYTHONPATH:devito/app\n", - "\n", - "# WORKDIR /devito \n", - " \n", - "CMD /bin/bash" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.69.yml'" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "total 36\r\n", - "-rw-r--r-- 1 root root 1073 Oct 30 05:29 Dockerfile_fwi01_azureml_sdk.v1.0.60\r\n", - "-rw-r--r-- 1 root root 1098 Sep 26 19:04 Dockerfile_fwi01_azureml_sdk.v1.0.62\r\n", - "-rw-r--r-- 1 root root 1085 Oct 24 01:05 Dockerfile_fwi01_azureml_sdk.v1.0.65\r\n", - "-rw-r--r-- 1 root root 1073 Nov 3 14:43 Dockerfile_fwi01_azureml_sdk.v1.0.69\r\n", - "-rw-r--r-- 1 root root 733 Nov 3 14:43 conda_env_fwi01_azureml.yml\r\n", - "-rw-r--r-- 1 root root 733 Oct 30 05:29 conda_env_fwi01_azureml_sdk.v1.0.60.yml\r\n", - "-rw-r--r-- 1 root root 713 Sep 26 19:04 conda_env_fwi01_azureml_sdk.v1.0.62.yml\r\n", - "-rw-r--r-- 1 root root 733 Oct 24 01:05 conda_env_fwi01_azureml_sdk.v1.0.65.yml\r\n", - "-rw-r--r-- 1 root root 733 Nov 3 14:43 conda_env_fwi01_azureml_sdk.v1.0.69.yml\r\n" - ] - } - ], - "source": [ - "shutil.copyfile(conda_common_file_path, conda_file_path)\n", - "\n", - "! ls -l $docker_file_dir" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69 -f /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.69 /workspace/examples/imaging/azureml_devito/notebooks/./../temp/docker_build '" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "['Sending build context to Docker daemon 16.9kB',\n", - " '',\n", - " 'Step 1/15 : FROM continuumio/miniconda3:4.7.10',\n", - " ' ---> 4a51de2367be',\n", - " 'Step 2/15 : MAINTAINER George Iordanescu ']" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "['Step 15/15 : CMD /bin/bash',\n", - " ' ---> Using cache',\n", - " ' ---> 3dc3d5d871a4',\n", - " 'Successfully built 3dc3d5d871a4',\n", - " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69']" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cli_command='docker build -t '+ docker_image_name + \\\n", - "' -f ' + docker_file_path + \\\n", - "' ' + docker_file_dir + ' ' +\\\n", - "docker_build_no_cache #'' #' --no-cache'\n", - "\n", - "\n", - "cli_command\n", - "docker_build_response = ! $cli_command\n", - "\n", - "docker_build_response[0:5] \n", - "docker_build_response[-5:] " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Docker containers can be run using python docker sdk" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "'bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": [ - "b'/\\n1.0.69\\n'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "docker_image_name\n", - "\n", - "sh_command='bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'\n", - "sh_command\n", - "client = docker.from_env()\n", - "client.containers.run(docker_image_name, \n", - " remove=True,\n", - " volumes={os.getenv('DOCKER_CONTAINER_MOUNT_POINT'): {'bind': '/workspace', 'mode': 'rw'}},\n", - " working_dir='/',\n", - " command=sh_command)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Docker containers can also be run in cli \n", - "\n", - "Here we also create a log file to capture commands execution in container. If flag docker_test_run_devito_tests is True, we run \n", - "and capture test commands output. Tests take abou 15 minutes to run. If flag docker_test_run_devito_tests is False, we show the results of a previous session. " - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'./fwi01_azureml_buildexperimentationdockerimage.log'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "fwi01_log_file = os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log']))\n", - "fwi01_log_file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Create command for running devito tests, capture output in a log file, save log file outside container" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "''" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "if docker_test_run_devito_tests:\n", - " run_devito_tests_command = ' python -m pytest tests/ ' + \\\n", - "'> ' + fwi01_log_file +' 2>&1; ' + \\\n", - "' mv ' + fwi01_log_file + ' /workspace/' \n", - " \n", - " with open(os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log'])), \"w\") as crt_log_file:\n", - " print('Before running e13n container... ', file=crt_log_file)\n", - " print('\\ncontent of devito tests log file before testing:')\n", - " !cat $fwi01_log_file\n", - "else:\n", - " run_devito_tests_command = '' \n", - "\n", - "# run_devito_tests_command = 'ls -l > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'\n", - "run_devito_tests_command" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/examples/imaging/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; \"'" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "# conda environments:\n", - "#\n", - "base /opt/conda\n", - "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", - "\n", - "total 536\n", - "-rw-r--r-- 1 root root 11521 Oct 30 05:30 conftest.py\n", - "-rw-r--r-- 1 root root 6006 Oct 30 05:30 test_adjoint.py\n", - "-rw-r--r-- 1 root root 13882 Oct 30 05:30 test_autotuner.py\n", - "-rw-r--r-- 1 root root 5902 Oct 30 05:30 test_builtins.py\n", - "-rw-r--r-- 1 root root 21885 Oct 30 05:30 test_caching.py\n", - "-rw-r--r-- 1 root root 9721 Oct 30 05:30 test_checkpointing.py\n", - "-rw-r--r-- 1 root root 1095 Oct 30 05:30 test_constant.py\n", - "-rw-r--r-- 1 root root 54642 Oct 30 05:30 test_data.py\n", - "-rw-r--r-- 1 root root 481 Oct 30 05:30 test_dependency_bugs.py\n", - "-rw-r--r-- 1 root root 16506 Oct 30 05:30 test_derivatives.py\n", - "-rw-r--r-- 1 root root 1473 Oct 30 05:30 test_differentiable.py\n", - "-rw-r--r-- 1 root root 30846 Oct 30 05:30 test_dimension.py\n", - "-rw-r--r-- 1 root root 21233 Oct 30 05:30 test_dle.py\n", - "-rw-r--r-- 1 root root 1169 Oct 30 05:30 test_docstrings.py\n", - "-rw-r--r-- 1 root root 26928 Oct 30 05:30 test_dse.py\n", - "-rw-r--r-- 1 root root 8205 Oct 30 05:30 test_gradient.py\n", - "-rw-r--r-- 1 root root 15227 Oct 30 05:30 test_interpolation.py\n", - "-rw-r--r-- 1 root root 31514 Oct 30 05:30 test_ir.py\n", - "-rw-r--r-- 1 root root 62102 Oct 30 05:30 test_mpi.py\n", - "-rw-r--r-- 1 root root 61208 Oct 30 05:30 test_operator.py\n", - "-rw-r--r-- 1 root root 13006 Oct 30 05:30 test_ops.py\n", - "-rw-r--r-- 1 root root 12228 Oct 30 05:30 test_pickle.py\n", - "-rw-r--r-- 1 root root 1809 Oct 30 05:30 test_resample.py\n", - "-rw-r--r-- 1 root root 1754 Oct 30 05:30 test_save.py\n", - "-rw-r--r-- 1 root root 5711 Oct 30 05:30 test_subdomains.py\n", - "-rw-r--r-- 1 root root 3320 Oct 30 05:30 test_symbolic_coefficients.py\n", - "-rw-r--r-- 1 root root 3186 Oct 30 05:30 test_timestepping.py\n", - "-rw-r--r-- 1 root root 603 Oct 30 05:30 test_tools.py\n", - "-rw-r--r-- 1 root root 3296 Oct 30 05:30 test_tti.py\n", - "-rw-r--r-- 1 root root 8835 Oct 30 05:30 test_visitors.py\n", - "-rw-r--r-- 1 root root 21800 Oct 30 05:30 test_yask.py\n", - "1.0.69\n", - "\n", - "content of devito tests log file after testing:\n", - "============================= test session starts ==============================\n", - "platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0\n", - "rootdir: /devito, inifile: setup.cfg\n", - "plugins: nbval-0.9.3, cov-2.7.1\n", - "collected 912 items / 2 skipped / 910 selected\n", - "\n", - "tests/test_adjoint.py .......................... [ 2%]\n", - "tests/test_autotuner.py ..........s.... [ 4%]\n", - "tests/test_builtins.py ....s.s.s [ 5%]\n", - "tests/test_caching.py .................................. [ 9%]\n", - "tests/test_checkpointing.py ....... [ 9%]\n", - "tests/test_constant.py . [ 10%]\n", - "tests/test_data.py ..........................sssssssssssssss.ss.. [ 15%]\n", - "tests/test_dependency_bugs.py . [ 15%]\n", - "tests/test_derivatives.py .............................................. [ 20%]\n", - "........................................................................ [ 28%]\n", - ".........................................................F...F.......... [ 36%]\n", - "...... [ 36%]\n", - "tests/test_differentiable.py .. [ 36%]\n", - "tests/test_dimension.py ............................... [ 40%]\n", - "tests/test_dle.py ...................................................... [ 46%]\n", - "......................................... [ 50%]\n", - "tests/test_docstrings.py ................ [ 52%]\n", - "tests/test_dse.py ......x............................................... [ 58%]\n", - ".......................s.... [ 61%]\n", - "tests/test_gradient.py .... [ 61%]\n", - "tests/test_interpolation.py ........................ [ 64%]\n", - "tests/test_ir.py ....................................................... [ 70%]\n", - "................ [ 72%]\n", - "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 78%]\n", - "ss [ 78%]\n", - "tests/test_operator.py ................................................. [ 83%]\n", - "........................................................................ [ 91%]\n", - ".................. [ 93%]\n", - "tests/test_pickle.py .................ss. [ 95%]\n", - "tests/test_resample.py . [ 96%]\n", - "tests/test_save.py .. [ 96%]\n", - "tests/test_subdomains.py ... [ 96%]\n", - "tests/test_symbolic_coefficients.py .....F [ 97%]\n", - "tests/test_timestepping.py ....... [ 98%]\n", - "tests/test_tools.py ..... [ 98%]\n", - "tests/test_tti.py .... [ 99%]\n", - "tests/test_visitors.py ......... [100%]\n", - "\n", - "=================================== FAILURES ===================================\n", - "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________\n", - "\n", - "self = , so = 12, ndim = 1\n", - "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", - "\n", - " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", - " @pytest.mark.parametrize('ndim', [1, 2])\n", - " @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [\n", - " ('dx', 'dx', -1),\n", - " ('dx2', 'dx2', 1),\n", - " ('dxl', 'dxr', -1),\n", - " ('dxr', 'dxl', -1)])\n", - " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", - " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", - " x = grid.dimensions[0]\n", - " f = Function(name='f', grid=grid, space_order=so)\n", - " f_deriv = Function(name='f_deriv', grid=grid, space_order=so)\n", - " g = Function(name='g', grid=grid, space_order=so)\n", - " g_deriv = Function(name='g_deriv', grid=grid, space_order=so)\n", - " \n", - " # Fill f and g with smooth cos/sin\n", - " Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply()\n", - " # Check symbolic expression are expected ones for the adjoint .T\n", - " deriv = getattr(f, derivative)\n", - " expected = adjoint_coeff * getattr(f, adjoint_name).evaluate\n", - " assert deriv.T.evaluate == expected\n", - " \n", - " # Compute numerical derivatives and verify dot test\n", - " # i.e = \n", - " \n", - " eq_f = Eq(f_deriv, deriv)\n", - " eq_g = Eq(g_deriv, getattr(g, derivative).T)\n", - " \n", - " op = Operator([eq_f, eq_g])\n", - " op()\n", - " \n", - " a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1))\n", - " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", - "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", - "E assert False\n", - "E + where False = ((1 - (0.22231627 / 0.2223134)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", - "\n", - "tests/test_derivatives.py:394: AssertionError\n", - "----------------------------- Captured stderr call -----------------------------\n", - "Operator `Kernel` run in 0.01 s\n", - "Operator `Kernel` run in 0.01 s\n", - "------------------------------ Captured log call -------------------------------\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________\n", - "\n", - "self = , so = 12, ndim = 2\n", - "derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1\n", - "\n", - " @pytest.mark.parametrize('so', [2, 4, 8, 12])\n", - " @pytest.mark.parametrize('ndim', [1, 2])\n", - " @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [\n", - " ('dx', 'dx', -1),\n", - " ('dx2', 'dx2', 1),\n", - " ('dxl', 'dxr', -1),\n", - " ('dxr', 'dxl', -1)])\n", - " def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff):\n", - " grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim))\n", - " x = grid.dimensions[0]\n", - " f = Function(name='f', grid=grid, space_order=so)\n", - " f_deriv = Function(name='f_deriv', grid=grid, space_order=so)\n", - " g = Function(name='g', grid=grid, space_order=so)\n", - " g_deriv = Function(name='g_deriv', grid=grid, space_order=so)\n", - " \n", - " # Fill f and g with smooth cos/sin\n", - " Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply()\n", - " # Check symbolic expression are expected ones for the adjoint .T\n", - " deriv = getattr(f, derivative)\n", - " expected = adjoint_coeff * getattr(f, adjoint_name).evaluate\n", - " assert deriv.T.evaluate == expected\n", - " \n", - " # Compute numerical derivatives and verify dot test\n", - " # i.e = \n", - " \n", - " eq_f = Eq(f_deriv, deriv)\n", - " eq_g = Eq(g_deriv, getattr(g, derivative).T)\n", - " \n", - " op = Operator([eq_f, eq_g])\n", - " op()\n", - " \n", - " a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1))\n", - " b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1))\n", - "> assert np.isclose(1 - a/b, 0, atol=1e-5)\n", - "E assert False\n", - "E + where False = ((1 - (11.3381815 / 11.338006)), 0, atol=1e-05)\n", - "E + where = np.isclose\n", - "\n", - "tests/test_derivatives.py:394: AssertionError\n", - "----------------------------- Captured stderr call -----------------------------\n", - "Operator `Kernel` run in 0.01 s\n", - "/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c: In function ‘Kernel’:\n", - "/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", - " #pragma omp simd aligned(f,f_deriv,g,g_deriv:32)\n", - " \n", - "Operator `Kernel` run in 0.01 s\n", - "------------------------------ Captured log call -------------------------------\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "______________________ TestSC.test_function_coefficients _______________________\n", - "\n", - "self = \n", - "\n", - " def test_function_coefficients(self):\n", - " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", - " so = 2\n", - " grid = Grid(shape=(4, 4))\n", - " f0 = TimeFunction(name='f0', grid=grid, space_order=so, coefficients='symbolic')\n", - " f1 = TimeFunction(name='f1', grid=grid, space_order=so)\n", - " x, y = grid.dimensions\n", - " \n", - " s = Dimension(name='s')\n", - " ncoeffs = so+1\n", - " \n", - " wshape = list(grid.shape)\n", - " wshape.append(ncoeffs)\n", - " wshape = as_tuple(wshape)\n", - " \n", - " wdims = list(grid.dimensions)\n", - " wdims.append(s)\n", - " wdims = as_tuple(wdims)\n", - " \n", - " w = Function(name='w', dimensions=wdims, shape=wshape)\n", - " w.data[:, :, 0] = -0.5/grid.spacing[0]\n", - " w.data[:, :, 1] = 0.0\n", - " w.data[:, :, 2] = 0.5/grid.spacing[0]\n", - " \n", - " f_x_coeffs = Coefficient(1, f0, x, w)\n", - " \n", - " subs = Substitutions(f_x_coeffs)\n", - " \n", - " eq0 = Eq(f0.dt + f0.dx, 1, coefficients=subs)\n", - " eq1 = Eq(f1.dt + f1.dx, 1)\n", - " \n", - " stencil0 = solve(eq0.evaluate, f0.forward)\n", - " stencil1 = solve(eq1.evaluate, f1.forward)\n", - " \n", - " op0 = Operator(Eq(f0.forward, stencil0))\n", - " op1 = Operator(Eq(f1.forward, stencil1))\n", - " \n", - " op0(time_m=0, time_M=5, dt=1.0)\n", - " op1(time_m=0, time_M=5, dt=1.0)\n", - " \n", - "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", - "E assert Data(False)\n", - "E + where Data(False) = (Data([[[ True, True, True, True],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...True],\\n [ True, True, True, True],\\n [ True, True, True, True],\\n [ True, True, True, True]]]))\n", - "E + where = np.all\n", - "E + and Data([[[ True, True, True, True],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...True],\\n [ True, True, True, True],\\n [ True, True, True, True],\\n [ True, True, True, True]]]) = ((Data([[[-11.4375 , -11.4375 , -11.4375 , -11.4375 ],\\n [ 49.59375, 49.59375, 49.59375, 49.59375],\\n [ -6.... [-24.25 , -24.25 , -24.25 , -24.25 ],\\n [ -1.9375 , -1.9375 , -1.9375 , -1.9375 ]]], dtype=float32) - Data([[[-11.437502 , -11.437502 , -11.437502 , -11.437502 ],\\n [ 49.59374 , 49.59374 , 49.59374 , 49.59374 ....249996 , -24.249996 , -24.249996 ],\\n [ -1.9375012, -1.9375012, -1.9375012, -1.9375012]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", - "E + where = np.isclose\n", - "\n", - "tests/test_symbolic_coefficients.py:96: AssertionError\n", - "----------------------------- Captured stderr call -----------------------------\n", - "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c: In function ‘Kernel’:\n", - "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", - " #pragma omp simd aligned(f0,w:32)\n", - " \n", - "Operator `Kernel` run in 0.01 s\n", - "/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c: In function ‘Kernel’:\n", - "/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", - " #pragma omp simd aligned(f1:32)\n", - " \n", - "Operator `Kernel` run in 0.01 s\n", - "------------------------------ Captured log call -------------------------------\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s\n", - "======= 3 failed, 828 passed, 82 skipped, 1 xfailed in 830.53s (0:13:50) =======\n" - ] - } - ], - "source": [ - "cli_command='docker run -it --rm --name fwi01_azureml_container ' +\\\n", - "' -v '+os.getenv('DOCKER_CONTAINER_MOUNT_POINT')+':/workspace:rw ' + \\\n", - "docker_image_name + \\\n", - "' /bin/bash -c \"conda env list ; ls -l /devito/tests; ' + \\\n", - "'python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; ' + \\\n", - "'cd /devito; ' + \\\n", - "run_devito_tests_command +\\\n", - "' \"'\n", - "\n", - "cli_command\n", - "! $cli_command\n", - "# # ============= 774 passed, 70 skipped, 1 xfailed in 1106.76 seconds =============\n", - "print('\\ncontent of devito tests log file after testing:')\n", - "!cat $fwi01_log_file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "###### Use the ACR created in previous notebook or docker hub to push your image" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'az acr login --name fwi01acr'" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Login Succeeded\r\n", - "WARNING! Your password will be stored unencrypted in /root/.docker/config.json.\r\n", - "Configure a credential helper to remove this warning. See\r\n", - "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", - "\r\n", - "\u001b[0m" - ] - } - ], - "source": [ - "# docker_pwd = os.getenv('DOCKER_PWD')\n", - "# docker_login = os.getenv('DOCKER_LOGIN')\n", - "# !docker login -u=$docker_login -p=$docker_pwd\n", - "# !docker push {docker_image_name}\n", - "\n", - "%dotenv $dotenv_file_path\n", - "cli_command='az acr login --name '+os.getenv('ACR_NAME')\n", - "# print cli command\n", - "cli_command\n", - "\n", - "# run cli command\n", - "cli_command = cli_command+' --username '+os.getenv('ACR_USERNAME') + ' --password ' + os.getenv('ACR_PASSWORD')\n", - "! $cli_command" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cli_command='docker push '+docker_image_name\n", - "cli_command" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", - "\n", - "\u001b[1B8b071694: Preparing \n", - "\u001b[1B5b417f99: Preparing \n", - "\u001b[1Bd24aa1b4: Preparing \n", - "\u001b[1Be41d536b: Preparing \n", - "\u001b[1Bf8fc4c9a: Preparing \n", - "\u001b[1Bba47210e: Preparing \n", - "\u001b[2Bba47210e: Layer already exists \u001b[3A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[Ksdk.v1.0.69: digest: sha256:9acb10b41bf2e750519b303c365870f67a64c059b90d8868ff139e7d54446948 size: 1800\n" - ] - } - ], - "source": [ - "! $cli_command" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!\n" - ] - } - ], - "source": [ - "# !jupyter nbconvert 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito --to html\n", - "print('Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log b/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log deleted file mode 100644 index 22b30a9b..00000000 --- a/contrib/examples/imaging/azureml_devito/notebooks/fwi01_azureml_buildexperimentationdockerimage.log +++ /dev/null @@ -1,216 +0,0 @@ -============================= test session starts ============================== -platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 -rootdir: /devito, inifile: setup.cfg -plugins: nbval-0.9.3, cov-2.7.1 -collected 912 items / 2 skipped / 910 selected - -tests/test_adjoint.py .......................... [ 2%] -tests/test_autotuner.py ..........s.... [ 4%] -tests/test_builtins.py ....s.s.s [ 5%] -tests/test_caching.py .................................. [ 9%] -tests/test_checkpointing.py ....... [ 9%] -tests/test_constant.py . [ 10%] -tests/test_data.py ..........................sssssssssssssss.ss.. [ 15%] -tests/test_dependency_bugs.py . [ 15%] -tests/test_derivatives.py .............................................. [ 20%] -........................................................................ [ 28%] -.........................................................F...F.......... [ 36%] -...... [ 36%] -tests/test_differentiable.py .. [ 36%] -tests/test_dimension.py ............................... [ 40%] -tests/test_dle.py ...................................................... [ 46%] -......................................... [ 50%] -tests/test_docstrings.py ................ [ 52%] -tests/test_dse.py ......x............................................... [ 58%] -.......................s.... [ 61%] -tests/test_gradient.py .... [ 61%] -tests/test_interpolation.py ........................ [ 64%] -tests/test_ir.py ....................................................... [ 70%] -................ [ 72%] -tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 78%] -ss [ 78%] -tests/test_operator.py ................................................. [ 83%] -........................................................................ [ 91%] -.................. [ 93%] -tests/test_pickle.py .................ss. [ 95%] -tests/test_resample.py . [ 96%] -tests/test_save.py .. [ 96%] -tests/test_subdomains.py ... [ 96%] -tests/test_symbolic_coefficients.py .....F [ 97%] -tests/test_timestepping.py ....... [ 98%] -tests/test_tools.py ..... [ 98%] -tests/test_tti.py .... [ 99%] -tests/test_visitors.py ......... [100%] - -=================================== FAILURES =================================== -____________________ TestFD.test_fd_adjoint[dx2-dx2-1-1-12] ____________________ - -self = , so = 12, ndim = 1 -derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 - - @pytest.mark.parametrize('so', [2, 4, 8, 12]) - @pytest.mark.parametrize('ndim', [1, 2]) - @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [ - ('dx', 'dx', -1), - ('dx2', 'dx2', 1), - ('dxl', 'dxr', -1), - ('dxr', 'dxl', -1)]) - def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): - grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) - x = grid.dimensions[0] - f = Function(name='f', grid=grid, space_order=so) - f_deriv = Function(name='f_deriv', grid=grid, space_order=so) - g = Function(name='g', grid=grid, space_order=so) - g_deriv = Function(name='g_deriv', grid=grid, space_order=so) - - # Fill f and g with smooth cos/sin - Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply() - # Check symbolic expression are expected ones for the adjoint .T - deriv = getattr(f, derivative) - expected = adjoint_coeff * getattr(f, adjoint_name).evaluate - assert deriv.T.evaluate == expected - - # Compute numerical derivatives and verify dot test - # i.e = - - eq_f = Eq(f_deriv, deriv) - eq_g = Eq(g_deriv, getattr(g, derivative).T) - - op = Operator([eq_f, eq_g]) - op() - - a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1)) - b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) -> assert np.isclose(1 - a/b, 0, atol=1e-5) -E assert False -E + where False = ((1 - (0.22231627 / 0.2223134)), 0, atol=1e-05) -E + where = np.isclose - -tests/test_derivatives.py:394: AssertionError ------------------------------ Captured stderr call ----------------------------- -Operator `Kernel` run in 0.01 s -Operator `Kernel` run in 0.01 s ------------------------------- Captured log call ------------------------------- -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -____________________ TestFD.test_fd_adjoint[dx2-dx2-1-2-12] ____________________ - -self = , so = 12, ndim = 2 -derivative = 'dx2', adjoint_name = 'dx2', adjoint_coeff = 1 - - @pytest.mark.parametrize('so', [2, 4, 8, 12]) - @pytest.mark.parametrize('ndim', [1, 2]) - @pytest.mark.parametrize('derivative, adjoint_name, adjoint_coeff', [ - ('dx', 'dx', -1), - ('dx2', 'dx2', 1), - ('dxl', 'dxr', -1), - ('dxr', 'dxl', -1)]) - def test_fd_adjoint(self, so, ndim, derivative, adjoint_name, adjoint_coeff): - grid = Grid(shape=tuple([51]*ndim), extent=tuple([25]*ndim)) - x = grid.dimensions[0] - f = Function(name='f', grid=grid, space_order=so) - f_deriv = Function(name='f_deriv', grid=grid, space_order=so) - g = Function(name='g', grid=grid, space_order=so) - g_deriv = Function(name='g_deriv', grid=grid, space_order=so) - - # Fill f and g with smooth cos/sin - Operator([Eq(g, cos(2*np.pi*x/5)), Eq(f, sin(2*np.pi*x/8))]).apply() - # Check symbolic expression are expected ones for the adjoint .T - deriv = getattr(f, derivative) - expected = adjoint_coeff * getattr(f, adjoint_name).evaluate - assert deriv.T.evaluate == expected - - # Compute numerical derivatives and verify dot test - # i.e = - - eq_f = Eq(f_deriv, deriv) - eq_g = Eq(g_deriv, getattr(g, derivative).T) - - op = Operator([eq_f, eq_g]) - op() - - a = np.dot(f_deriv.data.reshape(-1), g.data.reshape(-1)) - b = np.dot(g_deriv.data.reshape(-1), f.data.reshape(-1)) -> assert np.isclose(1 - a/b, 0, atol=1e-5) -E assert False -E + where False = ((1 - (11.3381815 / 11.338006)), 0, atol=1e-05) -E + where = np.isclose - -tests/test_derivatives.py:394: AssertionError ------------------------------ Captured stderr call ----------------------------- -Operator `Kernel` run in 0.01 s -/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c: In function ‘Kernel’: -/tmp/devito-jitcache-uid0/eef4d81cb97672165f9c867b35ecef3116800d37.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas] - #pragma omp simd aligned(f,f_deriv,g,g_deriv:32) - -Operator `Kernel` run in 0.01 s ------------------------------- Captured log call ------------------------------- -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -______________________ TestSC.test_function_coefficients _______________________ - -self = - - def test_function_coefficients(self): - """Test that custom function coefficients return the expected result""" - so = 2 - grid = Grid(shape=(4, 4)) - f0 = TimeFunction(name='f0', grid=grid, space_order=so, coefficients='symbolic') - f1 = TimeFunction(name='f1', grid=grid, space_order=so) - x, y = grid.dimensions - - s = Dimension(name='s') - ncoeffs = so+1 - - wshape = list(grid.shape) - wshape.append(ncoeffs) - wshape = as_tuple(wshape) - - wdims = list(grid.dimensions) - wdims.append(s) - wdims = as_tuple(wdims) - - w = Function(name='w', dimensions=wdims, shape=wshape) - w.data[:, :, 0] = -0.5/grid.spacing[0] - w.data[:, :, 1] = 0.0 - w.data[:, :, 2] = 0.5/grid.spacing[0] - - f_x_coeffs = Coefficient(1, f0, x, w) - - subs = Substitutions(f_x_coeffs) - - eq0 = Eq(f0.dt + f0.dx, 1, coefficients=subs) - eq1 = Eq(f1.dt + f1.dx, 1) - - stencil0 = solve(eq0.evaluate, f0.forward) - stencil1 = solve(eq1.evaluate, f1.forward) - - op0 = Operator(Eq(f0.forward, stencil0)) - op1 = Operator(Eq(f1.forward, stencil1)) - - op0(time_m=0, time_M=5, dt=1.0) - op1(time_m=0, time_M=5, dt=1.0) - -> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0)) -E assert Data(False) -E + where Data(False) = (Data([[[ True, True, True, True],\n [False, False, False, False],\n [ True, True, True, True],\n ...True],\n [ True, True, True, True],\n [ True, True, True, True],\n [ True, True, True, True]]])) -E + where = np.all -E + and Data([[[ True, True, True, True],\n [False, False, False, False],\n [ True, True, True, True],\n ...True],\n [ True, True, True, True],\n [ True, True, True, True],\n [ True, True, True, True]]]) = ((Data([[[-11.4375 , -11.4375 , -11.4375 , -11.4375 ],\n [ 49.59375, 49.59375, 49.59375, 49.59375],\n [ -6.... [-24.25 , -24.25 , -24.25 , -24.25 ],\n [ -1.9375 , -1.9375 , -1.9375 , -1.9375 ]]], dtype=float32) - Data([[[-11.437502 , -11.437502 , -11.437502 , -11.437502 ],\n [ 49.59374 , 49.59374 , 49.59374 , 49.59374 ....249996 , -24.249996 , -24.249996 ],\n [ -1.9375012, -1.9375012, -1.9375012, -1.9375012]]],\n dtype=float32)), 0.0, atol=1e-05, rtol=0) -E + where = np.isclose - -tests/test_symbolic_coefficients.py:96: AssertionError ------------------------------ Captured stderr call ----------------------------- -/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c: In function ‘Kernel’: -/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas] - #pragma omp simd aligned(f0,w:32) - -Operator `Kernel` run in 0.01 s -/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c: In function ‘Kernel’: -/tmp/devito-jitcache-uid0/b4ec5c37d966771de3236dbb40d450f5a48d787b.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas] - #pragma omp simd aligned(f1:32) - -Operator `Kernel` run in 0.01 s ------------------------------- Captured log call ------------------------------- -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -INFO Devito:logger.py:121 Operator `Kernel` run in 0.01 s -======= 3 failed, 828 passed, 82 skipped, 1 xfailed in 830.53s (0:13:50) ======= diff --git a/contrib/fwi/azureml_devito/README.md b/contrib/fwi/azureml_devito/README.md new file mode 100755 index 00000000..af99ddc7 --- /dev/null +++ b/contrib/fwi/azureml_devito/README.md @@ -0,0 +1,51 @@ +# DeepSeismic + +## Imaging + +This tutorial shows how to run [devito](https://www.devitoproject.org/) tutorial [notebooks](https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) in Azure Machine Learning ([Azure ML](https://docs.microsoft.com/en-us/azure/machine-learning/)) using [Azure Machine Learning Python SDK](https://docs.microsoft.com/en-us/azure/machine-learning/service/tutorial-1st-experiment-sdk-setup). + +For best experience use a Linux (Ubuntu) Azure [DSVM](https://docs.microsoft.com/en-us/azure/machine-learning/data-science-virtual-machine/dsvm-ubuntu-intro) and Jupyter Notebook with AzureML Python SDK and [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) to run the notebooks (see __Setting up Environment__ section below). + +Devito is a domain-specific Language (DSL) and code generation framework for the design of highly optimized finite difference kernels via symbolic computation for use in inversion methods. Here we show how ```devito``` can be openly used in the cloud by leveraging AzureML experimentation framework as a transparent and scalable platform for generic computation workloads. We focus on Full waveform inversion (__FWI__) problems where non-linear data-fitting procedures are applied for computing estimates of subsurface properties from seismic data. + + +### Setting up Environment + +The [conda environment](https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/environments.html) that encapsulates all the dependencies needed to run the notebooks described above can be created using the fwi_dev_conda_environment.yml file. See [here](https://github.com/Azure/MachineLearningNotebooks/blob/master/NBSETUP.md) generic instructions on how to install and run AzureML Python SDK in Jupyter Notebooks. + +To create the conda environment, run: +``` +conda env create -f fwi_dev_conda_environment.yml + +``` + +then, one can see the created environment within the list of available environments and activate it: +``` +conda env list +conda activate fwi_dev_conda_environment +``` + +Finally, start Jupyter notebook from within the activated environment: +``` +jupyter notebook +``` + +[Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) is also used to create an ACR in notebook 000_Setup_GeophysicsTutorial_FWI_Azure_devito, and then push and pull docker images. One can also create the ACR via Azure [portal](https://azure.microsoft.com/). + +### Run devito in Azure +The devito fwi examples are run in AzuremL using 4 notebooks: + - ```000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb```: sets up Azure resources (like resource groups, AzureML [workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-workspace)). + - ```010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb```: Creates a custom docker file and the associated image that contains ```devito``` [github repository](https://github.com/opesci/devito.git) (including devito fwi tutorial [notebooks](https://github.com/opesci/devito/tree/master/examples/seismic/tutorials)) and runs the official devito install [tests](https://github.com/opesci/devito/tree/master/tests). + - ```020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb```: shows how the devito fwi tutorial [notebooks](https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) can be run in AzureML using Azure Machine Learning [generic](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator?view=azure-ml-py) [estimators](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-train-ml-models) with custom docker images. FWI computation takes place on a managed AzureML [remote compute cluster](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets). + + ```Devito``` fwi computation artifacts (images and notebooks with data processing output results) are tracked under the AzureML workspace, and can be later downloaded and visualized. + + Two ways of running devito code are shown: + + (1) using __custom code__ (slightly modified graphing functions that save images to files). The AzureML experimentation job is defined by the devito code packaged as a py file. The experimentation job (defined by [azureml.core.experiment.Experiment](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.experiment.experiment?view=azure-ml-py) class can be used to track metrics or other artifacts (images) that are available in Azure portal. + + (2) using [__papermill__](https://github.com/nteract/papermill) invoked via its Python API to run unedited devito demo notebooks (including the [dask](https://dask.org/) local cluster [example](https://github.com/opesci/devito/blob/master/examples/seismic/tutorials/04_dask.ipynb) on the remote compute target and the results as saved notebooks that are available in Azure portal. + + - ```030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb```: shows how the devito fwi tutorial notebooks can be run in parallel on the elastically allocated AzureML [remote compute cluster](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets) created before. By submitting multiple jobs via azureml.core.Experiment submit(azureml.train.estimator.Estimator) one can use the [portal](https://portal.azure.com) to visualize the elastic allocation of AzureML [remote compute cluster](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets) nodes. + + diff --git a/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml new file mode 100755 index 00000000..e79343d6 --- /dev/null +++ b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml @@ -0,0 +1,16 @@ + +name: fwi_dev_conda_environment + +channels: + - anaconda +dependencies: + - python=3.7 + - numpy + - notebook + - nb_conda + - scikit-learn + - pip + - pip: + - python-dotenv + - papermill[azure] + - azureml-sdk[notebooks,automl,explain]==1.0.76 diff --git a/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb similarity index 83% rename from contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb rename to contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index af554269..3040bd87 100755 --- a/contrib/examples/imaging/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -1,5 +1,13 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -55,7 +63,8 @@ "from azureml.core import Workspace, Experiment\n", "from azureml.core.compute import ComputeTarget, AmlCompute\n", "from azureml.core.compute_target import ComputeTargetException\n", - "import platform, dotenv" + "import platform, dotenv\n", + "import pathlib" ] }, { @@ -67,13 +76,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.69\n" + "Azure ML SDK Version: 1.0.76\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1061-azure-x86_64-with-debian-10.0'" + "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" ] }, "execution_count": 3, @@ -83,7 +92,7 @@ { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks'" + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -141,7 +150,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /workspace/examples/imaging/azureml_devito/notebooks/./src/project_utils.py\n" + "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./src/project_utils.py\n" ] } ], @@ -358,7 +367,7 @@ "\n", "# experimentation_docker_image_name = \"fwi01_azureml\"\n", "# experimentation_docker_image_tag = \"sdk.v1.0.60\"\n", - "# docker_container_mount_point = '/datadrive01/prj/DeepSeismic/fwi' # use project directory or a subdirectory\n", + "# docker_container_mount_point = os.getcwd() # use project directory or a subdirectory\n", "\n", "# docker_login = \"georgedockeraccount\"\n", "# docker_pwd = \"\"\n", @@ -418,6 +427,7 @@ "%load_ext dotenv\n", "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH)) \n", "os.makedirs(os.path.join(*(prj_consts.DOTENV_FILE_PATH[:-1])), exist_ok=True)\n", + "pathlib.Path(dotenv_file_path).touch()\n", "\n", "# # show .env file path\n", "# !pwd\n", @@ -497,6 +507,15 @@ "execution_count": 14, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING - Warning: Falling back to use azure cli login credentials.\n", + "If you run your code in unattended mode, i.e., where you can't give a user input, then we recommend to use ServicePrincipalAuthentication or MsiAuthentication.\n", + "Please refer to aka.ms/aml-notebook-auth for different authentication mechanisms in azureml-sdk.\n" + ] + }, { "name": "stdout", "output_type": "stream", @@ -649,23 +668,79 @@ "name": "stdout", "output_type": "stream", "text": [ - "azure-cli 2.0.75\r\n", + "azure-cli 2.0.58 *\r\n", "\r\n", - "command-modules-nspkg 2.0.3\r\n", - "core 2.0.75\r\n", - "nspkg 3.0.4\r\n", - "telemetry 1.0.4\r\n", + "acr 2.2.0 *\r\n", + "acs 2.3.17 *\r\n", + "advisor 2.0.0 *\r\n", + "ams 0.4.1 *\r\n", + "appservice 0.2.13 *\r\n", + "backup 1.2.1 *\r\n", + "batch 3.4.1 *\r\n", + "batchai 0.4.7 *\r\n", + "billing 0.2.0 *\r\n", + "botservice 0.1.6 *\r\n", + "cdn 0.2.0 *\r\n", + "cloud 2.1.0 *\r\n", + "cognitiveservices 0.2.4 *\r\n", + "command-modules-nspkg 2.0.2 *\r\n", + "configure 2.0.20 *\r\n", + "consumption 0.4.2 *\r\n", + "container 0.3.13 *\r\n", + "core 2.0.58 *\r\n", + "cosmosdb 0.2.7 *\r\n", + "dla 0.2.4 *\r\n", + "dls 0.1.8 *\r\n", + "dms 0.1.2 *\r\n", + "eventgrid 0.2.1 *\r\n", + "eventhubs 0.3.3 *\r\n", + "extension 0.2.3 *\r\n", + "feedback 2.1.4 *\r\n", + "find 0.2.13 *\r\n", + "hdinsight 0.3.0 *\r\n", + "interactive 0.4.1 *\r\n", + "iot 0.3.6 *\r\n", + "iotcentral 0.1.6 *\r\n", + "keyvault 2.2.11 *\r\n", + "kusto 0.1.0 *\r\n", + "lab 0.1.5 *\r\n", + "maps 0.3.3 *\r\n", + "monitor 0.2.10 *\r\n", + "network 2.3.2 *\r\n", + "nspkg 3.0.3 *\r\n", + "policyinsights 0.1.1 *\r\n", + "profile 2.1.3 *\r\n", + "rdbms 0.3.7 *\r\n", + "redis 0.4.0 *\r\n", + "relay 0.1.3 *\r\n", + "reservations 0.4.1 *\r\n", + "resource 2.1.10 *\r\n", + "role 2.4.0 *\r\n", + "search 0.1.1 *\r\n", + "security 0.1.0 *\r\n", + "servicebus 0.3.3 *\r\n", + "servicefabric 0.1.12 *\r\n", + "signalr 1.0.0 *\r\n", + "sql 2.1.9 *\r\n", + "sqlvm 0.1.0 *\r\n", + "storage 2.3.1 *\r\n", + "telemetry 1.0.1 *\r\n", + "vm 2.2.15 *\r\n", + "\r\n", + "Extensions:\r\n", + "azure-ml-admin-cli 0.0.1\r\n", + "azure-cli-ml Unknown\r\n", "\r\n", "Python location '/opt/az/bin/python3'\r\n", - "Extensions directory '/root/.azure/cliextensions'\r\n", + "Extensions directory '/opt/az/extensions'\r\n", "\r\n", - "Python (Linux) 3.6.5 (default, Oct 11 2019, 09:04:03) \r\n", - "[GCC 6.3.0 20170516]\r\n", + "Python (Linux) 3.6.5 (default, Feb 12 2019, 02:10:43) \r\n", + "[GCC 5.4.0 20160609]\r\n", "\r\n", "Legal docs and information: aka.ms/AzureCliLegal\r\n", "\r\n", "\r\n", - "Your CLI is up-to-date.\r\n" + "\u001b[33mYou have 57 updates available. Consider updating your CLI installation.\u001b[0m\r\n" ] } ], @@ -705,6 +780,29 @@ "execution_count": 19, "metadata": {}, "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "[' \"loginServer\": \"fwi01acr.azurecr.io\",',\n", + " ' \"name\": \"fwi01acr\",',\n", + " ' \"networkRuleSet\": null,',\n", + " ' \"provisioningState\": \"Succeeded\",',\n", + " ' \"resourceGroup\": \"ghiordanfwirsg01\",',\n", + " ' \"sku\": {',\n", + " ' \"name\": \"Basic\",',\n", + " ' \"tier\": \"Basic\"',\n", + " ' },',\n", + " ' \"status\": null,',\n", + " ' \"storageAccount\": null,',\n", + " ' \"tags\": {},',\n", + " ' \"type\": \"Microsoft.ContainerRegistry/registries\"',\n", + " '}']" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -714,8 +812,8 @@ "cli_command='az acr create --resource-group '+ crt_resource_group +' --name ' + acr_name + ' --sku Basic'\n", "cli_command\n", "\n", - "if create_ACR_FLAG:\n", - " !$cli_command" + "response = !$cli_command\n", + "response[-14:]" ] }, { @@ -746,9 +844,8 @@ "cli_command='az acr update -n '+acr_name+' --admin-enabled true'\n", "cli_command\n", "\n", - "if create_ACR_FLAG:\n", - " response = !$cli_command\n", - " response" + "response = !$cli_command\n", + "# response" ] }, { @@ -794,18 +891,8 @@ ] }, { - "cell_type": "code", - "execution_count": 23, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finished running 000_Setup_GeophysicsTutorial_FWI_Azure_devito!\n" - ] - } - ], "source": [ "print('Finished running 000_Setup_GeophysicsTutorial_FWI_Azure_devito!')" ] @@ -827,7 +914,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb new file mode 100755 index 00000000..78276433 --- /dev/null +++ b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -0,0 +1,1064 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License.\n", + "\n", + "# FWI in Azure project\n", + "\n", + "## Create Experimentation Docker image\n", + "\n", + "FWI demo based on: \n", + "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", + "\n", + "\n", + "\n", + "In this notebook we create a custom docker image that will be used to run the devito demo notebooks in AzureML. \n", + "\n", + " - We transparently create a docker file, a conda environment .yml file, build the docker image and push it into dockerhub. Azure ACR could also be used for storing docker images. \n", + " - The conda environment .yml file lists conda and pip installs, and separates all python dependencies from the docker installs. \n", + " - The dockerfile is generic. The only AzureML depedency is azureml-sdk pip installable package in conda environment .yml file\n", + " - The created docer image will be run in following notebook in a container on the local AzureVM or on a remote AzureML compute cluster. This AzureML pattern decouples experimentation (or training) job definition (experimentation script, data location, dependencies and docker image) happening on the control plane machine that runs this notebook, from the elastically allocated and Azure managed VM/cluster that does the actual training/experimentation computation.\n", + " \n", + "\n", + "User input requiring steps:\n", + " - [Fill in and save docker image name settings, if needed. ](#docker_image_settings)\n", + " - [Update DOCKER_CONTAINER_MOUNT_POINT to match our local path](#docker_image_settings)\n", + " - [Set docker build and test flags](#docker_build_test_settings) \n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Allow multiple displays per cell\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\" " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "import shutil\n", + "import urllib\n", + "\n", + "import platform\n", + "import math\n", + "import docker" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "platform.platform()\n", + "os.getcwd()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "#### Setup docker image build and test process. \n", + " - devito tests take abou 15 mins (981.41 seconds). When running this notebook for first time make:\n", + " > docker_build_no_cache = '--no-cache' \n", + " > docker_test_run_devito_tests = True\n", + " \n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "docker_build_no_cache = '' # '--no-cache' # or '' #\n", + "docker_test_run_devito_tests = True # True # False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Import utilities functions" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_path_to_sys_path(path_to_append):\n", + " if not (any(path_to_append in paths for paths in sys.path)):\n", + " sys.path.append(path_to_append)\n", + " \n", + "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", + "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", + "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", + "\n", + "import project_utils\n", + "prj_consts = project_utils.project_consts()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Create experimentation docker file" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../not_shared/general.env'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", + "dotenv_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks\r\n" + ] + } + ], + "source": [ + "!pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# azureml_sdk_version set here must match azureml sdk version pinned in conda env file written to conda_common_file_path below\n", + "azureml_sdk_version = '1.0.76' " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "##### Input here docker image settings \n", + "in cell below we use [dotenv](https://github.com/theskumar/python-dotenv) to overwrite docker image properties already save in dotenv_file_path. Change as needed, e.g. update azureml_sdk version if using a different version.\n", + "\n", + "[Back](#user_input_requiring_steps) to summary of user input requiring steps." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(True, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', 'sdk.v1.0.76')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "(True,\n", + " 'DOCKER_CONTAINER_MOUNT_POINT',\n", + " '/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# SDK changes often, so we'll keep its version transparent \n", + "import dotenv\n", + "\n", + "# EXPERIMENTATION_IMAGE_VERSION should:\n", + "# - match sdk version in fwi01_conda_env01 environmnet in conda_env_fwi01_azureml_sdk.v1.0.XX.yml file below\n", + "# - match the conda env yml file name, e.g. conda_env_fwi01_azureml_sdk.v1.0.xx.yml referenced in \n", + "# Dockerfile_fwi01_azureml_sdk.v1.0.xx\n", + "# dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_NAME', 'fwi01_azureml')\n", + "dotenv.set_key(dotenv_file_path, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', ('sdk.v'+azureml_sdk_version))\n", + "\n", + "\n", + "docker_container_mount_point = os.getcwd()\n", + "# or something like \"/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'\n", + "dotenv.set_key(dotenv_file_path, 'DOCKER_CONTAINER_MOUNT_POINT', docker_container_mount_point)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'conda_env_fwi01_azureml_sdk.v1.0.76.yml'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.76.yml'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%reload_ext dotenv\n", + "%dotenv $dotenv_file_path\n", + "\n", + "docker_file_location = os.path.join(*(prj_consts.AML_EXPERIMENT_DIR + ['docker_build']))\n", + "\n", + "docker_file_name = 'Dockerfile'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "conda_dependency_file_name = 'conda_env'+ '_' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "conda_dependency_common_file_name = conda_dependency_file_name\n", + "\n", + "devito_conda_dependency_file_name = 'devito_conda_env'+'.yml'\n", + "\n", + "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", + "docker_image_name = docker_repo_name + '/' + os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", + "if image_version!=\"\":\n", + " docker_file_name = docker_file_name +'_'+ image_version\n", + " conda_dependency_file_name = conda_dependency_file_name+'_'+ image_version\n", + " docker_image_name = docker_image_name +':'+ image_version\n", + "conda_dependency_file_name=conda_dependency_file_name+'.yml'\n", + "conda_dependency_common_file_name = conda_dependency_common_file_name+'.yml'\n", + "\n", + "docker_file_dir = os.path.join(*([os.getcwd(), docker_file_location]))\n", + "os.makedirs(docker_file_dir, exist_ok=True)\n", + "docker_file_path = os.path.join(*([docker_file_dir]+[docker_file_name]))\n", + "conda_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_file_name]))\n", + "conda_common_file_path = os.path.join(*([docker_file_dir]+[conda_dependency_common_file_name]))\n", + "\n", + "docker_image_name\n", + "\n", + "conda_dependency_file_name\n", + "conda_file_path\n", + "conda_common_file_path\n", + "\n", + "docker_file_dir\n", + "docker_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" + ] + } + ], + "source": [ + "%%writefile $conda_common_file_path\n", + "name: fwi01_conda_env01\n", + " \n", + "#https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.13.1-cp37-cp37m-linux_x86_64.whl \n", + "# https://github.com/dask/dask-tutorial\n", + "\n", + "channels:\n", + " - anaconda\n", + " - conda-forge\n", + "dependencies:\n", + " - python=3.6 # 3.6 req by tf, not 3.7.2 \n", + " - dask\n", + " - distributed\n", + " - h5py\n", + " - matplotlib\n", + " - nb_conda\n", + " - notebook \n", + " - numpy \n", + " - pandas\n", + " - pip\n", + " - py-cpuinfo # all required by devito or dask-tutorial\n", + " - pytables\n", + " - python-graphviz\n", + " - requests\n", + " - pillow\n", + " - scipy\n", + " - snakeviz\n", + " - scikit-image\n", + " - toolz\n", + " - pip:\n", + " - anytree # required by devito\n", + " - azureml-sdk[notebooks,automl]==1.0.76\n", + " - codepy # required by devito\n", + " - papermill[azure]\n", + " - pyrevolve # required by devito" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76\n" + ] + } + ], + "source": [ + "%%writefile $docker_file_path \n", + "\n", + "FROM continuumio/miniconda3:4.7.10 \n", + "MAINTAINER George Iordanescu \n", + "\n", + "RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \\\n", + " gcc g++ \\\n", + " wget bzip2 \\\n", + " curl \\\n", + " git make \\\n", + " mpich \\ \n", + " libmpich-dev && \\\n", + " apt-get clean && \\\n", + " rm -rf /var/lib/apt/lists/*\n", + "\n", + "ENV CONDA_ENV_FILE_NAME conda_env_fwi01_azureml.yml\n", + "ADD $CONDA_ENV_FILE_NAME /tmp/$CONDA_ENV_FILE_NAME\n", + "ENV CONDA_DIR /opt/conda\n", + "ENV CONDA_ENV_NAME fwi01_conda_env\n", + "\n", + "RUN git clone https://github.com/opesci/devito.git && \\\n", + " cd devito && \\\n", + " /opt/conda/bin/conda env create -q --name $CONDA_ENV_NAME -f environment.yml && \\\n", + " pip install -e . \n", + " \n", + "ENV CONDA_AUTO_UPDATE_CONDA=false\n", + "ENV CONDA_DEFAULT_ENV=$CONDA_ENV_NAME\n", + "ENV CONDA_PREFIX=$CONDA_DIR/envs/$CONDA_DEFAULT_ENV\n", + "ENV PATH=$CONDA_PREFIX/bin:/opt/conda/bin:$PATH \n", + "\n", + "RUN /opt/conda/bin/conda env update --name $CONDA_ENV_NAME -f /tmp/$CONDA_ENV_FILE_NAME && \\\n", + " /opt/conda/bin/conda clean --yes --all\n", + "\n", + "ENV PYTHONPATH=$PYTHONPATH:devito/app\n", + "\n", + "# WORKDIR /devito \n", + " \n", + "CMD /bin/bash" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.76.yml'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 28\r\n", + "-rwxrwxrwx 1 root root 733 Dec 4 02:31 conda_env_fwi01_azureml_sdk.v1.0.69.yml\r\n", + "-rwxrwxrwx 1 root root 725 Dec 4 02:31 conda_env_fwi01_azureml_sdk.v1.0.74.yml\r\n", + "-rwxrwxrwx 1 root root 725 Dec 4 16:59 conda_env_fwi01_azureml_sdk.v1.0.76.yml\r\n", + "-rwxrwxrwx 1 root root 725 Dec 4 16:59 conda_env_fwi01_azureml.yml\r\n", + "-rwxrwxrwx 1 root root 1073 Dec 4 02:31 Dockerfile_fwi01_azureml_sdk.v1.0.69\r\n", + "-rwxrwxrwx 1 root root 1073 Dec 4 02:31 Dockerfile_fwi01_azureml_sdk.v1.0.74\r\n", + "-rwxrwxrwx 1 root root 1073 Dec 4 16:59 Dockerfile_fwi01_azureml_sdk.v1.0.76\r\n" + ] + } + ], + "source": [ + "shutil.copyfile(conda_common_file_path, conda_file_path)\n", + "\n", + "! ls -l $docker_file_dir" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 -f /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76 /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build '" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "['Sending build context to Docker daemon 13.31kB',\n", + " '',\n", + " 'Step 1/15 : FROM continuumio/miniconda3:4.7.10',\n", + " '4.7.10: Pulling from continuumio/miniconda3',\n", + " '1ab2bdfe9778: Pulling fs layer']" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "[' ---> Running in 64cc95908200',\n", + " 'Removing intermediate container 64cc95908200',\n", + " ' ---> 619ab5d20944',\n", + " 'Successfully built 619ab5d20944',\n", + " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76']" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cli_command='docker build -t '+ docker_image_name + \\\n", + "' -f ' + docker_file_path + \\\n", + "' ' + docker_file_dir + ' ' +\\\n", + "docker_build_no_cache #'' #' --no-cache'\n", + "\n", + "\n", + "cli_command\n", + "docker_build_response = ! $cli_command\n", + "\n", + "docker_build_response[0:5] \n", + "docker_build_response[-5:] " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Docker containers can be run using python docker sdk" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "b'/\\n1.0.76\\n'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docker_image_name\n", + "\n", + "sh_command='bash -c \"pwd;python -c \\'import azureml.core;print(azureml.core.VERSION)\\'\"'\n", + "sh_command\n", + "client = docker.from_env()\n", + "client.containers.run(docker_image_name, \n", + " remove=True,\n", + " volumes={os.getenv('DOCKER_CONTAINER_MOUNT_POINT'): {'bind': '/workspace', 'mode': 'rw'}},\n", + " working_dir='/',\n", + " command=sh_command)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Docker containers can also be run in cli \n", + "\n", + "Here we also create a log file to capture commands execution in container. If flag docker_test_run_devito_tests is True, we run \n", + "and capture test commands output. Tests take abou 15 minutes to run. If flag docker_test_run_devito_tests is False, we show the results of a previous session. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./fwi01_azureml_buildexperimentationdockerimage.log'" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fwi01_log_file = os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log']))\n", + "fwi01_log_file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create command for running devito tests, capture output in a log file, save log file outside container" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "content of devito tests log file before testing:\n", + "Before running e13n container... \r\n" + ] + }, + { + "data": { + "text/plain": [ + "' python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "if docker_test_run_devito_tests:\n", + " run_devito_tests_command = ' python -m pytest tests/ ' + \\\n", + "'> ' + fwi01_log_file +' 2>&1; ' + \\\n", + "' mv ' + fwi01_log_file + ' /workspace/' \n", + " \n", + " with open(os.path.join(*(['.', 'fwi01_azureml_buildexperimentationdockerimage.log'])), \"w\") as crt_log_file:\n", + " print('Before running e13n container... ', file=crt_log_file)\n", + " print('\\ncontent of devito tests log file before testing:')\n", + " !cat $fwi01_log_file\n", + "else:\n", + " run_devito_tests_command = '' \n", + "\n", + "# run_devito_tests_command = 'ls -l > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'\n", + "run_devito_tests_command" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/ \"'" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# conda environments:\n", + "#\n", + "base /opt/conda\n", + "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", + "\n", + "total 560\n", + "-rw-r--r-- 1 root root 11521 Dec 4 17:00 conftest.py\n", + "-rw-r--r-- 1 root root 6006 Dec 4 17:00 test_adjoint.py\n", + "-rw-r--r-- 1 root root 14586 Dec 4 17:00 test_autotuner.py\n", + "-rw-r--r-- 1 root root 7538 Dec 4 17:00 test_builtins.py\n", + "-rw-r--r-- 1 root root 24415 Dec 4 17:00 test_caching.py\n", + "-rw-r--r-- 1 root root 9721 Dec 4 17:00 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Dec 4 17:00 test_constant.py\n", + "-rw-r--r-- 1 root root 55954 Dec 4 17:00 test_data.py\n", + "-rw-r--r-- 1 root root 481 Dec 4 17:00 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16331 Dec 4 17:00 test_derivatives.py\n", + "-rw-r--r-- 1 root root 1473 Dec 4 17:00 test_differentiable.py\n", + "-rw-r--r-- 1 root root 30846 Dec 4 17:00 test_dimension.py\n", + "-rw-r--r-- 1 root root 24838 Dec 4 17:00 test_dle.py\n", + "-rw-r--r-- 1 root root 1169 Dec 4 17:00 test_docstrings.py\n", + "-rw-r--r-- 1 root root 32134 Dec 4 17:00 test_dse.py\n", + "-rw-r--r-- 1 root root 8205 Dec 4 17:00 test_gradient.py\n", + "-rw-r--r-- 1 root root 15227 Dec 4 17:00 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31816 Dec 4 17:00 test_ir.py\n", + "-rw-r--r-- 1 root root 63169 Dec 4 17:00 test_mpi.py\n", + "-rw-r--r-- 1 root root 67053 Dec 4 17:00 test_operator.py\n", + "-rw-r--r-- 1 root root 14875 Dec 4 17:00 test_ops.py\n", + "-rw-r--r-- 1 root root 12228 Dec 4 17:00 test_pickle.py\n", + "-rw-r--r-- 1 root root 1809 Dec 4 17:00 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Dec 4 17:00 test_save.py\n", + "-rw-r--r-- 1 root root 2115 Dec 4 17:00 test_staggered_utils.py\n", + "-rw-r--r-- 1 root root 5711 Dec 4 17:00 test_subdomains.py\n", + "-rw-r--r-- 1 root root 3320 Dec 4 17:00 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 7277 Dec 4 17:00 test_tensors.py\n", + "-rw-r--r-- 1 root root 3186 Dec 4 17:00 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Dec 4 17:00 test_tools.py\n", + "-rw-r--r-- 1 root root 3296 Dec 4 17:00 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Dec 4 17:00 test_visitors.py\n", + "-rw-r--r-- 1 root root 21802 Dec 4 17:00 test_yask.py\n", + "1.0.76\n", + "\n", + "content of devito tests log file after testing:\n", + "============================= test session starts ==============================\n", + "platform linux -- Python 3.6.9, pytest-5.3.1, py-1.8.0, pluggy-0.13.1\n", + "rootdir: /devito, inifile: setup.cfg\n", + "plugins: nbval-0.9.3, cov-2.8.1\n", + "collected 1056 items / 2 skipped / 1054 selected\n", + "\n", + "tests/test_adjoint.py .......................... [ 2%]\n", + "tests/test_autotuner.py ..........s..... [ 3%]\n", + "tests/test_builtins.py ....s...............s..s [ 6%]\n", + "tests/test_caching.py .................................................. [ 10%]\n", + " [ 10%]\n", + "tests/test_checkpointing.py ....... [ 11%]\n", + "tests/test_constant.py . [ 11%]\n", + "tests/test_data.py ..........................ssssssssssssssssss.ss.. [ 16%]\n", + "tests/test_dependency_bugs.py . [ 16%]\n", + "tests/test_derivatives.py .............................................. [ 20%]\n", + "........................................................................ [ 27%]\n", + "........................................................................ [ 34%]\n", + "...... [ 35%]\n", + "tests/test_differentiable.py .. [ 35%]\n", + "tests/test_dimension.py ............................... [ 38%]\n", + "tests/test_dle.py ...................................................... [ 43%]\n", + "........................................... [ 47%]\n", + "tests/test_docstrings.py ................ [ 48%]\n", + "tests/test_dse.py ......x............................................... [ 53%]\n", + "................x..........s.... [ 57%]\n", + "tests/test_gradient.py .... [ 57%]\n", + "tests/test_interpolation.py ........................ [ 59%]\n", + "tests/test_ir.py ....................................................... [ 64%]\n", + "................ [ 66%]\n", + "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 71%]\n", + "sss [ 71%]\n", + "tests/test_operator.py ................................................. [ 76%]\n", + "..............................................s......................... [ 83%]\n", + ".................................. [ 86%]\n", + "tests/test_pickle.py .................ss. [ 88%]\n", + "tests/test_resample.py . [ 88%]\n", + "tests/test_save.py .. [ 88%]\n", + "tests/test_staggered_utils.py ......... [ 89%]\n", + "tests/test_subdomains.py ... [ 89%]\n", + "tests/test_symbolic_coefficients.py .....F [ 90%]\n", + "tests/test_tensors.py .................................................. [ 95%]\n", + "........................... [ 97%]\n", + "tests/test_timestepping.py ....... [ 98%]\n", + "tests/test_tools.py ..... [ 98%]\n", + "tests/test_tti.py .... [ 99%]\n", + "tests/test_visitors.py ......... [100%]\n", + "\n", + "=================================== FAILURES ===================================\n", + "______________________ TestSC.test_function_coefficients _______________________\n", + "\n", + "self = \n", + "\n", + " def test_function_coefficients(self):\n", + " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", + " so = 2\n", + " grid = Grid(shape=(4, 4))\n", + " f0 = TimeFunction(name='f0', grid=grid, space_order=so, coefficients='symbolic')\n", + " f1 = TimeFunction(name='f1', grid=grid, space_order=so)\n", + " x, y = grid.dimensions\n", + " \n", + " s = Dimension(name='s')\n", + " ncoeffs = so+1\n", + " \n", + " wshape = list(grid.shape)\n", + " wshape.append(ncoeffs)\n", + " wshape = as_tuple(wshape)\n", + " \n", + " wdims = list(grid.dimensions)\n", + " wdims.append(s)\n", + " wdims = as_tuple(wdims)\n", + " \n", + " w = Function(name='w', dimensions=wdims, shape=wshape)\n", + " w.data[:, :, 0] = 0.0\n", + " w.data[:, :, 1] = -1.0/grid.spacing[0]\n", + " w.data[:, :, 2] = 1.0/grid.spacing[0]\n", + " \n", + " f_x_coeffs = Coefficient(1, f0, x, w)\n", + " \n", + " subs = Substitutions(f_x_coeffs)\n", + " \n", + " eq0 = Eq(f0.dt + f0.dx, 1, coefficients=subs)\n", + " eq1 = Eq(f1.dt + f1.dx, 1)\n", + " \n", + " stencil0 = solve(eq0.evaluate, f0.forward)\n", + " stencil1 = solve(eq1.evaluate, f1.forward)\n", + " \n", + " op0 = Operator(Eq(f0.forward, stencil0))\n", + " op1 = Operator(Eq(f1.forward, stencil1))\n", + " \n", + " op0(time_m=0, time_M=5, dt=1.0)\n", + " op1(time_m=0, time_M=5, dt=1.0)\n", + " \n", + "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", + "E assert Data(False)\n", + "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", + "E + where = np.all\n", + "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", + "E + where = np.isclose\n", + "\n", + "tests/test_symbolic_coefficients.py:96: AssertionError\n", + "----------------------------- Captured stderr call -----------------------------\n", + "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c: In function ‘Kernel’:\n", + "/tmp/devito-jitcache-uid0/28a0c1d4f6f5711828a8c4cd1ff27eaa7607404e.c:39: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + " #pragma omp simd aligned(f0,w:32)\n", + " \n", + "Operator `Kernel` run in 0.01 s\n", + "/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c: In function ‘Kernel’:\n", + "/tmp/devito-jitcache-uid0/0031268cb9efe9dfa4f656da51efd0d4fa4b9d00.c:38: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + " #pragma omp simd aligned(f1:32)\n", + " \n", + "Operator `Kernel` run in 0.01 s\n", + "------------------------------ Captured log call -------------------------------\n", + "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", + "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", + "====== 1 failed, 968 passed, 87 skipped, 2 xfailed in 1038.47s (0:17:18) =======\n" + ] + } + ], + "source": [ + "cli_command='docker run -it --rm --name fwi01_azureml_container ' +\\\n", + "' -v '+os.getenv('DOCKER_CONTAINER_MOUNT_POINT')+':/workspace:rw ' + \\\n", + "docker_image_name + \\\n", + "' /bin/bash -c \"conda env list ; ls -l /devito/tests; ' + \\\n", + "'python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; ' + \\\n", + "'cd /devito; ' + \\\n", + "run_devito_tests_command +\\\n", + "' \"'\n", + "\n", + "cli_command\n", + "! $cli_command\n", + "# # ============= 774 passed, 70 skipped, 1 xfailed in 1106.76 seconds =============\n", + "print('\\ncontent of devito tests log file after testing:')\n", + "!cat $fwi01_log_file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "###### Use the ACR created in previous notebook or docker hub to push your image" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'az acr login --name fwi01acr'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Login Succeeded\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\n", + "Configure a credential helper to remove this warning. See\n", + "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\n", + "\n", + "\u001b[0m" + ] + } + ], + "source": [ + "# docker_pwd = os.getenv('DOCKER_PWD')\n", + "# docker_login = os.getenv('DOCKER_LOGIN')\n", + "# !docker login -u=$docker_login -p=$docker_pwd\n", + "# !docker push {docker_image_name}\n", + "\n", + "%dotenv $dotenv_file_path\n", + "cli_command='az acr login --name '+os.getenv('ACR_NAME')\n", + "# print cli command\n", + "cli_command\n", + "\n", + "# run cli command\n", + "cli_command = cli_command+' --username '+os.getenv('ACR_USERNAME') + ' --password ' + os.getenv('ACR_PASSWORD')\n", + "! $cli_command" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cli_command='docker push '+docker_image_name\n", + "cli_command" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", + "\n", + "\u001b[1B01e8603a: Preparing \n", + "\u001b[1B83481e05: Preparing \n", + "\u001b[1Bf84794df: Preparing \n", + "\u001b[1B4559bd8a: Preparing \n", + "\u001b[1Bf8fc4c9a: Preparing \n", + "\u001b[1Bba47210e: Preparing \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[7B01e8603a: Pushing 1.055GB/2.967GBA\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 479.3MB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 910.8MB/2.967GB\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[6B83481e05: Pushing 2.253GB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 1.072GB/2.967GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 1.771GB/2.967GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[6B83481e05: Pushed 3.102GB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[Ksdk.v1.0.76: digest: sha256:25a4a6faad05e910366df62dc4ea0fbeb0d5fe912e56ad5bddb2325104653710 size: 1800\n" + ] + } + ], + "source": [ + "! $cli_command" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!\n" + ] + } + ], + "source": [ + "# !jupyter nbconvert 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito --to html\n", + "print('Finished running 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito!')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb new file mode 100755 index 00000000..c2902730 --- /dev/null +++ b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -0,0 +1,1003 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License. \n", + " \n", + " \n", + "# FWI demo based on: \n", + "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", + "\n", + "\n", + "\n", + "In this notebook we run the devito demo [notebooks](https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/) mentioned above by using an [AzureML estimator](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator.estimator?view=azure-ml-py) with custom docker image. The docker image and associated docker file were created in previous notebook.\n", + "\n", + "\n", + "#### This notebook is used as a control plane to submit experimentation jobs running devito in Azure in two modes (see [remote run azureml python script file invoking devito](#devito_demo_mode)):\n", + " - [Mode 1](#devito_demo_mode_1):\n", + " - uses custom code (slightly modified graphing functions save images to files too) \n", + " - experimentation job is defined by the devito code that is packaged as a py file to be run on an Azure remote compute target\n", + " - experimentation job can be used to track metrics or other artifacts (images)\n", + " \n", + " - Mode 2:\n", + " - papermill is invoked via its Python API to run unedited devito demo notebooks (https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) on the remote compute target and get back the results as saved notebooks that are then Available in Azure portal. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Allow multiple displays per cell\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\" " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "import shutil\n", + "import urllib\n", + "import azureml.core\n", + "from azureml.core import Workspace, Experiment\n", + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "from azureml.core.runconfig import MpiConfiguration\n", + "\n", + "\n", + "# from azureml.core.datastore import Datastore\n", + "# from azureml.data.data_reference import DataReference\n", + "# from azureml.pipeline.steps import HyperDriveStep\n", + "# from azureml.pipeline.core import Pipeline, PipelineData\n", + "# from azureml.train.dnn import TensorFlow\n", + "\n", + "from azureml.train.estimator import Estimator\n", + "from azureml.widgets import RunDetails\n", + "\n", + "import platform" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Azure ML SDK Version: 1.0.76\n" + ] + }, + { + "data": { + "text/plain": [ + "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Azure ML SDK Version: \", azureml.core.VERSION)\n", + "platform.platform()\n", + "os.getcwd()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../not_shared/general.env'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_path_to_sys_path(path_to_append):\n", + " if not (any(path_to_append in paths for paths in sys.path)):\n", + " sys.path.append(path_to_append)\n", + " \n", + "auxiliary_files_dir = os.path.join(*(['.', 'src']))\n", + "paths_to_append = [os.path.join(os.getcwd(), auxiliary_files_dir)]\n", + "[add_path_to_sys_path(crt_path) for crt_path in paths_to_append]\n", + "\n", + "import project_utils\n", + "prj_consts = project_utils.project_consts()\n", + "\n", + "dotenv_file_path = os.path.join(*(prj_consts.DOTENV_FILE_PATH))\n", + "dotenv_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../not_shared'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "workspace_config_dir = os.path.join(*(prj_consts.AML_WORKSPACE_CONFIG_DIR))\n", + "workspace_config_file = prj_consts.AML_WORKSPACE_CONFIG_FILE_NAME\n", + "workspace_config_dir" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'./../temp/devito_tutorial/azureml_01_modelling.py'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%dotenv $dotenv_file_path\n", + "\n", + "script_folder = prj_consts.AML_EXPERIMENT_DIR + ['devito_tutorial']\n", + "\n", + "devito_training_script_file = '01_modelling.py' # hardcoded in file azureml_training_script_full_file_name below\n", + "azureml_training_script_file = 'azureml_'+devito_training_script_file\n", + "experimentName = '020_AzureMLEstimator'\n", + "\n", + "os.makedirs(os.path.join(*(script_folder)), exist_ok=True)\n", + "script_path = os.path.join(*(script_folder))\n", + "training_script_full_file_name = os.path.join(script_path, devito_training_script_file)\n", + "azureml_training_script_full_file_name = os.path.join(script_path, azureml_training_script_file)\n", + "\n", + "training_script_full_file_name\n", + "azureml_training_script_full_file_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " \n", + "##### devito in Azure ML demo mode 1\n", + "Create devito demo script based on \n", + "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/01_modelling.ipynb\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to summary of modes od demoing devito in AzureML.\n", + "\n", + "Main purpose of this script is to extend _plot_velocity()_ and _plot_shotrecord()_ devito [plotting functions](https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py) to allow the mto work in batch mode, i.e. save output to a file." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $training_script_full_file_name\n", + "\n", + "import numpy as np\n", + "import os, argparse\n", + "\n", + "from examples.seismic import Model\n", + "from examples.seismic import TimeAxis\n", + "from examples.seismic import Receiver\n", + "from devito import TimeFunction\n", + "from devito import Eq, solve\n", + "from devito import Operator\n", + "\n", + "\n", + "# try:\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib import cm\n", + "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", + "\n", + "mpl.rc('font', size=16)\n", + "mpl.rc('figure', figsize=(8, 6))\n", + "# except:\n", + "# plt = None\n", + "# cm = None\n", + " \n", + "\n", + "\n", + "# \"all\" plotting utils in devito do not save to file, so we extend them here\n", + "# https://github.com/opesci/devito/blob/master/examples/seismic/plotting.py\n", + "def plot_velocity(model, source=None, receiver=None, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a two-dimensional velocity field from a seismic `Model`\n", + " object. Optionally also includes point markers for sources and receivers.\n", + "\n", + " Parameters\n", + " ----------\n", + " model : Model\n", + " Object that holds the velocity model.\n", + " source : array_like or float\n", + " Coordinates of the source point.\n", + " receiver : array_like or float\n", + " Coordinates of the receiver points.\n", + " colorbar : bool\n", + " Option to plot the colorbar.\n", + " \"\"\"\n", + " domain_size = 1.e-3 * np.array(model.domain_size)\n", + " extent = [model.origin[0], model.origin[0] + domain_size[0],\n", + " model.origin[1] + domain_size[1], model.origin[1]]\n", + "\n", + " plot = plt.imshow(np.transpose(model.vp.data), animated=True, cmap=cm.jet,\n", + " vmin=np.min(model.vp.data), vmax=np.max(model.vp.data),\n", + " extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Depth (km)')\n", + "\n", + " # Plot source points, if provided\n", + " if receiver is not None:\n", + " plt.scatter(1e-3*receiver[:, 0], 1e-3*receiver[:, 1],\n", + " s=25, c='green', marker='D')\n", + "\n", + " # Plot receiver points, if provided\n", + " if source is not None:\n", + " plt.scatter(1e-3*source[:, 0], 1e-3*source[:, 1],\n", + " s=25, c='red', marker='o')\n", + "\n", + " # Ensure axis limits\n", + " plt.xlim(model.origin[0], model.origin[0] + domain_size[0])\n", + " plt.ylim(model.origin[1] + domain_size[1], model.origin[1])\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " cbar = plt.colorbar(plot, cax=cax)\n", + " cbar.set_label('Velocity (km/s)')\n", + " plt.show()\n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def plot_shotrecord(rec, model, t0, tn, colorbar=True, file=None):\n", + " \"\"\"\n", + " Plot a shot record (receiver values over time).\n", + "\n", + " Parameters\n", + " ----------\n", + " rec :\n", + " Receiver data with shape (time, points).\n", + " model : Model\n", + " object that holds the velocity model.\n", + " t0 : int\n", + " Start of time dimension to plot.\n", + " tn : int\n", + " End of time dimension to plot.\n", + " \"\"\"\n", + " scale = np.max(rec) / 10.\n", + " extent = [model.origin[0], model.origin[0] + 1e-3*model.domain_size[0],\n", + " 1e-3*tn, t0]\n", + "\n", + " plot = plt.imshow(rec, vmin=-scale, vmax=scale, cmap=cm.gray, extent=extent)\n", + " plt.xlabel('X position (km)')\n", + " plt.ylabel('Time (s)')\n", + "\n", + " # Create aligned colorbar on the right\n", + " if colorbar:\n", + " ax = plt.gca()\n", + " divider = make_axes_locatable(ax)\n", + " cax = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", + " plt.colorbar(plot, cax=cax)\n", + " plt.show() \n", + " \n", + " if file is not None:\n", + " plt.savefig(file)\n", + " print('plotted image saved as {} file'.format(file))\n", + " \n", + " plt.clf()\n", + "\n", + "def main(output_folder): \n", + " # 1. Define the physical problem\n", + " # The first step is to define the physical model:\n", + " # - physical dimensions of interest\n", + " # - velocity profile of this physical domain\n", + "\n", + " # Define a physical size\n", + " shape = (101, 101) # Number of grid point (nx, nz)\n", + " spacing = (10., 10.) # Grid spacing in m. The domain size is now 1km by 1km\n", + " origin = (0., 0.) # What is the location of the top left corner. This is necessary to define\n", + " # the absolute location of the source and receivers\n", + "\n", + " # Define a velocity profile. The velocity is in km/s\n", + " v = np.empty(shape, dtype=np.float32)\n", + " v[:, :51] = 1.5\n", + " v[:, 51:] = 2.5\n", + "\n", + " # With the velocity and model size defined, we can create the seismic model that\n", + " # encapsulates this properties. We also define the size of the absorbing layer as 10 grid points\n", + " model = Model(vp=v, origin=origin, shape=shape, spacing=spacing,\n", + " space_order=2, nbpml=10)\n", + "\n", + " plot_velocity(model, \n", + " file= os.path.join(*( [output_folder,'output000.png'])))\n", + " \n", + " # 2. Acquisition geometry\n", + " t0 = 0. # Simulation starts a t=0\n", + " tn = 1000. # Simulation last 1 second (1000 ms)\n", + " dt = model.critical_dt # Time step from model grid spacing\n", + "\n", + " time_range = TimeAxis(start=t0, stop=tn, step=dt)\n", + " from examples.seismic import RickerSource\n", + "\n", + " f0 = 0.010 # Source peak frequency is 10Hz (0.010 kHz)\n", + " src = RickerSource(name='src', grid=model.grid, f0=f0,\n", + " npoint=1, time_range=time_range)\n", + "\n", + " # First, position source centrally in all dimensions, then set depth\n", + " src.coordinates.data[0, :] = np.array(model.domain_size) * .5\n", + " src.coordinates.data[0, -1] = 20. # Depth is 20m\n", + "\n", + " # We can plot the time signature to see the wavelet\n", + "# src.show()\n", + "\n", + " # Create symbol for 101 receivers\n", + " rec = Receiver(name='rec', grid=model.grid, npoint=101, time_range=time_range)\n", + "\n", + " # Prescribe even spacing for receivers along the x-axis\n", + " rec.coordinates.data[:, 0] = np.linspace(0, model.domain_size[0], num=101)\n", + " rec.coordinates.data[:, 1] = 20. # Depth is 20m\n", + "\n", + " # We can now show the source and receivers within our domain:\n", + " # Red dot: Source location\n", + " # Green dots: Receiver locations (every 4th point)\n", + " plot_velocity(model, source=src.coordinates.data,\n", + " receiver=rec.coordinates.data[::4, :], \n", + " file= os.path.join(*( [output_folder,'output010.png'])))\n", + " \n", + " # Define the wavefield with the size of the model and the time dimension\n", + " u = TimeFunction(name=\"u\", grid=model.grid, time_order=2, space_order=2)\n", + "\n", + " # We can now write the PDE\n", + " pde = model.m * u.dt2 - u.laplace + model.damp * u.dt\n", + "\n", + " # The PDE representation is as on paper\n", + " pde\n", + " \n", + " # This discrete PDE can be solved in a time-marching way updating u(t+dt) from the previous time step\n", + " # Devito as a shortcut for u(t+dt) which is u.forward. We can then rewrite the PDE as \n", + " # a time marching updating equation known as a stencil using customized SymPy functions\n", + "\n", + " stencil = Eq(u.forward, solve(pde, u.forward))\n", + " # Finally we define the source injection and receiver read function to generate the corresponding code\n", + " src_term = src.inject(field=u.forward, expr=src * dt**2 / model.m)\n", + "\n", + " # Create interpolation expression for receivers\n", + " rec_term = rec.interpolate(expr=u.forward)\n", + "\n", + " op = Operator([stencil] + src_term + rec_term, subs=model.spacing_map)\n", + " \n", + " op(time=time_range.num-1, dt=model.critical_dt)\n", + " plot_shotrecord(rec.data, model, t0, tn, \n", + " file= os.path.join(*( [output_folder,'output020.png'])))\n", + "\n", + "if __name__ == \"__main__\":\n", + " parser = argparse.ArgumentParser()\n", + " parser.add_argument('--output_folder', type=str, nargs='?', \\\n", + " dest='output_folder', help='ouput artifacts location',\\\n", + " default='.')\n", + " args = parser.parse_args()\n", + " \n", + " main(args.output_folder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Get experimentation docker image for devito" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", + "docker_image_name = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", + "if image_version!=\"\":\n", + " docker_image_name = docker_image_name +':'+ image_version\n", + "\n", + "full_docker_image_name = docker_repo_name + '/' + docker_image_name\n", + " \n", + "docker_image_name\n", + "full_docker_image_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Extract/decide the python path in custom docker image that corresponds to desired conda environment. Without this, AzureML tries to create a separate environment." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Login Succeeded\r\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\r\n", + "Configure a credential helper to remove this warning. See\r\n", + "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", + "\r\n", + "\u001b[0m" + ] + } + ], + "source": [ + "%dotenv $dotenv_file_path\n", + "cli_command='az acr login --name '+\\\n", + "os.getenv('ACR_NAME')+\\\n", + "' --username '+os.getenv('ACR_USERNAME') + ' --password ' + os.getenv('ACR_PASSWORD')\n", + "! $cli_command" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 /bin/bash -c \"which python\" '" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'/opt/conda/envs/fwi01_conda_env/bin/python'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_Python_path_command='docker run -i --rm --name fwi01_azureml_container02 '+ \\\n", + "full_docker_image_name + \\\n", + "' /bin/bash -c \"which python\" '\n", + "get_Python_path_command\n", + "\n", + "\n", + "import subprocess\n", + "python_path_in_docker_image = subprocess.check_output(get_Python_path_command,shell=True,stderr=subprocess.STDOUT).\\\n", + "decode('utf-8').strip()\n", + "python_path_in_docker_image" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "#### Create azureml_script_file that invokes:\n", + " - devito exclusive custom edited training_script_file\n", + " - unedited devito notebooks via papermill (invoked via cli and via ppapermill python API)\n", + "\n", + "[Back](#devito_in_AzureML_demoing_modes) to notebook summary." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./../temp/devito_tutorial/azureml_01_modelling.py\n" + ] + } + ], + "source": [ + "%%writefile $azureml_training_script_full_file_name\n", + "\n", + "import argparse\n", + "import os\n", + "os.system('conda env list')\n", + "\n", + "import azureml.core;\n", + "from azureml.core.run import Run\n", + "\n", + "print(azureml.core.VERSION)\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "parser.add_argument('--output_folder', type=str, dest='output_folder', help='ouput artifacts location')\n", + "\n", + "args = parser.parse_args()\n", + "print('args.output_folder is {} but it will be ignored since AzureML_tracked ./outputs will be used'.format(args.output_folder))\n", + "\n", + "# get the Azure ML run object\n", + "run = Run.get_context()\n", + "\n", + "# ./outputs/ folder is autotracked so should get uploaded at the end of the run\n", + "output_dir_AzureML_tracked = './outputs'\n", + "\n", + "crt_dir = os.getcwd()\n", + "\n", + "cli_command= \\\n", + "'cd /devito; /opt/conda/envs/fwi01_conda_env/bin/python '+ crt_dir +'/01_modelling.py' + \\\n", + "' --output_folder '+ crt_dir + output_dir_AzureML_tracked+ '/' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/01_modelling.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/devito_cli_py.log'\n", + "print('Running devito from cli on 01_modelling.py----BEGIN-----:') \n", + "print(cli_command); print('\\n');os.system(cli_command)\n", + "print('Running devito from cli on 01_modelling.py----END-----:\\n\\n')\n", + "\n", + "cli_command= \\\n", + "'cd /devito; papermill ' + \\\n", + "'./examples/seismic/tutorials/02_rtm.ipynb '+\\\n", + "crt_dir +'/outputs/02_rtm_output.ipynb ' + \\\n", + "'--log-output --no-progress-bar --kernel python3 ' + \\\n", + "' > '+ crt_dir + output_dir_AzureML_tracked + '/02_rtm_output.log' \n", + "# + \\\n", + "# ' 2>&1 ' + crt_dir +'/'+ output_dir_AzureML_tracked + '/papermill_cli.log'\n", + "\n", + "# FIXME - activate right conda env for running papermill from cli\n", + "activate_right_conda_env_fixed = False\n", + "if activate_right_conda_env_fixed:\n", + " print('Running papermill from cli on 02_rtm.ipynb----BEGIN-----:') \n", + " print(cli_command); print('\\n');os.system(cli_command)\n", + " print('Running papermill from cli on 02_rtm.ipynb----END-----:\\n\\n') \n", + "\n", + "\n", + "print('Running papermill from Python API on 03_fwi.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/03_fwi.ipynb',\n", + " crt_dir +'/outputs/03_fwi_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 03_fwi.ipynb----END-----:') \n", + "\n", + "print('Running papermill from Python API on 04_dask.ipynb----BEGIN-----:') \n", + "import papermill as pm\n", + "os.chdir('/devito')\n", + "pm.execute_notebook(\n", + " './examples/seismic/tutorials/04_dask.ipynb',\n", + " crt_dir +'/outputs/04_dask_output.ipynb'\n", + ")\n", + "print('Running papermill from Python API on 04_dask.ipynb----END-----:') \n", + " \n", + "\n", + "os.system('pwd')\n", + "os.system('ls -l /')\n", + "os.system('ls -l ./')\n", + "os.system('ls -l ' +crt_dir + output_dir_AzureML_tracked)\n", + "run.log('training_message01: ', 'finished experiment')\n", + "print('\\n')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['azureml_01_modelling.py', '01_modelling.py']" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "script_path=os.path.join(*(script_folder))\n", + "os.listdir(script_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize workspace\n", + "\n", + "Initialize a workspace object from persisted configuration. If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, make sure the config file is present at .\\config.json" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING - Warning: Falling back to use azure cli login credentials.\n", + "If you run your code in unattended mode, i.e., where you can't give a user input, then we recommend to use ServicePrincipalAuthentication or MsiAuthentication.\n", + "Please refer to aka.ms/aml-notebook-auth for different authentication mechanisms in azureml-sdk.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Workspace name: ghiordanfwiws\n", + "Azure region: eastus2\n", + "Subscription id: 7899\n" + ] + } + ], + "source": [ + "ws = Workspace.from_config(\n", + " path=os.path.join(os.getcwd(),\n", + " os.path.join(*([workspace_config_dir, '.azureml', workspace_config_file]))))\n", + "print('Workspace name: ' + ws.name, \n", + " 'Azure region: ' + ws.location, \n", + " 'Subscription id: ' + ws.subscription_id[0:4], sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create an Azure ML experiment\n", + "Let's create an experiment named \"tf-mnist\" and a folder to hold the training scripts. The script runs will be recorded under the experiment in Azure." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "exp = Experiment(workspace=ws, name=experimentName)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve or create a Azure Machine Learning compute\n", + "Azure Machine Learning Compute is a service for provisioning and managing clusters of Azure virtual machines for running machine learning workloads. Let's create a new Azure Machine Learning Compute in the current workspace, if it doesn't already exist. We will then run the training script on this compute target.\n", + "\n", + "If we could not find the compute with the given name in the previous cell, then we will create a new compute here. This process is broken down into the following steps:\n", + "\n", + "1. Create the configuration\n", + "2. Create the Azure Machine Learning compute\n", + "\n", + "**This process will take a few minutes and is providing only sparse output in the process. Please make sure to wait until the call returns before moving to the next cell.**" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gpuclstfwi02'" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", + "gpu_cluster_name" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found existing gpu cluster\n" + ] + } + ], + "source": [ + "# Verify that cluster does not exist already\n", + "max_nodes_value = 5\n", + "try:\n", + " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", + " print(\"Found existing gpu cluster\")\n", + "except ComputeTargetException:\n", + " print(\"Could not find ComputeTarget cluster!\")\n", + " \n", + "# # Create a new gpucluster using code below\n", + "# # Specify the configuration for the new cluster\n", + "# compute_config = AmlCompute.provisioning_configuration(vm_size=\"Standard_NC6\",\n", + "# min_nodes=0,\n", + "# max_nodes=max_nodes_value)\n", + "# # Create the cluster with the specified name and configuration\n", + "# gpu_cluster = ComputeTarget.create(ws, gpu_cluster_name, compute_config)\n", + "\n", + "# # Wait for the cluster to complete, show the output log\n", + "# gpu_cluster.wait_for_completion(show_output=True)\n", + " \n", + " \n", + "# for demo purposes, show how clsuter properties can be altered post-creation\n", + "gpu_cluster.update(min_nodes=0, max_nodes=max_nodes_value, idle_seconds_before_scaledown=1200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create an Azure ML SDK estimator with custom docker image " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f94f884975d14f94977e26fb8e35745f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'NOTSET',…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"created_utc\": \"2019-12-04T17:21:06.014446Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-04T17:30:00.733408Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=XtrqU6IMV%2B48BKF%2BQtFwy6LRoQjGkEJ2ALnJqe4N86Y%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=Z5mXvd7qJNNv2tWMiT1C25Tc%2Bu%2BblrWqkN6UFUzge2U%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=dOviChtDoL5vak4hJd2Pw3Pjj%2BkN5BmxbcPiW6ezqK0%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=1rch4YL2wvgtosymal8WdNdNW6K34Om02P0TMcWcjyc%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=DlifA0Xe8zotaCGo56XNplmG4xCoY0eegb1kFvRaOXg%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=8myM2hR0Cph66FXGtG4YmjmYoP%2F5c3bDBXacYucjnzI%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"logs/azureml/730_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/logs/azureml/730_azureml.log?sv=2019-02-02&sr=b&sig=AE4JyiNXVvzRYF8nTY0BrwltFPiXXIwSga9AlzFDEd8%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=Wk8VGtb5XKlUzkOqIRVsnhvdT3MGSH%2B%2BJEuS63MP0%2BI%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"logs/azureml/730_azureml.log\"]], \"run_duration\": \"0:08:54\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-04 17:28:21,458|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-04 17:28:21,458|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-04 17:28:21,459|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-04 17:28:21,459|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-04 17:28:21,801|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-04 17:28:21,801|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-04 17:28:22,775|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-04 17:28:22,780|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-04 17:28:22,785|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-04 17:28:22,790|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-04 17:28:22,792|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:28:22,798|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,799|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-04 17:28:22,799|azureml.core.authentication|DEBUG|Time to expire 1813963.200661 seconds\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,961|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,967|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,975|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,980|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,985|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,990|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,991|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-04 17:28:22,991|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:28:22,991|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|None\\n2019-12-04 17:28:22,992|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:28:22,992|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:28:22,993|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:28:22,993|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:28:23 GMT'\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:28:23,584|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1460,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-04T17:21:06.014446+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-04T17:25:54.100925+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575480062_180862b6/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-04 17:28:23,589|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-04 17:28:23,590|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'mlflow.source.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-04 17:28:23,590|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-04 17:28:23,591|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-04 17:28:23,591|azureml.WorkerPool|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-04 17:28:23,592|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:28:26,647|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,649|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,654|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,655|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-04 17:28:26,660|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,666|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,672|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,677|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,677|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-04 17:28:26,677|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|None\\n2019-12-04 17:28:26,678|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:28:27 GMT'\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1460,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-04T17:21:06.014446+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-04T17:25:54.100925+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575480062_180862b6/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-04 17:28:27,361|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-04 17:28:27,362|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'mlflow.source.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-04 17:28:27,362|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-04 17:28:52,796|azureml.core.authentication|DEBUG|Time to expire 1813933.203089 seconds\\n2019-12-04 17:29:22,797|azureml.core.authentication|DEBUG|Time to expire 1813903.202753 seconds\\n2019-12-04 17:29:37,671|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-04 17:29:37,671|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:29:37,672|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:29:37,708|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-04 17:29:37,708|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|INFO|complete is not setting status for submitted runs.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-04 17:29:37,710|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-04 17:29:37,711|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-04 17:29:37,711|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-04 17:29:37,712|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:29:37,713|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-04 17:29:37,713|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:29:37,713|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-04 17:29:37,713|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6/batch/metrics'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,715|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,715|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-04 17:29:37,715|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-04 17:29:37,716|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"db9cd91b-ba34-41bf-aad8-b744d46471a4\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-04T17:29:37.671117Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:29:37 GMT'\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG|\\n2019-12-04 17:29:37,835|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00024700164794921875 seconds.\\n\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-04 17:29:37,967|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# use a custom Docker image\n", + "from azureml.core.container_registry import ContainerRegistry\n", + "\n", + "image_name = docker_image_name\n", + "\n", + "# you can also point to an image in a private ACR\n", + "image_registry_details = ContainerRegistry()\n", + "image_registry_details.address = docker_repo_name\n", + "image_registry_details.username = os.getenv('ACR_USERNAME')\n", + "image_registry_details.password = os.getenv('ACR_PASSWORD') \n", + "\n", + "# don't let the system build a new conda environment\n", + "user_managed_dependencies = True\n", + "\n", + "# submit to a local Docker container. if you don't have Docker engine running locally, you can set compute_target to cpu_cluster.\n", + "script_params = {\n", + " '--output_folder': 'some_folder'\n", + "}\n", + "\n", + "\n", + "# distributed_training_conf = MpiConfiguration()\n", + "# distributed_training_conf.process_count_per_node = 2\n", + "\n", + "est = Estimator(source_directory=script_path, \n", + " compute_target=gpu_cluster,#'local', #gpu_cluster, \n", + " entry_script=azureml_training_script_file,\n", + " script_params=script_params,\n", + " use_docker=True,\n", + " custom_docker_image=image_name,\n", + " # uncomment below line to use your private ACR\n", + " image_registry_details=image_registry_details, \n", + " user_managed=user_managed_dependencies,\n", + " distributed_training=None,\n", + " node_count=1\n", + " )\n", + "est.run_config.environment.python.interpreter_path = python_path_in_docker_image\n", + "\n", + "run = exp.submit(est)\n", + "RunDetails(run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One can use the above link to currrent experiment run in Azure Portal to see tracked metrics, and images and output notebooks saved by azureml_training_script_full_file_name in {run_dir}/outputs on the remote compute target that are automatically saved by AzureML in the run history Azure portal pages." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'runId= 020_AzureMLEstimator_1575480062_180862b6'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "'experimentation baseImage: fwi01_azureml:sdk.v1.0.76'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_details = run.get_details()\n", + "\n", + "# print some details of job run\n", + "'runId= {}'.format(run_details['runId'])\n", + "'experimentation baseImage: {}'.format(run_details['runDefinition']['environment']['docker']['baseImage'])" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!\n" + ] + } + ], + "source": [ + "print('Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb similarity index 63% rename from contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb rename to contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb index 59843f6e..f6b40ef1 100755 --- a/contrib/examples/imaging/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -4,6 +4,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "Copyright (c) Microsoft Corporation. \n", + "Licensed under the MIT License. \n", + " \n", + "\n", "# FWI demo based on: \n", "This project ports devito (https://github.com/opesci/devito) into Azure and runs tutorial notebooks at:\n", "https://nbviewer.jupyter.org/github/opesci/devito/blob/master/examples/seismic/tutorials/\n", @@ -71,13 +75,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.69\n" + "Azure ML SDK Version: 1.0.76\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1061-azure-x86_64-with-debian-10.0'" + "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" ] }, "execution_count": 3, @@ -87,7 +91,7 @@ { "data": { "text/plain": [ - "'/workspace/examples/imaging/azureml_devito/notebooks'" + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -477,7 +481,7 @@ { "data": { "text/plain": [ - "'fwi01_azureml:sdk.v1.0.69'" + "'fwi01_azureml:sdk.v1.0.76'" ] }, "execution_count": 9, @@ -487,7 +491,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" ] }, "execution_count": 9, @@ -497,9 +501,10 @@ ], "source": [ "docker_repo_name = os.getenv('ACR_NAME')+'.azurecr.io' # or os.getenv('DOCKER_LOGIN')\n", - "docker_image_name = os.getenv('EXPERIMENTATION_IMAGE_TAG')\n", "\n", - "image_version = os.getenv('EXPERIMENTATION_IMAGE_VERSION')\n", + "docker_image_name = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_NAME')\n", + "\n", + "image_version = os.getenv('EXPERIMENTATION_DOCKER_IMAGE_TAG')\n", "if image_version!=\"\":\n", " docker_image_name = docker_image_name +':'+ image_version\n", "\n", @@ -524,7 +529,7 @@ { "data": { "text/plain": [ - "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.69 /bin/bash -c \"which python\" '" + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 /bin/bash -c \"which python\" '" ] }, "execution_count": 10, @@ -695,6 +700,15 @@ "execution_count": 13, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING - Warning: Falling back to use azure cli login credentials.\n", + "If you run your code in unattended mode, i.e., where you can't give a user input, then we recommend to use ServicePrincipalAuthentication or MsiAuthentication.\n", + "Please refer to aka.ms/aml-notebook-auth for different authentication mechanisms in azureml-sdk.\n" + ] + }, { "name": "stdout", "output_type": "stream", @@ -754,7 +768,7 @@ { "data": { "text/plain": [ - "'gpuclstfwi02'" + "'gpuclstfwi08'" ] }, "execution_count": 15, @@ -764,6 +778,7 @@ ], "source": [ "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", + "gpu_cluster_name = 'gpuclstfwi08'\n", "gpu_cluster_name" ] }, @@ -820,16 +835,23 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "2066a8170a4e4c11ada50b453920959b", + "model_id": "edb374a7e72648cd892b24fb0cc8c2b6", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'NOTSET',…" ] }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575481417_8013866e?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575481417_8013866e\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575481417_8013866e\", \"created_utc\": \"2019-12-04T17:43:40.27579Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":1,\\\"CurrentNodeCount\\\":2}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575481417_8013866e/azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt?sv=2019-02-02&sr=b&sig=lVAX2nGZV7C62e8t0DZkL19PV8vYW8dKjDExkyOnHj0%3D&st=2019-12-04T17%3A38%3A58Z&se=2019-12-05T01%3A48%3A58Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt\"]], \"run_duration\": \"0:05:18\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2019-12-04T17:48:07Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.76: Pulling from fwi01_azureml\\n1ab2bdfe9778: Pulling fs layer\\ndd7d28bd8be5: Pulling fs layer\\naf998e3a361b: Pulling fs layer\\n1f706d9b369a: Pulling fs layer\\ne5000b763efd: Pulling fs layer\\n8c572c1c1101: Pulling fs layer\\n5988e7f62200: Pulling fs layer\\n8c572c1c1101: Waiting\\n5988e7f62200: Waiting\\ne5000b763efd: Waiting\\n1f706d9b369a: Waiting\\naf998e3a361b: Verifying Checksum\\naf998e3a361b: Download complete\\ndd7d28bd8be5: Verifying Checksum\\ndd7d28bd8be5: Download complete\\ne5000b763efd: Verifying Checksum\\ne5000b763efd: Download complete\\n1ab2bdfe9778: Verifying Checksum\\n1ab2bdfe9778: Download complete\\n1f706d9b369a: Verifying Checksum\\n1f706d9b369a: Download complete\\n1ab2bdfe9778: Pull complete\\n5988e7f62200: Verifying Checksum\\n5988e7f62200: Download complete\\n8c572c1c1101: Verifying Checksum\\n8c572c1c1101: Download complete\\ndd7d28bd8be5: Pull complete\\naf998e3a361b: Pull complete\\n1f706d9b369a: Pull complete\\ne5000b763efd: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -885,51 +907,353 @@ "cell_type": "code", "execution_count": 18, "metadata": {}, + "outputs": [], + "source": [ + "response = run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final print 6, time 13.138 seconds: Counter({'Completed': 1})\r" + ] + } + ], + "source": [ + "import time\n", + "from collections import Counter\n", + "#wait till all jobs finished\n", + "\n", + "def wait_for_run_list_to_finish(the_run_list):\n", + " finished_status_list = ['Completed', 'Failed']\n", + " printing_counter = 0\n", + " start_time = time.time()\n", + " while (not all((crt_queried_job.get_status() in finished_status_list) for crt_queried_job in the_run_list)):\n", + " time.sleep(2)\n", + " printing_counter+= 1\n", + " print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\")\n", + "# final status\n", + " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", + "wait_for_run_list_to_finish([run])" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "run_duration in seconds 241.125075\n", + "run_duration= 4m 1.125s\n" + ] + } + ], + "source": [ + "import datetime, math\n", + "def get_run_duration(azureml_exp_run):\n", + " run_details = azureml_exp_run.get_details()\n", + " run_duration = datetime.datetime.strptime(run_details['endTimeUtc'], \"%Y-%m-%dT%H:%M:%S.%fZ\") - \\\n", + " datetime.datetime.strptime(run_details['startTimeUtc'], \"%Y-%m-%dT%H:%M:%S.%fZ\")\n", + " return run_duration.total_seconds()\n", + "run_duration = get_run_duration(run)\n", + "\n", + "run_seconds, run_minutes = math.modf(run_duration/60)\n", + "print('run_duration in seconds {}'.format(run_duration))\n", + "print('run_duration= {0:.0f}m {1:.3f}s'.format(run_minutes, run_seconds*60))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Showing details for run 15\n" + ] + }, { "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "78a6832f42fc4bb1bece76520e305f54", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "'runId= 020_AzureMLEstimator_1572793621_e35441c0'" + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'NOTSET',…" ] }, - "execution_count": 18, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/aml.mini.widget.v1": "{\"status\": \"Queued\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575482070_cc2fb88e?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575482070_cc2fb88e\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575482070_cc2fb88e\", \"created_utc\": \"2019-12-04T17:54:31.673448Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":0,\\\"RunningNodeCount\\\":10,\\\"CurrentNodeCount\\\":10}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Queued\", \"log_files\": {}, \"log_groups\": [], \"run_duration\": \"0:05:16\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"Your job is submitted in Azure cloud and we are monitoring to get logs...\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Counter16: submission of job 16 on 10 nodes took 9.082231044769287 seconds \n", + "run list length 16\n", + "Counter17: submission of job 17 on 10 nodes took 9.493095874786377 seconds \n", + "run list length 17\n", + "Counter18: submission of job 18 on 10 nodes took 11.00885009765625 seconds \n", + "run list length 18\n", + "Counter19: submission of job 19 on 10 nodes took 8.174627780914307 seconds \n", + "run list length 19\n" + ] + } + ], + "source": [ + "import time\n", + "from IPython.display import clear_output\n", + "\n", + "no_of_jobs = 20\n", + "no_of_nodes = 10\n", + "\n", + "job_counter = 0\n", + "print_cycle = 7\n", + "run_list = []\n", + "submit_time_list = []\n", + "for crt_nodes in range(no_of_nodes, (no_of_nodes+1)):\n", + " gpu_cluster.update(min_nodes=0, max_nodes=crt_nodes, idle_seconds_before_scaledown=1200)\n", + " clust_start_time = time.time()\n", + " for crt_job in range(1, no_of_jobs):\n", + " job_counter+= 1\n", + " start_time = time.time()\n", + " run = exp.submit(est)\n", + " end_time = time.time()\n", + " run_time = end_time - start_time\n", + " run_list.append(run)\n", + " submit_time_list.append(run_time)\n", + " print('Counter{}: submission of job {} on {} nodes took {} seconds '.format(job_counter, crt_job, crt_nodes, run_time))\n", + " print('run list length {}'.format(len(run_list)))\n", + " if ((job_counter-1) % print_cycle) == 0:\n", + " clear_output()\n", + " print('Showing details for run {}'.format(job_counter))\n", + " RunDetails(run).show()\n", + "# [all_jobs_done = True if (('Completed'==crt_queried_job.get_status()) for crt_queried_job in run_list)]" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 9.56427002, 10.400774 , 9.58173323, 10.15340447, 11.36087322,\n", + " 12.94680119, 10.28511715, 9.50729609, 10.37926507, 8.8516674 ,\n", + " 8.78578663, 10.88563776, 10.30812001, 8.43333387, 8.51853824,\n", + " 9.08223104, 9.49309587, 11.0088501 , 8.17462778])" + ] + }, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ - "'experimentation baseImage: fwi01_azureml:sdk.v1.0.69'" + "(array([0, 0, 0, 0, 1, 2, 3, 2, 2]),\n", + " array([ 6. , 6.44444444, 6.88888889, 7.33333333, 7.77777778,\n", + " 8.22222222, 8.66666667, 9.11111111, 9.55555556, 10. ]))" ] }, - "execution_count": 18, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "run_details = run.get_details()\n", + "import numpy as np\n", + "np.asarray(submit_time_list)\n", + "np.histogram(np.asarray(submit_time_list), bins=np.linspace(6.0, 10.0, num=10), density=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final print 130, time 478.619 seconds: Counter({'Completed': 19})izing': 1})'Running': 1})Queued': 1})\r" + ] + } + ], + "source": [ + "def wait_for_run_list_to_finish(the_run_list, plot_results=True):\n", + " finished_status_list = ['Completed', 'Failed']\n", + " printing_counter = 0\n", + " start_time = time.time()\n", + " while (not all((crt_queried_job.get_status() in finished_status_list) for crt_queried_job in the_run_list)):\n", + " time.sleep(2)\n", + " printing_counter+= 1\n", + " crt_status = Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list])\n", + " print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(crt_status)), end=\"\\r\")\n", + " if plot_results:\n", + "# import numpy as np\n", + " import matplotlib.pyplot as plt\n", + " plt.bar(crt_status.keys(), crt_status.values())\n", + " plt.show()\n", + " \n", + "# indexes = np.arange(len(labels))\n", + "# width = 1\n", + "\n", + "# plt.bar(indexes, values, width)\n", + "# plt.xticks(indexes + width * 0.5, labels)\n", + "# plt.show()\n", + "\n", + "# from pandas import Series\n", + "# crt_status = Series([crt_queried_job.get_status() for crt_queried_job in the_run_list])\n", + "# status_counts = crt_status.value_counts().sort_index()\n", + "# print('print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + "# str(status_counts)), end=\"\\r\")\n", + "# final status\n", + " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", + " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", "\n", - "# print some details of job run\n", - "'runId= {}'.format(run_details['runId'])\n", - "'experimentation baseImage: {}'.format(run_details['runDefinition']['environment']['docker']['baseImage'])" + " \n", + " \n", + "wait_for_run_list_to_finish(run_list, plot_results=False)" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "run_durations = [get_run_duration(crt_queried_job) for crt_queried_job in run_list]\n", + "run_statuses = [crt_queried_job.get_status() for crt_queried_job in run_list]" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([18, 0, 12, 4, 13, 17, 16, 1, 14, 15, 9, 10, 2, 8, 3, 5, 7,\n", + " 11, 6])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 95.714705 97.874433 101.482868 104.059911 104.543689 104.735191\n", + " 109.463122 109.99054 110.348304 111.175483 116.1721 237.121394\n", + " 238.584713 239.419802 239.850993 242.752749 243.522224 246.953329\n", + " 250.967768]\n", + "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed']\n" + ] + }, + { + "data": { + "text/plain": [ + "array([18, 0, 12, 4, 13, 17, 16, 1, 14, 15, 9, 10, 2, 8, 3, 5, 7,\n", + " 11, 6])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 95.714705 97.874433 101.482868 104.059911 104.543689 104.735191\n", + " 109.463122 109.99054 110.348304 111.175483 116.1721 237.121394\n", + " 238.584713 239.419802 239.850993 242.752749 243.522224 246.953329\n", + " 250.967768]\n", + "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed']\n" + ] + }, + { + "data": { + "text/plain": [ + "(array([0, 0, 2, 9, 0, 0, 0, 0, 0]),\n", + " array([ 50. , 66.66666667, 83.33333333, 100. ,\n", + " 116.66666667, 133.33333333, 150. , 166.66666667,\n", + " 183.33333333, 200. ]))" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_durations = np.asarray(run_durations)\n", + "run_statuses = np.asarray(run_statuses)\n", + "\n", + "extreme_k = 20\n", + "#longest runs\n", + "indices = np.argsort(run_durations)[-extreme_k:]\n", + "indices\n", + "print(run_durations[indices])\n", + "print(run_statuses[indices])\n", + "#shortest runs\n", + "indices = np.argsort(run_durations)[0:extreme_k]\n", + "indices\n", + "print(run_durations[indices])\n", + "print(run_statuses[indices])\n", + "\n", + "#run_durations histogram - counts and bins\n", + "np.histogram(run_durations, bins=np.linspace(50, 200, num=10), density=False)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!\n" + "Finished running 030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito!\n" ] } ], "source": [ - "print('Finished running 020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito!')" + "print('Finished running 030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito!')" ] } ], @@ -949,7 +1273,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.5" } }, "nbformat": 4, From 7383090df2cc1def83e99e85ece8468cacba3b4e Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Wed, 4 Dec 2019 18:07:43 -0500 Subject: [PATCH 119/207] addressing multiple issues from first bug bash (#81) * added README documentation per bug bush feedback * DOC: added HRNET download info to README * added hrnet download script and tested it * added legal headers to a few scripts. * changed /data to ~data in the main README * added Troubleshooting section to the README --- README.md | 107 +++++++++++++++++++++++++++++++++++--- scripts/data_symlink.sh | 6 ++- scripts/download_hrnet.sh | 19 +++++++ scripts/get_F3_voxel.sh | 2 + 4 files changed, 126 insertions(+), 8 deletions(-) create mode 100755 scripts/download_hrnet.sh diff --git a/README.md b/README.md index 344c5a1d..35349445 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ There are two ways to get started with the DeepSeismic codebase, which currently - if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/staging/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb) - to actually run the code, you'll need to set up a compute environment (which includes setting up a GPU-enabled Linux VM and downloading the appropriate Anaconda Python packages) and download the datasets which you'd like to work with - detailed steps for doing this are provided in the next `Interpretation` section below. +If you run into any problems, chances are your problem has already been solved in the [Troubleshooting](#troubleshooting) section. + ## Interpretation For seismic interpretation, the repository consists of extensible machine learning pipelines, that shows how you can leverage state-of-the-art segmentation algorithms (UNet, SEResNET, HRNet) for seismic interpretation, and also benchmarking results from running these algorithms using various seismic datasets (Dutch F3, and Penobscot). @@ -77,12 +79,14 @@ This repository provides examples on how to run seismic interpretation on two pu To download the Penobscot dataset run the [download_penobscot.sh](scripts/download_penobscot.sh) script, e.g. ``` -data_dir='/data/penobscot' -mkdir $data_dir -./scripts/download_penobscot.sh $data_dir +data_dir="$HOME/data/penobscot" +mkdir -p "$data_dir" +./scripts/download_penobscot.sh "$data_dir" ``` -Note that the specified download location (e.g `/data/penobscot`) should be configured with appropriate `write` pemissions. +Note that the specified download location should be configured with appropriate `write` permissions. On some Linux virtual machines, you may want to place the data into `/mnt` or `/data` folder so you have to make sure you have write access. + +To make things easier, we suggested you use your home directory where you might run out of space. If this happens on an [Azure Data Science Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/) you can resize the disk quite easily from [Azure Portal](https://portal.azure.com) - please see the [Troubleshooting](#troubleshooting) section at the end of this README regarding [how to do this](#how-to-resize-data-science-virtual-machine-disk). To prepare the data for the experiments (e.g. split into train/val/test), please run the following script (modifying arguments as desired): @@ -94,7 +98,7 @@ python scripts/prepare_penobscot.py split_inline --data-dir=/data/penobscot --va To download the F3 Netherlands dataset for 2D experiments, please follow the data download instructions at [this github repository](https://github.com/yalaudah/facies_classification_benchmark) (section Dataset). -Once you've downloaded the data set, make sure to create an empty `splits` directory, under the downloaded `data` directory. This is where your training/test/validation splits will be saved. +Once you've downloaded the data set, make sure to create an empty `splits` directory, under the downloaded `data` directory; you can re-use the same data directory as the one for Penobscot dataset created earlier. This is where your training/test/validation splits will be saved. ``` cd data @@ -166,8 +170,15 @@ We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manag ### Pretrained Models + #### HRNet -To achieve the same results as the benchmarks above you will need to download the HRNet model pretrained on ImageNet. This can be found [here](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk). Download this to your local drive and make sure you add the path to the experiment (or notebook) configuration file. + +To achieve the same results as the benchmarks above you will need to download the HRNet model [pretrained](https://github.com/HRNet/HRNet-Image-Classification) on ImageNet. We are specifically using the [HRNet-W48-C](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk) pre-trained model - download this model to your local drive and make sure you add the path to the experiment (or notebook) configuration file under `TEST.MODEL_PATH` setting. Other HRNet variants are also available [here](https://github.com/HRNet/HRNet-Image-Classification) - you can navigate to those from the [main HRNet landing page](https://github.com/HRNet/HRNet-Object-Detection) for object detection. + +To facilitate easier download on a Linux machine of your choice (or [Azure Data Science Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/) which we recommend), we created an automated download scipt for you, just run +```bash +./scripts/download_hrnet.sh 'your_folder_to_store_the_model' +``` ### Viewers (optional) @@ -255,4 +266,88 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope [Microsoft AI Labs Github](https://aka.ms/ai-labs) Find other Best Practice projects, and Azure AI design patterns in our central repository. +# Troubleshooting + +
+ Data Science Virtual Machine conda package installation errors + + It could happen that you don't have sufficient permissions to run conda commands / install packages in an Anaconda packages directory. To remedy the situation, please run the following commands + ```bash + rm -rf /anaconda/pkgs/* + sudo chown -R $(whoami) /anaconda + ``` + + After these commands complete, try installing the packages again. + +
+ +
+ Model training or scoring is not using GPU + + To see if GPU is being using while your model is being trained or used for inference, run + ```bash + nvidia-smi + ``` + and confirm that you see you Python process using the GPU. + + If not, you may want to try reverting to an older version of CUDA for use with pyTorch. After the environment has been setup, run the following command (by default we use CUDA 10) after running `conda activate seismic-interpretation` to activate the conda environment: + ```bash + conda install pytorch torchvision cudatoolkit=9.2 -c pytorch + ``` + + To test whether this setup worked, right after you can open `ipython` and execute the following code + ```python + import torch + torch.cuda.is_available() + ``` + + The output should say "True". + + If the output is still "False", you may want to try setting your environment variable to specify the device manually - to test this, start a new `ipython` session and type: + ```python + import os + os.environ['CUDA_VISIBLE_DEVICES']='0' + import torch + torch.cuda.is_available() + ``` + + Output should say "True" this time. If it does, you can make the change permanent by adding + ```bash + export CUDA_VISIBLE_DEVICES=0 + ``` + to your `$HOME/.bashrc` file. + +
+ +
+ GPU out of memory errors + + You should be able to see how much GPU memory your process is using by running + ```bash + nvidia-smi + ``` + and seeing if this amount is close to the physical memory limit specified by the GPU manufacturer. + + If we're getting close to the memory limit, you may want to lower the batch size in the model configuration file. Specifically, `TRAIN.BATCH_SIZE_PER_GPU` and `VALIDATION.BATCH_SIZE_PER_GPU` settings. + +
+ +
+ How to resize Data Science Virtual Machine disk + + 1. Go to the [Azure Portal](https://portal.azure.com) and find your virtual machine by typing its name in the search bar at the very top of the page. + + 2. In the Overview panel on the left hand side, click Stop button to stop the virtual machine. + + 3. Next, select Disks in the same panel on the left hand side. + + 4. Click the Name of the OS Disk - you'll be navigated to the Disk view. From this view, select Configuration on the left hand side and then increase Size in GB and hit the Save button. + + 5. Navigate back to the Virtual Machine view in Step 2 and click the Start button to start the virtual machine. + +
+ + + + diff --git a/scripts/data_symlink.sh b/scripts/data_symlink.sh index 7f629a6c..31fcc8ca 100755 --- a/scripts/data_symlink.sh +++ b/scripts/data_symlink.sh @@ -1,9 +1,11 @@ #!/bin/bash +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. # Azure VMs lose mounts after restart - this symlinks the data folder from user's # home directory after VM restart user=$(whoami) -sudo chown -R $user /mnt -sudo chgrp -R $user /mnt +sudo chown -R ${user} /mnt +sudo chgrp -R ${user} /mnt ln -s ~/dutchf3 /mnt diff --git a/scripts/download_hrnet.sh b/scripts/download_hrnet.sh new file mode 100755 index 00000000..ea5bc059 --- /dev/null +++ b/scripts/download_hrnet.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +echo Using "$1" as the download directory + +if [ ! -d "$1" ] +then + echo "Directory does not exist - creating..." + mkdir -p "$1" +fi + +full_path=$1/$2 + +echo "Downloading to ${full_path}" + +wget \ +'https://optgaw.dm.files.1drv.com/y4mYRZ1J12ATHBWxSwZJTycIFbgT88SoWegSN3NXEOJFTJWBLDZ3nyCbj-sDvmGobL2CAGzhPobs5gMt3466nKbATNs9toc5N569Z5xNicNUABQm0MVucO7Vi7cjP__n2MFL5qDZyL4cOx6VgoNjpb9lglVRqoTVfVHdJ3sM7qO-9sAODNzgmKrCrU7uHvB54YtsKLr51Qi6BlDn94DalmEJQ/hrnetv2_w48_imagenet_pretrained.pth?download&psid=1' \ +--output-document "${full_path}" diff --git a/scripts/get_F3_voxel.sh b/scripts/get_F3_voxel.sh index 77320fd8..83431338 100755 --- a/scripts/get_F3_voxel.sh +++ b/scripts/get_F3_voxel.sh @@ -1,4 +1,6 @@ #!/bin/bash +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. echo "Downloading Dutch F3 from https://github.com/bolgebrygg/MalenoV" # fetch Dutch F3 from Malenov project. From 62237ecab7873f1d2158a1165cb49e8eb808cfe7 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Wed, 4 Dec 2019 19:08:59 -0500 Subject: [PATCH 120/207] Dciborow/build bug (#68) * Update unit_test_steps.yml * Update environment.yml * Update setup_step.yml * Update setup_step.yml * Update unit_test_steps.yml * Update setup_step.yml --- .ci/steps/setup_step.yml | 6 +++++- environment/anaconda/local/environment.yml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.ci/steps/setup_step.yml b/.ci/steps/setup_step.yml index 6a51a9d0..816018c7 100644 --- a/.ci/steps/setup_step.yml +++ b/.ci/steps/setup_step.yml @@ -5,11 +5,15 @@ parameters: conda: seismic-interpretation steps: + +- bash: | + echo "##vso[task.prependpath]$CONDA/bin" + - bash: | echo "Running setup..." # make sure we have the latest and greatest - conda env create -f environment/anaconda/local/environment.yml --force + conda env create -f environment/anaconda/local/environment.yml python=3.6 --force source activate ${{parameters.conda}} pip install -e interpretation pip install -e cv_lib diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 9c533880..bcba252e 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -12,7 +12,7 @@ dependencies: - torchvision==0.4.2 - pandas==0.25.3 - opencv==4.1.2 - - scikit-learn=0.21.3 + - scikit-learn==0.21.3 - tensorflow==2.0 - tqdm==4.39.0 - itkwidgets==0.23.1 From 1e60d815877cfa00644d897650d313af9e5f6aec Mon Sep 17 00:00:00 2001 From: Mat Date: Thu, 5 Dec 2019 12:27:58 +0000 Subject: [PATCH 121/207] Adds AzureML libraries (#82) * Adds azure dependencies * Adds AzureML components --- .../azureml_tools/__init__.py | 6 + .../azureml_tools/config.py | 68 +++++ .../azureml_tools/experiment.py | 284 ++++++++++++++++++ .../azureml_tools/resource_group.py | 60 ++++ .../azureml_tools/storage.py | 4 + .../azureml_tools/subscription.py | 97 ++++++ .../azureml_tools/workspace.py | 94 ++++++ interpretation/requirements.txt | 4 +- 8 files changed, 616 insertions(+), 1 deletion(-) create mode 100644 interpretation/deepseismic_interpretation/azureml_tools/__init__.py create mode 100644 interpretation/deepseismic_interpretation/azureml_tools/config.py create mode 100644 interpretation/deepseismic_interpretation/azureml_tools/experiment.py create mode 100644 interpretation/deepseismic_interpretation/azureml_tools/resource_group.py create mode 100644 interpretation/deepseismic_interpretation/azureml_tools/subscription.py create mode 100644 interpretation/deepseismic_interpretation/azureml_tools/workspace.py diff --git a/interpretation/deepseismic_interpretation/azureml_tools/__init__.py b/interpretation/deepseismic_interpretation/azureml_tools/__init__.py new file mode 100644 index 00000000..03c68613 --- /dev/null +++ b/interpretation/deepseismic_interpretation/azureml_tools/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +from deepseismic_interpretation.azureml_tools.workspace import workspace_for_user +from deepseismic_interpretation.azureml_tools.experiment import PyTorchExperiment + diff --git a/interpretation/deepseismic_interpretation/azureml_tools/config.py b/interpretation/deepseismic_interpretation/azureml_tools/config.py new file mode 100644 index 00000000..afcb7fdb --- /dev/null +++ b/interpretation/deepseismic_interpretation/azureml_tools/config.py @@ -0,0 +1,68 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + + +import ast +import logging + +from dotenv import dotenv_values, find_dotenv, set_key + +_DEFAULTS = { + "CLUSTER_NAME": "gpucluster24rv3", + "CLUSTER_VM_SIZE": "Standard_NC24rs_v3", + "CLUSTER_MIN_NODES": 0, + "CLUSTER_MAX_NODES": 2, + "WORKSPACE": "workspace", + "RESOURCE_GROUP": "amlccrg", + "REGION": "eastus", + "DATASTORE_NAME": "datastore", + "CONTAINER_NAME": "container", + "ACCOUNT_NAME": "premiumstorage", + "SUBSCRIPTION_ID": None, +} + + +def load_config(dot_env_path: find_dotenv(raise_error_if_not_found=True)): + """ Load the variables from the .env file + Returns: + .env variables(dict) + """ + logger = logging.getLogger(__name__) + logger.info(f"Found config in {dot_env_path}") + return dotenv_values(dot_env_path) + + +def _convert(value): + try: + return ast.literal_eval(value) + except (ValueError, SyntaxError): + return value + + +class AzureMLConfig: + """Creates AzureMLConfig object + + Stores all the configuration options and syncs them with the .env file + """ + + _reserved = ("_dot_env_path",) + + def __init__(self): + self._dot_env_path = find_dotenv(raise_error_if_not_found=True) + + for k, v in load_config(dot_env_path=self._dot_env_path).items(): + self.__dict__[k] = _convert(v) + + for k, v in _DEFAULTS.items(): + if k not in self.__dict__: + setattr(self, k, v) + + def __setattr__(self, name, value): + if name not in self._reserved: + if not isinstance(value, str): + value = str(value) + set_key(self._dot_env_path, name, value) + self.__dict__[name] = value + + +experiment_config = AzureMLConfig() diff --git a/interpretation/deepseismic_interpretation/azureml_tools/experiment.py b/interpretation/deepseismic_interpretation/azureml_tools/experiment.py new file mode 100644 index 00000000..8353073b --- /dev/null +++ b/interpretation/deepseismic_interpretation/azureml_tools/experiment.py @@ -0,0 +1,284 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + + +import logging +import logging.config +import os + +import azureml.core +from azure.common.credentials import get_cli_profile +from azureml.core import Datastore, Environment +from azureml.core.compute import AmlCompute, ComputeTarget +from azureml.core.compute_target import ComputeTargetException +from azureml.core.conda_dependencies import CondaDependencies +from azureml.core.runconfig import EnvironmentDefinition +from azureml.train.dnn import Gloo, Nccl, PyTorch +from toolz import curry + +from deepseismic_interpretation.azureml_tools import workspace_for_user +from deepseismic_interpretation.azureml_tools.config import experiment_config +from deepseismic_interpretation.azureml_tools.resource_group import create_resource_group +from deepseismic_interpretation.azureml_tools.storage import create_premium_storage +from deepseismic_interpretation.azureml_tools.subscription import select_subscription + +_GPU_IMAGE = "mcr.microsoft.com/azureml/base-gpu:openmpi3.1.2-cuda10.0-cudnn7-ubuntu16.04" + + +def _create_cluster(workspace, cluster_name, vm_size, min_nodes, max_nodes): + """Creates AzureML cluster + + Args: + cluster_name (string): The name you wish to assign the cluster. + vm_size (string): The type of sku to use for your vm. + min_nodes (int): Minimum number of nodes in cluster. + Use 0 if you don't want to incur costs when it isn't being used. + max_nodes (int): Maximum number of nodes in cluster. + + """ + logger = logging.getLogger(__name__) + try: + compute_target = ComputeTarget(workspace=workspace, name=cluster_name) + logger.info("Found existing compute target.") + except ComputeTargetException: + logger.info("Creating a new compute target...") + compute_config = AmlCompute.provisioning_configuration( + vm_size=vm_size, min_nodes=min_nodes, max_nodes=max_nodes + ) + + # create the cluster + compute_target = ComputeTarget.create(workspace, cluster_name, compute_config) + compute_target.wait_for_completion(show_output=True) + + # use get_status() to get a detailed status for the current AmlCompute. + logger.debug(compute_target.get_status().serialize()) + + return compute_target + + +@curry +def _create_estimator( + estimator_class, project_folder, entry_script, compute_target, script_params, node_count, env_def, distributed, +): + logger = logging.getLogger(__name__) + + estimator = estimator_class( + project_folder, + entry_script=entry_script, + compute_target=compute_target, + script_params=script_params, + node_count=node_count, + environment_definition=env_def, + distributed_training=distributed, + ) + + logger.debug(estimator.conda_dependencies.__dict__) + return estimator + + +def _create_datastore( + aml_workspace, datastore_name, container_name, account_name, account_key, create_if_not_exists=True, +): + """Creates datastore + + Args: + datastore_name (string): Name you wish to assign to your datastore. + container_name (string): Name of your container. + account_name (string): Storage account name. + account_key (string): The storage account key. + + Returns: + azureml.core.Datastore + """ + logger = logging.getLogger(__name__) + ds = Datastore.register_azure_blob_container( + workspace=aml_workspace, + datastore_name=datastore_name, + container_name=container_name, + account_name=account_name, + account_key=account_key, + create_if_not_exists=create_if_not_exists, + ) + logger.info(f"Registered existing blob storage: {ds.name}.") + return ds + + +def _check_subscription_id(config): + if config.SUBSCRIPTION_ID is None: + profile = select_subscription() + config.SUBSCRIPTION_ID = profile.get_subscription_id() + return True, f"Selected subscription id is {config.SUBSCRIPTION_ID}" + + +_CHECK_FUNCTIONS = (_check_subscription_id,) + + +class ConfigError(Exception): + pass + + +def _check_config(config): + logger = logging.getLogger(__name__) + check_gen = (f(config) for f in _CHECK_FUNCTIONS) + check_results = list(filter(lambda state_msg: state_msg[0] == False, check_gen)) + if len(check_results) > 0: + error_msgs = "\n".join([msg for state, msg in check_results]) + msg = f"Config failed \n {error_msgs}" + logger.info(msg) + raise ConfigError(msg) + + +class BaseExperiment(object): + def __init__(self, experiment_name, config=experiment_config): + + self._logger = logging.getLogger(__name__) + self._logger.info("SDK version:" + str(azureml.core.VERSION)) + _check_config(config) + + profile = select_subscription(sub_name_or_id=config.SUBSCRIPTION_ID) + profile_credentials, subscription_id, _ = profile.get_login_credentials() + rg = create_resource_group(profile_credentials, subscription_id, config.REGION, config.RESOURCE_GROUP) + prem_str, storage_keys = create_premium_storage( + profile_credentials, subscription_id, config.REGION, config.RESOURCE_GROUP, config.ACCOUNT_NAME, + ) + + self._ws = workspace_for_user( + workspace_name=config.WORKSPACE, + resource_group=config.RESOURCE_GROUP, + subscription_id=config.SUBSCRIPTION_ID, + workspace_region=config.REGION, + ) + self._experiment = azureml.core.Experiment(self._ws, name=experiment_name) + self._cluster = _create_cluster( + self._ws, + cluster_name=config.CLUSTER_NAME, + vm_size=config.CLUSTER_VM_SIZE, + min_nodes=config.CLUSTER_MIN_NODES, + max_nodes=config.CLUSTER_MAX_NODES, + ) + + self._datastore = _create_datastore( + self._ws, + datastore_name=config.DATASTORE_NAME, + container_name=config.CONTAINER_NAME, + account_name=prem_str.name, + account_key=storage_keys["key1"], + ) + + @property + def cluster(self): + return self._cluster + + @property + def datastore(self): + return self._datastore + + +_DISTRIBUTED_DICT = {"nccl": Nccl(), "gloo": Gloo()} + + +def _get_distributed(distributed_string): + if distributed_string is not None: + return _DISTRIBUTED_DICT.get(distributed_string.lower()) + else: + return None + + +def create_environment_from_local(name="amlenv", conda_env_name=None): + """Creates environment from environment + + If no value is passed in to the conda_env_name it will simply select the + currently running environment + + Args: + name (str, optional): name of environment. Defaults to "amlenv". + conda_env_name (str, optional): name of the environment to use. Defaults to None. + + Returns: + azureml.core.Environment + """ + conda_env_name = os.getenv("CONDA_DEFAULT_ENV") if conda_env_name is None else conda_env_name + return Environment.from_existing_conda_environment(name, conda_env_name) + + +def create_environment_from_conda_file(conda_path, name="amlenv"): + """Creates environment from supplied conda file + + Args: + conda_path (str): path to conda environment file + name (str, optional): name of environment. Defaults to "amlenv". + + Returns: + azureml.core.Environment + """ + return Environment.from_existing_conda_specification(name, conda_path) + + +class PyTorchExperiment(BaseExperiment): + """Creates Experiment object that can be used to create clusters and submit experiments + + Returns: + PyTorchExperiment: PyTorchExperiment object + """ + + def _complete_datastore(self, script_params): + def _replace(value): + if isinstance(value, str) and "{datastore}" in value: + data_path = value.replace("{datastore}/", "") + return self.datastore.path(data_path).as_mount() + else: + return value + + return {key: _replace(value) for key, value in script_params.items()} + + def submit( + self, + project_folder, + entry_script, + script_params, + node_count=1, + workers_per_node=1, + distributed=None, + environment=None, + ): + """Submit experiment for remote execution on AzureML clusters. + + Args: + project_folder (string): Path of you source files for the experiment + entry_script (string): The filename of your script to run. Must be found in your project_folder + script_params (dict): Dictionary of script parameters + dependencies_file (string, optional): The location of your environment.yml to use to + create the environment your training script requires. + node_count (int, optional): [description]. + wait_for_completion (bool, optional): Whether to block until experiment is done. Defaults to True. + docker_args (tuple, optional): Docker arguments to pass. Defaults to (). + + Returns: + azureml.core.Run: AzureML Run object + """ + self._logger.debug(script_params) + + transformed_params = self._complete_datastore(script_params) + self._logger.debug("Transformed script params") + self._logger.debug(transformed_params) + + if environment is None: + environment = create_environment_from_local() + + environment.docker.shm_size = "8g" + environment.docker.base_image = _GPU_IMAGE + + estimator = _create_estimator( + PyTorch, + project_folder, + entry_script, + self.cluster, + transformed_params, + node_count, + environment, + _get_distributed(distributed), + ) + + self._logger.debug(estimator.conda_dependencies.__dict__) + return self._experiment.submit(estimator) + diff --git a/interpretation/deepseismic_interpretation/azureml_tools/resource_group.py b/interpretation/deepseismic_interpretation/azureml_tools/resource_group.py new file mode 100644 index 00000000..7094c760 --- /dev/null +++ b/interpretation/deepseismic_interpretation/azureml_tools/resource_group.py @@ -0,0 +1,60 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + + +from azure.mgmt.resource import ResourceManagementClient +from azure.common.credentials import get_cli_profile +import logging + + +def _get_resource_group_client(profile_credentials, subscription_id): + return ResourceManagementClient(profile_credentials, subscription_id) + + +def resource_group_exists(resource_group_name, resource_group_client=None): + return resource_group_client.resource_groups.check_existence(resource_group_name) + + +class ResourceGroupException(Exception): + pass + + +def create_resource_group(profile_credentials, subscription_id, location, resource_group_name): + """Creates resource group if it doesn't exist + + Args: + profile_credentials : credentials from Azure login + subscription_id (str): subscription you wish to use + location (str): location you wish the strage to be created in + resource_group_name (str): the name of the resource group you want the storage to be created under + + Raises: + ResourceGroupException: Exception if the resource group could not be created + + Returns: + ResourceGroup: an Azure resource group object + + Examples: + >>> profile = get_cli_profile() + >>> profile.set_active_subscription("YOUR-SUBSCRIPTION") + >>> cred, subscription_id, _ = profile.get_login_credentials() + >>> rg = create_resource_group(cred, subscription_id, "eastus", "testrg2") + """ + logger = logging.getLogger(__name__) + resource_group_client = _get_resource_group_client(profile_credentials, subscription_id) + if resource_group_exists(resource_group_name, resource_group_client=resource_group_client): + logger.debug(f"Found resource group {resource_group_name}") + resource_group = resource_group_client.resource_groups.get(resource_group_name) + else: + logger.debug(f"Creating resource group {resource_group_name} in {location}") + resource_group_params = {"location": location} + resource_group = resource_group_client.resource_groups.create_or_update( + resource_group_name, resource_group_params + ) + + if "Succeeded" not in resource_group.properties.provisioning_state: + raise ResourceGroupException( + f"Resource group not created successfully | State {resource_group.properties.provisioning_state}" + ) + + return resource_group diff --git a/interpretation/deepseismic_interpretation/azureml_tools/storage.py b/interpretation/deepseismic_interpretation/azureml_tools/storage.py index 29a3fbba..b84fd166 100644 --- a/interpretation/deepseismic_interpretation/azureml_tools/storage.py +++ b/interpretation/deepseismic_interpretation/azureml_tools/storage.py @@ -1,3 +1,7 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + + from azure.mgmt.storage import StorageManagementClient from azure.mgmt.storage.models import StorageAccountCreateParameters from azure.mgmt.storage.v2019_04_01.models import Kind, Sku, SkuName diff --git a/interpretation/deepseismic_interpretation/azureml_tools/subscription.py b/interpretation/deepseismic_interpretation/azureml_tools/subscription.py new file mode 100644 index 00000000..ce58c269 --- /dev/null +++ b/interpretation/deepseismic_interpretation/azureml_tools/subscription.py @@ -0,0 +1,97 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import json +import logging +import os +import subprocess +import sys + +from azure.common.client_factory import get_client_from_cli_profile +from azure.common.credentials import get_cli_profile +from azure.mgmt.resource import SubscriptionClient +from prompt_toolkit import prompt +from tabulate import tabulate +from toolz import pipe + +from knack.util import CLIError + +_GREEN = "\033[0;32m" +_BOLD = "\033[;1m" + + +def _run_az_cli_login(): + process = subprocess.Popen(["az", "login"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for c in iter(lambda: process.stdout.read(1), b""): + sys.stdout.write(_GREEN + _BOLD + c.decode(sys.stdout.encoding)) + + +def list_subscriptions(profile=None): + """Lists the subscriptions associated with the profile + + If you don't supply a profile it will try to get the profile from your Azure CLI login + + Args: + profile (azure.cli.core._profile.Profile, optional): Profile you wish to use. Defaults to None. + + Returns: + list: list of subscriptions + """ + if profile is None: + profile = subscription_profile() + cred, _, _ = profile.get_login_credentials() + sub_client = SubscriptionClient(cred) + return [ + {"Index": i, "Name": sub.display_name, "id": sub.subscription_id} + for i, sub in enumerate(sub_client.subscriptions.list()) + ] + + +def subscription_profile(): + """Return the Azure CLI profile + + Returns: + azure.cli.core._profile.Profile: Azure profile + """ + logger = logging.getLogger(__name__) + try: + return get_cli_profile() + except CLIError: + logger.info("Not logged in, running az login") + _run_az_cli_login() + return get_cli_profile() + + +def _prompt_sub_id_selection(profile): + sub_list = list_subscriptions(profile=profile) + pipe(sub_list, tabulate, print) + prompt_result = prompt("Please type in index of subscription you want to use: ") + selected_sub = sub_list[int(prompt_result)] + print(f"You selected index {prompt_result} sub id {selected_sub['id']} name {selected_sub['Name']}") + return selected_sub["id"] + + +def select_subscription(profile=None, sub_name_or_id=None): + """Sets active subscription + + If you don't supply a profile it will try to get the profile from your Azure CLI login + If you don't supply a subscription name or id it will list ones from your account and ask you to select one + + Args: + profile (azure.cli.core._profile.Profile, optional): Profile you wish to use. Defaults to None. + sub_name_or_id (str, optional): The subscription name or id to use. Defaults to None. + + Returns: + azure.cli.core._profile.Profile: Azure profile + + Example: + >>> profile = select_subscription() + """ + if profile is None: + profile = subscription_profile() + + if sub_name_or_id is None: + sub_name_or_id = _prompt_sub_id_selection(profile) + + profile.set_active_subscription(sub_name_or_id) + return profile diff --git a/interpretation/deepseismic_interpretation/azureml_tools/workspace.py b/interpretation/deepseismic_interpretation/azureml_tools/workspace.py new file mode 100644 index 00000000..b5331bce --- /dev/null +++ b/interpretation/deepseismic_interpretation/azureml_tools/workspace.py @@ -0,0 +1,94 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + + +import logging +import os +from pathlib import Path + +import azureml +from azureml.core.authentication import (AuthenticationException, + AzureCliAuthentication, + InteractiveLoginAuthentication, + ServicePrincipalAuthentication) + +_DEFAULT_AML_PATH = "aml_config/azml_config.json" + + +def _get_auth(): + """Returns authentication to Azure Machine Learning workspace.""" + logger = logging.getLogger(__name__) + if os.environ.get("AML_SP_PASSWORD", None): + logger.debug("Trying to authenticate with Service Principal") + aml_sp_password = os.environ.get("AML_SP_PASSWORD") + aml_sp_tennant_id = os.environ.get("AML_SP_TENNANT_ID") + aml_sp_username = os.environ.get("AML_SP_USERNAME") + auth = ServicePrincipalAuthentication(aml_sp_tennant_id, aml_sp_username, aml_sp_password) + else: + logger.debug("Trying to authenticate with CLI Authentication") + try: + auth = AzureCliAuthentication() + auth.get_authentication_header() + except AuthenticationException: + logger.debug("Trying to authenticate with Interactive login") + auth = InteractiveLoginAuthentication() + + return auth + + +def create_workspace( + workspace_name, resource_group, subscription_id, workspace_region, filename="azml_config.json", +): + """Creates Azure Machine Learning workspace.""" + logger = logging.getLogger(__name__) + auth = _get_auth() + + ws = azureml.core.Workspace.create( + name=workspace_name, + subscription_id=subscription_id, + resource_group=resource_group, + location=workspace_region, + create_resource_group=True, + exist_ok=True, + auth=auth, + ) + + logger.info(ws.get_details()) + ws.write_config(file_name=filename) + return ws + + +def load_workspace(path): + """Loads Azure Machine Learning workspace from a config file.""" + auth = _get_auth() + ws = azureml.core.Workspace.from_config(auth=auth, path=path) + logger = logging.getLogger(__name__) + logger.info( + "\n".join( + [ + "Workspace name: " + str(ws.name), + "Azure region: " + str(ws.location), + "Subscription id: " + str(ws.subscription_id), + "Resource group: " + str(ws.resource_group), + ] + ) + ) + return ws + + +def workspace_for_user( + workspace_name, resource_group, subscription_id, workspace_region, config_path=_DEFAULT_AML_PATH, +): + """Returns Azure Machine Learning workspace.""" + if os.path.isfile(config_path): + return load_workspace(config_path) + else: + path_obj = Path(config_path) + filename = path_obj.name + return create_workspace( + workspace_name, + resource_group, + subscription_id=subscription_id, + workspace_region=workspace_region, + filename=filename, + ) diff --git a/interpretation/requirements.txt b/interpretation/requirements.txt index 8f086ef6..c03e7464 100644 --- a/interpretation/requirements.txt +++ b/interpretation/requirements.txt @@ -1 +1,3 @@ -numpy>=1.17.0 \ No newline at end of file +numpy>=1.17.0 +azure-cli-core +azureml-sdk==1.0.74 \ No newline at end of file From 1111c6db0fbb94fddd2d847061045a18a8c4182d Mon Sep 17 00:00:00 2001 From: Mat Date: Thu, 5 Dec 2019 16:30:22 +0000 Subject: [PATCH 122/207] Fixes download script (#84) * Fixes download script * Updates readme --- README.md | 2 +- scripts/download_hrnet.sh | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 35349445..4949afbb 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ To achieve the same results as the benchmarks above you will need to download th To facilitate easier download on a Linux machine of your choice (or [Azure Data Science Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/) which we recommend), we created an automated download scipt for you, just run ```bash -./scripts/download_hrnet.sh 'your_folder_to_store_the_model' +./scripts/download_hrnet.sh 'your_folder_to_store_the_model' 'model_file' ``` ### Viewers (optional) diff --git a/scripts/download_hrnet.sh b/scripts/download_hrnet.sh index ea5bc059..157f20c3 100755 --- a/scripts/download_hrnet.sh +++ b/scripts/download_hrnet.sh @@ -1,6 +1,10 @@ #!/bin/bash # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +# +# Example: +# download_hrnet.sh /data/models hrnet.pth +# echo Using "$1" as the download directory @@ -14,6 +18,10 @@ full_path=$1/$2 echo "Downloading to ${full_path}" -wget \ -'https://optgaw.dm.files.1drv.com/y4mYRZ1J12ATHBWxSwZJTycIFbgT88SoWegSN3NXEOJFTJWBLDZ3nyCbj-sDvmGobL2CAGzhPobs5gMt3466nKbATNs9toc5N569Z5xNicNUABQm0MVucO7Vi7cjP__n2MFL5qDZyL4cOx6VgoNjpb9lglVRqoTVfVHdJ3sM7qO-9sAODNzgmKrCrU7uHvB54YtsKLr51Qi6BlDn94DalmEJQ/hrnetv2_w48_imagenet_pretrained.pth?download&psid=1' \ ---output-document "${full_path}" +wget --header 'Host: optgaw.dm.files.1drv.com' \ + --user-agent 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0' \ + --header 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \ + --header 'Accept-Language: en-GB,en;q=0.5' \ + --referer 'https://onedrive.live.com/' \ + --header 'Upgrade-Insecure-Requests: 1' 'https://optgaw.dm.files.1drv.com/y4m14W1OEuoniQMCT4m64UV8CSQT-dFe2ZRhU0LAZSal80V4phgVIlTYxI2tUi6BPVOy7l5rK8MKpZNywVvtz-NKL2ZWq-UYRL6MAjbLgdFA6zyW8RRrKBe_FcqcWr4YTXeJ18xfVqco6CdGZHFfORBE6EtFxEIrHWNjM032dWZLdqZ0eXd7RZTrHs1KKYa92zcs0Rj91CAyIK4hIaOomzEWA/hrnetv2_w48_imagenet_pretrained.pth?download&psid=1' \ + --output-document ${full_path} \ No newline at end of file From cc548e1ecf63c869c8802d99d328576766dc45cd Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Thu, 5 Dec 2019 17:00:22 +0000 Subject: [PATCH 123/207] clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 - Issue #83 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 35349445..a44b15e8 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ Follow the instruction bellow to read about compute requirements and install req
Compute environment -We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on Linux only. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DSVM)](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/). This VM will come installed with all the system requirements that are needed to run the notebooks in this repository. +We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on __Linux only__. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DSVM) for Linux (Ubuntu)](https://docs.microsoft.com/en-us/azure/machine-learning/data-science-virtual-machine/dsvm-ubuntu-intro). This VM will come installed with all the system requirements that are needed to create the conda environment described below and then run the notebooks in this repository. -For this repo, we recommend selecting an Ubuntu VM of type [Standard_NC6_v3](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series). The machine is powered by NVIDIA Tesla V100 GPU which can be found in most Azure regions. +For this repo, we recommend selecting a multi-GPU Ubuntu VM of type [Standard_NC12](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series). The machine is powered by NVIDIA Tesla V100 GPU which can be found in most Azure regions. > NOTE: For users new to Azure, your subscription may not come with a quota for GPUs. You may need to go into the Azure portal to increase your quota for GPU VMs. Learn more about how to do this here: https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits. From 017a5c454675a70c209e594add188e3b0ab60c15 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Thu, 5 Dec 2019 17:51:44 +0000 Subject: [PATCH 124/207] Add Troubleshooting section for DSVM warnings #89 --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index a44b15e8..f09380aa 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,17 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope # Troubleshooting +For Data Science Virtual Machine conda package installation issues, make sure you locate the anaconda location on the DSVM, for example by running: +``` +which python +``` +A typical output will be: +``` +someusername@somevm:/projects/DeepSeismic$ which python +/anaconda/envs/py35/bin/python +``` +which will indicate that anaconda folder is __/anaconda__. We'll refer to this location in instructions below, but you should update the commands according to your local anaconda folder. +
Data Science Virtual Machine conda package installation errors @@ -281,6 +292,22 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope
+
+ Data Science Virtual Machine conda package installation warnings + + It could happen that while creating the conda environment defined by environment/anaconda/local/environment.yml on an Ubuntu DSVM, one can get multiple warnings like this: + WARNING conda.gateways.disk.delete:unlink_or_rename_to_trash(140): Could not remove or rename /anaconda/pkgs/ipywidgets-7.5.1-py_0/site-packages/ipywidgets-7.5.1.dist-info/LICENSE. Please remove this file manually (you may need to reboot to free file handles) + + If that happens, similar to instructions above, stop the conda environment creation (type ```Ctrl+C```) and then change recursively the ownership /data/anaconda pckages from root to current user, by running this command: + + ```bash + sudo chown -R $USER /anaconda + ``` + + After these command completes, try creating the conda environment in __environment/anaconda/local/environment.yml__ again. + +
+
Model training or scoring is not using GPU From 1d3cd3340f4063508b6f707d5fc2a35f5429a07f Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Thu, 5 Dec 2019 18:01:32 +0000 Subject: [PATCH 125/207] Add Troubleshooting section for DSVM warnings, plus typo #89 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41a6b117..adcca7ed 100644 --- a/README.md +++ b/README.md @@ -298,7 +298,7 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l It could happen that while creating the conda environment defined by environment/anaconda/local/environment.yml on an Ubuntu DSVM, one can get multiple warnings like this: WARNING conda.gateways.disk.delete:unlink_or_rename_to_trash(140): Could not remove or rename /anaconda/pkgs/ipywidgets-7.5.1-py_0/site-packages/ipywidgets-7.5.1.dist-info/LICENSE. Please remove this file manually (you may need to reboot to free file handles) - If that happens, similar to instructions above, stop the conda environment creation (type ```Ctrl+C```) and then change recursively the ownership /data/anaconda pckages from root to current user, by running this command: + If that happens, similar to instructions above, stop the conda environment creation (type ```Ctrl+C```) and then change recursively the ownership /anaconda directory from root to current user, by running this command: ```bash sudo chown -R $USER /anaconda From 4239912fc4562c5641a95e4d633170f92c2cc506 Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Fri, 6 Dec 2019 15:56:47 +0000 Subject: [PATCH 126/207] modified hrnet notebook, addressing bug bash issues (#95) --- .../F3_block_training_and_evaluation_local.ipynb | 10 +++++++--- ...k.ipynb => HRNet_Penobscot_demo_notebook.ipynb} | 14 ++++++++++---- .../interpretation/notebooks/configs/hrnet.yaml | 2 -- 3 files changed, 17 insertions(+), 9 deletions(-) rename examples/interpretation/notebooks/{HRNet_demo_notebook.ipynb => HRNet_Penobscot_demo_notebook.ipynb} (95%) diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 319ad5eb..ea8f715a 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -167,7 +167,11 @@ "outputs": [], "source": [ "with open(CONFIG_FILE, \"rt\") as f_read:\n", - " config = yacs.config.load_cfg(f_read)\n" + " config = yacs.config.load_cfg(f_read)\n", + "\n", + "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", + "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", + "print(config)" ] }, { @@ -734,9 +738,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "Python 3.5", + "display_name": "seismic-interpretation", "language": "python", - "name": "python3" + "name": "seismic-interpretation" }, "language_info": { "codemirror_mode": { diff --git a/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb similarity index 95% rename from examples/interpretation/notebooks/HRNet_demo_notebook.ipynb rename to examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 2fb58ec8..47ce07c5 100644 --- a/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -20,7 +20,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this notebook, we demonstrate how to train an HRNet model for facies prediction a numpy dataset. The data expected in this notebook needs to be in the form of two 3D arrays. One array will contain the seismic information the other the mask. The network will be trained to take a 2D patch of data from the seismic block and learn to predict the 2D mask patch associated with it." + "In this notebook, we demonstrate how to train an HRNet model for facies prediction using [Penobscot](https://zenodo.org/record/1341774#.XepaaUB2vOg) dataset. The Penobscot 3D seismic dataset was acquired in the Scotian shelf, offshore Nova Scotia, Canada. Please refer to the top-level [README.md](../../../README.md) file to download and prepare this dataset for the experiments. \n", + "\n", + "The data expected in this notebook needs to be in the form of two 3D numpy arrays. One array will contain the seismic information, the other the mask. The network will be trained to take a 2D patch of data from the seismic block and learn to predict the 2D mask patch associated with it." ] }, { @@ -140,7 +142,11 @@ "outputs": [], "source": [ "with open(CONFIG_FILE, \"rt\") as f_read:\n", - " config = yacs.config.load_cfg(f_read)" + " config = yacs.config.load_cfg(f_read)\n", + "\n", + "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", + "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", + "print(config)" ] }, { @@ -620,9 +626,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "Python 3", + "display_name": "seismic-interpretation", "language": "python", - "name": "python3" + "name": "seismic-interpretation" }, "language_info": { "codemirror_mode": { diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index 544870e2..689f8c9c 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -20,8 +20,6 @@ DATASET: INLINE_WIDTH: 481 - - MODEL: NAME: seg_hrnet IN_CHANNELS: 3 From d54f9053c37f366014d77171ab0bdf4f625a9734 Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Fri, 6 Dec 2019 12:19:14 -0500 Subject: [PATCH 127/207] Update environment.yml (#93) * Update environment.yml * Update environment.yml --- environment/anaconda/local/environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index bcba252e..4eebd0b3 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -14,6 +14,7 @@ dependencies: - opencv==4.1.2 - scikit-learn==0.21.3 - tensorflow==2.0 + - opt-einsum>=2.3.2 - tqdm==4.39.0 - itkwidgets==0.23.1 - pytest From 6df188914b17cae1605ac7d2e2d56f9fa6c40df4 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Sat, 7 Dec 2019 03:02:18 +0000 Subject: [PATCH 128/207] tested both yml conda env and docker; udated conda yml to have docker sdk --- .../fwi_dev_conda_environment.yml | 1 + ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 5 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 142 +++++++------- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 8 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 181 +++++++++++++----- 5 files changed, 211 insertions(+), 126 deletions(-) diff --git a/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml index e79343d6..eedd1197 100755 --- a/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml +++ b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml @@ -14,3 +14,4 @@ dependencies: - python-dotenv - papermill[azure] - azureml-sdk[notebooks,automl,explain]==1.0.76 + - docker diff --git a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index 3040bd87..62683429 100755 --- a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -861,6 +861,7 @@ "metadata": {}, "outputs": [], "source": [ + "# create_ACR_FLAG=False\n", "if create_ACR_FLAG:\n", " import subprocess\n", " cli_command = 'az acr credential show -n '+acr_name\n", @@ -900,9 +901,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python [conda env:fwi_dev_conda_environment] *", "language": "python", - "name": "python3" + "name": "conda-env-fwi_dev_conda_environment-py" }, "language_info": { "codemirror_mode": { diff --git a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb index 78276433..aac30038 100755 --- a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -374,7 +374,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" + "Writing /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" ] } ], @@ -425,7 +425,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76\n" + "Writing /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76\n" ] } ], @@ -489,14 +489,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "total 28\r\n", - "-rwxrwxrwx 1 root root 733 Dec 4 02:31 conda_env_fwi01_azureml_sdk.v1.0.69.yml\r\n", - "-rwxrwxrwx 1 root root 725 Dec 4 02:31 conda_env_fwi01_azureml_sdk.v1.0.74.yml\r\n", - "-rwxrwxrwx 1 root root 725 Dec 4 16:59 conda_env_fwi01_azureml_sdk.v1.0.76.yml\r\n", - "-rwxrwxrwx 1 root root 725 Dec 4 16:59 conda_env_fwi01_azureml.yml\r\n", - "-rwxrwxrwx 1 root root 1073 Dec 4 02:31 Dockerfile_fwi01_azureml_sdk.v1.0.69\r\n", - "-rwxrwxrwx 1 root root 1073 Dec 4 02:31 Dockerfile_fwi01_azureml_sdk.v1.0.74\r\n", - "-rwxrwxrwx 1 root root 1073 Dec 4 16:59 Dockerfile_fwi01_azureml_sdk.v1.0.76\r\n" + "total 12\r\n", + "-rw-rw-r-- 1 loginvm022 loginvm022 725 Dec 6 15:26 conda_env_fwi01_azureml_sdk.v1.0.76.yml\r\n", + "-rw-rw-r-- 1 loginvm022 loginvm022 725 Dec 6 15:26 conda_env_fwi01_azureml.yml\r\n", + "-rw-rw-r-- 1 loginvm022 loginvm022 1073 Dec 6 15:26 Dockerfile_fwi01_azureml_sdk.v1.0.76\r\n" ] } ], @@ -524,7 +520,7 @@ { "data": { "text/plain": [ - "['Sending build context to Docker daemon 13.31kB',\n", + "['Sending build context to Docker daemon 6.144kB',\n", " '',\n", " 'Step 1/15 : FROM continuumio/miniconda3:4.7.10',\n", " '4.7.10: Pulling from continuumio/miniconda3',\n", @@ -538,10 +534,10 @@ { "data": { "text/plain": [ - "[' ---> Running in 64cc95908200',\n", - " 'Removing intermediate container 64cc95908200',\n", - " ' ---> 619ab5d20944',\n", - " 'Successfully built 619ab5d20944',\n", + "[' ---> Running in 00c2824f0cd3',\n", + " 'Removing intermediate container 00c2824f0cd3',\n", + " ' ---> 48fb03897096',\n", + " 'Successfully built 48fb03897096',\n", " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76']" ] }, @@ -725,39 +721,39 @@ "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", "\n", "total 560\n", - "-rw-r--r-- 1 root root 11521 Dec 4 17:00 conftest.py\n", - "-rw-r--r-- 1 root root 6006 Dec 4 17:00 test_adjoint.py\n", - "-rw-r--r-- 1 root root 14586 Dec 4 17:00 test_autotuner.py\n", - "-rw-r--r-- 1 root root 7538 Dec 4 17:00 test_builtins.py\n", - "-rw-r--r-- 1 root root 24415 Dec 4 17:00 test_caching.py\n", - "-rw-r--r-- 1 root root 9721 Dec 4 17:00 test_checkpointing.py\n", - "-rw-r--r-- 1 root root 1095 Dec 4 17:00 test_constant.py\n", - "-rw-r--r-- 1 root root 55954 Dec 4 17:00 test_data.py\n", - "-rw-r--r-- 1 root root 481 Dec 4 17:00 test_dependency_bugs.py\n", - "-rw-r--r-- 1 root root 16331 Dec 4 17:00 test_derivatives.py\n", - "-rw-r--r-- 1 root root 1473 Dec 4 17:00 test_differentiable.py\n", - "-rw-r--r-- 1 root root 30846 Dec 4 17:00 test_dimension.py\n", - "-rw-r--r-- 1 root root 24838 Dec 4 17:00 test_dle.py\n", - "-rw-r--r-- 1 root root 1169 Dec 4 17:00 test_docstrings.py\n", - "-rw-r--r-- 1 root root 32134 Dec 4 17:00 test_dse.py\n", - "-rw-r--r-- 1 root root 8205 Dec 4 17:00 test_gradient.py\n", - "-rw-r--r-- 1 root root 15227 Dec 4 17:00 test_interpolation.py\n", - "-rw-r--r-- 1 root root 31816 Dec 4 17:00 test_ir.py\n", - "-rw-r--r-- 1 root root 63169 Dec 4 17:00 test_mpi.py\n", - "-rw-r--r-- 1 root root 67053 Dec 4 17:00 test_operator.py\n", - "-rw-r--r-- 1 root root 14875 Dec 4 17:00 test_ops.py\n", - "-rw-r--r-- 1 root root 12228 Dec 4 17:00 test_pickle.py\n", - "-rw-r--r-- 1 root root 1809 Dec 4 17:00 test_resample.py\n", - "-rw-r--r-- 1 root root 1754 Dec 4 17:00 test_save.py\n", - "-rw-r--r-- 1 root root 2115 Dec 4 17:00 test_staggered_utils.py\n", - "-rw-r--r-- 1 root root 5711 Dec 4 17:00 test_subdomains.py\n", - "-rw-r--r-- 1 root root 3320 Dec 4 17:00 test_symbolic_coefficients.py\n", - "-rw-r--r-- 1 root root 7277 Dec 4 17:00 test_tensors.py\n", - "-rw-r--r-- 1 root root 3186 Dec 4 17:00 test_timestepping.py\n", - "-rw-r--r-- 1 root root 603 Dec 4 17:00 test_tools.py\n", - "-rw-r--r-- 1 root root 3296 Dec 4 17:00 test_tti.py\n", - "-rw-r--r-- 1 root root 8835 Dec 4 17:00 test_visitors.py\n", - "-rw-r--r-- 1 root root 21802 Dec 4 17:00 test_yask.py\n", + "-rw-r--r-- 1 root root 11521 Dec 6 15:26 conftest.py\n", + "-rw-r--r-- 1 root root 6006 Dec 6 15:26 test_adjoint.py\n", + "-rw-r--r-- 1 root root 14586 Dec 6 15:26 test_autotuner.py\n", + "-rw-r--r-- 1 root root 7538 Dec 6 15:26 test_builtins.py\n", + "-rw-r--r-- 1 root root 24415 Dec 6 15:26 test_caching.py\n", + "-rw-r--r-- 1 root root 9721 Dec 6 15:26 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Dec 6 15:26 test_constant.py\n", + "-rw-r--r-- 1 root root 55954 Dec 6 15:26 test_data.py\n", + "-rw-r--r-- 1 root root 481 Dec 6 15:26 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16331 Dec 6 15:26 test_derivatives.py\n", + "-rw-r--r-- 1 root root 1473 Dec 6 15:26 test_differentiable.py\n", + "-rw-r--r-- 1 root root 30846 Dec 6 15:26 test_dimension.py\n", + "-rw-r--r-- 1 root root 24838 Dec 6 15:26 test_dle.py\n", + "-rw-r--r-- 1 root root 1169 Dec 6 15:26 test_docstrings.py\n", + "-rw-r--r-- 1 root root 32134 Dec 6 15:26 test_dse.py\n", + "-rw-r--r-- 1 root root 8205 Dec 6 15:26 test_gradient.py\n", + "-rw-r--r-- 1 root root 15227 Dec 6 15:26 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31816 Dec 6 15:26 test_ir.py\n", + "-rw-r--r-- 1 root root 63169 Dec 6 15:26 test_mpi.py\n", + "-rw-r--r-- 1 root root 67053 Dec 6 15:26 test_operator.py\n", + "-rw-r--r-- 1 root root 14875 Dec 6 15:26 test_ops.py\n", + "-rw-r--r-- 1 root root 12228 Dec 6 15:26 test_pickle.py\n", + "-rw-r--r-- 1 root root 1809 Dec 6 15:26 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Dec 6 15:26 test_save.py\n", + "-rw-r--r-- 1 root root 2115 Dec 6 15:26 test_staggered_utils.py\n", + "-rw-r--r-- 1 root root 5711 Dec 6 15:26 test_subdomains.py\n", + "-rw-r--r-- 1 root root 3320 Dec 6 15:26 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 7277 Dec 6 15:26 test_tensors.py\n", + "-rw-r--r-- 1 root root 3186 Dec 6 15:26 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Dec 6 15:26 test_tools.py\n", + "-rw-r--r-- 1 root root 3296 Dec 6 15:26 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Dec 6 15:26 test_visitors.py\n", + "-rw-r--r-- 1 root root 21802 Dec 6 15:26 test_yask.py\n", "1.0.76\n", "\n", "content of devito tests log file after testing:\n", @@ -812,7 +808,7 @@ "=================================== FAILURES ===================================\n", "______________________ TestSC.test_function_coefficients _______________________\n", "\n", - "self = \n", + "self = \n", "\n", " def test_function_coefficients(self):\n", " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", @@ -856,10 +852,10 @@ " \n", "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", "E assert Data(False)\n", - "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", - "E + where = np.all\n", - "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", - "E + where = np.isclose\n", + "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", + "E + where = np.all\n", + "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", + "E + where = np.isclose\n", "\n", "tests/test_symbolic_coefficients.py:96: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", @@ -876,7 +872,7 @@ "------------------------------ Captured log call -------------------------------\n", "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", - "====== 1 failed, 968 passed, 87 skipped, 2 xfailed in 1038.47s (0:17:18) =======\n" + "====== 1 failed, 968 passed, 87 skipped, 2 xfailed in 1070.16s (0:17:50) =======\n" ] } ], @@ -906,7 +902,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -915,7 +911,7 @@ "'az acr login --name fwi01acr'" ] }, - "execution_count": 19, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" }, @@ -923,11 +919,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Login Succeeded\n", - "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\n", - "Configure a credential helper to remove this warning. See\n", - "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\n", - "\n", + "Login Succeeded\r\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm022/.docker/config.json.\r\n", + "Configure a credential helper to remove this warning. See\r\n", + "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", + "\r\n", "\u001b[0m" ] } @@ -938,7 +934,7 @@ "# !docker login -u=$docker_login -p=$docker_pwd\n", "# !docker push {docker_image_name}\n", "\n", - "%dotenv $dotenv_file_path\n", + "%dotenv -o $dotenv_file_path\n", "cli_command='az acr login --name '+os.getenv('ACR_NAME')\n", "# print cli command\n", "cli_command\n", @@ -950,7 +946,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -959,7 +955,7 @@ "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" ] }, - "execution_count": 20, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -971,7 +967,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -980,10 +976,10 @@ "text": [ "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", "\n", - "\u001b[1B01e8603a: Preparing \n", - "\u001b[1B83481e05: Preparing \n", - "\u001b[1Bf84794df: Preparing \n", - "\u001b[1B4559bd8a: Preparing \n", + "\u001b[1Bd6300f53: Preparing \n", + "\u001b[1B01af7f6b: Preparing \n", + "\u001b[1B41f0b573: Preparing \n", + "\u001b[1B04ca5654: Preparing \n", "\u001b[1Bf8fc4c9a: Preparing \n", "\u001b[1Bba47210e: Preparing \n" ] @@ -992,21 +988,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[7B01e8603a: Pushing 1.055GB/2.967GBA\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 479.3MB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 910.8MB/2.967GB\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K" + "\u001b[6B01af7f6b: Pushing 1.484GB/3.028GBA\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[2A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[6B83481e05: Pushing 2.253GB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 1.072GB/2.967GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 1.771GB/2.967GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K" + "\u001b[7Bd6300f53: Pushing 3.026GB/3.028GB\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2KPushing 2.58GB/2.968GB\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[6B83481e05: Pushed 3.102GB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[Ksdk.v1.0.76: digest: sha256:25a4a6faad05e910366df62dc4ea0fbeb0d5fe912e56ad5bddb2325104653710 size: 1800\n" + "\u001b[6B01af7f6b: Pushed 3.103GB/3.028GB\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2Ksdk.v1.0.76: digest: sha256:416dc7ce59c279822e967223790f7b8b7d99ba62bc643ca44b94551135b60b6b size: 1800\n" ] } ], @@ -1016,7 +1012,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 25, "metadata": {}, "outputs": [ { diff --git a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb index c2902730..db76f4c5 100755 --- a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -530,7 +530,7 @@ "output_type": "stream", "text": [ "Login Succeeded\r\n", - "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\r\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm022/.docker/config.json.\r\n", "Configure a credential helper to remove this warning. See\r\n", "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", "\r\n", @@ -859,7 +859,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f94f884975d14f94977e26fb8e35745f", + "model_id": "565b952db744469fa2137b6c94e15f7a", "version_major": 2, "version_minor": 0 }, @@ -872,7 +872,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"created_utc\": \"2019-12-04T17:21:06.014446Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-04T17:30:00.733408Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=XtrqU6IMV%2B48BKF%2BQtFwy6LRoQjGkEJ2ALnJqe4N86Y%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=Z5mXvd7qJNNv2tWMiT1C25Tc%2Bu%2BblrWqkN6UFUzge2U%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=dOviChtDoL5vak4hJd2Pw3Pjj%2BkN5BmxbcPiW6ezqK0%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=1rch4YL2wvgtosymal8WdNdNW6K34Om02P0TMcWcjyc%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=DlifA0Xe8zotaCGo56XNplmG4xCoY0eegb1kFvRaOXg%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=8myM2hR0Cph66FXGtG4YmjmYoP%2F5c3bDBXacYucjnzI%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"logs/azureml/730_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/logs/azureml/730_azureml.log?sv=2019-02-02&sr=b&sig=AE4JyiNXVvzRYF8nTY0BrwltFPiXXIwSga9AlzFDEd8%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=Wk8VGtb5XKlUzkOqIRVsnhvdT3MGSH%2B%2BJEuS63MP0%2BI%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"logs/azureml/730_azureml.log\"]], \"run_duration\": \"0:08:54\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-04 17:28:21,458|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-04 17:28:21,458|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-04 17:28:21,459|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-04 17:28:21,459|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-04 17:28:21,801|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-04 17:28:21,801|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-04 17:28:22,775|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-04 17:28:22,780|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-04 17:28:22,785|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-04 17:28:22,790|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-04 17:28:22,792|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:28:22,798|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,799|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-04 17:28:22,799|azureml.core.authentication|DEBUG|Time to expire 1813963.200661 seconds\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,961|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,967|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,975|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,980|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,985|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,990|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,991|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-04 17:28:22,991|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:28:22,991|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|None\\n2019-12-04 17:28:22,992|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:28:22,992|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:28:22,993|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:28:22,993|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:28:23 GMT'\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:28:23,584|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1460,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-04T17:21:06.014446+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-04T17:25:54.100925+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575480062_180862b6/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-04 17:28:23,589|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-04 17:28:23,590|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'mlflow.source.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-04 17:28:23,590|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-04 17:28:23,591|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-04 17:28:23,591|azureml.WorkerPool|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-04 17:28:23,592|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:28:26,647|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,649|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,654|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,655|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-04 17:28:26,660|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,666|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,672|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,677|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,677|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-04 17:28:26,677|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|None\\n2019-12-04 17:28:26,678|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:28:27 GMT'\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1460,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-04T17:21:06.014446+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-04T17:25:54.100925+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575480062_180862b6/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-04 17:28:27,361|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-04 17:28:27,362|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'mlflow.source.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-04 17:28:27,362|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-04 17:28:52,796|azureml.core.authentication|DEBUG|Time to expire 1813933.203089 seconds\\n2019-12-04 17:29:22,797|azureml.core.authentication|DEBUG|Time to expire 1813903.202753 seconds\\n2019-12-04 17:29:37,671|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-04 17:29:37,671|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:29:37,672|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:29:37,708|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-04 17:29:37,708|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|INFO|complete is not setting status for submitted runs.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-04 17:29:37,710|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-04 17:29:37,711|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-04 17:29:37,711|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-04 17:29:37,712|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:29:37,713|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-04 17:29:37,713|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:29:37,713|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-04 17:29:37,713|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6/batch/metrics'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,715|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,715|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-04 17:29:37,715|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-04 17:29:37,716|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"db9cd91b-ba34-41bf-aad8-b744d46471a4\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-04T17:29:37.671117Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:29:37 GMT'\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG|\\n2019-12-04 17:29:37,835|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00024700164794921875 seconds.\\n\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-04 17:29:37,967|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575674728_d40baeba\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575674728_d40baeba\", \"created_utc\": \"2019-12-06T23:25:30.597858Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-06T23:34:26.039772Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/55_azureml-execution-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt?sv=2019-02-02&sr=b&sig=1Fz2ltrBSXhF9tDzTuEOv35mBsOLsf%2BCVuTEuSCRWdg%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/65_job_prep-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt?sv=2019-02-02&sr=b&sig=PwHIdkWadtTAj29WuPOCF3g0RSrWdriOmKhqdjZNm3I%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=Iz8WkiOv%2BkEXeOox8p3P8XkLIdb8pjhCO%2Bo8slYUBGk%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/75_job_post-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/75_job_post-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt?sv=2019-02-02&sr=b&sig=gz88u5ZC%2B7N8QospVRIL8zd%2FEyQKbljoZXQD01jAyXM%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=4nj2pjm1rtKIjBmyudNaBEX6ITd3Gm%2BQLEUgjDYVBIc%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=NQLsveMtGHBEYsmiwoPvPpOv%2B6wabnQp2IwDrVjh49Q%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"logs/azureml/729_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/logs/azureml/729_azureml.log?sv=2019-02-02&sr=b&sig=HpwLZSHX0J%2B2eWILTIDA7%2BmpVIEF0%2BIFfM2LHgYGk8w%3D&st=2019-12-06T23%3A24%3A43Z&se=2019-12-07T07%3A34%3A43Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=g%2Fi60CvATRGwaeQM9b6QihJxeFX0jTl%2BOKELCYYQ3rM%3D&st=2019-12-06T23%3A24%3A43Z&se=2019-12-07T07%3A34%3A43Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\"], [\"logs/azureml/729_azureml.log\"]], \"run_duration\": \"0:08:55\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575674728_d40baeba\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-06 23:32:41,989|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-06 23:32:41,989|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-06 23:32:41,990|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-06 23:32:41,990|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-06 23:32:42,323|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-06 23:32:42,323|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-06 23:32:42,721|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-06 23:32:42,721|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-06 23:32:42,722|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-06 23:32:42,722|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-06 23:32:42,722|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-06 23:32:43,300|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-06 23:32:43,306|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-06 23:32:43,311|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-06 23:32:43,316|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-06 23:32:43,318|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-06 23:32:43,324|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,325|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-06 23:32:43,325|azureml.core.authentication|DEBUG|Time to expire 1813966.674698 seconds\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,326|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,326|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,326|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,356|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,361|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,369|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,374|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,379|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,385|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,385|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-06 23:32:43,386|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-06 23:32:43,386|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|Request headers:\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a72fb1c-fdba-4e6d-a244-7315dcdf5d54'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'request-id': '2a72fb1c-fdba-4e6d-a244-7315dcdf5d54'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|Request body:\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|None\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-06 23:32:43,442|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG|Response headers:\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Date': 'Fri, 06 Dec 2019 23:32:43 GMT'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a72fb1c-fdba-4e6d-a244-7315dcdf5d54'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG|Response content:\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1516,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-06T23:25:30.5978583+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-06T23:30:15.4122862+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575674728_d40baeba/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-06 23:32:43,449|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-06 23:32:43,450|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-06 23:32:43,450|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-06 23:32:43,451|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-06 23:32:43,451|azureml.WorkerPool|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,593|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,593|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,599|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,600|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-06 23:32:45,605|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,610|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,616|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,621|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,622|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-06 23:32:45,622|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba'\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG|Request headers:\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7502a986-27e5-47c2-8a48-e5501a0dda7c'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG| 'request-id': '7502a986-27e5-47c2-8a48-e5501a0dda7c'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG|Request body:\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG|None\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-06 23:32:46,018|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-06 23:32:46,018|msrest.http_logger|DEBUG|Response headers:\\n2019-12-06 23:32:46,018|msrest.http_logger|DEBUG| 'Date': 'Fri, 06 Dec 2019 23:32:46 GMT'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7502a986-27e5-47c2-8a48-e5501a0dda7c'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG|Response content:\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1516,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-06T23:25:30.5978583+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-06T23:30:15.4122862+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575674728_d40baeba/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-06 23:32:46,022|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-06 23:32:46,023|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-06 23:32:46,023|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-06 23:33:13,322|azureml.core.authentication|DEBUG|Time to expire 1813936.677149 seconds\\n2019-12-06 23:33:43,323|azureml.core.authentication|DEBUG|Time to expire 1813906.67683 seconds\\n2019-12-06 23:33:57,866|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-06 23:33:57,867|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-06 23:33:57,867|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:33:57,912|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-06 23:33:57,912|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba|INFO|complete is not setting status for submitted runs.\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,913|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,914|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,914|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-06 23:33:57,914|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-06 23:33:57,914|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-06 23:33:57,915|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-06 23:33:57,915|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-06 23:33:57,915|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-06 23:33:57,915|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-06 23:33:57,916|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-06 23:33:57,916|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-06 23:33:57,917|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:33:57,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-06 23:33:57,917|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba/batch/metrics'\\n2019-12-06 23:33:57,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG|Request headers:\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7318af30-3aa3-4d84-a4db-0595c67afd70'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG| 'request-id': '7318af30-3aa3-4d84-a4db-0595c67afd70'\\n2019-12-06 23:33:57,919|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-06 23:33:57,919|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-06 23:33:57,919|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG|Request body:\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"d160ffa3-e1bc-4ff2-b60f-7742b38cdfd2\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-06T23:33:57.866688Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-06 23:33:57,919|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-06 23:33:57,920|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-06 23:33:57,920|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-06 23:33:57,920|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG|Response headers:\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Date': 'Fri, 06 Dec 2019 23:33:58 GMT'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7318af30-3aa3-4d84-a4db-0595c67afd70'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG|Response content:\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG|\\n2019-12-06 23:33:58,045|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002143383026123047 seconds.\\n\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-06 23:33:58,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -935,7 +935,7 @@ { "data": { "text/plain": [ - "'runId= 020_AzureMLEstimator_1575480062_180862b6'" + "'runId= 020_AzureMLEstimator_1575674728_d40baeba'" ] }, "execution_count": 19, diff --git a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb index f6b40ef1..d8a5448a 100755 --- a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -797,7 +797,7 @@ ], "source": [ "# Verify that cluster does not exist already\n", - "max_nodes_value = 5\n", + "max_nodes_value = 2\n", "try:\n", " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", " print(\"Found existing gpu cluster\")\n", @@ -835,7 +835,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "edb374a7e72648cd892b24fb0cc8c2b6", + "model_id": "a0312dfcb82f419288e3c3c37c39b9dd", "version_major": 2, "version_minor": 0 }, @@ -848,7 +848,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575481417_8013866e?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575481417_8013866e\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575481417_8013866e\", \"created_utc\": \"2019-12-04T17:43:40.27579Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":1,\\\"CurrentNodeCount\\\":2}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575481417_8013866e/azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt?sv=2019-02-02&sr=b&sig=lVAX2nGZV7C62e8t0DZkL19PV8vYW8dKjDExkyOnHj0%3D&st=2019-12-04T17%3A38%3A58Z&se=2019-12-05T01%3A48%3A58Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt\"]], \"run_duration\": \"0:05:18\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2019-12-04T17:48:07Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.76: Pulling from fwi01_azureml\\n1ab2bdfe9778: Pulling fs layer\\ndd7d28bd8be5: Pulling fs layer\\naf998e3a361b: Pulling fs layer\\n1f706d9b369a: Pulling fs layer\\ne5000b763efd: Pulling fs layer\\n8c572c1c1101: Pulling fs layer\\n5988e7f62200: Pulling fs layer\\n8c572c1c1101: Waiting\\n5988e7f62200: Waiting\\ne5000b763efd: Waiting\\n1f706d9b369a: Waiting\\naf998e3a361b: Verifying Checksum\\naf998e3a361b: Download complete\\ndd7d28bd8be5: Verifying Checksum\\ndd7d28bd8be5: Download complete\\ne5000b763efd: Verifying Checksum\\ne5000b763efd: Download complete\\n1ab2bdfe9778: Verifying Checksum\\n1ab2bdfe9778: Download complete\\n1f706d9b369a: Verifying Checksum\\n1f706d9b369a: Download complete\\n1ab2bdfe9778: Pull complete\\n5988e7f62200: Verifying Checksum\\n5988e7f62200: Download complete\\n8c572c1c1101: Verifying Checksum\\n8c572c1c1101: Download complete\\ndd7d28bd8be5: Pull complete\\naf998e3a361b: Pull complete\\n1f706d9b369a: Pull complete\\ne5000b763efd: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575678435_be18a2fc?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575678435_be18a2fc\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575678435_be18a2fc\", \"created_utc\": \"2019-12-07T00:27:18.102865Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":1,\\\"CurrentNodeCount\\\":2}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575678435_be18a2fc/azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt?sv=2019-02-02&sr=b&sig=99MfEJ4IvLwXgM3jjLm4amfljnv7gOK3%2BQPb1GN%2BZKg%3D&st=2019-12-07T00%3A22%3A27Z&se=2019-12-07T08%3A32%3A27Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt\"]], \"run_duration\": \"0:05:10\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2019-12-07T00:31:04Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.76: Pulling from fwi01_azureml\\n1ab2bdfe9778: Pulling fs layer\\ndd7d28bd8be5: Pulling fs layer\\naf998e3a361b: Pulling fs layer\\n8f61820757bf: Pulling fs layer\\n0eb461057035: Pulling fs layer\\n23276e49c76d: Pulling fs layer\\nc55ca301ea9f: Pulling fs layer\\n0eb461057035: Waiting\\n8f61820757bf: Waiting\\nc55ca301ea9f: Waiting\\n1ab2bdfe9778: Verifying Checksum\\n1ab2bdfe9778: Download complete\\naf998e3a361b: Verifying Checksum\\naf998e3a361b: Download complete\\n0eb461057035: Verifying Checksum\\n0eb461057035: Download complete\\ndd7d28bd8be5: Verifying Checksum\\ndd7d28bd8be5: Download complete\\n1ab2bdfe9778: Pull complete\\n8f61820757bf: Verifying Checksum\\n8f61820757bf: Download complete\\ndd7d28bd8be5: Pull complete\\nc55ca301ea9f: Verifying Checksum\\nc55ca301ea9f: Download complete\\n23276e49c76d: Verifying Checksum\\n23276e49c76d: Download complete\\naf998e3a361b: Pull complete\\n8f61820757bf: Pull complete\\n0eb461057035: Pull complete\\n23276e49c76d: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -921,7 +921,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 6, time 13.138 seconds: Counter({'Completed': 1})\r" + "Final print 9, time 20.798 seconds: Counter({'Completed': 1})\r" ] } ], @@ -954,8 +954,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "run_duration in seconds 241.125075\n", - "run_duration= 4m 1.125s\n" + "run_duration in seconds 243.960763\n", + "run_duration= 4m 3.961s\n" ] } ], @@ -982,13 +982,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Showing details for run 15\n" + "Showing details for run 498\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "78a6832f42fc4bb1bece76520e305f54", + "model_id": "cd44e7b0a1c447dabe98bf114f420d76", "version_major": 2, "version_minor": 0 }, @@ -1001,7 +1001,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Queued\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575482070_cc2fb88e?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575482070_cc2fb88e\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575482070_cc2fb88e\", \"created_utc\": \"2019-12-04T17:54:31.673448Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":0,\\\"RunningNodeCount\\\":10,\\\"CurrentNodeCount\\\":10}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Queued\", \"log_files\": {}, \"log_groups\": [], \"run_duration\": \"0:05:16\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"Your job is submitted in Azure cloud and we are monitoring to get logs...\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"created_utc\": \"2019-12-07T01:54:55.33033Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-07T01:56:48.811115Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=9mQARzuRlCW%2F%2Brv3FDzJvm%2Fsaudk6GFjNypMRkV3O8g%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=TMxrg26ywABOyJtGYT3KVLrGP0TYIHQ9E3ePlr%2BQepg%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=vWkErsH55%2BLhIG%2FBJbtZb8NSNHFyNAzxk5VjW4p6lcM%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=cbDgvPNn4LNXDsUXZwmWCjRMj0O9PnFSqSCtuCPMTFo%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=wvqhR%2Bnzw0uLEsCGETAxkKrdwN5eI%2FgvTeB4juQ4aUI%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=kkirWrsrpjcrKndUUPxuJVeRWu0GthsVZ4cXpxbEGMg%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"logs/azureml/728_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/logs/azureml/728_azureml.log?sv=2019-02-02&sr=b&sig=pK%2F6TBBvQEPexjuRPR1FyOq6CUPXfnNBobkTmpmaeiM%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=o%2BPcdcJvKZyQWRA0HpaJbM%2BxhqFOkdDjgBqtxtHtoag%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"logs/azureml/728_azureml.log\"]], \"run_duration\": \"0:01:53\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-07 01:55:16,975|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-07 01:55:17,242|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-07 01:55:17,243|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-07 01:55:18,070|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-07 01:55:18,075|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-07 01:55:18,078|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-07 01:55:18,082|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-07 01:55:18,083|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:55:18,088|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,089|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-07 01:55:18,089|azureml.core.authentication|DEBUG|Time to expire 1814376.910384 seconds\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,118|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,122|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,128|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,132|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,136|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,141|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,141|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-07 01:55:18,142|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG|None\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:55:18 GMT'\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 2107,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-07T01:54:55.3303306+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-07T01:55:07.6378716+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575683693_ddd16e31/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-07 01:55:18,202|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-07 01:55:18,203|azureml.WorkerPool|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,157|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,158|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-07 01:55:20,162|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,166|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,170|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,175|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,175|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-07 01:55:20,175|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31'\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG|None\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:55:20 GMT'\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 2107,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-07T01:54:55.3303306+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-07T01:55:07.6378716+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575683693_ddd16e31/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-07 01:55:48,084|azureml.core.authentication|DEBUG|Time to expire 1814346.915499 seconds\\n2019-12-07 01:56:18,084|azureml.core.authentication|DEBUG|Time to expire 1814316.915133 seconds\\n2019-12-07 01:56:25,858|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-07 01:56:25,858|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:56:25,859|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-07 01:56:25,925|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|INFO|complete is not setting status for submitted runs.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-07 01:56:25,927|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-07 01:56:25,927|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-07 01:56:25,928|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-07 01:56:25,928|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-07 01:56:25,928|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-07 01:56:25,929|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:56:25,929|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-07 01:56:25,929|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-07 01:56:25,929|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31/batch/metrics'\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:25,931|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-07 01:56:25,932|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-07 01:56:25,932|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"1a8ad3d8-accf-42da-a07d-fd00ef5ee1e6\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-07T01:56:25.858188Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:56:26,050|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:56:26 GMT'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|\\n2019-12-07 01:56:26,052|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-07 01:56:26,182|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-07 01:56:26,182|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002186298370361328 seconds.\\n\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-07 01:56:26,183|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -1010,14 +1010,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Counter16: submission of job 16 on 10 nodes took 9.082231044769287 seconds \n", - "run list length 16\n", - "Counter17: submission of job 17 on 10 nodes took 9.493095874786377 seconds \n", - "run list length 17\n", - "Counter18: submission of job 18 on 10 nodes took 11.00885009765625 seconds \n", - "run list length 18\n", - "Counter19: submission of job 19 on 10 nodes took 8.174627780914307 seconds \n", - "run list length 19\n" + "Counter499: submission of job 499 on 400 nodes took 9.16640019416809 seconds \n", + "run list length 499\n" ] } ], @@ -1025,8 +1019,8 @@ "import time\n", "from IPython.display import clear_output\n", "\n", - "no_of_jobs = 20\n", - "no_of_nodes = 10\n", + "no_of_jobs = 500\n", + "no_of_nodes = 400\n", "\n", "job_counter = 0\n", "print_cycle = 7\n", @@ -1060,10 +1054,106 @@ { "data": { "text/plain": [ - "array([ 9.56427002, 10.400774 , 9.58173323, 10.15340447, 11.36087322,\n", - " 12.94680119, 10.28511715, 9.50729609, 10.37926507, 8.8516674 ,\n", - " 8.78578663, 10.88563776, 10.30812001, 8.43333387, 8.51853824,\n", - " 9.08223104, 9.49309587, 11.0088501 , 8.17462778])" + "array([10.16889381, 10.52522182, 8.67223501, 7.76976609, 8.98659873,\n", + " 9.54043746, 7.56379271, 7.95067477, 10.98772812, 8.58469343,\n", + " 9.19690919, 8.37747335, 8.49322033, 8.96249437, 11.00566387,\n", + " 10.18721223, 8.70340395, 9.07873917, 8.83641577, 9.93886757,\n", + " 8.43751788, 8.88584614, 8.46158338, 8.10118651, 7.95576859,\n", + " 8.02682757, 8.59585524, 11.43893504, 8.21132302, 7.56929898,\n", + " 9.16166759, 7.96446443, 8.20211887, 8.0066514 , 8.16604567,\n", + " 9.03855515, 9.27646971, 7.88356876, 8.6105082 , 8.63279152,\n", + " 9.63798594, 7.88380122, 11.83064437, 7.67609763, 8.36450744,\n", + " 10.36203027, 8.20605659, 8.27934074, 8.71854138, 7.48072934,\n", + " 7.98534775, 7.88993239, 9.49783468, 8.20365477, 8.31964707,\n", + " 8.24653029, 9.14784336, 8.39632297, 8.88221884, 10.17075896,\n", + " 7.93166018, 8.50952411, 8.35107565, 8.62145162, 9.1473949 ,\n", + " 10.16314006, 9.48931861, 9.52163553, 10.48561263, 8.70149064,\n", + " 8.83968425, 8.77899456, 8.19752908, 8.23720503, 8.44300842,\n", + " 10.4865036 , 9.38597918, 8.16601682, 10.31557417, 9.39266205,\n", + " 9.3517375 , 8.26235414, 9.90602231, 8.08361053, 9.55309701,\n", + " 8.37694287, 8.2842195 , 9.27187061, 8.05741239, 9.81221128,\n", + " 8.67282987, 7.50111246, 8.84159875, 7.5928266 , 8.2180264 ,\n", + " 11.30247498, 8.97954369, 9.08557224, 8.62394547, 27.931288 ,\n", + " 11.31702137, 9.03355598, 9.82408452, 10.98696327, 8.15972924,\n", + " 8.10580516, 8.6766634 , 9.18826079, 9.91399217, 9.63535714,\n", + " 8.84899211, 8.59690166, 9.08935356, 7.87525439, 9.04824638,\n", + " 10.58436322, 8.05351543, 8.0442934 , 8.51687765, 8.23182964,\n", + " 7.90365982, 9.41734576, 7.82690763, 7.86053801, 8.81060672,\n", + " 15.63083076, 9.12365007, 8.4692018 , 8.38626456, 9.1455934 ,\n", + " 7.9579742 , 8.32254815, 9.60984373, 7.72059083, 9.80256414,\n", + " 8.03569841, 8.56897283, 9.88993764, 9.825032 , 9.10494757,\n", + " 7.96795917, 8.83923078, 8.12920213, 9.14702606, 10.44252062,\n", + " 8.11435223, 11.10698366, 8.54753256, 11.07914209, 8.0072608 ,\n", + " 8.64252162, 7.86998582, 8.16502595, 9.72599697, 8.01553535,\n", + " 8.05236411, 9.4306016 , 8.3510747 , 8.15123487, 7.73660946,\n", + " 8.78807712, 8.42650437, 9.09502602, 67.75333071, 14.179214 ,\n", + " 13.08692336, 14.52568007, 12.39239168, 8.40634942, 8.3893857 ,\n", + " 7.80925822, 8.04524732, 10.61561441, 9.33992386, 8.05361605,\n", + " 8.71911073, 8.13864756, 8.18779135, 8.03402972, 8.20232296,\n", + " 10.52845287, 8.21701574, 9.63750052, 8.16265893, 7.95386362,\n", + " 7.85334754, 7.96290469, 8.1984942 , 8.32950211, 17.0101552 ,\n", + " 14.20266891, 13.09765553, 14.32137418, 8.90045214, 9.79849219,\n", + " 7.7378149 , 8.17814636, 8.0692122 , 8.02391315, 7.73337412,\n", + " 8.24749708, 8.21430159, 8.42469835, 7.93915629, 8.17162681,\n", + " 9.29439068, 8.39062524, 8.05844831, 12.62865376, 8.03868556,\n", + " 8.03020358, 8.72658324, 7.98921943, 10.13008642, 8.36204886,\n", + " 9.8618927 , 8.84138846, 8.26497674, 8.53586483, 11.22441888,\n", + " 8.60046291, 9.52709126, 8.1862669 , 8.47402501, 8.08845234,\n", + " 8.0216496 , 8.25297642, 9.52822161, 8.53732967, 9.20458651,\n", + " 7.84344959, 8.76693869, 9.55830622, 9.32047439, 9.61785316,\n", + " 14.20765901, 13.20616293, 12.79950929, 13.23175693, 10.48755121,\n", + " 7.89634991, 8.62207508, 10.17518067, 9.5078795 , 8.16943836,\n", + " 11.88958383, 8.53581595, 8.78866196, 9.86849713, 8.38485384,\n", + " 7.80456519, 8.7930553 , 8.67091751, 11.64525867, 10.70969439,\n", + " 9.57600379, 7.88863015, 9.16765165, 8.10214615, 8.1002388 ,\n", + " 7.79884577, 7.84607792, 10.70999765, 8.32228923, 8.15903163,\n", + " 8.16516185, 11.13710332, 8.67460465, 8.04933095, 7.92010641,\n", + " 9.71926355, 7.96389985, 8.50223684, 7.80719972, 7.94503832,\n", + " 9.14503789, 8.74866915, 8.32825327, 9.38176489, 8.7043674 ,\n", + " 8.11469626, 8.39300489, 8.52375507, 9.48120856, 9.30481339,\n", + " 11.00180173, 8.00356221, 9.36562443, 11.26503015, 8.29429078,\n", + " 10.5787971 , 8.23888326, 8.25085521, 9.65488529, 10.22367787,\n", + " 8.86958766, 8.67924905, 9.8065629 , 9.98437238, 10.44085979,\n", + " 8.48997521, 13.41537356, 8.53429914, 9.41697288, 8.75000739,\n", + " 8.67022324, 10.65776849, 8.78767824, 29.17240787, 8.29843664,\n", + " 10.48030996, 8.60965252, 9.05648637, 11.23915553, 7.71198177,\n", + " 8.58811665, 11.27894258, 11.26059055, 8.08691239, 9.09145069,\n", + " 8.37398744, 9.33932018, 9.50723815, 14.62887979, 8.08766961,\n", + " 8.1010766 , 8.15962887, 7.86279893, 7.81253982, 8.72090292,\n", + " 28.51810336, 8.20156765, 8.10436082, 9.35736108, 10.11271501,\n", + " 8.28001332, 8.10338402, 7.82260585, 7.74735689, 9.37371802,\n", + " 7.83298874, 8.09861684, 11.44845009, 13.80942464, 13.86787438,\n", + " 12.95256805, 13.5946703 , 9.04438519, 8.42931032, 7.69650388,\n", + " 8.3203001 , 8.93009233, 8.99896145, 10.261621 , 9.76696181,\n", + " 8.42695355, 9.45543766, 8.35829163, 8.19327784, 8.54582119,\n", + " 10.28408813, 9.96855664, 9.4126513 , 8.85548735, 8.37564468,\n", + " 7.85812593, 11.26866746, 11.99777699, 8.90290856, 9.73011518,\n", + " 11.37953544, 9.56070495, 13.08286595, 7.91717887, 8.70709944,\n", + " 8.89286566, 9.43534017, 9.63375568, 9.45693254, 9.41722798,\n", + " 8.95478702, 10.59636545, 9.07217526, 8.91465688, 8.43598938,\n", + " 10.09872103, 8.53826594, 10.51633263, 8.16474724, 9.60920191,\n", + " 8.79985189, 11.08250904, 15.82575488, 13.72388315, 13.76962495,\n", + " 15.5107224 , 12.99527621, 9.55358648, 11.27318692, 10.64224267,\n", + " 9.28194666, 8.15835619, 10.34727526, 9.13943338, 8.47959018,\n", + " 12.95671797, 8.67874169, 9.48093748, 11.13487458, 11.16393185,\n", + " 9.45039058, 9.26687908, 10.83345985, 10.013412 , 12.88114643,\n", + " 8.90868664, 9.11424375, 10.62471223, 10.37447572, 8.56728458,\n", + " 11.44042325, 8.61506176, 14.37763166, 9.26899981, 9.01356244,\n", + " 12.6770153 , 7.95549965, 8.69824529, 8.16541219, 10.80149889,\n", + " 9.85532331, 9.16404986, 11.05029202, 8.95759201, 9.60003638,\n", + " 8.64066339, 11.99474025, 10.88645577, 9.82658648, 8.38357234,\n", + " 8.1931479 , 8.36809587, 8.34779596, 9.29737759, 7.71148348,\n", + " 8.34155583, 8.46944427, 9.46755242, 8.39070392, 9.67334032,\n", + " 9.42819619, 8.90718842, 8.95999622, 17.03638124, 14.13874507,\n", + " 14.17324162, 14.82433629, 10.27358413, 7.75390744, 10.63386297,\n", + " 10.74013877, 9.25264263, 8.88592076, 15.62230277, 8.68499494,\n", + " 7.90613437, 10.8253715 , 9.28829837, 9.96133757, 8.82941794,\n", + " 11.07499003, 9.08565426, 8.76584291, 11.91541052, 9.45269704,\n", + " 9.68554997, 9.76184082, 10.95884109, 9.22084093, 9.07609534,\n", + " 9.72482204, 8.66262245, 8.85580897, 12.12771249, 9.1096139 ,\n", + " 9.55135322, 9.73613167, 12.00068331, 9.63835907, 8.8003633 ,\n", + " 10.78142428, 10.36234426, 8.7075491 , 8.79299307, 10.6836946 ,\n", + " 8.24508142, 9.70224071, 8.64105797, 9.16640019])" ] }, "execution_count": 22, @@ -1073,7 +1163,7 @@ { "data": { "text/plain": [ - "(array([0, 0, 0, 0, 1, 2, 3, 2, 2]),\n", + "(array([ 0, 0, 0, 16, 105, 85, 75, 61, 40]),\n", " array([ 6. , 6.44444444, 6.88888889, 7.33333333, 7.77777778,\n", " 8.22222222, 8.66666667, 9.11111111, 9.55555556, 10. ]))" ] @@ -1098,7 +1188,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 130, time 478.619 seconds: Counter({'Completed': 19})izing': 1})'Running': 1})Queued': 1})\r" + "Final print 24, time 107.859 seconds: Counter({'Completed': 478, 'Failed': 21})izing': 1})Running': 1})\r" ] } ], @@ -1135,8 +1225,6 @@ " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", "\n", - " \n", - " \n", "wait_for_run_list_to_finish(run_list, plot_results=False)" ] }, @@ -1158,8 +1246,8 @@ { "data": { "text/plain": [ - "array([18, 0, 12, 4, 13, 17, 16, 1, 14, 15, 9, 10, 2, 8, 3, 5, 7,\n", - " 11, 6])" + "array([28, 33, 15, 45, 18, 43, 30, 31, 65, 6, 42, 16, 11, 41, 19, 8, 5,\n", + " 2, 64, 34])" ] }, "execution_count": 25, @@ -1170,21 +1258,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ 95.714705 97.874433 101.482868 104.059911 104.543689 104.735191\n", - " 109.463122 109.99054 110.348304 111.175483 116.1721 237.121394\n", - " 238.584713 239.419802 239.850993 242.752749 243.522224 246.953329\n", - " 250.967768]\n", + "[244.173832 244.510378 245.027595 245.540781 247.395535 247.411761\n", + " 247.933416 248.256958 248.468753 249.724234 249.874347 250.013758\n", + " 250.53221 251.10704 251.400594 253.192625 253.421425 253.968411\n", + " 256.888013 260.331917]\n", "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed']\n" + " 'Completed' 'Failed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Failed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Failed' 'Completed']\n" ] }, { "data": { "text/plain": [ - "array([18, 0, 12, 4, 13, 17, 16, 1, 14, 15, 9, 10, 2, 8, 3, 5, 7,\n", - " 11, 6])" + "array([232, 54, 195, 214, 250, 48, 490, 261, 329, 140, 336, 129, 311,\n", + " 223, 226, 370, 319, 254, 197, 85])" ] }, "execution_count": 25, @@ -1195,20 +1283,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ 95.714705 97.874433 101.482868 104.059911 104.543689 104.735191\n", - " 109.463122 109.99054 110.348304 111.175483 116.1721 237.121394\n", - " 238.584713 239.419802 239.850993 242.752749 243.522224 246.953329\n", - " 250.967768]\n", - "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed']\n" + "[92.52469 92.854187 93.127771 93.19945 93.319895 93.372538 93.557287\n", + " 93.579393 93.646901 93.681486 93.890417 94.05724 94.162242 94.165297\n", + " 94.182998 94.263456 94.316783 94.400242 94.406081 94.583321]\n", + "['Completed' 'Completed' 'Completed' 'Completed' 'Failed' 'Completed'\n", + " 'Failed' 'Failed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Failed' 'Completed' 'Completed'\n", + " 'Failed' 'Completed']\n" ] }, { "data": { "text/plain": [ - "(array([0, 0, 2, 9, 0, 0, 0, 0, 0]),\n", + "(array([ 0, 0, 128, 320, 8, 1, 3, 3, 0]),\n", " array([ 50. , 66.66666667, 83.33333333, 100. ,\n", " 116.66666667, 133.33333333, 150. , 166.66666667,\n", " 183.33333333, 200. ]))" From 597bf322c495b8dd976943d6d746f99c9b7cfac7 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Sat, 7 Dec 2019 03:06:14 +0000 Subject: [PATCH 129/207] tested both yml conda env and docker; udated conda yml to have docker sdk; added --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index adcca7ed..6b8a4c1b 100644 --- a/README.md +++ b/README.md @@ -269,11 +269,11 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope # Troubleshooting For Data Science Virtual Machine conda package installation issues, make sure you locate the anaconda location on the DSVM, for example by running: -``` +```bash which python ``` A typical output will be: -``` +```bash someusername@somevm:/projects/DeepSeismic$ which python /anaconda/envs/py35/bin/python ``` From 585b9b3dc53a2c41d26f398c5d27f8b2b66700cd Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Sat, 7 Dec 2019 03:21:34 +0000 Subject: [PATCH 130/207] NVIDIA Tesla K80 (or V100 GPU for NCv2 series) - per Vanja's comment --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b8a4c1b..1c3738df 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Follow the instruction bellow to read about compute requirements and install req We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on __Linux only__. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DSVM) for Linux (Ubuntu)](https://docs.microsoft.com/en-us/azure/machine-learning/data-science-virtual-machine/dsvm-ubuntu-intro). This VM will come installed with all the system requirements that are needed to create the conda environment described below and then run the notebooks in this repository. -For this repo, we recommend selecting a multi-GPU Ubuntu VM of type [Standard_NC12](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series). The machine is powered by NVIDIA Tesla V100 GPU which can be found in most Azure regions. +For this repo, we recommend selecting a multi-GPU Ubuntu VM of type [Standard_NC12](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series). The machine is powered by NVIDIA Tesla K80 (or V100 GPU for NCv2 series) which can be found in most Azure regions. > NOTE: For users new to Azure, your subscription may not come with a quota for GPUs. You may need to go into the Azure portal to increase your quota for GPU VMs. Learn more about how to do this here: https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits. From ba18358be75e4f4b48805ad026f5d4c550c1a111 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 9 Dec 2019 09:57:40 -0500 Subject: [PATCH 131/207] notebook integration tests complete (#106) * added README documentation per bug bush feedback * HRNet notebook works with tests now * removed debug material from the notebook * corrected duplicate build names * conda init fix * changed setup deps * fixed F3 notebook - merge conflict and pytorch bug * main and notebook builds have functional setup now --- ..._block_training_and_evaluation_local.ipynb | 362 +++++++++++++++--- .../HRNet_Penobscot_demo_notebook.ipynb | 26 +- scripts/env_reinstall.sh | 10 + tests/cicd/main_build.yml | 44 ++- tests/cicd/notebooks_build.yml | 22 +- tests/cicd/src/conftest.py | 16 + tests/cicd/src/notebook_integration_tests.py | 6 +- 7 files changed, 394 insertions(+), 92 deletions(-) create mode 100755 scripts/env_reinstall.sh diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index ea8f715a..320f40a3 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -75,7 +75,7 @@ "from ignite.engine import Events\n", "from ignite.metrics import Loss\n", "from ignite.utils import convert_tensor\n", - "from toolz import compose\n", + "from toolz import compose, take\n", "from torch.utils import data\n", "\n", "from cv_lib.utils import load_log_configuration\n", @@ -141,7 +141,7 @@ " └── train_seismic.npy\n", "```\n", "\n", - "We recommend saving the data under `/mnt/dutchf3` since this notebook will use that location as the data root. Otherwise, modify the `DATASET.ROOT` field in the configuration file, described next. " + "We recommend saving the data under `$HOME/data/dutchf3` since this notebook will use that location as the data root. Otherwise, modify the `DATASET.ROOT` field in the configuration file, described next. " ] }, { @@ -162,9 +162,71 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Configuration loaded. Please check that the DATASET.ROOT:/data/dutchf3 points to your data location.\n", + "To modify any of the options, please edit the configuration file ./configs/patch_deconvnet_skip.yaml and reload. \n", + "\n", + "CUDNN:\n", + " BENCHMARK: True\n", + " DETERMINISTIC: False\n", + " ENABLED: True\n", + "DATASET:\n", + " CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852]\n", + " NUM_CLASSES: 6\n", + " ROOT: /data/dutchf3\n", + "GPUS: (0,)\n", + "LOG_CONFIG: logging.conf\n", + "LOG_DIR: log\n", + "MODEL:\n", + " IN_CHANNELS: 1\n", + " NAME: patch_deconvnet_skip\n", + "OUTPUT_DIR: output\n", + "PRINT_FREQ: 50\n", + "SEED: 2019\n", + "TEST:\n", + " CROSSLINE: True\n", + " INLINE: True\n", + " MODEL_PATH: /data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth\n", + " POST_PROCESSING:\n", + " CROP_PIXELS: 0\n", + " SIZE: 99\n", + " SPLIT: test1\n", + " TEST_STRIDE: 10\n", + "TRAIN:\n", + " AUGMENTATION: True\n", + " AUGMENTATIONS:\n", + " PAD:\n", + " HEIGHT: 99\n", + " WIDTH: 99\n", + " RESIZE:\n", + " HEIGHT: 99\n", + " WIDTH: 99\n", + " BATCH_SIZE_PER_GPU: 64\n", + " BEGIN_EPOCH: 0\n", + " DEPTH: none\n", + " END_EPOCH: 100\n", + " MAX_LR: 0.02\n", + " MEAN: 0.0009997\n", + " MIN_LR: 0.001\n", + " MODEL_DIR: models\n", + " MOMENTUM: 0.9\n", + " PATCH_SIZE: 99\n", + " SNAPSHOTS: 5\n", + " STD: 0.20977\n", + " STRIDE: 50\n", + " WEIGHT_DECAY: 0.0001\n", + "VALIDATION:\n", + " BATCH_SIZE_PER_GPU: 512\n", + "WORKERS: 4\n" + ] + } + ], "source": [ "with open(CONFIG_FILE, \"rt\") as f_read:\n", " config = yacs.config.load_cfg(f_read)\n", @@ -183,7 +245,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "tags": [ "parameters" @@ -191,8 +253,27 @@ }, "outputs": [], "source": [ - "max_epochs = config.TRAIN.END_EPOCH \n", - "max_snaphsots = config.TRAIN.SNAPSHOTS" + "# The number of datapoints you want to run in training or validation per batch \n", + "# Setting to None will run whole dataset\n", + "# useful for integration tests with a setting of something like 3\n", + "# Use only if you want to check things are running and don't want to run\n", + "# through whole dataset\n", + "max_iterations = None\n", + "# The number of epochs to run in training\n", + "max_epochs = config.TRAIN.END_EPOCH \n", + "max_snapshots = config.TRAIN.SNAPSHOTS\n", + "dataset_root = config.DATASET.ROOT" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# reduce number of test images if running a dummy model\n", + "if max_epochs<2:\n", + " N_EVALUATE=3" ] }, { @@ -211,13 +292,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of inline slices: 401\n", + "Number of crossline slices: 701\n", + "Depth dimension : 255\n" + ] + } + ], "source": [ "# Load training data and labels\n", - "train_seismic = np.load(path.join(config.DATASET.ROOT, \"train/train_seismic.npy\"))\n", - "train_labels = np.load(path.join(config.DATASET.ROOT, \"train/train_labels.npy\"))\n", + "train_seismic = np.load(path.join(dataset_root, \"train/train_seismic.npy\"))\n", + "train_labels = np.load(path.join(dataset_root, \"train/train_labels.npy\"))\n", "\n", "print(f\"Number of inline slices: {train_seismic.shape[0]}\")\n", "print(f\"Number of crossline slices: {train_seismic.shape[1]}\")\n", @@ -226,9 +317,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1aadea98bda8458fbc03782571b1d4b7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Viewer(geometries=[], gradient_opacity=0.22, point_sets=[], rendered_image=" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "idx = 100\n", "x_in = train_seismic[idx, :, :].swapaxes(0, 1)\n", @@ -262,9 +381,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "x_cr = train_seismic[:, idx, :].swapaxes(0, 1)\n", "x_crl = train_labels[:, idx, :].swapaxes(0, 1)\n", @@ -281,9 +413,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-12-09 04:08:24,354 - cv_lib.utils - INFO - logging.conf configuration file was loaded.\n" + ] + } + ], "source": [ "# Set up logging\n", "load_log_configuration(config.LOG_CONFIG)\n", @@ -303,7 +443,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -352,14 +492,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "This no longer pads the volume\n" + ] + } + ], "source": [ "TrainPatchLoader = get_patch_loader(config)\n", "\n", "train_set = TrainPatchLoader(\n", - " config.DATASET.ROOT,\n", + " dataset_root,\n", " split=\"train\",\n", " is_transform=True,\n", " stride=config.TRAIN.STRIDE,\n", @@ -394,7 +542,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -453,9 +601,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# create training engine\n", "trainer = create_supervised_trainer(\n", @@ -477,7 +636,7 @@ "# add model checkpointing\n", "output_dir = path.join(config.OUTPUT_DIR, config.TRAIN.MODEL_DIR)\n", "checkpoint_handler = ModelCheckpoint(\n", - " output_dir, \"model\", save_interval=2, n_saved=3, create_dir=True, require_empty=False\n", + " output_dir, \"model\", save_interval=1, n_saved=10, create_dir=True, require_empty=False\n", ")\n", "trainer.add_event_handler(\n", " Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model}\n", @@ -488,14 +647,59 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Start the training engine run." + "Limit the number of datapoints if we happen to be in test mode" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], + "source": [ + "if max_iterations is not None: \n", + " train_loader = take(max_iterations, train_loader)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Start the training engine run." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-12-09 04:08:27,209 - ignite.engine.engine.Engine - INFO - Engine run starting with max_epochs=1.\n", + "2019-12-09 04:08:27,211 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.02]\n", + "2019-12-09 04:08:31,265 - ignite.engine.engine.Engine - INFO - Epoch[1] Complete. Time taken: 00:00:04\n", + "2019-12-09 04:08:32,319 - ignite.engine.engine.Engine - INFO - Engine run complete. Time taken 00:00:05\n" + ] + }, + { + "data": { + "text/plain": [ + "State:\n", + "\toutput: \n", + "\tbatch: \n", + "\tdataloader: \n", + "\tmax_epochs: 1\n", + "\tmetrics: \n", + "\titeration: 3\n", + "\tepoch: 1" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "trainer.run(train_loader, max_epochs=max_epochs)" ] @@ -531,7 +735,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -541,13 +745,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", - "model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False)\n", - "model = model.to(device)\n" + "if max_epochs>1: \n", + " model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False)\n", + "else:\n", + " model.load_state_dict(torch.load(path.join(output_dir, \"model_patch_deconvnet_skip_1.pth\")), strict=False)\n", + "model = model.to(device)" ] }, { @@ -559,7 +766,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -586,22 +793,24 @@ ")\n", "\n", "# Process test data\n", - "pre_processing = _compose_processing_pipeline(config.TRAIN.DEPTH, aug=patch_aug)\n", - "output_processing = _output_processing_pipeline(config)\n", + "pre_processing = compose_processing_pipeline(config.TRAIN.DEPTH, aug=patch_aug)\n", + "output_processing = output_processing_pipeline(config)\n", "\n", "# Select the test split\n", "split = \"test1\" if \"both\" in config.TEST.SPLIT else config.TEST.SPLIT\n", "\n", - "labels = np.load(path.join(config.DATASET.ROOT, \"test_once\", split + \"_labels.npy\"))\n", - "section_file = path.join(config.DATASET.ROOT, \"splits\", \"section_\" + split + \".txt\")\n", - "_write_section_file(labels, section_file, config)\n", + "labels = np.load(path.join(dataset_root, \"test_once\", split + \"_labels.npy\"))\n", + "section_file = path.join(dataset_root, \"splits\", \"section_\" + split + \".txt\")\n", + "write_section_file(labels, section_file, config)\n", "\n", "# Load test data\n", "TestSectionLoader = get_test_loader(config)\n", "test_set = TestSectionLoader(\n", - " config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug\n", + " dataset_root, split=split, is_transform=True, augmentations=section_aug\n", ")\n", - "\n", + "# needed to fix this bug in pytorch https://github.com/pytorch/pytorch/issues/973\n", + "# one of the workers will quit prematurely\n", + "torch.multiprocessing.set_sharing_strategy('file_system')\n", "test_loader = data.DataLoader(\n", " test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False\n", ")\n" @@ -618,9 +827,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-12-09 04:08:36,547 - __main__ - INFO - split: test1, section: 0\n", + "2019-12-09 04:08:41,543 - __main__ - INFO - split: test1, section: 1\n", + "2019-12-09 04:08:42,067 - __main__ - INFO - split: test1, section: 2\n" + ] + } + ], "source": [ "CLASS_NAMES = [\n", " \"upper_ns\",\n", @@ -645,7 +864,7 @@ " # loop over testing data\n", " for i, (images, labels) in enumerate(test_subset):\n", " logger.info(f\"split: {split}, section: {i}\")\n", - " outputs = _patch_label_2d(\n", + " outputs = patch_label_2d(\n", " model,\n", " images,\n", " pre_processing,\n", @@ -679,9 +898,34 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pixel Acc: 0.522\n", + " upper_ns_accuracy 0.000\n", + " middle_ns_accuracy 0.000\n", + " lower_ns_accuracy 0.999\n", + " rijnland_chalk_accuracy 0.000\n", + " scruff_accuracy 0.001\n", + " zechstein_accuracy nan\n", + "Mean Class Acc: 0.200\n", + "Freq Weighted IoU: 0.273\n", + "Mean IoU: 0.105\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "invalid value encountered in true_divide\n", + "invalid value encountered in true_divide\n" + ] + } + ], "source": [ "# get scores\n", "score, _ = running_metrics_split.get_scores()\n", @@ -712,11 +956,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": { "scrolled": false }, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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AACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAA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0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAnihKmAAAgAElEQVQATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACg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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "fig = plt.figure(figsize=(15,50))\n", "\n", @@ -733,6 +990,13 @@ "_ = f_axes[0].set_title('Actual')\n", "_ = f_axes[1].set_title('Predicted') " ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 47ce07c5..cf6366fd 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -166,12 +166,16 @@ }, "outputs": [], "source": [ - "max_iterations = None # The number of iterations you want to run in training or validation\n", + "# The number of datapoints you want to run in training or validation per batch \n", "# Setting to None will run whole dataset\n", + "# useful for integration tests with a setting of something like 3\n", "# Use only if you want to check things are running and don't want to run\n", "# through whole dataset\n", - "max_epochs = config.TRAIN.END_EPOCH # The number of epochs to run in training\n", - "max_snapshots = config.TRAIN.SNAPSHOTS" + "max_iterations = None \n", + "# The number of epochs to run in training\n", + "max_epochs = config.TRAIN.END_EPOCH \n", + "max_snapshots = config.TRAIN.SNAPSHOTS\n", + "dataset_root = config.DATASET.ROOT" ] }, { @@ -199,8 +203,8 @@ "metadata": {}, "outputs": [], "source": [ - "image_dir = os.path.join(config.DATASET.ROOT, \"inlines\")\n", - "mask_dir = os.path.join(config.DATASET.ROOT, \"masks\")\n", + "image_dir = os.path.join(dataset_root, \"inlines\")\n", + "mask_dir = os.path.join(dataset_root, \"masks\")\n", "\n", "image_iter = pipe(os.path.join(image_dir, \"*.tiff\"), glob.iglob,)\n", "\n", @@ -360,7 +364,7 @@ "outputs": [], "source": [ "train_set = PenobscotInlinePatchDataset(\n", - " config.DATASET.ROOT,\n", + " dataset_root,\n", " config.TRAIN.PATCH_SIZE,\n", " config.TRAIN.STRIDE,\n", " split=\"train\",\n", @@ -370,7 +374,7 @@ ")\n", "\n", "val_set = PenobscotInlinePatchDataset(\n", - " config.DATASET.ROOT,\n", + " dataset_root,\n", " config.TRAIN.PATCH_SIZE,\n", " config.TRAIN.STRIDE,\n", " split=\"val\",\n", @@ -593,7 +597,7 @@ "outputs": [], "source": [ "logger.info(\"Starting training\")\n", - "trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH)" + "trainer.run(train_loader, max_epochs=max_epochs)" ] }, { @@ -610,7 +614,8 @@ "metadata": {}, "outputs": [], "source": [ - "%load_ext tensorboard" + "if max_epochs>1:\n", + " %load_ext tensorboard" ] }, { @@ -619,7 +624,8 @@ "metadata": {}, "outputs": [], "source": [ - "%tensorboard --logdir outputs --port 6007 --host 0.0.0.0" + "if max_epochs>1:\n", + " %tensorboard --logdir outputs --port 6007 --host 0.0.0.0" ] } ], diff --git a/scripts/env_reinstall.sh b/scripts/env_reinstall.sh new file mode 100755 index 00000000..9b2f2935 --- /dev/null +++ b/scripts/env_reinstall.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +conda env remove -n seismic-interpretation +yes | conda env create -f environment/anaconda/local/environment.yml +# don't use conda here as build VM's shell isn't setup when running as a build agent +source activate seismic-interpretation +pip install -e cv_lib +pip install -e interpretation +# temporary DS VM bugfix +yes | conda install pytorch torchvision cudatoolkit=9.2 -c pytorch diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 361934f1..1573f462 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -14,7 +14,7 @@ trigger: jobs: # partially disable setup for now - done manually on build VM - job: setup - timeoutInMinutes: 5 + timeoutInMinutes: 10 displayName: Setup pool: @@ -28,18 +28,13 @@ jobs: git branch uname -ra - # make sure we have the latest and greatest - # yes | conda env update -f environment/anaconda/local/environment.yml - conda activate seismic-interpretation - pip install -e interpretation - pip install -e cv_lib - # add this if pytorch stops detecting GPU - # conda install pytorch torchvision cudatoolkit=9.2 -c pytorch + ./scripts/env_reinstall.sh # copy your model files like so - using dummy file to illustrate azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name - job: unit_tests_job + dependsOn: setup timeoutInMinutes: 20 pool: name: deepseismicagentpool @@ -56,6 +51,7 @@ jobs: ################################################################################################### - job: hrnet_patch + dependsOn: setup timeoutInMinutes: 5 displayName: hrnet patch pool: @@ -72,6 +68,7 @@ jobs: python test.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/hrnet_running_model_1.pth - job: unet_patch + dependsOn: setup timeoutInMinutes: 5 displayName: unet patch pool: @@ -88,6 +85,7 @@ jobs: python test.py --cfg=configs/unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/unet_running_model_1.pth - job: seresnet_unet_patch + dependsOn: setup timeoutInMinutes: 5 displayName: seresnet unet patch pool: @@ -104,6 +102,7 @@ jobs: python test.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/seresnet_unet_running_model_1.pth - job: patch_deconvnet + dependsOn: setup timeoutInMinutes: 5 displayName: patch deconvnet pool: @@ -120,6 +119,7 @@ jobs: python test.py --cfg=configs/patch_deconvnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/patch_deconvnet_running_model_1.pth - job: patch_deconvnet_skip + dependsOn: setup timeoutInMinutes: 5 displayName: patch deconvnet skip pool: @@ -138,6 +138,7 @@ jobs: # Penobscot dataset - job: seresnet_unet + dependsOn: setup timeoutInMinutes: 5 displayName: seresnet unet pool: @@ -154,6 +155,7 @@ jobs: python test.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/models/seresnet_unet_running_model_1.pth - job: hrnet_penobscot + dependsOn: setup timeoutInMinutes: 5 displayName: hrnet penobscot pool: @@ -173,9 +175,10 @@ jobs: # DISTRIBUTED PATCH JOBS ################################################################################################### -- job: hrnet_patch +- job: hrnet_patch_dist + dependsOn: setup timeoutInMinutes: 5 - displayName: hrnet patch + displayName: hrnet patch distributed pool: name: deepseismicagentpool steps: @@ -186,9 +189,10 @@ jobs: cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 -- job: unet_patch +- job: unet_patch_dist + dependsOn: setup timeoutInMinutes: 5 - displayName: unet patch + displayName: unet patch distributed pool: name: deepseismicagentpool steps: @@ -199,9 +203,10 @@ jobs: cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 -- job: seresnet_unet_patch +- job: seresnet_unet_patch_dist + dependsOn: setup timeoutInMinutes: 5 - displayName: seresnet unet patch + displayName: seresnet unet patch distributed pool: name: deepseismicagentpool steps: @@ -212,9 +217,10 @@ jobs: cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 -- job: patch_deconvnet +- job: patch_deconvnet_dist + dependsOn: setup timeoutInMinutes: 5 - displayName: patch deconvnet + displayName: patch deconvnet distributed pool: name: deepseismicagentpool steps: @@ -225,9 +231,10 @@ jobs: cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/patch_deconvnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 -- job: patch_deconvnet_skip +- job: patch_deconvnet_skip_dist + dependsOn: setup timeoutInMinutes: 5 - displayName: patch deconvnet skip + displayName: patch deconvnet skip distributed pool: name: deepseismicagentpool steps: @@ -244,6 +251,7 @@ jobs: ################################################################################################### - job: section_deconvnet_skip + dependsOn: setup timeoutInMinutes: 5 displayName: section deconvnet skip pool: diff --git a/tests/cicd/notebooks_build.yml b/tests/cicd/notebooks_build.yml index d52ba42e..b1cb1d00 100644 --- a/tests/cicd/notebooks_build.yml +++ b/tests/cicd/notebooks_build.yml @@ -15,7 +15,7 @@ jobs: # partially disable setup for now - done manually on build VM - job: setup - timeoutInMinutes: 5 + timeoutInMinutes: 10 displayName: Setup pool: name: deepseismicagentpool @@ -27,32 +27,30 @@ jobs: ls git branch uname -ra - # make sure we have the latest and greatest - # yes | conda env update -f environment/anaconda/local/environment.yml - conda activate seismic-interpretation - pip install -e interpretation - pip install -e cv_lib - # add this if pytorch stops detecting GPU - # conda install pytorch torchvision cudatoolkit=9.2 -c pytorch + + ./scripts/env_reinstall.sh # copy your model files like so - using dummy file to illustrate azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name + - job: HRNET_demo + dependsOn: setup timeoutInMinutes: 5 displayName: HRNET demo pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation - pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/HRNet_demo_notebook.ipynb + source activate seismic-interpretation + pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb --dataset_root /home/alfred/data/penobscot - job: F3_block_training_and_evaluation_local + dependsOn: setup timeoutInMinutes: 5 displayName: F3 block training and evaluation local pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation - pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb + source activate seismic-interpretation + pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb --dataset_root /home/alfred/data/dutch_f3/data diff --git a/tests/cicd/src/conftest.py b/tests/cicd/src/conftest.py index 65b257b3..f85ae490 100644 --- a/tests/cicd/src/conftest.py +++ b/tests/cicd/src/conftest.py @@ -1,12 +1,28 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import pytest + def pytest_addoption(parser): parser.addoption("--nbname", action="store", type=str, default=None) + parser.addoption("--dataset_root", action="store", type=str, default=None) + +@pytest.fixture +def nbname(request): + return request.config.getoption("--nbname") + +@pytest.fixture +def dataset_root(request): + return request.config.getoption("--dataset_root") +""" def pytest_generate_tests(metafunc): # This is called for every test. Only get/set command line arguments # if the argument is specified in the list of test "fixturenames". option_value = metafunc.config.option.nbname if 'nbname' in metafunc.fixturenames and option_value is not None: metafunc.parametrize("nbname", [option_value]) + option_value = metafunc.config.option.dataset_root + if 'dataset_root' in metafunc.fixturenames and option_value is not None: + metafunc.parametrize("dataset_root", [option_value]) +""" \ No newline at end of file diff --git a/tests/cicd/src/notebook_integration_tests.py b/tests/cicd/src/notebook_integration_tests.py index 07c49687..1c0ccbf8 100644 --- a/tests/cicd/src/notebook_integration_tests.py +++ b/tests/cicd/src/notebook_integration_tests.py @@ -9,11 +9,11 @@ # don't add any markup as this just runs any notebook which name is supplied # @pytest.mark.integration # @pytest.mark.notebooks -def test_notebook_run(nbname): +def test_notebook_run(nbname, dataset_root): pm.execute_notebook( nbname, OUTPUT_NOTEBOOK, kernel_name=KERNEL_NAME, - parameters={"max_epochs":1, "max_snapshots":1}, - cwd="examples/interpretation/notebooks" + parameters={"max_iterations": 3, "max_epochs": 1, "max_snapshots": 1, "dataset_root": dataset_root}, + cwd="examples/interpretation/notebooks", ) From 459a54d6570e8cec44bd9ed47dcdfc934a7de0a1 Mon Sep 17 00:00:00 2001 From: Mat Date: Mon, 9 Dec 2019 20:29:19 +0000 Subject: [PATCH 132/207] Mat/test (#105) * added README documentation per bug bush feedback * Modifies scripts to run for only afew iterations when in debug/test mode * Updates training scripts and build * Making names unique * Fixes conda issue * HRNet notebook works with tests now * removed debug material from the notebook * corrected duplicate build names * conda init fix * Adds docstrings to training script * Testing somehting out * testing * test * test * test * test * test * test * test * test * test * test * test * adds seresnet * Modifies to work outside of git env * test * test * Fixes typo in DATASET * reducing steps * test * test * fixes the argument * Altering batch size to fit k80 * reducing batch size further * test * test * test * test * fixes distributed * test * test * adds missing import * Adds further tests * test * updates * test * Fixes section script * test * testing everyting once through * Final run for badge * changed setup deps, fixed F3 notebook --- .ci/steps/setup_step.yml | 1 + .../demo/local/configs/hrnet.yaml | 107 ------- .../interpretation/demo/local/default.py | 121 ------- .../interpretation/demo/local/logging.conf | 34 -- .../interpretation/demo/local/train.py | 299 ------------------ .../interpretation/demo/local/train.sh | 2 - .../dutchf3_patch/distributed/train.py | 29 +- .../local/configs/patch_deconvnet.yaml | 2 +- .../local/configs/patch_deconvnet_skip.yaml | 2 +- .../dutchf3_patch/local/test.py | 27 +- .../dutchf3_patch/local/train.py | 30 +- .../dutchf3_section/local/test.py | 12 +- .../dutchf3_section/local/train.py | 15 +- .../interpretation/penobscot/local/test.py | 24 +- .../interpretation/penobscot/local/train.py | 28 +- tests/cicd/main_build.yml | 181 +++++------ tests/cicd/penobscot.yml | 179 +++++++++++ 17 files changed, 402 insertions(+), 691 deletions(-) delete mode 100644 experiments/interpretation/demo/local/configs/hrnet.yaml delete mode 100644 experiments/interpretation/demo/local/default.py delete mode 100644 experiments/interpretation/demo/local/logging.conf delete mode 100644 experiments/interpretation/demo/local/train.py delete mode 100755 experiments/interpretation/demo/local/train.sh create mode 100644 tests/cicd/penobscot.yml diff --git a/.ci/steps/setup_step.yml b/.ci/steps/setup_step.yml index 816018c7..e30de986 100644 --- a/.ci/steps/setup_step.yml +++ b/.ci/steps/setup_step.yml @@ -14,6 +14,7 @@ steps: # make sure we have the latest and greatest conda env create -f environment/anaconda/local/environment.yml python=3.6 --force + conda init bash source activate ${{parameters.conda}} pip install -e interpretation pip install -e cv_lib diff --git a/experiments/interpretation/demo/local/configs/hrnet.yaml b/experiments/interpretation/demo/local/configs/hrnet.yaml deleted file mode 100644 index db735b56..00000000 --- a/experiments/interpretation/demo/local/configs/hrnet.yaml +++ /dev/null @@ -1,107 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: 'outputs' -LOG_DIR: 'log' -WORKERS: 4 -PRINT_FREQ: 10 -LOG_CONFIG: logging.conf -SEED: 2019 - - -DATASET: - NUM_CLASSES: 2 - ROOT: /data/msft/seam - CLASS_WEIGHTS: [] - INLINE_HEIGHT: 876 - INLINE_WIDTH: 751 - -MODEL: - NAME: seg_hrnet - IN_CHANNELS: 3 - PRETRAINED: '/data/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' - EXTRA: - FINAL_CONV_KERNEL: 1 - STAGE2: - NUM_MODULES: 1 - NUM_BRANCHES: 2 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - FUSE_METHOD: SUM - STAGE3: - NUM_MODULES: 4 - NUM_BRANCHES: 3 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - FUSE_METHOD: SUM - STAGE4: - NUM_MODULES: 3 - NUM_BRANCHES: 4 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - - 384 - FUSE_METHOD: SUM - -TRAIN: - COMPLETE_PATCHES_ONLY: True - BATCH_SIZE_PER_GPU: 32 - BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.0001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "none" #"patch" # Options are none, patch and section - STRIDE: 64 - PATCH_SIZE: 128 - AUGMENTATIONS: - RESIZE: - HEIGHT: 256 - WIDTH: 256 - PAD: - HEIGHT: 256 - WIDTH: 256 - MEAN: [-0.0001777, 0.49, -0.0000688] # First value is for images, second for depth and then combination of both - STD: [0.14076 , 0.2717, 0.06286] - MAX: 1 - MODEL_DIR: "models" - - -VALIDATION: - BATCH_SIZE_PER_GPU: 128 - COMPLETE_PATCHES_ONLY: True - -TEST: - COMPLETE_PATCHES_ONLY: False - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" - AUGMENTATIONS: - RESIZE: - HEIGHT: 256 - WIDTH: 256 - PAD: - HEIGHT: 256 - WIDTH: 256 diff --git a/experiments/interpretation/demo/local/default.py b/experiments/interpretation/demo/local/default.py deleted file mode 100644 index 24f7671a..00000000 --- a/experiments/interpretation/demo/local/default.py +++ /dev/null @@ -1,121 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright (c) Microsoft -# Licensed under the MIT License. -# ------------------------------------------------------------------------------ - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from yacs.config import CfgNode as CN - -_C = CN() - -_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models -_C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR -_C.GPUS = (0,) -_C.WORKERS = 4 -_C.PRINT_FREQ = 20 -_C.AUTO_RESUME = False -_C.PIN_MEMORY = True -_C.LOG_CONFIG = "logging.conf" -_C.SEED = 42 - -# size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only -_C.WINDOW_SIZE = 65 - -# Cudnn related params -_C.CUDNN = CN() -_C.CUDNN.BENCHMARK = True -_C.CUDNN.DETERMINISTIC = False -_C.CUDNN.ENABLED = True - -# DATASET related params -_C.DATASET = CN() -_C.DATASET.ROOT = "" -_C.DATASET.NUM_CLASSES = 7 -_C.DATASET.CLASS_WEIGHTS = [ - 0.02630481, - 0.05448931, - 0.0811898, - 0.01866496, - 0.15868563, - 0.0875993, - 0.5730662, -] -_C.DATASET.INLINE_HEIGHT = 1501 -_C.DATASET.INLINE_WIDTH = 481 - -# common params for NETWORK -_C.MODEL = CN() -_C.MODEL.NAME = "patch_deconvnet" -_C.MODEL.IN_CHANNELS = 1 -_C.MODEL.PRETRAINED = "" -_C.MODEL.EXTRA = CN(new_allowed=True) - - -# training -_C.TRAIN = CN() -_C.TRAIN.COMPLETE_PATCHES_ONLY = False -_C.TRAIN.MIN_LR = 0.001 -_C.TRAIN.MAX_LR = 0.01 -_C.TRAIN.MOMENTUM = 0.9 -_C.TRAIN.BEGIN_EPOCH = 0 -_C.TRAIN.END_EPOCH = 484 -_C.TRAIN.BATCH_SIZE_PER_GPU = 32 -_C.TRAIN.WEIGHT_DECAY = 0.0001 -_C.TRAIN.SNAPSHOTS = 5 -_C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR -_C.TRAIN.AUGMENTATION = True -_C.TRAIN.STRIDE = 50 -_C.TRAIN.PATCH_SIZE = 99 -_C.TRAIN.MEAN = [0.0009997] # 0.0009996710808862074 -_C.TRAIN.STD = [0.20977] # 0.20976548783479299 -_C.TRAIN.MAX = 255 -_C.TRAIN.DEPTH = "no" # Options are None, Patch and Section -# None adds no depth information and the num of channels remains at 1 -# Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 -# Section adds depth per section so contains depth information for the whole section, channels=3 -_C.TRAIN.AUGMENTATIONS = CN() -_C.TRAIN.AUGMENTATIONS.RESIZE = CN() -_C.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT = 200 -_C.TRAIN.AUGMENTATIONS.RESIZE.WIDTH = 200 -_C.TRAIN.AUGMENTATIONS.PAD = CN() -_C.TRAIN.AUGMENTATIONS.PAD.HEIGHT = 256 -_C.TRAIN.AUGMENTATIONS.PAD.WIDTH = 256 - -# validation -_C.VALIDATION = CN() -_C.VALIDATION.BATCH_SIZE_PER_GPU = 32 -_C.VALIDATION.COMPLETE_PATCHES_ONLY = False - -# TEST -_C.TEST = CN() -_C.TEST.MODEL_PATH = "" -_C.TEST.COMPLETE_PATCHES_ONLY = True -_C.TEST.AUGMENTATIONS = CN() -_C.TEST.AUGMENTATIONS.RESIZE = CN() -_C.TEST.AUGMENTATIONS.RESIZE.HEIGHT = 200 -_C.TEST.AUGMENTATIONS.RESIZE.WIDTH = 200 -_C.TEST.AUGMENTATIONS.PAD = CN() -_C.TEST.AUGMENTATIONS.PAD.HEIGHT = 256 -_C.TEST.AUGMENTATIONS.PAD.WIDTH = 256 - - -def update_config(cfg, options=None, config_file=None): - cfg.defrost() - - if config_file: - cfg.merge_from_file(config_file) - - if options: - cfg.merge_from_list(options) - - cfg.freeze() - - -if __name__ == "__main__": - import sys - - with open(sys.argv[1], "w") as f: - print(_C, file=f) diff --git a/experiments/interpretation/demo/local/logging.conf b/experiments/interpretation/demo/local/logging.conf deleted file mode 100644 index 56334fc4..00000000 --- a/experiments/interpretation/demo/local/logging.conf +++ /dev/null @@ -1,34 +0,0 @@ -[loggers] -keys=root,__main__,event_handlers - -[handlers] -keys=consoleHandler - -[formatters] -keys=simpleFormatter - -[logger_root] -level=INFO -handlers=consoleHandler - -[logger___main__] -level=INFO -handlers=consoleHandler -qualname=__main__ -propagate=0 - -[logger_event_handlers] -level=INFO -handlers=consoleHandler -qualname=event_handlers -propagate=0 - -[handler_consoleHandler] -class=StreamHandler -level=INFO -formatter=simpleFormatter -args=(sys.stdout,) - -[formatter_simpleFormatter] -format=%(asctime)s - %(name)s - %(levelname)s - %(message)s - diff --git a/experiments/interpretation/demo/local/train.py b/experiments/interpretation/demo/local/train.py deleted file mode 100644 index d896f84b..00000000 --- a/experiments/interpretation/demo/local/train.py +++ /dev/null @@ -1,299 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -# /* spell-checker: disable */ - -import logging -import logging.config -from os import path - -import cv2 -import fire -import numpy as np -import torch -from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize -from cv_lib.utils import load_log_configuration -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) -from cv_lib.segmentation import models, extract_metric_from -from cv_lib.segmentation.metrics import ( - pixelwise_accuracy, - class_accuracy, - mean_class_accuracy, - class_iou, - mean_iou, -) -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, - np_to_tb, -) -from cv_lib.segmentation.penobscot.engine import ( - create_supervised_evaluator, - create_supervised_trainer, -) -from deepseismic_interpretation.data import InlinePatchDataset -from deepseismic_interpretation.dutchf3.data import decode_segmap -from default import _C as config -from default import update_config -from ignite.contrib.handlers import CosineAnnealingScheduler -from ignite.engine import Events -from ignite.metrics import Loss -from ignite.utils import convert_tensor -from toolz import compose -from torch.nn import functional as F -from torch.utils import data - -mask_value = 255 -_SEG_COLOURS = np.asarray( - [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123],] -) - - -def _prepare_batch(batch, device=None, non_blocking=False): - x, y, ids, patch_locations = batch - return ( - convert_tensor(x, device=device, non_blocking=non_blocking), - convert_tensor(y, device=device, non_blocking=non_blocking), - ids, - patch_locations, - ) - - -def run(*options, cfg=None): - """Run training and validation of model - - Notes: - Options can be passed in via the options argument and loaded from the cfg file - Options loaded from default.py will be overridden by options loaded from cfg file - Options passed in through options argument will override option loaded from cfg file - - Args: - *options (str,int ,optional): Options used to overide what is loaded from the config. - To see what options are available consult default.py - cfg (str, optional): Location of config file to load. Defaults to None. - """ - - update_config(config, options=options, config_file=cfg) - - # Start logging - load_log_configuration(config.LOG_CONFIG) - logger = logging.getLogger(__name__) - logger.debug(config.WORKERS) - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK - - torch.manual_seed(config.SEED) - if torch.cuda.is_available(): - torch.cuda.manual_seed_all(config.SEED) - np.random.seed(seed=config.SEED) - - # Setup Augmentations - basic_aug = Compose( - [ - Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=config.TRAIN.MAX,), - PadIfNeeded( - min_height=config.TRAIN.PATCH_SIZE, - min_width=config.TRAIN.PATCH_SIZE, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=mask_value, - value=0, - ), - Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, - ), - PadIfNeeded( - min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, - min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, - always_apply=True, - mask_value=mask_value, - value=0, - ), - ] - ) - if config.TRAIN.AUGMENTATION: - train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)]) - val_aug = basic_aug - else: - train_aug = val_aug = basic_aug - - ny = 1001 - nx = 876 - nz = 751 - - img_name = "rtm3d.bin" - seam_image_name = path.join(config.DATASET.ROOT, img_name) - # load RTM image, note it is type np.float32 - img = np.fromfile(seam_image_name, dtype=np.float32).reshape(ny, nx, nz) / 71892250000.0 - - salt_name = "salt_mask.bin" - salt_mask_name = path.join(config.DATASET.ROOT, salt_name) - # load salt mask, note it is type np.int16 - mask = np.fromfile(salt_mask_name, dtype=np.int16).reshape(ny, nx, nz) - - train_set = InlinePatchDataset( - img, - mask, - config.TRAIN.PATCH_SIZE, - config.TRAIN.STRIDE, - split="train", - transforms=train_aug, - n_channels=config.MODEL.IN_CHANNELS, - complete_patches_only=config.TRAIN.COMPLETE_PATCHES_ONLY, - ) - - val_set = InlinePatchDataset( - img, - mask, - config.TRAIN.PATCH_SIZE, - config.TRAIN.STRIDE, - split="val", - transforms=val_aug, - n_channels=config.MODEL.IN_CHANNELS, - complete_patches_only=config.VALIDATION.COMPLETE_PATCHES_ONLY, - ) - logger.info(train_set) - logger.info(val_set) - n_classes = train_set.n_classes - - train_loader = data.DataLoader( - train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True, - ) - - val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,) - - model = getattr(models, config.MODEL.NAME).get_seg_model(config) - - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" - model = model.to(device) # Send to GPU - - optimizer = torch.optim.SGD( - model.parameters(), - lr=config.TRAIN.MAX_LR, - momentum=config.TRAIN.MOMENTUM, - weight_decay=config.TRAIN.WEIGHT_DECAY, - ) - - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) - summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) - snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) - - criterion = torch.nn.CrossEntropyLoss(ignore_index=mask_value, reduction="mean") - - trainer = create_supervised_trainer(model, optimizer, criterion, _prepare_batch, device=device) - - trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - - trainer.add_event_handler( - Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), - ) - trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) - trainer.add_event_handler( - Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), - ) - trainer.add_event_handler( - Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer), - ) - - def _select_pred_and_mask(model_out_dict): - return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) - - evaluator = create_supervised_evaluator( - model, - _prepare_batch, - metrics={ - "pixacc": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask), - "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask), - "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask), - "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask), - "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask), - }, - device=device, - ) - - # Set the validation run to start on the epoch completion of the training run - trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - logging_handlers.log_metrics( - "Validation results", - metrics_dict={ - "nll": "Avg loss :", - "pixacc": "Pixelwise Accuracy :", - "mca": "Avg Class Accuracy :", - "mIoU": "Avg Class IoU :", - }, - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - tensorboard_handlers.log_metrics( - summary_writer, - trainer, - "epoch", - metrics_dict={ - "mIoU": "Validation/mIoU", - "nll": "Validation/Loss", - "mca": "Validation/MCA", - "pixacc": "Validation/Pixel_Acc", - }, - ), - ) - - def _select_max(pred_tensor): - return pred_tensor.max(1)[1] - - def _tensor_to_numpy(pred_tensor): - return pred_tensor.squeeze().cpu().numpy() - - transform_func = compose( - np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy, - ) - - transform_pred = compose(transform_func, _select_max) - - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Validation/Image", "image"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Mask", "mask", transform_func=transform_func), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred), - ) - - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - - checkpoint_handler = SnapshotHandler( - path.join(output_dir, config.TRAIN.MODEL_DIR), - config.MODEL.NAME, - extract_metric_from("mIoU"), - snapshot_function, - ) - evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) - - logger.info("Starting training") - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) - - -if __name__ == "__main__": - fire.Fire(run) diff --git a/experiments/interpretation/demo/local/train.sh b/experiments/interpretation/demo/local/train.sh deleted file mode 100755 index b9bb9338..00000000 --- a/experiments/interpretation/demo/local/train.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -python train.py --cfg "configs/hrnet.yaml" \ No newline at end of file diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py index 94940f21..acdd3d5f 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -1,10 +1,19 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. - -"""Train models on Dutch F3 salt dataset +# +# To Run on 2 GPUs +# python -m torch.distributed.launch --nproc_per_node=2 train.py --cfg "configs/hrnet.yaml" +# +# To Test: +# python -m torch.distributed.launch --nproc_per_node=2 train.py TRAIN.END_EPOCH 1 TRAIN.SNAPSHOTS 1 --cfg "configs/hrnet.yaml" --debug +# +# /* spell-checker: disable */ +"""Train models on Dutch F3 dataset Trains models using PyTorch DistributedDataParallel Uses a warmup schedule that then goes into a cyclic learning rate + +Time to run on two V100s for 300 epochs: 2.5 days """ import logging @@ -63,6 +72,7 @@ from ignite.utils import convert_tensor from toolz import compose, curry from torch.utils import data +from toolz import take def prepare_batch(batch, device=None, non_blocking=False): @@ -78,7 +88,7 @@ def update_sampler_epoch(data_loader, engine): data_loader.sampler.epoch = engine.state.epoch -def run(*options, cfg=None, local_rank=0): +def run(*options, cfg=None, local_rank=0, debug=False): """Run training and validation of model Notes: @@ -239,6 +249,9 @@ def _select_pred_and_mask(model_out_dict): ) # Set the validation run to start on the epoch completion of the training run + if debug: + logger.info("Running Validation in Debug/Test mode") + val_loader = take(3, val_loader) trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) if local_rank == 0: # Run only on master process @@ -248,7 +261,11 @@ def _select_pred_and_mask(model_out_dict): ) trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + try: + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + except TypeError: + output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) logger.info(f"Logging Tensorboard to {path.join(output_dir, config.LOG_DIR)}") trainer.add_event_handler( @@ -314,6 +331,10 @@ def snapshot_function(): logger.info("Starting training") + if debug: + logger.info("Running Training in Debug/Test mode") + train_loader = take(3, train_loader) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml index 7f33b7f7..f58406fb 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml @@ -31,7 +31,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "No" # Options are No, Patch and Section + DEPTH: "none" # Options are None, Patch and Section STRIDE: 50 PATCH_SIZE: 99 AUGMENTATIONS: diff --git a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml index d82231e9..eb89ff00 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml @@ -30,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "no" #"patch" # Options are No, Patch and Section + DEPTH: "none" #"patch" # Options are None, Patch and Section STRIDE: 50 PATCH_SIZE: 99 AUGMENTATIONS: diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index 6e34e54e..fcc263d9 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -2,10 +2,16 @@ # Licensed under the MIT License. # commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 # url: https://github.com/olivesgatech/facies_classification_benchmark - +# +# To Test: +# python test.py TRAIN.END_EPOCH 1 TRAIN.SNAPSHOTS 1 --cfg "configs/hrnet.yaml" --debug +# +# /* spell-checker: disable */ """ Modified version of the Alaudah testing script -#TODO: Needs to be improved. Needs to be able to run across multiple GPUs and better factoring around the loader +Runs only on single GPU + +Estimated time to run on single V100: 5 hours """ import itertools @@ -31,6 +37,8 @@ from default import update_config from toolz import compose, curry, itertoolz, pipe from torch.utils import data +from toolz import take + _CLASS_NAMES = [ "upper_ns", @@ -245,7 +253,7 @@ def to_image(label_mask, n_classes=6): def _evaluate_split( - split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, + split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, debug=False ): logger = logging.getLogger(__name__) @@ -255,6 +263,11 @@ def _evaluate_split( n_classes = test_set.n_classes test_loader = data.DataLoader(test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False) + + if debug: + logger.info("Running in Debug/Test mode") + test_loader = take(1, test_loader) + running_metrics_split = runningScore(n_classes) # testing mode: @@ -319,7 +332,7 @@ def _write_section_file(labels, section_file): file_object.close() -def test(*options, cfg=None): +def test(*options, cfg=None, debug=False): update_config(config, options=options, config_file=cfg) n_classes = config.DATASET.NUM_CLASSES @@ -359,11 +372,11 @@ def test(*options, cfg=None): splits = ["test1", "test2"] if "Both" in config.TEST.SPLIT else [config.TEST.SPLIT] for sdx, split in enumerate(splits): - labels = np.load(path.join(config.DATASEST.ROOT, "test_once", split + "_labels.npy")) - section_file = path.join(config.DATASEST.ROOT, "splits", "section_" + split + ".txt") + labels = np.load(path.join(config.DATASET.ROOT, "test_once", split + "_labels.npy")) + section_file = path.join(config.DATASET.ROOT, "splits", "section_" + split + ".txt") _write_section_file(labels, section_file) _evaluate_split( - split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, + split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, debug=debug ) # FINAL TEST RESULTS: diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index ebc128f1..bdb5e723 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -1,6 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +# +# To Test: +# python train.py TRAIN.END_EPOCH 1 TRAIN.SNAPSHOTS 1 --cfg "configs/hrnet.yaml" --debug +# # /* spell-checker: disable */ +"""Train models on Dutch F3 dataset + +Trains models using PyTorch +Uses a warmup schedule that then goes into a cyclic learning rate + +Time to run on single V100 for 300 epochs: 4.5 days +""" import logging import logging.config @@ -54,6 +65,7 @@ from default import _C as config from default import update_config +from toolz import take def prepare_batch(batch, device=None, non_blocking=False): @@ -64,7 +76,7 @@ def prepare_batch(batch, device=None, non_blocking=False): ) -def run(*options, cfg=None): +def run(*options, cfg=None, debug=False): """Run training and validation of model Notes: @@ -77,10 +89,13 @@ def run(*options, cfg=None): config. To see what options are available consult default.py cfg (str, optional): Location of config file to load. Defaults to None. + debug (bool): Places scripts in debug/test mode and only executes a few iterations """ update_config(config, options=options, config_file=cfg) + + # Start logging load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) @@ -156,7 +171,10 @@ def run(*options, cfg=None): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + try: + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + except TypeError: + output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) @@ -202,6 +220,10 @@ def _select_pred_and_mask(model_out_dict): ) # Set the validation run to start on the epoch completion of the training run + if debug: + logger.info("Running Validation in Debug/Test mode") + val_loader = take(3, val_loader) + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) evaluator.add_event_handler( @@ -266,6 +288,10 @@ def snapshot_function(): evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") + if debug: + logger.info("Running Training in Debug/Test mode") + train_loader = take(3, train_loader) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/experiments/interpretation/dutchf3_section/local/test.py b/experiments/interpretation/dutchf3_section/local/test.py index e4585f83..4f0eda4a 100644 --- a/experiments/interpretation/dutchf3_section/local/test.py +++ b/experiments/interpretation/dutchf3_section/local/test.py @@ -25,6 +25,8 @@ from default import _C as config from default import update_config from torch.utils import data +from toolz import take + _CLASS_NAMES = [ "upper_ns", @@ -86,7 +88,7 @@ def reset(self): def _evaluate_split( - split, section_aug, model, device, running_metrics_overall, config, + split, section_aug, model, device, running_metrics_overall, config, debug=False ): logger = logging.getLogger(__name__) @@ -98,6 +100,10 @@ def _evaluate_split( n_classes = test_set.n_classes test_loader = data.DataLoader(test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False) + if debug: + logger.info("Running in Debug/Test mode") + test_loader = take(1, test_loader) + running_metrics_split = runningScore(n_classes) # testing mode: @@ -152,7 +158,7 @@ def _write_section_file(labels, section_file): file_object.close() -def test(*options, cfg=None): +def test(*options, cfg=None, debug=False): update_config(config, options=options, config_file=cfg) n_classes = config.DATASET.NUM_CLASSES @@ -178,7 +184,7 @@ def test(*options, cfg=None): labels = np.load(path.join(config.DATASET.ROOT, "test_once", split + "_labels.npy")) section_file = path.join(config.DATASET.ROOT, "splits", "section_" + split + ".txt") _write_section_file(labels, section_file) - _evaluate_split(split, section_aug, model, device, running_metrics_overall, config) + _evaluate_split(split, section_aug, model, device, running_metrics_overall, config, debug=debug) # FINAL TEST RESULTS: score, class_iou = running_metrics_overall.get_scores() diff --git a/experiments/interpretation/dutchf3_section/local/train.py b/experiments/interpretation/dutchf3_section/local/train.py index 99bf4279..69b5f7d4 100644 --- a/experiments/interpretation/dutchf3_section/local/train.py +++ b/experiments/interpretation/dutchf3_section/local/train.py @@ -50,6 +50,7 @@ from ignite.metrics import Loss from toolz import compose from torch.utils import data +from toolz import take def prepare_batch(batch, device="cuda", non_blocking=False): @@ -60,7 +61,7 @@ def prepare_batch(batch, device="cuda", non_blocking=False): ) -def run(*options, cfg=None): +def run(*options, cfg=None, debug=False): """Run training and validation of model Notes: @@ -149,7 +150,10 @@ def __len__(self): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + try: + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + except TypeError: + output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) @@ -197,7 +201,9 @@ def _select_pred_and_mask(model_out_dict): device=device, ) - # Set the validation run to start on the epoch completion of the training run + if debug: + logger.info("Running Validation in Debug/Test mode") + val_loader = take(3, val_loader) trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) evaluator.add_event_handler( @@ -272,6 +278,9 @@ def snapshot_function(): evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") + if debug: + logger.info("Running Validation in Debug/Test mode") + train_loader = take(3, train_loader) trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/experiments/interpretation/penobscot/local/test.py b/experiments/interpretation/penobscot/local/test.py index 7b244296..ec902ca5 100644 --- a/experiments/interpretation/penobscot/local/test.py +++ b/experiments/interpretation/penobscot/local/test.py @@ -1,6 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +# +# To Test: +# python test.py TRAIN.END_EPOCH 1 TRAIN.SNAPSHOTS 1 --cfg "configs/hrnet.yaml" --debug +# # /* spell-checker: disable */ +"""Train models on Penobscot dataset + +Test models using PyTorch + +Time to run on single V100: 30 minutes +""" + import logging import logging.config @@ -93,7 +104,7 @@ def _log_tensor_to_tensorboard(images_tensor, identifier, summary_writer, evalua mask_value = 255 -def run(*options, cfg=None): +def run(*options, cfg=None, debug=False): """Run testing of model Notes: @@ -175,7 +186,12 @@ def run(*options, cfg=None): device = "cuda" model = model.to(device) # Send to GPU - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + + try: + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + except TypeError: + output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) # weights are inversely proportional to the frequency of the classes in @@ -267,6 +283,10 @@ def _tensor_to_numpy(pred_tensor): ) logger.info("Starting training") + if debug: + logger.info("Running in Debug/Test mode") + test_loader = take(3, test_loader) + evaluator.run(test_loader, max_epochs=1) # Log top N and bottom N inlines in terms of IoU to tensorboard diff --git a/experiments/interpretation/penobscot/local/train.py b/experiments/interpretation/penobscot/local/train.py index 9f7c57c5..dfdfad7f 100644 --- a/experiments/interpretation/penobscot/local/train.py +++ b/experiments/interpretation/penobscot/local/train.py @@ -1,6 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +# +# To Test: +# python train.py TRAIN.END_EPOCH 1 TRAIN.SNAPSHOTS 1 --cfg "configs/hrnet.yaml" --debug +# # /* spell-checker: disable */ +"""Train models on Penobscot dataset + +Trains models using PyTorch +Uses a warmup schedule that then goes into a cyclic learning rate + +Time to run on single V100 for 300 epochs: 3.5 days +""" import logging import logging.config @@ -53,6 +64,8 @@ from default import _C as config from default import update_config +from toolz import take + mask_value = 255 _SEG_COLOURS = np.asarray( @@ -70,7 +83,7 @@ def _prepare_batch(batch, device=None, non_blocking=False): ) -def run(*options, cfg=None): +def run(*options, cfg=None, debug=False): """Run training and validation of model Notes: @@ -83,6 +96,7 @@ def run(*options, cfg=None): config. To see what options are available consult default.py cfg (str, optional): Location of config file to load. Defaults to None. + debug (bool): Places scripts in debug/test mode and only executes a few iterations """ update_config(config, options=options, config_file=cfg) @@ -176,7 +190,11 @@ def run(*options, cfg=None): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + try: + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + except TypeError: + output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) snapshot_duration = scheduler_step * len(train_loader) scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) @@ -220,6 +238,9 @@ def _select_pred_and_mask(model_out_dict): ) # Set the validation run to start on the epoch completion of the training run + if debug: + logger.info("Running Validation in Debug/Test mode") + val_loader = take(3, val_loader) trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) evaluator.add_event_handler( @@ -285,6 +306,9 @@ def snapshot_function(): evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") + if debug: + logger.info("Running Training in Debug/Test mode") + train_loader = take(3, train_loader) trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 1573f462..67301f03 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -16,10 +16,8 @@ jobs: - job: setup timeoutInMinutes: 10 displayName: Setup - pool: name: deepseismicagentpool - steps: - bash: | echo "Running setup..." @@ -35,217 +33,193 @@ jobs: - job: unit_tests_job dependsOn: setup - timeoutInMinutes: 20 + timeoutInMinutes: 5 + displayName: Unit Tests Job pool: name: deepseismicagentpool steps: - bash: | echo "Starting unit tests" - conda activate seismic-interpretation + source activate seismic-interpretation pytest --durations=0 cv_lib/tests/ echo "Unit test job passed" - displayName: Unit Tests Job + ################################################################################################### # LOCAL PATCH JOBS ################################################################################################### -- job: hrnet_patch +- job: hrnet_penobscot dependsOn: setup timeoutInMinutes: 5 - displayName: hrnet patch + displayName: hrnet penobscot pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation + conda env list + source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + cd experiments/interpretation/penobscot/local + python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug # find the latest model which we just trained - model=$(ls -td */*/*/hrnet/* | head -1) - # try running the test script - python test.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/hrnet_running_model_1.pth + model=$(ls -td */seg_hrnet/*/* | head -1) + echo ${model} + # # try running the test script + python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug -- job: unet_patch + +- job: seresnet_unet_penobscot dependsOn: setup timeoutInMinutes: 5 - displayName: unet patch + displayName: seresnet_unet penobscot pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation + conda env list + source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py --cfg=configs/unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + cd experiments/interpretation/penobscot/local + python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug # find the latest model which we just trained - model=$(ls -td */*/*/unet/* | head -1) + model=$(ls -td */resnet_unet/*/* | head -1) + echo ${model} # try running the test script - python test.py --cfg=configs/unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/unet_running_model_1.pth + python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug -- job: seresnet_unet_patch +- job: hrnet_dutchf3 dependsOn: setup timeoutInMinutes: 5 - displayName: seresnet unet patch + displayName: hrnet dutchf3 pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation + source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/local - python train.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug # find the latest model which we just trained - model=$(ls -td */*/*/seresnet_unet/* | head -1) + model=$(ls -td */seg_hrnet/*/* | head -1) + echo ${model} # try running the test script - python test.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/seresnet_unet_running_model_1.pth + python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug + -- job: patch_deconvnet +- job: unet_dutchf3 dependsOn: setup timeoutInMinutes: 5 - displayName: patch deconvnet + displayName: unet dutchf3 pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation + source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/local - python train.py --cfg=configs/patch_deconvnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug # find the latest model which we just trained - model=$(ls -td */*/*/patch_deconvnet/* | head -1) + model=$(ls -td */resnet_unet/*/* | head -1) + echo ${model} # try running the test script - python test.py --cfg=configs/patch_deconvnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/patch_deconvnet_running_model_1.pth + python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/unet.yaml --debug -- job: patch_deconvnet_skip +- job: seresnet_unet_dutchf3 dependsOn: setup timeoutInMinutes: 5 - displayName: patch deconvnet skip + displayName: seresnet unet dutchf3 pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation + source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/local - python train.py --cfg=configs/patch_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug # find the latest model which we just trained - model=$(ls -td */*/*/patch_deconvnet_skip/* | head -1) + model=$(ls -td */resnet_unet/*/* | head -1) # try running the test script - python test.py --cfg=configs/patch_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/patch_deconvnet_skip_running_model_1.pth - -# Penobscot dataset + python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug -- job: seresnet_unet +- job: patch_deconvnet_dutchf3 dependsOn: setup timeoutInMinutes: 5 - displayName: seresnet unet + displayName: patch deconvnet dutchf3 pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation + source activate seismic-interpretation # run the tests - cd experiments/interpretation/penobscot/local - python train.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + cd experiments/interpretation/dutchf3_patch/local + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.BATCH_SIZE_PER_GPU' 1 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet.yaml --debug # find the latest model which we just trained - model=$(ls -td */*/*/seresnet_unet/* | head -1) + model=$(ls -td */patch_deconvnet/*/* | head -1) # try running the test script - python test.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/models/seresnet_unet_running_model_1.pth + python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_1.pth --cfg=configs/patch_deconvnet.yaml --debug -- job: hrnet_penobscot +- job: patch_deconvnet_skip_dutchf3 dependsOn: setup timeoutInMinutes: 5 - displayName: hrnet penobscot + displayName: patch deconvnet skip dutchf3 pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation + source activate seismic-interpretation # run the tests - cd experiments/interpretation/penobscot/local - python train.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + cd experiments/interpretation/dutchf3_patch/local + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.BATCH_SIZE_PER_GPU' 1 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet_skip.yaml --debug # find the latest model which we just trained - model=$(ls -td */*/*/hrnet/* | head -1) + model=$(ls -td */patch_deconvnet_skip/*/* | head -1) # try running the test script - python test.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/models/hrnet_running_model_1.pth + python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_skip_running_model_1.pth --cfg=configs/patch_deconvnet_skip.yaml --debug + ################################################################################################### # DISTRIBUTED PATCH JOBS ################################################################################################### -- job: hrnet_patch_dist +- job: hrnet_dutchf3_dist dependsOn: setup timeoutInMinutes: 5 - displayName: hrnet patch distributed + displayName: hrnet dutchf3 distributed pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation - export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" + source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/hrnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug -- job: unet_patch_dist +- job: unet_dutchf3_dist dependsOn: setup timeoutInMinutes: 5 - displayName: unet patch distributed + displayName: unet dutchf3 distributed pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation - export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" + source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 - -- job: seresnet_unet_patch_dist - dependsOn: setup - timeoutInMinutes: 5 - displayName: seresnet unet patch distributed - pool: - name: deepseismicagentpool - steps: - - bash: | - conda activate seismic-interpretation - export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/seresnet_unet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug -- job: patch_deconvnet_dist - dependsOn: setup - timeoutInMinutes: 5 - displayName: patch deconvnet distributed - pool: - name: deepseismicagentpool - steps: - - bash: | - conda activate seismic-interpretation - export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/patch_deconvnet.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 - -- job: patch_deconvnet_skip_dist +- job: seresnet_unet_dutchf3_dist dependsOn: setup timeoutInMinutes: 5 - displayName: patch deconvnet skip distributed + displayName: seresnet unet dutchf3 distributed pool: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation - export PYTHONPATH="$PWD/interpretation:$PYTHONPATH" + source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py --cfg=configs/patch_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 - - + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug + ################################################################################################### # LOCAL SECTION JOBS ################################################################################################### @@ -258,12 +232,13 @@ jobs: name: deepseismicagentpool steps: - bash: | - conda activate seismic-interpretation + source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_section/local - python train.py --cfg=configs/section_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/section_deconvnet_skip.yaml --debug # find the latest model which we just trained - model=$(ls -td */*/*/section_deconvnet_skip/* | head -1) + model=$(ls -td */section_deconvnet_skip/*/* | head -1) + echo ${model} # try running the test script - python test.py --cfg=configs/section_deconvnet_skip.yaml 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/models/section_deconvnet_skip_running_model_1.pth + python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/section_deconvnet_skip_running_model_1.pth --cfg=configs/section_deconvnet_skip.yaml --debug diff --git a/tests/cicd/penobscot.yml b/tests/cicd/penobscot.yml new file mode 100644 index 00000000..3d7ae4e3 --- /dev/null +++ b/tests/cicd/penobscot.yml @@ -0,0 +1,179 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Pull request against these branches will trigger this build +pr: +- master +- staging + +# Any commit to this branch will trigger the build. +trigger: +- master +- staging + +jobs: + +# ################################################################################################### +# # LOCAL PATCH JOBS +# ################################################################################################### + +# - job: hrnet_penobscot +# timeoutInMinutes: 5 +# displayName: hrnet penobscot +# pool: +# name: deepseismicagentpool +# steps: +# - bash: | +# conda env list +# source activate seismic-interpretation +# # run the tests +# cd experiments/interpretation/penobscot/local +# python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug +# # find the latest model which we just trained +# model=$(ls -td */seg_hrnet/*/* | head -1) +# echo ${model} +# # # try running the test script +# python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug + +# - job: seresnet_unet_penobscot +# timeoutInMinutes: 5 +# displayName: seresnet_unet penobscot +# pool: +# name: deepseismicagentpool +# steps: +# - bash: | +# conda env list +# source activate seismic-interpretation +# # run the tests +# cd experiments/interpretation/penobscot/local +# python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug +# # find the latest model which we just trained +# model=$(ls -td */resnet_unet/*/* | head -1) +# echo ${model} +# # try running the test script +# python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug + +# - job: hrnet_dutchf3 +# timeoutInMinutes: 5 +# displayName: hrnet dutchf3 +# pool: +# name: deepseismicagentpool +# steps: +# - bash: | +# source activate seismic-interpretation +# # run the tests +# cd experiments/interpretation/dutchf3_patch/local +# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug +# # find the latest model which we just trained +# model=$(ls -td */seg_hrnet/*/* | head -1) +# echo ${model} +# # try running the test script +# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug + +# - job: unet_dutchf3 +# timeoutInMinutes: 5 +# displayName: unet dutchf3 +# pool: +# name: deepseismicagentpool +# steps: +# - bash: | +# source activate seismic-interpretation +# # run the tests +# cd experiments/interpretation/dutchf3_patch/local +# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug +# # find the latest model which we just trained +# model=$(ls -td */resnet_unet/*/* | head -1) +# echo ${model} +# # try running the test script +# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/unet.yaml --debug + +# - job: seresnet_unet_dutchf3 +# timeoutInMinutes: 5 +# displayName: seresnet unet dutchf3 +# pool: +# name: deepseismicagentpool +# steps: +# - bash: | +# source activate seismic-interpretation +# # run the tests +# cd experiments/interpretation/dutchf3_patch/local +# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug +# # find the latest model which we just trained +# model=$(ls -td */resnet_unet/*/* | head -1) +# # try running the test script +# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug + +# - job: patch_deconvnet_dutchf3 +# timeoutInMinutes: 5 +# displayName: patch deconvnet dutchf3 +# pool: +# name: deepseismicagentpool +# steps: +# - bash: | +# source activate seismic-interpretation +# # run the tests +# cd experiments/interpretation/dutchf3_patch/local +# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.BATCH_SIZE_PER_GPU' 1 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet.yaml --debug +# # find the latest model which we just trained +# model=$(ls -td */patch_deconvnet/*/* | head -1) +# # try running the test script +# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_1.pth --cfg=configs/patch_deconvnet.yaml --debug + +# - job: patch_deconvnet_skip_dutchf3 +# timeoutInMinutes: 5 +# displayName: patch deconvnet skip dutchf3 +# pool: +# name: deepseismicagentpool +# steps: +# - bash: | +# source activate seismic-interpretation +# # run the tests +# cd experiments/interpretation/dutchf3_patch/local +# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.BATCH_SIZE_PER_GPU' 1 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet_skip.yaml --debug +# # find the latest model which we just trained +# model=$(ls -td */patch_deconvnet_skip/*/* | head -1) +# # try running the test script +# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_skip_running_model_1.pth --cfg=configs/patch_deconvnet_skip.yaml --debug + + +# ################################################################################################### +# # DISTRIBUTED PATCH JOBS +# ################################################################################################### + +# - job: hrnet_dutchf3_dist +# timeoutInMinutes: 5 +# displayName: hrnet dutchf3 distributed +# pool: +# name: deepseismicagentpool +# steps: +# - bash: | +# source activate seismic-interpretation +# # run the tests +# cd experiments/interpretation/dutchf3_patch/distributed +# python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug + + +- job: unet_dutchf3_dist + timeoutInMinutes: 5 + displayName: unet dutchf3 distributed + pool: + name: deepseismicagentpool + steps: + - bash: | + source activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug + +- job: seresnet_unet_dutchf3_dist + timeoutInMinutes: 5 + displayName: seresnet unet dutchf3 distributed + pool: + name: deepseismicagentpool + steps: + - bash: | + source activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug + \ No newline at end of file From 0d90612991316cd5cdfc376b70152ef85504e764 Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 10 Dec 2019 08:12:36 -0800 Subject: [PATCH 133/207] Adds missing tests (#111) * added missing tests * Adding fixes for test * reinstating all tests --- .../distributed/configs/patch_deconvnet.yaml | 14 ++++---- .../configs/patch_deconvnet_skip.yaml | 31 +++++++++++++++-- tests/cicd/main_build.yml | 34 ++++++++++++++++--- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml index 7f33b7f7..eb89ff00 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml +++ b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml @@ -3,21 +3,20 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' +OUTPUT_DIR: 'output' LOG_DIR: 'log' WORKERS: 4 PRINT_FREQ: 10 LOG_CONFIG: logging.conf SEED: 2019 - DATASET: NUM_CLASSES: 6 ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: - NAME: patch_deconvnet + NAME: patch_deconvnet_skip IN_CHANNELS: 1 @@ -31,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "No" # Options are No, Patch and Section + DEPTH: "none" #"patch" # Options are None, Patch and Section STRIDE: 50 PATCH_SIZE: 99 AUGMENTATIONS: @@ -48,12 +47,13 @@ TRAIN: VALIDATION: BATCH_SIZE_PER_GPU: 512 -TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/5cc37bbe5302e1989ef1388d629400a16f82d1a9/patch_deconvnet/Aug27_200339/models/patch_deconvnet_snapshot1model_50.pth" +TEST: + MODEL_PATH: "" TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True CROSSLINE: True POST_PROCESSING: - SIZE: 99 + SIZE: 99 # CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right + diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml index c6c06481..eb89ff00 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml +++ b/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml @@ -13,7 +13,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 ROOT: /mnt/dutchf3 - DEPTH: 'no' + CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: NAME: patch_deconvnet_skip @@ -29,6 +29,31 @@ TRAIN: MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 + AUGMENTATION: True + DEPTH: "none" #"patch" # Options are None, Patch and Section + STRIDE: 50 + PATCH_SIZE: 99 + AUGMENTATIONS: + RESIZE: + HEIGHT: 99 + WIDTH: 99 + PAD: + HEIGHT: 99 + WIDTH: 99 + MEAN: 0.0009997 # 0.0009996710808862074 + STD: 0.20977 # 0.20976548783479299 + MODEL_DIR: "models" + +VALIDATION: + BATCH_SIZE_PER_GPU: 512 + +TEST: + MODEL_PATH: "" + TEST_STRIDE: 10 + SPLIT: 'Both' # Can be Both, Test1, Test2 + INLINE: True + CROSSLINE: True + POST_PROCESSING: + SIZE: 99 # + CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right -TEST: - BATCH_SIZE_PER_GPU: 128 diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 67301f03..017137b4 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -194,10 +194,10 @@ jobs: cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug -- job: unet_dutchf3_dist +- job: patch_deconvnet_skip_dist dependsOn: setup timeoutInMinutes: 5 - displayName: unet dutchf3 distributed + displayName: patch deconvnet skip distributed pool: name: deepseismicagentpool steps: @@ -205,8 +205,21 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug - + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'TRAIN.BATCH_SIZE_PER_GPU' 1 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet_skip.yaml --debug + +- job: patch_deconvnet_dist + dependsOn: setup + timeoutInMinutes: 5 + displayName: patch deconvnet distributed + pool: + name: deepseismicagentpool + steps: + - bash: | + source activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'TRAIN.BATCH_SIZE_PER_GPU' 1 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet.yaml --debug + - job: seresnet_unet_dutchf3_dist dependsOn: setup timeoutInMinutes: 5 @@ -219,6 +232,19 @@ jobs: # run the tests cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug + +- job: unet_dutchf3_dist + dependsOn: setup + timeoutInMinutes: 5 + displayName: unet dutchf3 distributed + pool: + name: deepseismicagentpool + steps: + - bash: | + source activate seismic-interpretation + # run the tests + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug ################################################################################################### # LOCAL SECTION JOBS From 9bbcc0bbb5826fa2b2a9e9d03d75c63aa558ed23 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 10 Dec 2019 15:07:40 -0500 Subject: [PATCH 134/207] Maxkaz/issues (#110) * added README documentation per bug bush feedback * added missing tests * closing out multiple post bug bash issues with single PR * Addressed comments * minor change --- README.md | 37 ++++++++++--------- .../scripts}/download_hrnet.sh | 0 .../dutchf3_patch/distributed/train.py | 6 ++- .../dutchf3_patch/local/test.py | 10 ++++- .../dutchf3_patch/local/train.py | 2 - .../dutchf3_section/local/test.py | 4 +- .../interpretation/penobscot/local/test.py | 3 +- .../interpretation/penobscot/local/train.py | 2 +- .../azureml_tools/__init__.py | 1 - .../azureml_tools/experiment.py | 1 - .../azureml_tools/storage.py | 3 +- .../azureml_tools/workspace.py | 10 +++-- scripts/autoformat.sh | 6 +++ tests/cicd/src/conftest.py | 6 ++- 14 files changed, 54 insertions(+), 37 deletions(-) rename {scripts => contrib/scripts}/download_hrnet.sh (100%) create mode 100755 scripts/autoformat.sh diff --git a/README.md b/README.md index 4949afbb..6c5cb30d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ DeepSeismic currently focuses on Seismic Interpretation (3D segmentation aka fac ### Quick Start There are two ways to get started with the DeepSeismic codebase, which currently focuses on Interpretation: -- if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/staging/examples/interpretation/notebooks/HRNet_demo_notebook.ipynb) +- if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/staging/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) - to actually run the code, you'll need to set up a compute environment (which includes setting up a GPU-enabled Linux VM and downloading the appropriate Anaconda Python packages) and download the datasets which you'd like to work with - detailed steps for doing this are provided in the next `Interpretation` section below. If you run into any problems, chances are your problem has already been solved in the [Troubleshooting](#troubleshooting) section. @@ -30,19 +30,17 @@ To run examples available on the repo, please follow instructions below to: Follow the instruction bellow to read about compute requirements and install required libraries. -
- Compute environment -We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on Linux only. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DSVM)](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/). This VM will come installed with all the system requirements that are needed to run the notebooks in this repository. +#### Compute environment + +We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on Linux only. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DS VM)](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/). This VM will come installed with all the system requirements that are needed to run the notebooks in this repository. For this repo, we recommend selecting an Ubuntu VM of type [Standard_NC6_v3](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series). The machine is powered by NVIDIA Tesla V100 GPU which can be found in most Azure regions. > NOTE: For users new to Azure, your subscription may not come with a quota for GPUs. You may need to go into the Azure portal to increase your quota for GPU VMs. Learn more about how to do this here: https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits. -
-
- Package Installation +#### Package Installation To install packages contained in this repository, navigate to the directory where you pulled the DeepSeismic repo to run: ```bash @@ -69,11 +67,16 @@ conda env update --file environment/anaconda/local/environment.yml ``` from the root of DeepSeismic repo. -
### Dataset download and preparation -This repository provides examples on how to run seismic interpretation on two publicly available annotated seismic datasets: [Penobscot](https://zenodo.org/record/1341774) and [F3 Netherlands](https://github.com/olivesgatech/facies_classification_benchmark). +This repository provides examples on how to run seismic interpretation on two publicly available annotated seismic datasets: [Penobscot](https://zenodo.org/record/1341774) and [F3 Netherlands](https://github.com/olivesgatech/facies_classification_benchmark). Their respective sizes (uncompressed on disk in your folder after downloading and pre-processing) are: +- **Penobscot**: 7.9 GB +- **Dutch F3**: 2.2 GB + +Please make sure you have enough disk space to download either dataset. + +We have experiments and notebooks which use either one dataset or the other. Depending on which experiment/notebook you want to run you'll need to download the corresponding dataset. We suggest you start by looking at [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/staging/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) which requires the Penobscot dataset. #### Penobscot To download the Penobscot dataset run the [download_penobscot.sh](scripts/download_penobscot.sh) script, e.g. @@ -91,7 +94,7 @@ To make things easier, we suggested you use your home directory where you might To prepare the data for the experiments (e.g. split into train/val/test), please run the following script (modifying arguments as desired): ``` -python scripts/prepare_penobscot.py split_inline --data-dir=/data/penobscot --val-ratio=.1 --test-ratio=.2 +python scripts/prepare_penobscot.py split_inline --data-dir="$HOME/data/penobscot" --val-ratio=.1 --test-ratio=.2 ``` #### F3 Netherlands @@ -173,12 +176,12 @@ We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manag #### HRNet -To achieve the same results as the benchmarks above you will need to download the HRNet model [pretrained](https://github.com/HRNet/HRNet-Image-Classification) on ImageNet. We are specifically using the [HRNet-W48-C](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk) pre-trained model - download this model to your local drive and make sure you add the path to the experiment (or notebook) configuration file under `TEST.MODEL_PATH` setting. Other HRNet variants are also available [here](https://github.com/HRNet/HRNet-Image-Classification) - you can navigate to those from the [main HRNet landing page](https://github.com/HRNet/HRNet-Object-Detection) for object detection. +To achieve the same results as the benchmarks above you will need to download the HRNet model [pretrained](https://github.com/HRNet/HRNet-Image-Classification) on ImageNet. We are specifically using the [HRNet-W48-C](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk) pre-trained model; other HRNet variants are also available [here](https://github.com/HRNet/HRNet-Image-Classification) - you can navigate to those from the [main HRNet landing page](https://github.com/HRNet/HRNet-Object-Detection) for object detection. + +Unfortunately the OneDrive location which is used to host the model is using a temporary authentication token, so there is no way for us to scipt up model download. There are two ways to upload and use the pre-trained HRNet model on DS VM: +- download the model to your local drive using a web browser of your choice and then upload the model to the DS VM using something like `scp`; navigate to Portal and copy DS VM's public IP from the Overview panel of your DS VM (you can search your DS VM by name in the search bar of the Portal) then use `scp local_model_location username@DS_VM_public_IP:./model/save/path` to upload +- alternatively you can use the same public IP to open remote desktop over SSH to your Linux VM using [X2Go](https://wiki.x2go.org/doku.php/download:start): you can basically open the web browser on your VM this way and download the model to VM's disk -To facilitate easier download on a Linux machine of your choice (or [Azure Data Science Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/) which we recommend), we created an automated download scipt for you, just run -```bash -./scripts/download_hrnet.sh 'your_folder_to_store_the_model' 'model_file' -``` ### Viewers (optional) @@ -198,7 +201,7 @@ pip install segyviewer To visualize cross-sections of a 3D volume, you can run [segyviewer](https://github.com/equinor/segyviewer) like so: ```bash -segyviewer /mnt/dutchf3/data.segy +segyviewer "${HOME}/data/dutchf3/data.segy" ``` ### Benchmarks @@ -297,7 +300,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope To test whether this setup worked, right after you can open `ipython` and execute the following code ```python - import torch + import torch torch.cuda.is_available() ``` diff --git a/scripts/download_hrnet.sh b/contrib/scripts/download_hrnet.sh similarity index 100% rename from scripts/download_hrnet.sh rename to contrib/scripts/download_hrnet.sh diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py index acdd3d5f..3f19c106 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -262,10 +262,12 @@ def _select_pred_and_mask(model_out_dict): trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) try: - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + output_dir = generate_path( + config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(), + ) except TypeError: output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) - + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) logger.info(f"Logging Tensorboard to {path.join(output_dir, config.LOG_DIR)}") trainer.add_event_handler( diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index fcc263d9..60a9ca00 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -376,7 +376,15 @@ def test(*options, cfg=None, debug=False): section_file = path.join(config.DATASET.ROOT, "splits", "section_" + split + ".txt") _write_section_file(labels, section_file) _evaluate_split( - split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, debug=debug + split, + section_aug, + model, + pre_processing, + output_processing, + device, + running_metrics_overall, + config, + debug=debug, ) # FINAL TEST RESULTS: diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index bdb5e723..fd8324c9 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -94,8 +94,6 @@ def run(*options, cfg=None, debug=False): update_config(config, options=options, config_file=cfg) - - # Start logging load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) diff --git a/experiments/interpretation/dutchf3_section/local/test.py b/experiments/interpretation/dutchf3_section/local/test.py index 4f0eda4a..5b4d6858 100644 --- a/experiments/interpretation/dutchf3_section/local/test.py +++ b/experiments/interpretation/dutchf3_section/local/test.py @@ -87,9 +87,7 @@ def reset(self): self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) -def _evaluate_split( - split, section_aug, model, device, running_metrics_overall, config, debug=False -): +def _evaluate_split(split, section_aug, model, device, running_metrics_overall, config, debug=False): logger = logging.getLogger(__name__) TestSectionLoader = get_test_loader(config) diff --git a/experiments/interpretation/penobscot/local/test.py b/experiments/interpretation/penobscot/local/test.py index ec902ca5..b8928c1e 100644 --- a/experiments/interpretation/penobscot/local/test.py +++ b/experiments/interpretation/penobscot/local/test.py @@ -186,12 +186,11 @@ def run(*options, cfg=None, debug=False): device = "cuda" model = model.to(device) # Send to GPU - try: output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) except TypeError: output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) - + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) # weights are inversely proportional to the frequency of the classes in diff --git a/experiments/interpretation/penobscot/local/train.py b/experiments/interpretation/penobscot/local/train.py index dfdfad7f..6b86956d 100644 --- a/experiments/interpretation/penobscot/local/train.py +++ b/experiments/interpretation/penobscot/local/train.py @@ -194,7 +194,7 @@ def run(*options, cfg=None, debug=False): output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) except TypeError: output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) - + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) snapshot_duration = scheduler_step * len(train_loader) scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) diff --git a/interpretation/deepseismic_interpretation/azureml_tools/__init__.py b/interpretation/deepseismic_interpretation/azureml_tools/__init__.py index 03c68613..962a4ec2 100644 --- a/interpretation/deepseismic_interpretation/azureml_tools/__init__.py +++ b/interpretation/deepseismic_interpretation/azureml_tools/__init__.py @@ -3,4 +3,3 @@ from deepseismic_interpretation.azureml_tools.workspace import workspace_for_user from deepseismic_interpretation.azureml_tools.experiment import PyTorchExperiment - diff --git a/interpretation/deepseismic_interpretation/azureml_tools/experiment.py b/interpretation/deepseismic_interpretation/azureml_tools/experiment.py index 8353073b..e2823c27 100644 --- a/interpretation/deepseismic_interpretation/azureml_tools/experiment.py +++ b/interpretation/deepseismic_interpretation/azureml_tools/experiment.py @@ -281,4 +281,3 @@ def submit( self._logger.debug(estimator.conda_dependencies.__dict__) return self._experiment.submit(estimator) - diff --git a/interpretation/deepseismic_interpretation/azureml_tools/storage.py b/interpretation/deepseismic_interpretation/azureml_tools/storage.py index b84fd166..1e4b9844 100644 --- a/interpretation/deepseismic_interpretation/azureml_tools/storage.py +++ b/interpretation/deepseismic_interpretation/azureml_tools/storage.py @@ -6,8 +6,7 @@ from azure.mgmt.storage.models import StorageAccountCreateParameters from azure.mgmt.storage.v2019_04_01.models import Kind, Sku, SkuName -from deepseismic_interpretation.azureml_tools.resource_group import \ - create_resource_group +from deepseismic_interpretation.azureml_tools.resource_group import create_resource_group class StorageAccountCreateFailure(Exception): diff --git a/interpretation/deepseismic_interpretation/azureml_tools/workspace.py b/interpretation/deepseismic_interpretation/azureml_tools/workspace.py index b5331bce..485a7874 100644 --- a/interpretation/deepseismic_interpretation/azureml_tools/workspace.py +++ b/interpretation/deepseismic_interpretation/azureml_tools/workspace.py @@ -7,10 +7,12 @@ from pathlib import Path import azureml -from azureml.core.authentication import (AuthenticationException, - AzureCliAuthentication, - InteractiveLoginAuthentication, - ServicePrincipalAuthentication) +from azureml.core.authentication import ( + AuthenticationException, + AzureCliAuthentication, + InteractiveLoginAuthentication, + ServicePrincipalAuthentication, +) _DEFAULT_AML_PATH = "aml_config/azml_config.json" diff --git a/scripts/autoformat.sh b/scripts/autoformat.sh new file mode 100755 index 00000000..574a2a44 --- /dev/null +++ b/scripts/autoformat.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# autoformats all files in the repo to black + +# example of using regex -regex ".*\.\(py\|ipynb\|md\|txt\)" +find . -type f -regex ".*\.py" -exec black {} + diff --git a/tests/cicd/src/conftest.py b/tests/cicd/src/conftest.py index f85ae490..c222d3b9 100644 --- a/tests/cicd/src/conftest.py +++ b/tests/cicd/src/conftest.py @@ -3,18 +3,22 @@ import pytest + def pytest_addoption(parser): parser.addoption("--nbname", action="store", type=str, default=None) parser.addoption("--dataset_root", action="store", type=str, default=None) + @pytest.fixture def nbname(request): return request.config.getoption("--nbname") + @pytest.fixture def dataset_root(request): return request.config.getoption("--dataset_root") + """ def pytest_generate_tests(metafunc): # This is called for every test. Only get/set command line arguments @@ -25,4 +29,4 @@ def pytest_generate_tests(metafunc): option_value = metafunc.config.option.dataset_root if 'dataset_root' in metafunc.fixturenames and option_value is not None: metafunc.parametrize("dataset_root", [option_value]) -""" \ No newline at end of file +""" From 855dabb75869c6a775f417d3a22b14c033eee60a Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 10 Dec 2019 16:34:59 -0800 Subject: [PATCH 135/207] Adds Readme information to experiments (#112) * Adds readmes to experiments * Updates instructions based on feedback --- README.md | 5 +- .../interpretation/dutchf3_patch/README.md | 71 ++++++------------- .../interpretation/dutchf3_section/README.md | 25 +++++++ .../interpretation/penobscot/README.md | 27 +++++++ 4 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 experiments/interpretation/dutchf3_section/README.md create mode 100644 experiments/interpretation/penobscot/README.md diff --git a/README.md b/README.md index 6c5cb30d..a39e10a5 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,10 @@ python -m ipykernel install --user --name seismic-interpretation We also provide scripts for a number of experiments we conducted using different segmentation approaches. These experiments are available under `experiments/interpretation`, and can be used as examples. Within each experiment start from the `train.sh` and `test.sh` scripts under the `local/` (single GPU) and `distributed/` (multiple GPUs) directories, which invoke the corresponding python scripts, `train.py` and `test.py`. Take a look at the experiment configurations (see Experiment Configuration Files section below) for experiment options and modify if necessary. -Please refer to individual experiment README files for more information. +Please refer to individual experiment README files for more information. +- [Penobscot](experiments/interpretation/penobscot/local/README.md) +- [F3 Netherlands Patch](experiments/interpretation/dutchf3_patch/local/README.md) +- [F3 Netherlands Section](experiments/interpretation/dutchf3_section/local/README.md) #### Configuration Files We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manage configuration options for the experiments. There are three ways to pass arguments to the experiment scripts (e.g. train.py or test.py): diff --git a/experiments/interpretation/dutchf3_patch/README.md b/experiments/interpretation/dutchf3_patch/README.md index 72a36dc5..8fbd2d60 100644 --- a/experiments/interpretation/dutchf3_patch/README.md +++ b/experiments/interpretation/dutchf3_patch/README.md @@ -1,58 +1,29 @@ -## F3 Netherlands Experiments Setup +## F3 Netherlands Patch Experiments +In this folder are training and testing scripts that work on the F3 Netherlands dataset. +You can run five different models on this dataset: +* [HRNet](local/configs/hrnet.yaml) +* [SEResNet](local/configs/seresnet_unet.yaml) +* [UNet](local/configs/unet.yaml) +* [PatchDeconvNet](local/configs/patch_patch_deconvnet.yaml) +* [PatchDeconvNet-Skip](local/configs/patch_deconvnet_skip.yaml.yaml) -### Conda env setup +All these models take 2D patches of the dataset as input and provide predictions for those patches. The patches need to be stitched together to form a whole inline or crossline. -Please set up a conda environment following the instructions in the top-level [README.md](../../../../README.md) file. +To understand the configuration files and the dafault parameters refer to this [section in the top level README](../../../README.md#configuration-files) -### Data download +### Setup -To download the F3 Netherlands data set, please follow the data download instructions at: -https://github.com/olivesgatech/facies_classification_benchmark - - -Once you've downloaded the F3 data set, you'll find your data files in the following directory tree: - -``` -data -├── splits -├── test_once -│ ├── test1_labels.npy -│ ├── test1_seismic.npy -│ ├── test2_labels.npy -│ └── test2_seismic.npy -└── train - ├── train_labels.npy - └── train_seismic.npy -``` - -### Data preparation - -To split the dataset into training and validation, please run the [prepare_data.py](prepare_data.py) script. - -Example run: -``` -// To split data into sections -python prepare_data.py split_train_val section --data-dir=/mnt/dutchf3 - -// To split data into patches -python prepare_data.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=100 -``` - -Please see `prepare_data.py` script for more arguments. - - -### Configuration files -We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manage configuration options for our experiments. - -We use the following three ways to pass options to the experiment scripts (e.g. train.py or test.py): - -- __default.py__ - A project config file `default.py` is a one-stop reference point for all configurable options, and provides sensible defaults for all options. - -- __yml config files__ - YAML configuration files under `configs/` are typically created one for each experiment. These are meant to be used for repeatable experiment runs and reproducible settings. Each configuration file only overrides the options that are changing in that experiment (e.g. options loaded from `defaults.py` during an experiment run will be overridden by options loaded from the yaml file) - -- __command line__ - Finally, options can be passed in through `options` argument, and those will override options loaded from the configuration file. We created CLIs for all our scripts (using Python Fire library), so you can pass these options via command-line arguments. - +Please set up a conda environment following the instructions in the top-level [README.md](../../../README.md#setting-up-environment) file. +Also follow instructions for [downloading and preparing](../../../README.md#f3-Netherlands) the data. ### Running experiments Now you're all set to run training and testing experiments on the F3 Netherlands dataset. Please start from the `train.sh` and `test.sh` scripts under the `local/` and `distributed/` directories, which invoke the corresponding python scripts. Take a look at the project configurations in (e.g in `default.py`) for experiment options and modify if necessary. + +### Monitoring progress with TensorBoard +- from the this directory, run `tensorboard --logdir='output'` (all runtime logging information is +written to the `output` folder +- open a web-browser and go to either vmpublicip:6006 if running remotely or localhost:6006 if running locally +> **NOTE**:If running remotely remember that the port must be open and accessible + +More information on Tensorboard can be found [here](https://www.tensorflow.org/get_started/summaries_and_tensorboard#launching_tensorboard). diff --git a/experiments/interpretation/dutchf3_section/README.md b/experiments/interpretation/dutchf3_section/README.md new file mode 100644 index 00000000..66d3cfcd --- /dev/null +++ b/experiments/interpretation/dutchf3_section/README.md @@ -0,0 +1,25 @@ +## F3 Netherlands Section Experiments +In this folder are training and testing scripts that work on the F3 Netherlands dataset. +You can run one model on this dataset: +* [SectionDeconvNet-Skip](local/configs/section_deconvnet_skip.yaml) + +This model takes 2D sections as input from the dataset whether these be inlines or crosslines and provides predictions for whole section. + +To understand the configuration files and the dafault parameters refer to this [section in the top level README](../../../README.md#configuration-files) + +### Setup + +Please set up a conda environment following the instructions in the top-level [README.md](../../../README.md#setting-up-environment) file. +Also follow instructions for [downloading and preparing](../../../README.md#f3-Netherlands) the data. + +### Running experiments + +Now you're all set to run training and testing experiments on the F3 Netherlands dataset. Please start from the `train.sh` and `test.sh` scripts under the `local/` directory, which invoke the corresponding python scripts. Take a look at the project configurations in (e.g in `default.py`) for experiment options and modify if necessary. + +### Monitoring progress with TensorBoard +- from the this directory, run `tensorboard --logdir='output'` (all runtime logging information is +written to the `output` folder +- open a web-browser and go to either vmpublicip:6006 if running remotely or localhost:6006 if running locally +> **NOTE**:If running remotely remember that the port must be open and accessible + +More information on Tensorboard can be found [here](https://www.tensorflow.org/get_started/summaries_and_tensorboard#launching_tensorboard). diff --git a/experiments/interpretation/penobscot/README.md b/experiments/interpretation/penobscot/README.md new file mode 100644 index 00000000..d870ac1c --- /dev/null +++ b/experiments/interpretation/penobscot/README.md @@ -0,0 +1,27 @@ +# Seismic Interpretation on Penobscot dataset +In this folder are training and testing scripts that work on the Penobscot dataset. +You can run two different models on this dataset: +* [HRNet](local/configs/hrnet.yaml) +* [SEResNet](local/configs/seresnet_unet.yaml) + +All these models take 2D patches of the dataset as input and provide predictions for those patches. The patches need to be stitched together to form a whole inline or crossline. + +To understand the configuration files and the dafault parameters refer to this [section in the top level README](../../../README.md#configuration-files) + +### Setup + +Please set up a conda environment following the instructions in the top-level [README.md](../../../README.md#setting-up-environment) file. +Also follow instructions for [downloading and preparing](../../../README.md#penobscot) the data. + +### Usage +- [`train.sh`](local/train.sh) - Will train the Segmentation model. The default configuration will execute for 300 epochs which will complete in around 3 days on a V100 GPU. During these 300 epochs succesive snapshots will be taken. By default a cyclic learning rate is applied. +- [`test.sh`](local/test.sh) - Will test your model against the test portion of the dataset. You will be able to view the performance of the trained model in Tensorboard. + +### Monitoring progress with TensorBoard +- from the this directory, run `tensorboard --logdir='output'` (all runtime logging information is +written to the `output` folder +- open a web-browser and go to either vmpublicip:6006 if running remotely or localhost:6006 if running locally +> **NOTE**:If running remotely remember that the port must be open and accessible + +More information on Tensorboard can be found [here](https://www.tensorflow.org/get_started/summaries_and_tensorboard#launching_tensorboard). + From 099c4af89d124a3dad848680d5a9dca16a3f267a Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 11 Dec 2019 11:22:26 -0500 Subject: [PATCH 136/207] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30eb1352..2f90f93f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Follow the instruction bellow to read about compute requirements and install req We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on __Linux only__. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DSVM) for Linux (Ubuntu)](https://docs.microsoft.com/en-us/azure/machine-learning/data-science-virtual-machine/dsvm-ubuntu-intro). This VM will come installed with all the system requirements that are needed to create the conda environment described below and then run the notebooks in this repository. -For this repo, we recommend selecting a multi-GPU Ubuntu VM of type [Standard_NC12](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series). The machine is powered by NVIDIA Tesla K80 (or V100 GPU for NCv2 series) which can be found in most Azure regions. +For this repo, we recommend selecting a multi-GPU Ubuntu VM of type [Standard_NC12](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#nc-series). The machine is powered by NVIDIA Tesla K80 (or V100 GPU for NCv2 series) which can be found in most Azure regions. > NOTE: For users new to Azure, your subscription may not come with a quota for GPUs. You may need to go into the Azure portal to increase your quota for GPU VMs. Learn more about how to do this here: https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits. From 5dd969d4018ddaef783f0c63f83006cb603e17bc Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 11 Dec 2019 12:16:10 -0500 Subject: [PATCH 137/207] BugBash2 Issue #83 and #89: clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 (#88) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * merge upstream into my fork (#1) * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme * sdk 1.0.76; tested conda env vs docker image; extented readme * removed reference to imaging * minor md formatting * minor md formatting * clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 - Issue #83 * Add Troubleshooting section for DSVM warnings #89 * Add Troubleshooting section for DSVM warnings, plus typo #89 * tested both yml conda env and docker; udated conda yml to have docker sdk * tested both yml conda env and docker; udated conda yml to have docker sdk; added * NVIDIA Tesla K80 (or V100 GPU for NCv2 series) - per Vanja's comment * Update README.md --- README.md | 31 ++- .../fwi_dev_conda_environment.yml | 1 + ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 5 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 142 +++++++------- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 8 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 181 +++++++++++++----- 6 files changed, 240 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index a39e10a5..2f90f93f 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ Follow the instruction bellow to read about compute requirements and install req #### Compute environment -We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on Linux only. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DS VM)](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/). This VM will come installed with all the system requirements that are needed to run the notebooks in this repository. +We recommend using a virtual machine to run the example notebooks and scripts. Specifically, you will need a GPU powered Linux machine, as this repository is developed and tested on __Linux only__. The easiest way to get started is to use the [Azure Data Science Virtual Machine (DSVM) for Linux (Ubuntu)](https://docs.microsoft.com/en-us/azure/machine-learning/data-science-virtual-machine/dsvm-ubuntu-intro). This VM will come installed with all the system requirements that are needed to create the conda environment described below and then run the notebooks in this repository. -For this repo, we recommend selecting an Ubuntu VM of type [Standard_NC6_v3](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#ncv3-series). The machine is powered by NVIDIA Tesla V100 GPU which can be found in most Azure regions. +For this repo, we recommend selecting a multi-GPU Ubuntu VM of type [Standard_NC12](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu#nc-series). The machine is powered by NVIDIA Tesla K80 (or V100 GPU for NCv2 series) which can be found in most Azure regions. > NOTE: For users new to Azure, your subscription may not come with a quota for GPUs. You may need to go into the Azure portal to increase your quota for GPU VMs. Learn more about how to do this here: https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits. @@ -274,6 +274,17 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope # Troubleshooting +For Data Science Virtual Machine conda package installation issues, make sure you locate the anaconda location on the DSVM, for example by running: +```bash +which python +``` +A typical output will be: +```bash +someusername@somevm:/projects/DeepSeismic$ which python +/anaconda/envs/py35/bin/python +``` +which will indicate that anaconda folder is __/anaconda__. We'll refer to this location in instructions below, but you should update the commands according to your local anaconda folder. +
Data Science Virtual Machine conda package installation errors @@ -287,6 +298,22 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope
+
+ Data Science Virtual Machine conda package installation warnings + + It could happen that while creating the conda environment defined by environment/anaconda/local/environment.yml on an Ubuntu DSVM, one can get multiple warnings like this: + WARNING conda.gateways.disk.delete:unlink_or_rename_to_trash(140): Could not remove or rename /anaconda/pkgs/ipywidgets-7.5.1-py_0/site-packages/ipywidgets-7.5.1.dist-info/LICENSE. Please remove this file manually (you may need to reboot to free file handles) + + If that happens, similar to instructions above, stop the conda environment creation (type ```Ctrl+C```) and then change recursively the ownership /anaconda directory from root to current user, by running this command: + + ```bash + sudo chown -R $USER /anaconda + ``` + + After these command completes, try creating the conda environment in __environment/anaconda/local/environment.yml__ again. + +
+
Model training or scoring is not using GPU diff --git a/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml index e79343d6..eedd1197 100755 --- a/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml +++ b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml @@ -14,3 +14,4 @@ dependencies: - python-dotenv - papermill[azure] - azureml-sdk[notebooks,automl,explain]==1.0.76 + - docker diff --git a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index 3040bd87..62683429 100755 --- a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -861,6 +861,7 @@ "metadata": {}, "outputs": [], "source": [ + "# create_ACR_FLAG=False\n", "if create_ACR_FLAG:\n", " import subprocess\n", " cli_command = 'az acr credential show -n '+acr_name\n", @@ -900,9 +901,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python [conda env:fwi_dev_conda_environment] *", "language": "python", - "name": "python3" + "name": "conda-env-fwi_dev_conda_environment-py" }, "language_info": { "codemirror_mode": { diff --git a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb index 78276433..aac30038 100755 --- a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -374,7 +374,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" + "Writing /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" ] } ], @@ -425,7 +425,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76\n" + "Writing /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76\n" ] } ], @@ -489,14 +489,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "total 28\r\n", - "-rwxrwxrwx 1 root root 733 Dec 4 02:31 conda_env_fwi01_azureml_sdk.v1.0.69.yml\r\n", - "-rwxrwxrwx 1 root root 725 Dec 4 02:31 conda_env_fwi01_azureml_sdk.v1.0.74.yml\r\n", - "-rwxrwxrwx 1 root root 725 Dec 4 16:59 conda_env_fwi01_azureml_sdk.v1.0.76.yml\r\n", - "-rwxrwxrwx 1 root root 725 Dec 4 16:59 conda_env_fwi01_azureml.yml\r\n", - "-rwxrwxrwx 1 root root 1073 Dec 4 02:31 Dockerfile_fwi01_azureml_sdk.v1.0.69\r\n", - "-rwxrwxrwx 1 root root 1073 Dec 4 02:31 Dockerfile_fwi01_azureml_sdk.v1.0.74\r\n", - "-rwxrwxrwx 1 root root 1073 Dec 4 16:59 Dockerfile_fwi01_azureml_sdk.v1.0.76\r\n" + "total 12\r\n", + "-rw-rw-r-- 1 loginvm022 loginvm022 725 Dec 6 15:26 conda_env_fwi01_azureml_sdk.v1.0.76.yml\r\n", + "-rw-rw-r-- 1 loginvm022 loginvm022 725 Dec 6 15:26 conda_env_fwi01_azureml.yml\r\n", + "-rw-rw-r-- 1 loginvm022 loginvm022 1073 Dec 6 15:26 Dockerfile_fwi01_azureml_sdk.v1.0.76\r\n" ] } ], @@ -524,7 +520,7 @@ { "data": { "text/plain": [ - "['Sending build context to Docker daemon 13.31kB',\n", + "['Sending build context to Docker daemon 6.144kB',\n", " '',\n", " 'Step 1/15 : FROM continuumio/miniconda3:4.7.10',\n", " '4.7.10: Pulling from continuumio/miniconda3',\n", @@ -538,10 +534,10 @@ { "data": { "text/plain": [ - "[' ---> Running in 64cc95908200',\n", - " 'Removing intermediate container 64cc95908200',\n", - " ' ---> 619ab5d20944',\n", - " 'Successfully built 619ab5d20944',\n", + "[' ---> Running in 00c2824f0cd3',\n", + " 'Removing intermediate container 00c2824f0cd3',\n", + " ' ---> 48fb03897096',\n", + " 'Successfully built 48fb03897096',\n", " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76']" ] }, @@ -725,39 +721,39 @@ "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", "\n", "total 560\n", - "-rw-r--r-- 1 root root 11521 Dec 4 17:00 conftest.py\n", - "-rw-r--r-- 1 root root 6006 Dec 4 17:00 test_adjoint.py\n", - "-rw-r--r-- 1 root root 14586 Dec 4 17:00 test_autotuner.py\n", - "-rw-r--r-- 1 root root 7538 Dec 4 17:00 test_builtins.py\n", - "-rw-r--r-- 1 root root 24415 Dec 4 17:00 test_caching.py\n", - "-rw-r--r-- 1 root root 9721 Dec 4 17:00 test_checkpointing.py\n", - "-rw-r--r-- 1 root root 1095 Dec 4 17:00 test_constant.py\n", - "-rw-r--r-- 1 root root 55954 Dec 4 17:00 test_data.py\n", - "-rw-r--r-- 1 root root 481 Dec 4 17:00 test_dependency_bugs.py\n", - "-rw-r--r-- 1 root root 16331 Dec 4 17:00 test_derivatives.py\n", - "-rw-r--r-- 1 root root 1473 Dec 4 17:00 test_differentiable.py\n", - "-rw-r--r-- 1 root root 30846 Dec 4 17:00 test_dimension.py\n", - "-rw-r--r-- 1 root root 24838 Dec 4 17:00 test_dle.py\n", - "-rw-r--r-- 1 root root 1169 Dec 4 17:00 test_docstrings.py\n", - "-rw-r--r-- 1 root root 32134 Dec 4 17:00 test_dse.py\n", - "-rw-r--r-- 1 root root 8205 Dec 4 17:00 test_gradient.py\n", - "-rw-r--r-- 1 root root 15227 Dec 4 17:00 test_interpolation.py\n", - "-rw-r--r-- 1 root root 31816 Dec 4 17:00 test_ir.py\n", - "-rw-r--r-- 1 root root 63169 Dec 4 17:00 test_mpi.py\n", - "-rw-r--r-- 1 root root 67053 Dec 4 17:00 test_operator.py\n", - "-rw-r--r-- 1 root root 14875 Dec 4 17:00 test_ops.py\n", - "-rw-r--r-- 1 root root 12228 Dec 4 17:00 test_pickle.py\n", - "-rw-r--r-- 1 root root 1809 Dec 4 17:00 test_resample.py\n", - "-rw-r--r-- 1 root root 1754 Dec 4 17:00 test_save.py\n", - "-rw-r--r-- 1 root root 2115 Dec 4 17:00 test_staggered_utils.py\n", - "-rw-r--r-- 1 root root 5711 Dec 4 17:00 test_subdomains.py\n", - "-rw-r--r-- 1 root root 3320 Dec 4 17:00 test_symbolic_coefficients.py\n", - "-rw-r--r-- 1 root root 7277 Dec 4 17:00 test_tensors.py\n", - "-rw-r--r-- 1 root root 3186 Dec 4 17:00 test_timestepping.py\n", - "-rw-r--r-- 1 root root 603 Dec 4 17:00 test_tools.py\n", - "-rw-r--r-- 1 root root 3296 Dec 4 17:00 test_tti.py\n", - "-rw-r--r-- 1 root root 8835 Dec 4 17:00 test_visitors.py\n", - "-rw-r--r-- 1 root root 21802 Dec 4 17:00 test_yask.py\n", + "-rw-r--r-- 1 root root 11521 Dec 6 15:26 conftest.py\n", + "-rw-r--r-- 1 root root 6006 Dec 6 15:26 test_adjoint.py\n", + "-rw-r--r-- 1 root root 14586 Dec 6 15:26 test_autotuner.py\n", + "-rw-r--r-- 1 root root 7538 Dec 6 15:26 test_builtins.py\n", + "-rw-r--r-- 1 root root 24415 Dec 6 15:26 test_caching.py\n", + "-rw-r--r-- 1 root root 9721 Dec 6 15:26 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Dec 6 15:26 test_constant.py\n", + "-rw-r--r-- 1 root root 55954 Dec 6 15:26 test_data.py\n", + "-rw-r--r-- 1 root root 481 Dec 6 15:26 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16331 Dec 6 15:26 test_derivatives.py\n", + "-rw-r--r-- 1 root root 1473 Dec 6 15:26 test_differentiable.py\n", + "-rw-r--r-- 1 root root 30846 Dec 6 15:26 test_dimension.py\n", + "-rw-r--r-- 1 root root 24838 Dec 6 15:26 test_dle.py\n", + "-rw-r--r-- 1 root root 1169 Dec 6 15:26 test_docstrings.py\n", + "-rw-r--r-- 1 root root 32134 Dec 6 15:26 test_dse.py\n", + "-rw-r--r-- 1 root root 8205 Dec 6 15:26 test_gradient.py\n", + "-rw-r--r-- 1 root root 15227 Dec 6 15:26 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31816 Dec 6 15:26 test_ir.py\n", + "-rw-r--r-- 1 root root 63169 Dec 6 15:26 test_mpi.py\n", + "-rw-r--r-- 1 root root 67053 Dec 6 15:26 test_operator.py\n", + "-rw-r--r-- 1 root root 14875 Dec 6 15:26 test_ops.py\n", + "-rw-r--r-- 1 root root 12228 Dec 6 15:26 test_pickle.py\n", + "-rw-r--r-- 1 root root 1809 Dec 6 15:26 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Dec 6 15:26 test_save.py\n", + "-rw-r--r-- 1 root root 2115 Dec 6 15:26 test_staggered_utils.py\n", + "-rw-r--r-- 1 root root 5711 Dec 6 15:26 test_subdomains.py\n", + "-rw-r--r-- 1 root root 3320 Dec 6 15:26 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 7277 Dec 6 15:26 test_tensors.py\n", + "-rw-r--r-- 1 root root 3186 Dec 6 15:26 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Dec 6 15:26 test_tools.py\n", + "-rw-r--r-- 1 root root 3296 Dec 6 15:26 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Dec 6 15:26 test_visitors.py\n", + "-rw-r--r-- 1 root root 21802 Dec 6 15:26 test_yask.py\n", "1.0.76\n", "\n", "content of devito tests log file after testing:\n", @@ -812,7 +808,7 @@ "=================================== FAILURES ===================================\n", "______________________ TestSC.test_function_coefficients _______________________\n", "\n", - "self = \n", + "self = \n", "\n", " def test_function_coefficients(self):\n", " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", @@ -856,10 +852,10 @@ " \n", "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", "E assert Data(False)\n", - "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", - "E + where = np.all\n", - "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", - "E + where = np.isclose\n", + "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", + "E + where = np.all\n", + "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", + "E + where = np.isclose\n", "\n", "tests/test_symbolic_coefficients.py:96: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", @@ -876,7 +872,7 @@ "------------------------------ Captured log call -------------------------------\n", "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", - "====== 1 failed, 968 passed, 87 skipped, 2 xfailed in 1038.47s (0:17:18) =======\n" + "====== 1 failed, 968 passed, 87 skipped, 2 xfailed in 1070.16s (0:17:50) =======\n" ] } ], @@ -906,7 +902,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -915,7 +911,7 @@ "'az acr login --name fwi01acr'" ] }, - "execution_count": 19, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" }, @@ -923,11 +919,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Login Succeeded\n", - "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\n", - "Configure a credential helper to remove this warning. See\n", - "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\n", - "\n", + "Login Succeeded\r\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm022/.docker/config.json.\r\n", + "Configure a credential helper to remove this warning. See\r\n", + "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", + "\r\n", "\u001b[0m" ] } @@ -938,7 +934,7 @@ "# !docker login -u=$docker_login -p=$docker_pwd\n", "# !docker push {docker_image_name}\n", "\n", - "%dotenv $dotenv_file_path\n", + "%dotenv -o $dotenv_file_path\n", "cli_command='az acr login --name '+os.getenv('ACR_NAME')\n", "# print cli command\n", "cli_command\n", @@ -950,7 +946,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -959,7 +955,7 @@ "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" ] }, - "execution_count": 20, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -971,7 +967,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -980,10 +976,10 @@ "text": [ "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", "\n", - "\u001b[1B01e8603a: Preparing \n", - "\u001b[1B83481e05: Preparing \n", - "\u001b[1Bf84794df: Preparing \n", - "\u001b[1B4559bd8a: Preparing \n", + "\u001b[1Bd6300f53: Preparing \n", + "\u001b[1B01af7f6b: Preparing \n", + "\u001b[1B41f0b573: Preparing \n", + "\u001b[1B04ca5654: Preparing \n", "\u001b[1Bf8fc4c9a: Preparing \n", "\u001b[1Bba47210e: Preparing \n" ] @@ -992,21 +988,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[7B01e8603a: Pushing 1.055GB/2.967GBA\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[5A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 479.3MB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 910.8MB/2.967GB\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K" + "\u001b[6B01af7f6b: Pushing 1.484GB/3.028GBA\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[2A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[6B83481e05: Pushing 2.253GB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 1.072GB/2.967GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 1.771GB/2.967GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K" + "\u001b[7Bd6300f53: Pushing 3.026GB/3.028GB\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2KPushing 2.58GB/2.968GB\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[6B83481e05: Pushed 3.102GB/3.028GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[Ksdk.v1.0.76: digest: sha256:25a4a6faad05e910366df62dc4ea0fbeb0d5fe912e56ad5bddb2325104653710 size: 1800\n" + "\u001b[6B01af7f6b: Pushed 3.103GB/3.028GB\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2Ksdk.v1.0.76: digest: sha256:416dc7ce59c279822e967223790f7b8b7d99ba62bc643ca44b94551135b60b6b size: 1800\n" ] } ], @@ -1016,7 +1012,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 25, "metadata": {}, "outputs": [ { diff --git a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb index c2902730..db76f4c5 100755 --- a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -530,7 +530,7 @@ "output_type": "stream", "text": [ "Login Succeeded\r\n", - "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\r\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm022/.docker/config.json.\r\n", "Configure a credential helper to remove this warning. See\r\n", "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", "\r\n", @@ -859,7 +859,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f94f884975d14f94977e26fb8e35745f", + "model_id": "565b952db744469fa2137b6c94e15f7a", "version_major": 2, "version_minor": 0 }, @@ -872,7 +872,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"created_utc\": \"2019-12-04T17:21:06.014446Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-04T17:30:00.733408Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=XtrqU6IMV%2B48BKF%2BQtFwy6LRoQjGkEJ2ALnJqe4N86Y%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=Z5mXvd7qJNNv2tWMiT1C25Tc%2Bu%2BblrWqkN6UFUzge2U%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=dOviChtDoL5vak4hJd2Pw3Pjj%2BkN5BmxbcPiW6ezqK0%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt?sv=2019-02-02&sr=b&sig=1rch4YL2wvgtosymal8WdNdNW6K34Om02P0TMcWcjyc%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=DlifA0Xe8zotaCGo56XNplmG4xCoY0eegb1kFvRaOXg%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=8myM2hR0Cph66FXGtG4YmjmYoP%2F5c3bDBXacYucjnzI%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"logs/azureml/730_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/logs/azureml/730_azureml.log?sv=2019-02-02&sr=b&sig=AE4JyiNXVvzRYF8nTY0BrwltFPiXXIwSga9AlzFDEd8%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575480062_180862b6/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=Wk8VGtb5XKlUzkOqIRVsnhvdT3MGSH%2B%2BJEuS63MP0%2BI%3D&st=2019-12-04T17%3A20%3A05Z&se=2019-12-05T01%3A30%3A05Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_a913ce8b92e4ddd29b172a046e5b07d3c58850b88b5c182c77198a079735866c_d.txt\"], [\"logs/azureml/730_azureml.log\"]], \"run_duration\": \"0:08:54\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575480062_180862b6\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-04 17:28:21,458|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-04 17:28:21,458|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-04 17:28:21,459|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-04 17:28:21,459|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-04 17:28:21,801|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-04 17:28:21,801|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-04 17:28:22,185|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-04 17:28:22,775|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-04 17:28:22,780|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-04 17:28:22,785|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-04 17:28:22,790|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-04 17:28:22,792|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:28:22,798|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,799|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-04 17:28:22,799|azureml.core.authentication|DEBUG|Time to expire 1813963.200661 seconds\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,799|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,800|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,961|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:22,967|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,975|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,980|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,985|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,990|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:22,991|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-04 17:28:22,991|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:28:22,991|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:28:22,992|msrest.http_logger|DEBUG|None\\n2019-12-04 17:28:22,992|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:28:22,992|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:28:22,993|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:28:22,993|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:28:23 GMT'\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:23,582|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '95e054c6-f67e-4220-a7cd-a0f5f92822fe'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-04 17:28:23,583|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:28:23,584|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1460,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-04T17:21:06.014446+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-04T17:25:54.100925+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575480062_180862b6/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-04 17:28:23,589|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-04 17:28:23,590|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'mlflow.source.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-04 17:28:23,590|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-04 17:28:23,591|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-04 17:28:23,591|azureml.WorkerPool|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:28:23,591|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-04 17:28:23,592|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:28:26,647|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,648|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,649|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-04 17:28:26,654|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,655|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-04 17:28:26,660|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,666|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,672|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,677|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:28:26,677|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-04 17:28:26,677|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:28:26,678|msrest.http_logger|DEBUG|None\\n2019-12-04 17:28:26,678|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:28:26,679|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:28:27 GMT'\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-04 17:28:27,358|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'd26b37c0-66c2-4996-b548-f32a279d53b6'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:28:27,359|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1460,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-04T17:21:06.014446+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": null,\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-04T17:25:54.100925+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575480062_180862b6\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"batchai\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"f912d6d88e3890e87b829679d70ce4f86cf619dd\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575480062_180862b6/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575480062_180862b6/diagnostics\\\",\\n \\\"computeRequest\\\": null,\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-04 17:28:27,361|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-04 17:28:27,362|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'batchai', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'mlflow.source.git.commit': 'f912d6d88e3890e87b829679d70ce4f86cf619dd', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-04 17:28:27,362|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-04 17:28:52,796|azureml.core.authentication|DEBUG|Time to expire 1813933.203089 seconds\\n2019-12-04 17:29:22,797|azureml.core.authentication|DEBUG|Time to expire 1813903.202753 seconds\\n2019-12-04 17:29:37,671|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-04 17:29:37,671|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:29:37,672|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:29:37,707|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575480062_180862b6/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575480062_180862b6\\n2019-12-04 17:29:37,708|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-04 17:29:37,708|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6|INFO|complete is not setting status for submitted runs.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-04 17:29:37,708|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-04 17:29:37,709|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-04 17:29:37,710|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-04 17:29:37,710|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-04 17:29:37,710|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-04 17:29:37,711|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-04 17:29:37,711|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-04 17:29:37,711|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-04 17:29:37,712|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-04 17:29:37,713|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-04 17:29:37,713|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-04 17:29:37,713|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-04 17:29:37,713|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575480062_180862b6/batch/metrics'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG|Request headers:\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-04 17:29:37,714|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-04 17:29:37,714|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,715|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,715|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-04 17:29:37,715|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-04 17:29:37,715|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-04 17:29:37,716|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG|Request body:\\n2019-12-04 17:29:37,716|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"db9cd91b-ba34-41bf-aad8-b744d46471a4\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-04T17:29:37.671117Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-04 17:29:37,716|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG|Response headers:\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG| 'Date': 'Wed, 04 Dec 2019 17:29:37 GMT'\\n2019-12-04 17:29:37,833|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0f5916a7-731b-4bd2-9980-35efac48934c'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG|Response content:\\n2019-12-04 17:29:37,834|msrest.http_logger|DEBUG|\\n2019-12-04 17:29:37,835|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-04 17:29:37,966|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00024700164794921875 seconds.\\n\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml._SubmittedRun#020_AzureMLEstimator_1575480062_180862b6.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-04 17:29:37,967|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-04 17:29:37,967|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575674728_d40baeba\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575674728_d40baeba\", \"created_utc\": \"2019-12-06T23:25:30.597858Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-06T23:34:26.039772Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/55_azureml-execution-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt?sv=2019-02-02&sr=b&sig=1Fz2ltrBSXhF9tDzTuEOv35mBsOLsf%2BCVuTEuSCRWdg%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/65_job_prep-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt?sv=2019-02-02&sr=b&sig=PwHIdkWadtTAj29WuPOCF3g0RSrWdriOmKhqdjZNm3I%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=Iz8WkiOv%2BkEXeOox8p3P8XkLIdb8pjhCO%2Bo8slYUBGk%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/75_job_post-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/75_job_post-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt?sv=2019-02-02&sr=b&sig=gz88u5ZC%2B7N8QospVRIL8zd%2FEyQKbljoZXQD01jAyXM%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=4nj2pjm1rtKIjBmyudNaBEX6ITd3Gm%2BQLEUgjDYVBIc%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=NQLsveMtGHBEYsmiwoPvPpOv%2B6wabnQp2IwDrVjh49Q%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"logs/azureml/729_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/logs/azureml/729_azureml.log?sv=2019-02-02&sr=b&sig=HpwLZSHX0J%2B2eWILTIDA7%2BmpVIEF0%2BIFfM2LHgYGk8w%3D&st=2019-12-06T23%3A24%3A43Z&se=2019-12-07T07%3A34%3A43Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=g%2Fi60CvATRGwaeQM9b6QihJxeFX0jTl%2BOKELCYYQ3rM%3D&st=2019-12-06T23%3A24%3A43Z&se=2019-12-07T07%3A34%3A43Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\"], [\"logs/azureml/729_azureml.log\"]], \"run_duration\": \"0:08:55\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575674728_d40baeba\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-06 23:32:41,989|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-06 23:32:41,989|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-06 23:32:41,990|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-06 23:32:41,990|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-06 23:32:42,323|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-06 23:32:42,323|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-06 23:32:42,721|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-06 23:32:42,721|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-06 23:32:42,722|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-06 23:32:42,722|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-06 23:32:42,722|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-06 23:32:43,300|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-06 23:32:43,306|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-06 23:32:43,311|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-06 23:32:43,316|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-06 23:32:43,318|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-06 23:32:43,324|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,325|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-06 23:32:43,325|azureml.core.authentication|DEBUG|Time to expire 1813966.674698 seconds\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,326|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,326|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,326|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,356|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,361|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,369|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,374|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,379|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,385|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,385|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-06 23:32:43,386|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-06 23:32:43,386|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|Request headers:\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a72fb1c-fdba-4e6d-a244-7315dcdf5d54'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'request-id': '2a72fb1c-fdba-4e6d-a244-7315dcdf5d54'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|Request body:\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|None\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-06 23:32:43,442|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG|Response headers:\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Date': 'Fri, 06 Dec 2019 23:32:43 GMT'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a72fb1c-fdba-4e6d-a244-7315dcdf5d54'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG|Response content:\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1516,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-06T23:25:30.5978583+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-06T23:30:15.4122862+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575674728_d40baeba/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-06 23:32:43,449|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-06 23:32:43,450|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-06 23:32:43,450|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-06 23:32:43,451|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-06 23:32:43,451|azureml.WorkerPool|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,593|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,593|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,599|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,600|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-06 23:32:45,605|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,610|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,616|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,621|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,622|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-06 23:32:45,622|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba'\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG|Request headers:\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7502a986-27e5-47c2-8a48-e5501a0dda7c'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG| 'request-id': '7502a986-27e5-47c2-8a48-e5501a0dda7c'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG|Request body:\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG|None\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-06 23:32:46,018|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-06 23:32:46,018|msrest.http_logger|DEBUG|Response headers:\\n2019-12-06 23:32:46,018|msrest.http_logger|DEBUG| 'Date': 'Fri, 06 Dec 2019 23:32:46 GMT'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7502a986-27e5-47c2-8a48-e5501a0dda7c'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG|Response content:\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1516,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-06T23:25:30.5978583+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-06T23:30:15.4122862+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575674728_d40baeba/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-06 23:32:46,022|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-06 23:32:46,023|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-06 23:32:46,023|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-06 23:33:13,322|azureml.core.authentication|DEBUG|Time to expire 1813936.677149 seconds\\n2019-12-06 23:33:43,323|azureml.core.authentication|DEBUG|Time to expire 1813906.67683 seconds\\n2019-12-06 23:33:57,866|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-06 23:33:57,867|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-06 23:33:57,867|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:33:57,912|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-06 23:33:57,912|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba|INFO|complete is not setting status for submitted runs.\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,913|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,914|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,914|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-06 23:33:57,914|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-06 23:33:57,914|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-06 23:33:57,915|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-06 23:33:57,915|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-06 23:33:57,915|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-06 23:33:57,915|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-06 23:33:57,916|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-06 23:33:57,916|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-06 23:33:57,917|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:33:57,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-06 23:33:57,917|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba/batch/metrics'\\n2019-12-06 23:33:57,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG|Request headers:\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7318af30-3aa3-4d84-a4db-0595c67afd70'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG| 'request-id': '7318af30-3aa3-4d84-a4db-0595c67afd70'\\n2019-12-06 23:33:57,919|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-06 23:33:57,919|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-06 23:33:57,919|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG|Request body:\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"d160ffa3-e1bc-4ff2-b60f-7742b38cdfd2\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-06T23:33:57.866688Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-06 23:33:57,919|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-06 23:33:57,920|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-06 23:33:57,920|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-06 23:33:57,920|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG|Response headers:\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Date': 'Fri, 06 Dec 2019 23:33:58 GMT'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7318af30-3aa3-4d84-a4db-0595c67afd70'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG|Response content:\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG|\\n2019-12-06 23:33:58,045|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002143383026123047 seconds.\\n\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-06 23:33:58,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -935,7 +935,7 @@ { "data": { "text/plain": [ - "'runId= 020_AzureMLEstimator_1575480062_180862b6'" + "'runId= 020_AzureMLEstimator_1575674728_d40baeba'" ] }, "execution_count": 19, diff --git a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb index f6b40ef1..d8a5448a 100755 --- a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -797,7 +797,7 @@ ], "source": [ "# Verify that cluster does not exist already\n", - "max_nodes_value = 5\n", + "max_nodes_value = 2\n", "try:\n", " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", " print(\"Found existing gpu cluster\")\n", @@ -835,7 +835,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "edb374a7e72648cd892b24fb0cc8c2b6", + "model_id": "a0312dfcb82f419288e3c3c37c39b9dd", "version_major": 2, "version_minor": 0 }, @@ -848,7 +848,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575481417_8013866e?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575481417_8013866e\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575481417_8013866e\", \"created_utc\": \"2019-12-04T17:43:40.27579Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":1,\\\"CurrentNodeCount\\\":2}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575481417_8013866e/azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt?sv=2019-02-02&sr=b&sig=lVAX2nGZV7C62e8t0DZkL19PV8vYW8dKjDExkyOnHj0%3D&st=2019-12-04T17%3A38%3A58Z&se=2019-12-05T01%3A48%3A58Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_36bddbf1a607b8242a5ba4311f66b8fc2a8705ea23f5ab49694722e91e9d8ea2_p.txt\"]], \"run_duration\": \"0:05:18\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2019-12-04T17:48:07Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.76: Pulling from fwi01_azureml\\n1ab2bdfe9778: Pulling fs layer\\ndd7d28bd8be5: Pulling fs layer\\naf998e3a361b: Pulling fs layer\\n1f706d9b369a: Pulling fs layer\\ne5000b763efd: Pulling fs layer\\n8c572c1c1101: Pulling fs layer\\n5988e7f62200: Pulling fs layer\\n8c572c1c1101: Waiting\\n5988e7f62200: Waiting\\ne5000b763efd: Waiting\\n1f706d9b369a: Waiting\\naf998e3a361b: Verifying Checksum\\naf998e3a361b: Download complete\\ndd7d28bd8be5: Verifying Checksum\\ndd7d28bd8be5: Download complete\\ne5000b763efd: Verifying Checksum\\ne5000b763efd: Download complete\\n1ab2bdfe9778: Verifying Checksum\\n1ab2bdfe9778: Download complete\\n1f706d9b369a: Verifying Checksum\\n1f706d9b369a: Download complete\\n1ab2bdfe9778: Pull complete\\n5988e7f62200: Verifying Checksum\\n5988e7f62200: Download complete\\n8c572c1c1101: Verifying Checksum\\n8c572c1c1101: Download complete\\ndd7d28bd8be5: Pull complete\\naf998e3a361b: Pull complete\\n1f706d9b369a: Pull complete\\ne5000b763efd: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575678435_be18a2fc?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575678435_be18a2fc\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575678435_be18a2fc\", \"created_utc\": \"2019-12-07T00:27:18.102865Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":1,\\\"CurrentNodeCount\\\":2}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575678435_be18a2fc/azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt?sv=2019-02-02&sr=b&sig=99MfEJ4IvLwXgM3jjLm4amfljnv7gOK3%2BQPb1GN%2BZKg%3D&st=2019-12-07T00%3A22%3A27Z&se=2019-12-07T08%3A32%3A27Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt\"]], \"run_duration\": \"0:05:10\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2019-12-07T00:31:04Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.76: Pulling from fwi01_azureml\\n1ab2bdfe9778: Pulling fs layer\\ndd7d28bd8be5: Pulling fs layer\\naf998e3a361b: Pulling fs layer\\n8f61820757bf: Pulling fs layer\\n0eb461057035: Pulling fs layer\\n23276e49c76d: Pulling fs layer\\nc55ca301ea9f: Pulling fs layer\\n0eb461057035: Waiting\\n8f61820757bf: Waiting\\nc55ca301ea9f: Waiting\\n1ab2bdfe9778: Verifying Checksum\\n1ab2bdfe9778: Download complete\\naf998e3a361b: Verifying Checksum\\naf998e3a361b: Download complete\\n0eb461057035: Verifying Checksum\\n0eb461057035: Download complete\\ndd7d28bd8be5: Verifying Checksum\\ndd7d28bd8be5: Download complete\\n1ab2bdfe9778: Pull complete\\n8f61820757bf: Verifying Checksum\\n8f61820757bf: Download complete\\ndd7d28bd8be5: Pull complete\\nc55ca301ea9f: Verifying Checksum\\nc55ca301ea9f: Download complete\\n23276e49c76d: Verifying Checksum\\n23276e49c76d: Download complete\\naf998e3a361b: Pull complete\\n8f61820757bf: Pull complete\\n0eb461057035: Pull complete\\n23276e49c76d: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -921,7 +921,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 6, time 13.138 seconds: Counter({'Completed': 1})\r" + "Final print 9, time 20.798 seconds: Counter({'Completed': 1})\r" ] } ], @@ -954,8 +954,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "run_duration in seconds 241.125075\n", - "run_duration= 4m 1.125s\n" + "run_duration in seconds 243.960763\n", + "run_duration= 4m 3.961s\n" ] } ], @@ -982,13 +982,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Showing details for run 15\n" + "Showing details for run 498\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "78a6832f42fc4bb1bece76520e305f54", + "model_id": "cd44e7b0a1c447dabe98bf114f420d76", "version_major": 2, "version_minor": 0 }, @@ -1001,7 +1001,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Queued\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575482070_cc2fb88e?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575482070_cc2fb88e\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575482070_cc2fb88e\", \"created_utc\": \"2019-12-04T17:54:31.673448Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"batchai\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"mlflow.source.git.commit\": \"f912d6d88e3890e87b829679d70ce4f86cf619dd\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":0,\\\"RunningNodeCount\\\":10,\\\"CurrentNodeCount\\\":10}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Queued\", \"log_files\": {}, \"log_groups\": [], \"run_duration\": \"0:05:16\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"Your job is submitted in Azure cloud and we are monitoring to get logs...\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"created_utc\": \"2019-12-07T01:54:55.33033Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-07T01:56:48.811115Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=9mQARzuRlCW%2F%2Brv3FDzJvm%2Fsaudk6GFjNypMRkV3O8g%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=TMxrg26ywABOyJtGYT3KVLrGP0TYIHQ9E3ePlr%2BQepg%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=vWkErsH55%2BLhIG%2FBJbtZb8NSNHFyNAzxk5VjW4p6lcM%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=cbDgvPNn4LNXDsUXZwmWCjRMj0O9PnFSqSCtuCPMTFo%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=wvqhR%2Bnzw0uLEsCGETAxkKrdwN5eI%2FgvTeB4juQ4aUI%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=kkirWrsrpjcrKndUUPxuJVeRWu0GthsVZ4cXpxbEGMg%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"logs/azureml/728_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/logs/azureml/728_azureml.log?sv=2019-02-02&sr=b&sig=pK%2F6TBBvQEPexjuRPR1FyOq6CUPXfnNBobkTmpmaeiM%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=o%2BPcdcJvKZyQWRA0HpaJbM%2BxhqFOkdDjgBqtxtHtoag%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"logs/azureml/728_azureml.log\"]], \"run_duration\": \"0:01:53\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-07 01:55:16,975|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-07 01:55:17,242|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-07 01:55:17,243|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-07 01:55:18,070|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-07 01:55:18,075|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-07 01:55:18,078|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-07 01:55:18,082|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-07 01:55:18,083|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:55:18,088|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,089|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-07 01:55:18,089|azureml.core.authentication|DEBUG|Time to expire 1814376.910384 seconds\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,118|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,122|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,128|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,132|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,136|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,141|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,141|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-07 01:55:18,142|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG|None\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:55:18 GMT'\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 2107,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-07T01:54:55.3303306+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-07T01:55:07.6378716+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575683693_ddd16e31/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-07 01:55:18,202|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-07 01:55:18,203|azureml.WorkerPool|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,157|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,158|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-07 01:55:20,162|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,166|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,170|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,175|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,175|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-07 01:55:20,175|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31'\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG|None\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:55:20 GMT'\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 2107,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-07T01:54:55.3303306+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-07T01:55:07.6378716+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575683693_ddd16e31/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-07 01:55:48,084|azureml.core.authentication|DEBUG|Time to expire 1814346.915499 seconds\\n2019-12-07 01:56:18,084|azureml.core.authentication|DEBUG|Time to expire 1814316.915133 seconds\\n2019-12-07 01:56:25,858|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-07 01:56:25,858|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:56:25,859|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-07 01:56:25,925|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|INFO|complete is not setting status for submitted runs.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-07 01:56:25,927|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-07 01:56:25,927|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-07 01:56:25,928|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-07 01:56:25,928|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-07 01:56:25,928|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-07 01:56:25,929|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:56:25,929|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-07 01:56:25,929|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-07 01:56:25,929|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31/batch/metrics'\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:25,931|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-07 01:56:25,932|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-07 01:56:25,932|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"1a8ad3d8-accf-42da-a07d-fd00ef5ee1e6\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-07T01:56:25.858188Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:56:26,050|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:56:26 GMT'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|\\n2019-12-07 01:56:26,052|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-07 01:56:26,182|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-07 01:56:26,182|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002186298370361328 seconds.\\n\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-07 01:56:26,183|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -1010,14 +1010,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Counter16: submission of job 16 on 10 nodes took 9.082231044769287 seconds \n", - "run list length 16\n", - "Counter17: submission of job 17 on 10 nodes took 9.493095874786377 seconds \n", - "run list length 17\n", - "Counter18: submission of job 18 on 10 nodes took 11.00885009765625 seconds \n", - "run list length 18\n", - "Counter19: submission of job 19 on 10 nodes took 8.174627780914307 seconds \n", - "run list length 19\n" + "Counter499: submission of job 499 on 400 nodes took 9.16640019416809 seconds \n", + "run list length 499\n" ] } ], @@ -1025,8 +1019,8 @@ "import time\n", "from IPython.display import clear_output\n", "\n", - "no_of_jobs = 20\n", - "no_of_nodes = 10\n", + "no_of_jobs = 500\n", + "no_of_nodes = 400\n", "\n", "job_counter = 0\n", "print_cycle = 7\n", @@ -1060,10 +1054,106 @@ { "data": { "text/plain": [ - "array([ 9.56427002, 10.400774 , 9.58173323, 10.15340447, 11.36087322,\n", - " 12.94680119, 10.28511715, 9.50729609, 10.37926507, 8.8516674 ,\n", - " 8.78578663, 10.88563776, 10.30812001, 8.43333387, 8.51853824,\n", - " 9.08223104, 9.49309587, 11.0088501 , 8.17462778])" + "array([10.16889381, 10.52522182, 8.67223501, 7.76976609, 8.98659873,\n", + " 9.54043746, 7.56379271, 7.95067477, 10.98772812, 8.58469343,\n", + " 9.19690919, 8.37747335, 8.49322033, 8.96249437, 11.00566387,\n", + " 10.18721223, 8.70340395, 9.07873917, 8.83641577, 9.93886757,\n", + " 8.43751788, 8.88584614, 8.46158338, 8.10118651, 7.95576859,\n", + " 8.02682757, 8.59585524, 11.43893504, 8.21132302, 7.56929898,\n", + " 9.16166759, 7.96446443, 8.20211887, 8.0066514 , 8.16604567,\n", + " 9.03855515, 9.27646971, 7.88356876, 8.6105082 , 8.63279152,\n", + " 9.63798594, 7.88380122, 11.83064437, 7.67609763, 8.36450744,\n", + " 10.36203027, 8.20605659, 8.27934074, 8.71854138, 7.48072934,\n", + " 7.98534775, 7.88993239, 9.49783468, 8.20365477, 8.31964707,\n", + " 8.24653029, 9.14784336, 8.39632297, 8.88221884, 10.17075896,\n", + " 7.93166018, 8.50952411, 8.35107565, 8.62145162, 9.1473949 ,\n", + " 10.16314006, 9.48931861, 9.52163553, 10.48561263, 8.70149064,\n", + " 8.83968425, 8.77899456, 8.19752908, 8.23720503, 8.44300842,\n", + " 10.4865036 , 9.38597918, 8.16601682, 10.31557417, 9.39266205,\n", + " 9.3517375 , 8.26235414, 9.90602231, 8.08361053, 9.55309701,\n", + " 8.37694287, 8.2842195 , 9.27187061, 8.05741239, 9.81221128,\n", + " 8.67282987, 7.50111246, 8.84159875, 7.5928266 , 8.2180264 ,\n", + " 11.30247498, 8.97954369, 9.08557224, 8.62394547, 27.931288 ,\n", + " 11.31702137, 9.03355598, 9.82408452, 10.98696327, 8.15972924,\n", + " 8.10580516, 8.6766634 , 9.18826079, 9.91399217, 9.63535714,\n", + " 8.84899211, 8.59690166, 9.08935356, 7.87525439, 9.04824638,\n", + " 10.58436322, 8.05351543, 8.0442934 , 8.51687765, 8.23182964,\n", + " 7.90365982, 9.41734576, 7.82690763, 7.86053801, 8.81060672,\n", + " 15.63083076, 9.12365007, 8.4692018 , 8.38626456, 9.1455934 ,\n", + " 7.9579742 , 8.32254815, 9.60984373, 7.72059083, 9.80256414,\n", + " 8.03569841, 8.56897283, 9.88993764, 9.825032 , 9.10494757,\n", + " 7.96795917, 8.83923078, 8.12920213, 9.14702606, 10.44252062,\n", + " 8.11435223, 11.10698366, 8.54753256, 11.07914209, 8.0072608 ,\n", + " 8.64252162, 7.86998582, 8.16502595, 9.72599697, 8.01553535,\n", + " 8.05236411, 9.4306016 , 8.3510747 , 8.15123487, 7.73660946,\n", + " 8.78807712, 8.42650437, 9.09502602, 67.75333071, 14.179214 ,\n", + " 13.08692336, 14.52568007, 12.39239168, 8.40634942, 8.3893857 ,\n", + " 7.80925822, 8.04524732, 10.61561441, 9.33992386, 8.05361605,\n", + " 8.71911073, 8.13864756, 8.18779135, 8.03402972, 8.20232296,\n", + " 10.52845287, 8.21701574, 9.63750052, 8.16265893, 7.95386362,\n", + " 7.85334754, 7.96290469, 8.1984942 , 8.32950211, 17.0101552 ,\n", + " 14.20266891, 13.09765553, 14.32137418, 8.90045214, 9.79849219,\n", + " 7.7378149 , 8.17814636, 8.0692122 , 8.02391315, 7.73337412,\n", + " 8.24749708, 8.21430159, 8.42469835, 7.93915629, 8.17162681,\n", + " 9.29439068, 8.39062524, 8.05844831, 12.62865376, 8.03868556,\n", + " 8.03020358, 8.72658324, 7.98921943, 10.13008642, 8.36204886,\n", + " 9.8618927 , 8.84138846, 8.26497674, 8.53586483, 11.22441888,\n", + " 8.60046291, 9.52709126, 8.1862669 , 8.47402501, 8.08845234,\n", + " 8.0216496 , 8.25297642, 9.52822161, 8.53732967, 9.20458651,\n", + " 7.84344959, 8.76693869, 9.55830622, 9.32047439, 9.61785316,\n", + " 14.20765901, 13.20616293, 12.79950929, 13.23175693, 10.48755121,\n", + " 7.89634991, 8.62207508, 10.17518067, 9.5078795 , 8.16943836,\n", + " 11.88958383, 8.53581595, 8.78866196, 9.86849713, 8.38485384,\n", + " 7.80456519, 8.7930553 , 8.67091751, 11.64525867, 10.70969439,\n", + " 9.57600379, 7.88863015, 9.16765165, 8.10214615, 8.1002388 ,\n", + " 7.79884577, 7.84607792, 10.70999765, 8.32228923, 8.15903163,\n", + " 8.16516185, 11.13710332, 8.67460465, 8.04933095, 7.92010641,\n", + " 9.71926355, 7.96389985, 8.50223684, 7.80719972, 7.94503832,\n", + " 9.14503789, 8.74866915, 8.32825327, 9.38176489, 8.7043674 ,\n", + " 8.11469626, 8.39300489, 8.52375507, 9.48120856, 9.30481339,\n", + " 11.00180173, 8.00356221, 9.36562443, 11.26503015, 8.29429078,\n", + " 10.5787971 , 8.23888326, 8.25085521, 9.65488529, 10.22367787,\n", + " 8.86958766, 8.67924905, 9.8065629 , 9.98437238, 10.44085979,\n", + " 8.48997521, 13.41537356, 8.53429914, 9.41697288, 8.75000739,\n", + " 8.67022324, 10.65776849, 8.78767824, 29.17240787, 8.29843664,\n", + " 10.48030996, 8.60965252, 9.05648637, 11.23915553, 7.71198177,\n", + " 8.58811665, 11.27894258, 11.26059055, 8.08691239, 9.09145069,\n", + " 8.37398744, 9.33932018, 9.50723815, 14.62887979, 8.08766961,\n", + " 8.1010766 , 8.15962887, 7.86279893, 7.81253982, 8.72090292,\n", + " 28.51810336, 8.20156765, 8.10436082, 9.35736108, 10.11271501,\n", + " 8.28001332, 8.10338402, 7.82260585, 7.74735689, 9.37371802,\n", + " 7.83298874, 8.09861684, 11.44845009, 13.80942464, 13.86787438,\n", + " 12.95256805, 13.5946703 , 9.04438519, 8.42931032, 7.69650388,\n", + " 8.3203001 , 8.93009233, 8.99896145, 10.261621 , 9.76696181,\n", + " 8.42695355, 9.45543766, 8.35829163, 8.19327784, 8.54582119,\n", + " 10.28408813, 9.96855664, 9.4126513 , 8.85548735, 8.37564468,\n", + " 7.85812593, 11.26866746, 11.99777699, 8.90290856, 9.73011518,\n", + " 11.37953544, 9.56070495, 13.08286595, 7.91717887, 8.70709944,\n", + " 8.89286566, 9.43534017, 9.63375568, 9.45693254, 9.41722798,\n", + " 8.95478702, 10.59636545, 9.07217526, 8.91465688, 8.43598938,\n", + " 10.09872103, 8.53826594, 10.51633263, 8.16474724, 9.60920191,\n", + " 8.79985189, 11.08250904, 15.82575488, 13.72388315, 13.76962495,\n", + " 15.5107224 , 12.99527621, 9.55358648, 11.27318692, 10.64224267,\n", + " 9.28194666, 8.15835619, 10.34727526, 9.13943338, 8.47959018,\n", + " 12.95671797, 8.67874169, 9.48093748, 11.13487458, 11.16393185,\n", + " 9.45039058, 9.26687908, 10.83345985, 10.013412 , 12.88114643,\n", + " 8.90868664, 9.11424375, 10.62471223, 10.37447572, 8.56728458,\n", + " 11.44042325, 8.61506176, 14.37763166, 9.26899981, 9.01356244,\n", + " 12.6770153 , 7.95549965, 8.69824529, 8.16541219, 10.80149889,\n", + " 9.85532331, 9.16404986, 11.05029202, 8.95759201, 9.60003638,\n", + " 8.64066339, 11.99474025, 10.88645577, 9.82658648, 8.38357234,\n", + " 8.1931479 , 8.36809587, 8.34779596, 9.29737759, 7.71148348,\n", + " 8.34155583, 8.46944427, 9.46755242, 8.39070392, 9.67334032,\n", + " 9.42819619, 8.90718842, 8.95999622, 17.03638124, 14.13874507,\n", + " 14.17324162, 14.82433629, 10.27358413, 7.75390744, 10.63386297,\n", + " 10.74013877, 9.25264263, 8.88592076, 15.62230277, 8.68499494,\n", + " 7.90613437, 10.8253715 , 9.28829837, 9.96133757, 8.82941794,\n", + " 11.07499003, 9.08565426, 8.76584291, 11.91541052, 9.45269704,\n", + " 9.68554997, 9.76184082, 10.95884109, 9.22084093, 9.07609534,\n", + " 9.72482204, 8.66262245, 8.85580897, 12.12771249, 9.1096139 ,\n", + " 9.55135322, 9.73613167, 12.00068331, 9.63835907, 8.8003633 ,\n", + " 10.78142428, 10.36234426, 8.7075491 , 8.79299307, 10.6836946 ,\n", + " 8.24508142, 9.70224071, 8.64105797, 9.16640019])" ] }, "execution_count": 22, @@ -1073,7 +1163,7 @@ { "data": { "text/plain": [ - "(array([0, 0, 0, 0, 1, 2, 3, 2, 2]),\n", + "(array([ 0, 0, 0, 16, 105, 85, 75, 61, 40]),\n", " array([ 6. , 6.44444444, 6.88888889, 7.33333333, 7.77777778,\n", " 8.22222222, 8.66666667, 9.11111111, 9.55555556, 10. ]))" ] @@ -1098,7 +1188,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 130, time 478.619 seconds: Counter({'Completed': 19})izing': 1})'Running': 1})Queued': 1})\r" + "Final print 24, time 107.859 seconds: Counter({'Completed': 478, 'Failed': 21})izing': 1})Running': 1})\r" ] } ], @@ -1135,8 +1225,6 @@ " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", "\n", - " \n", - " \n", "wait_for_run_list_to_finish(run_list, plot_results=False)" ] }, @@ -1158,8 +1246,8 @@ { "data": { "text/plain": [ - "array([18, 0, 12, 4, 13, 17, 16, 1, 14, 15, 9, 10, 2, 8, 3, 5, 7,\n", - " 11, 6])" + "array([28, 33, 15, 45, 18, 43, 30, 31, 65, 6, 42, 16, 11, 41, 19, 8, 5,\n", + " 2, 64, 34])" ] }, "execution_count": 25, @@ -1170,21 +1258,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ 95.714705 97.874433 101.482868 104.059911 104.543689 104.735191\n", - " 109.463122 109.99054 110.348304 111.175483 116.1721 237.121394\n", - " 238.584713 239.419802 239.850993 242.752749 243.522224 246.953329\n", - " 250.967768]\n", + "[244.173832 244.510378 245.027595 245.540781 247.395535 247.411761\n", + " 247.933416 248.256958 248.468753 249.724234 249.874347 250.013758\n", + " 250.53221 251.10704 251.400594 253.192625 253.421425 253.968411\n", + " 256.888013 260.331917]\n", "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed']\n" + " 'Completed' 'Failed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Failed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Failed' 'Completed']\n" ] }, { "data": { "text/plain": [ - "array([18, 0, 12, 4, 13, 17, 16, 1, 14, 15, 9, 10, 2, 8, 3, 5, 7,\n", - " 11, 6])" + "array([232, 54, 195, 214, 250, 48, 490, 261, 329, 140, 336, 129, 311,\n", + " 223, 226, 370, 319, 254, 197, 85])" ] }, "execution_count": 25, @@ -1195,20 +1283,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ 95.714705 97.874433 101.482868 104.059911 104.543689 104.735191\n", - " 109.463122 109.99054 110.348304 111.175483 116.1721 237.121394\n", - " 238.584713 239.419802 239.850993 242.752749 243.522224 246.953329\n", - " 250.967768]\n", - "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed']\n" + "[92.52469 92.854187 93.127771 93.19945 93.319895 93.372538 93.557287\n", + " 93.579393 93.646901 93.681486 93.890417 94.05724 94.162242 94.165297\n", + " 94.182998 94.263456 94.316783 94.400242 94.406081 94.583321]\n", + "['Completed' 'Completed' 'Completed' 'Completed' 'Failed' 'Completed'\n", + " 'Failed' 'Failed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Failed' 'Completed' 'Completed'\n", + " 'Failed' 'Completed']\n" ] }, { "data": { "text/plain": [ - "(array([0, 0, 2, 9, 0, 0, 0, 0, 0]),\n", + "(array([ 0, 0, 128, 320, 8, 1, 3, 3, 0]),\n", " array([ 50. , 66.66666667, 83.33333333, 100. ,\n", " 116.66666667, 133.33333333, 150. , 166.66666667,\n", " 183.33333333, 200. ]))" From 6260ccad1fa74796e8b340d37b5b3a2fe484fe22 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 11 Dec 2019 15:03:16 -0500 Subject: [PATCH 138/207] BugBash2 Issue #83 and #89: clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 (#88) (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * merge upstream into my fork (#1) * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme * sdk 1.0.76; tested conda env vs docker image; extented readme * removed reference to imaging * minor md formatting * minor md formatting * clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 - Issue #83 * Add Troubleshooting section for DSVM warnings #89 * Add Troubleshooting section for DSVM warnings, plus typo #89 * tested both yml conda env and docker; udated conda yml to have docker sdk * tested both yml conda env and docker; udated conda yml to have docker sdk; added * NVIDIA Tesla K80 (or V100 GPU for NCv2 series) - per Vanja's comment * Update README.md From 35578d7675811ce0f17004fad568243813d10089 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 11 Dec 2019 15:04:40 -0500 Subject: [PATCH 139/207] BugBash2 Issue #83 and #89: clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 (#88) (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * merge upstream into my fork (#1) * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme * sdk 1.0.76; tested conda env vs docker image; extented readme * removed reference to imaging * minor md formatting * minor md formatting * clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 - Issue #83 * Add Troubleshooting section for DSVM warnings #89 * Add Troubleshooting section for DSVM warnings, plus typo #89 * tested both yml conda env and docker; udated conda yml to have docker sdk * tested both yml conda env and docker; udated conda yml to have docker sdk; added * NVIDIA Tesla K80 (or V100 GPU for NCv2 series) - per Vanja's comment * Update README.md From a1ba0641e03d152d44ccba2104e8fc86ca9b5b5f Mon Sep 17 00:00:00 2001 From: Wee Hyong Tok Date: Wed, 11 Dec 2019 16:52:14 -0800 Subject: [PATCH 140/207] Remove related projects on AI Labs --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 2f90f93f..1aa3a069 100644 --- a/README.md +++ b/README.md @@ -268,9 +268,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | | **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | -# Related projects -[Microsoft AI Labs Github](https://aka.ms/ai-labs) Find other Best Practice projects, and Azure AI design patterns in our central repository. # Troubleshooting From ed3a6d1d5e566bdc59973b8bb32203b6307babbe Mon Sep 17 00:00:00 2001 From: Wee Hyong Tok Date: Wed, 11 Dec 2019 16:58:41 -0800 Subject: [PATCH 141/207] Added a reference to Azure machine learning (#115) Added a reference to Azure machine learning to show how folks can get started with using Azure Machine Learning --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1aa3a069..6394e2ee 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ There are two ways to get started with the DeepSeismic codebase, which currently If you run into any problems, chances are your problem has already been solved in the [Troubleshooting](#troubleshooting) section. +### Azure Machine Learning +[Azure Machine Learning](https://docs.microsoft.com/en-us/azure/machine-learning/) enables you to train and deploy your machine learning models and pipelines at scale, ane leverage open-source Python frameworks, such as PyTorch, TensorFlow, and scikit-learn. If you are looking at getting started with using the code in this repository with Aazure Machine Learning, refer to [Azure Machine Learning How-to](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml) to get started. + ## Interpretation For seismic interpretation, the repository consists of extensible machine learning pipelines, that shows how you can leverage state-of-the-art segmentation algorithms (UNet, SEResNET, HRNet) for seismic interpretation, and also benchmarking results from running these algorithms using various seismic datasets (Dutch F3, and Penobscot). From f8d67f537a697dd924b2d440dc435d7e40ff5c3b Mon Sep 17 00:00:00 2001 From: Wee Hyong Tok Date: Wed, 11 Dec 2019 17:00:11 -0800 Subject: [PATCH 142/207] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6394e2ee..473f47bf 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ There are two ways to get started with the DeepSeismic codebase, which currently If you run into any problems, chances are your problem has already been solved in the [Troubleshooting](#troubleshooting) section. ### Azure Machine Learning -[Azure Machine Learning](https://docs.microsoft.com/en-us/azure/machine-learning/) enables you to train and deploy your machine learning models and pipelines at scale, ane leverage open-source Python frameworks, such as PyTorch, TensorFlow, and scikit-learn. If you are looking at getting started with using the code in this repository with Aazure Machine Learning, refer to [Azure Machine Learning How-to](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml) to get started. +[Azure Machine Learning](https://docs.microsoft.com/en-us/azure/machine-learning/) enables you to train and deploy your machine learning models and pipelines at scale, ane leverage open-source Python frameworks, such as PyTorch, TensorFlow, and scikit-learn. If you are looking at getting started with using the code in this repository with Azure Machine Learning, refer to [Azure Machine Learning How-to](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml) to get started. ## Interpretation For seismic interpretation, the repository consists of extensible machine learning pipelines, that shows how you can leverage state-of-the-art segmentation algorithms (UNet, SEResNET, HRNet) for seismic interpretation, and also benchmarking results from running these algorithms using various seismic datasets (Dutch F3, and Penobscot). From de211f1b398953317b4971c0c28b814d47376ee9 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Thu, 12 Dec 2019 09:52:44 -0500 Subject: [PATCH 143/207] update fork from upstream (#4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed merge conflict resolution in LICENSE * BugBash2 Issue #83 and #89: clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 (#88) * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * merge upstream into my fork (#1) * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme * sdk 1.0.76; tested conda env vs docker image; extented readme * removed reference to imaging * minor md formatting * minor md formatting * clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 - Issue #83 * Add Troubleshooting section for DSVM warnings #89 * Add Troubleshooting section for DSVM warnings, plus typo #89 * tested both yml conda env and docker; udated conda yml to have docker sdk * tested both yml conda env and docker; udated conda yml to have docker sdk; added * NVIDIA Tesla K80 (or V100 GPU for NCv2 series) - per Vanja's comment * Update README.md * Remove related projects on AI Labs * Added a reference to Azure machine learning (#115) Added a reference to Azure machine learning to show how folks can get started with using Azure Machine Learning * Update README.md --- LICENSE | 2 +- README.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index cbf2717d..236805ac 100644 --- a/LICENSE +++ b/LICENSE @@ -19,4 +19,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE - + \ No newline at end of file diff --git a/README.md b/README.md index 2f90f93f..473f47bf 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ There are two ways to get started with the DeepSeismic codebase, which currently If you run into any problems, chances are your problem has already been solved in the [Troubleshooting](#troubleshooting) section. +### Azure Machine Learning +[Azure Machine Learning](https://docs.microsoft.com/en-us/azure/machine-learning/) enables you to train and deploy your machine learning models and pipelines at scale, ane leverage open-source Python frameworks, such as PyTorch, TensorFlow, and scikit-learn. If you are looking at getting started with using the code in this repository with Azure Machine Learning, refer to [Azure Machine Learning How-to](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml) to get started. + ## Interpretation For seismic interpretation, the repository consists of extensible machine learning pipelines, that shows how you can leverage state-of-the-art segmentation algorithms (UNet, SEResNET, HRNet) for seismic interpretation, and also benchmarking results from running these algorithms using various seismic datasets (Dutch F3, and Penobscot). @@ -268,9 +271,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | | **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | -# Related projects -[Microsoft AI Labs Github](https://aka.ms/ai-labs) Find other Best Practice projects, and Azure AI design patterns in our central repository. # Troubleshooting From ffb1b07ddfe79d2dc14e0fdc7fe9390b55e63985 Mon Sep 17 00:00:00 2001 From: Wee Hyong Tok Date: Thu, 12 Dec 2019 17:49:43 -0800 Subject: [PATCH 144/207] Update AUTHORS.md (#117) --- AUTHORS.md | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index f6dc11f3..2fa198ff 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,25 +1,30 @@ -Contributors to DeepSeismic -=========================== +Contributor +============ -TODO: add why we're working on Deep Learning for Seismic Imaging and Interpretation. - -Maintainers (sorted alphabetically) ---------------------------------------- -Maintainers are actively supporting the project and have made substantial contributions to the repository.
-They have admin access to the repo and provide support reviewing issues and pull requests. - -* **[Name](github account URL)** - * what you're responsible for #1 - * what you're responsible for #2 - * etc +All names are sorted alphabetically by last name. +Contributors, please add your name to the list when you submit a patch to the project. Contributors (sorted alphabetically) ------------------------------------- - To contributors: please add your name to the list when you submit a patch to the project. -* **[Name](github account URL)** - * contribution #1 - * contribution #2 - * etc +* Ashish Bhatia +* George Iordanescu +* Ilia Karmanov +* Mathew Salvaris +* Max Kaznady +* Vanja Paunic + + +## How to be a contributor to the repository +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From aefa70e35a1305fd144d81c95cca35d6793d2026 Mon Sep 17 00:00:00 2001 From: Wee Hyong Tok Date: Thu, 12 Dec 2019 17:52:34 -0800 Subject: [PATCH 145/207] Update AUTHORS.md (#118) --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 2fa198ff..b98f5b0d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -9,6 +9,7 @@ Contributors (sorted alphabetically) To contributors: please add your name to the list when you submit a patch to the project. * Ashish Bhatia +* Daniel Ciborowski * George Iordanescu * Ilia Karmanov * Mathew Salvaris From cb11052c5cdd540fc5adce130fa093f7aed683b0 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Fri, 13 Dec 2019 11:35:44 -0500 Subject: [PATCH 146/207] pre-release items (#119) * added README documentation per bug bush feedback * added missing tests * closing out multiple post bug bash issues with single PR * new badges in README * cleared notebook output * notebooks links * fixed bad merge --- README.md | 29 +- ..._block_training_and_evaluation_local.ipynb | 288 +++--------------- .../HRNet_Penobscot_demo_notebook.ipynb | 6 +- 3 files changed, 57 insertions(+), 266 deletions(-) diff --git a/README.md b/README.md index 473f47bf..a82fb2d5 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,17 @@ DeepSeismic currently focuses on Seismic Interpretation (3D segmentation aka fac ### Quick Start There are two ways to get started with the DeepSeismic codebase, which currently focuses on Interpretation: -- if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/staging/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) +- if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/master/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) - to actually run the code, you'll need to set up a compute environment (which includes setting up a GPU-enabled Linux VM and downloading the appropriate Anaconda Python packages) and download the datasets which you'd like to work with - detailed steps for doing this are provided in the next `Interpretation` section below. If you run into any problems, chances are your problem has already been solved in the [Troubleshooting](#troubleshooting) section. +### Pre-run notebooks + +Notebooks stored in the repository have output intentionally displaced - you can find full auto-generated versions of the notebooks here: +- [HRNet Penobscot demo](https://deepseismicstore.blob.core.windows.net/shared/HRNet_Penobscot_demo_notebook.ipynb?sp=r&st=2019-12-13T05:11:15Z&se=2019-12-13T13:11:15Z&spr=https&sv=2019-02-02&sr=b&sig=UFwueAhZcJn8o7g1nzD4GGS7lKv9lHIJXJb%2B7kbyUZc%3D) +- [Dutch F3 dataset](https://deepseismicstore.blob.core.windows.net/shared/F3_block_training_and_evaluation_local.ipynb?sp=r&st=2019-12-13T05:10:17Z&se=2019-12-13T13:10:17Z&spr=https&sv=2019-02-02&sr=b&sig=UUDCulVUddhqfxN0%2FqcdLZ7DmcGnIYk0j%2BlM0EN8WiM%3D) + ### Azure Machine Learning [Azure Machine Learning](https://docs.microsoft.com/en-us/azure/machine-learning/) enables you to train and deploy your machine learning models and pipelines at scale, ane leverage open-source Python frameworks, such as PyTorch, TensorFlow, and scikit-learn. If you are looking at getting started with using the code in this repository with Azure Machine Learning, refer to [Azure Machine Learning How-to](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml) to get started. @@ -79,7 +85,7 @@ This repository provides examples on how to run seismic interpretation on two pu Please make sure you have enough disk space to download either dataset. -We have experiments and notebooks which use either one dataset or the other. Depending on which experiment/notebook you want to run you'll need to download the corresponding dataset. We suggest you start by looking at [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/staging/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) which requires the Penobscot dataset. +We have experiments and notebooks which use either one dataset or the other. Depending on which experiment/notebook you want to run you'll need to download the corresponding dataset. We suggest you start by looking at [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/master/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) which requires the Penobscot dataset. #### Penobscot To download the Penobscot dataset run the [download_penobscot.sh](scripts/download_penobscot.sh) script, e.g. @@ -264,13 +270,12 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope ## Build Status | Build | Branch | Status | | --- | --- | --- | -| **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=staging) | -| **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=110&branchName=master) | -| **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=staging) | -| **Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=111&branchName=master) | -| **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=staging) | -| **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=120&branchName=master) | - +| **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=124&branchName=staging) | +| **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=124&branchName=master) | +| **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=staging) | +| **Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=master) | +| **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=staging) | +| **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=master) | # Troubleshooting @@ -302,10 +307,12 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l
Data Science Virtual Machine conda package installation warnings - It could happen that while creating the conda environment defined by environment/anaconda/local/environment.yml on an Ubuntu DSVM, one can get multiple warnings like this: + It could happen that while creating the conda environment defined by environment/anaconda/local/environment.yml on an Ubuntu DSVM, one can get multiple warnings like so: + ``` WARNING conda.gateways.disk.delete:unlink_or_rename_to_trash(140): Could not remove or rename /anaconda/pkgs/ipywidgets-7.5.1-py_0/site-packages/ipywidgets-7.5.1.dist-info/LICENSE. Please remove this file manually (you may need to reboot to free file handles) + ``` - If that happens, similar to instructions above, stop the conda environment creation (type ```Ctrl+C```) and then change recursively the ownership /anaconda directory from root to current user, by running this command: + If this happens, similar to instructions above, stop the conda environment creation (type ```Ctrl+C```) and then change recursively the ownership /anaconda directory from root to current user, by running this command: ```bash sudo chown -R $USER /anaconda diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 320f40a3..611d0598 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -162,71 +162,9 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Configuration loaded. Please check that the DATASET.ROOT:/data/dutchf3 points to your data location.\n", - "To modify any of the options, please edit the configuration file ./configs/patch_deconvnet_skip.yaml and reload. \n", - "\n", - "CUDNN:\n", - " BENCHMARK: True\n", - " DETERMINISTIC: False\n", - " ENABLED: True\n", - "DATASET:\n", - " CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852]\n", - " NUM_CLASSES: 6\n", - " ROOT: /data/dutchf3\n", - "GPUS: (0,)\n", - "LOG_CONFIG: logging.conf\n", - "LOG_DIR: log\n", - "MODEL:\n", - " IN_CHANNELS: 1\n", - " NAME: patch_deconvnet_skip\n", - "OUTPUT_DIR: output\n", - "PRINT_FREQ: 50\n", - "SEED: 2019\n", - "TEST:\n", - " CROSSLINE: True\n", - " INLINE: True\n", - " MODEL_PATH: /data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth\n", - " POST_PROCESSING:\n", - " CROP_PIXELS: 0\n", - " SIZE: 99\n", - " SPLIT: test1\n", - " TEST_STRIDE: 10\n", - "TRAIN:\n", - " AUGMENTATION: True\n", - " AUGMENTATIONS:\n", - " PAD:\n", - " HEIGHT: 99\n", - " WIDTH: 99\n", - " RESIZE:\n", - " HEIGHT: 99\n", - " WIDTH: 99\n", - " BATCH_SIZE_PER_GPU: 64\n", - " BEGIN_EPOCH: 0\n", - " DEPTH: none\n", - " END_EPOCH: 100\n", - " MAX_LR: 0.02\n", - " MEAN: 0.0009997\n", - " MIN_LR: 0.001\n", - " MODEL_DIR: models\n", - " MOMENTUM: 0.9\n", - " PATCH_SIZE: 99\n", - " SNAPSHOTS: 5\n", - " STD: 0.20977\n", - " STRIDE: 50\n", - " WEIGHT_DECAY: 0.0001\n", - "VALIDATION:\n", - " BATCH_SIZE_PER_GPU: 512\n", - "WORKERS: 4\n" - ] - } - ], + "outputs": [], "source": [ "with open(CONFIG_FILE, \"rt\") as f_read:\n", " config = yacs.config.load_cfg(f_read)\n", @@ -245,7 +183,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "tags": [ "parameters" @@ -267,7 +205,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -292,19 +230,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of inline slices: 401\n", - "Number of crossline slices: 701\n", - "Depth dimension : 255\n" - ] - } - ], + "outputs": [], "source": [ "# Load training data and labels\n", "train_seismic = np.load(path.join(dataset_root, \"train/train_seismic.npy\"))\n", @@ -317,24 +245,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "1aadea98bda8458fbc03782571b1d4b7", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Viewer(geometries=[], gradient_opacity=0.22, point_sets=[], rendered_image=" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "idx = 100\n", "x_in = train_seismic[idx, :, :].swapaxes(0, 1)\n", @@ -381,22 +281,9 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "x_cr = train_seismic[:, idx, :].swapaxes(0, 1)\n", "x_crl = train_labels[:, idx, :].swapaxes(0, 1)\n", @@ -413,17 +300,9 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-12-09 04:08:24,354 - cv_lib.utils - INFO - logging.conf configuration file was loaded.\n" - ] - } - ], + "outputs": [], "source": [ "# Set up logging\n", "load_log_configuration(config.LOG_CONFIG)\n", @@ -443,7 +322,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -492,17 +371,9 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "This no longer pads the volume\n" - ] - } - ], + "outputs": [], "source": [ "TrainPatchLoader = get_patch_loader(config)\n", "\n", @@ -542,7 +413,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -601,20 +472,9 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# create training engine\n", "trainer = create_supervised_trainer(\n", @@ -652,7 +512,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -669,37 +529,9 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-12-09 04:08:27,209 - ignite.engine.engine.Engine - INFO - Engine run starting with max_epochs=1.\n", - "2019-12-09 04:08:27,211 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.02]\n", - "2019-12-09 04:08:31,265 - ignite.engine.engine.Engine - INFO - Epoch[1] Complete. Time taken: 00:00:04\n", - "2019-12-09 04:08:32,319 - ignite.engine.engine.Engine - INFO - Engine run complete. Time taken 00:00:05\n" - ] - }, - { - "data": { - "text/plain": [ - "State:\n", - "\toutput: \n", - "\tbatch: \n", - "\tdataloader: \n", - "\tmax_epochs: 1\n", - "\tmetrics: \n", - "\titeration: 3\n", - "\tepoch: 1" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "trainer.run(train_loader, max_epochs=max_epochs)" ] @@ -735,7 +567,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -745,7 +577,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -766,7 +598,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -827,19 +659,9 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-12-09 04:08:36,547 - __main__ - INFO - split: test1, section: 0\n", - "2019-12-09 04:08:41,543 - __main__ - INFO - split: test1, section: 1\n", - "2019-12-09 04:08:42,067 - __main__ - INFO - split: test1, section: 2\n" - ] - } - ], + "outputs": [], "source": [ "CLASS_NAMES = [\n", " \"upper_ns\",\n", @@ -898,34 +720,9 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Pixel Acc: 0.522\n", - " upper_ns_accuracy 0.000\n", - " middle_ns_accuracy 0.000\n", - " lower_ns_accuracy 0.999\n", - " rijnland_chalk_accuracy 0.000\n", - " scruff_accuracy 0.001\n", - " zechstein_accuracy nan\n", - "Mean Class Acc: 0.200\n", - "Freq Weighted IoU: 0.273\n", - "Mean IoU: 0.105\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "invalid value encountered in true_divide\n", - "invalid value encountered in true_divide\n" - ] - } - ], + "outputs": [], "source": [ "# get scores\n", "score, _ = running_metrics_split.get_scores()\n", @@ -956,24 +753,11 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": { "scrolled": false }, - "outputs": [ - { - "data": { - "image/png": "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AACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAA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0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAnihKmAAAgAElEQVQATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACg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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "fig = plt.figure(figsize=(15,50))\n", "\n", @@ -1002,9 +786,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seismic-interpretation", + "display_name": "Python 3", "language": "python", - "name": "seismic-interpretation" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1016,7 +800,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index cf6366fd..3529e426 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -632,9 +632,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seismic-interpretation", + "display_name": "Python 3", "language": "python", - "name": "seismic-interpretation" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -646,7 +646,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.5.2" } }, "nbformat": 4, From ec9dea35fac425f2a7a059e14302e67dd12c51cb Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Fri, 13 Dec 2019 17:01:37 -0500 Subject: [PATCH 147/207] forked branch name is misleading. (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * merge upstream into my fork (#1) * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme * sdk 1.0.76; tested conda env vs docker image; extented readme * removed reference to imaging * minor md formatting * minor md formatting * clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 - Issue #83 * Add Troubleshooting section for DSVM warnings #89 * Add Troubleshooting section for DSVM warnings, plus typo #89 * tested both yml conda env and docker; udated conda yml to have docker sdk * tested both yml conda env and docker; udated conda yml to have docker sdk; added * NVIDIA Tesla K80 (or V100 GPU for NCv2 series) - per Vanja's comment * Update README.md * BugBash2 Issue #83 and #89: clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 (#88) (#2) * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * merge upstream into my fork (#1) * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme * sdk 1.0.76; tested conda env vs docker image; extented readme * removed reference to imaging * minor md formatting * minor md formatting * clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 - Issue #83 * Add Troubleshooting section for DSVM warnings #89 * Add Troubleshooting section for DSVM warnings, plus typo #89 * tested both yml conda env and docker; udated conda yml to have docker sdk * tested both yml conda env and docker; udated conda yml to have docker sdk; added * NVIDIA Tesla K80 (or V100 GPU for NCv2 series) - per Vanja's comment * Update README.md * BugBash2 Issue #83 and #89: clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 (#88) (#3) * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * merge upstream into my fork (#1) * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme * sdk 1.0.76; tested conda env vs docker image; extented readme * removed reference to imaging * minor md formatting * minor md formatting * clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 - Issue #83 * Add Troubleshooting section for DSVM warnings #89 * Add Troubleshooting section for DSVM warnings, plus typo #89 * tested both yml conda env and docker; udated conda yml to have docker sdk * tested both yml conda env and docker; udated conda yml to have docker sdk; added * NVIDIA Tesla K80 (or V100 GPU for NCv2 series) - per Vanja's comment * Update README.md * update fork from upstream (#4) * fixed merge conflict resolution in LICENSE * BugBash2 Issue #83 and #89: clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 (#88) * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * azureml sdk 1.0.74; foxed a few issues around ACR access; added nb 030 for scalability testing * merge upstream into my fork (#1) * MINOR: addressing broken F3 download link (#73) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * fixed link for F3 download * MINOR: python version fix to 3.6.7 (#72) * Adding system requirements in README (#74) * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * DOC: forking dislaimer and new build names. (#9) * Updating README.md with introduction material (#10) * Update README with introduction to DeepSeismic Add intro material for DeepSeismic * Adding logo file * Adding image to readme * Update README.md * Updates the 3D visualisation to use itkwidgets (#11) * Updates notebook to use itkwidgets for interactive visualisation * Adds jupytext to pre-commit (#12) * Add jupytext * Adds demo notebook for HRNet (#13) * Adding TF 2.0 to allow for tensorboard vis in notebooks * Modifies hrnet config for notebook * Add HRNet notebook for demo * Updates HRNet notebook and tidies F3 * removed my username references (#15) * moving 3D models into contrib folder (#16) * Weetok (#17) * Update it to include sections for imaging * Update README.md * Update README.md * added system requirements to readme * sdk 1.0.76; tested conda env vs docker image; extented readme * removed reference to imaging * minor md formatting * minor md formatting * clarify which DSVM we want to use - Ubuntu GPU-enabled VM, preferably NC12 - Issue #83 * Add Troubleshooting section for DSVM warnings #89 * Add Troubleshooting section for DSVM warnings, plus typo #89 * tested both yml conda env and docker; udated conda yml to have docker sdk * tested both yml conda env and docker; udated conda yml to have docker sdk; added * NVIDIA Tesla K80 (or V100 GPU for NCv2 series) - per Vanja's comment * Update README.md * Remove related projects on AI Labs * Added a reference to Azure machine learning (#115) Added a reference to Azure machine learning to show how folks can get started with using Azure Machine Learning * Update README.md * abandoned nb_conda, switched to ipykernel; test parallelization in AzureML is slow, switched nodes/jobs from 1e4/2e4 to 1e2/2e2 * -> test parallelization in AzureML is slow, switched nodes/jobs from 1e4/2e4 to 1e2/2e2 * authors and latest parallel via AzureML test * added exported conda env file * latest read.me --- AUTHORS.md | 3 +- contrib/fwi/azureml_devito/README.md | 13 +- .../fwi_dev_conda_environment.yml | 2 +- .../fwi_dev_conda_environment_exported.yml | 211 ++++++++++++ ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 25 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 314 ++++++++++-------- ..._block_training_and_evaluation_local.ipynb | 288 ++++++++++++++-- .../HRNet_Penobscot_demo_notebook.ipynb | 6 +- 8 files changed, 673 insertions(+), 189 deletions(-) create mode 100644 contrib/fwi/azureml_devito/fwi_dev_conda_environment_exported.yml diff --git a/AUTHORS.md b/AUTHORS.md index b98f5b0d..c0011f3e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -4,6 +4,7 @@ Contributor All names are sorted alphabetically by last name. Contributors, please add your name to the list when you submit a patch to the project. + Contributors (sorted alphabetically) ------------------------------------- To contributors: please add your name to the list when you submit a patch to the project. @@ -12,9 +13,9 @@ To contributors: please add your name to the list when you submit a patch to the * Daniel Ciborowski * George Iordanescu * Ilia Karmanov -* Mathew Salvaris * Max Kaznady * Vanja Paunic +* Mathew Salvaris ## How to be a contributor to the repository diff --git a/contrib/fwi/azureml_devito/README.md b/contrib/fwi/azureml_devito/README.md index af99ddc7..80b11e85 100755 --- a/contrib/fwi/azureml_devito/README.md +++ b/contrib/fwi/azureml_devito/README.md @@ -19,22 +19,31 @@ conda env create -f fwi_dev_conda_environment.yml ``` -then, one can see the created environment within the list of available environments and activate it: +then, one can see the created environment within the list of available environments and export it as a .yml file: ``` conda env list +conda env export --name fwi_dev_conda_environment -f ./contrib/fwi/azureml_devito/fwi_dev_conda_environment_exported.yml + +``` +The created conda environment needs to be activated, followed by the installation of its corresponding IPython kernel: +``` conda activate fwi_dev_conda_environment +python -m ipykernel install --user --name fwi_dev_conda_environment --display-name "fwi_dev_conda_environment Python" ``` Finally, start Jupyter notebook from within the activated environment: ``` jupyter notebook ``` +One can then choose the __fwi_dev_conda_environment Python__ kernel defined above either when a notebook is opened for the first time, or by using the "Kernel/Change kernel" notebook menu. + + [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) is also used to create an ACR in notebook 000_Setup_GeophysicsTutorial_FWI_Azure_devito, and then push and pull docker images. One can also create the ACR via Azure [portal](https://azure.microsoft.com/). ### Run devito in Azure The devito fwi examples are run in AzuremL using 4 notebooks: - - ```000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb```: sets up Azure resources (like resource groups, AzureML [workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-workspace)). + - ```000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb```: sets up Azure resources (like resource groups, AzureML [workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-workspace), Azure (docker) [container registry](https://azure.microsoft.com/en-us/services/container-registry/)). - ```010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb```: Creates a custom docker file and the associated image that contains ```devito``` [github repository](https://github.com/opesci/devito.git) (including devito fwi tutorial [notebooks](https://github.com/opesci/devito/tree/master/examples/seismic/tutorials)) and runs the official devito install [tests](https://github.com/opesci/devito/tree/master/tests). - ```020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb```: shows how the devito fwi tutorial [notebooks](https://github.com/opesci/devito/tree/master/examples/seismic/tutorials) can be run in AzureML using Azure Machine Learning [generic](https://docs.microsoft.com/en-us/python/api/azureml-train-core/azureml.train.estimator?view=azure-ml-py) [estimators](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-train-ml-models) with custom docker images. FWI computation takes place on a managed AzureML [remote compute cluster](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets). diff --git a/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml index eedd1197..d3757663 100755 --- a/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml +++ b/contrib/fwi/azureml_devito/fwi_dev_conda_environment.yml @@ -7,7 +7,7 @@ dependencies: - python=3.7 - numpy - notebook - - nb_conda + - ipykernel #nb_conda - scikit-learn - pip - pip: diff --git a/contrib/fwi/azureml_devito/fwi_dev_conda_environment_exported.yml b/contrib/fwi/azureml_devito/fwi_dev_conda_environment_exported.yml new file mode 100644 index 00000000..ac0566d3 --- /dev/null +++ b/contrib/fwi/azureml_devito/fwi_dev_conda_environment_exported.yml @@ -0,0 +1,211 @@ +name: fwi_dev_conda_environment +channels: + - anaconda + - defaults +dependencies: + - attrs=19.3.0=py_0 + - backcall=0.1.0=py37_0 + - blas=1.0=mkl + - bleach=3.1.0=py37_0 + - ca-certificates=2019.11.27=0 + - certifi=2019.11.28=py37_0 + - decorator=4.4.1=py_0 + - defusedxml=0.6.0=py_0 + - entrypoints=0.3=py37_0 + - gmp=6.1.2=hb3b607b_0 + - importlib_metadata=1.1.0=py37_0 + - intel-openmp=2019.5=281 + - ipykernel=5.1.3=py37h39e3cac_0 + - ipython=7.10.1=py37h39e3cac_0 + - ipython_genutils=0.2.0=py37_0 + - jedi=0.15.1=py37_0 + - jinja2=2.10.3=py_0 + - joblib=0.14.0=py_0 + - jsonschema=3.2.0=py37_0 + - jupyter_client=5.3.4=py37_0 + - jupyter_core=4.6.1=py37_0 + - libedit=3.1.20181209=hc058e9b_0 + - libffi=3.2.1=h4deb6c0_3 + - libgcc-ng=9.1.0=hdf63c60_0 + - libgfortran-ng=7.3.0=hdf63c60_0 + - libsodium=1.0.16=h1bed415_0 + - libstdcxx-ng=9.1.0=hdf63c60_0 + - markupsafe=1.1.1=py37h7b6447c_0 + - mistune=0.8.4=py37h7b6447c_0 + - mkl=2019.5=281 + - mkl-service=2.3.0=py37he904b0f_0 + - mkl_fft=1.0.15=py37ha843d7b_0 + - mkl_random=1.1.0=py37hd6b4f25_0 + - more-itertools=7.2.0=py37_0 + - nbconvert=5.6.1=py37_0 + - nbformat=4.4.0=py37_0 + - ncurses=6.1=he6710b0_1 + - notebook=6.0.2=py37_0 + - openssl=1.1.1=h7b6447c_0 + - pandoc=2.2.3.2=0 + - pandocfilters=1.4.2=py37_1 + - parso=0.5.1=py_0 + - pexpect=4.7.0=py37_0 + - pickleshare=0.7.5=py37_0 + - pip=19.3.1=py37_0 + - prometheus_client=0.7.1=py_0 + - prompt_toolkit=3.0.2=py_0 + - ptyprocess=0.6.0=py37_0 + - pygments=2.5.2=py_0 + - pyrsistent=0.15.6=py37h7b6447c_0 + - python=3.7.5=h0371630_0 + - python-dateutil=2.8.1=py_0 + - pyzmq=18.1.0=py37he6710b0_0 + - readline=7.0=h7b6447c_5 + - send2trash=1.5.0=py37_0 + - setuptools=42.0.2=py37_0 + - six=1.13.0=py37_0 + - sqlite=3.30.1=h7b6447c_0 + - terminado=0.8.3=py37_0 + - testpath=0.4.4=py_0 + - tk=8.6.8=hbc83047_0 + - tornado=6.0.3=py37h7b6447c_0 + - traitlets=4.3.3=py37_0 + - wcwidth=0.1.7=py37_0 + - webencodings=0.5.1=py37_1 + - xz=5.2.4=h14c3975_4 + - zeromq=4.3.1=he6710b0_3 + - zipp=0.6.0=py_0 + - zlib=1.2.11=h7b6447c_3 + - pip: + - adal==1.2.2 + - ansiwrap==0.8.4 + - applicationinsights==0.11.9 + - azure-common==1.1.23 + - azure-core==1.1.1 + - azure-datalake-store==0.0.48 + - azure-graphrbac==0.61.1 + - azure-mgmt-authorization==0.60.0 + - azure-mgmt-containerregistry==2.8.0 + - azure-mgmt-keyvault==2.0.0 + - azure-mgmt-resource==7.0.0 + - azure-mgmt-storage==7.0.0 + - azure-storage-blob==12.1.0 + - azureml-automl-core==1.0.76 + - azureml-automl-runtime==1.0.76.1 + - azureml-contrib-notebook==1.0.76 + - azureml-core==1.0.76 + - azureml-dataprep==1.1.33 + - azureml-dataprep-native==13.1.0 + - azureml-defaults==1.0.76 + - azureml-explain-model==1.0.76 + - azureml-interpret==1.0.76 + - azureml-model-management-sdk==1.0.1b6.post1 + - azureml-pipeline==1.0.76 + - azureml-pipeline-core==1.0.76 + - azureml-pipeline-steps==1.0.76 + - azureml-sdk==1.0.76 + - azureml-telemetry==1.0.76 + - azureml-train==1.0.76 + - azureml-train-automl==1.0.76 + - azureml-train-automl-client==1.0.76 + - azureml-train-automl-runtime==1.0.76.1 + - azureml-train-core==1.0.76 + - azureml-train-restclients-hyperdrive==1.0.76 + - azureml-widgets==1.0.76 + - backports-tempfile==1.0 + - backports-weakref==1.0.post1 + - boto==2.49.0 + - boto3==1.10.37 + - botocore==1.13.37 + - cffi==1.13.2 + - chardet==3.0.4 + - click==7.0 + - cloudpickle==1.2.2 + - configparser==3.7.4 + - contextlib2==0.6.0.post1 + - cryptography==2.8 + - cycler==0.10.0 + - cython==0.29.14 + - dill==0.3.1.1 + - distro==1.4.0 + - docker==4.1.0 + - docutils==0.15.2 + - dotnetcore2==2.1.11 + - fire==0.2.1 + - flake8==3.7.9 + - flask==1.0.3 + - fusepy==3.0.1 + - future==0.18.2 + - gensim==3.8.1 + - gunicorn==19.9.0 + - idna==2.8 + - imageio==2.6.1 + - interpret-community==0.2.3 + - interpret-core==0.1.19 + - ipywidgets==7.5.1 + - isodate==0.6.0 + - itsdangerous==1.1.0 + - jeepney==0.4.1 + - jmespath==0.9.4 + - json-logging-py==0.2 + - jsonform==0.0.2 + - jsonpickle==1.2 + - jsonsir==0.0.2 + - keras2onnx==1.6.0 + - kiwisolver==1.1.0 + - liac-arff==2.4.0 + - lightgbm==2.3.0 + - matplotlib==3.1.2 + - mccabe==0.6.1 + - msrest==0.6.10 + - msrestazure==0.6.2 + - ndg-httpsclient==0.5.1 + - networkx==2.4 + - nimbusml==1.6.1 + - numpy==1.16.2 + - oauthlib==3.1.0 + - onnx==1.6.0 + - onnxconverter-common==1.6.0 + - onnxmltools==1.4.1 + - packaging==19.2 + - pandas==0.23.4 + - papermill==1.2.1 + - pathspec==0.6.0 + - patsy==0.5.1 + - pillow==6.2.1 + - pmdarima==1.1.1 + - protobuf==3.11.1 + - psutil==5.6.7 + - pyasn1==0.4.8 + - pycodestyle==2.5.0 + - pycparser==2.19 + - pyflakes==2.1.1 + - pyjwt==1.7.1 + - pyopenssl==19.1.0 + - pyparsing==2.4.5 + - python-dotenv==0.10.3 + - python-easyconfig==0.1.7 + - pytz==2019.3 + - pywavelets==1.1.1 + - pyyaml==5.2 + - requests==2.22.0 + - requests-oauthlib==1.3.0 + - resource==0.2.1 + - ruamel-yaml==0.15.89 + - s3transfer==0.2.1 + - scikit-image==0.16.2 + - scikit-learn==0.20.3 + - scipy==1.1.0 + - secretstorage==3.1.1 + - shap==0.29.3 + - skl2onnx==1.4.9 + - sklearn-pandas==1.7.0 + - smart-open==1.9.0 + - statsmodels==0.10.2 + - tenacity==6.0.0 + - termcolor==1.1.0 + - textwrap3==0.9.2 + - tqdm==4.40.2 + - typing-extensions==3.7.4.1 + - urllib3==1.25.7 + - websocket-client==0.56.0 + - werkzeug==0.16.0 + - wheel==0.30.0 + - widgetsnbextension==3.5.1 +prefix: /data/anaconda/envs/fwi_dev_conda_environment diff --git a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index 62683429..a3679b99 100755 --- a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -82,7 +82,7 @@ { "data": { "text/plain": [ - "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" + "'Linux-4.15.0-1064-azure-x86_64-with-debian-stretch-sid'" ] }, "execution_count": 3, @@ -861,19 +861,24 @@ "metadata": {}, "outputs": [], "source": [ +<<<<<<< HEAD + "import subprocess\n", + "cli_command = 'az acr credential show -n '+acr_name\n", +======= "# create_ACR_FLAG=False\n", "if create_ACR_FLAG:\n", " import subprocess\n", " cli_command = 'az acr credential show -n '+acr_name\n", +>>>>>>> de211f1b398953317b4971c0c28b814d47376ee9 "\n", - " acr_username = subprocess.Popen(cli_command+' --query username',shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE).\\\n", - " communicate()[0].decode(\"utf-8\").split()[0].strip('\\\"')\n", + "acr_username = subprocess.Popen(cli_command+' --query username',shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE).\\\n", + "communicate()[0].decode(\"utf-8\").split()[0].strip('\\\"')\n", "\n", - " acr_password = subprocess.Popen(cli_command+' --query passwords[0].value',shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE).\\\n", - " communicate()[0].decode(\"utf-8\").split()[0].strip('\\\"')\n", + "acr_password = subprocess.Popen(cli_command+' --query passwords[0].value',shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE).\\\n", + "communicate()[0].decode(\"utf-8\").split()[0].strip('\\\"')\n", "\n", - " dotenv.set_key(dotenv_file_path, 'ACR_PASSWORD', acr_password)\n", - " dotenv.set_key(dotenv_file_path, 'ACR_USERNAME', acr_username)" + "response = dotenv.set_key(dotenv_file_path, 'ACR_PASSWORD', acr_password)\n", + "response = dotenv.set_key(dotenv_file_path, 'ACR_USERNAME', acr_username)" ] }, { @@ -901,9 +906,15 @@ ], "metadata": { "kernelspec": { +<<<<<<< HEAD + "display_name": "fwi_dev_conda_environment Python", + "language": "python", + "name": "fwi_dev_conda_environment" +======= "display_name": "Python [conda env:fwi_dev_conda_environment] *", "language": "python", "name": "conda-env-fwi_dev_conda_environment-py" +>>>>>>> de211f1b398953317b4971c0c28b814d47376ee9 }, "language_info": { "codemirror_mode": { diff --git a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb index d8a5448a..9e246e53 100755 --- a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -81,7 +81,7 @@ { "data": { "text/plain": [ - "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" + "'Linux-4.15.0-1064-azure-x86_64-with-debian-stretch-sid'" ] }, "execution_count": 3, @@ -768,7 +768,7 @@ { "data": { "text/plain": [ - "'gpuclstfwi08'" + "'gpuclstfwi07'" ] }, "execution_count": 15, @@ -778,7 +778,7 @@ ], "source": [ "gpu_cluster_name = os.getenv('GPU_CLUSTER_NAME')\n", - "gpu_cluster_name = 'gpuclstfwi08'\n", + "gpu_cluster_name = 'gpuclstfwi07'\n", "gpu_cluster_name" ] }, @@ -797,7 +797,7 @@ ], "source": [ "# Verify that cluster does not exist already\n", - "max_nodes_value = 2\n", + "max_nodes_value = 5\n", "try:\n", " gpu_cluster = ComputeTarget(workspace=ws, name=gpu_cluster_name)\n", " print(\"Found existing gpu cluster\")\n", @@ -835,7 +835,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "a0312dfcb82f419288e3c3c37c39b9dd", + "model_id": "a181598be9b8425e83c53527039e933f", "version_major": 2, "version_minor": 0 }, @@ -848,7 +848,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575678435_be18a2fc?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575678435_be18a2fc\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575678435_be18a2fc\", \"created_utc\": \"2019-12-07T00:27:18.102865Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":1,\\\"CurrentNodeCount\\\":2}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575678435_be18a2fc/azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt?sv=2019-02-02&sr=b&sig=99MfEJ4IvLwXgM3jjLm4amfljnv7gOK3%2BQPb1GN%2BZKg%3D&st=2019-12-07T00%3A22%3A27Z&se=2019-12-07T08%3A32%3A27Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt\"]], \"run_duration\": \"0:05:10\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2019-12-07T00:31:04Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.76: Pulling from fwi01_azureml\\n1ab2bdfe9778: Pulling fs layer\\ndd7d28bd8be5: Pulling fs layer\\naf998e3a361b: Pulling fs layer\\n8f61820757bf: Pulling fs layer\\n0eb461057035: Pulling fs layer\\n23276e49c76d: Pulling fs layer\\nc55ca301ea9f: Pulling fs layer\\n0eb461057035: Waiting\\n8f61820757bf: Waiting\\nc55ca301ea9f: Waiting\\n1ab2bdfe9778: Verifying Checksum\\n1ab2bdfe9778: Download complete\\naf998e3a361b: Verifying Checksum\\naf998e3a361b: Download complete\\n0eb461057035: Verifying Checksum\\n0eb461057035: Download complete\\ndd7d28bd8be5: Verifying Checksum\\ndd7d28bd8be5: Download complete\\n1ab2bdfe9778: Pull complete\\n8f61820757bf: Verifying Checksum\\n8f61820757bf: Download complete\\ndd7d28bd8be5: Pull complete\\nc55ca301ea9f: Verifying Checksum\\nc55ca301ea9f: Download complete\\n23276e49c76d: Verifying Checksum\\n23276e49c76d: Download complete\\naf998e3a361b: Pull complete\\n8f61820757bf: Pull complete\\n0eb461057035: Pull complete\\n23276e49c76d: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576175451_a1fbe2bb?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1576175451_a1fbe2bb\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1576175451_a1fbe2bb\", \"created_utc\": \"2019-12-12T18:30:56.157073Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"ghiordan/notebook_integration_test\", \"mlflow.source.git.branch\": \"ghiordan/notebook_integration_test\", \"azureml.git.commit\": \"aaa5035c48a6a2cd6e71a002f336956e7551ed03\", \"mlflow.source.git.commit\": \"aaa5035c48a6a2cd6e71a002f336956e7551ed03\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":1,\\\"CurrentNodeCount\\\":2}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_242cdc0ad87d0269451b0c31ba8863bf7943624086057999022a7958e401e1e0_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576175451_a1fbe2bb/azureml-logs/55_azureml-execution-tvmps_242cdc0ad87d0269451b0c31ba8863bf7943624086057999022a7958e401e1e0_d.txt?sv=2019-02-02&sr=b&sig=ol9foUO64whLh%2FTmkBvNub3qjkx9vqViztnn5c9EY0U%3D&st=2019-12-12T18%3A26%3A11Z&se=2019-12-13T02%3A36%3A11Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_242cdc0ad87d0269451b0c31ba8863bf7943624086057999022a7958e401e1e0_d.txt\"]], \"run_duration\": \"0:05:15\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2019-12-12T18:35:18Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.76: Pulling from fwi01_azureml\\n1ab2bdfe9778: Pulling fs layer\\ndd7d28bd8be5: Pulling fs layer\\naf998e3a361b: Pulling fs layer\\n8f61820757bf: Pulling fs layer\\n0eb461057035: Pulling fs layer\\n23276e49c76d: Pulling fs layer\\nc55ca301ea9f: Pulling fs layer\\n0eb461057035: Waiting\\n8f61820757bf: Waiting\\nc55ca301ea9f: Waiting\\n23276e49c76d: Waiting\\n1ab2bdfe9778: Verifying Checksum\\n1ab2bdfe9778: Download complete\\ndd7d28bd8be5: Verifying Checksum\\ndd7d28bd8be5: Download complete\\n0eb461057035: Verifying Checksum\\n0eb461057035: Download complete\\naf998e3a361b: Verifying Checksum\\naf998e3a361b: Download complete\\n8f61820757bf: Verifying Checksum\\n8f61820757bf: Download complete\\n1ab2bdfe9778: Pull complete\\nc55ca301ea9f: Verifying Checksum\\nc55ca301ea9f: Download complete\\n23276e49c76d: Verifying Checksum\\n23276e49c76d: Download complete\\ndd7d28bd8be5: Pull complete\\naf998e3a361b: Pull complete\\n8f61820757bf: Pull complete\\n0eb461057035: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -921,7 +921,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 9, time 20.798 seconds: Counter({'Completed': 1})\r" + "Final print 7, time 18.031 seconds: Counter({'Completed': 1})\r" ] } ], @@ -954,8 +954,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "run_duration in seconds 243.960763\n", - "run_duration= 4m 3.961s\n" + "run_duration in seconds 259.959389\n", + "run_duration= 4m 19.959s\n" ] } ], @@ -982,13 +982,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Showing details for run 498\n" + "Showing details for run 381\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "cd44e7b0a1c447dabe98bf114f420d76", + "model_id": "4c41edc487e24278b597a3b2b9cbe7fd", "version_major": 2, "version_minor": 0 }, @@ -1001,7 +1001,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"created_utc\": \"2019-12-07T01:54:55.33033Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-07T01:56:48.811115Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=9mQARzuRlCW%2F%2Brv3FDzJvm%2Fsaudk6GFjNypMRkV3O8g%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=TMxrg26ywABOyJtGYT3KVLrGP0TYIHQ9E3ePlr%2BQepg%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=vWkErsH55%2BLhIG%2FBJbtZb8NSNHFyNAzxk5VjW4p6lcM%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=cbDgvPNn4LNXDsUXZwmWCjRMj0O9PnFSqSCtuCPMTFo%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=wvqhR%2Bnzw0uLEsCGETAxkKrdwN5eI%2FgvTeB4juQ4aUI%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=kkirWrsrpjcrKndUUPxuJVeRWu0GthsVZ4cXpxbEGMg%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"logs/azureml/728_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/logs/azureml/728_azureml.log?sv=2019-02-02&sr=b&sig=pK%2F6TBBvQEPexjuRPR1FyOq6CUPXfnNBobkTmpmaeiM%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=o%2BPcdcJvKZyQWRA0HpaJbM%2BxhqFOkdDjgBqtxtHtoag%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"logs/azureml/728_azureml.log\"]], \"run_duration\": \"0:01:53\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-07 01:55:16,975|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-07 01:55:17,242|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-07 01:55:17,243|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-07 01:55:18,070|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-07 01:55:18,075|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-07 01:55:18,078|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-07 01:55:18,082|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-07 01:55:18,083|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:55:18,088|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,089|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-07 01:55:18,089|azureml.core.authentication|DEBUG|Time to expire 1814376.910384 seconds\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,118|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,122|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,128|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,132|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,136|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,141|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,141|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-07 01:55:18,142|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG|None\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:55:18 GMT'\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 2107,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-07T01:54:55.3303306+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-07T01:55:07.6378716+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575683693_ddd16e31/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-07 01:55:18,202|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-07 01:55:18,203|azureml.WorkerPool|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,157|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,158|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-07 01:55:20,162|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,166|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,170|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,175|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,175|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-07 01:55:20,175|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31'\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG|None\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:55:20 GMT'\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 2107,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-07T01:54:55.3303306+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-07T01:55:07.6378716+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575683693_ddd16e31/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-07 01:55:48,084|azureml.core.authentication|DEBUG|Time to expire 1814346.915499 seconds\\n2019-12-07 01:56:18,084|azureml.core.authentication|DEBUG|Time to expire 1814316.915133 seconds\\n2019-12-07 01:56:25,858|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-07 01:56:25,858|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:56:25,859|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-07 01:56:25,925|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|INFO|complete is not setting status for submitted runs.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-07 01:56:25,927|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-07 01:56:25,927|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-07 01:56:25,928|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-07 01:56:25,928|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-07 01:56:25,928|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-07 01:56:25,929|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:56:25,929|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-07 01:56:25,929|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-07 01:56:25,929|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31/batch/metrics'\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:25,931|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-07 01:56:25,932|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-07 01:56:25,932|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"1a8ad3d8-accf-42da-a07d-fd00ef5ee1e6\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-07T01:56:25.858188Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:56:26,050|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:56:26 GMT'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|\\n2019-12-07 01:56:26,052|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-07 01:56:26,182|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-07 01:56:26,182|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002186298370361328 seconds.\\n\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-07 01:56:26,183|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576180273_3ee1ab8c?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1576180273_3ee1ab8c\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1576180273_3ee1ab8c\", \"created_utc\": \"2019-12-12T19:51:15.427092Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"ghiordan/notebook_integration_test\", \"mlflow.source.git.branch\": \"ghiordan/notebook_integration_test\", \"azureml.git.commit\": \"aaa5035c48a6a2cd6e71a002f336956e7551ed03\", \"mlflow.source.git.commit\": \"aaa5035c48a6a2cd6e71a002f336956e7551ed03\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-12T19:53:32.429571Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_61ed9f4f6c365c7d4ea910e456f73e5a2619008d5ce156520cb27d51832a863d_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576180273_3ee1ab8c/azureml-logs/55_azureml-execution-tvmps_61ed9f4f6c365c7d4ea910e456f73e5a2619008d5ce156520cb27d51832a863d_d.txt?sv=2019-02-02&sr=b&sig=orRTsvrEZJx5gXCp%2B7tz%2FlDsY%2BYEz4Df1MEJ1RI%2FX1Y%3D&st=2019-12-12T19%3A43%3A33Z&se=2019-12-13T03%3A53%3A33Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_61ed9f4f6c365c7d4ea910e456f73e5a2619008d5ce156520cb27d51832a863d_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576180273_3ee1ab8c/azureml-logs/65_job_prep-tvmps_61ed9f4f6c365c7d4ea910e456f73e5a2619008d5ce156520cb27d51832a863d_d.txt?sv=2019-02-02&sr=b&sig=7bhRxRjJWzobj%2FPtj0Jzf%2Bo3y4teN3KPRihtITGXE%2BY%3D&st=2019-12-12T19%3A43%3A33Z&se=2019-12-13T03%3A53%3A33Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576180273_3ee1ab8c/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=u6iQ28znsmrF8roGcOueM%2BEzJ64EEQe%2FCJ9x0PfZawo%3D&st=2019-12-12T19%3A43%3A33Z&se=2019-12-13T03%3A53%3A33Z&sp=r\", \"azureml-logs/75_job_post-tvmps_61ed9f4f6c365c7d4ea910e456f73e5a2619008d5ce156520cb27d51832a863d_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576180273_3ee1ab8c/azureml-logs/75_job_post-tvmps_61ed9f4f6c365c7d4ea910e456f73e5a2619008d5ce156520cb27d51832a863d_d.txt?sv=2019-02-02&sr=b&sig=8d3E3t%2Bz62PGc9iCUhnbFSwGadJpd1L7TsmbgRRrO1U%3D&st=2019-12-12T19%3A43%3A33Z&se=2019-12-13T03%3A53%3A33Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576180273_3ee1ab8c/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=ouNBDNyuIX1yKBEgeAvMfSNGSWdqBZ%2FWS4RDV%2FYlCi4%3D&st=2019-12-12T19%3A43%3A33Z&se=2019-12-13T03%3A53%3A33Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576180273_3ee1ab8c/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=UhdiyTAu9lXtBxEC56ThQld%2BEfagAdEnP4%2BNz6FxTkg%3D&st=2019-12-12T19%3A43%3A33Z&se=2019-12-13T03%3A53%3A33Z&sp=r\", \"logs/azureml/728_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576180273_3ee1ab8c/logs/azureml/728_azureml.log?sv=2019-02-02&sr=b&sig=mlmn1YcFFNjG%2F2I3g6jeuBu7oqbBxoeA4dljWIdFeGM%3D&st=2019-12-12T19%3A43%3A33Z&se=2019-12-13T03%3A53%3A33Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576180273_3ee1ab8c/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=dH1hTzsYX9JInqawsgpjkjLbwfeN1mSFd0VQlkxpMqU%3D&st=2019-12-12T19%3A43%3A33Z&se=2019-12-13T03%3A53%3A33Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_61ed9f4f6c365c7d4ea910e456f73e5a2619008d5ce156520cb27d51832a863d_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_61ed9f4f6c365c7d4ea910e456f73e5a2619008d5ce156520cb27d51832a863d_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_61ed9f4f6c365c7d4ea910e456f73e5a2619008d5ce156520cb27d51832a863d_d.txt\"], [\"logs/azureml/728_azureml.log\"]], \"run_duration\": \"0:02:17\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1576180273_3ee1ab8c\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-12 19:51:48,212|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-12 19:51:48,212|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-12 19:51:48,213|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-12 19:51:48,213|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-12 19:51:48,517|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-12 19:51:48,517|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-12 19:51:48,901|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-12 19:51:48,901|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-12 19:51:48,901|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-12 19:51:48,901|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-12 19:51:48,902|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-12 19:51:49,493|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-12 19:51:49,498|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-12 19:51:49,502|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-12 19:51:49,506|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-12 19:51:49,508|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-12 19:51:49,514|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:49,515|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-12 19:51:49,515|azureml.core.authentication|DEBUG|Time to expire 1814365.484557 seconds\\n2019-12-12 19:51:49,515|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,515|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,515|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,515|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,516|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,516|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,516|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,516|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,516|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,547|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:49,552|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:49,559|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:49,563|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:49,568|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:49,572|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:49,573|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-12 19:51:49,574|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-12 19:51:49,574|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576180273_3ee1ab8c'\\n2019-12-12 19:51:49,575|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-12 19:51:49,575|msrest.http_logger|DEBUG|Request headers:\\n2019-12-12 19:51:49,575|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-12 19:51:49,575|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-12 19:51:49,575|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '768351bd-80f8-48eb-b849-be5096cf5808'\\n2019-12-12 19:51:49,575|msrest.http_logger|DEBUG| 'request-id': '768351bd-80f8-48eb-b849-be5096cf5808'\\n2019-12-12 19:51:49,575|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-12 19:51:49,575|msrest.http_logger|DEBUG|Request body:\\n2019-12-12 19:51:49,576|msrest.http_logger|DEBUG|None\\n2019-12-12 19:51:49,576|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-12 19:51:49,576|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-12 19:51:49,576|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-12 19:51:49,576|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-12 19:51:49,636|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-12 19:51:49,637|msrest.http_logger|DEBUG|Response headers:\\n2019-12-12 19:51:49,637|msrest.http_logger|DEBUG| 'Date': 'Thu, 12 Dec 2019 19:51:49 GMT'\\n2019-12-12 19:51:49,637|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-12 19:51:49,637|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-12 19:51:49,637|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-12 19:51:49,638|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-12 19:51:49,638|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-12 19:51:49,638|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '768351bd-80f8-48eb-b849-be5096cf5808'\\n2019-12-12 19:51:49,638|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-12 19:51:49,638|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-12 19:51:49,638|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-12 19:51:49,638|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-12 19:51:49,638|msrest.http_logger|DEBUG|Response content:\\n2019-12-12 19:51:49,638|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 4749,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576180273_3ee1ab8c\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-12T19:51:15.4270922+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576180273_3ee1ab8c\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-12T19:51:29.1480965+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576180273_3ee1ab8c\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/notebook_integration_test\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/notebook_integration_test\\\",\\n \\\"azureml.git.commit\\\": \\\"aaa5035c48a6a2cd6e71a002f336956e7551ed03\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"aaa5035c48a6a2cd6e71a002f336956e7551ed03\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576180273_3ee1ab8c/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576180273_3ee1ab8c/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576180273_3ee1ab8c/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-12 19:51:49,645|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-12 19:51:49,646|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/notebook_integration_test', 'mlflow.source.git.branch': 'ghiordan/notebook_integration_test', 'azureml.git.commit': 'aaa5035c48a6a2cd6e71a002f336956e7551ed03', 'mlflow.source.git.commit': 'aaa5035c48a6a2cd6e71a002f336956e7551ed03', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-12 19:51:49,646|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-12 19:51:49,647|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-12 19:51:49,647|azureml.WorkerPool|DEBUG|[START]\\n2019-12-12 19:51:49,647|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-12 19:51:49,647|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-12 19:51:49,647|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-12 19:51:49,647|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-12 19:51:49,647|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-12 19:51:49,648|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576180273_3ee1ab8c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576180273_3ee1ab8c\\n2019-12-12 19:51:49,648|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-12 19:51:49,648|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576180273_3ee1ab8c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576180273_3ee1ab8c\\n2019-12-12 19:51:51,686|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:51,686|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:51,686|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:51,686|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:51,686|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:51,687|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:51,687|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:51,687|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:51,687|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-12 19:51:51,692|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:51,693|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-12 19:51:51,698|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:51,702|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:51,707|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:51,711|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:51:51,712|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-12 19:51:51,712|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576180273_3ee1ab8c'\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG|Request headers:\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'eae24564-7461-48b2-8cb3-4691cf73eb2f'\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG| 'request-id': 'eae24564-7461-48b2-8cb3-4691cf73eb2f'\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG|Request body:\\n2019-12-12 19:51:51,713|msrest.http_logger|DEBUG|None\\n2019-12-12 19:51:51,713|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-12 19:51:51,713|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-12 19:51:51,714|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-12 19:51:51,714|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-12 19:51:51,788|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-12 19:51:51,788|msrest.http_logger|DEBUG|Response headers:\\n2019-12-12 19:51:51,788|msrest.http_logger|DEBUG| 'Date': 'Thu, 12 Dec 2019 19:51:51 GMT'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'eae24564-7461-48b2-8cb3-4691cf73eb2f'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG|Response content:\\n2019-12-12 19:51:51,789|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 4749,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576180273_3ee1ab8c\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-12T19:51:15.4270922+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576180273_3ee1ab8c\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-12T19:51:29.1480965+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576180273_3ee1ab8c\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/notebook_integration_test\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/notebook_integration_test\\\",\\n \\\"azureml.git.commit\\\": \\\"aaa5035c48a6a2cd6e71a002f336956e7551ed03\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"aaa5035c48a6a2cd6e71a002f336956e7551ed03\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576180273_3ee1ab8c/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576180273_3ee1ab8c/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576180273_3ee1ab8c/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-12 19:51:51,792|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-12 19:51:51,793|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/notebook_integration_test', 'mlflow.source.git.branch': 'ghiordan/notebook_integration_test', 'azureml.git.commit': 'aaa5035c48a6a2cd6e71a002f336956e7551ed03', 'mlflow.source.git.commit': 'aaa5035c48a6a2cd6e71a002f336956e7551ed03', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-12 19:51:51,793|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-12 19:52:19,509|azureml.core.authentication|DEBUG|Time to expire 1814335.490741 seconds\\n2019-12-12 19:52:49,509|azureml.core.authentication|DEBUG|Time to expire 1814305.49013 seconds\\n2019-12-12 19:53:10,285|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-12 19:53:10,286|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-12 19:53:10,286|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-12 19:53:11,287|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-12 19:53:11,287|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-12 19:53:11,287|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-12 19:53:11,288|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-12 19:53:11,288|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-12 19:53:11,288|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-12 19:53:11,288|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-12 19:53:11,288|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-12 19:53:11,288|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-12 19:53:11,288|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-12 19:53:11,290|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-12 19:53:11,290|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-12 19:53:11,290|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-12 19:53:11,291|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-12 19:53:11,291|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-12 19:53:11,291|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576180273_3ee1ab8c/batch/metrics'\\n2019-12-12 19:53:11,291|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-12 19:53:11,291|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-12 19:53:11,292|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-12 19:53:11,292|msrest.http_logger|DEBUG|Request headers:\\n2019-12-12 19:53:11,292|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-12 19:53:11,292|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-12 19:53:11,292|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-12 19:53:11,292|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-12 19:53:11,292|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-12 19:53:11,292|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'bbd539ca-e7b5-43dc-9079-059aa7100410'\\n2019-12-12 19:53:11,293|msrest.http_logger|DEBUG| 'request-id': 'bbd539ca-e7b5-43dc-9079-059aa7100410'\\n2019-12-12 19:53:11,293|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-12 19:53:11,293|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-12 19:53:11,293|msrest.http_logger|DEBUG|Request body:\\n2019-12-12 19:53:11,293|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"11bc43b3-d54c-46c9-9089-2c659181c6ec\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-12T19:53:10.285442Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-12 19:53:11,293|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-12 19:53:11,293|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-12 19:53:11,293|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-12 19:53:11,293|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-12 19:53:11,735|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-12 19:53:11,735|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-12 19:53:11,735|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-12 19:53:11,735|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576180273_3ee1ab8c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576180273_3ee1ab8c\\n2019-12-12 19:53:11,735|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576180273_3ee1ab8c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576180273_3ee1ab8c\\n2019-12-12 19:53:11,735|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-12 19:53:11,736|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-12 19:53:11,736|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c|INFO|complete is not setting status for submitted runs.\\n2019-12-12 19:53:11,736|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-12 19:53:11,736|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-12 19:53:11,736|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-12 19:53:11,736|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-12 19:53:11,736|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-12 19:53:11,737|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-12 19:53:11,737|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-12 19:53:11,738|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-12 19:53:11,739|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-12 19:53:11,739|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-12 19:53:11,739|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-12 19:53:11,739|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-12 19:53:12,398|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-12 19:53:12,398|msrest.http_logger|DEBUG|Response headers:\\n2019-12-12 19:53:12,398|msrest.http_logger|DEBUG| 'Date': 'Thu, 12 Dec 2019 19:53:12 GMT'\\n2019-12-12 19:53:12,398|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-12 19:53:12,398|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-12 19:53:12,399|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-12 19:53:12,399|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'bbd539ca-e7b5-43dc-9079-059aa7100410'\\n2019-12-12 19:53:12,399|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-12 19:53:12,399|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-12 19:53:12,399|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-12 19:53:12,399|msrest.http_logger|DEBUG|Response content:\\n2019-12-12 19:53:12,399|msrest.http_logger|DEBUG|\\n2019-12-12 19:53:12,400|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-12 19:53:12,490|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-12 19:53:12,491|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-12 19:53:12,491|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-12 19:53:12,491|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00010395050048828125 seconds.\\nWaiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.25048041343688965 seconds.\\nWaiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.5008423328399658 seconds.\\n\\n2019-12-12 19:53:12,491|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-12 19:53:12,491|azureml._SubmittedRun#020_AzureMLEstimator_1576180273_3ee1ab8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-12 19:53:12,491|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-12 19:53:12,492|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-12 19:53:12,492|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-12 19:53:12,492|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -1010,8 +1010,42 @@ "name": "stdout", "output_type": "stream", "text": [ - "Counter499: submission of job 499 on 400 nodes took 9.16640019416809 seconds \n", - "run list length 499\n" + "Counter382: submission of job 382 on 200 nodes took 8.540501594543457 seconds \n", + "run list length 382\n", + "Counter383: submission of job 383 on 200 nodes took 8.679041624069214 seconds \n", + "run list length 383\n", + "Counter384: submission of job 384 on 200 nodes took 10.69391679763794 seconds \n", + "run list length 384\n", + "Counter385: submission of job 385 on 200 nodes took 9.06492829322815 seconds \n", + "run list length 385\n", + "Counter386: submission of job 386 on 200 nodes took 9.60067343711853 seconds \n", + "run list length 386\n", + "Counter387: submission of job 387 on 200 nodes took 12.19172477722168 seconds \n", + "run list length 387\n", + "Counter388: submission of job 388 on 200 nodes took 10.080734491348267 seconds \n", + "run list length 388\n", + "Counter389: submission of job 389 on 200 nodes took 64.18558645248413 seconds \n", + "run list length 389\n", + "Counter390: submission of job 390 on 200 nodes took 9.707869052886963 seconds \n", + "run list length 390\n", + "Counter391: submission of job 391 on 200 nodes took 8.676024436950684 seconds \n", + "run list length 391\n", + "Counter392: submission of job 392 on 200 nodes took 8.557382822036743 seconds \n", + "run list length 392\n", + "Counter393: submission of job 393 on 200 nodes took 9.290260553359985 seconds \n", + "run list length 393\n", + "Counter394: submission of job 394 on 200 nodes took 11.108245134353638 seconds \n", + "run list length 394\n", + "Counter395: submission of job 395 on 200 nodes took 7.9163103103637695 seconds \n", + "run list length 395\n", + "Counter396: submission of job 396 on 200 nodes took 8.46341848373413 seconds \n", + "run list length 396\n", + "Counter397: submission of job 397 on 200 nodes took 7.894851446151733 seconds \n", + "run list length 397\n", + "Counter398: submission of job 398 on 200 nodes took 8.801857471466064 seconds \n", + "run list length 398\n", + "Counter399: submission of job 399 on 200 nodes took 8.962209224700928 seconds \n", + "run list length 399\n" ] } ], @@ -1019,11 +1053,11 @@ "import time\n", "from IPython.display import clear_output\n", "\n", - "no_of_jobs = 500\n", - "no_of_nodes = 400\n", + "no_of_jobs = 400 #int(3e4)\n", + "no_of_nodes = 200 #int(1e4)\n", "\n", "job_counter = 0\n", - "print_cycle = 7\n", + "print_cycle = 20\n", "run_list = []\n", "submit_time_list = []\n", "for crt_nodes in range(no_of_nodes, (no_of_nodes+1)):\n", @@ -1054,106 +1088,106 @@ { "data": { "text/plain": [ - "array([10.16889381, 10.52522182, 8.67223501, 7.76976609, 8.98659873,\n", - " 9.54043746, 7.56379271, 7.95067477, 10.98772812, 8.58469343,\n", - " 9.19690919, 8.37747335, 8.49322033, 8.96249437, 11.00566387,\n", - " 10.18721223, 8.70340395, 9.07873917, 8.83641577, 9.93886757,\n", - " 8.43751788, 8.88584614, 8.46158338, 8.10118651, 7.95576859,\n", - " 8.02682757, 8.59585524, 11.43893504, 8.21132302, 7.56929898,\n", - " 9.16166759, 7.96446443, 8.20211887, 8.0066514 , 8.16604567,\n", - " 9.03855515, 9.27646971, 7.88356876, 8.6105082 , 8.63279152,\n", - " 9.63798594, 7.88380122, 11.83064437, 7.67609763, 8.36450744,\n", - " 10.36203027, 8.20605659, 8.27934074, 8.71854138, 7.48072934,\n", - " 7.98534775, 7.88993239, 9.49783468, 8.20365477, 8.31964707,\n", - " 8.24653029, 9.14784336, 8.39632297, 8.88221884, 10.17075896,\n", - " 7.93166018, 8.50952411, 8.35107565, 8.62145162, 9.1473949 ,\n", - " 10.16314006, 9.48931861, 9.52163553, 10.48561263, 8.70149064,\n", - " 8.83968425, 8.77899456, 8.19752908, 8.23720503, 8.44300842,\n", - " 10.4865036 , 9.38597918, 8.16601682, 10.31557417, 9.39266205,\n", - " 9.3517375 , 8.26235414, 9.90602231, 8.08361053, 9.55309701,\n", - " 8.37694287, 8.2842195 , 9.27187061, 8.05741239, 9.81221128,\n", - " 8.67282987, 7.50111246, 8.84159875, 7.5928266 , 8.2180264 ,\n", - " 11.30247498, 8.97954369, 9.08557224, 8.62394547, 27.931288 ,\n", - " 11.31702137, 9.03355598, 9.82408452, 10.98696327, 8.15972924,\n", - " 8.10580516, 8.6766634 , 9.18826079, 9.91399217, 9.63535714,\n", - " 8.84899211, 8.59690166, 9.08935356, 7.87525439, 9.04824638,\n", - " 10.58436322, 8.05351543, 8.0442934 , 8.51687765, 8.23182964,\n", - " 7.90365982, 9.41734576, 7.82690763, 7.86053801, 8.81060672,\n", - " 15.63083076, 9.12365007, 8.4692018 , 8.38626456, 9.1455934 ,\n", - " 7.9579742 , 8.32254815, 9.60984373, 7.72059083, 9.80256414,\n", - " 8.03569841, 8.56897283, 9.88993764, 9.825032 , 9.10494757,\n", - " 7.96795917, 8.83923078, 8.12920213, 9.14702606, 10.44252062,\n", - " 8.11435223, 11.10698366, 8.54753256, 11.07914209, 8.0072608 ,\n", - " 8.64252162, 7.86998582, 8.16502595, 9.72599697, 8.01553535,\n", - " 8.05236411, 9.4306016 , 8.3510747 , 8.15123487, 7.73660946,\n", - " 8.78807712, 8.42650437, 9.09502602, 67.75333071, 14.179214 ,\n", - " 13.08692336, 14.52568007, 12.39239168, 8.40634942, 8.3893857 ,\n", - " 7.80925822, 8.04524732, 10.61561441, 9.33992386, 8.05361605,\n", - " 8.71911073, 8.13864756, 8.18779135, 8.03402972, 8.20232296,\n", - " 10.52845287, 8.21701574, 9.63750052, 8.16265893, 7.95386362,\n", - " 7.85334754, 7.96290469, 8.1984942 , 8.32950211, 17.0101552 ,\n", - " 14.20266891, 13.09765553, 14.32137418, 8.90045214, 9.79849219,\n", - " 7.7378149 , 8.17814636, 8.0692122 , 8.02391315, 7.73337412,\n", - " 8.24749708, 8.21430159, 8.42469835, 7.93915629, 8.17162681,\n", - " 9.29439068, 8.39062524, 8.05844831, 12.62865376, 8.03868556,\n", - " 8.03020358, 8.72658324, 7.98921943, 10.13008642, 8.36204886,\n", - " 9.8618927 , 8.84138846, 8.26497674, 8.53586483, 11.22441888,\n", - " 8.60046291, 9.52709126, 8.1862669 , 8.47402501, 8.08845234,\n", - " 8.0216496 , 8.25297642, 9.52822161, 8.53732967, 9.20458651,\n", - " 7.84344959, 8.76693869, 9.55830622, 9.32047439, 9.61785316,\n", - " 14.20765901, 13.20616293, 12.79950929, 13.23175693, 10.48755121,\n", - " 7.89634991, 8.62207508, 10.17518067, 9.5078795 , 8.16943836,\n", - " 11.88958383, 8.53581595, 8.78866196, 9.86849713, 8.38485384,\n", - " 7.80456519, 8.7930553 , 8.67091751, 11.64525867, 10.70969439,\n", - " 9.57600379, 7.88863015, 9.16765165, 8.10214615, 8.1002388 ,\n", - " 7.79884577, 7.84607792, 10.70999765, 8.32228923, 8.15903163,\n", - " 8.16516185, 11.13710332, 8.67460465, 8.04933095, 7.92010641,\n", - " 9.71926355, 7.96389985, 8.50223684, 7.80719972, 7.94503832,\n", - " 9.14503789, 8.74866915, 8.32825327, 9.38176489, 8.7043674 ,\n", - " 8.11469626, 8.39300489, 8.52375507, 9.48120856, 9.30481339,\n", - " 11.00180173, 8.00356221, 9.36562443, 11.26503015, 8.29429078,\n", - " 10.5787971 , 8.23888326, 8.25085521, 9.65488529, 10.22367787,\n", - " 8.86958766, 8.67924905, 9.8065629 , 9.98437238, 10.44085979,\n", - " 8.48997521, 13.41537356, 8.53429914, 9.41697288, 8.75000739,\n", - " 8.67022324, 10.65776849, 8.78767824, 29.17240787, 8.29843664,\n", - " 10.48030996, 8.60965252, 9.05648637, 11.23915553, 7.71198177,\n", - " 8.58811665, 11.27894258, 11.26059055, 8.08691239, 9.09145069,\n", - " 8.37398744, 9.33932018, 9.50723815, 14.62887979, 8.08766961,\n", - " 8.1010766 , 8.15962887, 7.86279893, 7.81253982, 8.72090292,\n", - " 28.51810336, 8.20156765, 8.10436082, 9.35736108, 10.11271501,\n", - " 8.28001332, 8.10338402, 7.82260585, 7.74735689, 9.37371802,\n", - " 7.83298874, 8.09861684, 11.44845009, 13.80942464, 13.86787438,\n", - " 12.95256805, 13.5946703 , 9.04438519, 8.42931032, 7.69650388,\n", - " 8.3203001 , 8.93009233, 8.99896145, 10.261621 , 9.76696181,\n", - " 8.42695355, 9.45543766, 8.35829163, 8.19327784, 8.54582119,\n", - " 10.28408813, 9.96855664, 9.4126513 , 8.85548735, 8.37564468,\n", - " 7.85812593, 11.26866746, 11.99777699, 8.90290856, 9.73011518,\n", - " 11.37953544, 9.56070495, 13.08286595, 7.91717887, 8.70709944,\n", - " 8.89286566, 9.43534017, 9.63375568, 9.45693254, 9.41722798,\n", - " 8.95478702, 10.59636545, 9.07217526, 8.91465688, 8.43598938,\n", - " 10.09872103, 8.53826594, 10.51633263, 8.16474724, 9.60920191,\n", - " 8.79985189, 11.08250904, 15.82575488, 13.72388315, 13.76962495,\n", - " 15.5107224 , 12.99527621, 9.55358648, 11.27318692, 10.64224267,\n", - " 9.28194666, 8.15835619, 10.34727526, 9.13943338, 8.47959018,\n", - " 12.95671797, 8.67874169, 9.48093748, 11.13487458, 11.16393185,\n", - " 9.45039058, 9.26687908, 10.83345985, 10.013412 , 12.88114643,\n", - " 8.90868664, 9.11424375, 10.62471223, 10.37447572, 8.56728458,\n", - " 11.44042325, 8.61506176, 14.37763166, 9.26899981, 9.01356244,\n", - " 12.6770153 , 7.95549965, 8.69824529, 8.16541219, 10.80149889,\n", - " 9.85532331, 9.16404986, 11.05029202, 8.95759201, 9.60003638,\n", - " 8.64066339, 11.99474025, 10.88645577, 9.82658648, 8.38357234,\n", - " 8.1931479 , 8.36809587, 8.34779596, 9.29737759, 7.71148348,\n", - " 8.34155583, 8.46944427, 9.46755242, 8.39070392, 9.67334032,\n", - " 9.42819619, 8.90718842, 8.95999622, 17.03638124, 14.13874507,\n", - " 14.17324162, 14.82433629, 10.27358413, 7.75390744, 10.63386297,\n", - " 10.74013877, 9.25264263, 8.88592076, 15.62230277, 8.68499494,\n", - " 7.90613437, 10.8253715 , 9.28829837, 9.96133757, 8.82941794,\n", - " 11.07499003, 9.08565426, 8.76584291, 11.91541052, 9.45269704,\n", - " 9.68554997, 9.76184082, 10.95884109, 9.22084093, 9.07609534,\n", - " 9.72482204, 8.66262245, 8.85580897, 12.12771249, 9.1096139 ,\n", - " 9.55135322, 9.73613167, 12.00068331, 9.63835907, 8.8003633 ,\n", - " 10.78142428, 10.36234426, 8.7075491 , 8.79299307, 10.6836946 ,\n", - " 8.24508142, 9.70224071, 8.64105797, 9.16640019])" + "array([ 8.53233099, 9.6772933 , 8.7021544 , 28.12693453,\n", + " 26.12712884, 8.02959847, 8.27554464, 7.93526673,\n", + " 10.38847923, 10.26227283, 7.99096107, 7.7994504 ,\n", + " 8.92605019, 7.71905446, 8.76512623, 11.84260702,\n", + " 8.87511253, 7.76093102, 8.48274088, 11.45512724,\n", + " 11.46913004, 13.58411193, 15.08223629, 12.01883411,\n", + " 13.69515061, 12.94075537, 11.91895461, 13.53630114,\n", + " 14.08951163, 12.01434255, 9.85318661, 7.87717223,\n", + " 13.62571478, 9.9318738 , 11.23619175, 10.18086481,\n", + " 184.52550459, 8.41797161, 8.30030227, 9.37715912,\n", + " 10.04398227, 10.35769916, 8.09892941, 26.54780507,\n", + " 7.72881198, 10.82559562, 9.51319575, 8.0031395 ,\n", + " 7.68946218, 7.65393519, 9.295995 , 9.97156954,\n", + " 8.27380228, 8.85742974, 8.7982173 , 7.58378983,\n", + " 11.53695369, 8.32523108, 26.38094878, 12.22401118,\n", + " 10.00425243, 10.81117606, 9.94590068, 8.83902907,\n", + " 7.8381927 , 8.91350174, 9.53015494, 17.5478363 ,\n", + " 15.34007668, 16.02998924, 12.38423824, 9.89037919,\n", + " 9.57786345, 9.03902459, 7.86881256, 7.6225996 ,\n", + " 10.52406168, 9.17591476, 9.12877059, 8.75068927,\n", + " 7.99741554, 7.83301091, 7.6275475 , 7.67830873,\n", + " 7.99491739, 9.88401365, 9.40572047, 8.11121702,\n", + " 8.14038825, 7.45031595, 9.68304396, 10.76943803,\n", + " 10.09554648, 9.57767701, 7.7197783 , 8.64100885,\n", + " 8.84749031, 8.4180398 , 11.00115633, 7.79180384,\n", + " 10.48066974, 10.16067934, 9.3959043 , 9.84234285,\n", + " 9.40512347, 11.3006804 , 9.1447556 , 8.61428738,\n", + " 8.27683949, 11.54868817, 11.24936724, 13.8472631 ,\n", + " 12.81225634, 13.69866681, 13.18902826, 12.62158394,\n", + " 9.98353195, 9.07694626, 8.2411623 , 9.68330097,\n", + " 8.04647088, 7.72701788, 11.57462502, 8.81296802,\n", + " 18.21019888, 28.25579047, 20.52909541, 30.63766003,\n", + " 69.55946541, 9.58495474, 9.68108845, 9.50967264,\n", + " 9.68480849, 11.61135411, 10.60314918, 10.50398946,\n", + " 8.68219256, 11.01642323, 32.81164861, 9.29215407,\n", + " 9.33582568, 9.92668819, 10.67389107, 10.43844223,\n", + " 14.61442542, 8.91103816, 8.23947215, 11.45713353,\n", + " 10.99629188, 10.09089899, 11.73967814, 8.14675379,\n", + " 9.13600969, 9.03533745, 7.92468333, 9.08910775,\n", + " 11.94980192, 8.94422436, 8.21661615, 11.25739408,\n", + " 10.33917546, 8.26420546, 10.04499006, 13.13062477,\n", + " 10.61878085, 8.62287593, 9.48258615, 10.43621016,\n", + " 11.34296155, 8.00507188, 10.61460066, 8.06711459,\n", + " 9.04712796, 12.10491657, 17.64639258, 9.078686 ,\n", + " 9.02982974, 12.08374047, 9.44510913, 8.54345846,\n", + " 8.54159427, 10.55086255, 8.29436111, 9.41221833,\n", + " 11.16688323, 7.64661026, 8.91549468, 8.0400703 ,\n", + " 10.38046765, 8.87727666, 9.14031005, 8.10071063,\n", + " 9.42238951, 8.64678621, 10.10962367, 11.33684564,\n", + " 8.00724602, 7.86412311, 10.50677729, 8.35650015,\n", + " 9.91056466, 9.56626225, 10.88657093, 7.70927572,\n", + " 7.87253642, 7.82360196, 9.33157229, 8.2978549 ,\n", + " 9.42948842, 10.35588026, 8.72255659, 9.58895707,\n", + " 8.96737742, 10.176759 , 9.56413746, 9.33201241,\n", + " 9.19050431, 8.94475698, 7.9108386 , 11.09678221,\n", + " 9.79926538, 11.52541971, 8.71367621, 9.49302125,\n", + " 10.17834425, 8.31368351, 9.84787917, 8.21552563,\n", + " 11.33579302, 9.64443612, 9.40933514, 8.25285316,\n", + " 11.36636925, 27.15671802, 12.11910629, 10.6145699 ,\n", + " 10.40268159, 9.36373115, 10.05504251, 9.38076162,\n", + " 10.04995155, 8.0100174 , 11.32109833, 10.73257399,\n", + " 8.74653697, 9.79397988, 8.2050643 , 8.40061426,\n", + " 9.43988299, 9.29069734, 8.44171834, 11.22942209,\n", + " 10.10757375, 10.54314733, 8.4060185 , 44.35749817,\n", + " 13.24065518, 12.40217805, 12.23765874, 12.47610807,\n", + " 13.63805914, 12.64036655, 14.40835738, 13.20102096,\n", + " 12.32834578, 8.14810824, 7.94945526, 8.29473567,\n", + " 8.05589199, 11.00759149, 10.05681944, 11.18587112,\n", + " 7.88193488, 10.06283331, 8.64938927, 8.4450736 ,\n", + " 8.18822265, 7.68098259, 9.11093211, 9.51930928,\n", + " 7.8107686 , 9.7526834 , 7.71634579, 7.77615952,\n", + " 11.67984819, 10.34155631, 8.45267701, 9.19745398,\n", + " 8.33982396, 7.65507722, 11.92601371, 9.71210384,\n", + " 11.42215395, 8.68903327, 9.65271091, 12.46663499,\n", + " 9.64360833, 11.56273746, 10.87845087, 10.03419089,\n", + " 8.11509705, 10.65581679, 11.02626204, 11.76155376,\n", + " 10.49183345, 8.06007385, 9.40408349, 7.81256628,\n", + " 10.06972933, 11.33354926, 9.28162503, 9.06613183,\n", + " 8.63264704, 9.1179111 , 9.6110661 , 11.36222959,\n", + " 9.93609858, 8.74147701, 26.92389488, 9.35497689,\n", + " 8.23636389, 8.72750926, 9.64041471, 8.22183347,\n", + " 8.35817862, 8.98578382, 10.50673199, 10.39634037,\n", + " 11.64511275, 8.84529662, 12.56962109, 11.60471106,\n", + " 11.26177692, 11.62119889, 9.93073487, 8.57714581,\n", + " 10.30800557, 8.18753552, 9.38936925, 11.06385326,\n", + " 13.62435651, 14.55144048, 15.32620382, 16.17922091,\n", + " 13.18469787, 12.85548043, 13.1920383 , 12.40876555,\n", + " 11.54759955, 13.09389758, 8.07989001, 10.20286989,\n", + " 8.21036196, 8.69421935, 9.40695333, 9.13243103,\n", + " 10.21486139, 11.8528738 , 8.02388597, 7.78483415,\n", + " 8.39981437, 11.52043891, 14.64896989, 13.78652668,\n", + " 12.86830854, 14.57606506, 9.48062491, 8.87380552,\n", + " 9.30743742, 8.60632491, 9.82326031, 10.27932405,\n", + " 12.26124072, 13.04312372, 64.35495448, 8.5248692 ,\n", + " 9.12889814, 7.9526372 , 8.12759137, 10.3482573 ,\n", + " 8.7727704 , 8.54050159, 8.67904162, 10.6939168 ,\n", + " 9.06492829, 9.60067344, 12.19172478, 10.08073449,\n", + " 64.18558645, 9.70786905, 8.67602444, 8.55738282,\n", + " 9.29026055, 11.10824513, 7.91631031, 8.46341848,\n", + " 7.89485145, 8.80185747, 8.96220922])" ] }, "execution_count": 22, @@ -1163,7 +1197,7 @@ { "data": { "text/plain": [ - "(array([ 0, 0, 0, 16, 105, 85, 75, 61, 40]),\n", + "(array([ 0, 0, 0, 18, 50, 41, 43, 42, 36]),\n", " array([ 6. , 6.44444444, 6.88888889, 7.33333333, 7.77777778,\n", " 8.22222222, 8.66666667, 9.11111111, 9.55555556, 10. ]))" ] @@ -1188,7 +1222,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 24, time 107.859 seconds: Counter({'Completed': 478, 'Failed': 21})izing': 1})Running': 1})\r" + "Final print 29, time 111.393 seconds: Counter({'Completed': 375, 'Failed': 24})izing': 1})Running': 1})\r" ] } ], @@ -1225,6 +1259,8 @@ " print('Final print {0:.0f}, time {1:.3f} seconds: {2}'.format(printing_counter, time.time() - start_time, \n", " str(Counter([crt_queried_job.get_status() for crt_queried_job in the_run_list]))), end=\"\\r\") \n", "\n", + " \n", + " \n", "wait_for_run_list_to_finish(run_list, plot_results=False)" ] }, @@ -1246,8 +1282,8 @@ { "data": { "text/plain": [ - "array([28, 33, 15, 45, 18, 43, 30, 31, 65, 6, 42, 16, 11, 41, 19, 8, 5,\n", - " 2, 64, 34])" + "array([ 27, 23, 21, 13, 24, 19, 22, 7, 25, 31, 10, 34, 36,\n", + " 15, 39, 8, 11, 20, 119, 121])" ] }, "execution_count": 25, @@ -1258,21 +1294,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "[244.173832 244.510378 245.027595 245.540781 247.395535 247.411761\n", - " 247.933416 248.256958 248.468753 249.724234 249.874347 250.013758\n", - " 250.53221 251.10704 251.400594 253.192625 253.421425 253.968411\n", - " 256.888013 260.331917]\n", + "[245.121065 246.873507 248.362496 250.491211 252.945049 253.106451\n", + " 254.001069 254.176103 255.789939 256.20988 256.800162 259.575836\n", + " 259.591015 259.798976 259.898521 259.956801 260.506194 278.777932\n", + " 406.332445 413.445591]\n", "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Failed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Failed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Failed' 'Completed']\n" + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed']\n" ] }, { "data": { "text/plain": [ - "array([232, 54, 195, 214, 250, 48, 490, 261, 329, 140, 336, 129, 311,\n", - " 223, 226, 370, 319, 254, 197, 85])" + "array([ 76, 181, 197, 45, 100, 328, 160, 83, 222, 335, 186, 41, 249,\n", + " 224, 334, 357, 235, 0, 305, 210])" ] }, "execution_count": 25, @@ -1283,19 +1319,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "[92.52469 92.854187 93.127771 93.19945 93.319895 93.372538 93.557287\n", - " 93.579393 93.646901 93.681486 93.890417 94.05724 94.162242 94.165297\n", - " 94.182998 94.263456 94.316783 94.400242 94.406081 94.583321]\n", - "['Completed' 'Completed' 'Completed' 'Completed' 'Failed' 'Completed'\n", - " 'Failed' 'Failed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + "[76.51845 90.802771 93.165009 93.429844 94.00823 94.132736 94.263307\n", + " 94.414542 94.495489 94.762381 95.230791 95.252626 95.30627 95.717838\n", + " 95.982519 96.311498 96.481302 96.706632 96.756483 96.924966]\n", + "['Failed' 'Failed' 'Failed' 'Completed' 'Failed' 'Failed' 'Completed'\n", + " 'Failed' 'Completed' 'Completed' 'Completed' 'Failed' 'Completed'\n", " 'Completed' 'Completed' 'Completed' 'Failed' 'Completed' 'Completed'\n", - " 'Failed' 'Completed']\n" + " 'Completed']\n" ] }, { "data": { "text/plain": [ - "(array([ 0, 0, 128, 320, 8, 1, 3, 3, 0]),\n", + "(array([ 0, 1, 61, 281, 25, 3, 0, 0, 0]),\n", " array([ 50. , 66.66666667, 83.33333333, 100. ,\n", " 116.66666667, 133.33333333, 150. , 166.66666667,\n", " 183.33333333, 200. ]))" @@ -1346,9 +1382,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "fwi_dev_conda_environment Python", "language": "python", - "name": "python3" + "name": "fwi_dev_conda_environment" }, "language_info": { "codemirror_mode": { diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 611d0598..320f40a3 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -162,9 +162,71 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Configuration loaded. Please check that the DATASET.ROOT:/data/dutchf3 points to your data location.\n", + "To modify any of the options, please edit the configuration file ./configs/patch_deconvnet_skip.yaml and reload. \n", + "\n", + "CUDNN:\n", + " BENCHMARK: True\n", + " DETERMINISTIC: False\n", + " ENABLED: True\n", + "DATASET:\n", + " CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852]\n", + " NUM_CLASSES: 6\n", + " ROOT: /data/dutchf3\n", + "GPUS: (0,)\n", + "LOG_CONFIG: logging.conf\n", + "LOG_DIR: log\n", + "MODEL:\n", + " IN_CHANNELS: 1\n", + " NAME: patch_deconvnet_skip\n", + "OUTPUT_DIR: output\n", + "PRINT_FREQ: 50\n", + "SEED: 2019\n", + "TEST:\n", + " CROSSLINE: True\n", + " INLINE: True\n", + " MODEL_PATH: /data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth\n", + " POST_PROCESSING:\n", + " CROP_PIXELS: 0\n", + " SIZE: 99\n", + " SPLIT: test1\n", + " TEST_STRIDE: 10\n", + "TRAIN:\n", + " AUGMENTATION: True\n", + " AUGMENTATIONS:\n", + " PAD:\n", + " HEIGHT: 99\n", + " WIDTH: 99\n", + " RESIZE:\n", + " HEIGHT: 99\n", + " WIDTH: 99\n", + " BATCH_SIZE_PER_GPU: 64\n", + " BEGIN_EPOCH: 0\n", + " DEPTH: none\n", + " END_EPOCH: 100\n", + " MAX_LR: 0.02\n", + " MEAN: 0.0009997\n", + " MIN_LR: 0.001\n", + " MODEL_DIR: models\n", + " MOMENTUM: 0.9\n", + " PATCH_SIZE: 99\n", + " SNAPSHOTS: 5\n", + " STD: 0.20977\n", + " STRIDE: 50\n", + " WEIGHT_DECAY: 0.0001\n", + "VALIDATION:\n", + " BATCH_SIZE_PER_GPU: 512\n", + "WORKERS: 4\n" + ] + } + ], "source": [ "with open(CONFIG_FILE, \"rt\") as f_read:\n", " config = yacs.config.load_cfg(f_read)\n", @@ -183,7 +245,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "tags": [ "parameters" @@ -205,7 +267,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -230,9 +292,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of inline slices: 401\n", + "Number of crossline slices: 701\n", + "Depth dimension : 255\n" + ] + } + ], "source": [ "# Load training data and labels\n", "train_seismic = np.load(path.join(dataset_root, \"train/train_seismic.npy\"))\n", @@ -245,9 +317,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1aadea98bda8458fbc03782571b1d4b7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Viewer(geometries=[], gradient_opacity=0.22, point_sets=[], rendered_image=" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "idx = 100\n", "x_in = train_seismic[idx, :, :].swapaxes(0, 1)\n", @@ -281,9 +381,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "x_cr = train_seismic[:, idx, :].swapaxes(0, 1)\n", "x_crl = train_labels[:, idx, :].swapaxes(0, 1)\n", @@ -300,9 +413,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-12-09 04:08:24,354 - cv_lib.utils - INFO - logging.conf configuration file was loaded.\n" + ] + } + ], "source": [ "# Set up logging\n", "load_log_configuration(config.LOG_CONFIG)\n", @@ -322,7 +443,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -371,9 +492,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "This no longer pads the volume\n" + ] + } + ], "source": [ "TrainPatchLoader = get_patch_loader(config)\n", "\n", @@ -413,7 +542,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -472,9 +601,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# create training engine\n", "trainer = create_supervised_trainer(\n", @@ -512,7 +652,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -529,9 +669,37 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-12-09 04:08:27,209 - ignite.engine.engine.Engine - INFO - Engine run starting with max_epochs=1.\n", + "2019-12-09 04:08:27,211 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.02]\n", + "2019-12-09 04:08:31,265 - ignite.engine.engine.Engine - INFO - Epoch[1] Complete. Time taken: 00:00:04\n", + "2019-12-09 04:08:32,319 - ignite.engine.engine.Engine - INFO - Engine run complete. Time taken 00:00:05\n" + ] + }, + { + "data": { + "text/plain": [ + "State:\n", + "\toutput: \n", + "\tbatch: \n", + "\tdataloader: \n", + "\tmax_epochs: 1\n", + "\tmetrics: \n", + "\titeration: 3\n", + "\tepoch: 1" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "trainer.run(train_loader, max_epochs=max_epochs)" ] @@ -567,7 +735,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -577,7 +745,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -598,7 +766,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -659,9 +827,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-12-09 04:08:36,547 - __main__ - INFO - split: test1, section: 0\n", + "2019-12-09 04:08:41,543 - __main__ - INFO - split: test1, section: 1\n", + "2019-12-09 04:08:42,067 - __main__ - INFO - split: test1, section: 2\n" + ] + } + ], "source": [ "CLASS_NAMES = [\n", " \"upper_ns\",\n", @@ -720,9 +898,34 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pixel Acc: 0.522\n", + " upper_ns_accuracy 0.000\n", + " middle_ns_accuracy 0.000\n", + " lower_ns_accuracy 0.999\n", + " rijnland_chalk_accuracy 0.000\n", + " scruff_accuracy 0.001\n", + " zechstein_accuracy nan\n", + "Mean Class Acc: 0.200\n", + "Freq Weighted IoU: 0.273\n", + "Mean IoU: 0.105\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "invalid value encountered in true_divide\n", + "invalid value encountered in true_divide\n" + ] + } + ], "source": [ "# get scores\n", "score, _ = running_metrics_split.get_scores()\n", @@ -753,11 +956,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": { "scrolled": false }, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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AACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAA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0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAnihKmAAAgAElEQVQATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACg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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "fig = plt.figure(figsize=(15,50))\n", "\n", @@ -786,9 +1002,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "Python 3", + "display_name": "seismic-interpretation", "language": "python", - "name": "python3" + "name": "seismic-interpretation" }, "language_info": { "codemirror_mode": { @@ -800,7 +1016,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 3529e426..cf6366fd 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -632,9 +632,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "Python 3", + "display_name": "seismic-interpretation", "language": "python", - "name": "python3" + "name": "seismic-interpretation" }, "language_info": { "codemirror_mode": { @@ -646,7 +646,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.7" } }, "nbformat": 4, From 051c809b5f61ad3c1ec0f3877974381b8512802f Mon Sep 17 00:00:00 2001 From: vapaunic <15053814+vapaunic@users.noreply.github.com> Date: Mon, 16 Dec 2019 13:25:11 +0000 Subject: [PATCH 148/207] Minor fix: broken links in README (#120) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a82fb2d5..68e01eb2 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,9 @@ python -m ipykernel install --user --name seismic-interpretation We also provide scripts for a number of experiments we conducted using different segmentation approaches. These experiments are available under `experiments/interpretation`, and can be used as examples. Within each experiment start from the `train.sh` and `test.sh` scripts under the `local/` (single GPU) and `distributed/` (multiple GPUs) directories, which invoke the corresponding python scripts, `train.py` and `test.py`. Take a look at the experiment configurations (see Experiment Configuration Files section below) for experiment options and modify if necessary. Please refer to individual experiment README files for more information. -- [Penobscot](experiments/interpretation/penobscot/local/README.md) -- [F3 Netherlands Patch](experiments/interpretation/dutchf3_patch/local/README.md) -- [F3 Netherlands Section](experiments/interpretation/dutchf3_section/local/README.md) +- [Penobscot](experiments/interpretation/penobscot/README.md) +- [F3 Netherlands Patch](experiments/interpretation/dutchf3_patch/README.md) +- [F3 Netherlands Section](experiments/interpretation/dutchf3_section/README.md) #### Configuration Files We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manage configuration options for the experiments. There are three ways to pass arguments to the experiment scripts (e.g. train.py or test.py): From 47ec9c9ccc93e2f682e35c1af21a7417b2433475 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 16 Dec 2019 10:59:01 -0500 Subject: [PATCH 149/207] fully-run notebooks links and fixed contrib voxel models (#123) * added README documentation per bug bush feedback * added missing tests * - added notebook links - made sure orginal voxel2pixel code runs * update ignite port of texturenet * resolved merge conflict * formatting change --- README.md | 4 ++-- .../experiments/interpretation/dutchf3_voxel/README.md | 8 +++++--- .../dutchf3_voxel/configs/texture_net.yaml | 2 +- .../{ => interpretation}/voxel2pixel/README.md | 10 +++++++--- .../{ => interpretation}/voxel2pixel/batch.py | 0 .../{ => interpretation}/voxel2pixel/data.py | 0 .../{ => interpretation}/voxel2pixel/tb_logger.py | 0 .../{ => interpretation}/voxel2pixel/test_parallel.py | 4 ++-- .../{ => interpretation}/voxel2pixel/texture_net.py | 0 .../{ => interpretation}/voxel2pixel/train.py | 2 +- .../{ => interpretation}/voxel2pixel/utils.py | 0 {scripts => contrib/scripts}/get_F3_voxel.sh | 9 ++++++--- 12 files changed, 24 insertions(+), 15 deletions(-) rename contrib/experiments/{ => interpretation}/voxel2pixel/README.md (82%) rename contrib/experiments/{ => interpretation}/voxel2pixel/batch.py (100%) rename contrib/experiments/{ => interpretation}/voxel2pixel/data.py (100%) rename contrib/experiments/{ => interpretation}/voxel2pixel/tb_logger.py (100%) rename contrib/experiments/{ => interpretation}/voxel2pixel/test_parallel.py (99%) rename contrib/experiments/{ => interpretation}/voxel2pixel/texture_net.py (100%) rename contrib/experiments/{ => interpretation}/voxel2pixel/train.py (99%) rename contrib/experiments/{ => interpretation}/voxel2pixel/utils.py (100%) rename {scripts => contrib/scripts}/get_F3_voxel.sh (70%) diff --git a/README.md b/README.md index 68e01eb2..11ea4c22 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ If you run into any problems, chances are your problem has already been solved i ### Pre-run notebooks Notebooks stored in the repository have output intentionally displaced - you can find full auto-generated versions of the notebooks here: -- [HRNet Penobscot demo](https://deepseismicstore.blob.core.windows.net/shared/HRNet_Penobscot_demo_notebook.ipynb?sp=r&st=2019-12-13T05:11:15Z&se=2019-12-13T13:11:15Z&spr=https&sv=2019-02-02&sr=b&sig=UFwueAhZcJn8o7g1nzD4GGS7lKv9lHIJXJb%2B7kbyUZc%3D) -- [Dutch F3 dataset](https://deepseismicstore.blob.core.windows.net/shared/F3_block_training_and_evaluation_local.ipynb?sp=r&st=2019-12-13T05:10:17Z&se=2019-12-13T13:10:17Z&spr=https&sv=2019-02-02&sr=b&sig=UUDCulVUddhqfxN0%2FqcdLZ7DmcGnIYk0j%2BlM0EN8WiM%3D) +- **HRNet Penobscot demo**: [[HTML](https://deepseismicstore.blob.core.windows.net/shared/HRNet_Penobscot_demo_notebook.html)] [[.ipynb](https://deepseismicstore.blob.core.windows.net/shared/HRNet_Penobscot_demo_notebook.ipynb)] +- **Dutch F3 dataset**: [[HTML](https://deepseismicstore.blob.core.windows.net/shared/F3_block_training_and_evaluation_local.html)] [[.ipynb](https://deepseismicstore.blob.core.windows.net/shared/F3_block_training_and_evaluation_local.ipynb)] ### Azure Machine Learning [Azure Machine Learning](https://docs.microsoft.com/en-us/azure/machine-learning/) enables you to train and deploy your machine learning models and pipelines at scale, ane leverage open-source Python frameworks, such as PyTorch, TensorFlow, and scikit-learn. If you are looking at getting started with using the code in this repository with Azure Machine Learning, refer to [Azure Machine Learning How-to](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml) to get started. diff --git a/contrib/experiments/interpretation/dutchf3_voxel/README.md b/contrib/experiments/interpretation/dutchf3_voxel/README.md index 0a016ff8..f794fb37 100644 --- a/contrib/experiments/interpretation/dutchf3_voxel/README.md +++ b/contrib/experiments/interpretation/dutchf3_voxel/README.md @@ -1,15 +1,17 @@ -First, make sure that `/mnt/dutch_f3` folder exists and you have write access. +First, make sure that `${HOME}/data/dutch_f3` folder exists and you have write access. Next, to get the main input dataset which is the [Dutch F3 dataset](https://terranubis.com/datainfo/Netherlands-Offshore-F3-Block-Complete), navigate to [MalenoV](https://github.com/bolgebrygg/MalenoV) project website and follow the links (which will lead to [this](https://drive.google.com/drive/folders/0B7brcf-eGK8CbGhBdmZoUnhiTWs) download). Save this file as -`/mnt/dutch_f3/data.segy` +`${HOME}/data/dutch_f3/data.segy` To download the train and validation masks, from the root of the repo, run ```bash -./scripts/get_F3_voxel.sh /mnt/dutch_f3 +./contrib/scripts/get_F3_voxel.sh ${HOME}/data/dutch_f3 ``` This will also download train and validation masks to the same location as data.segy. That's it! + +To run the training script, run `python train.py --cfg=configs/texture_net.yaml`. diff --git a/contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml b/contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml index bf08ca7e..aeeffb86 100644 --- a/contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml +++ b/contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml @@ -15,7 +15,7 @@ WINDOW_SIZE: 65 DATASET: NUM_CLASSES: 2 - ROOT: /mnt/dutchf3 + ROOT: /home/maxkaz/data/dutchf3 FILENAME: data.segy MODEL: diff --git a/contrib/experiments/voxel2pixel/README.md b/contrib/experiments/interpretation/voxel2pixel/README.md similarity index 82% rename from contrib/experiments/voxel2pixel/README.md rename to contrib/experiments/interpretation/voxel2pixel/README.md index a1b78b73..8e1f743c 100644 --- a/contrib/experiments/voxel2pixel/README.md +++ b/contrib/experiments/interpretation/voxel2pixel/README.md @@ -10,15 +10,19 @@ There is also an EAGE E-lecture which you can watch: [*Seismic interpretation with deep learning*](https://www.youtube.com/watch?v=lm85Ap4OstM) (YouTube) ### Setup to get started -- make sure you follow `SETUP.md` file in root of repo to install all the proper dependencies. -- download the data by running `scrips/get_F3_voxel.sh` from the root of this repo, i.e. from DeepSeismic folder. +- make sure you follow `README.md` file in root of repo to install all the proper dependencies. +- downgrade TensorFlow and pyTorch's CUDA: + - downgrade TensorFlow by running `pip install tensorflow-gpu==1.14` + - make sure pyTorch uses downgraded CUDA `pip install torch==1.3.1+cu92 torchvision==0.4.2+cu92 -f https://download.pytorch.org/whl/torch_stable.html` +- download the data by running `contrib/scrips/get_F3_voxel.sh` from the `contrib` folder of this repo. This will download the training and validation labels/masks. - to get the main input dataset which is the [Dutch F3 dataset](https://terranubis.com/datainfo/Netherlands-Offshore-F3-Block-Complete), navigate to [MalenoV](https://github.com/bolgebrygg/MalenoV) project website and follow the links (which will lead to [this](https://drive.google.com/drive/folders/0B7brcf-eGK8CbGhBdmZoUnhiTWs) download). Save this file as `interpretation/voxel2pixel/F3/data.segy` - +If you want to revert downgraded packages, just run `conda env update -f environment/anaconda/local/environment.yml` from the root folder of the repo. + ### Monitoring progress with TensorBoard - from the `voxel2pixel` directory, run `tensorboard --logdir='log'` (all runtime logging information is written to the `log` folder
diff --git a/contrib/experiments/voxel2pixel/batch.py b/contrib/experiments/interpretation/voxel2pixel/batch.py similarity index 100% rename from contrib/experiments/voxel2pixel/batch.py rename to contrib/experiments/interpretation/voxel2pixel/batch.py diff --git a/contrib/experiments/voxel2pixel/data.py b/contrib/experiments/interpretation/voxel2pixel/data.py similarity index 100% rename from contrib/experiments/voxel2pixel/data.py rename to contrib/experiments/interpretation/voxel2pixel/data.py diff --git a/contrib/experiments/voxel2pixel/tb_logger.py b/contrib/experiments/interpretation/voxel2pixel/tb_logger.py similarity index 100% rename from contrib/experiments/voxel2pixel/tb_logger.py rename to contrib/experiments/interpretation/voxel2pixel/tb_logger.py diff --git a/contrib/experiments/voxel2pixel/test_parallel.py b/contrib/experiments/interpretation/voxel2pixel/test_parallel.py similarity index 99% rename from contrib/experiments/voxel2pixel/test_parallel.py rename to contrib/experiments/interpretation/voxel2pixel/test_parallel.py index cd7bfbc2..4e095afc 100644 --- a/contrib/experiments/voxel2pixel/test_parallel.py +++ b/contrib/experiments/interpretation/voxel2pixel/test_parallel.py @@ -7,7 +7,7 @@ import os # set default number of GPUs which are discoverable -N_GPU = 1 +N_GPU = 4 DEVICE_IDS = list(range(N_GPU)) os.environ["CUDA_VISIBLE_DEVICES"] = ",".join([str(x) for x in DEVICE_IDS]) @@ -245,7 +245,7 @@ def main_worker(gpu, ngpus_per_node, args): parser = argparse.ArgumentParser(description="Seismic Distributed Scoring") -parser.add_argument("-d", "--data", default="/mnt/dutchf3", type=str, help="default dataset folder name") +parser.add_argument("-d", "--data", default="/home/maxkaz/data/dutchf3", type=str, help="default dataset folder name") parser.add_argument( "-s", "--slice", diff --git a/contrib/experiments/voxel2pixel/texture_net.py b/contrib/experiments/interpretation/voxel2pixel/texture_net.py similarity index 100% rename from contrib/experiments/voxel2pixel/texture_net.py rename to contrib/experiments/interpretation/voxel2pixel/texture_net.py diff --git a/contrib/experiments/voxel2pixel/train.py b/contrib/experiments/interpretation/voxel2pixel/train.py similarity index 99% rename from contrib/experiments/voxel2pixel/train.py rename to contrib/experiments/interpretation/voxel2pixel/train.py index 8d5d3ddc..74f664ca 100644 --- a/contrib/experiments/voxel2pixel/train.py +++ b/contrib/experiments/interpretation/voxel2pixel/train.py @@ -15,7 +15,7 @@ import utils # Parameters -ROOT_PATH = "/mnt/dutchf3" +ROOT_PATH = "/home/maxkaz/data/dutchf3" INPUT_VOXEL = "data.segy" TRAIN_MASK = "inline_339.png" VAL_MASK = "inline_405.png" diff --git a/contrib/experiments/voxel2pixel/utils.py b/contrib/experiments/interpretation/voxel2pixel/utils.py similarity index 100% rename from contrib/experiments/voxel2pixel/utils.py rename to contrib/experiments/interpretation/voxel2pixel/utils.py diff --git a/scripts/get_F3_voxel.sh b/contrib/scripts/get_F3_voxel.sh similarity index 70% rename from scripts/get_F3_voxel.sh rename to contrib/scripts/get_F3_voxel.sh index 83431338..850d73f8 100755 --- a/scripts/get_F3_voxel.sh +++ b/contrib/scripts/get_F3_voxel.sh @@ -2,19 +2,22 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -echo "Downloading Dutch F3 from https://github.com/bolgebrygg/MalenoV" +echo "Make sure you also download Dutch F3 data from https://github.com/bolgebrygg/MalenoV" # fetch Dutch F3 from Malenov project. # wget https://drive.google.com/open?id=0B7brcf-eGK8CUUZKLXJURFNYeXM -O interpretation/voxel2pixel/F3/data.segy if [ $# -eq 0 ] then - downdirtrain='interpretation/voxel2pixel/F3/train' - downdirval='interpretation/voxel2pixel/F3/val' + downdirtrain='experiments/interpretation/voxel2pixel/F3/train' + downdirval='experiments/interpretation/voxel2pixel/F3/val' else downdirtrain=$1 downdirval=$1 fi +mkdir -p ${downdirtrain} +mkdir -p ${downdirval} + echo "Downloading train label to $downdirtrain and validation label to $downdirval" wget https://github.com/waldeland/CNN-for-ASI/raw/master/F3/train/inline_339.png -O ${downdirtrain}/inline_339.png wget https://github.com/waldeland/CNN-for-ASI/raw/master/F3/val/inline_405.png -O ${downdirval}/inline_405.png From fc3369261c815a326158d97e49fc4e2d263e071a Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 17 Dec 2019 00:05:30 +0000 Subject: [PATCH 150/207] Adds reproduction instructions to readme (#122) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update main_build.yml for Azure Pipelines * Update main_build.yml for Azure Pipelines * BUILD: added build status badges (#6) * Adds dataloader for numpy datasets as well as demo pipeline for such a dataset (#7) * Finished version of numpy data loader * Working training script for demo * Adds the new metrics * Fixes docstrings and adds header * Removing extra setup.py * Log config file now experiment specific (#8) * Merging work on salt dataset * Adds computer vision to dependencies * Updates dependecies * Update * Updates the environemnt files * Updates readme and envs * Initial running version of dutchf3 * INFRA: added structure templates. * VOXEL: initial rough code push - need to clean up before PRing. * Working version * Working version before refactor * quick minor fixes in README * 3D SEG: first commit for PR. * 3D SEG: removed data files to avoid redistribution. * Updates * 3D SEG: restyled batch file, moving onto others. * Working HRNet * 3D SEG: finished going through Waldeland code * Updates test scripts and makes it take processing arguments * minor update * Fixing imports * Refactoring the experiments * Removing .vscode * Updates gitignore * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * added instructions for running f3dutch experiments, and fixed some issues in prepare_data.py script * minor wording fix * minor wording fix * enabled splitting dataset into sections, rather than only patches * enabled splitting dataset into sections, rather than only patches * merged duplicate ifelse blocks * merged duplicate ifelse blocks * refactored prepare_data.py * refactored prepare_data.py * added scripts for section train test * added scripts for section train test * section train/test works for single channel input * section train/test works for single channel input * Merged PR 174: F3 Dutch README, and fixed issues in prepare_data.py This PR includes the following changes: - added README instructions for running f3dutch experiments - prepare_dataset.py didn't work for creating section-based splits, so I fixed a few issues. There are no changes to the patch-based splitting logic. - ran black formatter on the file, which created all the formatting changes (sorry!) * Merged PR 204: Adds loaders to deepseismic from cv_lib * train and test script for section based training/testing * train and test script for section based training/testing * Merged PR 209: changes to section loaders in data.py Changes in this PR will affect patch scripts as well. The following are required changes in patch scripts: - get_train_loader() in train.py should be changed to get_patch_loader(). I created separate function to load section and patch loaders. - SectionLoader now swaps H and W dims. When loading test data in patch, this line can be removed (and tested) from test.py h, w = img.shape[-2], img.shape[-1] # height and width * Merged PR 210: BENCHMARKS: added placeholder for benchmarks. BENCHMARKS: added placeholder for benchmarks. * Merged PR 211: Fixes issues left over from changes to data.py * removing experiments from deep_seismic, following the new struct * removing experiments from deep_seismic, following the new struct * Merged PR 220: Adds Horovod and fixes Add Horovod training script Updates dependencies in Horovod docker file Removes hard coding of path in data.py * section train/test scripts * section train/test scripts * Add cv_lib to repo and updates instructions * Add cv_lib to repo and updates instructions * Removes data.py and updates readme * Removes data.py and updates readme * Updates requirements * Updates requirements * Merged PR 222: Moves cv_lib into repo and updates setup instructions * renamed train/test scripts * renamed train/test scripts * train test works on alaudah section experiments, a few minor bugs left * train test works on alaudah section experiments, a few minor bugs left * cleaning up loaders * cleaning up loaders * Merged PR 236: Cleaned up dutchf3 data loaders @ , @ , @ , please check out if this PR will affect your experiments. The main change is with the initialization of sections/patches attributes of loaders. Previously, we were unnecessarily assigning all train/val splits to train loaders, rather than only those belonging to the given split for that loader. Similar for test loaders. This will affect your code if you access these attributes. E.g. if you have something like this in your experiments: ``` train_set = TrainPatchLoader(…) patches = train_set.patches[train_set.split] ``` or ``` train_set = TrainSectionLoader(…) sections = train_set.sections[train_set.split] ``` * training testing for sections works * training testing for sections works * minor changes * minor changes * reverting changes on dutchf3/local/default.py file * reverting changes on dutchf3/local/default.py file * added config file * added config file * Updates the repo with preliminary results for 2D segmentation * Merged PR 248: Experiment: section-based Alaudah training/testing This PR includes the section-based experiments on dutchf3 to replicate Alaudah's work. No changes were introduced to the code outside this experiment. * Merged PR 253: Waldeland based voxel loaders and TextureNet model Related work items: #16357 * Merged PR 290: A demo notebook on local train/eval on F3 data set Notebook and associated files + minor change in a patch_deconvnet_skip.py model file. Related work items: #17432 * Merged PR 312: moved dutchf3_section to experiments/interpretation moved dutchf3_section to experiments/interpretation Related work items: #17683 * Merged PR 309: minor change to README to reflect the changes in prepare_data script minor change to README to reflect the changes in prepare_data script Related work items: #17681 * Merged PR 315: Removing voxel exp Related work items: #17702 * sync with new experiment structure * sync with new experiment structure * added a logging handler for array metrics * added a logging handler for array metrics * first draft of metrics based on the ignite confusion matrix * first draft of metrics based on the ignite confusion matrix * metrics now based on ignite.metrics * metrics now based on ignite.metrics * modified patch train.py with new metrics * modified patch train.py with new metrics * Merged PR 361: VOXEL: fixes to original voxel2pixel code to make it work with the rest of the repo. Realized there was one bug in the code and the rest of the functions did not work with the different versions of libraries which we have listed in the conda yaml file. Also updated the download script. Related work items: #18264 * modified metrics with ignore_index * modified metrics with ignore_index * Merged PR 405: minor mods to notebook, more documentation A very small PR - Just a few more lines of documentation in the notebook, to improve clarity. Related work items: #17432 * Merged PR 368: Adds penobscot Adds for penobscot - Dataset reader - Training script - Testing script - Section depth augmentation - Patch depth augmentation - Iinline visualisation for Tensorboard Related work items: #14560, #17697, #17699, #17700 * Merged PR 407: Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Azure ML SDK Version: 1.0.65; running devito in AzureML Estimators Related work items: #16362 * Merged PR 452: decouple docker image creation from azureml removed all azureml dependencies from 010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb All other changes are due to trivial reruns Related work items: #18346 * Merged PR 512: Pre-commit hooks for formatting and style checking Opening this PR to start the discussion - I added the required dotenv files and instructions for setting up pre-commit hooks for formatting and style checking. For formatting, we are using black, and style checking flake8. The following files are added: - .pre-commit-config.yaml - defines git hooks to be installed - .flake8 - settings for flake8 linter - pyproject.toml - settings for black formatter The last two files define the formatting and linting style we want to enforce on the repo. All of us would set up the pre-commit hooks locally, so regardless of what formatting/linting settings we have in our local editors, the settings specified by the git hooks would still be enforced prior to the commit, to ensure consistency among contributors. Some questions to start the discussion: - Do you want to change any of the default settings in the dotenv files - like the line lengths, error messages we exclude or include, or anything like that. - Do we want to have a requirements-dev.txt file for contributors? This setup uses pre-commit package, I didn't include it in the environment.yaml file, but instead instructed the user to install it in the CONTRIBUTING.MD file. - Once you have the hooks installed, it will only affect the files you are committing in the future. A big chunk of our codebase does not conform to the formatting/style settings. We will have to run the hooks on the codebase retrospectively. I'm happy to do that, but it will create many changes and a significant looking PR :) Any thoughts on how we should approach this? Thanks! Related work items: #18350 * Merged PR 513: 3D training script for Waldeland's model with Ignite Related work items: #16356 * Merged PR 565: Demo notebook updated with 3D graph Changes: 1) Updated demo notebook with the 3D visualization 2) Formatting changes due to new black/flake8 git hook Related work items: #17432 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * Merged PR 341: Tests for cv_lib/metrics This PR is dependent on the tests created in the previous branch !333. That's why the PR is to merge tests into vapaunic/metrics branch (so the changed files below only include the diff between these two branches. However, I can change this once the vapaunic/metrics is merged. I created these tests under cv_lib/ since metrics are a part of that library. I imagine we will have tests under deepseismic_interpretation/, and the top level /tests for integration testing. Let me know if you have any comments on this test, or the structure. As agreed, I'm using pytest. Related work items: #16955 * merged tests into this branch * merged tests into this branch * Merged PR 569: Minor PR: change to pre-commit configuration files Related work items: #18350 * Merged PR 586: Purging unused files and experiments Purging unused files and experiments Related work items: #20499 * moved prepare data under scripts * moved prepare data under scripts * removed untested model configs * removed untested model configs * fixed weird bug in penobscot data loader * fixed weird bug in penobscot data loader * penobscot experiments working for hrnet, seresnet, no depth and patch depth * penobscot experiments working for hrnet, seresnet, no depth and patch depth * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * removed a section loader bug in the penobscot loader * fixed bugs in my previous 'fix' * fixed bugs in my previous 'fix' * removed redundant _open_mask from subclasses * removed redundant _open_mask from subclasses * Merged PR 601: Fixes to penobscot experiments A few changes: - Instructions in README on how to download and process Penobscot and F3 2D data sets - moved prepare_data scripts to the scripts/ directory - fixed a weird issue with a class method in Penobscot data loader - fixed a bug in section loader (_add_extra_channel in section loader was not necessary and was causing an issue) - removed config files that were not tested or working in Penobscot experiments - modified default.py so it's working if train.py ran without a config file Related work items: #20694 * Merged PR 605: added common metrics to Waldeland model in Ignite Related work items: #19550 * Removed redundant extract_metric_from * Removed redundant extract_metric_from * formatting changes in metrics * formatting changes in metrics * modified penobscot experiment to use new local metrics * modified penobscot experiment to use new local metrics * modified section experimen to pass device to metrics * modified section experimen to pass device to metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * moved metrics out of dutchf3, modified distributed to work with the new metrics * fixed other experiments after new metrics * fixed other experiments after new metrics * removed apex metrics from distributed train.py * removed apex metrics from distributed train.py * added ignite-based metrics to dutch voxel experiment * added ignite-based metrics to dutch voxel experiment * removed apex metrics * removed apex metrics * modified penobscot test script to use new metrics * pytorch-ignite pre-release with new metrics until stable available * removed cell output from the F3 notebook * deleted .vscode * modified metric import in test_metrics.py * separated metrics out as a module * relative logger file path, modified section experiment * removed the REPO_PATH from init * created util logging function, and moved logging file to each experiment * modified demo experiment * modified penobscot experiment * modified dutchf3_voxel experiment * no logging in voxel2pixel * modified dutchf3 patch local experiment * modified patch distributed experiment * modified interpretation notebook * minor changes to comments * Updates notebook to use itkwidgets for interactive visualisation * Further updates * Fixes merge conflicts * removing files * Adding reproduction experiment instructions to readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 11ea4c22..479e8e37 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,14 @@ The dataset was split 70% training, 10% validation and 20% test. The results bel ![Best Penobscot SEResNet](assets/penobscot_seresnet_best.png "Best performing inlines, Mask and Predictions from SEResNet") ![Worst Penobscot SEResNet](assets/penobscot_seresnet_worst.png "Worst performing inlines Mask and Predictions from SEResNet") +#### Reproduce benchmarks +In order to reproduce the benchmarks you will need to navigate to the [experiments](experiments) folder. In there each of the experiments +are split into different folders. To run the Netherlands F3 experiment navigate to the [dutchf3_patch/local](experiments/dutchf3_patch/local) folder. In there is a training script [([train.sh](experiments/dutchf3_patch/local/train.sh)) +which will run the training for any configuration you pass in. Once you have run the training you will need to run the [test.sh](experiments/dutchf3_patch/local/test.sh) script. Make sure you specify +the path to the best performing model from your training run, either by passing it in as an argument or altering the YACS config file. + +To reproduce the benchmarks +for the Penobscot dataset follow the same instructions but navigate to the [penobscot](penobscot) folder. #### Scripts - [parallel_training.sh](scripts/parallel_training.sh): Script to launch multiple jobs in parallel. Used mainly for local hyperparameter tuning. Look at the script for further instructions From 335e90f9a770fb28b62e509c433421a92cbb8934 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 16 Dec 2019 21:39:48 -0500 Subject: [PATCH 151/207] checking in ablation study from ilkarman (#124) tests pass but final results aren't communicated to github. No way to trigger another commit other than to do a dummy commit --- contrib/scripts/README.md | 6 ++++++ contrib/scripts/ablation.sh | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 contrib/scripts/README.md create mode 100755 contrib/scripts/ablation.sh diff --git a/contrib/scripts/README.md b/contrib/scripts/README.md new file mode 100644 index 00000000..836a05dc --- /dev/null +++ b/contrib/scripts/README.md @@ -0,0 +1,6 @@ +This folder contains a variety of scripts which might be useful. + +# Ablation Study + +Contained in `ablation.sh`, the script demonstrates running the HRNet model with various patch sizes. + diff --git a/contrib/scripts/ablation.sh b/contrib/scripts/ablation.sh new file mode 100755 index 00000000..81fcdaa6 --- /dev/null +++ b/contrib/scripts/ablation.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +source activate seismic-interpretation + +# Patch_Size 100: Patch vs Section Depth +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=100 +python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 100 --cfg 'configs/hrnet.yaml' +python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 100 --cfg 'configs/hrnet.yaml' + +# Patch_Size 150: Patch vs Section Depth +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=150 +python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 150 --cfg 'configs/hrnet.yaml' +python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 150 --cfg 'configs/hrnet.yaml' + +# Patch_Size 200: Patch vs Section Depth +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=200 +python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 200 --cfg 'configs/hrnet.yaml' +python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 200 --cfg 'configs/hrnet.yaml' + +# Patch_Size 250: Patch vs Section Depth +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=250 +python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 250 TRAIN.AUGMENTATIONS.RESIZE.HEIGHT 250 TRAIN.AUGMENTATIONS.RESIZE.WIDTH 250 --cfg 'configs/hrnet.yaml' +python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 250 TRAIN.AUGMENTATIONS.RESIZE.HEIGHT 250 TRAIN.AUGMENTATIONS.RESIZE.WIDTH 250 --cfg 'configs/hrnet.yaml' + From 24384167c2e1ab4f52b43f83ff9b0c589e0576d6 Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Wed, 18 Dec 2019 14:32:47 -0500 Subject: [PATCH 152/207] minor bug in 000 nb; sdk.v1.0.79; FROM continuumio/miniconda3:4.7.12 (#126) --- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 16 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 178 ++++++------- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 22 +- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 249 ++++++++---------- 4 files changed, 219 insertions(+), 246 deletions(-) diff --git a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index c9a17f1e..2c58effb 100755 --- a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -64,7 +64,8 @@ "from azureml.core.compute import ComputeTarget, AmlCompute\n", "from azureml.core.compute_target import ComputeTargetException\n", "import platform, dotenv\n", - "import pathlib" + "import pathlib\n", + "import subprocess" ] }, { @@ -532,8 +533,8 @@ " workspace_name = crt_workspace_name,\n", " auth=project_utils.get_auth(dotenv_file_path))\n", " print(\"Workspace configuration loading succeeded. \")\n", - "# ws1.write_config(path=os.path.join(os.getcwd(), os.path.join(*([workspace_config_dir]))),\n", - "# file_name=workspace_config_file)\n", + " ws1.write_config(path=os.path.join(os.getcwd(), os.path.join(*([workspace_config_dir]))),\n", + " file_name=workspace_config_file)\n", " del ws1 # ws will be (re)created later using from_config() function\n", "except Exception as e :\n", " print('Exception msg: {}'.format(str(e )))\n", @@ -861,10 +862,7 @@ "metadata": {}, "outputs": [], "source": [ - "# create_ACR_FLAG=False\n", - "if create_ACR_FLAG:\n", - " import subprocess\n", - " cli_command = 'az acr credential show -n '+acr_name\n", + "cli_command = 'az acr credential show -n '+acr_name\n", "\n", "acr_username = subprocess.Popen(cli_command+' --query username',shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE).\\\n", "communicate()[0].decode(\"utf-8\").split()[0].strip('\\\"')\n", @@ -901,9 +899,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python [conda env:fwi_dev_conda_environment] *", + "display_name": "fwi_dev_conda_environment Python", "language": "python", - "name": "conda-env-fwi_dev_conda_environment-py" + "name": "fwi_dev_conda_environment" }, "language_info": { "codemirror_mode": { diff --git a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb index aac30038..5d78b5c6 100755 --- a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -65,7 +65,7 @@ { "data": { "text/plain": [ - "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" + "'Linux-4.15.0-1064-azure-x86_64-with-debian-stretch-sid'" ] }, "execution_count": 3, @@ -199,7 +199,7 @@ "outputs": [], "source": [ "# azureml_sdk_version set here must match azureml sdk version pinned in conda env file written to conda_common_file_path below\n", - "azureml_sdk_version = '1.0.76' " + "azureml_sdk_version = '1.0.79' " ] }, { @@ -222,7 +222,7 @@ { "data": { "text/plain": [ - "(True, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', 'sdk.v1.0.76')" + "(True, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', 'sdk.v1.0.79')" ] }, "execution_count": 9, @@ -267,7 +267,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" ] }, "execution_count": 10, @@ -277,7 +277,7 @@ { "data": { "text/plain": [ - "'conda_env_fwi01_azureml_sdk.v1.0.76.yml'" + "'conda_env_fwi01_azureml_sdk.v1.0.79.yml'" ] }, "execution_count": 10, @@ -287,7 +287,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.76.yml'" + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.79.yml'" ] }, "execution_count": 10, @@ -317,7 +317,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76'" + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.79'" ] }, "execution_count": 10, @@ -410,7 +410,7 @@ " - toolz\n", " - pip:\n", " - anytree # required by devito\n", - " - azureml-sdk[notebooks,automl]==1.0.76\n", + " - azureml-sdk[notebooks,automl]==1.0.79\n", " - codepy # required by devito\n", " - papermill[azure]\n", " - pyrevolve # required by devito" @@ -425,14 +425,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76\n" + "Writing /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.79\n" ] } ], "source": [ "%%writefile $docker_file_path \n", "\n", - "FROM continuumio/miniconda3:4.7.10 \n", + "FROM continuumio/miniconda3:4.7.12 \n", "MAINTAINER George Iordanescu \n", "\n", "RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \\\n", @@ -478,7 +478,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.76.yml'" + "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.79.yml'" ] }, "execution_count": 13, @@ -490,9 +490,9 @@ "output_type": "stream", "text": [ "total 12\r\n", - "-rw-rw-r-- 1 loginvm022 loginvm022 725 Dec 6 15:26 conda_env_fwi01_azureml_sdk.v1.0.76.yml\r\n", - "-rw-rw-r-- 1 loginvm022 loginvm022 725 Dec 6 15:26 conda_env_fwi01_azureml.yml\r\n", - "-rw-rw-r-- 1 loginvm022 loginvm022 1073 Dec 6 15:26 Dockerfile_fwi01_azureml_sdk.v1.0.76\r\n" + "-rw-rw-r-- 1 loginvm0_1 loginvm0_1 725 Dec 17 19:25 conda_env_fwi01_azureml_sdk.v1.0.79.yml\r\n", + "-rw-rw-r-- 1 loginvm0_1 loginvm0_1 725 Dec 17 19:25 conda_env_fwi01_azureml.yml\r\n", + "-rw-rw-r-- 1 loginvm0_1 loginvm0_1 1073 Dec 17 19:25 Dockerfile_fwi01_azureml_sdk.v1.0.79\r\n" ] } ], @@ -510,7 +510,7 @@ { "data": { "text/plain": [ - "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 -f /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.76 /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build '" + "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79 -f /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.79 /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build '" ] }, "execution_count": 14, @@ -522,9 +522,9 @@ "text/plain": [ "['Sending build context to Docker daemon 6.144kB',\n", " '',\n", - " 'Step 1/15 : FROM continuumio/miniconda3:4.7.10',\n", - " '4.7.10: Pulling from continuumio/miniconda3',\n", - " '1ab2bdfe9778: Pulling fs layer']" + " 'Step 1/15 : FROM continuumio/miniconda3:4.7.12',\n", + " '4.7.12: Pulling from continuumio/miniconda3',\n", + " 'b8f262c62ec6: Pulling fs layer']" ] }, "execution_count": 14, @@ -534,11 +534,11 @@ { "data": { "text/plain": [ - "[' ---> Running in 00c2824f0cd3',\n", - " 'Removing intermediate container 00c2824f0cd3',\n", - " ' ---> 48fb03897096',\n", - " 'Successfully built 48fb03897096',\n", - " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76']" + "[' ---> Running in b56a4fef410b',\n", + " 'Removing intermediate container b56a4fef410b',\n", + " ' ---> 74c9cf8d9517',\n", + " 'Successfully built 74c9cf8d9517',\n", + " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79']" ] }, "execution_count": 14, @@ -575,7 +575,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" ] }, "execution_count": 15, @@ -595,7 +595,7 @@ { "data": { "text/plain": [ - "b'/\\n1.0.76\\n'" + "b'/\\n1.0.79\\n'" ] }, "execution_count": 15, @@ -656,7 +656,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -674,7 +674,7 @@ "' python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'" ] }, - "execution_count": 17, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -698,16 +698,16 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/ \"'" + "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/ \"'" ] }, - "execution_count": 18, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" }, @@ -721,46 +721,46 @@ "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", "\n", "total 560\n", - "-rw-r--r-- 1 root root 11521 Dec 6 15:26 conftest.py\n", - "-rw-r--r-- 1 root root 6006 Dec 6 15:26 test_adjoint.py\n", - "-rw-r--r-- 1 root root 14586 Dec 6 15:26 test_autotuner.py\n", - "-rw-r--r-- 1 root root 7538 Dec 6 15:26 test_builtins.py\n", - "-rw-r--r-- 1 root root 24415 Dec 6 15:26 test_caching.py\n", - "-rw-r--r-- 1 root root 9721 Dec 6 15:26 test_checkpointing.py\n", - "-rw-r--r-- 1 root root 1095 Dec 6 15:26 test_constant.py\n", - "-rw-r--r-- 1 root root 55954 Dec 6 15:26 test_data.py\n", - "-rw-r--r-- 1 root root 481 Dec 6 15:26 test_dependency_bugs.py\n", - "-rw-r--r-- 1 root root 16331 Dec 6 15:26 test_derivatives.py\n", - "-rw-r--r-- 1 root root 1473 Dec 6 15:26 test_differentiable.py\n", - "-rw-r--r-- 1 root root 30846 Dec 6 15:26 test_dimension.py\n", - "-rw-r--r-- 1 root root 24838 Dec 6 15:26 test_dle.py\n", - "-rw-r--r-- 1 root root 1169 Dec 6 15:26 test_docstrings.py\n", - "-rw-r--r-- 1 root root 32134 Dec 6 15:26 test_dse.py\n", - "-rw-r--r-- 1 root root 8205 Dec 6 15:26 test_gradient.py\n", - "-rw-r--r-- 1 root root 15227 Dec 6 15:26 test_interpolation.py\n", - "-rw-r--r-- 1 root root 31816 Dec 6 15:26 test_ir.py\n", - "-rw-r--r-- 1 root root 63169 Dec 6 15:26 test_mpi.py\n", - "-rw-r--r-- 1 root root 67053 Dec 6 15:26 test_operator.py\n", - "-rw-r--r-- 1 root root 14875 Dec 6 15:26 test_ops.py\n", - "-rw-r--r-- 1 root root 12228 Dec 6 15:26 test_pickle.py\n", - "-rw-r--r-- 1 root root 1809 Dec 6 15:26 test_resample.py\n", - "-rw-r--r-- 1 root root 1754 Dec 6 15:26 test_save.py\n", - "-rw-r--r-- 1 root root 2115 Dec 6 15:26 test_staggered_utils.py\n", - "-rw-r--r-- 1 root root 5711 Dec 6 15:26 test_subdomains.py\n", - "-rw-r--r-- 1 root root 3320 Dec 6 15:26 test_symbolic_coefficients.py\n", - "-rw-r--r-- 1 root root 7277 Dec 6 15:26 test_tensors.py\n", - "-rw-r--r-- 1 root root 3186 Dec 6 15:26 test_timestepping.py\n", - "-rw-r--r-- 1 root root 603 Dec 6 15:26 test_tools.py\n", - "-rw-r--r-- 1 root root 3296 Dec 6 15:26 test_tti.py\n", - "-rw-r--r-- 1 root root 8835 Dec 6 15:26 test_visitors.py\n", - "-rw-r--r-- 1 root root 21802 Dec 6 15:26 test_yask.py\n", - "1.0.76\n", + "-rw-r--r-- 1 root root 11521 Dec 17 19:26 conftest.py\n", + "-rw-r--r-- 1 root root 6006 Dec 17 19:26 test_adjoint.py\n", + "-rw-r--r-- 1 root root 14586 Dec 17 19:26 test_autotuner.py\n", + "-rw-r--r-- 1 root root 7538 Dec 17 19:26 test_builtins.py\n", + "-rw-r--r-- 1 root root 24415 Dec 17 19:26 test_caching.py\n", + "-rw-r--r-- 1 root root 9721 Dec 17 19:26 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Dec 17 19:26 test_constant.py\n", + "-rw-r--r-- 1 root root 55954 Dec 17 19:26 test_data.py\n", + "-rw-r--r-- 1 root root 481 Dec 17 19:26 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16331 Dec 17 19:26 test_derivatives.py\n", + "-rw-r--r-- 1 root root 1473 Dec 17 19:26 test_differentiable.py\n", + "-rw-r--r-- 1 root root 30846 Dec 17 19:26 test_dimension.py\n", + "-rw-r--r-- 1 root root 24838 Dec 17 19:26 test_dle.py\n", + "-rw-r--r-- 1 root root 1169 Dec 17 19:26 test_docstrings.py\n", + "-rw-r--r-- 1 root root 32134 Dec 17 19:26 test_dse.py\n", + "-rw-r--r-- 1 root root 8205 Dec 17 19:26 test_gradient.py\n", + "-rw-r--r-- 1 root root 15227 Dec 17 19:26 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31816 Dec 17 19:26 test_ir.py\n", + "-rw-r--r-- 1 root root 63169 Dec 17 19:26 test_mpi.py\n", + "-rw-r--r-- 1 root root 67053 Dec 17 19:26 test_operator.py\n", + "-rw-r--r-- 1 root root 14875 Dec 17 19:26 test_ops.py\n", + "-rw-r--r-- 1 root root 12228 Dec 17 19:26 test_pickle.py\n", + "-rw-r--r-- 1 root root 1809 Dec 17 19:26 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Dec 17 19:26 test_save.py\n", + "-rw-r--r-- 1 root root 2115 Dec 17 19:26 test_staggered_utils.py\n", + "-rw-r--r-- 1 root root 5711 Dec 17 19:26 test_subdomains.py\n", + "-rw-r--r-- 1 root root 3320 Dec 17 19:26 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 7277 Dec 17 19:26 test_tensors.py\n", + "-rw-r--r-- 1 root root 3186 Dec 17 19:26 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Dec 17 19:26 test_tools.py\n", + "-rw-r--r-- 1 root root 3296 Dec 17 19:26 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Dec 17 19:26 test_visitors.py\n", + "-rw-r--r-- 1 root root 21802 Dec 17 19:26 test_yask.py\n", + "1.0.79\n", "\n", "content of devito tests log file after testing:\n", "============================= test session starts ==============================\n", - "platform linux -- Python 3.6.9, pytest-5.3.1, py-1.8.0, pluggy-0.13.1\n", + "platform linux -- Python 3.6.9, pytest-5.3.2, py-1.8.0, pluggy-0.13.1\n", "rootdir: /devito, inifile: setup.cfg\n", - "plugins: nbval-0.9.3, cov-2.8.1\n", + "plugins: cov-2.8.1, nbval-0.9.3\n", "collected 1056 items / 2 skipped / 1054 selected\n", "\n", "tests/test_adjoint.py .......................... [ 2%]\n", @@ -808,7 +808,7 @@ "=================================== FAILURES ===================================\n", "______________________ TestSC.test_function_coefficients _______________________\n", "\n", - "self = \n", + "self = \n", "\n", " def test_function_coefficients(self):\n", " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", @@ -852,10 +852,10 @@ " \n", "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", "E assert Data(False)\n", - "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", - "E + where = np.all\n", - "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", - "E + where = np.isclose\n", + "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", + "E + where = np.all\n", + "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", + "E + where = np.isclose\n", "\n", "tests/test_symbolic_coefficients.py:96: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", @@ -872,7 +872,7 @@ "------------------------------ Captured log call -------------------------------\n", "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", - "====== 1 failed, 968 passed, 87 skipped, 2 xfailed in 1070.16s (0:17:50) =======\n" + "====== 1 failed, 968 passed, 87 skipped, 2 xfailed in 1250.42s (0:20:50) =======\n" ] } ], @@ -902,7 +902,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -911,7 +911,7 @@ "'az acr login --name fwi01acr'" ] }, - "execution_count": 22, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" }, @@ -920,7 +920,7 @@ "output_type": "stream", "text": [ "Login Succeeded\r\n", - "WARNING! Your password will be stored unencrypted in /home/loginvm022/.docker/config.json.\r\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\r\n", "Configure a credential helper to remove this warning. See\r\n", "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", "\r\n", @@ -946,16 +946,16 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" ] }, - "execution_count": 23, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -967,7 +967,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -976,33 +976,33 @@ "text": [ "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", "\n", - "\u001b[1Bd6300f53: Preparing \n", - "\u001b[1B01af7f6b: Preparing \n", - "\u001b[1B41f0b573: Preparing \n", - "\u001b[1B04ca5654: Preparing \n", - "\u001b[1Bf8fc4c9a: Preparing \n", - "\u001b[1Bba47210e: Preparing \n" + "\u001b[1B92dd230d: Preparing \n", + "\u001b[1B589afea5: Preparing \n", + "\u001b[1B66b51e6b: Preparing \n", + "\u001b[1B87808927: Preparing \n", + "\u001b[1Bcb249b79: Preparing \n", + "\u001b[1B190fd43a: Preparing \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[6B01af7f6b: Pushing 1.484GB/3.028GBA\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[2A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K" + "\u001b[6B589afea5: Pushing 1.15GB/3.042GBA\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 894.4MB/3.042GB\u001b[6A\u001b[1K\u001b[KPushing 899.4MB/3.042GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[7Bd6300f53: Pushing 3.026GB/3.028GB\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2KPushing 2.58GB/2.968GB\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K" + "\u001b[6B589afea5: Pushing 2.285GB/3.042GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 2.037GB/3.042GB\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[6B01af7f6b: Pushed 3.103GB/3.028GB\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2Ksdk.v1.0.76: digest: sha256:416dc7ce59c279822e967223790f7b8b7d99ba62bc643ca44b94551135b60b6b size: 1800\n" + "\u001b[7B92dd230d: Pushed 3.049GB/2.97GBB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 2.971GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 2.993GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[Ksdk.v1.0.79: digest: sha256:9f887658e5189ca6e69442ffcea2b158d1727302232cd07211b53ea268bdafbf size: 1800\n" ] } ], @@ -1012,7 +1012,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 23, "metadata": {}, "outputs": [ { diff --git a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb index db76f4c5..43323835 100755 --- a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -81,7 +81,7 @@ { "data": { "text/plain": [ - "'Linux-4.15.0-1063-azure-x86_64-with-debian-stretch-sid'" + "'Linux-4.15.0-1064-azure-x86_64-with-debian-stretch-sid'" ] }, "execution_count": 3, @@ -246,7 +246,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting ./../temp/devito_tutorial/01_modelling.py\n" + "Writing ./../temp/devito_tutorial/01_modelling.py\n" ] } ], @@ -481,7 +481,7 @@ { "data": { "text/plain": [ - "'fwi01_azureml:sdk.v1.0.76'" + "'fwi01_azureml:sdk.v1.0.79'" ] }, "execution_count": 9, @@ -491,7 +491,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" ] }, "execution_count": 9, @@ -530,7 +530,7 @@ "output_type": "stream", "text": [ "Login Succeeded\r\n", - "WARNING! Your password will be stored unencrypted in /home/loginvm022/.docker/config.json.\r\n", + "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\r\n", "Configure a credential helper to remove this warning. See\r\n", "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", "\r\n", @@ -554,7 +554,7 @@ { "data": { "text/plain": [ - "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 /bin/bash -c \"which python\" '" + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79 /bin/bash -c \"which python\" '" ] }, "execution_count": 11, @@ -606,7 +606,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting ./../temp/devito_tutorial/azureml_01_modelling.py\n" + "Writing ./../temp/devito_tutorial/azureml_01_modelling.py\n" ] } ], @@ -859,7 +859,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "565b952db744469fa2137b6c94e15f7a", + "model_id": "cfac8c9cc9f346fd887ef7f9a7ec1c32", "version_major": 2, "version_minor": 0 }, @@ -872,7 +872,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575674728_d40baeba\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575674728_d40baeba\", \"created_utc\": \"2019-12-06T23:25:30.597858Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-06T23:34:26.039772Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/55_azureml-execution-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt?sv=2019-02-02&sr=b&sig=1Fz2ltrBSXhF9tDzTuEOv35mBsOLsf%2BCVuTEuSCRWdg%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/65_job_prep-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt?sv=2019-02-02&sr=b&sig=PwHIdkWadtTAj29WuPOCF3g0RSrWdriOmKhqdjZNm3I%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=Iz8WkiOv%2BkEXeOox8p3P8XkLIdb8pjhCO%2Bo8slYUBGk%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/75_job_post-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/75_job_post-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt?sv=2019-02-02&sr=b&sig=gz88u5ZC%2B7N8QospVRIL8zd%2FEyQKbljoZXQD01jAyXM%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=4nj2pjm1rtKIjBmyudNaBEX6ITd3Gm%2BQLEUgjDYVBIc%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=NQLsveMtGHBEYsmiwoPvPpOv%2B6wabnQp2IwDrVjh49Q%3D&st=2019-12-06T23%3A24%3A44Z&se=2019-12-07T07%3A34%3A44Z&sp=r\", \"logs/azureml/729_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/logs/azureml/729_azureml.log?sv=2019-02-02&sr=b&sig=HpwLZSHX0J%2B2eWILTIDA7%2BmpVIEF0%2BIFfM2LHgYGk8w%3D&st=2019-12-06T23%3A24%3A43Z&se=2019-12-07T07%3A34%3A43Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575674728_d40baeba/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=g%2Fi60CvATRGwaeQM9b6QihJxeFX0jTl%2BOKELCYYQ3rM%3D&st=2019-12-06T23%3A24%3A43Z&se=2019-12-07T07%3A34%3A43Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_d8d8a91061fed6f3a36a0e0da11655ae12488195551133265afca81050ad2db4_d.txt\"], [\"logs/azureml/729_azureml.log\"]], \"run_duration\": \"0:08:55\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575674728_d40baeba\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-06 23:32:41,989|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-06 23:32:41,989|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-06 23:32:41,990|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-06 23:32:41,990|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-06 23:32:42,323|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-06 23:32:42,323|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-06 23:32:42,721|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-06 23:32:42,721|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-06 23:32:42,722|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-06 23:32:42,722|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-06 23:32:42,722|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-06 23:32:43,300|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-06 23:32:43,306|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-06 23:32:43,311|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-06 23:32:43,316|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-06 23:32:43,318|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-06 23:32:43,324|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,325|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-06 23:32:43,325|azureml.core.authentication|DEBUG|Time to expire 1813966.674698 seconds\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,325|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,326|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,326|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,326|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,356|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:43,361|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,369|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,374|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,379|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,385|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:43,385|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-06 23:32:43,386|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-06 23:32:43,386|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|Request headers:\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a72fb1c-fdba-4e6d-a244-7315dcdf5d54'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'request-id': '2a72fb1c-fdba-4e6d-a244-7315dcdf5d54'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|Request body:\\n2019-12-06 23:32:43,387|msrest.http_logger|DEBUG|None\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-06 23:32:43,387|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-06 23:32:43,442|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG|Response headers:\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Date': 'Fri, 06 Dec 2019 23:32:43 GMT'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-06 23:32:43,443|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '2a72fb1c-fdba-4e6d-a244-7315dcdf5d54'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG|Response content:\\n2019-12-06 23:32:43,444|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1516,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-06T23:25:30.5978583+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-06T23:30:15.4122862+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575674728_d40baeba/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-06 23:32:43,449|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-06 23:32:43,450|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-06 23:32:43,450|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-06 23:32:43,451|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-06 23:32:43,451|azureml.WorkerPool|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-06 23:32:43,451|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,592|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,593|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,593|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-06 23:32:45,599|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,600|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-06 23:32:45,605|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,610|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,616|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,621|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:32:45,622|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-06 23:32:45,622|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba'\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG|Request headers:\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-06 23:32:45,622|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7502a986-27e5-47c2-8a48-e5501a0dda7c'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG| 'request-id': '7502a986-27e5-47c2-8a48-e5501a0dda7c'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG|Request body:\\n2019-12-06 23:32:45,623|msrest.http_logger|DEBUG|None\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-06 23:32:45,623|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-06 23:32:46,018|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-06 23:32:46,018|msrest.http_logger|DEBUG|Response headers:\\n2019-12-06 23:32:46,018|msrest.http_logger|DEBUG| 'Date': 'Fri, 06 Dec 2019 23:32:46 GMT'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7502a986-27e5-47c2-8a48-e5501a0dda7c'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG|Response content:\\n2019-12-06 23:32:46,019|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 1516,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-06T23:25:30.5978583+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-06T23:30:15.4122862+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575674728_d40baeba\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575674728_d40baeba/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575674728_d40baeba/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-06 23:32:46,022|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-06 23:32:46,023|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-06 23:32:46,023|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-06 23:33:13,322|azureml.core.authentication|DEBUG|Time to expire 1813936.677149 seconds\\n2019-12-06 23:33:43,323|azureml.core.authentication|DEBUG|Time to expire 1813906.67683 seconds\\n2019-12-06 23:33:57,866|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-06 23:33:57,867|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-06 23:33:57,867|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:33:57,911|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575674728_d40baeba/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575674728_d40baeba\\n2019-12-06 23:33:57,912|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-06 23:33:57,912|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba|INFO|complete is not setting status for submitted runs.\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,912|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,913|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-06 23:33:57,913|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,914|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:57,914|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-06 23:33:57,914|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-06 23:33:57,914|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-06 23:33:57,914|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-06 23:33:57,915|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-06 23:33:57,915|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-06 23:33:57,915|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-06 23:33:57,915|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-06 23:33:57,916|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-06 23:33:57,916|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-06 23:33:57,917|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-06 23:33:57,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-06 23:33:57,917|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575674728_d40baeba/batch/metrics'\\n2019-12-06 23:33:57,917|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG|Request headers:\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-06 23:33:57,918|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7318af30-3aa3-4d84-a4db-0595c67afd70'\\n2019-12-06 23:33:57,918|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG| 'request-id': '7318af30-3aa3-4d84-a4db-0595c67afd70'\\n2019-12-06 23:33:57,919|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-06 23:33:57,919|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-06 23:33:57,919|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG|Request body:\\n2019-12-06 23:33:57,919|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"d160ffa3-e1bc-4ff2-b60f-7742b38cdfd2\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-06T23:33:57.866688Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-06 23:33:57,919|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-06 23:33:57,920|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-06 23:33:57,920|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-06 23:33:57,920|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG|Response headers:\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Date': 'Fri, 06 Dec 2019 23:33:58 GMT'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '7318af30-3aa3-4d84-a4db-0595c67afd70'\\n2019-12-06 23:33:58,044|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG|Response content:\\n2019-12-06 23:33:58,045|msrest.http_logger|DEBUG|\\n2019-12-06 23:33:58,045|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002143383026123047 seconds.\\n\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml._SubmittedRun#020_AzureMLEstimator_1575674728_d40baeba.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-06 23:33:58,170|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-06 23:33:58,170|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576613450_585d065c?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1576613450_585d065c\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1576613450_585d065c\", \"created_utc\": \"2019-12-17T20:10:54.479957Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"ghiordan/azureml_devito03\", \"mlflow.source.git.branch\": \"ghiordan/azureml_devito03\", \"azureml.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"mlflow.source.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-17T20:20:27.928692Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/55_azureml-execution-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt?sv=2019-02-02&sr=b&sig=xAqF2NZ8MTrYgUXMbxagR8kh%2BT9o5uScuBFJwvJOSGw%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/65_job_prep-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt?sv=2019-02-02&sr=b&sig=KZQDqgrmMR1X8BB5rb0C1Jg%2BfncC8l3MXYkKI9anv5c%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=nX0jFpfGXYoETXMnnjiK0CGdjW9fwJqhAg5%2BB5RYWEU%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/75_job_post-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/75_job_post-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt?sv=2019-02-02&sr=b&sig=g1AS%2BwhRfqkTeScqQmeRWg2%2FhTFYY9QSz%2FeQMhnVXCo%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=9jqsBUn4oITucw0a%2BcH4jYpBUz%2BJkVJu35Yzi24Q%2B8Y%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=8rDEAz1Pun06dF8Nh7mMRtLxvI83axVNdXPutXZ9NXw%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"logs/azureml/689_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/logs/azureml/689_azureml.log?sv=2019-02-02&sr=b&sig=3BrTrM6pyz6zL48sMVMmkyzFDd5WbSntii94gnNuhTc%3D&st=2019-12-17T20%3A10%3A38Z&se=2019-12-18T04%3A20%3A38Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=hwXqleQ4EWbK4%2BGvsm8084zIMZt%2FObojTRdNYIAqraI%3D&st=2019-12-17T20%3A10%3A38Z&se=2019-12-18T04%3A20%3A38Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\"], [\"logs/azureml/689_azureml.log\"]], \"run_duration\": \"0:09:33\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1576613450_585d065c\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-17 20:18:34,466|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-17 20:18:34,467|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-17 20:18:34,467|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-17 20:18:34,467|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-17 20:18:34,856|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-17 20:18:34,856|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-17 20:18:35,243|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-17 20:18:35,243|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-17 20:18:35,243|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-17 20:18:35,243|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-17 20:18:35,243|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-17 20:18:35,824|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-17 20:18:35,829|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-17 20:18:35,834|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-17 20:18:35,839|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-17 20:18:35,841|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-17 20:18:35,847|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,848|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-17 20:18:35,848|azureml.core.authentication|DEBUG|Time to expire 1813938.151401 seconds\\n2019-12-17 20:18:35,848|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,848|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,848|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,885|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,890|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,898|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,909|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,916|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,921|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,921|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-17 20:18:35,922|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-17 20:18:35,922|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576613450_585d065c'\\n2019-12-17 20:18:35,922|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-17 20:18:35,922|msrest.http_logger|DEBUG|Request headers:\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '5bbe21c2-a44f-4982-b9b6-5be1bb03d946'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'request-id': '5bbe21c2-a44f-4982-b9b6-5be1bb03d946'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG|Request body:\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG|None\\n2019-12-17 20:18:35,923|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-17 20:18:35,923|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-17 20:18:35,923|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-17 20:18:35,923|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-17 20:18:36,091|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG|Response headers:\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Date': 'Tue, 17 Dec 2019 20:18:36 GMT'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '5bbe21c2-a44f-4982-b9b6-5be1bb03d946'\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG|Response content:\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 4768,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-17T20:10:54.4799573+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-17T20:16:11.2772574+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576613450_585d065c/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576613450_585d065c/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576613450_585d065c/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-17 20:18:36,099|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-17 20:18:36,099|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-17 20:18:36,099|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-17 20:18:36,100|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-17 20:18:36,100|azureml.WorkerPool|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576613450_585d065c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576613450_585d065c\\n2019-12-17 20:18:36,101|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-17 20:18:36,101|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576613450_585d065c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576613450_585d065c\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,077|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,077|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,077|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,083|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,084|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-17 20:18:38,089|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,094|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,100|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,105|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,105|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-17 20:18:38,106|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576613450_585d065c'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG|Request headers:\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '70d23c35-adf5-449f-8ef3-6bd03bda21e8'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'request-id': '70d23c35-adf5-449f-8ef3-6bd03bda21e8'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG|Request body:\\n2019-12-17 20:18:38,107|msrest.http_logger|DEBUG|None\\n2019-12-17 20:18:38,107|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-17 20:18:38,107|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-17 20:18:38,107|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-17 20:18:38,107|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-17 20:18:38,241|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-17 20:18:38,241|msrest.http_logger|DEBUG|Response headers:\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Date': 'Tue, 17 Dec 2019 20:18:38 GMT'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '70d23c35-adf5-449f-8ef3-6bd03bda21e8'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-17 20:18:38,243|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-17 20:18:38,243|msrest.http_logger|DEBUG|Response content:\\n2019-12-17 20:18:38,243|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 4768,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-17T20:10:54.4799573+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-17T20:16:11.2772574+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576613450_585d065c/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576613450_585d065c/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576613450_585d065c/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-17 20:18:38,245|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-17 20:18:38,245|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-17 20:18:38,245|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-17 20:19:05,842|azureml.core.authentication|DEBUG|Time to expire 1813908.157661 seconds\\n2019-12-17 20:19:35,842|azureml.core.authentication|DEBUG|Time to expire 1813878.157334 seconds\\n2019-12-17 20:19:57,499|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-17 20:19:57,499|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-17 20:19:57,500|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576613450_585d065c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576613450_585d065c\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576613450_585d065c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576613450_585d065c\\n2019-12-17 20:19:57,568|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-17 20:19:57,568|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c|INFO|complete is not setting status for submitted runs.\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,569|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-17 20:19:57,570|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,570|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,570|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,570|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-17 20:19:57,570|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-17 20:19:57,570|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-17 20:19:57,570|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-17 20:19:57,570|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-17 20:19:57,571|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-17 20:19:57,571|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-17 20:19:57,571|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-17 20:19:57,571|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-17 20:19:57,571|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-17 20:19:57,572|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-17 20:19:57,573|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-17 20:19:57,573|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:19:57,573|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-17 20:19:57,573|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576613450_585d065c/batch/metrics'\\n2019-12-17 20:19:57,573|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG|Request headers:\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'e3f652cb-49f5-4b75-a293-ced63c71b52e'\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG| 'request-id': 'e3f652cb-49f5-4b75-a293-ced63c71b52e'\\n2019-12-17 20:19:57,575|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-17 20:19:57,575|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79 sdk_run'\\n2019-12-17 20:19:57,575|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG|Request body:\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"63af7c5d-57b4-4ea4-aff6-bc0f503d9be8\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-17T20:19:57.499216Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-17 20:19:57,576|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-17 20:19:57,576|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-17 20:19:57,576|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-17 20:19:57,576|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-17 20:19:57,765|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-17 20:19:57,765|msrest.http_logger|DEBUG|Response headers:\\n2019-12-17 20:19:57,765|msrest.http_logger|DEBUG| 'Date': 'Tue, 17 Dec 2019 20:19:57 GMT'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'e3f652cb-49f5-4b75-a293-ced63c71b52e'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG|Response content:\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG|\\n2019-12-17 20:19:57,767|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00021457672119140625 seconds.\\n\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-17 20:19:57,826|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-17 20:19:57,827|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -935,7 +935,7 @@ { "data": { "text/plain": [ - "'runId= 020_AzureMLEstimator_1575674728_d40baeba'" + "'runId= 020_AzureMLEstimator_1576613450_585d065c'" ] }, "execution_count": 19, @@ -945,7 +945,7 @@ { "data": { "text/plain": [ - "'experimentation baseImage: fwi01_azureml:sdk.v1.0.76'" + "'experimentation baseImage: fwi01_azureml:sdk.v1.0.79'" ] }, "execution_count": 19, diff --git a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb index 1b6ecaf4..106bde7b 100755 --- a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -481,7 +481,7 @@ { "data": { "text/plain": [ - "'fwi01_azureml:sdk.v1.0.76'" + "'fwi01_azureml:sdk.v1.0.79'" ] }, "execution_count": 9, @@ -491,7 +491,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" ] }, "execution_count": 9, @@ -529,7 +529,7 @@ { "data": { "text/plain": [ - "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.76 /bin/bash -c \"which python\" '" + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79 /bin/bash -c \"which python\" '" ] }, "execution_count": 10, @@ -835,7 +835,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "a0312dfcb82f419288e3c3c37c39b9dd", + "model_id": "521e64911a6d4083888e995ff79da564", "version_major": 2, "version_minor": 0 }, @@ -848,7 +848,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575678435_be18a2fc?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575678435_be18a2fc\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575678435_be18a2fc\", \"created_utc\": \"2019-12-07T00:27:18.102865Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":1,\\\"CurrentNodeCount\\\":2}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575678435_be18a2fc/azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt?sv=2019-02-02&sr=b&sig=99MfEJ4IvLwXgM3jjLm4amfljnv7gOK3%2BQPb1GN%2BZKg%3D&st=2019-12-07T00%3A22%3A27Z&se=2019-12-07T08%3A32%3A27Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_e010639b61f121ff1dbd780d646c8bd4bc6a423228429632e00c37ab5e150756_p.txt\"]], \"run_duration\": \"0:05:10\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2019-12-07T00:31:04Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.76: Pulling from fwi01_azureml\\n1ab2bdfe9778: Pulling fs layer\\ndd7d28bd8be5: Pulling fs layer\\naf998e3a361b: Pulling fs layer\\n8f61820757bf: Pulling fs layer\\n0eb461057035: Pulling fs layer\\n23276e49c76d: Pulling fs layer\\nc55ca301ea9f: Pulling fs layer\\n0eb461057035: Waiting\\n8f61820757bf: Waiting\\nc55ca301ea9f: Waiting\\n1ab2bdfe9778: Verifying Checksum\\n1ab2bdfe9778: Download complete\\naf998e3a361b: Verifying Checksum\\naf998e3a361b: Download complete\\n0eb461057035: Verifying Checksum\\n0eb461057035: Download complete\\ndd7d28bd8be5: Verifying Checksum\\ndd7d28bd8be5: Download complete\\n1ab2bdfe9778: Pull complete\\n8f61820757bf: Verifying Checksum\\n8f61820757bf: Download complete\\ndd7d28bd8be5: Pull complete\\nc55ca301ea9f: Verifying Checksum\\nc55ca301ea9f: Download complete\\n23276e49c76d: Verifying Checksum\\n23276e49c76d: Download complete\\naf998e3a361b: Pull complete\\n8f61820757bf: Pull complete\\n0eb461057035: Pull complete\\n23276e49c76d: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576639227_6528b852?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1576639227_6528b852\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1576639227_6528b852\", \"created_utc\": \"2019-12-18T03:20:30.235502Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"ghiordan/azureml_devito03\", \"mlflow.source.git.branch\": \"ghiordan/azureml_devito03\", \"azureml.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"mlflow.source.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-18T03:22:33.515195Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=wEJg5h%2FN%2BCt%2BavdQxsl%2BlSnQqqxmd8500luwM1gktnk%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=cMfZVjgZuTxt3xBtMpsCQeIJ%2BVQmH%2FdalkHgyged5ow%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=O59A3Pe3NfBZ%2Bm7z3Z4WB69ZE60K3qlNPb%2BCO6NV7Dw%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=qj3UaXzOtJWiFXaT%2FYy%2BrBj3X14pYuekf8cZDbrQiHY%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=eImLU0mmrY4KANoEJL%2FXEFaOm%2Fp5%2F8StuxJdfr2URjY%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=t9NdOE5AOFhCK%2BqSNfJs51l2Zn3Z9Mx8vYDaiR0wNUU%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"logs/azureml/688_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/logs/azureml/688_azureml.log?sv=2019-02-02&sr=b&sig=ju9O1BPKJ16IKUYN4fEHv%2BHBTTdruFSD%2FRyKBT1zbPI%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=HV5hFHBamtTh7MaJ%2Fcq56Hssz6QPNlbXvrjz1T1KOTA%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"logs/azureml/688_azureml.log\"]], \"run_duration\": \"0:02:03\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1576639227_6528b852\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-18 03:20:52,190|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-18 03:20:52,190|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-18 03:20:52,191|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-18 03:20:52,191|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-18 03:20:52,458|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-18 03:20:52,459|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-18 03:20:52,795|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-18 03:20:52,796|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-18 03:20:52,796|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-18 03:20:52,796|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-18 03:20:52,796|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-18 03:20:53,320|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-18 03:20:53,324|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-18 03:20:53,328|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-18 03:20:53,332|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-18 03:20:53,333|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:20:53,338|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,339|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-18 03:20:53,339|azureml.core.authentication|DEBUG|Time to expire 1814376.660805 seconds\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,340|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,368|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,372|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,378|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,382|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,387|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,391|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,391|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-18 03:20:53,392|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:20:53,392|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576639227_6528b852'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '9ba8101b-982f-4288-b938-521153e4955e'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'request-id': '9ba8101b-982f-4288-b938-521153e4955e'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG|None\\n2019-12-18 03:20:53,393|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:20:53,393|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:20:53,393|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:20:53,393|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:20:53 GMT'\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '9ba8101b-982f-4288-b938-521153e4955e'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 5540,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-18T03:20:30.2355028+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-18T03:20:43.031023+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576639227_6528b852/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576639227_6528b852/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576639227_6528b852/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-18 03:20:53,561|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-18 03:20:53,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-18 03:20:53,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-18 03:20:53,562|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-18 03:20:53,563|azureml.WorkerPool|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576639227_6528b852/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576639227_6528b852\\n2019-12-18 03:20:53,563|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-18 03:20:53,563|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576639227_6528b852/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576639227_6528b852\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,881|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,881|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,881|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,886|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,887|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-18 03:20:55,892|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,897|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,901|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,906|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,906|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-18 03:20:55,906|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576639227_6528b852'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b5b744e6-b6a5-4eb8-b360-e0f471c112b4'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'request-id': 'b5b744e6-b6a5-4eb8-b360-e0f471c112b4'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|None\\n2019-12-18 03:20:55,907|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:20:55,907|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:20:55,907|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:20:55,908|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:20:55 GMT'\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b5b744e6-b6a5-4eb8-b360-e0f471c112b4'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 5540,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-18T03:20:30.2355028+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-18T03:20:43.031023+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576639227_6528b852/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576639227_6528b852/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576639227_6528b852/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-18 03:20:55,968|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-18 03:20:55,968|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-18 03:20:55,968|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-18 03:21:23,334|azureml.core.authentication|DEBUG|Time to expire 1814346.666025 seconds\\n2019-12-18 03:21:53,334|azureml.core.authentication|DEBUG|Time to expire 1814316.665578 seconds\\n2019-12-18 03:22:06,840|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-18 03:22:06,840|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:22:06,841|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576639227_6528b852/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576639227_6528b852\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576639227_6528b852/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576639227_6528b852\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-18 03:22:07,053|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-18 03:22:07,053|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852|INFO|complete is not setting status for submitted runs.\\n2019-12-18 03:22:07,053|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,053|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-18 03:22:07,053|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,054|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,055|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-18 03:22:07,055|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-18 03:22:07,056|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-18 03:22:07,056|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-18 03:22:07,056|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-18 03:22:07,056|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-18 03:22:07,057|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-18 03:22:07,057|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-18 03:22:07,057|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-18 03:22:07,058|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:22:07,058|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-18 03:22:07,058|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-18 03:22:07,059|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:22:07,059|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-18 03:22:07,059|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576639227_6528b852/batch/metrics'\\n2019-12-18 03:22:07,059|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-18 03:22:07,059|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-18 03:22:07,059|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:22:07,060|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:22:07,060|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-18 03:22:07,060|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'f3fe67d8-6f74-4c18-8a93-845b18aca364'\\n2019-12-18 03:22:07,060|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG| 'request-id': 'f3fe67d8-6f74-4c18-8a93-845b18aca364'\\n2019-12-18 03:22:07,060|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,061|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-18 03:22:07,061|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:22:07,061|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79 sdk_run'\\n2019-12-18 03:22:07,061|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-18 03:22:07,061|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:22:07,061|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"85c6d826-5d8d-4ec9-9ac2-20ebbc1af5a3\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-18T03:22:06.840237Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-18 03:22:07,061|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:22:07,061|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:22:07,061|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:22:07,062|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:22:07,321|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:22:07,321|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:22:07 GMT'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'f3fe67d8-6f74-4c18-8a93-845b18aca364'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG|\\n2019-12-18 03:22:07,323|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-18 03:22:07,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-18 03:22:07,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-18 03:22:07,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-18 03:22:07,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00022602081298828125 seconds.\\nWaiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.2506241798400879 seconds.\\n\\n2019-12-18 03:22:07,563|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,563|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,563|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-18 03:22:07,563|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-18 03:22:07,563|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-18 03:22:07,563|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -921,7 +921,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 9, time 20.798 seconds: Counter({'Completed': 1})\r" + "Final print 10, time 21.283 seconds: Counter({'Completed': 1})\r" ] } ], @@ -954,8 +954,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "run_duration in seconds 243.960763\n", - "run_duration= 4m 3.961s\n" + "run_duration in seconds 110.484172\n", + "run_duration= 1m 50.484s\n" ] } ], @@ -982,13 +982,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Showing details for run 498\n" + "Showing details for run 181\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "cd44e7b0a1c447dabe98bf114f420d76", + "model_id": "6ecf3c99f6e54422b7892e0dfeddca35", "version_major": 2, "version_minor": 0 }, @@ -1001,7 +1001,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"created_utc\": \"2019-12-07T01:54:55.33033Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"staging\", \"mlflow.source.git.branch\": \"staging\", \"azureml.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"mlflow.source.git.commit\": \"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-07T01:56:48.811115Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=9mQARzuRlCW%2F%2Brv3FDzJvm%2Fsaudk6GFjNypMRkV3O8g%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=TMxrg26ywABOyJtGYT3KVLrGP0TYIHQ9E3ePlr%2BQepg%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=vWkErsH55%2BLhIG%2FBJbtZb8NSNHFyNAzxk5VjW4p6lcM%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt?sv=2019-02-02&sr=b&sig=cbDgvPNn4LNXDsUXZwmWCjRMj0O9PnFSqSCtuCPMTFo%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=wvqhR%2Bnzw0uLEsCGETAxkKrdwN5eI%2FgvTeB4juQ4aUI%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=kkirWrsrpjcrKndUUPxuJVeRWu0GthsVZ4cXpxbEGMg%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"logs/azureml/728_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/logs/azureml/728_azureml.log?sv=2019-02-02&sr=b&sig=pK%2F6TBBvQEPexjuRPR1FyOq6CUPXfnNBobkTmpmaeiM%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1575683693_ddd16e31/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=o%2BPcdcJvKZyQWRA0HpaJbM%2BxhqFOkdDjgBqtxtHtoag%3D&st=2019-12-07T01%3A46%3A50Z&se=2019-12-07T09%3A56%3A50Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"azureml-logs/65_job_prep-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_01b47c06fd150418ce69a91b330cb6996c9e9e076f7368a183a2f9a708f17ccb_p.txt\"], [\"logs/azureml/728_azureml.log\"]], \"run_duration\": \"0:01:53\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1575683693_ddd16e31\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-07 01:55:16,975|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-07 01:55:16,976|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-07 01:55:17,242|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-07 01:55:17,243|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-07 01:55:17,566|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-07 01:55:18,070|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-07 01:55:18,075|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-07 01:55:18,078|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-07 01:55:18,082|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-07 01:55:18,083|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:55:18,088|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,089|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-07 01:55:18,089|azureml.core.authentication|DEBUG|Time to expire 1814376.910384 seconds\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,089|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,090|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,118|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:18,122|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,128|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,132|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,136|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,141|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:18,141|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-07 01:55:18,142|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,142|msrest.http_logger|DEBUG| 'request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:55:18,143|msrest.http_logger|DEBUG|None\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:55:18,143|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:55:18 GMT'\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:18,196|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '066d53de-da2b-470f-936a-ed66dab2d28c'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:55:18,197|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 2107,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-07T01:54:55.3303306+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-07T01:55:07.6378716+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575683693_ddd16e31/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-07 01:55:18,202|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-07 01:55:18,202|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-07 01:55:18,203|azureml.WorkerPool|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-07 01:55:18,203|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,151|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,152|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-07 01:55:20,157|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,158|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-07 01:55:20,162|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,166|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,170|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,175|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:55:20,175|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-07 01:55:20,175|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31'\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-07 01:55:20,175|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76'\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:55:20,176|msrest.http_logger|DEBUG|None\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:55:20,176|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:55:20 GMT'\\n2019-12-07 01:55:20,259|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b087e081-4f44-4f48-8adf-8c816a59faae'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:55:20,260|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 2107,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-07T01:54:55.3303306+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-07T01:55:07.6378716+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1575683693_ddd16e31\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"staging\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"staging\\\",\\n \\\"azureml.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"1d3cd3340f4063508b6f707d5fc2a35f5429a07f\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi08\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1575683693_ddd16e31/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1575683693_ddd16e31/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false\\n}\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'staging', 'mlflow.source.git.branch': 'staging', 'azureml.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'mlflow.source.git.commit': '1d3cd3340f4063508b6f707d5fc2a35f5429a07f', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-07 01:55:20,262|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-07 01:55:48,084|azureml.core.authentication|DEBUG|Time to expire 1814346.915499 seconds\\n2019-12-07 01:56:18,084|azureml.core.authentication|DEBUG|Time to expire 1814316.915133 seconds\\n2019-12-07 01:56:25,858|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-07 01:56:25,858|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:56:25,859|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-07 01:56:25,924|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1575683693_ddd16e31/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1575683693_ddd16e31\\n2019-12-07 01:56:25,925|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-07 01:56:25,925|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31|INFO|complete is not setting status for submitted runs.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,925|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:25,926|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-07 01:56:25,927|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-07 01:56:25,927|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-07 01:56:25,927|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-07 01:56:25,928|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-07 01:56:25,928|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-07 01:56:25,928|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-07 01:56:25,929|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-07 01:56:25,929|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-07 01:56:25,929|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-07 01:56:25,929|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1575683693_ddd16e31/batch/metrics'\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-07 01:56:25,930|msrest.http_logger|DEBUG|Request headers:\\n2019-12-07 01:56:25,930|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:25,931|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:25,931|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-07 01:56:25,931|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-07 01:56:25,932|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.0) msrest/0.6.10 azureml._restclient/core.1.0.76 sdk_run'\\n2019-12-07 01:56:25,932|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG|Request body:\\n2019-12-07 01:56:25,932|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"1a8ad3d8-accf-42da-a07d-fd00ef5ee1e6\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-07T01:56:25.858188Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-07 01:56:25,932|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-07 01:56:26,050|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|Response headers:\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Date': 'Sat, 07 Dec 2019 01:56:26 GMT'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '18a01463-68a6-4c03-bc10-c9e912702ee6'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|Response content:\\n2019-12-07 01:56:26,051|msrest.http_logger|DEBUG|\\n2019-12-07 01:56:26,052|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-07 01:56:26,182|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-07 01:56:26,182|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002186298370361328 seconds.\\n\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml._SubmittedRun#020_AzureMLEstimator_1575683693_ddd16e31.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-07 01:56:26,183|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-07 01:56:26,183|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576641205_aa71d72d?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1576641205_aa71d72d\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1576641205_aa71d72d\", \"created_utc\": \"2019-12-18T03:53:27.157172Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"ghiordan/azureml_devito03\", \"mlflow.source.git.branch\": \"ghiordan/azureml_devito03\", \"azureml.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"mlflow.source.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-18T03:55:25.640243Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=SVn%2BtGC1ko6RF9S2V%2BcPrQljGBljydlDSTlRwgiY2Ng%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=9tUhIp88GxWlrx8Z%2FpNmYnxeNXF8wzQW1I3XK5U9Lpc%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=ZuZAONbZA0%2FBHCGTly%2FMT8l75Epo%2FziQ0d5cmbfh95I%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=C7odtYllisuM4%2Fy4amWPUU%2F9P0t3yXb9pyzJiVKJi7o%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=YMq8lCKloWiglWxGWxgTR8yuMU6AftDEUvE71wr%2FqJQ%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=W4UKWaHyWa1uboGDv8fxcacMOku6iU%2BzkbjcgTLYUVk%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"logs/azureml/688_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/logs/azureml/688_azureml.log?sv=2019-02-02&sr=b&sig=FXEhXZv%2Bm0UHbZQ2k7PYFWQEwZmND8rbqkdIwMFKD2I%3D&st=2019-12-18T03%3A45%3A28Z&se=2019-12-18T11%3A55%3A28Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=OKGeQJTWen%2FksWvC13BBMsLC%2Ffq%2BGMpRa6D3azdXWAU%3D&st=2019-12-18T03%3A45%3A28Z&se=2019-12-18T11%3A55%3A28Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"logs/azureml/688_azureml.log\"]], \"run_duration\": \"0:01:58\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1576641205_aa71d72d\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-18 03:53:49,953|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-18 03:53:49,953|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-18 03:53:49,954|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-18 03:53:49,954|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-18 03:53:50,231|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-18 03:53:50,232|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-18 03:53:50,560|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-18 03:53:50,560|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-18 03:53:50,560|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-18 03:53:50,560|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-18 03:53:50,560|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-18 03:53:51,076|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-18 03:53:51,080|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-18 03:53:51,084|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-18 03:53:51,088|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-18 03:53:51,089|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:53:51,094|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,095|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-18 03:53:51,095|azureml.core.authentication|DEBUG|Time to expire 1814375.90442 seconds\\n2019-12-18 03:53:51,095|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,095|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,095|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,095|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,125|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,129|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,135|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,139|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,144|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,148|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,148|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-18 03:53:51,149|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:53:51,149|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576641205_aa71d72d'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0584c9c0-de25-4df5-a0b6-133c534975d3'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'request-id': '0584c9c0-de25-4df5-a0b6-133c534975d3'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG|None\\n2019-12-18 03:53:51,150|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:53:51,151|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:53:51,151|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:53:51,151|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:53:51,211|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:53:51 GMT'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0584c9c0-de25-4df5-a0b6-133c534975d3'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-18 03:53:51,213|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:53:51,213|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 5721,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-18T03:53:27.1571726+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-18T03:53:38.4738149+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576641205_aa71d72d/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576641205_aa71d72d/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576641205_aa71d72d/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-18 03:53:51,218|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-18 03:53:51,218|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-18 03:53:51,218|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-18 03:53:51,219|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-18 03:53:51,219|azureml.WorkerPool|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576641205_aa71d72d/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576641205_aa71d72d\\n2019-12-18 03:53:51,219|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-18 03:53:51,219|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576641205_aa71d72d/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576641205_aa71d72d\\n2019-12-18 03:53:53,104|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,104|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,104|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,104|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,110|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,111|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-18 03:53:53,115|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,120|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,124|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,128|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,129|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-18 03:53:53,129|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576641205_aa71d72d'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '5eef3ce8-6e03-46f3-8eb4-7e7beb90d699'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'request-id': '5eef3ce8-6e03-46f3-8eb4-7e7beb90d699'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-18 03:53:53,130|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:53:53,130|msrest.http_logger|DEBUG|None\\n2019-12-18 03:53:53,130|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:53:53,130|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:53:53,130|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:53:53,130|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:53:53,207|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:53:53 GMT'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '5eef3ce8-6e03-46f3-8eb4-7e7beb90d699'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:53:53,209|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-18 03:53:53,209|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:53:53,209|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 5721,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-18T03:53:27.1571726+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-18T03:53:38.4738149+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576641205_aa71d72d/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576641205_aa71d72d/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576641205_aa71d72d/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-18 03:53:53,211|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-18 03:53:53,211|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-18 03:53:53,212|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-18 03:54:21,090|azureml.core.authentication|DEBUG|Time to expire 1814345.909671 seconds\\n2019-12-18 03:54:51,091|azureml.core.authentication|DEBUG|Time to expire 1814315.908961 seconds\\n2019-12-18 03:55:03,497|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-18 03:55:03,497|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:55:03,497|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576641205_aa71d72d/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576641205_aa71d72d\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576641205_aa71d72d/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576641205_aa71d72d\\n2019-12-18 03:55:03,631|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-18 03:55:03,631|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-18 03:55:03,631|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d|INFO|complete is not setting status for submitted runs.\\n2019-12-18 03:55:03,631|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,631|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-18 03:55:03,631|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,632|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,633|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-18 03:55:03,634|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-18 03:55:03,634|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-18 03:55:03,634|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-18 03:55:03,634|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-18 03:55:03,634|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-18 03:55:03,635|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-18 03:55:03,635|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-18 03:55:03,635|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-18 03:55:03,635|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-18 03:55:03,636|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:55:03,636|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-18 03:55:03,637|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:55:03,637|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-18 03:55:03,637|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576641205_aa71d72d/batch/metrics'\\n2019-12-18 03:55:03,637|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-18 03:55:03,637|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-18 03:55:03,637|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:55:03,638|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:55:03,638|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-18 03:55:03,638|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'c3ed59b5-cecd-48f2-9640-db3eb82cfbc4'\\n2019-12-18 03:55:03,638|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG| 'request-id': 'c3ed59b5-cecd-48f2-9640-db3eb82cfbc4'\\n2019-12-18 03:55:03,639|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,639|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-18 03:55:03,639|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:55:03,639|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79 sdk_run'\\n2019-12-18 03:55:03,639|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-18 03:55:03,639|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:55:03,639|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"91fbac36-3af1-45bd-b8e1-cd5376d16624\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-18T03:55:03.496961Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-18 03:55:03,639|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:55:03,640|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:55:03,640|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:55:03,640|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:55:03,785|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:55:03 GMT'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'c3ed59b5-cecd-48f2-9640-db3eb82cfbc4'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG|\\n2019-12-18 03:55:03,788|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002357959747314453 seconds.\\n\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-18 03:55:03,890|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -1010,8 +1010,42 @@ "name": "stdout", "output_type": "stream", "text": [ - "Counter499: submission of job 499 on 400 nodes took 9.16640019416809 seconds \n", - "run list length 499\n" + "Counter182: submission of job 182 on 20 nodes took 9.67591118812561 seconds \n", + "run list length 182\n", + "Counter183: submission of job 183 on 20 nodes took 8.417665958404541 seconds \n", + "run list length 183\n", + "Counter184: submission of job 184 on 20 nodes took 8.162678956985474 seconds \n", + "run list length 184\n", + "Counter185: submission of job 185 on 20 nodes took 8.167269706726074 seconds \n", + "run list length 185\n", + "Counter186: submission of job 186 on 20 nodes took 7.944813966751099 seconds \n", + "run list length 186\n", + "Counter187: submission of job 187 on 20 nodes took 9.100224256515503 seconds \n", + "run list length 187\n", + "Counter188: submission of job 188 on 20 nodes took 8.355362892150879 seconds \n", + "run list length 188\n", + "Counter189: submission of job 189 on 20 nodes took 9.092441320419312 seconds \n", + "run list length 189\n", + "Counter190: submission of job 190 on 20 nodes took 10.007514238357544 seconds \n", + "run list length 190\n", + "Counter191: submission of job 191 on 20 nodes took 8.018926858901978 seconds \n", + "run list length 191\n", + "Counter192: submission of job 192 on 20 nodes took 8.925177574157715 seconds \n", + "run list length 192\n", + "Counter193: submission of job 193 on 20 nodes took 8.075469493865967 seconds \n", + "run list length 193\n", + "Counter194: submission of job 194 on 20 nodes took 8.114925384521484 seconds \n", + "run list length 194\n", + "Counter195: submission of job 195 on 20 nodes took 11.568782329559326 seconds \n", + "run list length 195\n", + "Counter196: submission of job 196 on 20 nodes took 7.691022157669067 seconds \n", + "run list length 196\n", + "Counter197: submission of job 197 on 20 nodes took 7.677650451660156 seconds \n", + "run list length 197\n", + "Counter198: submission of job 198 on 20 nodes took 7.701624393463135 seconds \n", + "run list length 198\n", + "Counter199: submission of job 199 on 20 nodes took 8.586235761642456 seconds \n", + "run list length 199\n" ] } ], @@ -1019,8 +1053,9 @@ "import time\n", "from IPython.display import clear_output\n", "\n", - "no_of_jobs = 500\n", - "no_of_nodes = 400\n", + "no_of_nodes = int(20)\n", + "no_of_jobs = int(no_of_nodes*10)\n", + "\n", "\n", "job_counter = 0\n", "print_cycle = 20\n", @@ -1054,106 +1089,46 @@ { "data": { "text/plain": [ - "array([10.16889381, 10.52522182, 8.67223501, 7.76976609, 8.98659873,\n", - " 9.54043746, 7.56379271, 7.95067477, 10.98772812, 8.58469343,\n", - " 9.19690919, 8.37747335, 8.49322033, 8.96249437, 11.00566387,\n", - " 10.18721223, 8.70340395, 9.07873917, 8.83641577, 9.93886757,\n", - " 8.43751788, 8.88584614, 8.46158338, 8.10118651, 7.95576859,\n", - " 8.02682757, 8.59585524, 11.43893504, 8.21132302, 7.56929898,\n", - " 9.16166759, 7.96446443, 8.20211887, 8.0066514 , 8.16604567,\n", - " 9.03855515, 9.27646971, 7.88356876, 8.6105082 , 8.63279152,\n", - " 9.63798594, 7.88380122, 11.83064437, 7.67609763, 8.36450744,\n", - " 10.36203027, 8.20605659, 8.27934074, 8.71854138, 7.48072934,\n", - " 7.98534775, 7.88993239, 9.49783468, 8.20365477, 8.31964707,\n", - " 8.24653029, 9.14784336, 8.39632297, 8.88221884, 10.17075896,\n", - " 7.93166018, 8.50952411, 8.35107565, 8.62145162, 9.1473949 ,\n", - " 10.16314006, 9.48931861, 9.52163553, 10.48561263, 8.70149064,\n", - " 8.83968425, 8.77899456, 8.19752908, 8.23720503, 8.44300842,\n", - " 10.4865036 , 9.38597918, 8.16601682, 10.31557417, 9.39266205,\n", - " 9.3517375 , 8.26235414, 9.90602231, 8.08361053, 9.55309701,\n", - " 8.37694287, 8.2842195 , 9.27187061, 8.05741239, 9.81221128,\n", - " 8.67282987, 7.50111246, 8.84159875, 7.5928266 , 8.2180264 ,\n", - " 11.30247498, 8.97954369, 9.08557224, 8.62394547, 27.931288 ,\n", - " 11.31702137, 9.03355598, 9.82408452, 10.98696327, 8.15972924,\n", - " 8.10580516, 8.6766634 , 9.18826079, 9.91399217, 9.63535714,\n", - " 8.84899211, 8.59690166, 9.08935356, 7.87525439, 9.04824638,\n", - " 10.58436322, 8.05351543, 8.0442934 , 8.51687765, 8.23182964,\n", - " 7.90365982, 9.41734576, 7.82690763, 7.86053801, 8.81060672,\n", - " 15.63083076, 9.12365007, 8.4692018 , 8.38626456, 9.1455934 ,\n", - " 7.9579742 , 8.32254815, 9.60984373, 7.72059083, 9.80256414,\n", - " 8.03569841, 8.56897283, 9.88993764, 9.825032 , 9.10494757,\n", - " 7.96795917, 8.83923078, 8.12920213, 9.14702606, 10.44252062,\n", - " 8.11435223, 11.10698366, 8.54753256, 11.07914209, 8.0072608 ,\n", - " 8.64252162, 7.86998582, 8.16502595, 9.72599697, 8.01553535,\n", - " 8.05236411, 9.4306016 , 8.3510747 , 8.15123487, 7.73660946,\n", - " 8.78807712, 8.42650437, 9.09502602, 67.75333071, 14.179214 ,\n", - " 13.08692336, 14.52568007, 12.39239168, 8.40634942, 8.3893857 ,\n", - " 7.80925822, 8.04524732, 10.61561441, 9.33992386, 8.05361605,\n", - " 8.71911073, 8.13864756, 8.18779135, 8.03402972, 8.20232296,\n", - " 10.52845287, 8.21701574, 9.63750052, 8.16265893, 7.95386362,\n", - " 7.85334754, 7.96290469, 8.1984942 , 8.32950211, 17.0101552 ,\n", - " 14.20266891, 13.09765553, 14.32137418, 8.90045214, 9.79849219,\n", - " 7.7378149 , 8.17814636, 8.0692122 , 8.02391315, 7.73337412,\n", - " 8.24749708, 8.21430159, 8.42469835, 7.93915629, 8.17162681,\n", - " 9.29439068, 8.39062524, 8.05844831, 12.62865376, 8.03868556,\n", - " 8.03020358, 8.72658324, 7.98921943, 10.13008642, 8.36204886,\n", - " 9.8618927 , 8.84138846, 8.26497674, 8.53586483, 11.22441888,\n", - " 8.60046291, 9.52709126, 8.1862669 , 8.47402501, 8.08845234,\n", - " 8.0216496 , 8.25297642, 9.52822161, 8.53732967, 9.20458651,\n", - " 7.84344959, 8.76693869, 9.55830622, 9.32047439, 9.61785316,\n", - " 14.20765901, 13.20616293, 12.79950929, 13.23175693, 10.48755121,\n", - " 7.89634991, 8.62207508, 10.17518067, 9.5078795 , 8.16943836,\n", - " 11.88958383, 8.53581595, 8.78866196, 9.86849713, 8.38485384,\n", - " 7.80456519, 8.7930553 , 8.67091751, 11.64525867, 10.70969439,\n", - " 9.57600379, 7.88863015, 9.16765165, 8.10214615, 8.1002388 ,\n", - " 7.79884577, 7.84607792, 10.70999765, 8.32228923, 8.15903163,\n", - " 8.16516185, 11.13710332, 8.67460465, 8.04933095, 7.92010641,\n", - " 9.71926355, 7.96389985, 8.50223684, 7.80719972, 7.94503832,\n", - " 9.14503789, 8.74866915, 8.32825327, 9.38176489, 8.7043674 ,\n", - " 8.11469626, 8.39300489, 8.52375507, 9.48120856, 9.30481339,\n", - " 11.00180173, 8.00356221, 9.36562443, 11.26503015, 8.29429078,\n", - " 10.5787971 , 8.23888326, 8.25085521, 9.65488529, 10.22367787,\n", - " 8.86958766, 8.67924905, 9.8065629 , 9.98437238, 10.44085979,\n", - " 8.48997521, 13.41537356, 8.53429914, 9.41697288, 8.75000739,\n", - " 8.67022324, 10.65776849, 8.78767824, 29.17240787, 8.29843664,\n", - " 10.48030996, 8.60965252, 9.05648637, 11.23915553, 7.71198177,\n", - " 8.58811665, 11.27894258, 11.26059055, 8.08691239, 9.09145069,\n", - " 8.37398744, 9.33932018, 9.50723815, 14.62887979, 8.08766961,\n", - " 8.1010766 , 8.15962887, 7.86279893, 7.81253982, 8.72090292,\n", - " 28.51810336, 8.20156765, 8.10436082, 9.35736108, 10.11271501,\n", - " 8.28001332, 8.10338402, 7.82260585, 7.74735689, 9.37371802,\n", - " 7.83298874, 8.09861684, 11.44845009, 13.80942464, 13.86787438,\n", - " 12.95256805, 13.5946703 , 9.04438519, 8.42931032, 7.69650388,\n", - " 8.3203001 , 8.93009233, 8.99896145, 10.261621 , 9.76696181,\n", - " 8.42695355, 9.45543766, 8.35829163, 8.19327784, 8.54582119,\n", - " 10.28408813, 9.96855664, 9.4126513 , 8.85548735, 8.37564468,\n", - " 7.85812593, 11.26866746, 11.99777699, 8.90290856, 9.73011518,\n", - " 11.37953544, 9.56070495, 13.08286595, 7.91717887, 8.70709944,\n", - " 8.89286566, 9.43534017, 9.63375568, 9.45693254, 9.41722798,\n", - " 8.95478702, 10.59636545, 9.07217526, 8.91465688, 8.43598938,\n", - " 10.09872103, 8.53826594, 10.51633263, 8.16474724, 9.60920191,\n", - " 8.79985189, 11.08250904, 15.82575488, 13.72388315, 13.76962495,\n", - " 15.5107224 , 12.99527621, 9.55358648, 11.27318692, 10.64224267,\n", - " 9.28194666, 8.15835619, 10.34727526, 9.13943338, 8.47959018,\n", - " 12.95671797, 8.67874169, 9.48093748, 11.13487458, 11.16393185,\n", - " 9.45039058, 9.26687908, 10.83345985, 10.013412 , 12.88114643,\n", - " 8.90868664, 9.11424375, 10.62471223, 10.37447572, 8.56728458,\n", - " 11.44042325, 8.61506176, 14.37763166, 9.26899981, 9.01356244,\n", - " 12.6770153 , 7.95549965, 8.69824529, 8.16541219, 10.80149889,\n", - " 9.85532331, 9.16404986, 11.05029202, 8.95759201, 9.60003638,\n", - " 8.64066339, 11.99474025, 10.88645577, 9.82658648, 8.38357234,\n", - " 8.1931479 , 8.36809587, 8.34779596, 9.29737759, 7.71148348,\n", - " 8.34155583, 8.46944427, 9.46755242, 8.39070392, 9.67334032,\n", - " 9.42819619, 8.90718842, 8.95999622, 17.03638124, 14.13874507,\n", - " 14.17324162, 14.82433629, 10.27358413, 7.75390744, 10.63386297,\n", - " 10.74013877, 9.25264263, 8.88592076, 15.62230277, 8.68499494,\n", - " 7.90613437, 10.8253715 , 9.28829837, 9.96133757, 8.82941794,\n", - " 11.07499003, 9.08565426, 8.76584291, 11.91541052, 9.45269704,\n", - " 9.68554997, 9.76184082, 10.95884109, 9.22084093, 9.07609534,\n", - " 9.72482204, 8.66262245, 8.85580897, 12.12771249, 9.1096139 ,\n", - " 9.55135322, 9.73613167, 12.00068331, 9.63835907, 8.8003633 ,\n", - " 10.78142428, 10.36234426, 8.7075491 , 8.79299307, 10.6836946 ,\n", - " 8.24508142, 9.70224071, 8.64105797, 9.16640019])" + "array([72.32372785, 9.84876561, 8.23770022, 8.51928997, 10.07481861,\n", + " 9.50573421, 8.61487699, 9.2067368 , 8.7046783 , 8.50711679,\n", + " 10.19562602, 9.36683083, 7.55569768, 7.81828165, 8.05966425,\n", + " 7.95713139, 8.46397591, 8.00363493, 8.74197745, 9.85158968,\n", + " 8.15499783, 8.4186945 , 8.3459394 , 7.77093124, 10.68578815,\n", + " 8.50453854, 10.00224447, 7.92351389, 8.35350633, 9.25221825,\n", + " 8.34743953, 7.69802356, 8.29426646, 8.59166431, 8.46652174,\n", + " 9.47893 , 75.07776022, 8.41120005, 8.96823859, 10.98724246,\n", + " 7.94364333, 7.90829492, 8.94897985, 10.17237568, 9.11909747,\n", + " 7.87749696, 8.00650215, 8.36645675, 8.91567326, 10.34763336,\n", + " 7.77076221, 7.88283515, 7.75079894, 9.26020312, 8.62849283,\n", + " 7.80995584, 7.80822968, 10.76239038, 8.14607382, 7.99221134,\n", + " 9.6921885 , 8.19335723, 9.15633559, 8.00351334, 9.54636669,\n", + " 7.59108806, 7.69910932, 8.04463506, 7.93083286, 8.62544155,\n", + " 8.79564786, 9.13332033, 7.80911231, 8.35303783, 7.82778549,\n", + " 8.06088972, 9.1007092 , 8.3380692 , 8.64629507, 8.07920575,\n", + " 8.02019548, 10.71363115, 8.34652901, 7.74129391, 7.91959643,\n", + " 7.85336089, 7.90019083, 12.90023398, 19.41495585, 13.15474701,\n", + " 12.07031298, 11.95169044, 7.79440594, 10.60236144, 8.72420573,\n", + " 8.06074238, 8.54056549, 8.02011943, 9.75681472, 8.5499773 ,\n", + " 7.79605484, 8.42818856, 9.18867898, 7.9006598 , 27.39237142,\n", + " 9.0595603 , 8.78542948, 7.90973282, 8.57101846, 8.46719694,\n", + " 8.13528228, 8.01672626, 8.10046387, 9.75899887, 8.07786036,\n", + " 8.67664742, 8.93552232, 9.15524864, 9.84150887, 10.98953986,\n", + " 7.7957077 , 8.00527406, 8.84030366, 8.21525049, 8.41219711,\n", + " 8.58533263, 9.19599056, 8.0582397 , 9.54104137, 7.90521908,\n", + " 9.04605484, 9.31400728, 8.40495944, 9.40762448, 8.44924903,\n", + " 8.32500076, 9.12283754, 7.8790009 , 7.81203079, 9.60578918,\n", + " 8.49342728, 8.78831935, 8.37885499, 7.84795952, 8.0113368 ,\n", + " 10.01567674, 8.65693212, 7.91696692, 8.66499114, 8.16211271,\n", + " 10.52479053, 8.01505375, 7.76023841, 8.17951965, 8.72431993,\n", + " 8.06374121, 8.10564208, 8.59537721, 9.92992234, 8.32705522,\n", + " 9.78222585, 9.35200691, 9.76717973, 10.28895092, 9.19370604,\n", + " 8.0294354 , 8.37053967, 9.8634398 , 8.63042641, 8.18737698,\n", + " 43.09095621, 8.65846062, 10.26536632, 8.97337079, 8.09767246,\n", + " 11.08810735, 9.55694532, 15.43015337, 12.9441483 , 74.00292444,\n", + " 8.60825634, 9.67591119, 8.41766596, 8.16267896, 8.16726971,\n", + " 7.94481397, 9.10022426, 8.35536289, 9.09244132, 10.00751424,\n", + " 8.01892686, 8.92517757, 8.07546949, 8.11492538, 11.56878233,\n", + " 7.69102216, 7.67765045, 7.70162439, 8.58623576])" ] }, "execution_count": 22, @@ -1163,7 +1138,7 @@ { "data": { "text/plain": [ - "(array([ 0, 0, 0, 16, 105, 85, 75, 61, 40]),\n", + "(array([ 0, 0, 0, 12, 61, 44, 20, 19, 13]),\n", " array([ 6. , 6.44444444, 6.88888889, 7.33333333, 7.77777778,\n", " 8.22222222, 8.66666667, 9.11111111, 9.55555556, 10. ]))" ] @@ -1188,7 +1163,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 24, time 107.859 seconds: Counter({'Completed': 478, 'Failed': 21})izing': 1})Running': 1})\r" + "Final print 40, time 119.696 seconds: Counter({'Completed': 184, 'Failed': 15})izing': 1})Running': 1})\r" ] } ], @@ -1246,8 +1221,8 @@ { "data": { "text/plain": [ - "array([28, 33, 15, 45, 18, 43, 30, 31, 65, 6, 42, 16, 11, 41, 19, 8, 5,\n", - " 2, 64, 34])" + "array([ 76, 160, 87, 148, 146, 78, 140, 122, 6, 34, 192, 27, 74,\n", + " 94, 9, 169, 82, 152, 89, 38])" ] }, "execution_count": 25, @@ -1258,22 +1233,21 @@ "name": "stdout", "output_type": "stream", "text": [ - - "[244.173832 244.510378 245.027595 245.540781 247.395535 247.411761\n", - " 247.933416 248.256958 248.468753 249.724234 249.874347 250.013758\n", - " 250.53221 251.10704 251.400594 253.192625 253.421425 253.968411\n", - " 256.888013 260.331917]\n", + "[117.302675 117.403537 117.598663 117.907701 119.020195 122.495359\n", + " 128.828526 131.806861 132.140995 159.631798 166.340537 166.637734\n", + " 166.748339 169.732802 170.608667 170.923515 171.960079 186.075458\n", + " 213.533596 223.50992 ]\n", "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Failed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Failed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Failed' 'Completed']\n" + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Failed']\n" ] }, { "data": { "text/plain": [ - "array([232, 54, 195, 214, 250, 48, 490, 261, 329, 140, 336, 129, 311,\n", - " 223, 226, 370, 319, 254, 197, 85])" + "array([ 43, 125, 136, 85, 147, 195, 183, 50, 174, 116, 45, 23, 112,\n", + " 100, 72, 25, 120, 93, 98, 77])" ] }, "execution_count": 25, @@ -1284,19 +1258,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "[92.52469 92.854187 93.127771 93.19945 93.319895 93.372538 93.557287\n", - " 93.579393 93.646901 93.681486 93.890417 94.05724 94.162242 94.165297\n", - " 94.182998 94.263456 94.316783 94.400242 94.406081 94.583321]\n", - "['Completed' 'Completed' 'Completed' 'Completed' 'Failed' 'Completed'\n", - " 'Failed' 'Failed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Failed' 'Completed' 'Completed'\n", - " 'Failed' 'Completed']\n" + "[ 94.171413 97.399767 99.25924 99.408774 99.514601 99.538883\n", + " 99.752909 99.831267 100.191572 100.296421 100.401819 100.462001\n", + " 100.867313 100.895425 100.990763 101.042026 101.110186 101.184303\n", + " 101.320054 101.384652]\n", + "['Failed' 'Completed' 'Completed' 'Failed' 'Completed' 'Completed'\n", + " 'Failed' 'Completed' 'Completed' 'Completed' 'Failed' 'Completed'\n", + " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + " 'Completed' 'Completed']\n" ] }, { "data": { "text/plain": [ - "(array([ 0, 0, 128, 320, 8, 1, 3, 3, 0]),\n", + "(array([ 0, 0, 8, 169, 11, 0, 3, 5, 1]),\n", " array([ 50. , 66.66666667, 83.33333333, 100. ,\n", " 116.66666667, 133.33333333, 150. , 166.66666667,\n", " 183.33333333, 200. ]))" From 7e74f52d103dd0c9f5065d213a0c0f4e4da92bd7 Mon Sep 17 00:00:00 2001 From: Sharat Chikkerur Date: Tue, 31 Dec 2019 15:30:47 -0500 Subject: [PATCH 153/207] Added download script for dutch F3 dataset. Also adding Sharat/WH as authors. (#129) * added sharat, weehyong to authors * adding a download script for Dutch F3 dataset * Adding script instructions for dutch f3 * Update README.md prepare scripts expect root level directory for dutch f3 dataset. (it is downloaded into $dir/data by the script) --- AUTHORS.md | 3 ++- README.md | 12 +++++++++--- scripts/download_dutch_f3.sh | 25 +++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100755 scripts/download_dutch_f3.sh diff --git a/AUTHORS.md b/AUTHORS.md index c0011f3e..cb2995fa 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -16,7 +16,8 @@ To contributors: please add your name to the list when you submit a patch to the * Max Kaznady * Vanja Paunic * Mathew Salvaris - +* Sharat Chikkerur +* Wee Hyong Tok ## How to be a contributor to the repository This project welcomes contributions and suggestions. Most contributions require you to agree to a diff --git a/README.md b/README.md index 479e8e37..9e078d7b 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,13 @@ python scripts/prepare_penobscot.py split_inline --data-dir="$HOME/data/penobsco #### F3 Netherlands To download the F3 Netherlands dataset for 2D experiments, please follow the data download instructions at -[this github repository](https://github.com/yalaudah/facies_classification_benchmark) (section Dataset). +[this github repository](https://github.com/yalaudah/facies_classification_benchmark) (section Dataset). Atternatively, you can use the [download script](scripts/download_dutch_f3.sh) + +``` +data_dir="$HOME/data/dutch" +mkdir -p "$data_dir" +./scripts/download_dutch_f3.sh "$data_dir" +``` Once you've downloaded the data set, make sure to create an empty `splits` directory, under the downloaded `data` directory; you can re-use the same data directory as the one for Penobscot dataset created earlier. This is where your training/test/validation splits will be saved. @@ -136,11 +142,11 @@ To prepare the data for the experiments (e.g. split into train/val/test), please ``` # For section-based experiments -python scripts/prepare_dutchf3.py split_train_val section --data-dir=/mnt/dutchf3 +python scripts/prepare_dutchf3.py split_train_val section --data-dir=/mnt/dutchf3/data # For patch-based experiments -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutchf3 --stride=50 --patch=100 +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutchf3/data --stride=50 --patch=100 ``` diff --git a/scripts/download_dutch_f3.sh b/scripts/download_dutch_f3.sh new file mode 100755 index 00000000..2298961b --- /dev/null +++ b/scripts/download_dutch_f3.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# commitHash: 9abd6c82b2319ea5bfa9ff1c9a56478caad07ab9 +# url: https://github.com/yalaudah/facies_classification_benchmark +# +# Download the Dutch F3 dataset and extract +if [ ! -d $1 ]; then + echo "$1 does not exist" + exit 1; +fi; + +echo "Extracting to $1" +# Download the files: +temp_file=$(mktemp -d)/data.zip +wget -o /dev/null -O $temp_file https://www.dropbox.com/s/p6cbgbocxwj04sw/data.zip?dl=1 + +# Check that the md5 checksum matches to varify file integrity +echo "Expected output: MD5(data.zip)= bc5932279831a95c0b244fd765376d85" + +openssl dgst -md5 $temp_file + +# Unzip the data +unzip -d $1 $temp_file && rm $temp_file +echo "Download complete" From 801bf95abce9c14d0330d9e215396907e07e4a24 Mon Sep 17 00:00:00 2001 From: Sharat Chikkerur Date: Tue, 31 Dec 2019 18:55:31 -0500 Subject: [PATCH 154/207] Using env variable during dutchF3 splits --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e078d7b..2586caea 100644 --- a/README.md +++ b/README.md @@ -142,11 +142,11 @@ To prepare the data for the experiments (e.g. split into train/val/test), please ``` # For section-based experiments -python scripts/prepare_dutchf3.py split_train_val section --data-dir=/mnt/dutchf3/data +python scripts/prepare_dutchf3.py split_train_val section --data-dir=$data_dir/data # For patch-based experiments -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutchf3/data --stride=50 --patch=100 +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=$data_dir/data --stride=50 --patch=100 ``` From 15c86a3fa96d87ac47a1c645f2b8092b6bd14067 Mon Sep 17 00:00:00 2001 From: Mat Date: Wed, 1 Jan 2020 12:55:37 +0000 Subject: [PATCH 155/207] Improvements to dutchf3 (#128) * Adds padding to distributed training pipeline * Adds exception if supplied weights file is not found * Fixes hrnet location * Removes unecessary config --- .../cv_lib/segmentation/models/seg_hrnet.py | 10 +- .../notebooks/configs/hrnet.yaml | 2 +- .../dutchf3_patch/distributed/train.py | 7 + .../dutchf3_patch/local/configs/hrnet.yaml | 2 +- .../dutchf3_patch/local/test.sh | 2 +- .../dutchf3_patch/local/train.py | 11 +- .../penobscot/local/configs/hrnet.yaml | 2 +- .../dutchf3/data.py | 10 +- scripts/logging.conf | 34 ++++ scripts/prepare_dutchf3.py | 22 ++- tests/cicd/penobscot.yml | 179 ------------------ 11 files changed, 84 insertions(+), 197 deletions(-) create mode 100644 scripts/logging.conf delete mode 100644 tests/cicd/penobscot.yml diff --git a/cv_lib/cv_lib/segmentation/models/seg_hrnet.py b/cv_lib/cv_lib/segmentation/models/seg_hrnet.py index dd06118e..6671603f 100644 --- a/cv_lib/cv_lib/segmentation/models/seg_hrnet.py +++ b/cv_lib/cv_lib/segmentation/models/seg_hrnet.py @@ -427,14 +427,18 @@ def init_weights( elif isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) + + if pretrained and not os.path.isfile(pretrained): + raise FileNotFoundError(f"The file {pretrained} was not found. Please supply correct path or leave empty") + if os.path.isfile(pretrained): pretrained_dict = torch.load(pretrained) logger.info("=> loading pretrained model {}".format(pretrained)) model_dict = self.state_dict() pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict.keys()} - # for k, _ in pretrained_dict.items(): - # logger.info( - # '=> loading {} pretrained model {}'.format(k, pretrained)) + for k, _ in pretrained_dict.items(): + logger.info( + '=> loading {} pretrained model {}'.format(k, pretrained)) model_dict.update(pretrained_dict) self.load_state_dict(model_dict) diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index 689f8c9c..c746d58c 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -23,7 +23,7 @@ DATASET: MODEL: NAME: seg_hrnet IN_CHANNELS: 3 - PRETRAINED: '/data/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py index 3f19c106..5cc8dc54 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -131,6 +131,13 @@ def run(*options, cfg=None, local_rank=0, debug=False): basic_aug = Compose( [ Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1), + PadIfNeeded( + min_height=config.TRAIN.PATCH_SIZE, + min_width=config.TRAIN.PATCH_SIZE, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), Resize( config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), diff --git a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml index c1964c98..435be500 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml @@ -92,7 +92,7 @@ VALIDATION: BATCH_SIZE_PER_GPU: 128 TEST: - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/dutchf3/local/output/mat/exp/237c16780794800631c3f1895cacc475e15aca99/seg_hrnet/Sep17_115731/models/seg_hrnet_running_model_33.pth" + MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/interpretation/dutchf3_patch/local/output/staging/0d1d2bbf9685995a0515ca1d9de90f9bcec0db90/seg_hrnet/Dec20_233535/models/seg_hrnet_running_model_33.pth" TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True diff --git a/experiments/interpretation/dutchf3_patch/local/test.sh b/experiments/interpretation/dutchf3_patch/local/test.sh index ad68cf2e..a497e127 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.sh +++ b/experiments/interpretation/dutchf3_patch/local/test.sh @@ -1,2 +1,2 @@ #!/bin/bash -python test.py --cfg "configs/seresnet_unet.yaml" \ No newline at end of file +python test.py --cfg "configs/hrnet.yaml" \ No newline at end of file diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index fd8324c9..eca75b1b 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -110,6 +110,13 @@ def run(*options, cfg=None, debug=False): basic_aug = Compose( [ Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1), + PadIfNeeded( + min_height=config.TRAIN.PATCH_SIZE, + min_width=config.TRAIN.PATCH_SIZE, + border_mode=cv2.BORDER_CONSTANT, + always_apply=True, + mask_value=255, + ), Resize( config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), @@ -138,7 +145,7 @@ def run(*options, cfg=None, debug=False): patch_size=config.TRAIN.PATCH_SIZE, augmentations=train_aug, ) - + logger.info(train_set) val_set = TrainPatchLoader( config.DATASET.ROOT, split="val", @@ -147,7 +154,7 @@ def run(*options, cfg=None, debug=False): patch_size=config.TRAIN.PATCH_SIZE, augmentations=val_aug, ) - + logger.info(val_set) n_classes = train_set.n_classes train_loader = data.DataLoader( diff --git a/experiments/interpretation/penobscot/local/configs/hrnet.yaml b/experiments/interpretation/penobscot/local/configs/hrnet.yaml index 4ebf6484..06d2d765 100644 --- a/experiments/interpretation/penobscot/local/configs/hrnet.yaml +++ b/experiments/interpretation/penobscot/local/configs/hrnet.yaml @@ -21,7 +21,7 @@ DATASET: MODEL: NAME: seg_hrnet IN_CHANNELS: 3 - PRETRAINED: '/data/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index 36d69f21..7d272028 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -13,7 +13,7 @@ import torch from toolz import curry from torch.utils import data - +import logging from deepseismic_interpretation.dutchf3.utils.batch import ( interpolate_to_fit_data, parse_labels_in_image, @@ -641,7 +641,6 @@ def __getitem__(self, index): elif direction == "x": im = self.seismic[idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size] lbl = self.labels[idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size] - im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) # TODO: Add check for rotation augmentations and raise warning if found @@ -707,6 +706,11 @@ def __getitem__(self, index): if self.is_transform: im, lbl = self.transform(im, lbl) return im, lbl + + def __repr__(self): + unique, counts = np.unique(self.labels, return_counts=True) + ratio = counts/np.sum(counts) + return "\n".join(f"{lbl}: {cnt} [{rat}]"for lbl, cnt, rat in zip(unique, counts, ratio)) _TRAIN_PATCH_LOADERS = { @@ -751,6 +755,8 @@ def get_voxel_loader(cfg): def get_test_loader(cfg): + logger = logging.getLogger(__name__) + logger.info(f"Test loader {cfg.TRAIN.DEPTH}") return _TEST_LOADERS.get(cfg.TRAIN.DEPTH, TestSectionLoader) diff --git a/scripts/logging.conf b/scripts/logging.conf new file mode 100644 index 00000000..c4037cc2 --- /dev/null +++ b/scripts/logging.conf @@ -0,0 +1,34 @@ +[loggers] +keys=root,__main__,event_handlers + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[logger___main__] +level=DEBUG +handlers=consoleHandler +qualname=__main__ +propagate=0 + +[logger_event_handlers] +level=INFO +handlers=consoleHandler +qualname=event_handlers +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=DEBUG +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + diff --git a/scripts/prepare_dutchf3.py b/scripts/prepare_dutchf3.py index 40d9f4e6..2e9e57f9 100644 --- a/scripts/prepare_dutchf3.py +++ b/scripts/prepare_dutchf3.py @@ -119,8 +119,8 @@ def split_patch_train_val(data_dir, stride, patch, per_val=0.2, log_config=None) # Generate patches from sections # Process inlines - horz_locations = range(0, xline - patch, stride) - vert_locations = range(0, depth - patch, stride) + horz_locations = range(0, xline - stride, stride) + vert_locations = range(0, depth - stride, stride) logger.debug("Generating Inline patches") logger.debug(horz_locations) logger.debug(vert_locations) @@ -131,21 +131,29 @@ def _i_extract_patches(iline_range, horz_locations, vert_locations): for j, k in locations: yield "i_" + str(i) + "_" + str(j) + "_" + str(k) + test_iline_range = list(test_iline_range) test_i_list = list(_i_extract_patches(test_iline_range, horz_locations, vert_locations)) train_i_list = list(_i_extract_patches(train_iline_range, horz_locations, vert_locations)) + logger.debug(train_iline_range) + logger.debug(test_iline_range) # Process crosslines - horz_locations = range(0, iline - patch, stride) - vert_locations = range(0, depth - patch, stride) + horz_locations = range(0, iline - stride, stride) + vert_locations = range(0, depth - stride, stride) + logger.debug("Generating Crossline patches") + logger.debug(horz_locations) + logger.debug(vert_locations) def _x_extract_patches(xline_range, horz_locations, vert_locations): for j in xline_range: locations = ([i, k] for i in horz_locations for k in vert_locations) for i, k in locations: yield "x_" + str(i) + "_" + str(j) + "_" + str(k) - + test_xline_range = list(test_xline_range) test_x_list = list(_x_extract_patches(test_xline_range, horz_locations, vert_locations)) train_x_list = list(_x_extract_patches(train_xline_range, horz_locations, vert_locations)) + logger.debug(train_xline_range) + logger.debug(test_xline_range) train_list = train_x_list + train_i_list test_list = test_x_list + test_i_list @@ -254,7 +262,7 @@ def split_alaudah_et_al_19(data_dir, stride, fraction_validation=0.2, loader_typ # TODO: Try https://github.com/Chilipp/docrep for doscstring reuse class SplitTrainValCLI(object): - def section(self, data_dir, per_val=0.2, log_config=None): + def section(self, data_dir, per_val=0.2, log_config="logging.conf"): """Generate section based train and validation files for Netherlands F3 dataset. Args: @@ -265,7 +273,7 @@ def section(self, data_dir, per_val=0.2, log_config=None): """ return split_section_train_val(data_dir, per_val=per_val, log_config=log_config) - def patch(self, data_dir, stride, patch, per_val=0.2, log_config=None): + def patch(self, data_dir, stride, patch, per_val=0.2, log_config="logging.conf"): """Generate patch based train and validation files for Netherlands F3 dataset. Args: diff --git a/tests/cicd/penobscot.yml b/tests/cicd/penobscot.yml deleted file mode 100644 index 3d7ae4e3..00000000 --- a/tests/cicd/penobscot.yml +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -# Pull request against these branches will trigger this build -pr: -- master -- staging - -# Any commit to this branch will trigger the build. -trigger: -- master -- staging - -jobs: - -# ################################################################################################### -# # LOCAL PATCH JOBS -# ################################################################################################### - -# - job: hrnet_penobscot -# timeoutInMinutes: 5 -# displayName: hrnet penobscot -# pool: -# name: deepseismicagentpool -# steps: -# - bash: | -# conda env list -# source activate seismic-interpretation -# # run the tests -# cd experiments/interpretation/penobscot/local -# python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug -# # find the latest model which we just trained -# model=$(ls -td */seg_hrnet/*/* | head -1) -# echo ${model} -# # # try running the test script -# python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug - -# - job: seresnet_unet_penobscot -# timeoutInMinutes: 5 -# displayName: seresnet_unet penobscot -# pool: -# name: deepseismicagentpool -# steps: -# - bash: | -# conda env list -# source activate seismic-interpretation -# # run the tests -# cd experiments/interpretation/penobscot/local -# python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug -# # find the latest model which we just trained -# model=$(ls -td */resnet_unet/*/* | head -1) -# echo ${model} -# # try running the test script -# python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug - -# - job: hrnet_dutchf3 -# timeoutInMinutes: 5 -# displayName: hrnet dutchf3 -# pool: -# name: deepseismicagentpool -# steps: -# - bash: | -# source activate seismic-interpretation -# # run the tests -# cd experiments/interpretation/dutchf3_patch/local -# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug -# # find the latest model which we just trained -# model=$(ls -td */seg_hrnet/*/* | head -1) -# echo ${model} -# # try running the test script -# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug - -# - job: unet_dutchf3 -# timeoutInMinutes: 5 -# displayName: unet dutchf3 -# pool: -# name: deepseismicagentpool -# steps: -# - bash: | -# source activate seismic-interpretation -# # run the tests -# cd experiments/interpretation/dutchf3_patch/local -# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug -# # find the latest model which we just trained -# model=$(ls -td */resnet_unet/*/* | head -1) -# echo ${model} -# # try running the test script -# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/unet.yaml --debug - -# - job: seresnet_unet_dutchf3 -# timeoutInMinutes: 5 -# displayName: seresnet unet dutchf3 -# pool: -# name: deepseismicagentpool -# steps: -# - bash: | -# source activate seismic-interpretation -# # run the tests -# cd experiments/interpretation/dutchf3_patch/local -# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug -# # find the latest model which we just trained -# model=$(ls -td */resnet_unet/*/* | head -1) -# # try running the test script -# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug - -# - job: patch_deconvnet_dutchf3 -# timeoutInMinutes: 5 -# displayName: patch deconvnet dutchf3 -# pool: -# name: deepseismicagentpool -# steps: -# - bash: | -# source activate seismic-interpretation -# # run the tests -# cd experiments/interpretation/dutchf3_patch/local -# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.BATCH_SIZE_PER_GPU' 1 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet.yaml --debug -# # find the latest model which we just trained -# model=$(ls -td */patch_deconvnet/*/* | head -1) -# # try running the test script -# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_1.pth --cfg=configs/patch_deconvnet.yaml --debug - -# - job: patch_deconvnet_skip_dutchf3 -# timeoutInMinutes: 5 -# displayName: patch deconvnet skip dutchf3 -# pool: -# name: deepseismicagentpool -# steps: -# - bash: | -# source activate seismic-interpretation -# # run the tests -# cd experiments/interpretation/dutchf3_patch/local -# python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.BATCH_SIZE_PER_GPU' 1 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet_skip.yaml --debug -# # find the latest model which we just trained -# model=$(ls -td */patch_deconvnet_skip/*/* | head -1) -# # try running the test script -# python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_skip_running_model_1.pth --cfg=configs/patch_deconvnet_skip.yaml --debug - - -# ################################################################################################### -# # DISTRIBUTED PATCH JOBS -# ################################################################################################### - -# - job: hrnet_dutchf3_dist -# timeoutInMinutes: 5 -# displayName: hrnet dutchf3 distributed -# pool: -# name: deepseismicagentpool -# steps: -# - bash: | -# source activate seismic-interpretation -# # run the tests -# cd experiments/interpretation/dutchf3_patch/distributed -# python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug - - -- job: unet_dutchf3_dist - timeoutInMinutes: 5 - displayName: unet dutchf3 distributed - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug - -- job: seresnet_unet_dutchf3_dist - timeoutInMinutes: 5 - displayName: seresnet unet dutchf3 distributed - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug - \ No newline at end of file From 4dd55b82d317bfc6f609cdcd5c055bbcac99308d Mon Sep 17 00:00:00 2001 From: George Iordanescu Date: Mon, 6 Jan 2020 21:50:25 -0600 Subject: [PATCH 156/207] Ghiordan/azureml devito04 (#130) * exported conda env .yml file for AzureML control plane * both control plane and experimentation docker images use azure_ml sdk 1.0.81 --- .../fwi_dev_conda_environment_exported.yml | 0 ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 108 ++------- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 226 +++++++++--------- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 37 ++- ..._GeophysicsTutorial_FWI_Azure_devito.ipynb | 198 ++++++++------- 5 files changed, 246 insertions(+), 323 deletions(-) mode change 100644 => 100755 contrib/fwi/azureml_devito/fwi_dev_conda_environment_exported.yml diff --git a/contrib/fwi/azureml_devito/fwi_dev_conda_environment_exported.yml b/contrib/fwi/azureml_devito/fwi_dev_conda_environment_exported.yml old mode 100644 new mode 100755 diff --git a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb index 2c58effb..0b16843b 100755 --- a/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/000_Setup_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -77,13 +77,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.76\n" + "Azure ML SDK Version: 1.0.81\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1064-azure-x86_64-with-debian-stretch-sid'" + "'Linux-4.15.0-1064-azure-x86_64-with-debian-10.1'" ] }, "execution_count": 3, @@ -93,7 +93,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" + "'/workspace/contrib/fwi/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -151,7 +151,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Overwriting /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./src/project_utils.py\n" + "Overwriting /workspace/contrib/fwi/azureml_devito/notebooks/./src/project_utils.py\n" ] } ], @@ -508,15 +508,6 @@ "execution_count": 14, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING - Warning: Falling back to use azure cli login credentials.\n", - "If you run your code in unattended mode, i.e., where you can't give a user input, then we recommend to use ServicePrincipalAuthentication or MsiAuthentication.\n", - "Please refer to aka.ms/aml-notebook-auth for different authentication mechanisms in azureml-sdk.\n" - ] - }, { "name": "stdout", "output_type": "stream", @@ -669,84 +660,35 @@ "name": "stdout", "output_type": "stream", "text": [ - "azure-cli 2.0.58 *\r\n", - "\r\n", - "acr 2.2.0 *\r\n", - "acs 2.3.17 *\r\n", - "advisor 2.0.0 *\r\n", - "ams 0.4.1 *\r\n", - "appservice 0.2.13 *\r\n", - "backup 1.2.1 *\r\n", - "batch 3.4.1 *\r\n", - "batchai 0.4.7 *\r\n", - "billing 0.2.0 *\r\n", - "botservice 0.1.6 *\r\n", - "cdn 0.2.0 *\r\n", - "cloud 2.1.0 *\r\n", - "cognitiveservices 0.2.4 *\r\n", - "command-modules-nspkg 2.0.2 *\r\n", - "configure 2.0.20 *\r\n", - "consumption 0.4.2 *\r\n", - "container 0.3.13 *\r\n", - "core 2.0.58 *\r\n", - "cosmosdb 0.2.7 *\r\n", - "dla 0.2.4 *\r\n", - "dls 0.1.8 *\r\n", - "dms 0.1.2 *\r\n", - "eventgrid 0.2.1 *\r\n", - "eventhubs 0.3.3 *\r\n", - "extension 0.2.3 *\r\n", - "feedback 2.1.4 *\r\n", - "find 0.2.13 *\r\n", - "hdinsight 0.3.0 *\r\n", - "interactive 0.4.1 *\r\n", - "iot 0.3.6 *\r\n", - "iotcentral 0.1.6 *\r\n", - "keyvault 2.2.11 *\r\n", - "kusto 0.1.0 *\r\n", - "lab 0.1.5 *\r\n", - "maps 0.3.3 *\r\n", - "monitor 0.2.10 *\r\n", - "network 2.3.2 *\r\n", - "nspkg 3.0.3 *\r\n", - "policyinsights 0.1.1 *\r\n", - "profile 2.1.3 *\r\n", - "rdbms 0.3.7 *\r\n", - "redis 0.4.0 *\r\n", - "relay 0.1.3 *\r\n", - "reservations 0.4.1 *\r\n", - "resource 2.1.10 *\r\n", - "role 2.4.0 *\r\n", - "search 0.1.1 *\r\n", - "security 0.1.0 *\r\n", - "servicebus 0.3.3 *\r\n", - "servicefabric 0.1.12 *\r\n", - "signalr 1.0.0 *\r\n", - "sql 2.1.9 *\r\n", - "sqlvm 0.1.0 *\r\n", - "storage 2.3.1 *\r\n", - "telemetry 1.0.1 *\r\n", - "vm 2.2.15 *\r\n", + "azure-cli 2.0.78\r\n", "\r\n", - "Extensions:\r\n", - "azure-ml-admin-cli 0.0.1\r\n", - "azure-cli-ml Unknown\r\n", + "command-modules-nspkg 2.0.3\r\n", + "core 2.0.78\r\n", + "nspkg 3.0.4\r\n", + "telemetry 1.0.4\r\n", "\r\n", "Python location '/opt/az/bin/python3'\r\n", - "Extensions directory '/opt/az/extensions'\r\n", + "Extensions directory '/root/.azure/cliextensions'\r\n", "\r\n", - "Python (Linux) 3.6.5 (default, Feb 12 2019, 02:10:43) \r\n", - "[GCC 5.4.0 20160609]\r\n", + "Python (Linux) 3.6.5 (default, Dec 12 2019, 11:11:10) \r\n", + "[GCC 6.3.0 20170516]\r\n", "\r\n", "Legal docs and information: aka.ms/AzureCliLegal\r\n", "\r\n", "\r\n", - "\u001b[33mYou have 57 updates available. Consider updating your CLI installation.\u001b[0m\r\n" + "\r\n", + "Your CLI is up-to-date.\r\n", + "\r\n", + "\u001b[33m\u001b[1mPlease let us know how we are doing: \u001b[34mhttps://aka.ms/clihats\u001b[0m\r\n" ] } ], "source": [ "!az --version\n", + "\n", + "# !az login\n", + "# ! az account set --subscription $subscription_id\n", + "\n", "if create_ACR_FLAG:\n", " !az login\n", " response01 = ! az account list --all --refresh -o table\n", @@ -785,9 +727,9 @@ { "data": { "text/plain": [ - "[' \"loginServer\": \"fwi01acr.azurecr.io\",',\n", - " ' \"name\": \"fwi01acr\",',\n", - " ' \"networkRuleSet\": null,',\n", + "[' \"type\": \"Notary\"',\n", + " ' }',\n", + " ' },',\n", " ' \"provisioningState\": \"Succeeded\",',\n", " ' \"resourceGroup\": \"ghiordanfwirsg01\",',\n", " ' \"sku\": {',\n", @@ -899,9 +841,9 @@ ], "metadata": { "kernelspec": { - "display_name": "fwi_dev_conda_environment Python", + "display_name": "Python [conda env:aml-sdk-conda-env] *", "language": "python", - "name": "fwi_dev_conda_environment" + "name": "conda-env-aml-sdk-conda-env-py" }, "language_info": { "codemirror_mode": { diff --git a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb index 5d78b5c6..792506bf 100755 --- a/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/010_CreateExperimentationDockerImage_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -65,7 +65,7 @@ { "data": { "text/plain": [ - "'Linux-4.15.0-1064-azure-x86_64-with-debian-stretch-sid'" + "'Linux-4.15.0-1064-azure-x86_64-with-debian-10.1'" ] }, "execution_count": 3, @@ -75,7 +75,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" + "'/workspace/contrib/fwi/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -184,7 +184,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks\r\n" + "/workspace/contrib/fwi/azureml_devito/notebooks\r\n" ] } ], @@ -199,7 +199,7 @@ "outputs": [], "source": [ "# azureml_sdk_version set here must match azureml sdk version pinned in conda env file written to conda_common_file_path below\n", - "azureml_sdk_version = '1.0.79' " + "azureml_sdk_version = '1.0.81' " ] }, { @@ -222,7 +222,7 @@ { "data": { "text/plain": [ - "(True, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', 'sdk.v1.0.79')" + "(True, 'EXPERIMENTATION_DOCKER_IMAGE_TAG', 'sdk.v1.0.81')" ] }, "execution_count": 9, @@ -255,6 +255,7 @@ "\n", "\n", "docker_container_mount_point = os.getcwd()\n", + "docker_container_mount_point = '/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'\n", "# or something like \"/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'\n", "dotenv.set_key(dotenv_file_path, 'DOCKER_CONTAINER_MOUNT_POINT', docker_container_mount_point)" ] @@ -267,7 +268,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81'" ] }, "execution_count": 10, @@ -277,7 +278,7 @@ { "data": { "text/plain": [ - "'conda_env_fwi01_azureml_sdk.v1.0.79.yml'" + "'conda_env_fwi01_azureml_sdk.v1.0.81.yml'" ] }, "execution_count": 10, @@ -287,7 +288,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.79.yml'" + "'/workspace/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.81.yml'" ] }, "execution_count": 10, @@ -297,7 +298,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml'" + "'/workspace/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml'" ] }, "execution_count": 10, @@ -307,7 +308,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build'" + "'/workspace/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build'" ] }, "execution_count": 10, @@ -317,7 +318,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.79'" + "'/workspace/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.81'" ] }, "execution_count": 10, @@ -374,7 +375,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" + "Overwriting /workspace/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml.yml\n" ] } ], @@ -410,7 +411,7 @@ " - toolz\n", " - pip:\n", " - anytree # required by devito\n", - " - azureml-sdk[notebooks,automl]==1.0.79\n", + " - azureml-sdk[notebooks,automl]\n", " - codepy # required by devito\n", " - papermill[azure]\n", " - pyrevolve # required by devito" @@ -425,7 +426,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.79\n" + "Writing /workspace/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.81\n" ] } ], @@ -478,7 +479,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.79.yml'" + "'/workspace/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/conda_env_fwi01_azureml_sdk.v1.0.81.yml'" ] }, "execution_count": 13, @@ -489,10 +490,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "total 12\r\n", - "-rw-rw-r-- 1 loginvm0_1 loginvm0_1 725 Dec 17 19:25 conda_env_fwi01_azureml_sdk.v1.0.79.yml\r\n", - "-rw-rw-r-- 1 loginvm0_1 loginvm0_1 725 Dec 17 19:25 conda_env_fwi01_azureml.yml\r\n", - "-rw-rw-r-- 1 loginvm0_1 loginvm0_1 1073 Dec 17 19:25 Dockerfile_fwi01_azureml_sdk.v1.0.79\r\n" + "total 20\r\n", + "-rwxrwxrwx 1 1003 1003 1073 Dec 17 19:25 Dockerfile_fwi01_azureml_sdk.v1.0.79\r\n", + "-rw-r--r-- 1 root root 1073 Jan 4 00:30 Dockerfile_fwi01_azureml_sdk.v1.0.81\r\n", + "-rwxrwxrwx 1 1003 1003 717 Jan 4 00:30 conda_env_fwi01_azureml.yml\r\n", + "-rwxrwxrwx 1 1003 1003 725 Dec 17 19:25 conda_env_fwi01_azureml_sdk.v1.0.79.yml\r\n", + "-rw-r--r-- 1 root root 717 Jan 4 00:30 conda_env_fwi01_azureml_sdk.v1.0.81.yml\r\n" ] } ], @@ -510,7 +513,7 @@ { "data": { "text/plain": [ - "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79 -f /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.79 /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build '" + "'docker build -t fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81 -f /workspace/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build/Dockerfile_fwi01_azureml_sdk.v1.0.81 /workspace/contrib/fwi/azureml_devito/notebooks/./../temp/docker_build '" ] }, "execution_count": 14, @@ -520,11 +523,11 @@ { "data": { "text/plain": [ - "['Sending build context to Docker daemon 6.144kB',\n", + "['Sending build context to Docker daemon 9.728kB',\n", " '',\n", " 'Step 1/15 : FROM continuumio/miniconda3:4.7.12',\n", - " '4.7.12: Pulling from continuumio/miniconda3',\n", - " 'b8f262c62ec6: Pulling fs layer']" + " ' ---> 406f2b43ea59',\n", + " 'Step 2/15 : MAINTAINER George Iordanescu ']" ] }, "execution_count": 14, @@ -534,11 +537,11 @@ { "data": { "text/plain": [ - "[' ---> Running in b56a4fef410b',\n", - " 'Removing intermediate container b56a4fef410b',\n", - " ' ---> 74c9cf8d9517',\n", - " 'Successfully built 74c9cf8d9517',\n", - " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79']" + "[' ---> Running in 815d23815e0f',\n", + " 'Removing intermediate container 815d23815e0f',\n", + " ' ---> b9555c46cc92',\n", + " 'Successfully built b9555c46cc92',\n", + " 'Successfully tagged fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81']" ] }, "execution_count": 14, @@ -575,7 +578,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81'" ] }, "execution_count": 15, @@ -595,7 +598,7 @@ { "data": { "text/plain": [ - "b'/\\n1.0.79\\n'" + "b'/\\n1.0.81\\n'" ] }, "execution_count": 15, @@ -656,7 +659,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -665,7 +668,7 @@ "text": [ "\n", "content of devito tests log file before testing:\n", - "Before running e13n container... \r\n" + "Before running e13n container... \n" ] }, { @@ -674,7 +677,7 @@ "' python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/'" ] }, - "execution_count": 18, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -698,16 +701,16 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/ \"'" + "'docker run -it --rm --name fwi01_azureml_container -v /datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks:/workspace:rw fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81 /bin/bash -c \"conda env list ; ls -l /devito/tests; python -c \\'import azureml.core;print(azureml.core.VERSION)\\'; cd /devito; python -m pytest tests/ > ./fwi01_azureml_buildexperimentationdockerimage.log 2>&1; mv ./fwi01_azureml_buildexperimentationdockerimage.log /workspace/ \"'" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" }, @@ -721,50 +724,54 @@ "fwi01_conda_env * /opt/conda/envs/fwi01_conda_env\n", "\n", "total 560\n", - "-rw-r--r-- 1 root root 11521 Dec 17 19:26 conftest.py\n", - "-rw-r--r-- 1 root root 6006 Dec 17 19:26 test_adjoint.py\n", - "-rw-r--r-- 1 root root 14586 Dec 17 19:26 test_autotuner.py\n", - "-rw-r--r-- 1 root root 7538 Dec 17 19:26 test_builtins.py\n", - "-rw-r--r-- 1 root root 24415 Dec 17 19:26 test_caching.py\n", - "-rw-r--r-- 1 root root 9721 Dec 17 19:26 test_checkpointing.py\n", - "-rw-r--r-- 1 root root 1095 Dec 17 19:26 test_constant.py\n", - "-rw-r--r-- 1 root root 55954 Dec 17 19:26 test_data.py\n", - "-rw-r--r-- 1 root root 481 Dec 17 19:26 test_dependency_bugs.py\n", - "-rw-r--r-- 1 root root 16331 Dec 17 19:26 test_derivatives.py\n", - "-rw-r--r-- 1 root root 1473 Dec 17 19:26 test_differentiable.py\n", - "-rw-r--r-- 1 root root 30846 Dec 17 19:26 test_dimension.py\n", - "-rw-r--r-- 1 root root 24838 Dec 17 19:26 test_dle.py\n", - "-rw-r--r-- 1 root root 1169 Dec 17 19:26 test_docstrings.py\n", - "-rw-r--r-- 1 root root 32134 Dec 17 19:26 test_dse.py\n", - "-rw-r--r-- 1 root root 8205 Dec 17 19:26 test_gradient.py\n", - "-rw-r--r-- 1 root root 15227 Dec 17 19:26 test_interpolation.py\n", - "-rw-r--r-- 1 root root 31816 Dec 17 19:26 test_ir.py\n", - "-rw-r--r-- 1 root root 63169 Dec 17 19:26 test_mpi.py\n", - "-rw-r--r-- 1 root root 67053 Dec 17 19:26 test_operator.py\n", - "-rw-r--r-- 1 root root 14875 Dec 17 19:26 test_ops.py\n", - "-rw-r--r-- 1 root root 12228 Dec 17 19:26 test_pickle.py\n", - "-rw-r--r-- 1 root root 1809 Dec 17 19:26 test_resample.py\n", - "-rw-r--r-- 1 root root 1754 Dec 17 19:26 test_save.py\n", - "-rw-r--r-- 1 root root 2115 Dec 17 19:26 test_staggered_utils.py\n", - "-rw-r--r-- 1 root root 5711 Dec 17 19:26 test_subdomains.py\n", - "-rw-r--r-- 1 root root 3320 Dec 17 19:26 test_symbolic_coefficients.py\n", - "-rw-r--r-- 1 root root 7277 Dec 17 19:26 test_tensors.py\n", - "-rw-r--r-- 1 root root 3186 Dec 17 19:26 test_timestepping.py\n", - "-rw-r--r-- 1 root root 603 Dec 17 19:26 test_tools.py\n", - "-rw-r--r-- 1 root root 3296 Dec 17 19:26 test_tti.py\n", - "-rw-r--r-- 1 root root 8835 Dec 17 19:26 test_visitors.py\n", - "-rw-r--r-- 1 root root 21802 Dec 17 19:26 test_yask.py\n", - "1.0.79\n", + "-rw-r--r-- 1 root root 11521 Jan 4 00:30 conftest.py\n", + "-rw-r--r-- 1 root root 5937 Jan 4 00:30 test_adjoint.py\n", + "-rw-r--r-- 1 root root 12326 Jan 4 00:30 test_autotuner.py\n", + "-rw-r--r-- 1 root root 7538 Jan 4 00:30 test_builtins.py\n", + "-rw-r--r-- 1 root root 24415 Jan 4 00:30 test_caching.py\n", + "-rw-r--r-- 1 root root 9721 Jan 4 00:30 test_checkpointing.py\n", + "-rw-r--r-- 1 root root 1095 Jan 4 00:30 test_constant.py\n", + "-rw-r--r-- 1 root root 55954 Jan 4 00:30 test_data.py\n", + "-rw-r--r-- 1 root root 481 Jan 4 00:30 test_dependency_bugs.py\n", + "-rw-r--r-- 1 root root 16331 Jan 4 00:30 test_derivatives.py\n", + "-rw-r--r-- 1 root root 1473 Jan 4 00:30 test_differentiable.py\n", + "-rw-r--r-- 1 root root 30846 Jan 4 00:30 test_dimension.py\n", + "-rw-r--r-- 1 root root 23484 Jan 4 00:30 test_dle.py\n", + "-rw-r--r-- 1 root root 1175 Jan 4 00:30 test_docstrings.py\n", + "-rw-r--r-- 1 root root 32930 Jan 4 00:30 test_dse.py\n", + "-rw-r--r-- 1 root root 8205 Jan 4 00:30 test_gradient.py\n", + "-rw-r--r-- 1 root root 15227 Jan 4 00:30 test_interpolation.py\n", + "-rw-r--r-- 1 root root 31797 Jan 4 00:30 test_ir.py\n", + "-rw-r--r-- 1 root root 63169 Jan 4 00:30 test_mpi.py\n", + "-rw-r--r-- 1 root root 67153 Jan 4 00:30 test_operator.py\n", + "-rw-r--r-- 1 root root 14780 Jan 4 00:30 test_ops.py\n", + "-rw-r--r-- 1 root root 12237 Jan 4 00:30 test_pickle.py\n", + "-rw-r--r-- 1 root root 1809 Jan 4 00:30 test_resample.py\n", + "-rw-r--r-- 1 root root 1754 Jan 4 00:30 test_save.py\n", + "-rw-r--r-- 1 root root 2115 Jan 4 00:30 test_staggered_utils.py\n", + "-rw-r--r-- 1 root root 5711 Jan 4 00:30 test_subdomains.py\n", + "-rw-r--r-- 1 root root 3320 Jan 4 00:30 test_symbolic_coefficients.py\n", + "-rw-r--r-- 1 root root 7277 Jan 4 00:30 test_tensors.py\n", + "-rw-r--r-- 1 root root 3186 Jan 4 00:30 test_timestepping.py\n", + "-rw-r--r-- 1 root root 603 Jan 4 00:30 test_tools.py\n", + "-rw-r--r-- 1 root root 3125 Jan 4 00:30 test_tti.py\n", + "-rw-r--r-- 1 root root 8835 Jan 4 00:30 test_visitors.py\n", + "-rw-r--r-- 1 root root 21716 Jan 4 00:30 test_yask.py\n", + "1.0.81\n", "\n", "content of devito tests log file after testing:\n", "============================= test session starts ==============================\n", "platform linux -- Python 3.6.9, pytest-5.3.2, py-1.8.0, pluggy-0.13.1\n", "rootdir: /devito, inifile: setup.cfg\n", - "plugins: cov-2.8.1, nbval-0.9.3\n", - "collected 1056 items / 2 skipped / 1054 selected\n", + "plugins: nbval-0.9.4, cov-2.8.1\n", + "/tmp/devito-jitcache-uid0/db6a48f1a0b6e8998ac851d39f5db8ac9b0e69eb.c: In function ‘bf0’:\n", + "/tmp/devito-jitcache-uid0/db6a48f1a0b6e8998ac851d39f5db8ac9b0e69eb.c:217: warning: ignoring #pragma omp simd [-Wunknown-pragmas]\n", + " #pragma omp simd aligned(damp,delta,epsilon,phi,theta,u,v,vp:32)\n", + " \n", + "collected 1065 items / 2 skipped / 1063 selected\n", "\n", "tests/test_adjoint.py .......................... [ 2%]\n", - "tests/test_autotuner.py ..........s..... [ 3%]\n", + "tests/test_autotuner.py .........s..... [ 3%]\n", "tests/test_builtins.py ....s...............s..s [ 6%]\n", "tests/test_caching.py .................................................. [ 10%]\n", " [ 10%]\n", @@ -775,20 +782,20 @@ "tests/test_derivatives.py .............................................. [ 20%]\n", "........................................................................ [ 27%]\n", "........................................................................ [ 34%]\n", - "...... [ 35%]\n", - "tests/test_differentiable.py .. [ 35%]\n", - "tests/test_dimension.py ............................... [ 38%]\n", - "tests/test_dle.py ...................................................... [ 43%]\n", - "........................................... [ 47%]\n", - "tests/test_docstrings.py ................ [ 48%]\n", - "tests/test_dse.py ......x............................................... [ 53%]\n", - "................x..........s.... [ 57%]\n", + "...... [ 34%]\n", + "tests/test_differentiable.py .. [ 34%]\n", + "tests/test_dimension.py ............................... [ 37%]\n", + "tests/test_dle.py ...................................................... [ 42%]\n", + "......................................................... [ 48%]\n", + "tests/test_docstrings.py ................ [ 49%]\n", + "tests/test_dse.py ......x............................................... [ 54%]\n", + "............x..........s.... [ 57%]\n", "tests/test_gradient.py .... [ 57%]\n", - "tests/test_interpolation.py ........................ [ 59%]\n", - "tests/test_ir.py ....................................................... [ 64%]\n", + "tests/test_interpolation.py ........................ [ 60%]\n", + "tests/test_ir.py ....................................................... [ 65%]\n", "................ [ 66%]\n", "tests/test_mpi.py ssssssssssssssssssssssssssssssssssssssssssssssssssssss [ 71%]\n", - "sss [ 71%]\n", + "sss [ 72%]\n", "tests/test_operator.py ................................................. [ 76%]\n", "..............................................s......................... [ 83%]\n", ".................................. [ 86%]\n", @@ -808,7 +815,7 @@ "=================================== FAILURES ===================================\n", "______________________ TestSC.test_function_coefficients _______________________\n", "\n", - "self = \n", + "self = \n", "\n", " def test_function_coefficients(self):\n", " \"\"\"Test that custom function coefficients return the expected result\"\"\"\n", @@ -852,10 +859,10 @@ " \n", "> assert np.all(np.isclose(f0.data[:] - f1.data[:], 0.0, atol=1e-5, rtol=0))\n", "E assert Data(False)\n", - "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", - "E + where = np.all\n", - "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", - "E + where = np.isclose\n", + "E + where Data(False) = (Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]))\n", + "E + where = np.all\n", + "E + and Data([[[False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True],\\n ...alse],\\n [False, False, False, False],\\n [False, False, False, False],\\n [ True, True, True, True]]]) = ((Data([[[-1452., -1452., -1452., -1452.],\\n [ 3327., 3327., 3327., 3327.],\\n [-3414., -3414., -3414., -341...3., 383., 383.],\\n [ -598., -598., -598., -598.],\\n [ 341., 341., 341., 341.]]], dtype=float32) - Data([[[-1451.9998 , -1451.9998 , -1451.9998 , -1451.9998 ],\\n [ 3326.9995 , 3326.9995 , 3326.9995 , 33...4 , -597.99994 , -597.99994 ],\\n [ 341. , 341. , 341. , 341. ]]],\\n dtype=float32)), 0.0, atol=1e-05, rtol=0)\n", + "E + where = np.isclose\n", "\n", "tests/test_symbolic_coefficients.py:96: AssertionError\n", "----------------------------- Captured stderr call -----------------------------\n", @@ -870,9 +877,9 @@ " \n", "Operator `Kernel` run in 0.01 s\n", "------------------------------ Captured log call -------------------------------\n", - "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", - "INFO Devito:logger.py:129 Operator `Kernel` run in 0.01 s\n", - "====== 1 failed, 968 passed, 87 skipped, 2 xfailed in 1250.42s (0:20:50) =======\n" + "INFO Devito:logger.py:120 Operator `Kernel` run in 0.01 s\n", + "INFO Devito:logger.py:120 Operator `Kernel` run in 0.01 s\n", + "======= 1 failed, 977 passed, 87 skipped, 2 xfailed in 912.69s (0:15:12) =======\n" ] } ], @@ -902,7 +909,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -911,7 +918,7 @@ "'az acr login --name fwi01acr'" ] }, - "execution_count": 20, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" }, @@ -920,7 +927,7 @@ "output_type": "stream", "text": [ "Login Succeeded\r\n", - "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\r\n", + "WARNING! Your password will be stored unencrypted in /root/.docker/config.json.\r\n", "Configure a credential helper to remove this warning. See\r\n", "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", "\r\n", @@ -946,16 +953,16 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" + "'docker push fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81'" ] }, - "execution_count": 21, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -967,7 +974,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -976,10 +983,10 @@ "text": [ "The push refers to repository [fwi01acr.azurecr.io/fwi01_azureml]\n", "\n", - "\u001b[1B92dd230d: Preparing \n", - "\u001b[1B589afea5: Preparing \n", - "\u001b[1B66b51e6b: Preparing \n", - "\u001b[1B87808927: Preparing \n", + "\u001b[1Bd3b23d9e: Preparing \n", + "\u001b[1Bdf104634: Preparing \n", + "\u001b[1Bbb9ec1ac: Preparing \n", + "\u001b[1B71d4d165: Preparing \n", "\u001b[1Bcb249b79: Preparing \n", "\u001b[1B190fd43a: Preparing \n" ] @@ -988,21 +995,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[6B589afea5: Pushing 1.15GB/3.042GBA\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[3A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[2A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[1A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[4A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 894.4MB/3.042GB\u001b[6A\u001b[1K\u001b[KPushing 899.4MB/3.042GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K" + "\u001b[7Bd3b23d9e: Pushing 1.525GB/2.966GBA\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[5A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[1A\u001b[2K\u001b[2A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[4A\u001b[2K\u001b[4A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[6B589afea5: Pushing 2.285GB/3.042GB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[KPushing 2.037GB/3.042GB\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[7B92dd230d: Pushed 3.049GB/2.97GBB\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 2.971GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[KPushing 2.993GB\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[K\u001b[6A\u001b[1K\u001b[K\u001b[7A\u001b[1K\u001b[Ksdk.v1.0.79: digest: sha256:9f887658e5189ca6e69442ffcea2b158d1727302232cd07211b53ea268bdafbf size: 1800\n" + "\u001b[6Bdf104634: Pushed 3.046GB/2.966GB\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[6A\u001b[2K\u001b[7A\u001b[2K\u001b[7A\u001b[2Ksdk.v1.0.81: digest: sha256:bf183c89265716cbe65b38f03f4f8c0472dfd394a813cc51dea2513cc27eff45 size: 1800\n" ] } ], @@ -1012,7 +1012,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": {}, "outputs": [ { diff --git a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb index 43323835..e24df855 100755 --- a/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -75,13 +75,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.76\n" + "Azure ML SDK Version: 1.0.81\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1064-azure-x86_64-with-debian-stretch-sid'" + "'Linux-4.15.0-1064-azure-x86_64-with-debian-10.1'" ] }, "execution_count": 3, @@ -91,7 +91,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" + "'/workspace/contrib/fwi/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -246,7 +246,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing ./../temp/devito_tutorial/01_modelling.py\n" + "Overwriting ./../temp/devito_tutorial/01_modelling.py\n" ] } ], @@ -481,7 +481,7 @@ { "data": { "text/plain": [ - "'fwi01_azureml:sdk.v1.0.79'" + "'fwi01_azureml:sdk.v1.0.81'" ] }, "execution_count": 9, @@ -491,7 +491,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81'" ] }, "execution_count": 9, @@ -530,7 +530,7 @@ "output_type": "stream", "text": [ "Login Succeeded\r\n", - "WARNING! Your password will be stored unencrypted in /home/loginvm0_1/.docker/config.json.\r\n", + "WARNING! Your password will be stored unencrypted in /root/.docker/config.json.\r\n", "Configure a credential helper to remove this warning. See\r\n", "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\r\n", "\r\n", @@ -554,7 +554,7 @@ { "data": { "text/plain": [ - "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79 /bin/bash -c \"which python\" '" + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81 /bin/bash -c \"which python\" '" ] }, "execution_count": 11, @@ -606,7 +606,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing ./../temp/devito_tutorial/azureml_01_modelling.py\n" + "Overwriting ./../temp/devito_tutorial/azureml_01_modelling.py\n" ] } ], @@ -725,15 +725,6 @@ "execution_count": 14, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING - Warning: Falling back to use azure cli login credentials.\n", - "If you run your code in unattended mode, i.e., where you can't give a user input, then we recommend to use ServicePrincipalAuthentication or MsiAuthentication.\n", - "Please refer to aka.ms/aml-notebook-auth for different authentication mechanisms in azureml-sdk.\n" - ] - }, { "name": "stdout", "output_type": "stream", @@ -859,12 +850,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "cfac8c9cc9f346fd887ef7f9a7ec1c32", + "model_id": "583bcb0530354691b76ee44c897972cd", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'NOTSET',…" + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" ] }, "metadata": {}, @@ -872,7 +863,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576613450_585d065c?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1576613450_585d065c\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1576613450_585d065c\", \"created_utc\": \"2019-12-17T20:10:54.479957Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"ghiordan/azureml_devito03\", \"mlflow.source.git.branch\": \"ghiordan/azureml_devito03\", \"azureml.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"mlflow.source.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-17T20:20:27.928692Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/55_azureml-execution-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt?sv=2019-02-02&sr=b&sig=xAqF2NZ8MTrYgUXMbxagR8kh%2BT9o5uScuBFJwvJOSGw%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/65_job_prep-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt?sv=2019-02-02&sr=b&sig=KZQDqgrmMR1X8BB5rb0C1Jg%2BfncC8l3MXYkKI9anv5c%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=nX0jFpfGXYoETXMnnjiK0CGdjW9fwJqhAg5%2BB5RYWEU%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/75_job_post-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/75_job_post-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt?sv=2019-02-02&sr=b&sig=g1AS%2BwhRfqkTeScqQmeRWg2%2FhTFYY9QSz%2FeQMhnVXCo%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=9jqsBUn4oITucw0a%2BcH4jYpBUz%2BJkVJu35Yzi24Q%2B8Y%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=8rDEAz1Pun06dF8Nh7mMRtLxvI83axVNdXPutXZ9NXw%3D&st=2019-12-17T20%3A10%3A37Z&se=2019-12-18T04%3A20%3A37Z&sp=r\", \"logs/azureml/689_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/logs/azureml/689_azureml.log?sv=2019-02-02&sr=b&sig=3BrTrM6pyz6zL48sMVMmkyzFDd5WbSntii94gnNuhTc%3D&st=2019-12-17T20%3A10%3A38Z&se=2019-12-18T04%3A20%3A38Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576613450_585d065c/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=hwXqleQ4EWbK4%2BGvsm8084zIMZt%2FObojTRdNYIAqraI%3D&st=2019-12-17T20%3A10%3A38Z&se=2019-12-18T04%3A20%3A38Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_830adb90e61c0fc2c85aec9af8d968b9519fb3b8faf182dcba20a63f98ccb512_d.txt\"], [\"logs/azureml/689_azureml.log\"]], \"run_duration\": \"0:09:33\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1576613450_585d065c\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-17 20:18:34,466|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-17 20:18:34,467|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-17 20:18:34,467|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-17 20:18:34,467|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-17 20:18:34,856|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-17 20:18:34,856|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-17 20:18:35,243|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-17 20:18:35,243|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-17 20:18:35,243|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-17 20:18:35,243|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-17 20:18:35,243|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-17 20:18:35,824|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-17 20:18:35,829|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-17 20:18:35,834|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-17 20:18:35,839|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-17 20:18:35,841|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-17 20:18:35,847|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,848|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-17 20:18:35,848|azureml.core.authentication|DEBUG|Time to expire 1813938.151401 seconds\\n2019-12-17 20:18:35,848|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,848|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,848|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,849|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,885|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:35,890|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,898|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,909|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,916|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,921|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:35,921|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-17 20:18:35,922|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-17 20:18:35,922|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576613450_585d065c'\\n2019-12-17 20:18:35,922|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-17 20:18:35,922|msrest.http_logger|DEBUG|Request headers:\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '5bbe21c2-a44f-4982-b9b6-5be1bb03d946'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'request-id': '5bbe21c2-a44f-4982-b9b6-5be1bb03d946'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG|Request body:\\n2019-12-17 20:18:35,923|msrest.http_logger|DEBUG|None\\n2019-12-17 20:18:35,923|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-17 20:18:35,923|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-17 20:18:35,923|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-17 20:18:35,923|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-17 20:18:36,091|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG|Response headers:\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Date': 'Tue, 17 Dec 2019 20:18:36 GMT'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-17 20:18:36,092|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '5bbe21c2-a44f-4982-b9b6-5be1bb03d946'\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG|Response content:\\n2019-12-17 20:18:36,093|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 4768,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-17T20:10:54.4799573+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-17T20:16:11.2772574+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576613450_585d065c/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576613450_585d065c/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576613450_585d065c/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-17 20:18:36,099|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-17 20:18:36,099|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-17 20:18:36,099|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-17 20:18:36,100|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-17 20:18:36,100|azureml.WorkerPool|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-17 20:18:36,100|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576613450_585d065c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576613450_585d065c\\n2019-12-17 20:18:36,101|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-17 20:18:36,101|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576613450_585d065c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576613450_585d065c\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,076|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,077|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,077|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,077|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-17 20:18:38,083|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,084|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-17 20:18:38,089|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,094|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,100|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,105|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:18:38,105|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-17 20:18:38,106|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576613450_585d065c'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG|Request headers:\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '70d23c35-adf5-449f-8ef3-6bd03bda21e8'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'request-id': '70d23c35-adf5-449f-8ef3-6bd03bda21e8'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-17 20:18:38,106|msrest.http_logger|DEBUG|Request body:\\n2019-12-17 20:18:38,107|msrest.http_logger|DEBUG|None\\n2019-12-17 20:18:38,107|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-17 20:18:38,107|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-17 20:18:38,107|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-17 20:18:38,107|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-17 20:18:38,241|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-17 20:18:38,241|msrest.http_logger|DEBUG|Response headers:\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Date': 'Tue, 17 Dec 2019 20:18:38 GMT'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '70d23c35-adf5-449f-8ef3-6bd03bda21e8'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-17 20:18:38,242|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-17 20:18:38,243|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-17 20:18:38,243|msrest.http_logger|DEBUG|Response content:\\n2019-12-17 20:18:38,243|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 4768,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-17T20:10:54.4799573+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-17T20:16:11.2772574+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576613450_585d065c\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576613450_585d065c/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576613450_585d065c/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576613450_585d065c/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-17 20:18:38,245|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-17 20:18:38,245|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-17 20:18:38,245|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-17 20:19:05,842|azureml.core.authentication|DEBUG|Time to expire 1813908.157661 seconds\\n2019-12-17 20:19:35,842|azureml.core.authentication|DEBUG|Time to expire 1813878.157334 seconds\\n2019-12-17 20:19:57,499|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-17 20:19:57,499|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-17 20:19:57,500|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576613450_585d065c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576613450_585d065c\\n2019-12-17 20:19:57,567|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576613450_585d065c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576613450_585d065c\\n2019-12-17 20:19:57,568|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-17 20:19:57,568|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c|INFO|complete is not setting status for submitted runs.\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-17 20:19:57,568|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,569|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-17 20:19:57,569|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-17 20:19:57,570|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,570|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,570|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,570|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-17 20:19:57,570|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-17 20:19:57,570|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-17 20:19:57,570|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-17 20:19:57,570|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-17 20:19:57,571|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-17 20:19:57,571|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-17 20:19:57,571|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-17 20:19:57,571|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-17 20:19:57,571|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-17 20:19:57,572|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-17 20:19:57,573|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-17 20:19:57,573|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-17 20:19:57,573|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-17 20:19:57,573|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576613450_585d065c/batch/metrics'\\n2019-12-17 20:19:57,573|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG|Request headers:\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-17 20:19:57,574|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'e3f652cb-49f5-4b75-a293-ced63c71b52e'\\n2019-12-17 20:19:57,574|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG| 'request-id': 'e3f652cb-49f5-4b75-a293-ced63c71b52e'\\n2019-12-17 20:19:57,575|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-17 20:19:57,575|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79 sdk_run'\\n2019-12-17 20:19:57,575|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG|Request body:\\n2019-12-17 20:19:57,575|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"63af7c5d-57b4-4ea4-aff6-bc0f503d9be8\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-17T20:19:57.499216Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-17 20:19:57,576|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-17 20:19:57,576|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-17 20:19:57,576|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-17 20:19:57,576|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-17 20:19:57,765|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-17 20:19:57,765|msrest.http_logger|DEBUG|Response headers:\\n2019-12-17 20:19:57,765|msrest.http_logger|DEBUG| 'Date': 'Tue, 17 Dec 2019 20:19:57 GMT'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'e3f652cb-49f5-4b75-a293-ced63c71b52e'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG|Response content:\\n2019-12-17 20:19:57,766|msrest.http_logger|DEBUG|\\n2019-12-17 20:19:57,767|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00021457672119140625 seconds.\\n\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml._SubmittedRun#020_AzureMLEstimator_1576613450_585d065c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-17 20:19:57,826|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-17 20:19:57,826|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-17 20:19:57,827|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1578104365_7f98f029?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1578104365_7f98f029\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1578104365_7f98f029\", \"created_utc\": \"2020-01-04T02:19:29.146046Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\", \"azureml.git.branch\": \"ghiordan/azureml_devito04\", \"mlflow.source.git.branch\": \"ghiordan/azureml_devito04\", \"azureml.git.commit\": \"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\", \"mlflow.source.git.commit\": \"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2020-01-04T02:28:26.519321Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_81163cbc97f5ab08ce9248634652cf612a0c3b341dc76c61636cd017f913afb2_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578104365_7f98f029/azureml-logs/55_azureml-execution-tvmps_81163cbc97f5ab08ce9248634652cf612a0c3b341dc76c61636cd017f913afb2_d.txt?sv=2019-02-02&sr=b&sig=gs3WJ%2BZC5QV8V%2BdMT1zXbfYW0CHBUb%2FAS7HzsNABmTI%3D&st=2020-01-04T02%3A18%3A37Z&se=2020-01-04T10%3A28%3A37Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_81163cbc97f5ab08ce9248634652cf612a0c3b341dc76c61636cd017f913afb2_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578104365_7f98f029/azureml-logs/65_job_prep-tvmps_81163cbc97f5ab08ce9248634652cf612a0c3b341dc76c61636cd017f913afb2_d.txt?sv=2019-02-02&sr=b&sig=O1S10hFUaUhxPJXpUxGbq%2BHkCnDNQlzhIGsLA8oVAuw%3D&st=2020-01-04T02%3A18%3A37Z&se=2020-01-04T10%3A28%3A37Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578104365_7f98f029/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=Llu%2BGoIPioRU9AnUPEbBXr2BZuIY%2BzQ7wbDZb%2BfwP8k%3D&st=2020-01-04T02%3A18%3A37Z&se=2020-01-04T10%3A28%3A37Z&sp=r\", \"azureml-logs/75_job_post-tvmps_81163cbc97f5ab08ce9248634652cf612a0c3b341dc76c61636cd017f913afb2_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578104365_7f98f029/azureml-logs/75_job_post-tvmps_81163cbc97f5ab08ce9248634652cf612a0c3b341dc76c61636cd017f913afb2_d.txt?sv=2019-02-02&sr=b&sig=MPmJi0vLgA869NOFWCR7okSEO4t9uTtrQ4kxelUV0yA%3D&st=2020-01-04T02%3A18%3A37Z&se=2020-01-04T10%3A28%3A37Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578104365_7f98f029/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=nv%2Fd5rAAqJsP8uo8HcDag25bVwIw5aeST16VPRBo8vk%3D&st=2020-01-04T02%3A18%3A37Z&se=2020-01-04T10%3A28%3A37Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578104365_7f98f029/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=rWbAamDOk%2F9T5bwuSJmlvZ4QehlE31Fo2fZ5Rhbcau8%3D&st=2020-01-04T02%3A18%3A37Z&se=2020-01-04T10%3A28%3A37Z&sp=r\", \"logs/azureml/689_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578104365_7f98f029/logs/azureml/689_azureml.log?sv=2019-02-02&sr=b&sig=xQwoXQUPG1BYQWaF9sRCbk%2Bm3%2FasZOTLyCWfGIOTPhw%3D&st=2020-01-04T02%3A18%3A37Z&se=2020-01-04T10%3A28%3A37Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578104365_7f98f029/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=jZcSWDRZeWwKlQ82%2FYIYVQo6yerPGKV56HLnHF4cbPE%3D&st=2020-01-04T02%3A18%3A37Z&se=2020-01-04T10%3A28%3A37Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_81163cbc97f5ab08ce9248634652cf612a0c3b341dc76c61636cd017f913afb2_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_81163cbc97f5ab08ce9248634652cf612a0c3b341dc76c61636cd017f913afb2_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_81163cbc97f5ab08ce9248634652cf612a0c3b341dc76c61636cd017f913afb2_d.txt\"], [\"logs/azureml/689_azureml.log\"]], \"run_duration\": \"0:08:57\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1578104365_7f98f029\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2020-01-04 02:26:41,710|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2020-01-04 02:26:41,711|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2020-01-04 02:26:41,711|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2020-01-04 02:26:41,711|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2020-01-04 02:26:42,039|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2020-01-04 02:26:42,040|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2020-01-04 02:26:42,431|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2020-01-04 02:26:42,431|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2020-01-04 02:26:42,431|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2020-01-04 02:26:42,431|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2020-01-04 02:26:42,431|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2020-01-04 02:26:43,032|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2020-01-04 02:26:43,037|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2020-01-04 02:26:43,043|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2020-01-04 02:26:43,048|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2020-01-04 02:26:43,050|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2020-01-04 02:26:43,056|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:43,057|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2020-01-04 02:26:43,057|azureml.core.authentication|DEBUG|Time to expire 1813965.942446 seconds\\n2020-01-04 02:26:43,057|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,057|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,057|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,058|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,058|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,058|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,058|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,058|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,058|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,094|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:43,099|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:43,107|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:43,112|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:43,118|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:43,123|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:43,124|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2020-01-04 02:26:43,124|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2020-01-04 02:26:43,124|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1578104365_7f98f029'\\n2020-01-04 02:26:43,125|msrest.http_logger|DEBUG|Request method: 'GET'\\n2020-01-04 02:26:43,125|msrest.http_logger|DEBUG|Request headers:\\n2020-01-04 02:26:43,125|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2020-01-04 02:26:43,125|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2020-01-04 02:26:43,125|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '27689e15-3f4c-44fb-a317-1ce64e869860'\\n2020-01-04 02:26:43,126|msrest.http_logger|DEBUG| 'request-id': '27689e15-3f4c-44fb-a317-1ce64e869860'\\n2020-01-04 02:26:43,126|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.81'\\n2020-01-04 02:26:43,126|msrest.http_logger|DEBUG|Request body:\\n2020-01-04 02:26:43,126|msrest.http_logger|DEBUG|None\\n2020-01-04 02:26:43,126|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2020-01-04 02:26:43,126|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2020-01-04 02:26:43,126|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2020-01-04 02:26:43,126|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2020-01-04 02:26:43,182|msrest.http_logger|DEBUG|Response status: 200\\n2020-01-04 02:26:43,182|msrest.http_logger|DEBUG|Response headers:\\n2020-01-04 02:26:43,182|msrest.http_logger|DEBUG| 'Date': 'Sat, 04 Jan 2020 02:26:43 GMT'\\n2020-01-04 02:26:43,182|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2020-01-04 02:26:43,182|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2020-01-04 02:26:43,183|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2020-01-04 02:26:43,183|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2020-01-04 02:26:43,183|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2020-01-04 02:26:43,183|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '27689e15-3f4c-44fb-a317-1ce64e869860'\\n2020-01-04 02:26:43,183|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2020-01-04 02:26:43,183|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2020-01-04 02:26:43,183|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2020-01-04 02:26:43,183|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2020-01-04 02:26:43,183|msrest.http_logger|DEBUG|Response content:\\n2020-01-04 02:26:43,184|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 6733,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1578104365_7f98f029\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2020-01-04T02:19:29.1460463+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1578104365_7f98f029\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2020-01-04T02:24:16.7785992+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1578104365_7f98f029\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito04\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito04\\\",\\n \\\"azureml.git.commit\\\": \\\"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1578104365_7f98f029/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1578104365_7f98f029/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1578104365_7f98f029/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2020-01-04 02:26:43,189|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2020-01-04 02:26:43,190|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/seismic-deeplearning.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/seismic-deeplearning.git', 'azureml.git.branch': 'ghiordan/azureml_devito04', 'mlflow.source.git.branch': 'ghiordan/azureml_devito04', 'azureml.git.commit': 'b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb', 'mlflow.source.git.commit': 'b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2020-01-04 02:26:43,190|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2020-01-04 02:26:43,190|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2020-01-04 02:26:43,191|azureml.WorkerPool|DEBUG|[START]\\n2020-01-04 02:26:43,191|azureml.SendRunKillSignal|DEBUG|[START]\\n2020-01-04 02:26:43,191|azureml.RunStatusContext|DEBUG|[START]\\n2020-01-04 02:26:43,191|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunContextManager.RunStatusContext|DEBUG|[START]\\n2020-01-04 02:26:43,191|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2020-01-04 02:26:43,191|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2020-01-04 02:26:43,191|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1578104365_7f98f029/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1578104365_7f98f029\\n2020-01-04 02:26:43,191|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2020-01-04 02:26:43,191|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1578104365_7f98f029/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1578104365_7f98f029\\n2020-01-04 02:26:45,676|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:45,676|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:45,676|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:45,676|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:45,676|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:45,677|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:45,677|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:45,677|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:45,677|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 02:26:45,684|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:45,685|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2020-01-04 02:26:45,690|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:45,695|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:45,701|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:45,706|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:26:45,707|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2020-01-04 02:26:45,707|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2020-01-04 02:26:45,707|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1578104365_7f98f029'\\n2020-01-04 02:26:45,707|msrest.http_logger|DEBUG|Request method: 'GET'\\n2020-01-04 02:26:45,707|msrest.http_logger|DEBUG|Request headers:\\n2020-01-04 02:26:45,707|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2020-01-04 02:26:45,708|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2020-01-04 02:26:45,708|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'de302e01-309f-47c2-99d4-7d72185c98a5'\\n2020-01-04 02:26:45,708|msrest.http_logger|DEBUG| 'request-id': 'de302e01-309f-47c2-99d4-7d72185c98a5'\\n2020-01-04 02:26:45,708|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.81'\\n2020-01-04 02:26:45,708|msrest.http_logger|DEBUG|Request body:\\n2020-01-04 02:26:45,708|msrest.http_logger|DEBUG|None\\n2020-01-04 02:26:45,708|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2020-01-04 02:26:45,708|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2020-01-04 02:26:45,708|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2020-01-04 02:26:45,708|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG|Response status: 200\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG|Response headers:\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG| 'Date': 'Sat, 04 Jan 2020 02:26:45 GMT'\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'de302e01-309f-47c2-99d4-7d72185c98a5'\\n2020-01-04 02:26:45,768|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2020-01-04 02:26:45,769|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2020-01-04 02:26:45,769|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2020-01-04 02:26:45,769|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2020-01-04 02:26:45,769|msrest.http_logger|DEBUG|Response content:\\n2020-01-04 02:26:45,769|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 6733,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1578104365_7f98f029\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2020-01-04T02:19:29.1460463+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 10,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1578104365_7f98f029\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2020-01-04T02:24:16.7785992+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1578104365_7f98f029\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito04\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito04\\\",\\n \\\"azureml.git.commit\\\": \\\"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi02\\\",\\n \\\"tags\\\": {\\n \\\"_aml_system_ComputeTargetStatus\\\": \\\"{\\\\\\\"AllocationState\\\\\\\":\\\\\\\"steady\\\\\\\",\\\\\\\"PreparingNodeCount\\\\\\\":1,\\\\\\\"RunningNodeCount\\\\\\\":0,\\\\\\\"CurrentNodeCount\\\\\\\":1}\\\"\\n },\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1578104365_7f98f029/020_UseAzureMLEstimatorForExperimentation_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1578104365_7f98f029/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1578104365_7f98f029/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2020-01-04 02:26:45,771|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2020-01-04 02:26:45,771|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/seismic-deeplearning.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/seismic-deeplearning.git', 'azureml.git.branch': 'ghiordan/azureml_devito04', 'mlflow.source.git.branch': 'ghiordan/azureml_devito04', 'azureml.git.commit': 'b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb', 'mlflow.source.git.commit': 'b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2020-01-04 02:26:45,772|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2020-01-04 02:27:13,055|azureml.core.authentication|DEBUG|Time to expire 1813935.945032 seconds\\n2020-01-04 02:27:43,055|azureml.core.authentication|DEBUG|Time to expire 1813905.944679 seconds\\n2020-01-04 02:28:03,352|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2020-01-04 02:28:03,353|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2020-01-04 02:28:03,353|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2020-01-04 02:28:03,431|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2020-01-04 02:28:03,431|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2020-01-04 02:28:03,431|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2020-01-04 02:28:03,431|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1578104365_7f98f029/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1578104365_7f98f029\\n2020-01-04 02:28:03,431|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1578104365_7f98f029/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1578104365_7f98f029\\n2020-01-04 02:28:03,431|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2020-01-04 02:28:03,431|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2020-01-04 02:28:03,431|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029|INFO|complete is not setting status for submitted runs.\\n2020-01-04 02:28:03,432|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2020-01-04 02:28:03,432|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2020-01-04 02:28:03,432|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2020-01-04 02:28:03,432|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2020-01-04 02:28:03,432|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2020-01-04 02:28:03,432|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2020-01-04 02:28:03,432|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2020-01-04 02:28:03,432|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2020-01-04 02:28:03,432|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2020-01-04 02:28:03,433|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2020-01-04 02:28:03,433|azureml.RunStatusContext|DEBUG|[STOP]\\n2020-01-04 02:28:03,433|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2020-01-04 02:28:03,433|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2020-01-04 02:28:03,433|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2020-01-04 02:28:03,433|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2020-01-04 02:28:03,433|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2020-01-04 02:28:03,433|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2020-01-04 02:28:03,433|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2020-01-04 02:28:03,433|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2020-01-04 02:28:03,434|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2020-01-04 02:28:03,434|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2020-01-04 02:28:03,434|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2020-01-04 02:28:03,434|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2020-01-04 02:28:03,434|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2020-01-04 02:28:03,434|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2020-01-04 02:28:03,435|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2020-01-04 02:28:03,435|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2020-01-04 02:28:03,436|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2020-01-04 02:28:03,436|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2020-01-04 02:28:03,437|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2020-01-04 02:28:03,437|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2020-01-04 02:28:03,437|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2020-01-04 02:28:03,438|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 02:28:03,438|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2020-01-04 02:28:03,438|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1578104365_7f98f029/batch/metrics'\\n2020-01-04 02:28:03,438|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2020-01-04 02:28:03,438|msrest.http_logger|DEBUG|Request method: 'POST'\\n2020-01-04 02:28:03,438|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2020-01-04 02:28:03,439|msrest.http_logger|DEBUG|Request headers:\\n2020-01-04 02:28:03,439|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2020-01-04 02:28:03,439|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2020-01-04 02:28:03,439|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2020-01-04 02:28:03,439|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2020-01-04 02:28:03,439|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2020-01-04 02:28:03,439|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '09e4c3b1-5347-4544-add3-1e02c72558a0'\\n2020-01-04 02:28:03,439|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2020-01-04 02:28:03,439|msrest.http_logger|DEBUG| 'request-id': '09e4c3b1-5347-4544-add3-1e02c72558a0'\\n2020-01-04 02:28:03,440|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2020-01-04 02:28:03,440|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2020-01-04 02:28:03,440|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2020-01-04 02:28:03,440|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.81 sdk_run'\\n2020-01-04 02:28:03,440|msrest.http_logger|DEBUG|Request body:\\n2020-01-04 02:28:03,440|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"acfd5695-72ca-449b-a3c8-3b7b72780aec\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2020-01-04T02:28:03.352477Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2020-01-04 02:28:03,440|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2020-01-04 02:28:03,440|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2020-01-04 02:28:03,440|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2020-01-04 02:28:03,441|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2020-01-04 02:28:03,582|msrest.http_logger|DEBUG|Response status: 200\\n2020-01-04 02:28:03,582|msrest.http_logger|DEBUG|Response headers:\\n2020-01-04 02:28:03,582|msrest.http_logger|DEBUG| 'Date': 'Sat, 04 Jan 2020 02:28:03 GMT'\\n2020-01-04 02:28:03,582|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2020-01-04 02:28:03,583|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2020-01-04 02:28:03,583|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2020-01-04 02:28:03,583|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '09e4c3b1-5347-4544-add3-1e02c72558a0'\\n2020-01-04 02:28:03,583|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2020-01-04 02:28:03,583|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2020-01-04 02:28:03,583|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2020-01-04 02:28:03,583|msrest.http_logger|DEBUG|Response content:\\n2020-01-04 02:28:03,583|msrest.http_logger|DEBUG|\\n2020-01-04 02:28:03,584|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2020-01-04 02:28:03,690|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2020-01-04 02:28:03,691|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2020-01-04 02:28:03,691|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2020-01-04 02:28:03,691|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00022459030151367188 seconds.\\n\\n2020-01-04 02:28:03,691|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2020-01-04 02:28:03,691|azureml._SubmittedRun#020_AzureMLEstimator_1578104365_7f98f029.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2020-01-04 02:28:03,691|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2020-01-04 02:28:03,691|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2020-01-04 02:28:03,691|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2020-01-04 02:28:03,691|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.0.81\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -935,7 +926,7 @@ { "data": { "text/plain": [ - "'runId= 020_AzureMLEstimator_1576613450_585d065c'" + "'runId= 020_AzureMLEstimator_1578104365_7f98f029'" ] }, "execution_count": 19, @@ -945,7 +936,7 @@ { "data": { "text/plain": [ - "'experimentation baseImage: fwi01_azureml:sdk.v1.0.79'" + "'experimentation baseImage: fwi01_azureml:sdk.v1.0.81'" ] }, "execution_count": 19, diff --git a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb index 106bde7b..fa055491 100755 --- a/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb +++ b/contrib/fwi/azureml_devito/notebooks/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb @@ -75,13 +75,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "Azure ML SDK Version: 1.0.76\n" + "Azure ML SDK Version: 1.0.81\n" ] }, { "data": { "text/plain": [ - "'Linux-4.15.0-1064-azure-x86_64-with-debian-stretch-sid'" + "'Linux-4.15.0-1064-azure-x86_64-with-debian-10.1'" ] }, "execution_count": 3, @@ -91,7 +91,7 @@ { "data": { "text/plain": [ - "'/datadrive01/prj/DeepSeismic/contrib/fwi/azureml_devito/notebooks'" + "'/workspace/contrib/fwi/azureml_devito/notebooks'" ] }, "execution_count": 3, @@ -481,7 +481,7 @@ { "data": { "text/plain": [ - "'fwi01_azureml:sdk.v1.0.79'" + "'fwi01_azureml:sdk.v1.0.81'" ] }, "execution_count": 9, @@ -491,7 +491,7 @@ { "data": { "text/plain": [ - "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79'" + "'fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81'" ] }, "execution_count": 9, @@ -529,7 +529,7 @@ { "data": { "text/plain": [ - "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.79 /bin/bash -c \"which python\" '" + "'docker run -i --rm --name fwi01_azureml_container02 fwi01acr.azurecr.io/fwi01_azureml:sdk.v1.0.81 /bin/bash -c \"which python\" '" ] }, "execution_count": 10, @@ -700,15 +700,6 @@ "execution_count": 13, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING - Warning: Falling back to use azure cli login credentials.\n", - "If you run your code in unattended mode, i.e., where you can't give a user input, then we recommend to use ServicePrincipalAuthentication or MsiAuthentication.\n", - "Please refer to aka.ms/aml-notebook-auth for different authentication mechanisms in azureml-sdk.\n" - ] - }, { "name": "stdout", "output_type": "stream", @@ -835,12 +826,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "521e64911a6d4083888e995ff79da564", + "model_id": "40e874e0889e460086a809fdb5a0b03b", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'NOTSET',…" + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" ] }, "metadata": {}, @@ -848,7 +839,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576639227_6528b852?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1576639227_6528b852\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1576639227_6528b852\", \"created_utc\": \"2019-12-18T03:20:30.235502Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"ghiordan/azureml_devito03\", \"mlflow.source.git.branch\": \"ghiordan/azureml_devito03\", \"azureml.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"mlflow.source.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-18T03:22:33.515195Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=wEJg5h%2FN%2BCt%2BavdQxsl%2BlSnQqqxmd8500luwM1gktnk%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=cMfZVjgZuTxt3xBtMpsCQeIJ%2BVQmH%2FdalkHgyged5ow%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=O59A3Pe3NfBZ%2Bm7z3Z4WB69ZE60K3qlNPb%2BCO6NV7Dw%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=qj3UaXzOtJWiFXaT%2FYy%2BrBj3X14pYuekf8cZDbrQiHY%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=eImLU0mmrY4KANoEJL%2FXEFaOm%2Fp5%2F8StuxJdfr2URjY%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=t9NdOE5AOFhCK%2BqSNfJs51l2Zn3Z9Mx8vYDaiR0wNUU%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"logs/azureml/688_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/logs/azureml/688_azureml.log?sv=2019-02-02&sr=b&sig=ju9O1BPKJ16IKUYN4fEHv%2BHBTTdruFSD%2FRyKBT1zbPI%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576639227_6528b852/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=HV5hFHBamtTh7MaJ%2Fcq56Hssz6QPNlbXvrjz1T1KOTA%3D&st=2019-12-18T03%3A12%3A38Z&se=2019-12-18T11%3A22%3A38Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"logs/azureml/688_azureml.log\"]], \"run_duration\": \"0:02:03\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1576639227_6528b852\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-18 03:20:52,190|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-18 03:20:52,190|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-18 03:20:52,191|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-18 03:20:52,191|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-18 03:20:52,458|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-18 03:20:52,459|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-18 03:20:52,795|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-18 03:20:52,796|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-18 03:20:52,796|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-18 03:20:52,796|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-18 03:20:52,796|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-18 03:20:53,320|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-18 03:20:53,324|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-18 03:20:53,328|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-18 03:20:53,332|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-18 03:20:53,333|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:20:53,338|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,339|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-18 03:20:53,339|azureml.core.authentication|DEBUG|Time to expire 1814376.660805 seconds\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,339|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,340|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,368|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:53,372|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,378|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,382|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,387|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,391|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:53,391|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-18 03:20:53,392|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:20:53,392|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576639227_6528b852'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '9ba8101b-982f-4288-b938-521153e4955e'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'request-id': '9ba8101b-982f-4288-b938-521153e4955e'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:20:53,393|msrest.http_logger|DEBUG|None\\n2019-12-18 03:20:53,393|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:20:53,393|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:20:53,393|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:20:53,393|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:20:53 GMT'\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-18 03:20:53,555|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '9ba8101b-982f-4288-b938-521153e4955e'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:20:53,556|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 5540,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-18T03:20:30.2355028+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-18T03:20:43.031023+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576639227_6528b852/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576639227_6528b852/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576639227_6528b852/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-18 03:20:53,561|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-18 03:20:53,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-18 03:20:53,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-18 03:20:53,562|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-18 03:20:53,563|azureml.WorkerPool|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-18 03:20:53,563|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576639227_6528b852/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576639227_6528b852\\n2019-12-18 03:20:53,563|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-18 03:20:53,563|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576639227_6528b852/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576639227_6528b852\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,880|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,881|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,881|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,881|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:20:55,886|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,887|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-18 03:20:55,892|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,897|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,901|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,906|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:20:55,906|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-18 03:20:55,906|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576639227_6528b852'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b5b744e6-b6a5-4eb8-b360-e0f471c112b4'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'request-id': 'b5b744e6-b6a5-4eb8-b360-e0f471c112b4'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:20:55,907|msrest.http_logger|DEBUG|None\\n2019-12-18 03:20:55,907|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:20:55,907|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:20:55,907|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:20:55,908|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:20:55 GMT'\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-18 03:20:55,964|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'b5b744e6-b6a5-4eb8-b360-e0f471c112b4'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:20:55,965|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 5540,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-18T03:20:30.2355028+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-18T03:20:43.031023+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576639227_6528b852\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576639227_6528b852/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576639227_6528b852/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576639227_6528b852/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-18 03:20:55,968|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-18 03:20:55,968|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-18 03:20:55,968|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-18 03:21:23,334|azureml.core.authentication|DEBUG|Time to expire 1814346.666025 seconds\\n2019-12-18 03:21:53,334|azureml.core.authentication|DEBUG|Time to expire 1814316.665578 seconds\\n2019-12-18 03:22:06,840|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-18 03:22:06,840|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:22:06,841|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576639227_6528b852/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576639227_6528b852\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576639227_6528b852/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576639227_6528b852\\n2019-12-18 03:22:07,052|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-18 03:22:07,053|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-18 03:22:07,053|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852|INFO|complete is not setting status for submitted runs.\\n2019-12-18 03:22:07,053|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,053|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-18 03:22:07,053|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,054|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,054|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,055|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,055|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-18 03:22:07,055|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-18 03:22:07,056|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-18 03:22:07,056|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-18 03:22:07,056|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-18 03:22:07,056|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-18 03:22:07,057|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-18 03:22:07,057|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-18 03:22:07,057|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-18 03:22:07,058|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:22:07,058|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-18 03:22:07,058|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-18 03:22:07,059|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:22:07,059|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-18 03:22:07,059|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576639227_6528b852/batch/metrics'\\n2019-12-18 03:22:07,059|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-18 03:22:07,059|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-18 03:22:07,059|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:22:07,060|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:22:07,060|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-18 03:22:07,060|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'f3fe67d8-6f74-4c18-8a93-845b18aca364'\\n2019-12-18 03:22:07,060|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-18 03:22:07,060|msrest.http_logger|DEBUG| 'request-id': 'f3fe67d8-6f74-4c18-8a93-845b18aca364'\\n2019-12-18 03:22:07,060|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:22:07,061|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-18 03:22:07,061|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:22:07,061|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79 sdk_run'\\n2019-12-18 03:22:07,061|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-18 03:22:07,061|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:22:07,061|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"85c6d826-5d8d-4ec9-9ac2-20ebbc1af5a3\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-18T03:22:06.840237Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-18 03:22:07,061|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:22:07,061|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:22:07,061|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:22:07,062|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:22:07,321|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:22:07,321|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:22:07 GMT'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'f3fe67d8-6f74-4c18-8a93-845b18aca364'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:22:07,322|msrest.http_logger|DEBUG|\\n2019-12-18 03:22:07,323|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-18 03:22:07,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-18 03:22:07,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-18 03:22:07,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-18 03:22:07,562|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.00022602081298828125 seconds.\\nWaiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.2506241798400879 seconds.\\n\\n2019-12-18 03:22:07,563|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,563|azureml._SubmittedRun#020_AzureMLEstimator_1576639227_6528b852.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:22:07,563|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-18 03:22:07,563|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-18 03:22:07,563|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-18 03:22:07,563|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Running\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1578105537_18907742?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1578105537_18907742\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1578105537_18907742\", \"created_utc\": \"2020-01-04T02:39:01.163504Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\", \"azureml.git.branch\": \"ghiordan/azureml_devito04\", \"mlflow.source.git.branch\": \"ghiordan/azureml_devito04\", \"azureml.git.commit\": \"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\", \"mlflow.source.git.commit\": \"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {\"_aml_system_ComputeTargetStatus\": \"{\\\"AllocationState\\\":\\\"steady\\\",\\\"PreparingNodeCount\\\":1,\\\"RunningNodeCount\\\":0,\\\"CurrentNodeCount\\\":1}\"}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": null, \"status\": \"Running\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_37354120d8a20f3b502269d3e8b5c071826d11429ecdbd884f819e97a5e689a3_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578105537_18907742/azureml-logs/55_azureml-execution-tvmps_37354120d8a20f3b502269d3e8b5c071826d11429ecdbd884f819e97a5e689a3_d.txt?sv=2019-02-02&sr=b&sig=kELMOp%2B45Wj9tH6EYGAZYb7EHmMEShXIeJCJ3LSb7fs%3D&st=2020-01-04T02%3A34%3A08Z&se=2020-01-04T10%3A44%3A08Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/55_azureml-execution-tvmps_37354120d8a20f3b502269d3e8b5c071826d11429ecdbd884f819e97a5e689a3_d.txt\"]], \"run_duration\": \"0:05:07\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [], \"run_logs\": \"2020-01-04T02:42:01Z Starting output-watcher...\\nLogin Succeeded\\nsdk.v1.0.81: Pulling from fwi01_azureml\\nb8f262c62ec6: Pulling fs layer\\n0a43c0154f16: Pulling fs layer\\n906d7b5da8fb: Pulling fs layer\\n265baab7d98a: Pulling fs layer\\n300780db9fa5: Pulling fs layer\\n6df08465f871: Pulling fs layer\\nb074f450fcba: Pulling fs layer\\n6df08465f871: Waiting\\nb074f450fcba: Waiting\\n300780db9fa5: Waiting\\n265baab7d98a: Waiting\\n0a43c0154f16: Verifying Checksum\\n0a43c0154f16: Download complete\\n906d7b5da8fb: Verifying Checksum\\n906d7b5da8fb: Download complete\\n300780db9fa5: Download complete\\nb8f262c62ec6: Download complete\\n265baab7d98a: Verifying Checksum\\n265baab7d98a: Download complete\\nb8f262c62ec6: Pull complete\\nb074f450fcba: Verifying Checksum\\nb074f450fcba: Download complete\\n6df08465f871: Verifying Checksum\\n6df08465f871: Download complete\\n0a43c0154f16: Pull complete\\n906d7b5da8fb: Pull complete\\n265baab7d98a: Pull complete\\n300780db9fa5: Pull complete\\n6df08465f871: Pull complete\\n\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.0.81\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -921,7 +912,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 10, time 21.283 seconds: Counter({'Completed': 1})\r" + "Final print 10, time 21.403 seconds: Counter({'Completed': 1})\r" ] } ], @@ -954,8 +945,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "run_duration in seconds 110.484172\n", - "run_duration= 1m 50.484s\n" + "run_duration in seconds 252.429646\n", + "run_duration= 4m 12.430s\n" ] } ], @@ -988,12 +979,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6ecf3c99f6e54422b7892e0dfeddca35", + "model_id": "846e641584ed4d83b15afd06e51173cb", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'NOTSET',…" + "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" ] }, "metadata": {}, @@ -1001,7 +992,7 @@ }, { "data": { - "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576641205_aa71d72d?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1576641205_aa71d72d\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1576641205_aa71d72d\", \"created_utc\": \"2019-12-18T03:53:27.157172Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/DeepSeismic.git\", \"azureml.git.branch\": \"ghiordan/azureml_devito03\", \"mlflow.source.git.branch\": \"ghiordan/azureml_devito03\", \"azureml.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"mlflow.source.git.commit\": \"335e90f9a770fb28b62e509c433421a92cbb8934\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2019-12-18T03:55:25.640243Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=SVn%2BtGC1ko6RF9S2V%2BcPrQljGBljydlDSTlRwgiY2Ng%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=9tUhIp88GxWlrx8Z%2FpNmYnxeNXF8wzQW1I3XK5U9Lpc%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=ZuZAONbZA0%2FBHCGTly%2FMT8l75Epo%2FziQ0d5cmbfh95I%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt?sv=2019-02-02&sr=b&sig=C7odtYllisuM4%2Fy4amWPUU%2F9P0t3yXb9pyzJiVKJi7o%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=YMq8lCKloWiglWxGWxgTR8yuMU6AftDEUvE71wr%2FqJQ%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=W4UKWaHyWa1uboGDv8fxcacMOku6iU%2BzkbjcgTLYUVk%3D&st=2019-12-18T03%3A45%3A29Z&se=2019-12-18T11%3A55%3A29Z&sp=r\", \"logs/azureml/688_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/logs/azureml/688_azureml.log?sv=2019-02-02&sr=b&sig=FXEhXZv%2Bm0UHbZQ2k7PYFWQEwZmND8rbqkdIwMFKD2I%3D&st=2019-12-18T03%3A45%3A28Z&se=2019-12-18T11%3A55%3A28Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1576641205_aa71d72d/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=OKGeQJTWen%2FksWvC13BBMsLC%2Ffq%2BGMpRa6D3azdXWAU%3D&st=2019-12-18T03%3A45%3A28Z&se=2019-12-18T11%3A55%3A28Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_fb8a31a02be2925bd1ff1e4e593f18326131dd07931d8b07ccb10c64b890038b_d.txt\"], [\"logs/azureml/688_azureml.log\"]], \"run_duration\": \"0:01:58\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1576641205_aa71d72d\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2019-12-18 03:53:49,953|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2019-12-18 03:53:49,953|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2019-12-18 03:53:49,954|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2019-12-18 03:53:49,954|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2019-12-18 03:53:50,231|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2019-12-18 03:53:50,232|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2019-12-18 03:53:50,560|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-18 03:53:50,560|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-18 03:53:50,560|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2019-12-18 03:53:50,560|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2019-12-18 03:53:50,560|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2019-12-18 03:53:51,076|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2019-12-18 03:53:51,080|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2019-12-18 03:53:51,084|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2019-12-18 03:53:51,088|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2019-12-18 03:53:51,089|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:53:51,094|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,095|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2019-12-18 03:53:51,095|azureml.core.authentication|DEBUG|Time to expire 1814375.90442 seconds\\n2019-12-18 03:53:51,095|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,095|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,095|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,095|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,096|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,125|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:51,129|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,135|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,139|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,144|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,148|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:51,148|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-18 03:53:51,149|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:53:51,149|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576641205_aa71d72d'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0584c9c0-de25-4df5-a0b6-133c534975d3'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'request-id': '0584c9c0-de25-4df5-a0b6-133c534975d3'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:53:51,150|msrest.http_logger|DEBUG|None\\n2019-12-18 03:53:51,150|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:53:51,151|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:53:51,151|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:53:51,151|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:53:51,211|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:53:51 GMT'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '0584c9c0-de25-4df5-a0b6-133c534975d3'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:53:51,212|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-18 03:53:51,213|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:53:51,213|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 5721,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-18T03:53:27.1571726+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-18T03:53:38.4738149+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576641205_aa71d72d/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576641205_aa71d72d/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576641205_aa71d72d/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-18 03:53:51,218|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-18 03:53:51,218|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-18 03:53:51,218|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-18 03:53:51,219|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2019-12-18 03:53:51,219|azureml.WorkerPool|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.SendRunKillSignal|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.RunStatusContext|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunContextManager.RunStatusContext|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2019-12-18 03:53:51,219|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576641205_aa71d72d/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576641205_aa71d72d\\n2019-12-18 03:53:51,219|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-18 03:53:51,219|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576641205_aa71d72d/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576641205_aa71d72d\\n2019-12-18 03:53:53,104|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,104|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,104|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,104|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,105|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2019-12-18 03:53:53,110|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,111|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2019-12-18 03:53:53,115|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,120|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,124|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,128|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:53:53,129|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2019-12-18 03:53:53,129|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576641205_aa71d72d'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG|Request method: 'GET'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '5eef3ce8-6e03-46f3-8eb4-7e7beb90d699'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'request-id': '5eef3ce8-6e03-46f3-8eb4-7e7beb90d699'\\n2019-12-18 03:53:53,129|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79'\\n2019-12-18 03:53:53,130|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:53:53,130|msrest.http_logger|DEBUG|None\\n2019-12-18 03:53:53,130|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:53:53,130|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:53:53,130|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:53:53,130|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:53:53,207|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:53:53 GMT'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '5eef3ce8-6e03-46f3-8eb4-7e7beb90d699'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:53:53,208|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:53:53,209|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2019-12-18 03:53:53,209|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:53:53,209|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 5721,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2019-12-18T03:53:27.1571726+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2019-12-18T03:53:38.4738149+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1576641205_aa71d72d\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/DeepSeismic.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito03\\\",\\n \\\"azureml.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"335e90f9a770fb28b62e509c433421a92cbb8934\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1576641205_aa71d72d/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576641205_aa71d72d/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1576641205_aa71d72d/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2019-12-18 03:53:53,211|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2019-12-18 03:53:53,211|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/DeepSeismic.git', 'azureml.git.branch': 'ghiordan/azureml_devito03', 'mlflow.source.git.branch': 'ghiordan/azureml_devito03', 'azureml.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'mlflow.source.git.commit': '335e90f9a770fb28b62e509c433421a92cbb8934', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2019-12-18 03:53:53,212|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2019-12-18 03:54:21,090|azureml.core.authentication|DEBUG|Time to expire 1814345.909671 seconds\\n2019-12-18 03:54:51,091|azureml.core.authentication|DEBUG|Time to expire 1814315.908961 seconds\\n2019-12-18 03:55:03,497|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-18 03:55:03,497|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:55:03,497|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576641205_aa71d72d/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576641205_aa71d72d\\n2019-12-18 03:55:03,630|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1576641205_aa71d72d/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1576641205_aa71d72d\\n2019-12-18 03:55:03,631|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2019-12-18 03:55:03,631|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2019-12-18 03:55:03,631|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d|INFO|complete is not setting status for submitted runs.\\n2019-12-18 03:55:03,631|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,631|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2019-12-18 03:55:03,631|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,632|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,632|azureml.RunStatusContext|DEBUG|[STOP]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,633|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,633|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2019-12-18 03:55:03,634|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2019-12-18 03:55:03,634|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2019-12-18 03:55:03,634|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-18 03:55:03,634|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2019-12-18 03:55:03,634|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2019-12-18 03:55:03,635|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2019-12-18 03:55:03,635|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2019-12-18 03:55:03,635|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2019-12-18 03:55:03,635|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2019-12-18 03:55:03,636|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2019-12-18 03:55:03,636|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2019-12-18 03:55:03,637|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2019-12-18 03:55:03,637|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2019-12-18 03:55:03,637|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1576641205_aa71d72d/batch/metrics'\\n2019-12-18 03:55:03,637|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2019-12-18 03:55:03,637|msrest.http_logger|DEBUG|Request method: 'POST'\\n2019-12-18 03:55:03,637|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG|Request headers:\\n2019-12-18 03:55:03,638|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2019-12-18 03:55:03,638|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2019-12-18 03:55:03,638|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'c3ed59b5-cecd-48f2-9640-db3eb82cfbc4'\\n2019-12-18 03:55:03,638|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2019-12-18 03:55:03,638|msrest.http_logger|DEBUG| 'request-id': 'c3ed59b5-cecd-48f2-9640-db3eb82cfbc4'\\n2019-12-18 03:55:03,639|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2019-12-18 03:55:03,639|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2019-12-18 03:55:03,639|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2019-12-18 03:55:03,639|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.79 sdk_run'\\n2019-12-18 03:55:03,639|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2019-12-18 03:55:03,639|msrest.http_logger|DEBUG|Request body:\\n2019-12-18 03:55:03,639|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"91fbac36-3af1-45bd-b8e1-cd5376d16624\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2019-12-18T03:55:03.496961Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2019-12-18 03:55:03,639|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2019-12-18 03:55:03,640|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2019-12-18 03:55:03,640|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2019-12-18 03:55:03,640|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2019-12-18 03:55:03,785|msrest.http_logger|DEBUG|Response status: 200\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG|Response headers:\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Date': 'Wed, 18 Dec 2019 03:55:03 GMT'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'x-ms-client-request-id': 'c3ed59b5-cecd-48f2-9640-db3eb82cfbc4'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG|Response content:\\n2019-12-18 03:55:03,786|msrest.http_logger|DEBUG|\\n2019-12-18 03:55:03,788|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002357959747314453 seconds.\\n\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml._SubmittedRun#020_AzureMLEstimator_1576641205_aa71d72d.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2019-12-18 03:55:03,890|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2019-12-18 03:55:03,890|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"NOTSET\", \"sdk_version\": \"1.0.76\"}, \"loading\": false}" + "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1578107635_6a48bf8c?wsid=/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourcegroups/ghiordanfwirsg01/workspaces/ghiordanfwiws\", \"run_id\": \"020_AzureMLEstimator_1578107635_6a48bf8c\", \"run_properties\": {\"run_id\": \"020_AzureMLEstimator_1578107635_6a48bf8c\", \"created_utc\": \"2020-01-04T03:13:57.423637Z\", \"properties\": {\"_azureml.ComputeTargetType\": \"amlcompute\", \"ContentSnapshotId\": \"a5071b2a-37a7-40da-8340-69cc894091cb\", \"azureml.git.repository_uri\": \"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\", \"mlflow.source.git.repoURL\": \"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\", \"azureml.git.branch\": \"ghiordan/azureml_devito04\", \"mlflow.source.git.branch\": \"ghiordan/azureml_devito04\", \"azureml.git.commit\": \"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\", \"mlflow.source.git.commit\": \"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\", \"azureml.git.dirty\": \"True\", \"ProcessInfoFile\": \"azureml-logs/process_info.json\", \"ProcessStatusFile\": \"azureml-logs/process_status.json\"}, \"tags\": {}, \"script_name\": null, \"arguments\": null, \"end_time_utc\": \"2020-01-04T03:15:49.565041Z\", \"status\": \"Completed\", \"log_files\": {\"azureml-logs/55_azureml-execution-tvmps_bb49cbe82626e1162dc32b5dd0516d96fa46f67a2765150b71dc08d139a6770d_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578107635_6a48bf8c/azureml-logs/55_azureml-execution-tvmps_bb49cbe82626e1162dc32b5dd0516d96fa46f67a2765150b71dc08d139a6770d_d.txt?sv=2019-02-02&sr=b&sig=OZ%2BtuSv7JS9GjYmN7pkI3Uys4EFn3L%2B7x6Z5ZhPCBvk%3D&st=2020-01-04T03%3A05%3A57Z&se=2020-01-04T11%3A15%3A57Z&sp=r\", \"azureml-logs/65_job_prep-tvmps_bb49cbe82626e1162dc32b5dd0516d96fa46f67a2765150b71dc08d139a6770d_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578107635_6a48bf8c/azureml-logs/65_job_prep-tvmps_bb49cbe82626e1162dc32b5dd0516d96fa46f67a2765150b71dc08d139a6770d_d.txt?sv=2019-02-02&sr=b&sig=Qpd%2Fo0g2YYl2z7AyTsnO0qf3hVlrReqtSwa1kiC3Xpo%3D&st=2020-01-04T03%3A05%3A57Z&se=2020-01-04T11%3A15%3A57Z&sp=r\", \"azureml-logs/70_driver_log.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578107635_6a48bf8c/azureml-logs/70_driver_log.txt?sv=2019-02-02&sr=b&sig=m0m%2FtKBFCFwd3snV6dwxoJxXFt5gum70hFx%2FYXhTBxg%3D&st=2020-01-04T03%3A05%3A57Z&se=2020-01-04T11%3A15%3A57Z&sp=r\", \"azureml-logs/75_job_post-tvmps_bb49cbe82626e1162dc32b5dd0516d96fa46f67a2765150b71dc08d139a6770d_d.txt\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578107635_6a48bf8c/azureml-logs/75_job_post-tvmps_bb49cbe82626e1162dc32b5dd0516d96fa46f67a2765150b71dc08d139a6770d_d.txt?sv=2019-02-02&sr=b&sig=n7V311vapVnNI16qMoRTD3ofR7m2UC4qms6UfGa3BFc%3D&st=2020-01-04T03%3A05%3A57Z&se=2020-01-04T11%3A15%3A57Z&sp=r\", \"azureml-logs/process_info.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578107635_6a48bf8c/azureml-logs/process_info.json?sv=2019-02-02&sr=b&sig=ywZQvmv%2FRx%2Fo8i01PwG0Vk6brrp2SN%2FYRX7zM8BNMKY%3D&st=2020-01-04T03%3A05%3A57Z&se=2020-01-04T11%3A15%3A57Z&sp=r\", \"azureml-logs/process_status.json\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578107635_6a48bf8c/azureml-logs/process_status.json?sv=2019-02-02&sr=b&sig=WWJCJ%2BrdtrknVBPh6iNfCNGkwoKW17xEF7Tm%2B4zBmrU%3D&st=2020-01-04T03%3A05%3A57Z&se=2020-01-04T11%3A15%3A57Z&sp=r\", \"logs/azureml/687_azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578107635_6a48bf8c/logs/azureml/687_azureml.log?sv=2019-02-02&sr=b&sig=3hS3J3REFV71A%2Bjeere6UVwXWLhcIPYcsCRahA2hDaw%3D&st=2020-01-04T03%3A05%3A57Z&se=2020-01-04T11%3A15%3A57Z&sp=r\", \"logs/azureml/azureml.log\": \"https://ghiordanstoragee145cef0b.blob.core.windows.net/azureml/ExperimentRun/dcid.020_AzureMLEstimator_1578107635_6a48bf8c/logs/azureml/azureml.log?sv=2019-02-02&sr=b&sig=IIO36ZTtOuc9mnkmqzFCVO85QuVPDbC7HoHRb333MjU%3D&st=2020-01-04T03%3A05%3A57Z&se=2020-01-04T11%3A15%3A57Z&sp=r\"}, \"log_groups\": [[\"azureml-logs/process_info.json\", \"azureml-logs/process_status.json\", \"logs/azureml/azureml.log\"], [\"azureml-logs/55_azureml-execution-tvmps_bb49cbe82626e1162dc32b5dd0516d96fa46f67a2765150b71dc08d139a6770d_d.txt\"], [\"azureml-logs/65_job_prep-tvmps_bb49cbe82626e1162dc32b5dd0516d96fa46f67a2765150b71dc08d139a6770d_d.txt\"], [\"azureml-logs/70_driver_log.txt\"], [\"azureml-logs/75_job_post-tvmps_bb49cbe82626e1162dc32b5dd0516d96fa46f67a2765150b71dc08d139a6770d_d.txt\"], [\"logs/azureml/687_azureml.log\"]], \"run_duration\": \"0:01:52\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"training_message01: \", \"run_id\": \"020_AzureMLEstimator_1578107635_6a48bf8c\", \"categories\": [0], \"series\": [{\"data\": [\"finished experiment\"]}]}], \"run_logs\": \"2020-01-04 03:14:20,312|azureml|DEBUG|Inputs:: kwargs: {'OutputCollection': True, 'snapshotProject': True, 'only_in_process_features': True, 'skip_track_logs_dir': True}, track_folders: None, deny_list: None, directories_to_watch: []\\n2020-01-04 03:14:20,313|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Execution target type: batchai\\n2020-01-04 03:14:20,313|azureml.history._tracking.PythonWorkingDirectory|DEBUG|Failed to import pyspark with error: No module named 'pyspark'\\n2020-01-04 03:14:20,313|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Pinning working directory for filesystems: ['pyfs']\\n2020-01-04 03:14:20,576|azureml._base_sdk_common.user_agent|DEBUG|Fetching client info from /root/.azureml/clientinfo.json\\n2020-01-04 03:14:20,577|azureml._base_sdk_common.user_agent|DEBUG|Error loading client info: [Errno 2] No such file or directory: '/root/.azureml/clientinfo.json'\\n2020-01-04 03:14:20,895|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2020-01-04 03:14:20,895|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2020-01-04 03:14:20,896|azureml.core._experiment_method|DEBUG|Trying to register submit_function search, on method \\n2020-01-04 03:14:20,896|azureml.core._experiment_method|DEBUG|Registered submit_function search, on method \\n2020-01-04 03:14:20,896|azureml.core.run|DEBUG|Adding new factory for run source hyperdrive\\n2020-01-04 03:14:21,383|azureml.core.run|DEBUG|Adding new factory for run source azureml.PipelineRun\\n2020-01-04 03:14:21,387|azureml.core.run|DEBUG|Adding new factory for run source azureml.ReusedStepRun\\n2020-01-04 03:14:21,391|azureml.core.run|DEBUG|Adding new factory for run source azureml.StepRun\\n2020-01-04 03:14:21,395|azureml.core.run|DEBUG|Adding new factory for run source azureml.scriptrun\\n2020-01-04 03:14:21,396|azureml.core.authentication.TokenRefresherDaemon|DEBUG|Starting daemon and triggering first instance\\n2020-01-04 03:14:21,401|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:21,401|azureml._restclient.clientbase|INFO|Created a worker pool for first use\\n2020-01-04 03:14:21,402|azureml.core.authentication|DEBUG|Time to expire 1814375.597972 seconds\\n2020-01-04 03:14:21,402|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,402|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,402|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,402|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,402|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,402|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,402|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,402|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,402|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,461|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:21,466|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:21,472|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:21,476|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:21,480|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:21,484|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:21,485|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2020-01-04 03:14:21,485|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2020-01-04 03:14:21,485|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1578107635_6a48bf8c'\\n2020-01-04 03:14:21,486|msrest.http_logger|DEBUG|Request method: 'GET'\\n2020-01-04 03:14:21,486|msrest.http_logger|DEBUG|Request headers:\\n2020-01-04 03:14:21,486|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2020-01-04 03:14:21,486|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2020-01-04 03:14:21,486|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '27adf543-ba6f-46d0-9089-c0da73fbd53d'\\n2020-01-04 03:14:21,486|msrest.http_logger|DEBUG| 'request-id': '27adf543-ba6f-46d0-9089-c0da73fbd53d'\\n2020-01-04 03:14:21,486|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.81'\\n2020-01-04 03:14:21,486|msrest.http_logger|DEBUG|Request body:\\n2020-01-04 03:14:21,486|msrest.http_logger|DEBUG|None\\n2020-01-04 03:14:21,486|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2020-01-04 03:14:21,486|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2020-01-04 03:14:21,486|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2020-01-04 03:14:21,487|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG|Response status: 200\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG|Response headers:\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG| 'Date': 'Sat, 04 Jan 2020 03:14:21 GMT'\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '27adf543-ba6f-46d0-9089-c0da73fbd53d'\\n2020-01-04 03:14:21,576|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2020-01-04 03:14:21,577|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2020-01-04 03:14:21,577|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2020-01-04 03:14:21,577|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2020-01-04 03:14:21,577|msrest.http_logger|DEBUG|Response content:\\n2020-01-04 03:14:21,577|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 6915,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1578107635_6a48bf8c\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2020-01-04T03:13:57.4236375+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1578107635_6a48bf8c\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2020-01-04T03:14:09.4469741+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1578107635_6a48bf8c\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito04\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito04\\\",\\n \\\"azureml.git.commit\\\": \\\"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1578107635_6a48bf8c/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1578107635_6a48bf8c/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1578107635_6a48bf8c/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2020-01-04 03:14:21,582|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2020-01-04 03:14:21,582|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/seismic-deeplearning.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/seismic-deeplearning.git', 'azureml.git.branch': 'ghiordan/azureml_devito04', 'mlflow.source.git.branch': 'ghiordan/azureml_devito04', 'azureml.git.commit': 'b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb', 'mlflow.source.git.commit': 'b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2020-01-04 03:14:21,582|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2020-01-04 03:14:21,583|azureml|WARNING|Could not import azureml.mlflow or azureml.contrib.mlflow mlflow APIs will not run against AzureML services. Add azureml-mlflow as a conda dependency for the run if this behavior is desired\\n2020-01-04 03:14:21,583|azureml.WorkerPool|DEBUG|[START]\\n2020-01-04 03:14:21,583|azureml.SendRunKillSignal|DEBUG|[START]\\n2020-01-04 03:14:21,583|azureml.RunStatusContext|DEBUG|[START]\\n2020-01-04 03:14:21,583|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunContextManager.RunStatusContext|DEBUG|[START]\\n2020-01-04 03:14:21,583|azureml.WorkingDirectoryCM|DEBUG|[START]\\n2020-01-04 03:14:21,583|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[START]\\n2020-01-04 03:14:21,583|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1578107635_6a48bf8c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1578107635_6a48bf8c\\n2020-01-04 03:14:21,583|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2020-01-04 03:14:21,583|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Storing working dir for pyfs as /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1578107635_6a48bf8c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1578107635_6a48bf8c\\n2020-01-04 03:14:23,532|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:23,533|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:23,533|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:23,533|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:23,533|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:23,533|azureml._base_sdk_common.service_discovery|DEBUG|Constructing mms service url in from history url environment variable None, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:23,533|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:23,533|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:23,533|azureml._base_sdk_common.service_discovery|DEBUG|Found history service url in environment variable AZUREML_SERVICE_ENDPOINT, history service url: https://eastus2.experiments.azureml.net.\\n2020-01-04 03:14:23,538|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:23,539|azureml._run_impl.run_history_facade|DEBUG|Created a static thread pool for RunHistoryFacade class\\n2020-01-04 03:14:23,543|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:23,548|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:23,552|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:23,556|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:14:23,557|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[START]\\n2020-01-04 03:14:23,557|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2020-01-04 03:14:23,557|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1578107635_6a48bf8c'\\n2020-01-04 03:14:23,557|msrest.http_logger|DEBUG|Request method: 'GET'\\n2020-01-04 03:14:23,557|msrest.http_logger|DEBUG|Request headers:\\n2020-01-04 03:14:23,557|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2020-01-04 03:14:23,557|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2020-01-04 03:14:23,558|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '25b11562-e86e-4813-adad-c438311009ab'\\n2020-01-04 03:14:23,558|msrest.http_logger|DEBUG| 'request-id': '25b11562-e86e-4813-adad-c438311009ab'\\n2020-01-04 03:14:23,558|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.81'\\n2020-01-04 03:14:23,558|msrest.http_logger|DEBUG|Request body:\\n2020-01-04 03:14:23,558|msrest.http_logger|DEBUG|None\\n2020-01-04 03:14:23,558|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2020-01-04 03:14:23,558|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2020-01-04 03:14:23,558|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2020-01-04 03:14:23,558|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG|Response status: 200\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG|Response headers:\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG| 'Date': 'Sat, 04 Jan 2020 03:14:23 GMT'\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG| 'Content-Type': 'application/json; charset=utf-8'\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG| 'Transfer-Encoding': 'chunked'\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG| 'Vary': 'Accept-Encoding'\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '25b11562-e86e-4813-adad-c438311009ab'\\n2020-01-04 03:14:23,700|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2020-01-04 03:14:23,701|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2020-01-04 03:14:23,701|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2020-01-04 03:14:23,701|msrest.http_logger|DEBUG| 'Content-Encoding': 'gzip'\\n2020-01-04 03:14:23,701|msrest.http_logger|DEBUG|Response content:\\n2020-01-04 03:14:23,701|msrest.http_logger|DEBUG|{\\n \\\"runNumber\\\": 6915,\\n \\\"rootRunId\\\": \\\"020_AzureMLEstimator_1578107635_6a48bf8c\\\",\\n \\\"experimentId\\\": \\\"8d96276b-f420-4a67-86be-f933dd3d38cd\\\",\\n \\\"createdUtc\\\": \\\"2020-01-04T03:13:57.4236375+00:00\\\",\\n \\\"createdBy\\\": {\\n \\\"userObjectId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"userPuId\\\": \\\"1003000090A95868\\\",\\n \\\"userIdp\\\": null,\\n \\\"userAltSecId\\\": null,\\n \\\"userIss\\\": \\\"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/\\\",\\n \\\"userTenantId\\\": \\\"72f988bf-86f1-41af-91ab-2d7cd011db47\\\",\\n \\\"userName\\\": \\\"George Iordanescu\\\"\\n },\\n \\\"userId\\\": \\\"b77869a0-66f2-4288-89ef-13c10accc4dc\\\",\\n \\\"token\\\": null,\\n \\\"tokenExpiryTimeUtc\\\": null,\\n \\\"error\\\": null,\\n \\\"warnings\\\": null,\\n \\\"revision\\\": 7,\\n \\\"runId\\\": \\\"020_AzureMLEstimator_1578107635_6a48bf8c\\\",\\n \\\"parentRunId\\\": null,\\n \\\"status\\\": \\\"Running\\\",\\n \\\"startTimeUtc\\\": \\\"2020-01-04T03:14:09.4469741+00:00\\\",\\n \\\"endTimeUtc\\\": null,\\n \\\"heartbeatEnabled\\\": false,\\n \\\"options\\\": {\\n \\\"generateDataContainerIdIfNotSpecified\\\": true\\n },\\n \\\"name\\\": null,\\n \\\"dataContainerId\\\": \\\"dcid.020_AzureMLEstimator_1578107635_6a48bf8c\\\",\\n \\\"description\\\": null,\\n \\\"hidden\\\": false,\\n \\\"runType\\\": \\\"azureml.scriptrun\\\",\\n \\\"properties\\\": {\\n \\\"_azureml.ComputeTargetType\\\": \\\"amlcompute\\\",\\n \\\"ContentSnapshotId\\\": \\\"a5071b2a-37a7-40da-8340-69cc894091cb\\\",\\n \\\"azureml.git.repository_uri\\\": \\\"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\\\",\\n \\\"mlflow.source.git.repoURL\\\": \\\"git@github.com:georgeAccnt-GH/seismic-deeplearning.git\\\",\\n \\\"azureml.git.branch\\\": \\\"ghiordan/azureml_devito04\\\",\\n \\\"mlflow.source.git.branch\\\": \\\"ghiordan/azureml_devito04\\\",\\n \\\"azureml.git.commit\\\": \\\"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\\\",\\n \\\"mlflow.source.git.commit\\\": \\\"b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb\\\",\\n \\\"azureml.git.dirty\\\": \\\"True\\\",\\n \\\"ProcessInfoFile\\\": \\\"azureml-logs/process_info.json\\\",\\n \\\"ProcessStatusFile\\\": \\\"azureml-logs/process_status.json\\\"\\n },\\n \\\"scriptName\\\": \\\"azureml_01_modelling.py\\\",\\n \\\"target\\\": \\\"gpuclstfwi07\\\",\\n \\\"tags\\\": {},\\n \\\"inputDatasets\\\": [],\\n \\\"runDefinition\\\": null,\\n \\\"createdFrom\\\": {\\n \\\"type\\\": \\\"Notebook\\\",\\n \\\"locationType\\\": \\\"ArtifactId\\\",\\n \\\"location\\\": \\\"LocalUpload/020_AzureMLEstimator_1578107635_6a48bf8c/030_ScaleJobsUsingAzuremL_GeophysicsTutorial_FWI_Azure_devito.ipynb\\\"\\n },\\n \\\"cancelUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1578107635_6a48bf8c/cancel\\\",\\n \\\"completeUri\\\": null,\\n \\\"diagnosticsUri\\\": \\\"https://eastus2.experiments.azureml.net/execution/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runId/020_AzureMLEstimator_1578107635_6a48bf8c/diagnostics\\\",\\n \\\"computeRequest\\\": {\\n \\\"nodeCount\\\": 1\\n },\\n \\\"retainForLifetimeOfWorkspace\\\": false,\\n \\\"queueingInfo\\\": null\\n}\\n2020-01-04 03:14:23,703|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.RunClient.get-async:False|DEBUG|[STOP]\\n2020-01-04 03:14:23,703|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c|DEBUG|Constructing run from dto. type: azureml.scriptrun, source: None, props: {'_azureml.ComputeTargetType': 'amlcompute', 'ContentSnapshotId': 'a5071b2a-37a7-40da-8340-69cc894091cb', 'azureml.git.repository_uri': 'git@github.com:georgeAccnt-GH/seismic-deeplearning.git', 'mlflow.source.git.repoURL': 'git@github.com:georgeAccnt-GH/seismic-deeplearning.git', 'azureml.git.branch': 'ghiordan/azureml_devito04', 'mlflow.source.git.branch': 'ghiordan/azureml_devito04', 'azureml.git.commit': 'b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb', 'mlflow.source.git.commit': 'b93dcf2325fbc8b1dff1ad74ad14ee41f4e184bb', 'azureml.git.dirty': 'True', 'ProcessInfoFile': 'azureml-logs/process_info.json', 'ProcessStatusFile': 'azureml-logs/process_status.json'}\\n2020-01-04 03:14:23,704|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunContextManager|DEBUG|Valid logs dir, setting up content loader\\n2020-01-04 03:14:51,396|azureml.core.authentication|DEBUG|Time to expire 1814345.603107 seconds\\n2020-01-04 03:15:21,397|azureml.core.authentication|DEBUG|Time to expire 1814315.602798 seconds\\n2020-01-04 03:15:29,099|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2020-01-04 03:15:29,099|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2020-01-04 03:15:29,100|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2020-01-04 03:15:29,189|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Calling pyfs\\n2020-01-04 03:15:29,189|azureml.history._tracking.PythonWorkingDirectory|INFO|Current working dir: /devito\\n2020-01-04 03:15:29,189|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|pyfs has path /devito\\n2020-01-04 03:15:29,189|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|Reverting working dir from /devito to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1578107635_6a48bf8c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1578107635_6a48bf8c\\n2020-01-04 03:15:29,189|azureml.history._tracking.PythonWorkingDirectory|INFO|Setting working dir to /mnt/batch/tasks/shared/LS_root/jobs/ghiordanfwiws/azureml/020_azuremlestimator_1578107635_6a48bf8c/mounts/workspaceblobstore/azureml/020_AzureMLEstimator_1578107635_6a48bf8c\\n2020-01-04 03:15:29,189|azureml.history._tracking.PythonWorkingDirectory.workingdir|DEBUG|[STOP]\\n2020-01-04 03:15:29,189|azureml.WorkingDirectoryCM|DEBUG|[STOP]\\n2020-01-04 03:15:29,189|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c|INFO|complete is not setting status for submitted runs.\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient|DEBUG|Overrides: Max batch size: 50, batch cushion: 5, Interval: 1.\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.PostMetricsBatchDaemon|DEBUG|Starting daemon and triggering first instance\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient|DEBUG|Used for use_batch=True.\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300 is different from task queue timeout 120, using flush timeout\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300 seconds on tasks: [].\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2020-01-04 03:15:29,190|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2020-01-04 03:15:29,190|azureml.RunStatusContext|DEBUG|[STOP]\\n2020-01-04 03:15:29,191|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2020-01-04 03:15:29,191|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2020-01-04 03:15:29,191|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2020-01-04 03:15:29,191|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [].\\n2020-01-04 03:15:29,191|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|\\n2020-01-04 03:15:29,191|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2020-01-04 03:15:29,191|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2020-01-04 03:15:29,191|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[START]\\n2020-01-04 03:15:29,191|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Start]\\n2020-01-04 03:15:29,191|azureml.BatchTaskQueueAdd_1_Batches.WorkerPool|DEBUG|submitting future: _handle_batch\\n2020-01-04 03:15:29,191|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Batch size 1.\\n2020-01-04 03:15:29,192|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch|DEBUG|Using basic handler - no exception handling\\n2020-01-04 03:15:29,192|azureml._restclient.clientbase.WorkerPool|DEBUG|submitting future: _log_batch\\n2020-01-04 03:15:29,192|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|Adding task 0__handle_batch to queue of approximate size: 0\\n2020-01-04 03:15:29,192|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch|DEBUG|Using basic handler - no exception handling\\n2020-01-04 03:15:29,192|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[START]\\n2020-01-04 03:15:29,192|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|[Stop] - waiting default timeout\\n2020-01-04 03:15:29,192|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Adding task 0__log_batch to queue of approximate size: 0\\n2020-01-04 03:15:29,193|msrest.service_client|DEBUG|Accept header absent and forced to application/json\\n2020-01-04 03:15:29,194|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[START]\\n2020-01-04 03:15:29,194|msrest.universal_http.requests|DEBUG|Configuring retry: max_retries=3, backoff_factor=0.8, max_backoff=90\\n2020-01-04 03:15:29,194|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Overriding default flush timeout from None to 120\\n2020-01-04 03:15:29,194|msrest.http_logger|DEBUG|Request URL: 'https://eastus2.experiments.azureml.net/history/v1.0/subscriptions/789908e0-5fc2-4c4d-b5f5-9764b0d602b3/resourceGroups/ghiordanfwirsg01/providers/Microsoft.MachineLearningServices/workspaces/ghiordanfwiws/experiments/020_AzureMLEstimator/runs/020_AzureMLEstimator_1578107635_6a48bf8c/batch/metrics'\\n2020-01-04 03:15:29,195|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|Waiting 120 seconds on tasks: [AsyncTask(0__handle_batch)].\\n2020-01-04 03:15:29,195|msrest.http_logger|DEBUG|Request method: 'POST'\\n2020-01-04 03:15:29,195|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[START]\\n2020-01-04 03:15:29,195|msrest.http_logger|DEBUG|Request headers:\\n2020-01-04 03:15:29,195|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|Awaiter is BatchTaskQueueAdd_1_Batches\\n2020-01-04 03:15:29,195|msrest.http_logger|DEBUG| 'Accept': 'application/json'\\n2020-01-04 03:15:29,195|azureml.BatchTaskQueueAdd_1_Batches.0__handle_batch.WaitingTask|DEBUG|[STOP]\\n2020-01-04 03:15:29,195|msrest.http_logger|DEBUG| 'Content-Type': 'application/json-patch+json; charset=utf-8'\\n2020-01-04 03:15:29,195|azureml.BatchTaskQueueAdd_1_Batches|DEBUG|\\n2020-01-04 03:15:29,196|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '3bf60291-a896-44db-a757-77b3dc584c55'\\n2020-01-04 03:15:29,196|azureml.BatchTaskQueueAdd_1_Batches.WaitFlushSource:BatchTaskQueueAdd_1_Batches|DEBUG|[STOP]\\n2020-01-04 03:15:29,196|msrest.http_logger|DEBUG| 'request-id': '3bf60291-a896-44db-a757-77b3dc584c55'\\n2020-01-04 03:15:29,196|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[START]\\n2020-01-04 03:15:29,196|msrest.http_logger|DEBUG| 'Content-Length': '410'\\n2020-01-04 03:15:29,196|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|flush timeout 300.0 is different from task queue timeout 120, using flush timeout\\n2020-01-04 03:15:29,196|msrest.http_logger|DEBUG| 'User-Agent': 'python/3.6.9 (Linux-4.15.0-1057-azure-x86_64-with-debian-10.1) msrest/0.6.10 azureml._restclient/core.1.0.81 sdk_run'\\n2020-01-04 03:15:29,196|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|Waiting 300.0 seconds on tasks: [AsyncTask(0__log_batch)].\\n2020-01-04 03:15:29,196|msrest.http_logger|DEBUG|Request body:\\n2020-01-04 03:15:29,197|msrest.http_logger|DEBUG|{\\\"values\\\": [{\\\"metricId\\\": \\\"688c12af-7b49-4dc4-b4de-5fabd7959e2d\\\", \\\"metricType\\\": \\\"azureml.v1.scalar\\\", \\\"createdUtc\\\": \\\"2020-01-04T03:15:29.099399Z\\\", \\\"name\\\": \\\"training_message01: \\\", \\\"description\\\": \\\"\\\", \\\"numCells\\\": 1, \\\"cells\\\": [{\\\"training_message01: \\\": \\\"finished experiment\\\"}], \\\"schema\\\": {\\\"numProperties\\\": 1, \\\"properties\\\": [{\\\"propertyId\\\": \\\"training_message01: \\\", \\\"name\\\": \\\"training_message01: \\\", \\\"type\\\": \\\"string\\\"}]}}]}\\n2020-01-04 03:15:29,197|msrest.universal_http|DEBUG|Configuring redirects: allow=True, max=30\\n2020-01-04 03:15:29,197|msrest.universal_http|DEBUG|Configuring request: timeout=100, verify=True, cert=None\\n2020-01-04 03:15:29,197|msrest.universal_http|DEBUG|Configuring proxies: ''\\n2020-01-04 03:15:29,197|msrest.universal_http|DEBUG|Evaluate proxies against ENV settings: True\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG|Response status: 200\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG|Response headers:\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG| 'Date': 'Sat, 04 Jan 2020 03:15:29 GMT'\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG| 'Content-Length': '0'\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG| 'Connection': 'keep-alive'\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG| 'Request-Context': 'appId=cid-v1:2d2e8e63-272e-4b3c-8598-4ee570a0e70d'\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG| 'x-ms-client-request-id': '3bf60291-a896-44db-a757-77b3dc584c55'\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG| 'x-ms-client-session-id': ''\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG| 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains; preload'\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG| 'X-Content-Type-Options': 'nosniff'\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG|Response content:\\n2020-01-04 03:15:29,354|msrest.http_logger|DEBUG|\\n2020-01-04 03:15:29,355|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.post_batch-async:False|DEBUG|[STOP]\\n2020-01-04 03:15:29,447|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[START]\\n2020-01-04 03:15:29,447|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|Awaiter is PostMetricsBatch\\n2020-01-04 03:15:29,447|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.0__log_batch.WaitingTask|DEBUG|[STOP]\\n2020-01-04 03:15:29,447|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch|DEBUG|Waiting on task: 0__log_batch.\\n1 tasks left. Current duration of flush 0.0002353191375732422 seconds.\\n\\n2020-01-04 03:15:29,447|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.PostMetricsBatch.WaitFlushSource:MetricsClient|DEBUG|[STOP]\\n2020-01-04 03:15:29,447|azureml._SubmittedRun#020_AzureMLEstimator_1578107635_6a48bf8c.RunHistoryFacade.MetricsClient.FlushingMetricsClient|DEBUG|[STOP]\\n2020-01-04 03:15:29,447|azureml.SendRunKillSignal|DEBUG|[STOP]\\n2020-01-04 03:15:29,447|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[START]\\n2020-01-04 03:15:29,448|azureml.HistoryTrackingWorkerPool.WorkerPoolShutdown|DEBUG|[STOP]\\n2020-01-04 03:15:29,448|azureml.WorkerPool|DEBUG|[STOP]\\n\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.0.81\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" @@ -1010,41 +1001,41 @@ "name": "stdout", "output_type": "stream", "text": [ - "Counter182: submission of job 182 on 20 nodes took 9.67591118812561 seconds \n", + "Counter182: submission of job 182 on 20 nodes took 8.759885311126709 seconds \n", "run list length 182\n", - "Counter183: submission of job 183 on 20 nodes took 8.417665958404541 seconds \n", + "Counter183: submission of job 183 on 20 nodes took 9.231754302978516 seconds \n", "run list length 183\n", - "Counter184: submission of job 184 on 20 nodes took 8.162678956985474 seconds \n", + "Counter184: submission of job 184 on 20 nodes took 13.600019454956055 seconds \n", "run list length 184\n", - "Counter185: submission of job 185 on 20 nodes took 8.167269706726074 seconds \n", + "Counter185: submission of job 185 on 20 nodes took 8.25251030921936 seconds \n", "run list length 185\n", - "Counter186: submission of job 186 on 20 nodes took 7.944813966751099 seconds \n", + "Counter186: submission of job 186 on 20 nodes took 8.89614224433899 seconds \n", "run list length 186\n", - "Counter187: submission of job 187 on 20 nodes took 9.100224256515503 seconds \n", + "Counter187: submission of job 187 on 20 nodes took 9.315387725830078 seconds \n", "run list length 187\n", - "Counter188: submission of job 188 on 20 nodes took 8.355362892150879 seconds \n", + "Counter188: submission of job 188 on 20 nodes took 8.64873480796814 seconds \n", "run list length 188\n", - "Counter189: submission of job 189 on 20 nodes took 9.092441320419312 seconds \n", + "Counter189: submission of job 189 on 20 nodes took 8.950633525848389 seconds \n", "run list length 189\n", - "Counter190: submission of job 190 on 20 nodes took 10.007514238357544 seconds \n", + "Counter190: submission of job 190 on 20 nodes took 7.8102922439575195 seconds \n", "run list length 190\n", - "Counter191: submission of job 191 on 20 nodes took 8.018926858901978 seconds \n", + "Counter191: submission of job 191 on 20 nodes took 8.68752121925354 seconds \n", "run list length 191\n", - "Counter192: submission of job 192 on 20 nodes took 8.925177574157715 seconds \n", + "Counter192: submission of job 192 on 20 nodes took 10.058020830154419 seconds \n", "run list length 192\n", - "Counter193: submission of job 193 on 20 nodes took 8.075469493865967 seconds \n", + "Counter193: submission of job 193 on 20 nodes took 10.503464221954346 seconds \n", "run list length 193\n", - "Counter194: submission of job 194 on 20 nodes took 8.114925384521484 seconds \n", + "Counter194: submission of job 194 on 20 nodes took 15.409441709518433 seconds \n", "run list length 194\n", - "Counter195: submission of job 195 on 20 nodes took 11.568782329559326 seconds \n", + "Counter195: submission of job 195 on 20 nodes took 12.09773850440979 seconds \n", "run list length 195\n", - "Counter196: submission of job 196 on 20 nodes took 7.691022157669067 seconds \n", + "Counter196: submission of job 196 on 20 nodes took 8.979861497879028 seconds \n", "run list length 196\n", - "Counter197: submission of job 197 on 20 nodes took 7.677650451660156 seconds \n", + "Counter197: submission of job 197 on 20 nodes took 9.068669319152832 seconds \n", "run list length 197\n", - "Counter198: submission of job 198 on 20 nodes took 7.701624393463135 seconds \n", + "Counter198: submission of job 198 on 20 nodes took 8.007090330123901 seconds \n", "run list length 198\n", - "Counter199: submission of job 199 on 20 nodes took 8.586235761642456 seconds \n", + "Counter199: submission of job 199 on 20 nodes took 9.039068460464478 seconds \n", "run list length 199\n" ] } @@ -1089,46 +1080,46 @@ { "data": { "text/plain": [ - "array([72.32372785, 9.84876561, 8.23770022, 8.51928997, 10.07481861,\n", - " 9.50573421, 8.61487699, 9.2067368 , 8.7046783 , 8.50711679,\n", - " 10.19562602, 9.36683083, 7.55569768, 7.81828165, 8.05966425,\n", - " 7.95713139, 8.46397591, 8.00363493, 8.74197745, 9.85158968,\n", - " 8.15499783, 8.4186945 , 8.3459394 , 7.77093124, 10.68578815,\n", - " 8.50453854, 10.00224447, 7.92351389, 8.35350633, 9.25221825,\n", - " 8.34743953, 7.69802356, 8.29426646, 8.59166431, 8.46652174,\n", - " 9.47893 , 75.07776022, 8.41120005, 8.96823859, 10.98724246,\n", - " 7.94364333, 7.90829492, 8.94897985, 10.17237568, 9.11909747,\n", - " 7.87749696, 8.00650215, 8.36645675, 8.91567326, 10.34763336,\n", - " 7.77076221, 7.88283515, 7.75079894, 9.26020312, 8.62849283,\n", - " 7.80995584, 7.80822968, 10.76239038, 8.14607382, 7.99221134,\n", - " 9.6921885 , 8.19335723, 9.15633559, 8.00351334, 9.54636669,\n", - " 7.59108806, 7.69910932, 8.04463506, 7.93083286, 8.62544155,\n", - " 8.79564786, 9.13332033, 7.80911231, 8.35303783, 7.82778549,\n", - " 8.06088972, 9.1007092 , 8.3380692 , 8.64629507, 8.07920575,\n", - " 8.02019548, 10.71363115, 8.34652901, 7.74129391, 7.91959643,\n", - " 7.85336089, 7.90019083, 12.90023398, 19.41495585, 13.15474701,\n", - " 12.07031298, 11.95169044, 7.79440594, 10.60236144, 8.72420573,\n", - " 8.06074238, 8.54056549, 8.02011943, 9.75681472, 8.5499773 ,\n", - " 7.79605484, 8.42818856, 9.18867898, 7.9006598 , 27.39237142,\n", - " 9.0595603 , 8.78542948, 7.90973282, 8.57101846, 8.46719694,\n", - " 8.13528228, 8.01672626, 8.10046387, 9.75899887, 8.07786036,\n", - " 8.67664742, 8.93552232, 9.15524864, 9.84150887, 10.98953986,\n", - " 7.7957077 , 8.00527406, 8.84030366, 8.21525049, 8.41219711,\n", - " 8.58533263, 9.19599056, 8.0582397 , 9.54104137, 7.90521908,\n", - " 9.04605484, 9.31400728, 8.40495944, 9.40762448, 8.44924903,\n", - " 8.32500076, 9.12283754, 7.8790009 , 7.81203079, 9.60578918,\n", - " 8.49342728, 8.78831935, 8.37885499, 7.84795952, 8.0113368 ,\n", - " 10.01567674, 8.65693212, 7.91696692, 8.66499114, 8.16211271,\n", - " 10.52479053, 8.01505375, 7.76023841, 8.17951965, 8.72431993,\n", - " 8.06374121, 8.10564208, 8.59537721, 9.92992234, 8.32705522,\n", - " 9.78222585, 9.35200691, 9.76717973, 10.28895092, 9.19370604,\n", - " 8.0294354 , 8.37053967, 9.8634398 , 8.63042641, 8.18737698,\n", - " 43.09095621, 8.65846062, 10.26536632, 8.97337079, 8.09767246,\n", - " 11.08810735, 9.55694532, 15.43015337, 12.9441483 , 74.00292444,\n", - " 8.60825634, 9.67591119, 8.41766596, 8.16267896, 8.16726971,\n", - " 7.94481397, 9.10022426, 8.35536289, 9.09244132, 10.00751424,\n", - " 8.01892686, 8.92517757, 8.07546949, 8.11492538, 11.56878233,\n", - " 7.69102216, 7.67765045, 7.70162439, 8.58623576])" + "array([ 9.38066268, 9.179739 , 7.78263307, 8.48762512, 8.10376453,\n", + " 8.56524658, 9.3991704 , 9.5658536 , 8.36927128, 8.75663853,\n", + " 12.13388062, 14.47540092, 11.96517801, 8.72594619, 8.40938139,\n", + " 9.32459807, 7.49898648, 7.6770916 , 8.02397871, 7.90862179,\n", + " 8.10608029, 11.15578365, 8.79269648, 8.14802432, 8.24170065,\n", + " 8.16714478, 7.58979988, 8.1290102 , 8.62176943, 8.25858569,\n", + " 9.62505913, 47.5489819 , 8.66863251, 9.16207457, 8.39729404,\n", + " 8.35266876, 9.4563067 , 8.82322693, 8.02664924, 8.37660813,\n", + " 9.95896864, 8.33410001, 8.31452036, 8.16242218, 8.89259601,\n", + " 8.59475064, 8.67619205, 8.53494453, 8.38801956, 8.262182 ,\n", + " 8.15596676, 8.39553308, 7.6218369 , 8.0278995 , 8.25957584,\n", + " 9.44061399, 8.49700546, 8.50827813, 7.66743159, 7.89283347,\n", + " 9.41012764, 7.84124994, 9.12892008, 9.09043598, 8.20927215,\n", + " 8.52260566, 8.71522832, 8.5416894 , 8.18118405, 9.00752664,\n", + " 8.04391742, 8.14891124, 8.28526998, 9.01239848, 8.38266683,\n", + " 14.62356496, 14.96698308, 9.08445382, 9.09234452, 10.11763263,\n", + " 9.4793036 , 8.85432053, 8.04963088, 11.1493423 , 13.14935565,\n", + " 11.87230349, 9.03305769, 7.69870114, 9.0376091 , 7.95852947,\n", + " 9.09171367, 7.89099622, 7.95618558, 8.01989579, 7.80822897,\n", + " 8.87989831, 7.47948337, 8.94316697, 11.28595853, 8.06794882,\n", + " 8.43929172, 13.42541838, 12.66442442, 10.31300116, 8.40639782,\n", + " 9.44638705, 7.86042881, 7.9562912 , 7.63827801, 9.36086607,\n", + " 8.08337593, 8.08384132, 8.62920904, 7.93341088, 27.11030984,\n", + " 8.74523377, 7.85957456, 7.72122622, 8.07594705, 7.69747329,\n", + " 10.03533864, 7.98938775, 8.65719438, 7.70120573, 8.13396835,\n", + " 7.81585193, 8.7429204 , 8.03873968, 7.91843748, 8.5353601 ,\n", + " 9.08414316, 9.40751314, 11.16570473, 12.92745161, 12.54237223,\n", + " 8.64982891, 8.20941329, 8.21981692, 8.69059372, 9.09631157,\n", + " 8.40581775, 8.06178737, 7.88094616, 8.26159453, 8.75838733,\n", + " 8.35216618, 8.49808455, 8.0595901 , 8.94898224, 8.55322099,\n", + " 9.57320905, 8.15324783, 8.81365919, 8.33678389, 10.17447209,\n", + " 10.76701999, 8.4331758 , 8.84952474, 8.16706038, 8.91656828,\n", + " 11.22193599, 7.92432523, 9.3538506 , 8.28465366, 8.90996122,\n", + " 8.44929314, 7.96649742, 7.91064453, 8.33018184, 9.50152779,\n", + " 8.73313498, 7.64226604, 9.21144247, 8.67113829, 7.94187903,\n", + " 9.58002162, 8.84625363, 9.22457576, 9.34697914, 8.5770123 ,\n", + " 8.62616229, 8.75988531, 9.2317543 , 13.60001945, 8.25251031,\n", + " 8.89614224, 9.31538773, 8.64873481, 8.95063353, 7.81029224,\n", + " 8.68752122, 10.05802083, 10.50346422, 15.40944171, 12.0977385 ,\n", + " 8.9798615 , 9.06866932, 8.00709033, 9.03906846])" ] }, "execution_count": 22, @@ -1138,7 +1129,7 @@ { "data": { "text/plain": [ - "(array([ 0, 0, 0, 12, 61, 44, 20, 19, 13]),\n", + "(array([ 0, 0, 0, 12, 50, 44, 40, 20, 5]),\n", " array([ 6. , 6.44444444, 6.88888889, 7.33333333, 7.77777778,\n", " 8.22222222, 8.66666667, 9.11111111, 9.55555556, 10. ]))" ] @@ -1163,7 +1154,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Final print 40, time 119.696 seconds: Counter({'Completed': 184, 'Failed': 15})izing': 1})Running': 1})\r" + "Final print 38, time 113.758 seconds: Counter({'Completed': 183, 'Failed': 16})izing': 1})Running': 1})\r" ] } ], @@ -1221,8 +1212,8 @@ { "data": { "text/plain": [ - "array([ 76, 160, 87, 148, 146, 78, 140, 122, 6, 34, 192, 27, 74,\n", - " 94, 9, 169, 82, 152, 89, 38])" + "array([68, 43, 18, 4, 10, 7, 9, 3, 15, 13, 2, 19, 16, 12, 11, 5, 8,\n", + " 20, 23, 14])" ] }, "execution_count": 25, @@ -1233,21 +1224,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "[117.302675 117.403537 117.598663 117.907701 119.020195 122.495359\n", - " 128.828526 131.806861 132.140995 159.631798 166.340537 166.637734\n", - " 166.748339 169.732802 170.608667 170.923515 171.960079 186.075458\n", - " 213.533596 223.50992 ]\n", - "['Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + "[125.670241 235.262071 242.972693 243.289016 245.793012 246.206194\n", + " 246.548031 247.237064 248.347261 248.495178 248.532195 249.195745\n", + " 251.287567 251.48567 251.764877 252.207378 253.225733 253.885991\n", + " 256.071908 258.126523]\n", + "['Completed' 'Completed' 'Completed' 'Failed' 'Completed' 'Completed'\n", " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", - " 'Completed' 'Failed']\n" + " 'Completed' 'Completed']\n" ] }, { "data": { "text/plain": [ - "array([ 43, 125, 136, 85, 147, 195, 183, 50, 174, 116, 45, 23, 112,\n", - " 100, 72, 25, 120, 93, 98, 77])" + "array([137, 140, 113, 70, 136, 135, 159, 149, 179, 101, 198, 171, 142,\n", + " 116, 181, 125, 185, 151, 115, 154])" ] }, "execution_count": 25, @@ -1258,20 +1249,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "[ 94.171413 97.399767 99.25924 99.408774 99.514601 99.538883\n", - " 99.752909 99.831267 100.191572 100.296421 100.401819 100.462001\n", - " 100.867313 100.895425 100.990763 101.042026 101.110186 101.184303\n", - " 101.320054 101.384652]\n", - "['Failed' 'Completed' 'Completed' 'Failed' 'Completed' 'Completed'\n", - " 'Failed' 'Completed' 'Completed' 'Completed' 'Failed' 'Completed'\n", - " 'Completed' 'Completed' 'Completed' 'Completed' 'Completed' 'Completed'\n", + "[93.328156 93.536588 93.550991 93.575355 93.699247 93.909906 94.273905\n", + " 94.396221 94.500897 94.526355 94.56506 94.763535 95.017692 95.024318\n", + " 95.145761 95.297085 95.374434 95.387763 95.447059 95.533165]\n", + "['Completed' 'Completed' 'Completed' 'Failed' 'Completed' 'Failed'\n", + " 'Completed' 'Failed' 'Completed' 'Completed' 'Completed' 'Failed'\n", + " 'Completed' 'Completed' 'Failed' 'Completed' 'Completed' 'Failed'\n", " 'Completed' 'Completed']\n" ] }, { "data": { "text/plain": [ - "(array([ 0, 0, 8, 169, 11, 0, 3, 5, 1]),\n", + "(array([ 0, 0, 73, 102, 5, 0, 0, 0, 0]),\n", " array([ 50. , 66.66666667, 83.33333333, 100. ,\n", " 116.66666667, 133.33333333, 150. , 166.66666667,\n", " 183.33333333, 200. ]))" @@ -1322,9 +1312,9 @@ ], "metadata": { "kernelspec": { - "display_name": "fwi_dev_conda_environment Python", + "display_name": "Python [conda env:aml-sdk-conda-env] *", "language": "python", - "name": "fwi_dev_conda_environment" + "name": "conda-env-aml-sdk-conda-env-py" }, "language_info": { "codemirror_mode": { From 6265e0e6a936e16f99b2e82a245494c47b450142 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Wed, 22 Jan 2020 14:21:04 -0500 Subject: [PATCH 157/207] making model snapshots more verbose / friendly (#152) * added scripts which reproduce results * build error fix * modified all local training runs to use model_dir for model name * extended model naming to distributed setup as well * added pillow breakage fix too * removing execution scripts from this PR * upgrading pytorch version to keep up with torchvision to keep up with Pillow * reduced validation batch size for deconvnets to combat OOM with pyTorch 1.4.0 --- environment/anaconda/local/environment.yml | 7 +- .../dutchf3_patch/distributed/train.py | 13 +- .../local/configs/patch_deconvnet.yaml | 4 +- .../local/configs/patch_deconvnet_skip.yaml | 3 +- .../dutchf3_patch/local/train.py | 9 +- .../dutchf3_section/local/train.py | 16 +-- .../interpretation/penobscot/local/train.py | 9 +- tests/cicd/main_build.yml | 135 ++++++++++++------ 8 files changed, 123 insertions(+), 73 deletions(-) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 4eebd0b3..40812d9f 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -5,11 +5,11 @@ channels: dependencies: - python=3.6.7 - pip - - pytorch==1.3.1 + - pytorch==1.4.0 - cudatoolkit==10.1.243 - jupyter - ipykernel - - torchvision==0.4.2 + - torchvision>=0.5.0 - pandas==0.25.3 - opencv==4.1.2 - scikit-learn==0.21.3 @@ -35,4 +35,5 @@ dependencies: - black - pylint - scipy==1.1.0 - - jupytext==1.3.0 + - jupytext==1.3.0 + diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/experiments/interpretation/dutchf3_patch/distributed/train.py index 5cc8dc54..d5259937 100644 --- a/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -104,6 +104,9 @@ def run(*options, cfg=None, local_rank=0, debug=False): """ update_config(config, options=options, config_file=cfg) + # we will write the model under outputs / config_file_name / model_dir + config_file_name = "default_config" if not cfg else cfg.split("/")[-1].split(".")[0] + # Start logging load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) @@ -266,14 +269,12 @@ def _select_pred_and_mask(model_out_dict): trainer.add_event_handler( Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) - trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) try: - output_dir = generate_path( - config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(), - ) + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) except TypeError: - output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) + output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) logger.info(f"Logging Tensorboard to {path.join(output_dir, config.LOG_DIR)}") @@ -331,7 +332,7 @@ def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 checkpoint_handler = SnapshotHandler( - path.join(output_dir, config.TRAIN.MODEL_DIR), + output_dir, config.MODEL.NAME, extract_metric_from("mIoU"), snapshot_function, diff --git a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml index f58406fb..7c695f96 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml @@ -10,7 +10,6 @@ PRINT_FREQ: 10 LOG_CONFIG: logging.conf SEED: 2019 - DATASET: NUM_CLASSES: 6 ROOT: /mnt/dutchf3 @@ -20,7 +19,6 @@ MODEL: NAME: patch_deconvnet IN_CHANNELS: 1 - TRAIN: BATCH_SIZE_PER_GPU: 64 BEGIN_EPOCH: 0 @@ -46,7 +44,7 @@ TRAIN: MODEL_DIR: "models" VALIDATION: - BATCH_SIZE_PER_GPU: 512 + BATCH_SIZE_PER_GPU: 64 TEST: MODEL_PATH: "/data/home/mat/repos/DeepSeismic/interpretation/experiments/segmentation/dutchf3/local/output/mat/exp/5cc37bbe5302e1989ef1388d629400a16f82d1a9/patch_deconvnet/Aug27_200339/models/patch_deconvnet_snapshot1model_50.pth" diff --git a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml index eb89ff00..d47a2c8d 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml @@ -19,7 +19,6 @@ MODEL: NAME: patch_deconvnet_skip IN_CHANNELS: 1 - TRAIN: BATCH_SIZE_PER_GPU: 64 BEGIN_EPOCH: 0 @@ -45,7 +44,7 @@ TRAIN: MODEL_DIR: "models" VALIDATION: - BATCH_SIZE_PER_GPU: 512 + BATCH_SIZE_PER_GPU: 64 TEST: MODEL_PATH: "" diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index eca75b1b..ad8600d8 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -94,6 +94,9 @@ def run(*options, cfg=None, debug=False): update_config(config, options=options, config_file=cfg) + # we will write the model under outputs / config_file_name / model_dir + config_file_name = "default_config" if not cfg else cfg.split("/")[-1].split(".")[0] + # Start logging load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) @@ -177,9 +180,9 @@ def run(*options, cfg=None, debug=False): ) try: - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) except TypeError: - output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) + output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) @@ -285,7 +288,7 @@ def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 checkpoint_handler = SnapshotHandler( - path.join(output_dir, config.TRAIN.MODEL_DIR), + output_dir, config.MODEL.NAME, extract_metric_from("mIoU"), snapshot_function, diff --git a/experiments/interpretation/dutchf3_section/local/train.py b/experiments/interpretation/dutchf3_section/local/train.py index 69b5f7d4..dd8c5b44 100644 --- a/experiments/interpretation/dutchf3_section/local/train.py +++ b/experiments/interpretation/dutchf3_section/local/train.py @@ -78,6 +78,9 @@ def run(*options, cfg=None, debug=False): update_config(config, options=options, config_file=cfg) + # we will write the model under outputs / config_file_name / model_dir + config_file_name = "default_config" if not cfg else cfg.split("/")[-1].split(".")[0] + # Start logging load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) @@ -151,9 +154,11 @@ def __len__(self): ) try: - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + output_dir = generate_path( + config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(), + ) except TypeError: - output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) + output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) @@ -268,12 +273,7 @@ def _tensor_to_numpy(pred_tensor): def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 - checkpoint_handler = SnapshotHandler( - path.join(output_dir, config.TRAIN.MODEL_DIR), - config.MODEL.NAME, - extract_metric_from("mIoU"), - snapshot_function, - ) + checkpoint_handler = SnapshotHandler(output_dir, config.MODEL.NAME, extract_metric_from("mIoU"), snapshot_function,) evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) diff --git a/experiments/interpretation/penobscot/local/train.py b/experiments/interpretation/penobscot/local/train.py index 6b86956d..e615140b 100644 --- a/experiments/interpretation/penobscot/local/train.py +++ b/experiments/interpretation/penobscot/local/train.py @@ -101,6 +101,9 @@ def run(*options, cfg=None, debug=False): update_config(config, options=options, config_file=cfg) + # we will write the model under outputs / config_file_name / model_dir + config_file_name = "default_config" if not cfg else cfg.split("/")[-1].split(".")[0] + # Start logging load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) @@ -191,9 +194,9 @@ def run(*options, cfg=None, debug=False): ) try: - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config.MODEL.NAME, current_datetime(),) + output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) except TypeError: - output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),) + output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) snapshot_duration = scheduler_step * len(train_loader) @@ -298,7 +301,7 @@ def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 checkpoint_handler = SnapshotHandler( - path.join(output_dir, config.TRAIN.MODEL_DIR), + output_dir, config.MODEL.NAME, extract_metric_from("mIoU"), snapshot_function, diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 017137b4..1b3f39de 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -60,15 +60,18 @@ jobs: conda env list source activate seismic-interpretation # run the tests - cd experiments/interpretation/penobscot/local - python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug + cd experiments/interpretation/penobscot/local + python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/hrnet.yaml --debug # find the latest model which we just trained - model=$(ls -td */seg_hrnet/*/* | head -1) + model=$(ls -td output/hrnet/section_depth/* | head -1) echo ${model} # # try running the test script python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug - - job: seresnet_unet_penobscot dependsOn: setup timeoutInMinutes: 5 @@ -80,33 +83,39 @@ jobs: conda env list source activate seismic-interpretation # run the tests - cd experiments/interpretation/penobscot/local - python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug + cd experiments/interpretation/penobscot/local + python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/seresnet_unet.yaml --debug # find the latest model which we just trained - model=$(ls -td */resnet_unet/*/* | head -1) + model=$(ls -td output/seresnet_unet/section_depth/* | head -1) echo ${model} # try running the test script python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug -- job: hrnet_dutchf3 +- job: hrnet_dutchf3_patch dependsOn: setup timeoutInMinutes: 5 - displayName: hrnet dutchf3 + displayName: hrnet dutchf3 patch pool: name: deepseismicagentpool steps: - bash: | source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug + cd experiments/interpretation/dutchf3_patch/local + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/hrnet.yaml --debug # find the latest model which we just trained - model=$(ls -td */seg_hrnet/*/* | head -1) + model=$(ls -td output/hrnet/section_depth/* | head -1) echo ${model} # try running the test script python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug - - job: unet_dutchf3 dependsOn: setup timeoutInMinutes: 5 @@ -117,10 +126,13 @@ jobs: - bash: | source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug + cd experiments/interpretation/dutchf3_patch/local + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/unet.yaml --debug # find the latest model which we just trained - model=$(ls -td */resnet_unet/*/* | head -1) + model=$(ls -td output/unet/section_depth/* | head -1) echo ${model} # try running the test script python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/unet.yaml --debug @@ -135,10 +147,13 @@ jobs: - bash: | source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug + cd experiments/interpretation/dutchf3_patch/local + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/seresnet_unet.yaml --debug # find the latest model which we just trained - model=$(ls -td */resnet_unet/*/* | head -1) + model=$(ls -td output/seresnet_unet/section_depth/* | head -1) # try running the test script python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug @@ -152,10 +167,13 @@ jobs: - bash: | source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.BATCH_SIZE_PER_GPU' 1 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet.yaml --debug + cd experiments/interpretation/dutchf3_patch/local + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet.yaml --debug # find the latest model which we just trained - model=$(ls -td */patch_deconvnet/*/* | head -1) + model=$(ls -td output/patch_deconvnet/no_depth/* | head -1) # try running the test script python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_1.pth --cfg=configs/patch_deconvnet.yaml --debug @@ -170,9 +188,12 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.BATCH_SIZE_PER_GPU' 1 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet_skip.yaml --debug + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet_skip.yaml --debug # find the latest model which we just trained - model=$(ls -td */patch_deconvnet_skip/*/* | head -1) + model=$(ls -td output/patch_deconvnet_skip/no_depth/* | head -1) # try running the test script python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_skip_running_model_1.pth --cfg=configs/patch_deconvnet_skip.yaml --debug @@ -191,61 +212,82 @@ jobs: - bash: | source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/hrnet.yaml --debug + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ + 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/hrnet.yaml --debug -- job: patch_deconvnet_skip_dist +- job: unet_dutchf3_dist dependsOn: setup timeoutInMinutes: 5 - displayName: patch deconvnet skip distributed + displayName: unet dutchf3 distributed pool: name: deepseismicagentpool steps: - bash: | source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'TRAIN.BATCH_SIZE_PER_GPU' 1 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet_skip.yaml --debug + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ + 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/unet.yaml --debug -- job: patch_deconvnet_dist +- job: seresnet_unet_dutchf3_dist dependsOn: setup timeoutInMinutes: 5 - displayName: patch deconvnet distributed + displayName: seresnet unet dutchf3 distributed pool: name: deepseismicagentpool steps: - bash: | source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'TRAIN.BATCH_SIZE_PER_GPU' 1 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/patch_deconvnet.yaml --debug + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ + 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/seresnet_unet.yaml --debug -- job: seresnet_unet_dutchf3_dist +- job: patch_deconvnet_dutchf3_dist dependsOn: setup timeoutInMinutes: 5 - displayName: seresnet unet dutchf3 distributed + displayName: patch deconvnet dutchf3 distributed pool: name: deepseismicagentpool steps: - bash: | source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/seresnet_unet.yaml --debug + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ + 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet.yaml --debug -- job: unet_dutchf3_dist +- job: patch_deconvnet_skip_dutchf3_dist dependsOn: setup timeoutInMinutes: 5 - displayName: unet dutchf3 distributed + displayName: patch deconvnet skip dutchf3 distributed pool: name: deepseismicagentpool steps: - bash: | source activate seismic-interpretation # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/unet.yaml --debug - + cd experiments/interpretation/dutchf3_patch/distributed + python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ + 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet_skip.yaml --debug + ################################################################################################### # LOCAL SECTION JOBS ################################################################################################### @@ -261,9 +303,12 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_section/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 --cfg=configs/section_deconvnet_skip.yaml --debug + python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/section_deconvnet_skip.yaml --debug # find the latest model which we just trained - model=$(ls -td */section_deconvnet_skip/*/* | head -1) + model=$(ls -td output/section_deconvnet_skip/no_depth/* | head -1) echo ${model} # try running the test script python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/section_deconvnet_skip_running_model_1.pth --cfg=configs/section_deconvnet_skip.yaml --debug From 1e10ac31deea76604f009448e5ba191cf4aac9cc Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Thu, 23 Jan 2020 11:38:43 -0500 Subject: [PATCH 158/207] notebook enhancements from sharatsc (#153) * added sharat, weehyong to authors * adding a download script for Dutch F3 dataset * Adding script instructions for dutch f3 * Update README.md prepare scripts expect root level directory for dutch f3 dataset. (it is downloaded into $dir/data by the script) * added scripts which reproduce results * Adding readme text for the notebooks and checking if config is correctly setup * build error fix * fixed notebook build error * removed scripts from older pull * fixing pip version for DS VM bug fix * fixing pip version for DS VM bug fix * notebook fixes Co-authored-by: Sharat Chikkerur --- README.md | 1 - environment/anaconda/local/environment.yml | 2 +- examples/interpretation/README.md | 6 ++++- .../HRNet_Penobscot_demo_notebook.ipynb | 25 +++++++++++++------ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2586caea..6cbc048f 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,6 @@ python scripts/prepare_dutchf3.py split_train_val section --data-dir=$data_dir/d # For patch-based experiments python scripts/prepare_dutchf3.py split_train_val patch --data-dir=$data_dir/data --stride=50 --patch=100 - ``` Refer to the script itself for more argument options. diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 40812d9f..a8079d76 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -4,7 +4,7 @@ channels: - pytorch dependencies: - python=3.6.7 - - pip + - pip=19.0 - pytorch==1.4.0 - cudatoolkit==10.1.243 - jupyter diff --git a/examples/interpretation/README.md b/examples/interpretation/README.md index 7f151c60..2706244f 100644 --- a/examples/interpretation/README.md +++ b/examples/interpretation/README.md @@ -1 +1,5 @@ -Description of examples +The repository contains the following notebook examples +* [Dutch F3 dataset](notebooks/F3_block_training_and_evaluation_local.ipynb): This notebook illustrates section and patch based segmentation approaches on the [Dutch F3](https://terranubis.com/datainfo/Netherlands-Offshore-F3-Block-Complete) open dataset. + +* [Penobscot dataset](notebooks/HRNet_Penobscot_demo_notebook.ipynb): +This notebook illustrates the use of HRNet based segmentation algorithm on the [Penobscot](https://terranubis.com/datainfo/Penobscot) dataset diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index cf6366fd..8274d966 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -31,10 +31,10 @@ "source": [ "## Environment setup\n", "\n", - "To set up the conda environment, please follow the instructions in the top-level [README.md](../../../README.md) file.\n", - "\n", - "__Note__: To register the conda environment in Jupyter, run:\n", - "`python -m ipykernel install --user --name envname`\n" + "* *Conda enviornment*: To set up the conda environment, please follow the instructions in the top-level [README.md](../../../README.md) file. To register the conda environment in Jupyter, run:\n", + "`python -m ipykernel install --user --name envname`\n", + "* *Dataset* : Please download the dataset using the [download script](../../scripts/download_penobscot.sh) and execute the [data preparation script](../../scripts/prepare_penobscot.py) to prepare te data for training and evaluations. Finally, update the [config file](configs/hrnet.yaml) (DATA.ROOT variable) to reflect where the data is stored. \n", + "* *Pre-trained model*: Please download the HRNET model pre-trained on ImageNet data as per instructions in [README.md](../../../README.md) file. Update the [config file](configs/hrnet.yaml) (MODEL.PRETRAINED variable) to reflect the path where the model snapshot is stored. Alternatively, you can leave the variable empty to start training from scratch. \n" ] }, { @@ -178,6 +178,17 @@ "dataset_root = config.DATASET.ROOT" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# make sure data location exists and we're specifying a pre-trained model\n", + "assert path.exists(dataset_root) , \"Path defined in DATASET.ROOT:%s does not exist\"%(dataset_root)\n", + "assert (not config.MODEL.PRETRAINED or path.exists(config.MODEL.PRETRAINED)), \"Model pre-trained path has to be empty or should exist: %s\"%(config.MODEL.PRETRAINED) " + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -230,6 +241,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "## Visualization\n", "Let's visualize the dataset." ] }, @@ -358,9 +370,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "lines_to_next_cell": 2 - }, + "metadata": {}, "outputs": [], "source": [ "train_set = PenobscotInlinePatchDataset(\n", @@ -577,6 +587,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "## Training\n", "Start the training engine run." ] }, From 1074a9318b72a559396774ba6bf282ea1300753a Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 24 Jan 2020 17:55:50 +0000 Subject: [PATCH 159/207] Fixes a few typos, and links to the troubleshooting section when running the conda command --- README.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 2586caea..ca9d1021 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ This repository shows you how to perform seismic imaging and interpretation on Azure. It empowers geophysicists and data scientists to run seismic experiments using state-of-art DSL-based PDE solvers and segmentation algorithms on Azure. -The repository provides sample notebooks, data loaders for seismic data, utilities, and out-of-the box ML pipelines, organized as follows: +The repository provides sample notebooks, data loaders for seismic data, utilities, and out-of-the-box ML pipelines, organized as follows: - **sample notebooks**: these can be found in the `examples` folder - they are standard Jupyter notebooks which highlight how to use the codebase by walking the user through a set of pre-made examples -- **experiments**: the goal is to provide runnable Python scripts which train and test (score) our machine learning models in `experiments` folder. The models themselves are swappable, meaning a single train script can be used to run a different model on the same dataset by simply swapping out the configuration file which defines the model. Experiments are organized by model types and datasets - for example, "2D segmentation on Dutch F3 dataset", "2D segmentation on Penobscot dataset" and "3D segmentation on Penobscot dataset" are all different experiments. As another example, if one is swapping 2D segmentation models on Dutch F3 dataset, one would just point the train and test scripts to a different configuration file within the same experiment. +- **experiments**: the goal is to provide runnable Python scripts that train and test (score) our machine learning models in the `experiments` folder. The models themselves are swappable, meaning a single train script can be used to run a different model on the same dataset by simply swapping out the configuration file which defines the model. Experiments are organized by model types and datasets - for example, "2D segmentation on Dutch F3 dataset", "2D segmentation on Penobscot dataset" and "3D segmentation on Penobscot dataset" are all different experiments. As another example, if one is swapping 2D segmentation models on the Dutch F3 dataset, one would just point the train and test scripts to a different configuration file within the same experiment. - **pip installable utilities**: we provide `cv_lib` and `deepseismic_interpretation` utilities (more info below) which are used by both sample notebooks and experiments mentioned above DeepSeismic currently focuses on Seismic Interpretation (3D segmentation aka facies classification) with experimental code provided around Seismic Imaging. @@ -14,7 +14,7 @@ DeepSeismic currently focuses on Seismic Interpretation (3D segmentation aka fac There are two ways to get started with the DeepSeismic codebase, which currently focuses on Interpretation: - if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/master/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) -- to actually run the code, you'll need to set up a compute environment (which includes setting up a GPU-enabled Linux VM and downloading the appropriate Anaconda Python packages) and download the datasets which you'd like to work with - detailed steps for doing this are provided in the next `Interpretation` section below. +- to run the code, you'll need to set up a compute environment (which includes setting up a GPU-enabled Linux VM and downloading the appropriate Anaconda Python packages) and download the datasets which you'd like to work with - detailed steps for doing this are provided in the next `Interpretation` section below. If you run into any problems, chances are your problem has already been solved in the [Troubleshooting](#troubleshooting) section. @@ -37,7 +37,7 @@ To run examples available on the repo, please follow instructions below to: ### Setting up Environment -Follow the instruction bellow to read about compute requirements and install required libraries. +Follow the instructions below to read about compute requirements and install required libraries. #### Compute environment @@ -55,9 +55,9 @@ To install packages contained in this repository, navigate to the directory wher ```bash conda env create -f environment/anaconda/local/environment.yml ``` -This will create the appropriate conda environment to run experiments. +This will create the appropriate conda environment to run experiments. If you run into problems with this step, see the [troubleshooting section](#Troubleshooting). -Next you will need to install the common package for interpretation: +Next, you will need to install the common package for interpretation: ```bash conda activate seismic-interpretation pip install -e interpretation @@ -196,9 +196,9 @@ We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manag To achieve the same results as the benchmarks above you will need to download the HRNet model [pretrained](https://github.com/HRNet/HRNet-Image-Classification) on ImageNet. We are specifically using the [HRNet-W48-C](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk) pre-trained model; other HRNet variants are also available [here](https://github.com/HRNet/HRNet-Image-Classification) - you can navigate to those from the [main HRNet landing page](https://github.com/HRNet/HRNet-Object-Detection) for object detection. -Unfortunately the OneDrive location which is used to host the model is using a temporary authentication token, so there is no way for us to scipt up model download. There are two ways to upload and use the pre-trained HRNet model on DS VM: +Unfortunately, the OneDrive location which is used to host the model is using a temporary authentication token, so there is no way for us to script up model download. There are two ways to upload and use the pre-trained HRNet model on DS VM: - download the model to your local drive using a web browser of your choice and then upload the model to the DS VM using something like `scp`; navigate to Portal and copy DS VM's public IP from the Overview panel of your DS VM (you can search your DS VM by name in the search bar of the Portal) then use `scp local_model_location username@DS_VM_public_IP:./model/save/path` to upload -- alternatively you can use the same public IP to open remote desktop over SSH to your Linux VM using [X2Go](https://wiki.x2go.org/doku.php/download:start): you can basically open the web browser on your VM this way and download the model to VM's disk +- alternatively, you can use the same public IP to open remote desktop over SSH to your Linux VM using [X2Go](https://wiki.x2go.org/doku.php/download:start): you can basically open the web browser on your VM this way and download the model to VM's disk ### Viewers (optional) @@ -228,7 +228,7 @@ segyviewer "${HOME}/data/dutchf3/data.segy" This section contains benchmarks of different algorithms for seismic interpretation on 3D seismic datasets with densely-annotated data. -Below are the results from the models contained in this repo. To run them check the instructions in folder. Alternatively take a look in for how to run them on your own dataset +Below are the results from the models contained in this repo. To run them check the instructions in folder. Alternatively, take a look in for how to run them on your own dataset #### Netherlands F3 @@ -243,7 +243,7 @@ Below are the results from the models contained in this repo. To run them check #### Penobscot -Trained and tested on full dataset. Inlines with artefacts were left in for training, validation and testing. +Trained and tested on the full dataset. Inlines with artifacts were left in for training, validation and testing. The dataset was split 70% training, 10% validation and 20% test. The results below are from the test set | Source | Experiment | PA | IoU | MCA | @@ -255,8 +255,7 @@ The dataset was split 70% training, 10% validation and 20% test. The results bel ![Worst Penobscot SEResNet](assets/penobscot_seresnet_worst.png "Worst performing inlines Mask and Predictions from SEResNet") #### Reproduce benchmarks -In order to reproduce the benchmarks you will need to navigate to the [experiments](experiments) folder. In there each of the experiments -are split into different folders. To run the Netherlands F3 experiment navigate to the [dutchf3_patch/local](experiments/dutchf3_patch/local) folder. In there is a training script [([train.sh](experiments/dutchf3_patch/local/train.sh)) +In order to reproduce the benchmarks, you will need to navigate to the [experiments](experiments) folder. In there, each of the experiments are split into different folders. To run the Netherlands F3 experiment navigate to the [dutchf3_patch/local](experiments/dutchf3_patch/local) folder. In there is a training script [([train.sh](experiments/dutchf3_patch/local/train.sh)) which will run the training for any configuration you pass in. Once you have run the training you will need to run the [test.sh](experiments/dutchf3_patch/local/test.sh) script. Make sure you specify the path to the best performing model from your training run, either by passing it in as an argument or altering the YACS config file. @@ -303,7 +302,7 @@ A typical output will be: someusername@somevm:/projects/DeepSeismic$ which python /anaconda/envs/py35/bin/python ``` -which will indicate that anaconda folder is __/anaconda__. We'll refer to this location in instructions below, but you should update the commands according to your local anaconda folder. +which will indicate that anaconda folder is __/anaconda__. We'll refer to this location in the instructions below, but you should update the commands according to your local anaconda folder.
Data Science Virtual Machine conda package installation errors @@ -339,13 +338,13 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l
Model training or scoring is not using GPU - To see if GPU is being using while your model is being trained or used for inference, run + To see if GPU is being used while your model is being trained or used for inference, run ```bash nvidia-smi ``` - and confirm that you see you Python process using the GPU. + and confirm that you see your Python process using the GPU. - If not, you may want to try reverting to an older version of CUDA for use with pyTorch. After the environment has been setup, run the following command (by default we use CUDA 10) after running `conda activate seismic-interpretation` to activate the conda environment: + If not, you may want to try reverting to an older version of CUDA for use with PyTorch. After the environment has been set up, run the following command (by default we use CUDA 10) after running `conda activate seismic-interpretation` to activate the conda environment: ```bash conda install pytorch torchvision cudatoolkit=9.2 -c pytorch ``` @@ -366,7 +365,7 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l torch.cuda.is_available() ``` - Output should say "True" this time. If it does, you can make the change permanent by adding + The output should say "True" this time. If it does, you can make the change permanent by adding ```bash export CUDA_VISIBLE_DEVICES=0 ``` @@ -381,7 +380,7 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l ```bash nvidia-smi ``` - and seeing if this amount is close to the physical memory limit specified by the GPU manufacturer. + and see if this amount is close to the physical memory limit specified by the GPU manufacturer. If we're getting close to the memory limit, you may want to lower the batch size in the model configuration file. Specifically, `TRAIN.BATCH_SIZE_PER_GPU` and `VALIDATION.BATCH_SIZE_PER_GPU` settings. @@ -392,11 +391,11 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l 1. Go to the [Azure Portal](https://portal.azure.com) and find your virtual machine by typing its name in the search bar at the very top of the page. - 2. In the Overview panel on the left hand side, click Stop button to stop the virtual machine. + 2. In the Overview panel on the left-hand side, click the Stop button to stop the virtual machine. - 3. Next, select Disks in the same panel on the left hand side. + 3. Next, select Disks in the same panel on the left-hand side. - 4. Click the Name of the OS Disk - you'll be navigated to the Disk view. From this view, select Configuration on the left hand side and then increase Size in GB and hit the Save button. + 4. Click the Name of the OS Disk - you'll be navigated to the Disk view. From this view, select Configuration on the left-hand side and then increase Size in GB and hit the Save button. 5. Navigate back to the Virtual Machine view in Step 2 and click the Start button to start the virtual machine. From e7c07387e2857a603b4a928f3f0ced43db0ecdcb Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Fri, 24 Jan 2020 14:08:42 -0600 Subject: [PATCH 160/207] Readme update Fixes a few typos, and links to the troubleshooting section when running the conda command (#160) --- README.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6cbc048f..fa527c1a 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ This repository shows you how to perform seismic imaging and interpretation on Azure. It empowers geophysicists and data scientists to run seismic experiments using state-of-art DSL-based PDE solvers and segmentation algorithms on Azure. -The repository provides sample notebooks, data loaders for seismic data, utilities, and out-of-the box ML pipelines, organized as follows: +The repository provides sample notebooks, data loaders for seismic data, utilities, and out-of-the-box ML pipelines, organized as follows: - **sample notebooks**: these can be found in the `examples` folder - they are standard Jupyter notebooks which highlight how to use the codebase by walking the user through a set of pre-made examples -- **experiments**: the goal is to provide runnable Python scripts which train and test (score) our machine learning models in `experiments` folder. The models themselves are swappable, meaning a single train script can be used to run a different model on the same dataset by simply swapping out the configuration file which defines the model. Experiments are organized by model types and datasets - for example, "2D segmentation on Dutch F3 dataset", "2D segmentation on Penobscot dataset" and "3D segmentation on Penobscot dataset" are all different experiments. As another example, if one is swapping 2D segmentation models on Dutch F3 dataset, one would just point the train and test scripts to a different configuration file within the same experiment. +- **experiments**: the goal is to provide runnable Python scripts that train and test (score) our machine learning models in the `experiments` folder. The models themselves are swappable, meaning a single train script can be used to run a different model on the same dataset by simply swapping out the configuration file which defines the model. Experiments are organized by model types and datasets - for example, "2D segmentation on Dutch F3 dataset", "2D segmentation on Penobscot dataset" and "3D segmentation on Penobscot dataset" are all different experiments. As another example, if one is swapping 2D segmentation models on the Dutch F3 dataset, one would just point the train and test scripts to a different configuration file within the same experiment. - **pip installable utilities**: we provide `cv_lib` and `deepseismic_interpretation` utilities (more info below) which are used by both sample notebooks and experiments mentioned above DeepSeismic currently focuses on Seismic Interpretation (3D segmentation aka facies classification) with experimental code provided around Seismic Imaging. @@ -14,7 +14,7 @@ DeepSeismic currently focuses on Seismic Interpretation (3D segmentation aka fac There are two ways to get started with the DeepSeismic codebase, which currently focuses on Interpretation: - if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/master/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) -- to actually run the code, you'll need to set up a compute environment (which includes setting up a GPU-enabled Linux VM and downloading the appropriate Anaconda Python packages) and download the datasets which you'd like to work with - detailed steps for doing this are provided in the next `Interpretation` section below. +- to run the code, you'll need to set up a compute environment (which includes setting up a GPU-enabled Linux VM and downloading the appropriate Anaconda Python packages) and download the datasets which you'd like to work with - detailed steps for doing this are provided in the next `Interpretation` section below. If you run into any problems, chances are your problem has already been solved in the [Troubleshooting](#troubleshooting) section. @@ -37,7 +37,7 @@ To run examples available on the repo, please follow instructions below to: ### Setting up Environment -Follow the instruction bellow to read about compute requirements and install required libraries. +Follow the instructions below to read about compute requirements and install required libraries. #### Compute environment @@ -55,9 +55,9 @@ To install packages contained in this repository, navigate to the directory wher ```bash conda env create -f environment/anaconda/local/environment.yml ``` -This will create the appropriate conda environment to run experiments. +This will create the appropriate conda environment to run experiments. If you run into problems with this step, see the [troubleshooting section](#Troubleshooting). -Next you will need to install the common package for interpretation: +Next, you will need to install the common package for interpretation: ```bash conda activate seismic-interpretation pip install -e interpretation @@ -195,9 +195,9 @@ We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manag To achieve the same results as the benchmarks above you will need to download the HRNet model [pretrained](https://github.com/HRNet/HRNet-Image-Classification) on ImageNet. We are specifically using the [HRNet-W48-C](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk) pre-trained model; other HRNet variants are also available [here](https://github.com/HRNet/HRNet-Image-Classification) - you can navigate to those from the [main HRNet landing page](https://github.com/HRNet/HRNet-Object-Detection) for object detection. -Unfortunately the OneDrive location which is used to host the model is using a temporary authentication token, so there is no way for us to scipt up model download. There are two ways to upload and use the pre-trained HRNet model on DS VM: +Unfortunately, the OneDrive location which is used to host the model is using a temporary authentication token, so there is no way for us to script up model download. There are two ways to upload and use the pre-trained HRNet model on DS VM: - download the model to your local drive using a web browser of your choice and then upload the model to the DS VM using something like `scp`; navigate to Portal and copy DS VM's public IP from the Overview panel of your DS VM (you can search your DS VM by name in the search bar of the Portal) then use `scp local_model_location username@DS_VM_public_IP:./model/save/path` to upload -- alternatively you can use the same public IP to open remote desktop over SSH to your Linux VM using [X2Go](https://wiki.x2go.org/doku.php/download:start): you can basically open the web browser on your VM this way and download the model to VM's disk +- alternatively, you can use the same public IP to open remote desktop over SSH to your Linux VM using [X2Go](https://wiki.x2go.org/doku.php/download:start): you can basically open the web browser on your VM this way and download the model to VM's disk ### Viewers (optional) @@ -227,7 +227,7 @@ segyviewer "${HOME}/data/dutchf3/data.segy" This section contains benchmarks of different algorithms for seismic interpretation on 3D seismic datasets with densely-annotated data. -Below are the results from the models contained in this repo. To run them check the instructions in folder. Alternatively take a look in for how to run them on your own dataset +Below are the results from the models contained in this repo. To run them check the instructions in folder. Alternatively, take a look in for how to run them on your own dataset #### Netherlands F3 @@ -242,7 +242,7 @@ Below are the results from the models contained in this repo. To run them check #### Penobscot -Trained and tested on full dataset. Inlines with artefacts were left in for training, validation and testing. +Trained and tested on the full dataset. Inlines with artifacts were left in for training, validation and testing. The dataset was split 70% training, 10% validation and 20% test. The results below are from the test set | Source | Experiment | PA | IoU | MCA | @@ -254,8 +254,7 @@ The dataset was split 70% training, 10% validation and 20% test. The results bel ![Worst Penobscot SEResNet](assets/penobscot_seresnet_worst.png "Worst performing inlines Mask and Predictions from SEResNet") #### Reproduce benchmarks -In order to reproduce the benchmarks you will need to navigate to the [experiments](experiments) folder. In there each of the experiments -are split into different folders. To run the Netherlands F3 experiment navigate to the [dutchf3_patch/local](experiments/dutchf3_patch/local) folder. In there is a training script [([train.sh](experiments/dutchf3_patch/local/train.sh)) +In order to reproduce the benchmarks, you will need to navigate to the [experiments](experiments) folder. In there, each of the experiments are split into different folders. To run the Netherlands F3 experiment navigate to the [dutchf3_patch/local](experiments/dutchf3_patch/local) folder. In there is a training script [([train.sh](experiments/dutchf3_patch/local/train.sh)) which will run the training for any configuration you pass in. Once you have run the training you will need to run the [test.sh](experiments/dutchf3_patch/local/test.sh) script. Make sure you specify the path to the best performing model from your training run, either by passing it in as an argument or altering the YACS config file. @@ -302,7 +301,7 @@ A typical output will be: someusername@somevm:/projects/DeepSeismic$ which python /anaconda/envs/py35/bin/python ``` -which will indicate that anaconda folder is __/anaconda__. We'll refer to this location in instructions below, but you should update the commands according to your local anaconda folder. +which will indicate that anaconda folder is __/anaconda__. We'll refer to this location in the instructions below, but you should update the commands according to your local anaconda folder.
Data Science Virtual Machine conda package installation errors @@ -338,13 +337,13 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l
Model training or scoring is not using GPU - To see if GPU is being using while your model is being trained or used for inference, run + To see if GPU is being used while your model is being trained or used for inference, run ```bash nvidia-smi ``` - and confirm that you see you Python process using the GPU. + and confirm that you see your Python process using the GPU. - If not, you may want to try reverting to an older version of CUDA for use with pyTorch. After the environment has been setup, run the following command (by default we use CUDA 10) after running `conda activate seismic-interpretation` to activate the conda environment: + If not, you may want to try reverting to an older version of CUDA for use with PyTorch. After the environment has been set up, run the following command (by default we use CUDA 10) after running `conda activate seismic-interpretation` to activate the conda environment: ```bash conda install pytorch torchvision cudatoolkit=9.2 -c pytorch ``` @@ -365,7 +364,7 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l torch.cuda.is_available() ``` - Output should say "True" this time. If it does, you can make the change permanent by adding + The output should say "True" this time. If it does, you can make the change permanent by adding ```bash export CUDA_VISIBLE_DEVICES=0 ``` @@ -380,7 +379,7 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l ```bash nvidia-smi ``` - and seeing if this amount is close to the physical memory limit specified by the GPU manufacturer. + and see if this amount is close to the physical memory limit specified by the GPU manufacturer. If we're getting close to the memory limit, you may want to lower the batch size in the model configuration file. Specifically, `TRAIN.BATCH_SIZE_PER_GPU` and `VALIDATION.BATCH_SIZE_PER_GPU` settings. @@ -391,11 +390,11 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l 1. Go to the [Azure Portal](https://portal.azure.com) and find your virtual machine by typing its name in the search bar at the very top of the page. - 2. In the Overview panel on the left hand side, click Stop button to stop the virtual machine. + 2. In the Overview panel on the left-hand side, click the Stop button to stop the virtual machine. - 3. Next, select Disks in the same panel on the left hand side. + 3. Next, select Disks in the same panel on the left-hand side. - 4. Click the Name of the OS Disk - you'll be navigated to the Disk view. From this view, select Configuration on the left hand side and then increase Size in GB and hit the Save button. + 4. Click the Name of the OS Disk - you'll be navigated to the Disk view. From this view, select Configuration on the left-hand side and then increase Size in GB and hit the Save button. 5. Navigate back to the Virtual Machine view in Step 2 and click the Start button to start the virtual machine. From a629e5e9bb547c68076d3f0d75939e51858aa4f6 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 27 Jan 2020 11:11:20 -0500 Subject: [PATCH 161/207] scripts to reproduce model results (#155) * added scripts which reproduce results * build error fix * modified all local training runs to use model_dir for model name * extended model naming to distributed setup as well * added pillow breakage fix too * removing execution scripts from this PR * upgrading pytorch version to keep up with torchvision to keep up with Pillow * initial checkin of the run scripts to reproduce results * edited version of run_all to run all jobs to match presentation/github results * fixed typos in main run_all launch script * final version of the scripts which reproduce repo results * added README description which reproduces the results --- README.md | 5 ++ scripts/run_all.sh | 109 ++++++++++++++++++++++++++ scripts/run_distributed.sh | 59 +++++++++++++++ scripts/test_all.sh | 151 +++++++++++++++++++++++++++++++++++++ 4 files changed, 324 insertions(+) create mode 100755 scripts/run_all.sh create mode 100755 scripts/run_distributed.sh create mode 100755 scripts/test_all.sh diff --git a/README.md b/README.md index fa527c1a..2ab1817b 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,11 @@ for the Penobscot dataset follow the same instructions but navigate to the [peno - [kill_windows.sh](scripts/kill_windows.sh): Script to kill multiple tmux windows. Used to kill jobs that parallel_training.sh might have started. +- [run_all.sh](scripts/run_all.sh): similar to `parallel_training.sh` above, provides a multiprocess execution on an ND40 VM with 8 GPUs. Designed to work with `test_all.sh` script below. Trains 8 models concurrently. + +- [run_distributed.sh](scripts/run_distributed.sh): sequentially launches distributed training jobs, which should produce the same results on Dutch F3 dataset with patch based methods as the single-GPU training (just takes less time per model to train). Also designed to work with `test_all.sh` script below. + +- [test_all.sh](scripts/test_all.sh): after running `run_all.sh` and `run_distributed.sh` scripts above, this script scores single-GPU-trained and multi-GPU-trained models in the repo to reproduce the results given in the table. ## Contributing diff --git a/scripts/run_all.sh b/scripts/run_all.sh new file mode 100755 index 00000000..5ed0bbfc --- /dev/null +++ b/scripts/run_all.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +# specify pretrained HRNet backbone +PRETRAINED_HRNET='/home/maxkaz/models/hrnetv2_w48_imagenet_pretrained.pth' +# DATA_F3='/home/alfred/data/dutch_f3/data' +# DATA_PENOBSCOT='/home/maxkaz/data/penobscot' +DATA_F3='/storage/data/dutchf3/data' +DATA_PENOBSCOT='/storage/data/penobscot' +# subdirectory where results are written +OUTPUT_DIR='output' + +# bug to fix conda not launching from a bash shell +source /data/anaconda/etc/profile.d/conda.sh +conda activate seismic-interpretation + +cd experiments/interpretation/penobscot/local + +# Penobscot seresnet unet with section depth +export CUDA_VISIBLE_DEVICES=0 +nohup time python train.py \ + 'DATASET.ROOT' "${DATA_PENOBSCOT}" \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg "configs/seresnet_unet.yaml" > seresnet_unet.log 2>&1 & +# wait for python to pick up the runtime env before switching it +sleep 1 + +# Penobscot hrnet with section depth +export CUDA_VISIBLE_DEVICES=1 +nohup time python train.py \ + 'DATASET.ROOT' "${DATA_PENOBSCOT}" \ + 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/hrnet.yaml > hrnet.log 2>&1 & +# wait for python to pick up the runtime env before switching it +sleep 1 + +cd ../../dutchf3_patch/local + +# patch based without skip connections +export CUDA_VISIBLE_DEVICES=2 +nohup time python train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet.yaml > patch_deconvnet.log 2>&1 & +# wait for python to pick up the runtime env before switching it +sleep 1 + +# patch based with skip connections +export CUDA_VISIBLE_DEVICES=3 +nohup time python train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet_skip.yaml > patch_deconvnet_skip.log 2>&1 & +# wait for python to pick up the runtime env before switching it +sleep 1 + +# squeeze excitation resnet unet + section depth +export CUDA_VISIBLE_DEVICES=4 +nohup time python train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/seresnet_unet.yaml > seresnet_unet.log 2>&1 & +# wait for python to pick up the runtime env before switching it +sleep 1 + +# HRNet + patch depth +export CUDA_VISIBLE_DEVICES=5 +nohup time python train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'patch' \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'patch_depth' \ + --cfg=configs/hrnet.yaml > hrnet_patch.log 2>&1 & +# wait for python to pick up the runtime env before switching it +sleep 1 + +# HRNet + section depth +export CUDA_VISIBLE_DEVICES=6 +nohup time python train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/hrnet.yaml > hrnet_section.log 2>&1 & +# wait for python to pick up the runtime env before switching it +sleep 1 + +cd ../../dutchf3_section/local + +# and finally do a section-based model for comparison +# (deconv with skip connections and no depth) +export CUDA_VISIBLE_DEVICES=7 +nohup time python train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/section_deconvnet_skip.yaml > section_deconvnet_skip.log 2>&1 & +# wait for python to pick up the runtime env before switching it +sleep 1 + +unset CUDA_VISIBLE_DEVICES + +echo "LAUNCHED ALL LOCAL JOBS" + diff --git a/scripts/run_distributed.sh b/scripts/run_distributed.sh new file mode 100755 index 00000000..80f24bae --- /dev/null +++ b/scripts/run_distributed.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# number of GPUs to train on +NGPU=8 +# specify pretrained HRNet backbone +PRETRAINED_HRNET='/home/maxkaz/models/hrnetv2_w48_imagenet_pretrained.pth' +# DATA_F3='/home/alfred/data/dutch_f3/data' +# DATA_PENOBSCOT='/home/maxkaz/data/penobscot' +DATA_F3='/storage/data/dutchf3/data' +DATA_PENOBSCOT='/storage/data/penobscot' +# subdirectory where results are written +OUTPUT_DIR='output' + +unset CUDA_VISIBLE_DEVICES +# bug to fix conda not launching from a bash shell +source /data/anaconda/etc/profile.d/conda.sh +conda activate seismic-interpretation +export PYTHONPATH=/storage/repos/forks/seismic-deeplearning-1/interpretation:$PYTHONPATH + +cd experiments/interpretation/dutchf3_patch/distributed/ + +# patch based without skip connections +nohup time python -m torch.distributed.launch --nproc_per_node=${NGPU} train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet.yaml > patch_deconvnet.log 2>&1 + +# patch based with skip connections +nohup time python -m torch.distributed.launch --nproc_per_node=${NGPU} train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet_skip.yaml > patch_deconvnet_skip.log 2>&1 + +# squeeze excitation resnet unet + section depth +nohup time python -m torch.distributed.launch --nproc_per_node=${NGPU} train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/seresnet_unet.yaml > seresnet_unet.log 2>&1 + +# HRNet + patch depth +nohup time python -m torch.distributed.launch --nproc_per_node=${NGPU} train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'patch' \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'patch_depth' \ + --cfg=configs/hrnet.yaml > hrnet_patch.log 2>&1 + +# HRNet + section depth +nohup time python -m torch.distributed.launch --nproc_per_node=${NGPU} train.py \ + 'DATASET.ROOT' "${DATA_F3}" \ + 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ + 'OUTPUT_DIR' "${OUTPUT_DIR}" 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/hrnet.yaml > hrnet_section.log 2>&1 + +echo "TADA" diff --git a/scripts/test_all.sh b/scripts/test_all.sh new file mode 100755 index 00000000..23607c19 --- /dev/null +++ b/scripts/test_all.sh @@ -0,0 +1,151 @@ +#!/bin/bash + +# DATA_F3='/home/alfred/data/dutch_f3/data' +# DATA_PENOBSCOT='/home/maxkaz/data/penobscot' +DATA_F3='/storage/data/dutchf3/data' +DATA_PENOBSCOT='/storage/data/penobscot' +# name of your git branch which you ran the training code from +BRANCH="maxkaz/names" +# name of directory where results are kept +OUTPUT_DIR="output" + +# bug to fix conda not launching from a bash shell +source /data/anaconda/etc/profile.d/conda.sh +conda activate seismic-interpretation + +cd experiments/interpretation/penobscot/local + +# Penobscot seresnet unet with section depth +export CUDA_VISIBLE_DEVICES=0 +CONFIG_NAME='seresnet_unet' +model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & +sleep 1 + +# Penobscot hrnet with section depth +export CUDA_VISIBLE_DEVICES=1 +CONFIG_NAME='hrnet' +model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & +sleep 1 + +cd ../../dutchf3_patch/local + +# patch based without skip connections +export CUDA_VISIBLE_DEVICES=2 +CONFIG_NAME='patch_deconvnet' +model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & +sleep 1 + +# patch based with skip connections +export CUDA_VISIBLE_DEVICES=3 +CONFIG_NAME='patch_deconvnet_skip' +model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & +sleep 1 + +# squeeze excitation resnet unet + section depth +export CUDA_VISIBLE_DEVICES=4 +CONFIG_NAME='seresnet_unet' +model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & +sleep 1 + +# HRNet + patch depth +export CUDA_VISIBLE_DEVICES=5 +CONFIG_NAME='hrnet' +model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/patch_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & +sleep 1 + +# HRNet + section depth +export CUDA_VISIBLE_DEVICES=6 +CONFIG_NAME='hrnet' +model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & +sleep 1 + +cd ../../dutchf3_section/local + +# and finally do a section-based model for comparison +# (deconv with skip connections and no depth) +export CUDA_VISIBLE_DEVICES=7 +CONFIG_NAME='section_deconvnet_skip' +model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & +sleep 1 + +echo "Waiting for all local runs to finish" +wait + +# scoring scripts are in the local folder +# models are in the distributed folder +cd ../../dutchf3_patch/local + +# patch based without skip connections +export CUDA_VISIBLE_DEVICES=2 +CONFIG_NAME='patch_deconvnet' +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & +sleep 1 + +# patch based with skip connections +export CUDA_VISIBLE_DEVICES=3 +CONFIG_NAME='patch_deconvnet_skip' +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & +sleep 1 + +# squeeze excitation resnet unet + section depth +export CUDA_VISIBLE_DEVICES=4 +CONFIG_NAME='seresnet_unet' +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & +sleep 1 + +# HRNet + patch depth +export CUDA_VISIBLE_DEVICES=5 +CONFIG_NAME='hrnet' +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/patch_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & +sleep 1 + +# HRNet + section depth +export CUDA_VISIBLE_DEVICES=6 +CONFIG_NAME='hrnet' +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +nohup time python test.py \ + 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & +sleep 1 + +echo "Waiting for all distributed runs to finish" + +wait + +echo "TADA" From a74677a95b38b6e054e44272ec60f5c8f0d8a7b9 Mon Sep 17 00:00:00 2001 From: Sharat Chikkerur Date: Thu, 30 Jan 2020 13:26:40 -0500 Subject: [PATCH 162/207] Fix data path in the README (#167) * added sharat, weehyong to authors * adding a download script for Dutch F3 dataset * Adding script instructions for dutch f3 * Update README.md prepare scripts expect root level directory for dutch f3 dataset. (it is downloaded into $dir/data by the script) * Adding readme text for the notebooks and checking if config is correctly setup * fixing prepare script example Co-authored-by: maxkazmsft --- README.md | 2 +- .../notebooks/HRNet_Penobscot_demo_notebook.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2ab1817b..a17a383b 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ To make things easier, we suggested you use your home directory where you might To prepare the data for the experiments (e.g. split into train/val/test), please run the following script (modifying arguments as desired): ``` -python scripts/prepare_penobscot.py split_inline --data-dir="$HOME/data/penobscot" --val-ratio=.1 --test-ratio=.2 +python scripts/prepare_penobscot.py split_inline --data-dir=$data_dir --val-ratio=.1 --test-ratio=.2 ``` #### F3 Netherlands diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 8274d966..95f0b9b8 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -662,4 +662,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From f592344b20b54ecffd12f02bc6d7cc3326789e35 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Thu, 30 Jan 2020 17:44:31 -0600 Subject: [PATCH 163/207] Update F3_block_training_and_evaluation_local.ipynb (#163) Minor fix to figure axes Co-authored-by: maxkazmsft --- .../notebooks/F3_block_training_and_evaluation_local.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 320f40a3..b2c54194 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -369,7 +369,7 @@ "x_in = train_seismic[idx, :, :].swapaxes(0, 1)\n", "x_inl = train_labels[idx, :, :].swapaxes(0, 1)\n", "\n", - "plot_aline(x_in, x_inl, xlabel=\"inline\")" + "plot_aline(x_in, x_inl, xlabel=\"crossline (relative)\")" ] }, { @@ -401,7 +401,7 @@ "x_cr = train_seismic[:, idx, :].swapaxes(0, 1)\n", "x_crl = train_labels[:, idx, :].swapaxes(0, 1)\n", "\n", - "plot_aline(x_cr, x_crl, xlabel=\"crossline\")" + "plot_aline(x_cr, x_crl, xlabel=\"inline (relative)\")" ] }, { From da9311358ec4aab8d60f1f0d234e3c22502d1850 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Fri, 31 Jan 2020 13:02:55 -0500 Subject: [PATCH 164/207] Maxkaz/test fixes (#168) * added sharat, weehyong to authors * adding a download script for Dutch F3 dataset * Adding script instructions for dutch f3 * Update README.md prepare scripts expect root level directory for dutch f3 dataset. (it is downloaded into $dir/data by the script) * added scripts which reproduce results * Adding readme text for the notebooks and checking if config is correctly setup * build error fix * fixed notebook build error * removed scripts from older pull * fixing pip version for DS VM bug fix * fixing pip version for DS VM bug fix * notebook fixes * fixes to benchmark test script * explicitly setting backbone model location in tests * fixed test links in the README * addressed PR comments Co-authored-by: Sharat Chikkerur --- README.md | 8 ++--- scripts/run_all.sh | 13 ++++--- scripts/test_all.sh | 75 ++++++++++++++++++++++++++++----------- tests/cicd/main_build.yml | 10 ++++-- 4 files changed, 75 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index a17a383b..bb94a38e 100644 --- a/README.md +++ b/README.md @@ -289,10 +289,10 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | --- | --- | --- | | **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=124&branchName=staging) | | **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=124&branchName=master) | -| **Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=staging) | -| **Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=master) | -| **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=staging) | -| **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=master) | +| **Core Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=staging) | +| **Core Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=master) | +| **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=staging) | +| **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=master) | # Troubleshooting diff --git a/scripts/run_all.sh b/scripts/run_all.sh index 5ed0bbfc..82a26cf7 100755 --- a/scripts/run_all.sh +++ b/scripts/run_all.sh @@ -1,11 +1,14 @@ #!/bin/bash +# specify absolute locations to your models and data +MODEL_ROOT="/your/model/root" +DATA_ROOT="/your/data/root" + # specify pretrained HRNet backbone -PRETRAINED_HRNET='/home/maxkaz/models/hrnetv2_w48_imagenet_pretrained.pth' -# DATA_F3='/home/alfred/data/dutch_f3/data' -# DATA_PENOBSCOT='/home/maxkaz/data/penobscot' -DATA_F3='/storage/data/dutchf3/data' -DATA_PENOBSCOT='/storage/data/penobscot' +PRETRAINED_HRNET="${MODEL_ROOT}/hrnetv2_w48_imagenet_pretrained.pth" +DATA_F3="${DATA_ROOT}/dutchf3/data" +DATA_PENOBSCOT="${DATA_ROOT}/penobscot" + # subdirectory where results are written OUTPUT_DIR='output' diff --git a/scripts/test_all.sh b/scripts/test_all.sh index 23607c19..a715de34 100755 --- a/scripts/test_all.sh +++ b/scripts/test_all.sh @@ -1,14 +1,31 @@ #!/bin/bash -# DATA_F3='/home/alfred/data/dutch_f3/data' -# DATA_PENOBSCOT='/home/maxkaz/data/penobscot' -DATA_F3='/storage/data/dutchf3/data' -DATA_PENOBSCOT='/storage/data/penobscot' +# specify absolute locations to your models, data and storage +MODEL_ROOT="/your/model/root" +DATA_ROOT="/your/data/root" +STORAGE_ROOT="/your/storage/root" + +# specify pretrained HRNet backbone +PRETRAINED_HRNET="${MODEL_ROOT}/hrnetv2_w48_imagenet_pretrained.pth" +DATA_F3="${DATA_ROOT}/dutchf3/data" +DATA_PENOBSCOT="${DATA_ROOT}/penobscot" # name of your git branch which you ran the training code from -BRANCH="maxkaz/names" +BRANCH="your/git/branch/with/slashes/if/they/exist/in/branch/name" + # name of directory where results are kept OUTPUT_DIR="output" +# directory where to copy pre-trained models to +OUTPUT_PRETRAINED="${STORAGE_ROOT}/pretrained_models/" + +if [ -d ${OUTPUT_PRETRAINED} ]; then + echo "erasing pre-trained models in ${OUTPUT_PRETRAINED}" + rm -rf "${OUTPUT_PRETRAINED}" +fi + +mkdir -p "${OUTPUT_PRETRAINED}" +echo "Pre-trained models will be copied to ${OUTPUT_PRETRAINED}" + # bug to fix conda not launching from a bash shell source /data/anaconda/etc/profile.d/conda.sh conda activate seismic-interpretation @@ -19,8 +36,9 @@ cd experiments/interpretation/penobscot/local export CUDA_VISIBLE_DEVICES=0 CONFIG_NAME='seresnet_unet' model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/penobscot_seresnetunet_patch_section_depth.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_PENOBSCOT}" 'TEST.MODEL_PATH' "${model}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & sleep 1 @@ -28,8 +46,10 @@ sleep 1 export CUDA_VISIBLE_DEVICES=1 CONFIG_NAME='hrnet' model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/penobscot_hrnet_patch_section_depth.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_PENOBSCOT}" 'TEST.MODEL_PATH' "${model}" \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & sleep 1 @@ -39,8 +59,9 @@ cd ../../dutchf3_patch/local export CUDA_VISIBLE_DEVICES=2 CONFIG_NAME='patch_deconvnet' model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_deconvnet_patch_no_depth.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & sleep 1 @@ -48,8 +69,9 @@ sleep 1 export CUDA_VISIBLE_DEVICES=3 CONFIG_NAME='patch_deconvnet_skip' model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_deconvnetskip_patch_no_depth.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & sleep 1 @@ -57,8 +79,9 @@ sleep 1 export CUDA_VISIBLE_DEVICES=4 CONFIG_NAME='seresnet_unet' model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_seresnetunet_patch_section_depth.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & sleep 1 @@ -66,18 +89,22 @@ sleep 1 export CUDA_VISIBLE_DEVICES=5 CONFIG_NAME='hrnet' model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/patch_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_hrnet_patch_patch_depth.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ - --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_patch_test.log 2>&1 & sleep 1 # HRNet + section depth export CUDA_VISIBLE_DEVICES=6 CONFIG_NAME='hrnet' model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_hrnet_patch_section_depth.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ - --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ + --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_section_test.log 2>&1 & sleep 1 cd ../../dutchf3_section/local @@ -87,8 +114,9 @@ cd ../../dutchf3_section/local export CUDA_VISIBLE_DEVICES=7 CONFIG_NAME='section_deconvnet_skip' model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_deconvnetskip_patch_no_depth.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & sleep 1 @@ -103,8 +131,9 @@ cd ../../dutchf3_patch/local export CUDA_VISIBLE_DEVICES=2 CONFIG_NAME='patch_deconvnet' model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_deconvnet_patch_no_depth_distributed.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & sleep 1 @@ -112,8 +141,9 @@ sleep 1 export CUDA_VISIBLE_DEVICES=3 CONFIG_NAME='patch_deconvnet_skip' model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_deconvnetskip_patch_no_depth_distributed.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & sleep 1 @@ -121,8 +151,9 @@ sleep 1 export CUDA_VISIBLE_DEVICES=4 CONFIG_NAME='seresnet_unet' model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_seresnetunet_patch_section_depth_distributed.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & sleep 1 @@ -130,8 +161,10 @@ sleep 1 export CUDA_VISIBLE_DEVICES=5 CONFIG_NAME='hrnet' model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/patch_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_hrnet_patch_patch_depth_distributed.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & sleep 1 @@ -139,8 +172,10 @@ sleep 1 export CUDA_VISIBLE_DEVICES=6 CONFIG_NAME='hrnet' model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +cp $model ${OUTPUT_PRETRAINED}/dutchf3_hrnet_patch_section_depth_distributed.pth nohup time python test.py \ - 'DATASET.ROOT' "${DATASET_ROOT}" 'TEST.MODEL_PATH' "${model}" \ + 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ + 'MODEL.PRETRAINED' "${PRETRAINED_HRNET}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_distributed_test.log 2>&1 & sleep 1 diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 1b3f39de..4eb57a61 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -70,7 +70,10 @@ jobs: model=$(ls -td output/hrnet/section_depth/* | head -1) echo ${model} # # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug + python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ + --cfg=configs/hrnet.yaml --debug - job: seresnet_unet_penobscot dependsOn: setup @@ -114,7 +117,10 @@ jobs: model=$(ls -td output/hrnet/section_depth/* | head -1) echo ${model} # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth --cfg=configs/hrnet.yaml --debug + python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ + --cfg=configs/hrnet.yaml --debug - job: unet_dutchf3 dependsOn: setup From 283173bb90c8304741e417a249089a9a2700e818 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 3 Feb 2020 13:26:01 -0500 Subject: [PATCH 165/207] add data tests for download and preprocessing; resolve preprocessing bugs (#175) * added sharat, weehyong to authors * adding a download script for Dutch F3 dataset * Adding script instructions for dutch f3 * Update README.md prepare scripts expect root level directory for dutch f3 dataset. (it is downloaded into $dir/data by the script) * added scripts which reproduce results * Adding readme text for the notebooks and checking if config is correctly setup * build error fix * fixed notebook build error * removed scripts from older pull * fixing pip version for DS VM bug fix * fixing pip version for DS VM bug fix * notebook fixes * fixes to benchmark test script * explicitly setting backbone model location in tests * addressed PR comments * added dataset download and preprocessing tests * changed data dir to not break master, added separate data prep script for builds * modified README to reflect code changes; added license header * adding fixes to data download script for the builds * forgot to ass the readme fix to data preprocessing script * fixes Co-authored-by: Sharat Chikkerur --- README.md | 27 +++++----- .../dutchf3_section/local/logging.conf | 3 ++ scripts/download_dutch_f3.sh | 1 + tests/cicd/main_build.yml | 54 +++++++++++-------- tests/cicd/notebooks_build.yml | 16 ++++-- tests/cicd/src/scripts/get_data_for_builds.sh | 43 +++++++++++++++ 6 files changed, 106 insertions(+), 38 deletions(-) create mode 100755 tests/cicd/src/scripts/get_data_for_builds.sh diff --git a/README.md b/README.md index bb94a38e..767cb66c 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,9 @@ To make things easier, we suggested you use your home directory where you might To prepare the data for the experiments (e.g. split into train/val/test), please run the following script (modifying arguments as desired): ``` -python scripts/prepare_penobscot.py split_inline --data-dir=$data_dir --val-ratio=.1 --test-ratio=.2 +cd scripts +python prepare_penobscot.py split_inline --data-dir=$data_dir --val-ratio=.1 --test-ratio=.2 +cd .. ``` #### F3 Netherlands @@ -112,18 +114,13 @@ To download the F3 Netherlands dataset for 2D experiments, please follow the dat ``` data_dir="$HOME/data/dutch" -mkdir -p "$data_dir" -./scripts/download_dutch_f3.sh "$data_dir" +mkdir -p "${data_dir}" +./scripts/download_dutch_f3.sh "${data_dir}" ``` -Once you've downloaded the data set, make sure to create an empty `splits` directory, under the downloaded `data` directory; you can re-use the same data directory as the one for Penobscot dataset created earlier. This is where your training/test/validation splits will be saved. - -``` -cd data -mkdir splits -``` +Download scripts also automatically create any subfolders in `${data_dir}` which are needed for the data preprocessing scripts. -At this point, your `data` directory tree should look like this: +At this point, your `${data_dir}` directory should contain a `data` folder, which should look like this: ``` data @@ -141,12 +138,18 @@ data To prepare the data for the experiments (e.g. split into train/val/test), please run the following script: ``` +# change working directory to scripts folder +cd scripts + # For section-based experiments -python scripts/prepare_dutchf3.py split_train_val section --data-dir=$data_dir/data +python prepare_dutchf3.py split_train_val section --data-dir=${data_dir}/data # For patch-based experiments -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=$data_dir/data --stride=50 --patch=100 +python prepare_dutchf3.py split_train_val patch --data-dir=${data_dir}/data --stride=50 --patch=100 + +# go back to repo root +cd .. ``` Refer to the script itself for more argument options. diff --git a/experiments/interpretation/dutchf3_section/local/logging.conf b/experiments/interpretation/dutchf3_section/local/logging.conf index 56334fc4..a67fb7a2 100644 --- a/experiments/interpretation/dutchf3_section/local/logging.conf +++ b/experiments/interpretation/dutchf3_section/local/logging.conf @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + [loggers] keys=root,__main__,event_handlers diff --git a/scripts/download_dutch_f3.sh b/scripts/download_dutch_f3.sh index 2298961b..ec41ff1b 100755 --- a/scripts/download_dutch_f3.sh +++ b/scripts/download_dutch_f3.sh @@ -22,4 +22,5 @@ openssl dgst -md5 $temp_file # Unzip the data unzip -d $1 $temp_file && rm $temp_file +mkdir $1/data/splits echo "Download complete" diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 4eb57a61..4e0f145c 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -20,17 +20,26 @@ jobs: name: deepseismicagentpool steps: - bash: | + # terminate as soon as any internal script fails + set -e + echo "Running setup..." pwd ls git branch uname -ra - ./scripts/env_reinstall.sh + ./scripts/env_reinstall.sh + # use hardcoded root for now because not sure how env changes under ADO policy + DATA_ROOT="/home/alfred/data_dynamic" + + ./tests/cicd/src/scripts/get_data_for_builds.sh ${DATA_ROOT} + # copy your model files like so - using dummy file to illustrate azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name + - job: unit_tests_job dependsOn: setup timeoutInMinutes: 5 @@ -61,7 +70,7 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/penobscot/local - python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ @@ -70,7 +79,7 @@ jobs: model=$(ls -td output/hrnet/section_depth/* | head -1) echo ${model} # # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' \ + python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ --cfg=configs/hrnet.yaml --debug @@ -87,7 +96,7 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/penobscot/local - python train.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/seresnet_unet.yaml --debug @@ -95,7 +104,7 @@ jobs: model=$(ls -td output/seresnet_unet/section_depth/* | head -1) echo ${model} # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/penobscot' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug + python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug - job: hrnet_dutchf3_patch dependsOn: setup @@ -108,7 +117,7 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ @@ -117,7 +126,7 @@ jobs: model=$(ls -td output/hrnet/section_depth/* | head -1) echo ${model} # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' \ + python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ --cfg=configs/hrnet.yaml --debug @@ -133,7 +142,7 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/unet.yaml --debug @@ -141,7 +150,7 @@ jobs: model=$(ls -td output/unet/section_depth/* | head -1) echo ${model} # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/unet.yaml --debug + python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/unet.yaml --debug - job: seresnet_unet_dutchf3 dependsOn: setup @@ -154,14 +163,14 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/seresnet_unet.yaml --debug # find the latest model which we just trained model=$(ls -td output/seresnet_unet/section_depth/* | head -1) # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug + python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug - job: patch_deconvnet_dutchf3 dependsOn: setup @@ -174,14 +183,14 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ --cfg=configs/patch_deconvnet.yaml --debug # find the latest model which we just trained model=$(ls -td output/patch_deconvnet/no_depth/* | head -1) # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_1.pth --cfg=configs/patch_deconvnet.yaml --debug + python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_1.pth --cfg=configs/patch_deconvnet.yaml --debug - job: patch_deconvnet_skip_dutchf3 dependsOn: setup @@ -194,14 +203,14 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ --cfg=configs/patch_deconvnet_skip.yaml --debug # find the latest model which we just trained model=$(ls -td output/patch_deconvnet_skip/no_depth/* | head -1) # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_skip_running_model_1.pth --cfg=configs/patch_deconvnet_skip.yaml --debug + python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_skip_running_model_1.pth --cfg=configs/patch_deconvnet_skip.yaml --debug ################################################################################################### @@ -220,7 +229,7 @@ jobs: # run the tests cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ - 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ @@ -238,7 +247,7 @@ jobs: # run the tests cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ - 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/unet.yaml --debug @@ -255,7 +264,7 @@ jobs: # run the tests cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ - 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/seresnet_unet.yaml --debug @@ -272,7 +281,7 @@ jobs: # run the tests cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ - 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ --cfg=configs/patch_deconvnet.yaml --debug @@ -289,7 +298,7 @@ jobs: # run the tests cd experiments/interpretation/dutchf3_patch/distributed python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ - 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ --cfg=configs/patch_deconvnet_skip.yaml --debug @@ -309,7 +318,7 @@ jobs: source activate seismic-interpretation # run the tests cd experiments/interpretation/dutchf3_section/local - python train.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ --cfg=configs/section_deconvnet_skip.yaml --debug @@ -317,5 +326,6 @@ jobs: model=$(ls -td output/section_deconvnet_skip/no_depth/* | head -1) echo ${model} # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/section_deconvnet_skip_running_model_1.pth --cfg=configs/section_deconvnet_skip.yaml --debug + python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/section_deconvnet_skip_running_model_1.pth --cfg=configs/section_deconvnet_skip.yaml --debug + diff --git a/tests/cicd/notebooks_build.yml b/tests/cicd/notebooks_build.yml index b1cb1d00..48a7c9a3 100644 --- a/tests/cicd/notebooks_build.yml +++ b/tests/cicd/notebooks_build.yml @@ -22,14 +22,22 @@ jobs: steps: - bash: | + # terminate as soon as any internal script fails + set -e + echo "Running setup..." pwd ls git branch uname -ra - + ./scripts/env_reinstall.sh - + + # use hardcoded root for now because not sure how env changes under ADO policy + DATA_ROOT="/home/alfred/data_dynamic" + + ./tests/cicd/src/scripts/get_data_for_builds.sh ${DATA_ROOT} + # copy your model files like so - using dummy file to illustrate azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name @@ -42,7 +50,7 @@ jobs: steps: - bash: | source activate seismic-interpretation - pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb --dataset_root /home/alfred/data/penobscot + pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb --dataset_root /home/alfred/data_dynamic/penobscot - job: F3_block_training_and_evaluation_local dependsOn: setup @@ -53,4 +61,4 @@ jobs: steps: - bash: | source activate seismic-interpretation - pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb --dataset_root /home/alfred/data/dutch_f3/data + pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb --dataset_root /home/alfred/data_dynamic/dutch_f3/data diff --git a/tests/cicd/src/scripts/get_data_for_builds.sh b/tests/cicd/src/scripts/get_data_for_builds.sh new file mode 100755 index 00000000..e9d498bd --- /dev/null +++ b/tests/cicd/src/scripts/get_data_for_builds.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Downloads and prepares the data for the rest of the builds to use + +# Download the Dutch F3 dataset and extract +if [ -z $1 ]; then + echo "You need to specify a download location for the data" + exit 1; +fi; + +DATA_ROOT=$1 + +source activate seismic-interpretation + +# these have to match the rest of the build jobs unless we want to +# define this in ADO pipelines +DATA_F3="${DATA_ROOT}/dutch_f3" +DATA_PENOBSCOT="${DATA_ROOT}/penobscot" + +# remove data +if [ -d ${DATA_ROOT} ]; then + echo "Erasing data root dir ${DATA_ROOT}" + rm -rf "${DATA_ROOT}" +fi +mkdir -p "${DATA_F3}" +mkdir -p "${DATA_PENOBSCOT}" + +# test download scripts in parallel +./scripts/download_penobscot.sh "${DATA_PENOBSCOT}" & +./scripts/download_dutch_f3.sh "${DATA_F3}" & + +wait + +# change imposed by download script +DATA_F3="${DATA_F3}/data" + +# test preprocessing scripts +cd scripts +python prepare_penobscot.py split_inline --data-dir=${DATA_PENOBSCOT} --val-ratio=.1 --test-ratio=.2 +python prepare_dutchf3.py split_train_val section --data-dir=${DATA_F3} +python prepare_dutchf3.py split_train_val patch --data-dir=${DATA_F3} --stride=50 --patch=100 From 1b24b50039f0234be06d75a74d549033ad0c463b Mon Sep 17 00:00:00 2001 From: Sharat Chikkerur Date: Tue, 4 Feb 2020 13:22:53 -0500 Subject: [PATCH 166/207] Adding content to interpretation README (#171) * added sharat, weehyong to authors * adding a download script for Dutch F3 dataset * Adding script instructions for dutch f3 * Update README.md prepare scripts expect root level directory for dutch f3 dataset. (it is downloaded into $dir/data by the script) * Adding readme text for the notebooks and checking if config is correctly setup * fixing prepare script example * Adding more content to interpretation README * Update README.md * Update HRNet_Penobscot_demo_notebook.ipynb Co-authored-by: maxkazmsft --- examples/interpretation/README.md | 9 ++++++--- .../notebooks/HRNet_Penobscot_demo_notebook.ipynb | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/interpretation/README.md b/examples/interpretation/README.md index 2706244f..01bc2dff 100644 --- a/examples/interpretation/README.md +++ b/examples/interpretation/README.md @@ -1,5 +1,8 @@ -The repository contains the following notebook examples -* [Dutch F3 dataset](notebooks/F3_block_training_and_evaluation_local.ipynb): This notebook illustrates section and patch based segmentation approaches on the [Dutch F3](https://terranubis.com/datainfo/Netherlands-Offshore-F3-Block-Complete) open dataset. +The folder contains notebook examples illustrating the use of segmentation algorithms on openly available datasets. Make sure you have followed the [set up instructions](../README.md) before running these examples. We provide the following notebook examples + +* [Dutch F3 dataset](notebooks/F3_block_training_and_evaluation_local.ipynb): This notebook illustrates section and patch based segmentation approaches on the [Dutch F3](https://terranubis.com/datainfo/Netherlands-Offshore-F3-Block-Complete) open dataset. This notebook uses denconvolution based segmentation algorithm on 2D patches. The notebook will guide you through visualization of the input volume, setting up model training and evaluation. + * [Penobscot dataset](notebooks/HRNet_Penobscot_demo_notebook.ipynb): -This notebook illustrates the use of HRNet based segmentation algorithm on the [Penobscot](https://terranubis.com/datainfo/Penobscot) dataset +In this notebook, we demonstrate how to train an [HRNet](https://github.com/HRNet/HRNet-Semantic-Segmentation) model for facies prediction using [Penobscot](https://terranubis.com/datainfo/Penobscot) dataset. The Penobscot 3D seismic dataset was acquired in the Scotian shelf, offshore Nova Scotia, Canada. This notebook illustrates the use of HRNet based segmentation algorithm on the dataset. Details of HRNet based model can be found [here](https://arxiv.org/abs/1904.04514) + diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 95f0b9b8..8274d966 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -662,4 +662,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From e93d75d5619d1f21ca67dfee84b221fc6e3adadf Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Wed, 5 Feb 2020 11:33:32 -0600 Subject: [PATCH 167/207] added utility to validate paths --- .../notebooks/configs/unet.yaml | 2 +- .../interpretation/notebooks/utilities.py | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml index d1efc6b5..f6787fc5 100644 --- a/examples/interpretation/notebooks/configs/unet.yaml +++ b/examples/interpretation/notebooks/configs/unet.yaml @@ -48,7 +48,7 @@ TRAIN: MODEL_DIR: "models" TEST: - MODEL_PATH: "" + # MODEL_PATH: TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index 0ef72f02..54091dec 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -2,11 +2,13 @@ # Licensed under the MIT License. import itertools +from os import path import matplotlib.pyplot as plt import numpy as np import torch import torch.nn.functional as F +import yacs from ignite.utils import convert_tensor from scipy.ndimage import zoom from toolz import compose, curry, itertoolz, pipe @@ -240,3 +242,38 @@ def plot_aline(aline, labels, xlabel, ylabel="depth"): plt.imshow(labels) plt.xlabel(xlabel) plt.title("Label") + +def validate_config_paths(config): + """Checks that all paths in the config file are valid""" + # TODO: this is currently hardcoded, in the future, its better to have a more generic solution. + + # Make sure DATASET.ROOT directory exist: + assert path.isdir(config.DATASET.ROOT), f"The DATASET.ROOT specified in the config file is not a \ + valid directory. Please make sure this path is correct: {config.DATASET.ROOT}" + + # if a pretrained model path is specified in the config, it should exist: + if hasattr(config, MODEL.PRETRAINED): + assert path.exists(config.MODEL.PRETRAINED), f"A pretrained model is specified in the config \ + file but does not exist. Please make sure this path is correct: {config.MODEL.PRETRAINED}" + + # if a test model path is specified in the config, it should exist: + if hasattr(config, TEST.MODEL_PATH): + assert path.exists(config.TEST.MODEL_PATH), f"The TEST.MODEL_PATH specified in the config \ + file does not exist. Please make sure this path is correct: {config.TEST.MODEL_PATH}" + + # Furthermore, if this is a HRNet model, the pretrained model path should exist if the test model is specified: + if "hrnet" in config.MODEL.NAME: + if hasattr(config, TEST.MODEL_PATH): + assert path.exists(config.MODEL.PRETRAINED), "For an HRNet model, you should specify the MODEL.PRETRAINED \ + path in the config file if the TEST.MODEL_PATH is also specified." + + # Finally, make sure these directories exist or create them: + if not path.isdir(config.OUTPUT_DIR): + os.makedirs(config.OUTPUT_DIR) + + if not path.isdir(config.LOG_DIR): + os.makedirs(config.LOG_DIR) + + if not path.isdir(config.MODEL_DIR): + os.makedirs(config.MODEL_DIR) + From b554e2fc00ad6624e801d5d444a7d311b4598d2e Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Wed, 5 Feb 2020 13:33:51 -0600 Subject: [PATCH 168/207] better handling of AttributeErrors --- .../interpretation/notebooks/utilities.py | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index 54091dec..1b42519c 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. import itertools -from os import path +import os import matplotlib.pyplot as plt import numpy as np @@ -247,33 +247,50 @@ def validate_config_paths(config): """Checks that all paths in the config file are valid""" # TODO: this is currently hardcoded, in the future, its better to have a more generic solution. + # define the home directory + home = os.path.expanduser('~') + # Make sure DATASET.ROOT directory exist: - assert path.isdir(config.DATASET.ROOT), f"The DATASET.ROOT specified in the config file is not a \ - valid directory. Please make sure this path is correct: {config.DATASET.ROOT}" + assert os.path.isdir(os.path.join(home, config.DATASET.ROOT)), f"The DATASET.ROOT specified in the config file is not a \ +valid directory. Please make sure this path is correct: {config.DATASET.ROOT}" # if a pretrained model path is specified in the config, it should exist: - if hasattr(config, MODEL.PRETRAINED): - assert path.exists(config.MODEL.PRETRAINED), f"A pretrained model is specified in the config \ - file but does not exist. Please make sure this path is correct: {config.MODEL.PRETRAINED}" + try: + assert os.path.isfile(config.MODEL.PRETRAINED), f"A pretrained model is specified in the config \ +file but does not exist. Please make sure this path is correct: {config.MODEL.PRETRAINED}" + except AttributeError: + pass # if a test model path is specified in the config, it should exist: - if hasattr(config, TEST.MODEL_PATH): - assert path.exists(config.TEST.MODEL_PATH), f"The TEST.MODEL_PATH specified in the config \ - file does not exist. Please make sure this path is correct: {config.TEST.MODEL_PATH}" + try: + assert os.path.isfile(config.TEST.MODEL_PATH), f"The TEST.MODEL_PATH specified in the config \ +file does not exist. Please make sure this path is correct: {config.TEST.MODEL_PATH}" + except AttributeError: + pass # Furthermore, if this is a HRNet model, the pretrained model path should exist if the test model is specified: if "hrnet" in config.MODEL.NAME: - if hasattr(config, TEST.MODEL_PATH): - assert path.exists(config.MODEL.PRETRAINED), "For an HRNet model, you should specify the MODEL.PRETRAINED \ - path in the config file if the TEST.MODEL_PATH is also specified." + try: + assert os.path.isfile(config.MODEL.PRETRAINED), "For an HRNet model, you should specify the MODEL.PRETRAINED \ +path in the config file if the TEST.MODEL_PATH is also specified." + except AttributeError: + pass # Finally, make sure these directories exist or create them: - if not path.isdir(config.OUTPUT_DIR): - os.makedirs(config.OUTPUT_DIR) - - if not path.isdir(config.LOG_DIR): - os.makedirs(config.LOG_DIR) - - if not path.isdir(config.MODEL_DIR): - os.makedirs(config.MODEL_DIR) - + try: + if not os.path.isdir(config.OUTPUT_DIR): + os.makedirs(config.OUTPUT_DIR) + except AttributeError: + pass + + try: + if not os.path.isdir(config.LOG_DIR): + os.makedirs(config.LOG_DIR) + except AttributeError: + pass + + try: + if not os.path.isdir(config.MODEL_DIR): + os.makedirs(config.MODEL_DIR) + except AttributeError: + pass \ No newline at end of file From f9f033a37d6c51ad5e59bd14bc95d0bcac15b353 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Wed, 5 Feb 2020 13:36:28 -0600 Subject: [PATCH 169/207] fixing paths in configs to match those in readme.md --- examples/interpretation/notebooks/configs/hrnet.yaml | 6 +++--- .../notebooks/configs/patch_deconvnet_skip.yaml | 4 ++-- examples/interpretation/notebooks/configs/unet.yaml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index c746d58c..28c9cd7f 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -14,7 +14,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 7 - ROOT: /mnt/penobscot + ROOT: "data/penobscot" CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] INLINE_HEIGHT: 1501 INLINE_WIDTH: 481 @@ -23,7 +23,7 @@ DATASET: MODEL: NAME: seg_hrnet IN_CHANNELS: 3 - PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + PRETRAINED: "models/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth" EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: @@ -99,7 +99,7 @@ VALIDATION: TEST: COMPLETE_PATCHES_ONLY: False - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + MODEL_PATH: "data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" AUGMENTATIONS: RESIZE: HEIGHT: 256 diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml index 52657ea0..39847104 100644 --- a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml +++ b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml @@ -12,7 +12,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /data/dutchf3 + ROOT: "data/dutch" CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: @@ -48,7 +48,7 @@ VALIDATION: BATCH_SIZE_PER_GPU: 512 TEST: - MODEL_PATH: '/data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth' + # MODEL_PATH: "data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth" TEST_STRIDE: 10 SPLIT: 'test1' # Can be both, test1, test2 INLINE: True diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml index f6787fc5..28b51b82 100644 --- a/examples/interpretation/notebooks/configs/unet.yaml +++ b/examples/interpretation/notebooks/configs/unet.yaml @@ -15,7 +15,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: '/data/dutchf3' + ROOT: "data/dutch" CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: From edcb9dc7e38d2704667bae6a8484c074186005f0 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Wed, 5 Feb 2020 13:36:50 -0600 Subject: [PATCH 170/207] minor formatting improvements --- examples/interpretation/notebooks/configs/hrnet.yaml | 4 ++-- .../notebooks/configs/patch_deconvnet_skip.yaml | 8 ++++---- examples/interpretation/notebooks/configs/unet.yaml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index 28c9cd7f..7096b1c5 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -3,8 +3,8 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'outputs' -LOG_DIR: 'log' +OUTPUT_DIR: "outputs" +LOG_DIR: "log" WORKERS: 4 PRINT_FREQ: 50 diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml index 39847104..225d47c3 100644 --- a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml +++ b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml @@ -3,8 +3,8 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' +OUTPUT_DIR: "output" +LOG_DIR: "log" WORKERS: 4 PRINT_FREQ: 50 LOG_CONFIG: logging.conf @@ -30,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" #"patch" # Options are No, Patch and Section + DEPTH: "none" # Options are No, Patch and Section STRIDE: 50 PATCH_SIZE: 99 AUGMENTATIONS: @@ -50,7 +50,7 @@ VALIDATION: TEST: # MODEL_PATH: "data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth" TEST_STRIDE: 10 - SPLIT: 'test1' # Can be both, test1, test2 + SPLIT: "test1" # Can be both, test1, test2 INLINE: True CROSSLINE: True POST_PROCESSING: diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml index 28b51b82..514a1b2b 100644 --- a/examples/interpretation/notebooks/configs/unet.yaml +++ b/examples/interpretation/notebooks/configs/unet.yaml @@ -5,8 +5,8 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' +OUTPUT_DIR: "output" +LOG_DIR: "log" WORKERS: 4 PRINT_FREQ: 50 LOG_CONFIG: logging.conf @@ -50,7 +50,7 @@ TRAIN: TEST: # MODEL_PATH: TEST_STRIDE: 10 - SPLIT: 'Both' # Can be Both, Test1, Test2 + SPLIT: "Both" # Can be Both, Test1, Test2 INLINE: True CROSSLINE: True POST_PROCESSING: From 09387da2b80df21fe046f21911356b02f2ab1676 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Wed, 5 Feb 2020 13:39:06 -0600 Subject: [PATCH 171/207] add validate_config_paths to notebooks --- .../notebooks/F3_block_training_and_evaluation_local.ipynb | 3 ++- .../notebooks/HRNet_Penobscot_demo_notebook.ipynb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index b2c54194..834bc341 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -107,6 +107,7 @@ " output_processing_pipeline,\n", " write_section_file,\n", " runningScore,\n", + " validate_config_paths\n", ")\n", "\n", "# set device\n", @@ -1021,4 +1022,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 95f0b9b8..088793ee 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -96,7 +96,7 @@ "from toolz import compose\n", "from torch.utils import data\n", "from itkwidgets import view\n", - "from utilities import plot_aline\n", + "from utilities import plot_aline, validate_config_paths\n", "from toolz import take\n", "\n", "\n", From ab7823f3dc59ee1e6ab4dba7715599e8c9b93123 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Thu, 6 Feb 2020 15:20:23 -0600 Subject: [PATCH 172/207] adding generic and absolute paths in the config + minor cleanup --- examples/interpretation/notebooks/configs/hrnet.yaml | 6 +++--- .../notebooks/configs/patch_deconvnet_skip.yaml | 8 ++++---- examples/interpretation/notebooks/configs/unet.yaml | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index 7096b1c5..00eeb4a9 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -14,7 +14,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 7 - ROOT: "data/penobscot" + ROOT: "/home/username/data/penobscot" CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] INLINE_HEIGHT: 1501 INLINE_WIDTH: 481 @@ -77,7 +77,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" #"patch" # Options are none, patch and section + DEPTH: "none" # Options are none, patch and section STRIDE: 64 PATCH_SIZE: 128 AUGMENTATIONS: @@ -99,7 +99,7 @@ VALIDATION: TEST: COMPLETE_PATCHES_ONLY: False - MODEL_PATH: "data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + MODEL_PATH: "/your/trained/model/path/model_name.pth" # must be an absolute path AUGMENTATIONS: RESIZE: HEIGHT: 256 diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml index 225d47c3..3199451c 100644 --- a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml +++ b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml @@ -12,7 +12,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: "data/dutch" + ROOT: "/home/username/data/dutch" CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: @@ -40,15 +40,15 @@ TRAIN: PAD: HEIGHT: 99 WIDTH: 99 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 + MEAN: 0.0009997 + STD: 0.20977 MODEL_DIR: "models" VALIDATION: BATCH_SIZE_PER_GPU: 512 TEST: - # MODEL_PATH: "data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth" + MODEL_PATH: "/your/trained/model/path/model_name.pth" # must be an absolute path TEST_STRIDE: 10 SPLIT: "test1" # Can be both, test1, test2 INLINE: True diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml index 514a1b2b..b42fc177 100644 --- a/examples/interpretation/notebooks/configs/unet.yaml +++ b/examples/interpretation/notebooks/configs/unet.yaml @@ -15,7 +15,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: "data/dutch" + ROOT: "/home/username/data/dutch" CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: @@ -43,12 +43,12 @@ TRAIN: PAD: HEIGHT: 256 WIDTH: 256 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 + MEAN: 0.0009997 + STD: 0.20977 MODEL_DIR: "models" TEST: - # MODEL_PATH: + MODEL_PATH: "/your/trained/model/path/model_name.pth" # must be an absolute path TEST_STRIDE: 10 SPLIT: "Both" # Can be Both, Test1, Test2 INLINE: True From 27bff84578a29b2a8cd37567bf660e7a8e6832f8 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Thu, 6 Feb 2020 15:51:37 -0600 Subject: [PATCH 173/207] better format for validate_config_paths() --- .../interpretation/notebooks/utilities.py | 59 ++++++------------- 1 file changed, 17 insertions(+), 42 deletions(-) diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index 1b42519c..bd90688f 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -247,50 +247,25 @@ def validate_config_paths(config): """Checks that all paths in the config file are valid""" # TODO: this is currently hardcoded, in the future, its better to have a more generic solution. - # define the home directory - home = os.path.expanduser('~') - # Make sure DATASET.ROOT directory exist: - assert os.path.isdir(os.path.join(home, config.DATASET.ROOT)), f"The DATASET.ROOT specified in the config file is not a \ -valid directory. Please make sure this path is correct: {config.DATASET.ROOT}" + assert os.path.isdir(config.DATASET.ROOT), \ + "The DATASET.ROOT specified in the config file is not a valid directory." \ + f" Please make sure this path is correct: {config.DATASET.ROOT}" # if a pretrained model path is specified in the config, it should exist: - try: - assert os.path.isfile(config.MODEL.PRETRAINED), f"A pretrained model is specified in the config \ -file but does not exist. Please make sure this path is correct: {config.MODEL.PRETRAINED}" - except AttributeError: - pass + if 'PRETRAINED' in config.MODEL.keys(): + assert os.path.isfile(config.MODEL.PRETRAINED), \ + "A pretrained model is specified in the config file but does not exist." \ + f" Please make sure this path is correct: {config.MODEL.PRETRAINED}" # if a test model path is specified in the config, it should exist: - try: - assert os.path.isfile(config.TEST.MODEL_PATH), f"The TEST.MODEL_PATH specified in the config \ -file does not exist. Please make sure this path is correct: {config.TEST.MODEL_PATH}" - except AttributeError: - pass - - # Furthermore, if this is a HRNet model, the pretrained model path should exist if the test model is specified: - if "hrnet" in config.MODEL.NAME: - try: - assert os.path.isfile(config.MODEL.PRETRAINED), "For an HRNet model, you should specify the MODEL.PRETRAINED \ -path in the config file if the TEST.MODEL_PATH is also specified." - except AttributeError: - pass - - # Finally, make sure these directories exist or create them: - try: - if not os.path.isdir(config.OUTPUT_DIR): - os.makedirs(config.OUTPUT_DIR) - except AttributeError: - pass - - try: - if not os.path.isdir(config.LOG_DIR): - os.makedirs(config.LOG_DIR) - except AttributeError: - pass - - try: - if not os.path.isdir(config.MODEL_DIR): - os.makedirs(config.MODEL_DIR) - except AttributeError: - pass \ No newline at end of file + if 'TEST' in config.keys(): + if 'MODEL_PATH' in config.TEST.keys(): + assert os.path.isfile(config.TEST.MODEL_PATH), \ + "The TEST.MODEL_PATH specified in the config file does not exist." \ + f" Please make sure this path is correct: {config.TEST.MODEL_PATH}" + # Furthermore, if this is a HRNet model, the pretrained model path should exist if the test model is specified: + if "hrnet" in config.MODEL.NAME: + assert os.path.isfile(config.MODEL.PRETRAINED), \ + "For an HRNet model, you should specify the MODEL.PRETRAINED path" \ + " in the config file if the TEST.MODEL_PATH is also specified." \ No newline at end of file From 944ef44d8201d80ece0269e68a3a0eca8c79e5aa Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Thu, 6 Feb 2020 15:52:29 -0600 Subject: [PATCH 174/207] added dummy path in hrnet config --- examples/interpretation/notebooks/configs/hrnet.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index 00eeb4a9..97bb7828 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -23,7 +23,7 @@ DATASET: MODEL: NAME: seg_hrnet IN_CHANNELS: 3 - PRETRAINED: "models/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth" + PRETRAINED: "/home/username/models/path/to/hrnetv2_w48_imagenet_pretrained.pth" EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: From 1964accb18bcb972dcc04bcc0b4a272be3401e9b Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Thu, 6 Feb 2020 15:57:08 -0600 Subject: [PATCH 175/207] modified HRNet notebook --- .../notebooks/HRNet_Penobscot_demo_notebook.ipynb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 088793ee..a00f37bc 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -146,7 +146,8 @@ "\n", "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", - "print(config)" + "print(config)\n", + "validate_config_paths(config)" ] }, { @@ -178,17 +179,6 @@ "dataset_root = config.DATASET.ROOT" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# make sure data location exists and we're specifying a pre-trained model\n", - "assert path.exists(dataset_root) , \"Path defined in DATASET.ROOT:%s does not exist\"%(dataset_root)\n", - "assert (not config.MODEL.PRETRAINED or path.exists(config.MODEL.PRETRAINED)), \"Model pre-trained path has to be empty or should exist: %s\"%(config.MODEL.PRETRAINED) " - ] - }, { "cell_type": "markdown", "metadata": {}, From 7c622625a10cf14d1fb6f11d0a8ba172a4700ac7 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Thu, 6 Feb 2020 16:06:57 -0600 Subject: [PATCH 176/207] added missing validate_config_paths() --- .../F3_block_training_and_evaluation_local.ipynb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 834bc341..2f8f25c8 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -234,7 +234,8 @@ "\n", "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", - "print(config)" + "print(config)\n", + "validate_config_paths(config)" ] }, { @@ -991,13 +992,6 @@ "_ = f_axes[0].set_title('Actual')\n", "_ = f_axes[1].set_title('Predicted') " ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 566e5ce684698c724df0c78554d7637bcfda652a Mon Sep 17 00:00:00 2001 From: kirasoderstrom Date: Mon, 10 Feb 2020 07:02:08 -0800 Subject: [PATCH 177/207] Updates to prepare dutchf3 (#185) * updating patch to patch_size when we are using it as an integer * modifying the range function in the prepare_dutchf3 script to get all of our data * updating path to logging.config so the script can locate it * manually reverting back log path to troubleshoot build tests * updating patch to patch_size for testing on preprocessing scripts * updating patch to patch_size where applicable in ablation.sh * reverting back changes on ablation.sh to validate build pass --- scripts/prepare_dutchf3.py | 31 ++++++++++--------- tests/cicd/src/scripts/get_data_for_builds.sh | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/scripts/prepare_dutchf3.py b/scripts/prepare_dutchf3.py index 2e9e57f9..be0a60eb 100644 --- a/scripts/prepare_dutchf3.py +++ b/scripts/prepare_dutchf3.py @@ -86,13 +86,13 @@ def split_section_train_val(data_dir, per_val=0.2, log_config=None): _write_split_files(splits_path, train_list, test_list, "section") -def split_patch_train_val(data_dir, stride, patch, per_val=0.2, log_config=None): +def split_patch_train_val(data_dir, stride, patch_size, per_val=0.2, log_config=None): """Generate train and validation files for Netherlands F3 dataset. Args: data_dir (str): data directory path stride (int): stride to use when sectioning of the volume - patch (int): size of patch to extract + patch_size (int): size of patch to extract per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. """ @@ -119,8 +119,8 @@ def split_patch_train_val(data_dir, stride, patch, per_val=0.2, log_config=None) # Generate patches from sections # Process inlines - horz_locations = range(0, xline - stride, stride) - vert_locations = range(0, depth - stride, stride) + horz_locations = range(0, xline - patch_size + 1, stride) + vert_locations = range(0, depth - patch_size + 1, stride) logger.debug("Generating Inline patches") logger.debug(horz_locations) logger.debug(vert_locations) @@ -138,8 +138,8 @@ def _i_extract_patches(iline_range, horz_locations, vert_locations): logger.debug(test_iline_range) # Process crosslines - horz_locations = range(0, iline - stride, stride) - vert_locations = range(0, depth - stride, stride) + horz_locations = range(0, iline - patch_size + 1, stride) + vert_locations = range(0, depth - patch_size + 1, stride) logger.debug("Generating Crossline patches") logger.debug(horz_locations) logger.debug(vert_locations) @@ -176,7 +176,7 @@ def run_split_func(loader_type, *args, **kwargs): split_func(*args, **kwargs) -def split_alaudah_et_al_19(data_dir, stride, fraction_validation=0.2, loader_type="patch", log_config=None): +def split_alaudah_et_al_19(data_dir, stride, patch_size, fraction_validation=0.2, loader_type="patch", log_config=None): """Generate train and validation files (with overlap) for Netherlands F3 dataset. The original split method from https://github.com/olivesgatech/facies_classification_benchmark DON'T USE, SEE NOTES BELOW @@ -184,6 +184,7 @@ def split_alaudah_et_al_19(data_dir, stride, fraction_validation=0.2, loader_typ Args: data_dir (str): data directory path stride (int): stride to use when sectioning of the volume + patch_size (int): size of patch to extract fraction_validation (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. loader_type (str, optional): type of data loader, can be "patch" or "section". @@ -222,8 +223,8 @@ def split_alaudah_et_al_19(data_dir, stride, fraction_validation=0.2, loader_typ x_list = ["x_" + str(x) for x in range(xline)] elif loader_type == "patch": i_list = [] - horz_locations = range(0, xline - stride, stride) - vert_locations = range(0, depth - stride, stride) + horz_locations = range(0, xline - patch_size + 1, stride) + vert_locations = range(0, depth - patch_size + 1, stride) logger.debug("Generating Inline patches") logger.debug(horz_locations) logger.debug(vert_locations) @@ -238,8 +239,8 @@ def split_alaudah_et_al_19(data_dir, stride, fraction_validation=0.2, loader_typ i_list = list(itertools.chain(*i_list)) x_list = [] - horz_locations = range(0, iline - stride, stride) - vert_locations = range(0, depth - stride, stride) + horz_locations = range(0, iline - patch_size + 1, stride) + vert_locations = range(0, depth - patch_size + 1, stride) for j in range(xline): # for every xline: # images are references by top-left corner: @@ -273,25 +274,25 @@ def section(self, data_dir, per_val=0.2, log_config="logging.conf"): """ return split_section_train_val(data_dir, per_val=per_val, log_config=log_config) - def patch(self, data_dir, stride, patch, per_val=0.2, log_config="logging.conf"): + def patch(self, data_dir, stride, patch_size, per_val=0.2, log_config="logging.conf"): """Generate patch based train and validation files for Netherlands F3 dataset. Args: data_dir (str): data directory path stride (int): stride to use when sectioning of the volume - patch (int): size of patch to extract + patch_size (int): size of patch to extract per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. log_config (str): path to log configurations """ - return split_patch_train_val(data_dir, stride, patch, per_val=per_val, log_config=log_config) + return split_patch_train_val(data_dir, stride, patch_size, per_val=per_val, log_config=log_config) if __name__ == "__main__": """Example: python prepare_data.py split_train_val section --data-dir=/mnt/dutch or - python prepare_data.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=100 + python prepare_data.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=100 """ fire.Fire( diff --git a/tests/cicd/src/scripts/get_data_for_builds.sh b/tests/cicd/src/scripts/get_data_for_builds.sh index e9d498bd..d78e566b 100755 --- a/tests/cicd/src/scripts/get_data_for_builds.sh +++ b/tests/cicd/src/scripts/get_data_for_builds.sh @@ -40,4 +40,4 @@ DATA_F3="${DATA_F3}/data" cd scripts python prepare_penobscot.py split_inline --data-dir=${DATA_PENOBSCOT} --val-ratio=.1 --test-ratio=.2 python prepare_dutchf3.py split_train_val section --data-dir=${DATA_F3} -python prepare_dutchf3.py split_train_val patch --data-dir=${DATA_F3} --stride=50 --patch=100 +python prepare_dutchf3.py split_train_val patch --data-dir=${DATA_F3} --stride=50 --patch_size=100 From 4f20319e442ba875f1fea16cc7399faa889a96c9 Mon Sep 17 00:00:00 2001 From: kirasoderstrom Date: Mon, 10 Feb 2020 15:35:17 -0800 Subject: [PATCH 178/207] update patch to patch_size in ablation.sh (#191) Co-authored-by: Sharat Chikkerur --- contrib/scripts/ablation.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/scripts/ablation.sh b/contrib/scripts/ablation.sh index 81fcdaa6..95e350c6 100755 --- a/contrib/scripts/ablation.sh +++ b/contrib/scripts/ablation.sh @@ -3,22 +3,22 @@ source activate seismic-interpretation # Patch_Size 100: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=100 +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=100 python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 100 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 100 --cfg 'configs/hrnet.yaml' # Patch_Size 150: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=150 +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=150 python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 150 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 150 --cfg 'configs/hrnet.yaml' # Patch_Size 200: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=200 +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=200 python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 200 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 200 --cfg 'configs/hrnet.yaml' # Patch_Size 250: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch=250 +python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=250 python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 250 TRAIN.AUGMENTATIONS.RESIZE.HEIGHT 250 TRAIN.AUGMENTATIONS.RESIZE.WIDTH 250 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 250 TRAIN.AUGMENTATIONS.RESIZE.HEIGHT 250 TRAIN.AUGMENTATIONS.RESIZE.WIDTH 250 --cfg 'configs/hrnet.yaml' From ffb2855249e581f4900665c2616cefb019621675 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 11 Feb 2020 11:45:56 -0500 Subject: [PATCH 179/207] closes https://github.com/microsoft/seismic-deeplearning/issues/181 (#187) * added sharat, weehyong to authors * adding a download script for Dutch F3 dataset * Adding script instructions for dutch f3 * Update README.md prepare scripts expect root level directory for dutch f3 dataset. (it is downloaded into $dir/data by the script) * added scripts which reproduce results * Adding readme text for the notebooks and checking if config is correctly setup * build error fix * fixed notebook build error * removed scripts from older pull * fixing pip version for DS VM bug fix * fixing pip version for DS VM bug fix * notebook fixes * fixes to benchmark test script * explicitly setting backbone model location in tests * addressed PR comments * addded ability to load pretrained HRNet model on the build server from custom location * fixed build failure * another fix Co-authored-by: Sharat Chikkerur --- .../F3_block_training_and_evaluation_local.ipynb | 3 ++- .../notebooks/HRNet_Penobscot_demo_notebook.ipynb | 8 ++++++-- tests/cicd/notebooks_build.yml | 10 ++++++++-- tests/cicd/src/conftest.py | 5 +++++ tests/cicd/src/notebook_integration_tests.py | 10 ++++++++-- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index b2c54194..61270609 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -262,7 +262,8 @@ "# The number of epochs to run in training\n", "max_epochs = config.TRAIN.END_EPOCH \n", "max_snapshots = config.TRAIN.SNAPSHOTS\n", - "dataset_root = config.DATASET.ROOT" + "dataset_root = config.DATASET.ROOT\n", + "model_pretrained = None" ] }, { diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 8274d966..fed506bd 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -175,7 +175,8 @@ "# The number of epochs to run in training\n", "max_epochs = config.TRAIN.END_EPOCH \n", "max_snapshots = config.TRAIN.SNAPSHOTS\n", - "dataset_root = config.DATASET.ROOT" + "dataset_root = config.DATASET.ROOT\n", + "model_pretrained = config.MODEL.PRETRAINED" ] }, { @@ -186,7 +187,7 @@ "source": [ "# make sure data location exists and we're specifying a pre-trained model\n", "assert path.exists(dataset_root) , \"Path defined in DATASET.ROOT:%s does not exist\"%(dataset_root)\n", - "assert (not config.MODEL.PRETRAINED or path.exists(config.MODEL.PRETRAINED)), \"Model pre-trained path has to be empty or should exist: %s\"%(config.MODEL.PRETRAINED) " + "assert (not model_pretrained or path.exists(model_pretrained)), \"Model pre-trained path has to be empty or should exist: %s\"%(model_pretrained) " ] }, { @@ -420,6 +421,9 @@ "metadata": {}, "outputs": [], "source": [ + "# our models currently work through common config file, in order to run this notebook from Papermill\n", + "# in our tests we need to update the config with papermill setting. \n", + "config.MODEL.PRETRAINED = model_pretrained\n", "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", "\n", "device = \"cpu\"\n", diff --git a/tests/cicd/notebooks_build.yml b/tests/cicd/notebooks_build.yml index 48a7c9a3..3d394cea 100644 --- a/tests/cicd/notebooks_build.yml +++ b/tests/cicd/notebooks_build.yml @@ -50,7 +50,10 @@ jobs: steps: - bash: | source activate seismic-interpretation - pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb --dataset_root /home/alfred/data_dynamic/penobscot + pytest -s tests/cicd/src/notebook_integration_tests.py \ + --nbname examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb \ + --dataset_root /home/alfred/data_dynamic/penobscot \ + --model_pretrained /home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth - job: F3_block_training_and_evaluation_local dependsOn: setup @@ -61,4 +64,7 @@ jobs: steps: - bash: | source activate seismic-interpretation - pytest -s tests/cicd/src/notebook_integration_tests.py --nbname examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb --dataset_root /home/alfred/data_dynamic/dutch_f3/data + pytest -s tests/cicd/src/notebook_integration_tests.py \ + --nbname examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb \ + --dataset_root /home/alfred/data_dynamic/dutch_f3/data \ + --model_pretrained /home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth diff --git a/tests/cicd/src/conftest.py b/tests/cicd/src/conftest.py index c222d3b9..95f8eea3 100644 --- a/tests/cicd/src/conftest.py +++ b/tests/cicd/src/conftest.py @@ -7,6 +7,7 @@ def pytest_addoption(parser): parser.addoption("--nbname", action="store", type=str, default=None) parser.addoption("--dataset_root", action="store", type=str, default=None) + parser.addoption("--model_pretrained", action="store", type=str, default=None) @pytest.fixture @@ -18,6 +19,10 @@ def nbname(request): def dataset_root(request): return request.config.getoption("--dataset_root") +@pytest.fixture +def model_pretrained(request): + return request.config.getoption("--model_pretrained") + """ def pytest_generate_tests(metafunc): diff --git a/tests/cicd/src/notebook_integration_tests.py b/tests/cicd/src/notebook_integration_tests.py index 1c0ccbf8..12593f75 100644 --- a/tests/cicd/src/notebook_integration_tests.py +++ b/tests/cicd/src/notebook_integration_tests.py @@ -9,11 +9,17 @@ # don't add any markup as this just runs any notebook which name is supplied # @pytest.mark.integration # @pytest.mark.notebooks -def test_notebook_run(nbname, dataset_root): +def test_notebook_run(nbname, dataset_root, model_pretrained): pm.execute_notebook( nbname, OUTPUT_NOTEBOOK, kernel_name=KERNEL_NAME, - parameters={"max_iterations": 3, "max_epochs": 1, "max_snapshots": 1, "dataset_root": dataset_root}, + parameters={ + "max_iterations": 3, + "max_epochs": 1, + "max_snapshots": 1, + "dataset_root": dataset_root, + "model_pretrained": model_pretrained, + }, cwd="examples/interpretation/notebooks", ) From 14430d18e6e258282fb985b55ca04181c96c49f6 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Tue, 11 Feb 2020 15:21:26 -0600 Subject: [PATCH 180/207] read parameters from papermill --- .../F3_block_training_and_evaluation_local.ipynb | 9 ++++++++- .../notebooks/HRNet_Penobscot_demo_notebook.ipynb | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index c5a7e36c..854e67ec 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -234,6 +234,13 @@ "\n", "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", + "\n", + "# if the notebook is run using papermill, override the configs with whatever is passed:\n", + "if 'dataset_root' in locals():\n", + " config.DATASET.ROOT = dataset_root\n", + "if 'model_pretrained' in locals():\n", + " config.MODEL.PRETRAINED = model_pretrained\n", + " \n", "print(config)\n", "validate_config_paths(config)" ] @@ -1017,4 +1024,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 029c0ea2..a37e3f1b 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -146,6 +146,13 @@ "\n", "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", + "\n", + "# if the notebook is run using papermill, override the configs with whatever is passed:\n", + "if 'dataset_root' in locals():\n", + " config.DATASET.ROOT = dataset_root\n", + "if 'model_pretrained' in locals():\n", + " config.MODEL.PRETRAINED = model_pretrained\n", + "\n", "print(config)\n", "validate_config_paths(config)" ] From 94131729333b65f18965863c7e8187b2bc851a6c Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Wed, 12 Feb 2020 16:04:55 -0500 Subject: [PATCH 181/207] fixes to test_all script to reproduce model results (#201) * added sharat, weehyong to authors * adding a download script for Dutch F3 dataset * Adding script instructions for dutch f3 * Update README.md prepare scripts expect root level directory for dutch f3 dataset. (it is downloaded into $dir/data by the script) * added scripts which reproduce results * Adding readme text for the notebooks and checking if config is correctly setup * build error fix * fixed notebook build error * removed scripts from older pull * fixing pip version for DS VM bug fix * fixing pip version for DS VM bug fix * notebook fixes * fixes to benchmark test script * explicitly setting backbone model location in tests * addressed PR comments * updated model test script to work on master and staging branches to reproduce results Co-authored-by: Sharat Chikkerur --- scripts/test_all.sh | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/scripts/test_all.sh b/scripts/test_all.sh index a715de34..23427136 100755 --- a/scripts/test_all.sh +++ b/scripts/test_all.sh @@ -35,6 +35,9 @@ cd experiments/interpretation/penobscot/local # Penobscot seresnet unet with section depth export CUDA_VISIBLE_DEVICES=0 CONFIG_NAME='seresnet_unet' +# master +# model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/resnet_unet/*/section_depth/*.pth | head -1) +# new staging structure model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/penobscot_seresnetunet_patch_section_depth.pth nohup time python test.py \ @@ -45,6 +48,9 @@ sleep 1 # Penobscot hrnet with section depth export CUDA_VISIBLE_DEVICES=1 CONFIG_NAME='hrnet' +# master +# model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/seg_hrnet/*/section_depth/*.pth | head -1) +# new staging structure model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/penobscot_hrnet_patch_section_depth.pth nohup time python test.py \ @@ -78,6 +84,9 @@ sleep 1 # squeeze excitation resnet unet + section depth export CUDA_VISIBLE_DEVICES=4 CONFIG_NAME='seresnet_unet' +# master +# model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/resnet_unet/*/section_depth/*.pth | head -1) +# staging model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/dutchf3_seresnetunet_patch_section_depth.pth nohup time python test.py \ @@ -88,7 +97,10 @@ sleep 1 # HRNet + patch depth export CUDA_VISIBLE_DEVICES=5 CONFIG_NAME='hrnet' -model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/patch_depth/*/*.pth | head -1) +# master +# model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/seg_hrnet/*/patch_depth/*.pth | head -1) +# staging +model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/$CONFIG_NAME/patch_depth/*/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/dutchf3_hrnet_patch_patch_depth.pth nohup time python test.py \ 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ @@ -99,6 +111,9 @@ sleep 1 # HRNet + section depth export CUDA_VISIBLE_DEVICES=6 CONFIG_NAME='hrnet' +# master +# model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/seg_hrnet/*/section_depth/*.pth | head -1) +# staging model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/dutchf3_hrnet_patch_section_depth.pth nohup time python test.py \ @@ -114,7 +129,7 @@ cd ../../dutchf3_section/local export CUDA_VISIBLE_DEVICES=7 CONFIG_NAME='section_deconvnet_skip' model=$(ls -td ${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) -cp $model ${OUTPUT_PRETRAINED}/dutchf3_deconvnetskip_patch_no_depth.pth +cp $model ${OUTPUT_PRETRAINED}/dutchf3_deconvnetskip_section_no_depth.pth nohup time python test.py \ 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ --cfg "configs/${CONFIG_NAME}.yaml" > ${CONFIG_NAME}_test.log 2>&1 & @@ -130,7 +145,7 @@ cd ../../dutchf3_patch/local # patch based without skip connections export CUDA_VISIBLE_DEVICES=2 CONFIG_NAME='patch_deconvnet' -model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/*/no_depth/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/dutchf3_deconvnet_patch_no_depth_distributed.pth nohup time python test.py \ 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ @@ -140,7 +155,7 @@ sleep 1 # patch based with skip connections export CUDA_VISIBLE_DEVICES=3 CONFIG_NAME='patch_deconvnet_skip' -model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/no_depth/*/*.pth | head -1) +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/*/no_depth/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/dutchf3_deconvnetskip_patch_no_depth_distributed.pth nohup time python test.py \ 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ @@ -150,7 +165,7 @@ sleep 1 # squeeze excitation resnet unet + section depth export CUDA_VISIBLE_DEVICES=4 CONFIG_NAME='seresnet_unet' -model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/resnet_unet/*/section_depth/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/dutchf3_seresnetunet_patch_section_depth_distributed.pth nohup time python test.py \ 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ @@ -160,7 +175,7 @@ sleep 1 # HRNet + patch depth export CUDA_VISIBLE_DEVICES=5 CONFIG_NAME='hrnet' -model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/patch_depth/*/*.pth | head -1) +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/seg_hrnet/*/patch_depth/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/dutchf3_hrnet_patch_patch_depth_distributed.pth nohup time python test.py \ 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ @@ -171,7 +186,7 @@ sleep 1 # HRNet + section depth export CUDA_VISIBLE_DEVICES=6 CONFIG_NAME='hrnet' -model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/${CONFIG_NAME}/section_depth/*/*.pth | head -1) +model=$(ls -td ../distributed/${OUTPUT_DIR}/${BRANCH}/*/seg_hrnet/*/section_depth/*.pth | head -1) cp $model ${OUTPUT_PRETRAINED}/dutchf3_hrnet_patch_section_depth_distributed.pth nohup time python test.py \ 'DATASET.ROOT' "${DATA_F3}" 'TEST.MODEL_PATH' "${model}" \ From c432d87d18e78c09f080bfc07d8e70196891e476 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Thu, 13 Feb 2020 13:29:29 -0600 Subject: [PATCH 182/207] Solves issue #54: check/validate config to make sure datapath and model path are valid (#198) Co-authored-by: maxkazmsft --- ..._block_training_and_evaluation_local.ipynb | 95 +++++-------------- .../HRNet_Penobscot_demo_notebook.ipynb | 15 +-- .../notebooks/configs/hrnet.yaml | 13 +-- .../configs/patch_deconvnet_skip.yaml | 17 ++-- .../notebooks/configs/unet.yaml | 15 +-- .../interpretation/notebooks/utilities.py | 29 ++++++ 6 files changed, 84 insertions(+), 100 deletions(-) diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 61270609..d99df5a7 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -107,6 +107,7 @@ " output_processing_pipeline,\n", " write_section_file,\n", " runningScore,\n", + " validate_config_paths\n", ")\n", "\n", "# set device\n", @@ -162,78 +163,15 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Configuration loaded. Please check that the DATASET.ROOT:/data/dutchf3 points to your data location.\n", - "To modify any of the options, please edit the configuration file ./configs/patch_deconvnet_skip.yaml and reload. \n", - "\n", - "CUDNN:\n", - " BENCHMARK: True\n", - " DETERMINISTIC: False\n", - " ENABLED: True\n", - "DATASET:\n", - " CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852]\n", - " NUM_CLASSES: 6\n", - " ROOT: /data/dutchf3\n", - "GPUS: (0,)\n", - "LOG_CONFIG: logging.conf\n", - "LOG_DIR: log\n", - "MODEL:\n", - " IN_CHANNELS: 1\n", - " NAME: patch_deconvnet_skip\n", - "OUTPUT_DIR: output\n", - "PRINT_FREQ: 50\n", - "SEED: 2019\n", - "TEST:\n", - " CROSSLINE: True\n", - " INLINE: True\n", - " MODEL_PATH: /data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth\n", - " POST_PROCESSING:\n", - " CROP_PIXELS: 0\n", - " SIZE: 99\n", - " SPLIT: test1\n", - " TEST_STRIDE: 10\n", - "TRAIN:\n", - " AUGMENTATION: True\n", - " AUGMENTATIONS:\n", - " PAD:\n", - " HEIGHT: 99\n", - " WIDTH: 99\n", - " RESIZE:\n", - " HEIGHT: 99\n", - " WIDTH: 99\n", - " BATCH_SIZE_PER_GPU: 64\n", - " BEGIN_EPOCH: 0\n", - " DEPTH: none\n", - " END_EPOCH: 100\n", - " MAX_LR: 0.02\n", - " MEAN: 0.0009997\n", - " MIN_LR: 0.001\n", - " MODEL_DIR: models\n", - " MOMENTUM: 0.9\n", - " PATCH_SIZE: 99\n", - " SNAPSHOTS: 5\n", - " STD: 0.20977\n", - " STRIDE: 50\n", - " WEIGHT_DECAY: 0.0001\n", - "VALIDATION:\n", - " BATCH_SIZE_PER_GPU: 512\n", - "WORKERS: 4\n" - ] - } - ], + "outputs": [], "source": [ "with open(CONFIG_FILE, \"rt\") as f_read:\n", " config = yacs.config.load_cfg(f_read)\n", "\n", "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", - "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", - "print(config)" + "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n" ] }, { @@ -266,6 +204,24 @@ "model_pretrained = None" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# override the configs if papermill was used\n", + "if 'PRETRAINED' in config.MODEL.keys():\n", + " opts = [\"DATASET.ROOT\", dataset_root, \"MODEL.PRETRAINED\", model_pretrained]\n", + "else:\n", + " opts = [\"DATASET.ROOT\", dataset_root]\n", + " \n", + "config.merge_from_list(opts) \n", + " \n", + "print(config)\n", + "validate_config_paths(config)" + ] + }, { "cell_type": "code", "execution_count": 4, @@ -991,13 +947,6 @@ "_ = f_axes[0].set_title('Actual')\n", "_ = f_axes[1].set_title('Predicted') " ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index fed506bd..822cdd8f 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -96,7 +96,7 @@ "from toolz import compose\n", "from torch.utils import data\n", "from itkwidgets import view\n", - "from utilities import plot_aline\n", + "from utilities import plot_aline, validate_config_paths\n", "from toolz import take\n", "\n", "\n", @@ -145,8 +145,7 @@ " config = yacs.config.load_cfg(f_read)\n", "\n", "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", - "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", - "print(config)" + "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n" ] }, { @@ -185,9 +184,13 @@ "metadata": {}, "outputs": [], "source": [ - "# make sure data location exists and we're specifying a pre-trained model\n", - "assert path.exists(dataset_root) , \"Path defined in DATASET.ROOT:%s does not exist\"%(dataset_root)\n", - "assert (not model_pretrained or path.exists(model_pretrained)), \"Model pre-trained path has to be empty or should exist: %s\"%(model_pretrained) " + "# override the configs if papermill was used\n", + "opts = [\"DATASET.ROOT\", dataset_root, \"MODEL.PRETRAINED\", model_pretrained]\n", + " \n", + "config.merge_from_list(opts)\n", + "\n", + "print(config)\n", + "validate_config_paths(config)" ] }, { diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index c746d58c..9fc6b0ad 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -3,8 +3,8 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'outputs' -LOG_DIR: 'log' +OUTPUT_DIR: "outputs" +LOG_DIR: "log" WORKERS: 4 PRINT_FREQ: 50 @@ -14,7 +14,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 7 - ROOT: /mnt/penobscot + ROOT: "/home/username/data/penobscot" CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] INLINE_HEIGHT: 1501 INLINE_WIDTH: 481 @@ -23,7 +23,7 @@ DATASET: MODEL: NAME: seg_hrnet IN_CHANNELS: 3 - PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + PRETRAINED: "/home/username/models/path/to/hrnetv2_w48_imagenet_pretrained.pth" EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: @@ -77,7 +77,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" #"patch" # Options are none, patch and section + DEPTH: "none" # Options are none, patch and section STRIDE: 64 PATCH_SIZE: 128 AUGMENTATIONS: @@ -99,7 +99,8 @@ VALIDATION: TEST: COMPLETE_PATCHES_ONLY: False - MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/segmentation/penobscot/local/output/penobscot/437970c875226e7e39c8109c0de8d21c5e5d6e3b/seg_hrnet/Sep25_144942/models/seg_hrnet_running_model_28.pth" + # Uncomment this if you want to test a model: + # MODEL_PATH: "/your/trained/model/path/model_name.pth" # must be an absolute path AUGMENTATIONS: RESIZE: HEIGHT: 256 diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml index 52657ea0..1fbc2c4e 100644 --- a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml +++ b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml @@ -3,8 +3,8 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' +OUTPUT_DIR: "output" +LOG_DIR: "log" WORKERS: 4 PRINT_FREQ: 50 LOG_CONFIG: logging.conf @@ -12,7 +12,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /data/dutchf3 + ROOT: "/home/username/data/dutch" CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: @@ -30,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" #"patch" # Options are No, Patch and Section + DEPTH: "none" # Options are No, Patch and Section STRIDE: 50 PATCH_SIZE: 99 AUGMENTATIONS: @@ -40,17 +40,18 @@ TRAIN: PAD: HEIGHT: 99 WIDTH: 99 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 + MEAN: 0.0009997 + STD: 0.20977 MODEL_DIR: "models" VALIDATION: BATCH_SIZE_PER_GPU: 512 TEST: - MODEL_PATH: '/data/home/mat/repos/DeepSeismic/examples/interpretation/notebooks/output/models/model_patch_deconvnet_skip_2.pth' + # Uncomment this if you want to test a model: + # MODEL_PATH: "/your/trained/model/path/model_name.pth" # must be an absolute path TEST_STRIDE: 10 - SPLIT: 'test1' # Can be both, test1, test2 + SPLIT: "test1" # Can be both, test1, test2 INLINE: True CROSSLINE: True POST_PROCESSING: diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml index d1efc6b5..2fb08952 100644 --- a/examples/interpretation/notebooks/configs/unet.yaml +++ b/examples/interpretation/notebooks/configs/unet.yaml @@ -5,8 +5,8 @@ CUDNN: DETERMINISTIC: false ENABLED: true GPUS: (0,) -OUTPUT_DIR: 'output' -LOG_DIR: 'log' +OUTPUT_DIR: "output" +LOG_DIR: "log" WORKERS: 4 PRINT_FREQ: 50 LOG_CONFIG: logging.conf @@ -15,7 +15,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: '/data/dutchf3' + ROOT: "/home/username/data/dutch" CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: @@ -43,14 +43,15 @@ TRAIN: PAD: HEIGHT: 256 WIDTH: 256 - MEAN: 0.0009997 # 0.0009996710808862074 - STD: 0.20977 # 0.20976548783479299 + MEAN: 0.0009997 + STD: 0.20977 MODEL_DIR: "models" TEST: - MODEL_PATH: "" + # Uncomment this if you want to test a model: + # MODEL_PATH: "/your/trained/model/path/model_name.pth" # must be an absolute path TEST_STRIDE: 10 - SPLIT: 'Both' # Can be Both, Test1, Test2 + SPLIT: "Both" # Can be Both, Test1, Test2 INLINE: True CROSSLINE: True POST_PROCESSING: diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index 0ef72f02..bd90688f 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -2,11 +2,13 @@ # Licensed under the MIT License. import itertools +import os import matplotlib.pyplot as plt import numpy as np import torch import torch.nn.functional as F +import yacs from ignite.utils import convert_tensor from scipy.ndimage import zoom from toolz import compose, curry, itertoolz, pipe @@ -240,3 +242,30 @@ def plot_aline(aline, labels, xlabel, ylabel="depth"): plt.imshow(labels) plt.xlabel(xlabel) plt.title("Label") + +def validate_config_paths(config): + """Checks that all paths in the config file are valid""" + # TODO: this is currently hardcoded, in the future, its better to have a more generic solution. + + # Make sure DATASET.ROOT directory exist: + assert os.path.isdir(config.DATASET.ROOT), \ + "The DATASET.ROOT specified in the config file is not a valid directory." \ + f" Please make sure this path is correct: {config.DATASET.ROOT}" + + # if a pretrained model path is specified in the config, it should exist: + if 'PRETRAINED' in config.MODEL.keys(): + assert os.path.isfile(config.MODEL.PRETRAINED), \ + "A pretrained model is specified in the config file but does not exist." \ + f" Please make sure this path is correct: {config.MODEL.PRETRAINED}" + + # if a test model path is specified in the config, it should exist: + if 'TEST' in config.keys(): + if 'MODEL_PATH' in config.TEST.keys(): + assert os.path.isfile(config.TEST.MODEL_PATH), \ + "The TEST.MODEL_PATH specified in the config file does not exist." \ + f" Please make sure this path is correct: {config.TEST.MODEL_PATH}" + # Furthermore, if this is a HRNet model, the pretrained model path should exist if the test model is specified: + if "hrnet" in config.MODEL.NAME: + assert os.path.isfile(config.MODEL.PRETRAINED), \ + "For an HRNet model, you should specify the MODEL.PRETRAINED path" \ + " in the config file if the TEST.MODEL_PATH is also specified." \ No newline at end of file From 8fdba2ea8669edea0257ab8d7758a09c72d88d77 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Thu, 20 Feb 2020 01:58:38 +0000 Subject: [PATCH 183/207] Adding dockerfile to solve issue #146 (#204) * Fixes a few typos, and links to the troubleshooting section when running the conda command * added draft dockerfile and readme * fixes to dockerfile * minor improvements * add code to download the datasets * Update Dockerfile * use miniconda * activate jupyter kernel * comment out code to download data * Updates to Dockerfile to activate conda env * updating the Dockerfile * change branch to staging (bug fix) * download the datasets * Update README.md * final modifications to Dockerfile * Updated the README file * Updated the README to use --mount instead of --volume --volume has the disadvantage of requiring the mount point to exist prior to running the docker image. Otherwise, it will create an empty directory. --mount however allows us to mount files directly to any location. * Update the hrnet.yml to match the mount point in the docker image * Update the dutchf3 paths in the config files * Update the dockerfile to prepare the datasets for training * support for nvidia-docker * fix gitpython bug * fixing the "out of shared memory" bug Co-authored-by: maxkazmsft --- docker/Dockerfile | 59 +++++++++++++++++++ docker/README.md | 40 +++++++++++++ environment/anaconda/local/environment.yml | 2 +- .../notebooks/configs/hrnet.yaml | 2 +- .../configs/patch_deconvnet_skip.yaml | 2 +- .../notebooks/configs/unet.yaml | 2 +- 6 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 docker/Dockerfile create mode 100644 docker/README.md diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..e3ea5c29 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,59 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Pull from cuda 10.2 image (based on Ubuntu 18.04): +FROM nvidia/cuda:10.2-base + +# Install base dependencies +RUN apt-get update && \ + apt-get install -y curl git build-essential wget bzip2 ca-certificates \ + libglib2.0-0 libxext6 libsm6 libxrender1 + +ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 +ENV PATH /opt/conda/bin:$PATH +SHELL ["/bin/bash", "-c"] + +# Install Anaconda and download the seismic-deeplearning repo +RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \ + /bin/bash ~/miniconda.sh -b -p /opt/conda && \ + rm ~/miniconda.sh && \ + ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \ + echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc && \ + apt-get install -y zip && \ + wget --quiet https://github.com/microsoft/seismic-deeplearning/archive/staging.zip -O staging.zip && \ + unzip staging.zip && rm staging.zip + +WORKDIR seismic-deeplearning-staging + +RUN conda env create -n seismic-interpretation --file environment/anaconda/local/environment.yml && \ + source activate seismic-interpretation && \ + python -m ipykernel install --user --name seismic-interpretation && \ + pip install -e interpretation && \ + pip install -e cv_lib + +# Download Penobscot dataset: +RUN data_dir="/home/username/data/penobscot" && \ + mkdir -p "$data_dir" && \ + ./scripts/download_penobscot.sh "$data_dir" && \ + cd scripts && \ + source activate seismic-interpretation && \ + python prepare_penobscot.py split_inline --data-dir=$data_dir --val-ratio=.1 --test-ratio=.2 && \ + cd .. + +# Download F3 dataset: +RUN data_dir="/home/username/data/dutch" && \ + mkdir -p "$data_dir" && \ + ./scripts/download_dutch_f3.sh "$data_dir" && \ + cd scripts && \ + source activate seismic-interpretation && \ + python prepare_dutchf3.py split_train_val section --data-dir=${data_dir}/data && \ + python prepare_dutchf3.py split_train_val patch --data-dir=${data_dir}/data --stride=50 --patch_size=100 && \ + cd .. + +# Run notebook +EXPOSE 8888/tcp + +CMD source activate seismic-interpretation && \ + jupyter notebook --allow-root --ip 0.0.0.0 --port 8888 + + diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..16e4bdc4 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,40 @@ +This Docker image allows the user to run the notebooks in this repository on any operating system without having to setup the environment or install anything other than the Docker engine. For instructions on how to install the Docker engine, click [here](https://www.docker.com/get-started). + +# Download the HRNet model: + +To run the [`HRNet_Penobscot_demo_notebook.ipynb`](https://github.com/microsoft/seismic-deeplearning/blob/master/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb), you will need to manually download the [HRNet-W48-C](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk) pretrained model. You can follow the instructions [here.](https://github.com/microsoft/seismic-deeplearning#hrnet). + +If you are using an Azure Virtual Machine to run this code, you can download the model to your local machine, and then copy it to your Azure VM through the command below. Please make sure you update the `` and `` feilds. +```bash +scp hrnetv2_w48_imagenet_pretrained.pth @:/home//seismic-deeplearning/docker/hrnetv2_w48_imagenet_pretrained.pth +``` + +Once you have the model downloaded (ideally under the `docker` directory), you can process to build the Docker image. + +# Install `nvidia-docker` +In order to give the Docker container access to the GPU, we need to install [`nvidia-docker`](https://github.com/NVIDIA/nvidia-docker). Please follow the instructions [here](https://github.com/NVIDIA/nvidia-docker#quickstart). For an Ubuntu 16.04/18.04 based system, run the following commands: + +```bash +curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - +curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list + +sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit +sudo systemctl restart docker +``` + +# Build the Docker image: + +In the `docker` directory, run the following command to build the Docker image and tag it as `seismic-deeplearning`: + +```bash +sudo docker build -t seismic-deeplearning . +``` +This process will take a few minutes to complete. + +# Run the Docker image: +Once the Docker image is built, you can run it anytime using the following command: +```bash +sudo docker run --rm -it -p 8888:8888 --gpus=all --shm-size 11G --mount type=bind,source=$PWD/hrnetv2_w48_imagenet_pretrained.pth,target=/home/username/models/hrnetv2_w48_imagenet_pretrained.pth seismic-deeplearning +``` + +If you have saved the pretrained model in a different directory, make sure you replace `$PWD/hrnetv2_w48_imagenet_pretrained.pth` with the **absolute** path to the pretrained HRNet model. The command above will run a jupyter notebook server that you can access by clicking on the link in your terminal. You can then navigate to the notebook that you would like to run. \ No newline at end of file diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index a8079d76..e3d38115 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -26,7 +26,7 @@ dependencies: - toolz==0.10.0 - tabulate==0.8.2 - Jinja2==2.10.3 - - gitpython==3.0.5 + - gitpython==3.0.6 - tensorboard==2.0.1 - tensorboardx==1.9 - invoke==1.3.0 diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index 9fc6b0ad..24d7e65a 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -23,7 +23,7 @@ DATASET: MODEL: NAME: seg_hrnet IN_CHANNELS: 3 - PRETRAINED: "/home/username/models/path/to/hrnetv2_w48_imagenet_pretrained.pth" + PRETRAINED: "/home/username/models/hrnetv2_w48_imagenet_pretrained.pth" EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml index 1fbc2c4e..e7016570 100644 --- a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml +++ b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml @@ -12,7 +12,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: "/home/username/data/dutch" + ROOT: "/home/username/data/dutch/data" CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml index 2fb08952..4c48f16f 100644 --- a/examples/interpretation/notebooks/configs/unet.yaml +++ b/examples/interpretation/notebooks/configs/unet.yaml @@ -15,7 +15,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: "/home/username/data/dutch" + ROOT: "/home/username/data/dutch/data" CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: From 75840780e64650f064db5ca1cde39b4d44eeee64 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Fri, 21 Feb 2020 22:06:26 +0000 Subject: [PATCH 184/207] remove duplicate code for validation (#208) --- ..._block_training_and_evaluation_local.ipynb | 21 +++++-------------- .../HRNet_Penobscot_demo_notebook.ipynb | 15 +++---------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 06474a12..38f80473 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -171,16 +171,7 @@ " config = yacs.config.load_cfg(f_read)\n", "\n", "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", - "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", - "\n", - "# if the notebook is run using papermill, override the configs with whatever is passed:\n", - "if 'dataset_root' in locals():\n", - " config.DATASET.ROOT = dataset_root\n", - "if 'model_pretrained' in locals():\n", - " config.MODEL.PRETRAINED = model_pretrained\n", - " \n", - "print(config)\n", - "validate_config_paths(config)" + "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')" ] }, { @@ -923,9 +914,7 @@ { "cell_type": "code", "execution_count": 21, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -961,9 +950,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seismic-interpretation", + "display_name": "Python [conda env:seismic-interpretation]", "language": "python", - "name": "seismic-interpretation" + "name": "conda-env-seismic-interpretation-py" }, "language_info": { "codemirror_mode": { @@ -979,5 +968,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index 5a44c852..afdfb498 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -145,16 +145,7 @@ " config = yacs.config.load_cfg(f_read)\n", "\n", "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", - "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n", - "\n", - "# if the notebook is run using papermill, override the configs with whatever is passed:\n", - "if 'dataset_root' in locals():\n", - " config.DATASET.ROOT = dataset_root\n", - "if 'model_pretrained' in locals():\n", - " config.MODEL.PRETRAINED = model_pretrained\n", - "\n", - "print(config)\n", - "validate_config_paths(config)" + "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n" ] }, { @@ -659,9 +650,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "seismic-interpretation", + "display_name": "Python [conda env:seismic-interpretation]", "language": "python", - "name": "seismic-interpretation" + "name": "conda-env-seismic-interpretation-py" }, "language_info": { "codemirror_mode": { From 83ca9a437d85c927cdfaa954f961ae74e9d9d2f0 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Sat, 22 Feb 2020 00:45:55 +0000 Subject: [PATCH 185/207] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 767cb66c..ad908eeb 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ python prepare_dutchf3.py split_train_val section --data-dir=${data_dir}/data # For patch-based experiments -python prepare_dutchf3.py split_train_val patch --data-dir=${data_dir}/data --stride=50 --patch=100 +python prepare_dutchf3.py split_train_val patch --data-dir=${data_dir}/data --stride=50 --patch_size=100 # go back to repo root cd .. From adc157e0660077a1173fa491b32fc1773883b912 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 24 Feb 2020 09:26:10 -0500 Subject: [PATCH 186/207] updating readme metrics; adding runtimes (#210) --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ad908eeb..113fd1d3 100644 --- a/README.md +++ b/README.md @@ -234,24 +234,24 @@ Below are the results from the models contained in this repo. To run them check #### Netherlands F3 -| Source | Experiment | PA | FW IoU | MCA | -|------------------|-----------------------------------|-------------|--------------|------------| -| Alaudah et al.| Section-based | 0.905 | 0.817 | .832 | -| | Patch-based | 0.852 | 0.743 | .689 | -| DeepSeismic | Patch-based+fixed | .869 | .761 | .775 | -| | SEResNet UNet+section depth | .917 | .849 | .834 | -| | HRNet(patch)+patch_depth | .908 | .843 | .837 | -| | HRNet(patch)+section_depth | .928 | .871 | .871 | +| Source | Experiment | PA | FW IoU | MCA | V100 (16GB) training time | +|------------------|-----------------------------------|-------------|--------------|------------|-----------------------------| +| Alaudah et al.| Section-based | 0.905 | 0.817 | .832 | N/A | +| | Patch-based | 0.852 | 0.743 | .689 | N/A | +| DeepSeismic | Patch-based+fixed | .875 | .784 | .740 | 08h 54min | +| | SEResNet UNet+section depth | .910 | .841 | .809 | 55h 02min | +| | HRNet(patch)+patch_depth | .884 | .795 | .739 | 67h 41min | +| | HRNet(patch)+section_depth | .900 | .820 | .767 | 55h 08min | #### Penobscot Trained and tested on the full dataset. Inlines with artifacts were left in for training, validation and testing. The dataset was split 70% training, 10% validation and 20% test. The results below are from the test set -| Source | Experiment | PA | IoU | MCA | -|------------------|-------------------------------------|-------------|--------------|------------| -| DeepSeismic | SEResNet UNet + section depth | 1.0 | .98 | .99 | -| | HRNet(patch) + section depth | 1.0 | .97 | .98 | +| Source | Experiment | PA | mIoU | MCA | V100 (16GB) training time | +|------------------|-------------------------------------|-------------|--------------|------------|-----------------------------| +| DeepSeismic | SEResNet UNet + section depth | 0.72 | .35 | .47 | 92h 59min | +| | HRNet(patch) + section depth | 0.91 | .75 | .85 | 80h 50min | ![Best Penobscot SEResNet](assets/penobscot_seresnet_best.png "Best performing inlines, Mask and Predictions from SEResNet") ![Worst Penobscot SEResNet](assets/penobscot_seresnet_worst.png "Worst performing inlines Mask and Predictions from SEResNet") From d318ed35f88760b9ea0c5bfe5eeac952e5af1bdd Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 24 Feb 2020 12:30:28 -0500 Subject: [PATCH 187/207] adds ability to specify cwd for notebook tests (#207) * added sharat, weehyong to authors * adding a download script for Dutch F3 dataset * Adding script instructions for dutch f3 * Update README.md prepare scripts expect root level directory for dutch f3 dataset. (it is downloaded into $dir/data by the script) * added scripts which reproduce results * Adding readme text for the notebooks and checking if config is correctly setup * build error fix * fixed notebook build error * removed scripts from older pull * fixing pip version for DS VM bug fix * fixing pip version for DS VM bug fix * notebook fixes * fixes to benchmark test script * explicitly setting backbone model location in tests * addressed PR comments * updated model test script to work on master and staging branches to reproduce results * enables ability to change notebook execution dir * fix Co-authored-by: Sharat Chikkerur --- tests/cicd/src/conftest.py | 4 ++++ tests/cicd/src/notebook_integration_tests.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/cicd/src/conftest.py b/tests/cicd/src/conftest.py index 95f8eea3..c7c627fc 100644 --- a/tests/cicd/src/conftest.py +++ b/tests/cicd/src/conftest.py @@ -8,6 +8,7 @@ def pytest_addoption(parser): parser.addoption("--nbname", action="store", type=str, default=None) parser.addoption("--dataset_root", action="store", type=str, default=None) parser.addoption("--model_pretrained", action="store", type=str, default=None) + parser.addoption("--cwd", action="store", type=str, default="examples/interpretation/notebooks") @pytest.fixture @@ -23,6 +24,9 @@ def dataset_root(request): def model_pretrained(request): return request.config.getoption("--model_pretrained") +@pytest.fixture +def cwd(request): + return request.config.getoption("--cwd") """ def pytest_generate_tests(metafunc): diff --git a/tests/cicd/src/notebook_integration_tests.py b/tests/cicd/src/notebook_integration_tests.py index 12593f75..6a705e41 100644 --- a/tests/cicd/src/notebook_integration_tests.py +++ b/tests/cicd/src/notebook_integration_tests.py @@ -9,7 +9,7 @@ # don't add any markup as this just runs any notebook which name is supplied # @pytest.mark.integration # @pytest.mark.notebooks -def test_notebook_run(nbname, dataset_root, model_pretrained): +def test_notebook_run(nbname, dataset_root, model_pretrained, cwd): pm.execute_notebook( nbname, OUTPUT_NOTEBOOK, @@ -21,5 +21,5 @@ def test_notebook_run(nbname, dataset_root, model_pretrained): "dataset_root": dataset_root, "model_pretrained": model_pretrained, }, - cwd="examples/interpretation/notebooks", + cwd=cwd, ) From b85b5a370ecd1f3f048b50c2aaa4d4df930f73a6 Mon Sep 17 00:00:00 2001 From: Yazeed Alaudah Date: Fri, 28 Feb 2020 14:41:48 +0000 Subject: [PATCH 188/207] 138 download pretrained models to hrnet notebook (#213) * removed duplicate code in the notebooks * initial draft * done with download_pretrained_model * updated notebook and utils * updating model dir in config * updates to util * update to notebook * model download fixes and HRNet pre-trained model demo run * fix to non-existant model_dir directory on the build server * typo Co-authored-by: maxkazmsft --- environment/anaconda/local/environment.yml | 4 +- ..._block_training_and_evaluation_local.ipynb | 2 +- .../HRNet_Penobscot_demo_notebook.ipynb | 13 ++- .../notebooks/configs/hrnet.yaml | 19 +++- .../interpretation/notebooks/utilities.py | 98 ++++++++++++++++++- 5 files changed, 122 insertions(+), 14 deletions(-) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index e3d38115..22f2f114 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -16,7 +16,7 @@ dependencies: - tensorflow==2.0 - opt-einsum>=2.3.2 - tqdm==4.39.0 - - itkwidgets==0.23.1 + - itkwidgets==0.23.1 - pytest - papermill>=1.0.1 - pip: @@ -36,4 +36,4 @@ dependencies: - pylint - scipy==1.1.0 - jupytext==1.3.0 - + - validators diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb index 38f80473..cd5fa509 100644 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb @@ -171,7 +171,7 @@ " config = yacs.config.load_cfg(f_read)\n", "\n", "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", - "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')" + "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n" ] }, { diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb index afdfb498..0033a0be 100644 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb @@ -96,7 +96,7 @@ "from toolz import compose\n", "from torch.utils import data\n", "from itkwidgets import view\n", - "from utilities import plot_aline, validate_config_paths\n", + "from utilities import plot_aline, validate_config_paths, download_pretrained_model\n", "from toolz import take\n", "\n", "\n", @@ -188,6 +188,10 @@ "opts = [\"DATASET.ROOT\", dataset_root, \"MODEL.PRETRAINED\", model_pretrained]\n", " \n", "config.merge_from_list(opts)\n", + "config = download_pretrained_model(config)\n", + "\n", + "# update model pretrained (in case it was changed when the pretrained model was downloaded)\n", + "model_pretrained = config.MODEL.PRETRAINED \n", "\n", "print(config)\n", "validate_config_paths(config)" @@ -614,6 +618,7 @@ "metadata": {}, "outputs": [], "source": [ + "%%time\n", "logger.info(\"Starting training\")\n", "trainer.run(train_loader, max_epochs=max_epochs)" ] @@ -650,9 +655,9 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "Python [conda env:seismic-interpretation]", + "display_name": "seismic-interpretation", "language": "python", - "name": "conda-env-seismic-interpretation-py" + "name": "seismic-interpretation" }, "language_info": { "codemirror_mode": { @@ -668,5 +673,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml index 24d7e65a..97b0585f 100644 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ b/examples/interpretation/notebooks/configs/hrnet.yaml @@ -70,12 +70,21 @@ TRAIN: COMPLETE_PATCHES_ONLY: True BATCH_SIZE_PER_GPU: 32 BEGIN_EPOCH: 0 - END_EPOCH: 300 - MIN_LR: 0.0001 - MAX_LR: 0.02 + # leave at 300 for full training, for demo purposes leave at 1 + # END_EPOCH: 300 + END_EPOCH: 1 + # these are the default learning rates to reproduce model performance + # MIN_LR: 0.0001 + # MAX_LR: 0.02 + # for demo purposes with pre-trained models, set these to something small + MIN_LR: 0.000000001 + MAX_LR: 0.000000001 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 + # change to the following to repro model results + # SNAPSHOTS: 5 + # demo mode snapshot + SNAPSHOTS: 1 AUGMENTATION: True DEPTH: "none" # Options are none, patch and section STRIDE: 64 @@ -90,7 +99,7 @@ TRAIN: MEAN: [-0.0001777, 0.49, -0.0000688] # First value is for images, second for depth and then combination of both STD: [0.14076 , 0.2717, 0.06286] MAX: 1 - MODEL_DIR: "models" + MODEL_DIR: "/home/username/models" VALIDATION: diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index bd90688f..d238a626 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -3,7 +3,9 @@ import itertools import os - +import urllib +import pathlib +import validators import matplotlib.pyplot as plt import numpy as np import torch @@ -268,4 +270,96 @@ def validate_config_paths(config): if "hrnet" in config.MODEL.NAME: assert os.path.isfile(config.MODEL.PRETRAINED), \ "For an HRNet model, you should specify the MODEL.PRETRAINED path" \ - " in the config file if the TEST.MODEL_PATH is also specified." \ No newline at end of file + " in the config file if the TEST.MODEL_PATH is also specified." + +def download_pretrained_model(config): + """ + This function reads the config file and downloads model pretrained on the penobscot or dutch + f3 datasets from the deepseismicsharedstore Azure storage. + + Pre-trained model is specified with MODEL.PRETRAINED parameter: + - if it's a URL, model is downloaded from the URL + - if it's a valid file handle, model is loaded from that file + - otherwise model is loaded from a pre-made URL which this code creates + + Running this code will overwrite the config.MODEL.PRETRAINED parameter value to the downloaded + pretrained model. The is the model which training is initialized from. + If this parameter is blank, we start from a randomly-initialized model. + + DATASET.ROOT parameter specifies the dataset which the model was pre-trained on + + MODEL.DEPTH optional parameter specified whether or not depth information was used in the model + and what kind of depth augmentation it was. + + We determine the pre-trained model name from these two parameters. + + """ + + # this assumes the name of the dataset is preserved in the path -- this is the default behaviour of the code. + if "dutch" in config.DATASET.ROOT: + dataset = "dutch" + elif "penobscot" in config.DATASET.ROOT: + dataset = "penobscot" + else: + raise NameError("Unknown dataset name. Only dutch f3 and penobscot are currently supported.") + + if "hrnet" in config.MODEL.NAME: + model = "hrnet" + elif "deconvnet" in config.MODEL.NAME: + model = "deconvnet" + elif "unet" in config.MODEL.NAME: + model = "unet" + else: + raise NameError("Unknown model name. Only hrnet, deconvnet, and unet are currently supported.") + + # check if the user already supplied a URL, otherwise figure out the URL + if validators.url(config.MODEL.PRETRAINED): + url = config.MODEL.PRETRAINED + print(f"Will use user-supplied URL of '{url}'") + elif os.path.isfile(config.MODEL.PRETRAINED): + url = None + print(f"Will use user-supplied file on local disk of '{config.MODEL.PRETRAINED}'") + else: + # As more pretrained models are added, add their URLs below: + if dataset == "penobscot": + if model == "hrnet": + # TODO: the code should check if the model uses patches or sections. + url = "https://deepseismicsharedstore.blob.core.windows.net/master-public-models/penobscot_hrnet_patch_section_depth.pth" + # add other models here .. + # elif dataset == "dutch": + # add other models here .. + else: + raise NotImplementedError("We don't store a pretrained model for this dataset/model combination yet.") + + print(f"Could not find a user-supplied URL, downloading from '{url}'") + + # make sure the model_dir directory is writeable + model_dir = config.TRAIN.MODEL_DIR + + if not os.path.isdir(os.path.dirname(model_dir)) or not os.access(os.path.dirname(model_dir), os.W_OK): + print (f"Cannot write to TRAIN.MODEL_DIR={config.TRAIN.MODEL_DIR}") + home = str(pathlib.Path.home()) + model_dir = os.path.join(home, "models") + print (f"Will write to TRAIN.MODEL_DIR={model_dir}") + + if not os.path.isdir(model_dir): + os.makedirs(model_dir) + + if url: + # Download the pretrained model: + pretrained_model_path = os.path.join(model_dir, "pretrained_" + dataset + "_" + model + ".pth") + + # always redownload the model + print(f"Downloading the pretrained model to '{pretrained_model_path}'. This will take a few mintues.. \n") + urllib.request.urlretrieve(url, pretrained_model_path) + print("Model successfully downloaded.. \n") + else: + # use same model which was on disk anyway - no download needed + pretrained_model_path = config.MODEL.PRETRAINED + + # Update config MODEL.PRETRAINED + # TODO: Only HRNet uses a pretrained model currently. + opts = ["MODEL.PRETRAINED", pretrained_model_path, "TRAIN.MODEL_DIR", model_dir] + config.merge_from_list(opts) + + return config From c41fa03afac20d30a6a2de09c25d5441a7365ba8 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 31 Mar 2020 14:24:19 -0400 Subject: [PATCH 189/207] 226 (#231) * added ability to use pre-trained models on Dutch F3 dataset * moved black notebook formatter instructions to README * finished Dutch F3 notebook training - pre trained model runtime is down to 1 minute; starting on test set performance * finished dutch f3 notebook * fixed Docker not running out-of-the-box with the given parameters * cleaned up other notebooks and files which are not scoped for this release * tweaks to notebook from Docker * fixed Docker instructions and port 9000 for TB * notebook build fixes * small Dockerfile fix * notebook build fixes * increased max_iterations in tests * finished tweaking the notebook to get the tests to pass * more fixes for build tests * dummy commit to re-trigger the builds * addressed PR comments * reverting back data.Subset to toolz.take --- README.md | 17 +- contrib/scripts/ablation.sh | 8 +- .../event_handlers/tensorboard_handlers.py | 17 +- .../cv_lib/segmentation/dutchf3/__init__.py | 0 docker/Dockerfile | 27 +- docker/README.md | 7 +- ..._patch_model_training_and_evaluation.ipynb | 1154 +++++++++++++++++ ..._block_training_and_evaluation_local.ipynb | 972 -------------- .../HRNet_Penobscot_demo_notebook.ipynb | 677 ---------- .../notebooks/configs/hrnet.yaml | 119 -- .../configs/patch_deconvnet_skip.yaml | 60 - .../notebooks/configs/unet.yaml | 60 - .../interpretation/notebooks/logging.conf | 35 +- .../interpretation/notebooks/utilities.py | 147 ++- .../dutchf3_patch/local/configs/hrnet.yaml | 4 +- .../local/configs/patch_deconvnet_skip.yaml | 2 +- .../dutchf3_patch/local/test.py | 99 +- .../dutchf3_patch/local/train.py | 97 +- .../interpretation/penobscot/local/train.py | 11 +- .../dutchf3/data.py | 176 ++- .../dutchf3/tests/test_dataloaders.py | 325 +++++ scripts/prepare_dutchf3.py | 287 ++-- tests/cicd/aml_build.yml | 54 + tests/cicd/component_governance.yml | 2 + tests/cicd/main_build.yml | 36 +- tests/cicd/notebooks_build.yml | 25 +- tests/cicd/src/notebook_integration_tests.py | 2 +- tests/cicd/src/scripts/get_data_for_builds.sh | 4 +- tests/test_prepare_dutchf3.py | 318 +++++ 29 files changed, 2564 insertions(+), 2178 deletions(-) create mode 100644 cv_lib/cv_lib/segmentation/dutchf3/__init__.py create mode 100644 examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb delete mode 100644 examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb delete mode 100644 examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb delete mode 100644 examples/interpretation/notebooks/configs/hrnet.yaml delete mode 100644 examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml delete mode 100644 examples/interpretation/notebooks/configs/unet.yaml mode change 100644 => 120000 examples/interpretation/notebooks/logging.conf create mode 100644 interpretation/deepseismic_interpretation/dutchf3/tests/test_dataloaders.py create mode 100644 tests/cicd/aml_build.yml create mode 100644 tests/test_prepare_dutchf3.py diff --git a/README.md b/README.md index 113fd1d3..369e2143 100644 --- a/README.md +++ b/README.md @@ -142,11 +142,12 @@ To prepare the data for the experiments (e.g. split into train/val/test), please cd scripts # For section-based experiments -python prepare_dutchf3.py split_train_val section --data-dir=${data_dir}/data +python prepare_dutchf3.py split_train_val section --data_dir=${data_dir} --label_file=train/train_labels.npy --output_dir=splits # For patch-based experiments -python prepare_dutchf3.py split_train_val patch --data-dir=${data_dir}/data --stride=50 --patch_size=100 +python prepare_dutchf3.py split_train_val patch --data_dir=${data_dir} --label_file=train/train_labels.npy --output_dir=splits \ +--stride=50 --patch_size=100 # go back to repo root cd .. @@ -165,6 +166,15 @@ Make sure to run the notebooks in the conda environment we previously set up (`s python -m ipykernel install --user --name seismic-interpretation ``` +__Optional__: if you plan to develop a notebook, you can install black formatter with the following commands: +```bash +conda activate seismic-interpretation +jupyter nbextension install https://github.com/drillan/jupyter-black/archive/master.zip --user +jupyter nbextension enable jupyter-black-master/jupyter-black +``` + +This will enable your notebook with a Black formatter button, which then clicked will automatically format a notebook cell which you're in. + #### Experiments We also provide scripts for a number of experiments we conducted using different segmentation approaches. These experiments are available under `experiments/interpretation`, and can be used as examples. Within each experiment start from the `train.sh` and `test.sh` scripts under the `local/` (single GPU) and `distributed/` (multiple GPUs) directories, which invoke the corresponding python scripts, `train.py` and `test.py`. Take a look at the experiment configurations (see Experiment Configuration Files section below) for experiment options and modify if necessary. @@ -296,6 +306,8 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Core Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=master) | | **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=staging) | | **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=master) | +| **Azure ML Tests** | staging | TODO add badge link | +| **Azure ML Tests** | master | TODO add badge link | # Troubleshooting @@ -411,4 +423,3 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l - diff --git a/contrib/scripts/ablation.sh b/contrib/scripts/ablation.sh index 95e350c6..6d5a0245 100755 --- a/contrib/scripts/ablation.sh +++ b/contrib/scripts/ablation.sh @@ -3,22 +3,22 @@ source activate seismic-interpretation # Patch_Size 100: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=100 +python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=100 python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 100 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 100 --cfg 'configs/hrnet.yaml' # Patch_Size 150: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=150 +python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=150 python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 150 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 150 --cfg 'configs/hrnet.yaml' # Patch_Size 200: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=200 +python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=200 python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 200 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 200 --cfg 'configs/hrnet.yaml' # Patch_Size 250: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=250 +python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=250 python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 250 TRAIN.AUGMENTATIONS.RESIZE.HEIGHT 250 TRAIN.AUGMENTATIONS.RESIZE.WIDTH 250 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 250 TRAIN.AUGMENTATIONS.RESIZE.HEIGHT 250 TRAIN.AUGMENTATIONS.RESIZE.WIDTH 250 --cfg 'configs/hrnet.yaml' diff --git a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py index 654c9b4d..a9ba5f4c 100644 --- a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py +++ b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py @@ -6,10 +6,7 @@ import logging import logging.config -try: - from tensorboardX import SummaryWriter -except ImportError: - raise RuntimeError("No tensorboardX package is found. Please install with the command: \npip install tensorboardX") +from tensorboardX import SummaryWriter def create_summary_writer(log_dir): @@ -52,16 +49,22 @@ def log_lr(summary_writer, optimizer, log_interval, engine): def log_metrics(summary_writer, train_engine, log_interval, engine, metrics_dict=_DEFAULT_METRICS): metrics = engine.state.metrics for m in metrics_dict: - summary_writer.add_scalar(metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval)) + summary_writer.add_scalar( + metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval) + ) -def create_image_writer(summary_writer, label, output_variable, normalize=False, transform_func=lambda x: x): +def create_image_writer( + summary_writer, label, output_variable, normalize=False, transform_func=lambda x: x +): logger = logging.getLogger(__name__) def write_to(engine): try: data_tensor = transform_func(engine.state.output[output_variable]) - image_grid = torchvision.utils.make_grid(data_tensor, normalize=normalize, scale_each=True) + image_grid = torchvision.utils.make_grid( + data_tensor, normalize=normalize, scale_each=True + ) summary_writer.add_image(label, image_grid, engine.state.epoch) except KeyError: logger.warning("Predictions and or ground truth labels not available to report") diff --git a/cv_lib/cv_lib/segmentation/dutchf3/__init__.py b/cv_lib/cv_lib/segmentation/dutchf3/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/docker/Dockerfile b/docker/Dockerfile index e3ea5c29..9a7414c9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -31,14 +31,15 @@ RUN conda env create -n seismic-interpretation --file environment/anaconda/local pip install -e interpretation && \ pip install -e cv_lib +# TODO: add back in later when Penobscot notebook is available # Download Penobscot dataset: -RUN data_dir="/home/username/data/penobscot" && \ - mkdir -p "$data_dir" && \ - ./scripts/download_penobscot.sh "$data_dir" && \ - cd scripts && \ - source activate seismic-interpretation && \ - python prepare_penobscot.py split_inline --data-dir=$data_dir --val-ratio=.1 --test-ratio=.2 && \ - cd .. +# RUN data_dir="/home/username/data/penobscot" && \ +# mkdir -p "$data_dir" && \ +# ./scripts/download_penobscot.sh "$data_dir" && \ +# cd scripts && \ +# source activate seismic-interpretation && \ +# python prepare_penobscot.py split_inline --data-dir=$data_dir --val-ratio=.1 --test-ratio=.2 && \ +# cd .. # Download F3 dataset: RUN data_dir="/home/username/data/dutch" && \ @@ -46,14 +47,14 @@ RUN data_dir="/home/username/data/dutch" && \ ./scripts/download_dutch_f3.sh "$data_dir" && \ cd scripts && \ source activate seismic-interpretation && \ - python prepare_dutchf3.py split_train_val section --data-dir=${data_dir}/data && \ - python prepare_dutchf3.py split_train_val patch --data-dir=${data_dir}/data --stride=50 --patch_size=100 && \ + python prepare_dutchf3.py split_train_val section --data-dir=${data_dir}/data --label_file=train/train_labels.npy --output_dir=splits && \ + python prepare_dutchf3.py split_train_val patch --data-dir=${data_dir}/data --label_file=train/train_labels.npy --output_dir=splits --stride=50 --patch_size=100 && \ cd .. # Run notebook -EXPOSE 8888/tcp +EXPOSE 9000/tcp +# TensorBoard inside notebook +EXPOSE 9001/tcp CMD source activate seismic-interpretation && \ - jupyter notebook --allow-root --ip 0.0.0.0 --port 8888 - - + jupyter notebook --allow-root --ip 0.0.0.0 --port 9000 diff --git a/docker/README.md b/docker/README.md index 16e4bdc4..00d13fe4 100644 --- a/docker/README.md +++ b/docker/README.md @@ -32,9 +32,10 @@ sudo docker build -t seismic-deeplearning . This process will take a few minutes to complete. # Run the Docker image: -Once the Docker image is built, you can run it anytime using the following command: +Once the Docker image is built, you can run it anytime using the following command: ```bash -sudo docker run --rm -it -p 8888:8888 --gpus=all --shm-size 11G --mount type=bind,source=$PWD/hrnetv2_w48_imagenet_pretrained.pth,target=/home/username/models/hrnetv2_w48_imagenet_pretrained.pth seismic-deeplearning +sudo nvidia-docker run --rm -it -p 9000:9000 -p 9001:9001 --shm-size 11G --mount type=bind,source=$PWD/hrnetv2_w48_imagenet_pretrained.pth,target=/home/username/models/hrnetv2_w48_imagenet_pretrained.pth seismic-deeplearning ``` -If you have saved the pretrained model in a different directory, make sure you replace `$PWD/hrnetv2_w48_imagenet_pretrained.pth` with the **absolute** path to the pretrained HRNet model. The command above will run a jupyter notebook server that you can access by clicking on the link in your terminal. You can then navigate to the notebook that you would like to run. \ No newline at end of file +If you have saved the pretrained model in a different directory, make sure you replace `$PWD/hrnetv2_w48_imagenet_pretrained.pth` with the **absolute** path to the pretrained HRNet model. The command above will run a jupyter notebook server that you can access by clicking on the link in your terminal. You can then navigate to the notebook that you would like to run. + diff --git a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb new file mode 100644 index 00000000..cc1ff3a7 --- /dev/null +++ b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb @@ -0,0 +1,1154 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Model training and evaluation on F3 Netherlands dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Seismic interpretation, also referred to as facies classification, is a task of determining types of rock in the earth’s subsurface, given seismic data. Seismic interpretation is used as a standard approach for determining precise locations of oil deposits for drilling, therefore reducing risks and potential losses. In recent years, there has been a great interest in using fully-supervised deep learning models for seismic interpretation. \n", + "\n", + "In this notebook, we demonstrate how to train a deep neural network for facies prediction using F3 Netherlands dataset. The F3 block is located in the North Sea off the shores of Netherlands. The dataset contains 6 classes (facies or lithostratigraphic units), all of which are of varying thickness (class imbalance). Processed data is available in numpy format as a `401 x 701 x 255` array. The processed F3 data is made available by [Alaudah et al. 2019](https://github.com/olivesgatech/facies_classification_benchmark).\n", + "\n", + "We specifically demonstrate a patch-based model approach, where we process a patch of an inline or crossline slice, instead of the entire slice." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Environment setup\n", + "\n", + "To set up the conda environment and the Jupyter notebook kernel, please follow the instructions in the top-level [README.md](../../../README.md) file." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook-specific parameters\n", + "\n", + "Now let's set parameters which are required only for this notebook.\n", + "\n", + "We use configuration files to specify experiment configuration, such as hyperparameters used in training and evaluation, as well as other experiment settings. \n", + "\n", + "This notebook is designed to showcase the patch-based models on Dutch F3 dataset, hence we load the configuration files from that experiment by navigating to the `experiments` folder in the root directory. Each configuration file specifies a different Computer Vision model which is loaded for this notebook.\n", + "\n", + "Modify the `CONFIG_FILE` variable below if you would like to run the experiment using a different configuration file from the same experiment.\n", + "\n", + "For \"out-of-the-box\" Docker experience we, already pre-poppulated each model configuration file with the correct paramters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# load an existing experiment configuration file\n", + "CONFIG_FILE = (\n", + " \"../../../experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml\"\n", + ")\n", + "# number of images to score\n", + "N_EVALUATE = 20\n", + "# demo flag - by default notebook runs in demo mode and only fine-tunes the pre-trained model. Set to False for full re-training.\n", + "DEMO = True\n", + "# options are test1 or test2 - picks which Dutch F3 test set split to use\n", + "TEST_SPLIT = \"test1\"\n", + "\n", + "import os\n", + "assert os.path.isfile(CONFIG_FILE), \"Experiment config file CONFIG_FILE not found on disk\"\n", + "assert isinstance(N_EVALUATE, int) and N_EVALUATE>0, \"Number of images to score has to be a positive integer\"\n", + "assert isinstance(DEMO, bool), \"demo mode should be a boolean\"\n", + "assert TEST_SPLIT == \"test1\" or TEST_SPLIT == \"test2\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data download and preparation\n", + "\n", + "To download and prepare the F3 data set, please follow the instructions in the top-level [README](../../../README.md) file. Once you have downloaded and prepared the data set, you will find your files in the following directory tree:\n", + "\n", + "```\n", + "data\n", + "├── splits\n", + "├── test_once\n", + "│ ├── test1_labels.npy\n", + "│ ├── test1_seismic.npy\n", + "│ ├── test2_labels.npy\n", + "│ └── test2_seismic.npy\n", + "└── train\n", + " ├── train_labels.npy\n", + " └── train_seismic.npy\n", + "```\n", + "\n", + "We recommend saving the data under `$HOME/data/dutchf3` since this notebook will use that location as the data root. Otherwise, modify the `DATASET.ROOT` field in the configuration file, described next. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Library imports\n", + "\n", + "Let's load required libraries - the first step fixes the seeds to obtain reproducible results and the rest of the steps import the libraries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "np.random.seed(seed=1234)\n", + "\n", + "import torch\n", + "\n", + "torch.backends.cudnn.benchmark = True\n", + "torch.manual_seed(1234)\n", + "if torch.cuda.is_available():\n", + " torch.cuda.manual_seed_all(1234)\n", + "\n", + "# now we can import the rest of the libraries\n", + "import logging\n", + "import logging.config\n", + "from os import path\n", + "\n", + "import random\n", + "import matplotlib.pyplot as plt\n", + "\n", + "plt.rcParams.update({\"font.size\": 16})\n", + "\n", + "import yacs.config\n", + "\n", + "import cv2\n", + "from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize\n", + "from ignite.contrib.handlers import CosineAnnealingScheduler\n", + "from ignite.handlers import ModelCheckpoint\n", + "from ignite.engine import Events\n", + "from ignite.metrics import Loss\n", + "from ignite.utils import convert_tensor\n", + "from toolz import compose, take\n", + "from torch.utils import data\n", + "\n", + "from cv_lib.utils import load_log_configuration\n", + "from cv_lib.event_handlers import SnapshotHandler, logging_handlers\n", + "from cv_lib.event_handlers.logging_handlers import Evaluator\n", + "from cv_lib.event_handlers import tensorboard_handlers\n", + "from cv_lib.event_handlers.tensorboard_handlers import (\n", + " create_image_writer,\n", + " create_summary_writer, \n", + ")\n", + "from cv_lib.segmentation import models\n", + "from cv_lib.segmentation.dutchf3.engine import (\n", + " create_supervised_evaluator,\n", + " create_supervised_trainer,\n", + ")\n", + "\n", + "from cv_lib.segmentation.metrics import (\n", + " pixelwise_accuracy,\n", + " class_accuracy,\n", + " mean_class_accuracy,\n", + " class_iou,\n", + " mean_iou,\n", + ")\n", + "\n", + "from cv_lib.segmentation.dutchf3.utils import (\n", + " current_datetime,\n", + " generate_path,\n", + " git_branch,\n", + " git_hash,\n", + " np_to_tb,\n", + ")\n", + "\n", + "from deepseismic_interpretation.dutchf3.data import (\n", + " get_patch_loader,\n", + " decode_segmap,\n", + " get_test_loader,\n", + ")\n", + "\n", + "from itkwidgets import view\n", + "\n", + "from utilities import (\n", + " plot_aline,\n", + " patch_label_2d,\n", + " compose_processing_pipeline,\n", + " output_processing_pipeline,\n", + " write_section_file,\n", + " runningScore,\n", + " validate_config_paths,\n", + " download_pretrained_model,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Experiment configuration file\n", + "\n", + "Let's load the experiment configuration!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with open(CONFIG_FILE, \"rt\") as f_read:\n", + " config = yacs.config.load_cfg(f_read)\n", + "\n", + "print(\n", + " f\"Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.\"\n", + ")\n", + "print(\n", + " f\"To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We run test pipelines to test the notebooks, which use [papermill](https://papermill.readthedocs.io/en/latest/). If this notebook is being executed as part of such pipeline, the variables below are overridden. If not, we simply update these variable from a static configuration file specified earlier.\n", + "\n", + "Override parameters in case we use papermill:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "parameters" + ] + }, + "outputs": [], + "source": [ + "# The number of datapoints you want to run in training or validation per batch\n", + "# Setting to None will run whole dataset\n", + "# useful for integration tests with a setting of something like 3\n", + "# Use only if you want to check things are running and don't want to run\n", + "# through whole dataset\n", + "# The number of epochs to run in training\n", + "max_epochs = config.TRAIN.END_EPOCH\n", + "max_snapshots = config.TRAIN.SNAPSHOTS\n", + "papermill = False\n", + "dataset_root = config.DATASET.ROOT\n", + "model_pretrained = config.MODEL.PRETRAINED" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# read back the parameters from papermill to config if papermill was used to run this notebook\n", + "if papermill:\n", + " # reduce number of images scored for testing\n", + " N_EVALUATE=2\n", + "\n", + "opts = [\n", + " \"DATASET.ROOT\",\n", + " dataset_root,\n", + " \"TRAIN.END_EPOCH\",\n", + " max_epochs,\n", + " \"TRAIN.SNAPSHOTS\",\n", + " max_snapshots,\n", + "]\n", + "if \"PRETRAINED\" in config.MODEL.keys():\n", + " opts += [\"MODEL.PRETRAINED\", model_pretrained]\n", + "\n", + "config.merge_from_list(opts)\n", + "\n", + "# download pre-trained model if possible\n", + "config = download_pretrained_model(config)\n", + "\n", + "# update model pretrained (in case it was changed when the pretrained model was downloaded)\n", + "model_pretrained = config.MODEL.PRETRAINED" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These are the final configs which are going to be used for this notebook - please check them carefully:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if DEMO:\n", + " opts = [\n", + " \"TRAIN.END_EPOCH\",\n", + " 1,\n", + " \"TRAIN.SNAPSHOTS\",\n", + " 1,\n", + " \"TRAIN.MAX_LR\",\n", + " 10 ** -9,\n", + " \"TRAIN.MIN_LR\",\n", + " 10 ** -9,\n", + " ]\n", + " config.merge_from_list(opts)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(config)\n", + "validate_config_paths(config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For tests we reduce the number of data used by the Jupyter notebook (pending Ignite 0.3.0 where we can just reduce the number of batches per EPOCH)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## F3 data set " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize a few sections of the F3 data set. The processed F3 data set is stored as a 3D numpy array. Let's view slices of the data along inline and crossline directions. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load training data and labels\n", + "train_seismic = np.load(path.join(config.DATASET.ROOT, \"train/train_seismic.npy\"))\n", + "train_labels = np.load(path.join(config.DATASET.ROOT, \"train/train_labels.npy\"))\n", + "\n", + "print(f\"Number of inline slices: {train_seismic.shape[0]}\")\n", + "print(f\"Number of crossline slices: {train_seismic.shape[1]}\")\n", + "print(f\"Depth dimension : {train_seismic.shape[2]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "view(train_labels, slicing_planes=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's plot a __crossline__ slice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "idx = 100\n", + "x_in = train_seismic[idx, :, :].swapaxes(0, 1)\n", + "x_inl = train_labels[idx, :, :].swapaxes(0, 1)\n", + "\n", + "plot_aline(x_in, x_inl, xlabel=\"crossline (relative)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's plot an __inline__ slice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x_cr = train_seismic[:, idx, :].swapaxes(0, 1)\n", + "x_crl = train_labels[:, idx, :].swapaxes(0, 1)\n", + "\n", + "plot_aline(x_cr, x_crl, xlabel=\"inline (relative)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model training" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set up logging\n", + "load_log_configuration(config.LOG_CONFIG)\n", + "logger = logging.getLogger(__name__)\n", + "logger.debug(config.WORKERS)\n", + "\n", + "scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up data augmentation\n", + "\n", + "Let's define our data augmentation pipeline, which includes basic transformations, such as _data normalization, resizing, and padding_ if necessary. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Setup Augmentations\n", + "base_aug = Compose(\n", + " [\n", + " Normalize(\n", + " mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1\n", + " ),\n", + " PadIfNeeded(\n", + " min_height=config.TRAIN.PATCH_SIZE,\n", + " min_width=config.TRAIN.PATCH_SIZE,\n", + " border_mode=0,\n", + " always_apply=True,\n", + " mask_value=255,\n", + " value=0,\n", + " ),\n", + " Resize(\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", + " always_apply=True,\n", + " ),\n", + " PadIfNeeded(\n", + " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", + " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", + " border_mode=cv2.BORDER_CONSTANT,\n", + " always_apply=True,\n", + " mask_value=255,\n", + " ),\n", + " ]\n", + ")\n", + "\n", + "if config.TRAIN.AUGMENTATION:\n", + " train_aug = Compose([base_aug, HorizontalFlip(p=0.5)])\n", + " val_aug = base_aug\n", + "else:\n", + " raise NotImplementedError(\n", + " \"We don't support turning off data augmentation at this time\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load the data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For training the model, we will use a patch-based approach. Rather than using entire sections (crosslines or inlines) of the data, we extract a large number of small patches from the sections, and use the patches as our data. This allows us to generate larger set of images for training, but is also a more feasible approach for large seismic volumes. \n", + "\n", + "We are using a custom patch data loader from our __`deepseismic_interpretation`__ library for generating and loading patches from seismic section data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS\n", + "\n", + "TrainPatchLoader = get_patch_loader(config)\n", + "\n", + "train_set = TrainPatchLoader(\n", + " config.DATASET.ROOT,\n", + " split=\"train\",\n", + " is_transform=True,\n", + " stride=config.TRAIN.STRIDE,\n", + " patch_size=config.TRAIN.PATCH_SIZE,\n", + " augmentations=train_aug,\n", + ")\n", + "n_classes = train_set.n_classes\n", + "logger.info(train_set)\n", + "val_set = TrainPatchLoader(\n", + " config.DATASET.ROOT,\n", + " split=\"val\",\n", + " is_transform=True,\n", + " stride=config.TRAIN.STRIDE,\n", + " patch_size=config.TRAIN.PATCH_SIZE,\n", + " augmentations=val_aug,\n", + ")\n", + "logger.info(val_set)\n", + "\n", + "snapshot_duration = scheduler_step * len(train_set)\n", + "\n", + "# use the following code when this error\n", + "# https://stackoverflow.com/questions/53810497/indexerror-when-iterating-my-dataset-using-dataloader-in-pytorch\n", + "# has been fixed and remove the toolz.take workaround at the bottom of this cell\n", + "# if DEMO:\n", + "# count = config.TRAIN.BATCH_SIZE_PER_GPU * 10\n", + "# train_set = data.Subset(train_set, list(range(count)))\n", + "# val_set = data.Subset(val_set, list(range(config.VALIDATION.BATCH_SIZE_PER_GPU)))\n", + "# snapshot_duration = scheduler_step * count\n", + "\n", + "train_loader = data.DataLoader(\n", + " train_set,\n", + " batch_size=config.TRAIN.BATCH_SIZE_PER_GPU,\n", + " num_workers=config.WORKERS,\n", + " shuffle=True,\n", + ")\n", + "val_loader = data.DataLoader(\n", + " val_set,\n", + " batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU,\n", + " num_workers=config.WORKERS,\n", + ")\n", + "\n", + "# the commented out data.Subset step above causes the Ignite engine to fail, so this \n", + "# is the workaround for now which sets batch size to 10\n", + "if DEMO:\n", + " count = max(20 if papermill else 100, config.TRAIN.BATCH_SIZE_PER_GPU)\n", + " train_loader = take(count, train_loader)\n", + " val_loader = take(config.VALIDATION.BATCH_SIZE_PER_GPU, val_loader)\n", + " snapshot_duration = scheduler_step * count" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also must specify a batch transformation function which allows us to selectively manipulate the data for each batch into the format which model training expects in the next step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def prepare_batch(batch, device=None, non_blocking=False):\n", + " x, y = batch\n", + " return (\n", + " convert_tensor(x, device=device, non_blocking=non_blocking),\n", + " convert_tensor(y, device=device, non_blocking=non_blocking),\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up model training" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, let's define a model to train, an optimization algorithm, and a loss function. \n", + "\n", + "Note that the model is loaded from our __`cv_lib`__ library, using the name of the model as specified in the configuration file. To load a different model, either change the `MODEL.NAME` field in the configuration file, or create a new one corresponding to the model you wish to train." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# load a model\n", + "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", + "\n", + "# Send to GPU if available\n", + "device = \"cpu\"\n", + "if torch.cuda.is_available():\n", + " device = \"cuda\"\n", + "model = model.to(device)\n", + "\n", + "# SGD optimizer\n", + "optimizer = torch.optim.SGD(\n", + " model.parameters(),\n", + " lr=config.TRAIN.MAX_LR,\n", + " momentum=config.TRAIN.MOMENTUM,\n", + " weight_decay=config.TRAIN.WEIGHT_DECAY,\n", + ")\n", + "\n", + "# learning rate scheduler\n", + "scheduler = CosineAnnealingScheduler(\n", + " optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration\n", + ")\n", + "\n", + "# weights are inversely proportional to the frequency of the classes in the training set\n", + "class_weights = torch.tensor(\n", + " config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False\n", + ")\n", + "\n", + "# loss function\n", + "criterion = torch.nn.CrossEntropyLoss(\n", + " weight=class_weights, ignore_index=255, reduction=\"mean\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Training the model\n", + "\n", + "We use [ignite](https://pytorch.org/ignite/index.html) framework to create training and validation loops in our codebase. Ignite provides an easy way to create compact training/validation loops without too much boilerplate code.\n", + "\n", + "In this notebook, we demonstrate the use of ignite on the training loop only. We create a training engine `trainer` that loops multiple times over the training dataset and updates model parameters. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# create training engine\n", + "trainer = create_supervised_trainer(\n", + " model, optimizer, criterion, prepare_batch, device=device\n", + ")\n", + "\n", + "# add learning rate scheduler\n", + "trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Logging\n", + "\n", + "We add various events to the trainer, using an event system, that allows us to interact with the engine on each step of the run, such as, when the trainer is started/completed, when the epoch is started/completed and so on. \n", + "\n", + "Over the next few cells, we use event handlers to add the following events to the training loop:\n", + "- log training output\n", + "- log and schedule learning rate and\n", + "- periodically save model to disk." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# define and create main output directory \n", + "# output_dir = path.join(config.OUTPUT_DIR+\"_nb\", config.TRAIN.MODEL_DIR)\n", + "output_dir = config.OUTPUT_DIR+\"_nb\"\n", + "generate_path(output_dir)\n", + "\n", + "# define main summary writer which logs all model summaries\n", + "summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR))\n", + "\n", + "# add logging of training output\n", + "trainer.add_event_handler(\n", + " Events.ITERATION_COMPLETED,\n", + " logging_handlers.log_training_output(log_interval=config.PRINT_FREQ),\n", + ")\n", + "\n", + "# add logging of learning rate\n", + "trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer))\n", + "\n", + "# log learning rate to tensorboard\n", + "trainer.add_event_handler(\n", + " Events.EPOCH_STARTED,\n", + " tensorboard_handlers.log_lr(summary_writer, optimizer, \"epoch\"),\n", + ")\n", + "\n", + "# log training summary to tensorboard as well\n", + "trainer.add_event_handler(\n", + " Events.ITERATION_COMPLETED,\n", + " tensorboard_handlers.log_training_output(summary_writer),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also checkpoint models and snapshot them to disk with every training epoch." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# add model checkpointing\n", + "checkpoint_handler = ModelCheckpoint(\n", + " output_dir,\n", + " \"model_f3_nb\",\n", + " save_interval=1,\n", + " n_saved=1,\n", + " create_dir=True,\n", + " require_empty=False,\n", + ")\n", + "\n", + "trainer.add_event_handler(\n", + " Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we need to score the model on validation set as it's training. To do this we need to add helper functions to manipulate data into the required shape just as we've done to prepare each batch for training at the beginning of this notebook.\n", + "\n", + "We also set up evaluation metrics which we want to record on the training set." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# helper function for\n", + "def _select_pred_and_mask(model_out_dict):\n", + " return (model_out_dict[\"y_pred\"].squeeze(), model_out_dict[\"mask\"].squeeze())\n", + "\n", + "\n", + "def _select_max(pred_tensor):\n", + " return pred_tensor.max(1)[1]\n", + "\n", + "\n", + "def _tensor_to_numpy(pred_tensor):\n", + " return pred_tensor.squeeze().cpu().numpy()\n", + "\n", + "\n", + "def snapshot_function():\n", + " return (trainer.state.iteration % snapshot_duration) == 0\n", + "\n", + "evaluator = create_supervised_evaluator(\n", + " model,\n", + " prepare_batch,\n", + " metrics={\n", + " \"nll\": Loss(criterion, output_transform=_select_pred_and_mask),\n", + " \"pixacc\": pixelwise_accuracy(\n", + " n_classes, output_transform=_select_pred_and_mask, device=device\n", + " ),\n", + " \"cacc\": class_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", + " \"mca\": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", + " \"ciou\": class_iou(n_classes, output_transform=_select_pred_and_mask),\n", + " \"mIoU\": mean_iou(n_classes, output_transform=_select_pred_and_mask),\n", + " },\n", + " device=device,\n", + ")\n", + "\n", + "trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader))\n", + "\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED,\n", + " logging_handlers.log_metrics(\n", + " \"Validation results\",\n", + " metrics_dict={\n", + " \"nll\": \"Avg loss :\",\n", + " \"pixacc\": \"Pixelwise Accuracy :\",\n", + " \"mca\": \"Avg Class Accuracy :\",\n", + " \"mIoU\": \"Avg Class IoU :\",\n", + " },\n", + " ),\n", + ")\n", + "\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED,\n", + " tensorboard_handlers.log_metrics(\n", + " summary_writer,\n", + " trainer,\n", + " \"epoch\",\n", + " metrics_dict={\n", + " \"mIoU\": \"Validation/mIoU\",\n", + " \"nll\": \"Validation/Loss\",\n", + " \"mca\": \"Validation/MCA\",\n", + " \"pixacc\": \"Validation/Pixel_Acc\",\n", + " },\n", + " ),\n", + ")\n", + "\n", + "\n", + "transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy)\n", + "\n", + "transform_pred = compose(transform_func, _select_max)\n", + "\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED,\n", + " create_image_writer(summary_writer, \"Validation/Image\", \"image\"),\n", + ")\n", + "\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED,\n", + " create_image_writer(\n", + " summary_writer, \"Validation/Mask\", \"mask\", transform_func=transform_func\n", + " ),\n", + ")\n", + "\n", + "evaluator.add_event_handler(\n", + " Events.EPOCH_COMPLETED,\n", + " create_image_writer(\n", + " summary_writer, \"Validation/Pred\", \"y_pred\", transform_func=transform_pred\n", + " ),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Start the training engine run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tensorboard\n", + "Using tensorboard for monitoring runs can be quite enlightening. Just ensure that the appropriate port is open on the VM so you can access it. Below we have the command for running tensorboard in your notebook. You can as easily view it in a seperate browser window by pointing the browser to the appropriate location and port." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not papermill:\n", + " %load_ext tensorboard" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not papermill:\n", + " %tensorboard --logdir $output_dir --port 9001 --host 0.0.0.0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Evaluation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will next evaluate the performance of the model by looking at how well it predicts facies labels on samples from the test set.\n", + "\n", + "We will use the following evaluation metrics:\n", + "\n", + "- Pixel Accuracy (PA)\n", + "- Class Accuracy (CA)\n", + "- Mean Class Accuracy (MCA)\n", + "- Frequency Weighted intersection-over-union (FW IoU)\n", + "- Mean IoU (MIoU)\n", + "\n", + "You have an option here to use either the pre-trained model which we provided for you or to use the model which we just fine-tuned in this notebook. By default, we use the fine-tuned model, but you can change that in the cell below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# use the model which we just fine-tuned\n", + "opts = [\"TEST.MODEL_PATH\", path.join(output_dir, \"model_f3_nb_seg_hrnet_1.pth\")]\n", + "# uncomment the line below to use the pre-trained model instead\n", + "# opts = [\"TEST.MODEL_PATH\", config.MODEL.PRETRAINED]\n", + "config.merge_from_list(opts)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.load_state_dict(torch.load(config.TEST.MODEL_PATH))\n", + "model = model.to(device)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we load the test data and define the augmentations on it. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Augmentation\n", + "# augment entire sections with the same normalization\n", + "section_aug = Compose(\n", + " [Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1,)]\n", + ")\n", + "\n", + "# augment each patch and not the entire sectiom which the patches are taken from\n", + "patch_aug = Compose(\n", + " [\n", + " Resize(\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", + " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", + " always_apply=True,\n", + " ),\n", + " PadIfNeeded(\n", + " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", + " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", + " border_mode=cv2.BORDER_CONSTANT,\n", + " always_apply=True,\n", + " mask_value=255,\n", + " ),\n", + " ]\n", + ")\n", + "\n", + "# Process test data\n", + "pre_processing = compose_processing_pipeline(config.TRAIN.DEPTH, aug=patch_aug)\n", + "output_processing = output_processing_pipeline(config)\n", + "\n", + "# Select the test split\n", + "split = TEST_SPLIT\n", + "\n", + "labels = np.load(path.join(config.DATASET.ROOT, \"test_once\", split + \"_labels.npy\"))\n", + "section_file = path.join(config.DATASET.ROOT, \"splits\", \"section_\" + split + \".txt\")\n", + "write_section_file(labels, section_file, config)\n", + "\n", + "# Load test data\n", + "TestSectionLoader = get_test_loader(config)\n", + "test_set = TestSectionLoader(\n", + " config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug\n", + ")\n", + "# needed to fix this bug in pytorch https://github.com/pytorch/pytorch/issues/973\n", + "# one of the workers will quit prematurely\n", + "torch.multiprocessing.set_sharing_strategy(\"file_system\")\n", + "test_loader = data.DataLoader(\n", + " test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Predict segmentation mask on the test data\n", + "\n", + "For demonstration purposes and efficiency, we will only use a subset of the test data to predict segmentation mask on. More precisely, we will score `N_EVALUATE` images. If you would like to evaluate more images, set this variable to the desired number of images." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CLASS_NAMES = [\n", + " \"upper_ns\",\n", + " \"middle_ns\",\n", + " \"lower_ns\",\n", + " \"rijnland_chalk\",\n", + " \"scruff\",\n", + " \"zechstein\",\n", + "]\n", + "\n", + "n_classes = len(CLASS_NAMES)\n", + "\n", + "# keep only N_EVALUATE sections to score\n", + "test_subset = random.sample(list(test_loader), N_EVALUATE)\n", + "\n", + "results = list()\n", + "running_metrics_split = runningScore(n_classes)\n", + "\n", + "# testing mode\n", + "with torch.no_grad():\n", + " model.eval()\n", + " # loop over testing data\n", + " for i, (images, labels) in enumerate(test_subset):\n", + " logger.info(f\"split: {split}, section: {i}\")\n", + " outputs = patch_label_2d(\n", + " model,\n", + " images,\n", + " pre_processing,\n", + " output_processing,\n", + " config.TRAIN.PATCH_SIZE,\n", + " config.TEST.TEST_STRIDE,\n", + " config.VALIDATION.BATCH_SIZE_PER_GPU,\n", + " device,\n", + " n_classes,\n", + " )\n", + "\n", + " pred = outputs.detach().max(1)[1].numpy()\n", + " gt = labels.numpy()\n", + " \n", + " # update evaluation metrics\n", + " running_metrics_split.update(gt, pred)\n", + " \n", + " # keep ground truth and result for plotting\n", + " results.append((np.squeeze(gt), np.squeeze(pred)))\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's view the obtained metrics on this subset of test images. Note that we trained our model for for a small number of epochs, for demonstration purposes, so the performance results here are not meant to be representative. \n", + "\n", + "The performance exceed the ones shown here when the models are trained properly. For the full report on benchmarking performance results, please refer to the [README.md](../../../README.md) file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get scores\n", + "score, _ = running_metrics_split.get_scores()\n", + "\n", + "# Log split results\n", + "print(f'Pixel Acc: {score[\"Pixel Acc: \"]:.3f}')\n", + "for cdx, class_name in enumerate(CLASS_NAMES):\n", + " print(f' {class_name}_accuracy {score[\"Class Accuracy: \"][cdx]:.3f}')\n", + "\n", + "print(f'Mean Class Acc: {score[\"Mean Class Acc: \"]:.3f}')\n", + "print(f'Freq Weighted IoU: {score[\"Freq Weighted IoU: \"]:.3f}')\n", + "print(f'Mean IoU: {score[\"Mean IoU: \"]:0.3f}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualize predictions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize the predictions on entire test sections. Note that the crosslines and inlines have different dimensions, however we were able to use them jointly for our network training and evaluation, since we were using smaller patches from the sections, whose size we can control via hyperparameter in the experiment configuration file. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = plt.figure(figsize=(15, 50))\n", + "# only plot a few images\n", + "nplot = min(N_EVALUATE, 10)\n", + "for idx in range(nplot):\n", + " # plot actual\n", + " plt.subplot(nplot, 2, 2 * (idx + 1) - 1)\n", + " plt.imshow(results[idx][0])\n", + " # plot predicted\n", + " plt.subplot(nplot, 2, 2 * (idx + 1))\n", + " plt.imshow(results[idx][1])\n", + " \n", + "f_axes = fig.axes\n", + "_ = f_axes[0].set_title(\"Actual\")\n", + "_ = f_axes[1].set_title(\"Predicted\")\n", + "fig.savefig(\"plot_predictions.png\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "display_name": "seismic-interpretation", + "language": "python", + "name": "seismic-interpretation" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb b/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb deleted file mode 100644 index cd5fa509..00000000 --- a/examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb +++ /dev/null @@ -1,972 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Model training and evaluation on F3 Netherlands dataset" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Seismic interpretation, also referred to as facies classification, is a task of determining types of rock in the earth’s subsurface, given seismic data. Seismic interpretation is used as a standard approach for determining precise locations of oil deposits for drilling, therefore reducing risks and potential losses. In recent years, there has been a great interest in using fully-supervised deep learning models for seismic interpretation. \n", - "\n", - "In this notebook, we demonstrate how to train a deep neural network for facies prediction using F3 Netherlands dataset. The F3 block is located in the North Sea off the shores of Netherlands. The dataset contains 6 classes (facies or lithostratigraphic units), all of which are of varying thickness (class imbalance). Processed data is available in numpy format as a `401 x 701 x 255` array. The processed F3 data is made available by [Alaudah et al. 2019](https://github.com/olivesgatech/facies_classification_benchmark). " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Environment setup\n", - "\n", - "To set up the conda environment, please follow the instructions in the top-level [README.md](../../../README.md) file.\n", - "\n", - "__Note__: To register the conda environment in Jupyter, run:\n", - "`python -m ipykernel install --user --name envname`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Library imports" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's load required libraries." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "import logging.config\n", - "from os import path\n", - "import random\n", - "import matplotlib.pyplot as plt\n", - "plt.rcParams.update({\"font.size\": 16})\n", - "\n", - "import yacs.config\n", - "\n", - "import cv2\n", - "import numpy as np\n", - "import torch\n", - "from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize\n", - "from ignite.contrib.handlers import CosineAnnealingScheduler\n", - "from ignite.handlers import ModelCheckpoint\n", - "from ignite.engine import Events\n", - "from ignite.metrics import Loss\n", - "from ignite.utils import convert_tensor\n", - "from toolz import compose, take\n", - "from torch.utils import data\n", - "\n", - "from cv_lib.utils import load_log_configuration\n", - "from cv_lib.event_handlers import SnapshotHandler, logging_handlers\n", - "from cv_lib.segmentation import models\n", - "from cv_lib.segmentation.dutchf3.engine import create_supervised_trainer\n", - "\n", - "from cv_lib.segmentation.dutchf3.utils import (\n", - " current_datetime,\n", - " generate_path,\n", - " git_branch,\n", - " git_hash,\n", - " np_to_tb,\n", - ")\n", - "\n", - "from deepseismic_interpretation.dutchf3.data import (\n", - " get_patch_loader,\n", - " decode_segmap,\n", - " get_test_loader,\n", - ")\n", - "\n", - "from itkwidgets import view\n", - "\n", - "from utilities import (\n", - " plot_aline,\n", - " prepare_batch,\n", - " patch_label_2d,\n", - " compose_processing_pipeline,\n", - " output_processing_pipeline,\n", - " write_section_file,\n", - " runningScore,\n", - " validate_config_paths\n", - ")\n", - "\n", - "# set device\n", - "device = \"cpu\"\n", - "if torch.cuda.is_available():\n", - " device = \"cuda\"\n", - "\n", - "# number of images to score\n", - "N_EVALUATE = 20\n", - "# experiment configuration file\n", - "CONFIG_FILE = \"./configs/patch_deconvnet_skip.yaml\"\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data download and preparation\n", - "\n", - "To download and prepare the F3 data set, please follow the instructions in the top-level [README](../../../README.md) file. Once you've downloaded and prepared the data set, you'll find your files in the following directory tree:\n", - "\n", - "```\n", - "data\n", - "├── splits\n", - "├── test_once\n", - "│ ├── test1_labels.npy\n", - "│ ├── test1_seismic.npy\n", - "│ ├── test2_labels.npy\n", - "│ └── test2_seismic.npy\n", - "└── train\n", - " ├── train_labels.npy\n", - " └── train_seismic.npy\n", - "```\n", - "\n", - "We recommend saving the data under `$HOME/data/dutchf3` since this notebook will use that location as the data root. Otherwise, modify the `DATASET.ROOT` field in the configuration file, described next. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Experiment configuration file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We use configuration files to specify experiment configuration, such as hyperparameters used in training and evaluation, as well as other experiment settings. We provide several configuration files for this notebook, under `./configs`, mainly differing in the DNN architecture used for defining the model.\n", - "\n", - "Modify the `CONFIG_FILE` variable above if you would like to run the experiment using a different configuration file." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "with open(CONFIG_FILE, \"rt\") as f_read:\n", - " config = yacs.config.load_cfg(f_read)\n", - "\n", - "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", - "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Override parameters in case we use papermill" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "tags": [ - "parameters" - ] - }, - "outputs": [], - "source": [ - "# The number of datapoints you want to run in training or validation per batch \n", - "# Setting to None will run whole dataset\n", - "# useful for integration tests with a setting of something like 3\n", - "# Use only if you want to check things are running and don't want to run\n", - "# through whole dataset\n", - "max_iterations = None\n", - "# The number of epochs to run in training\n", - "max_epochs = config.TRAIN.END_EPOCH \n", - "max_snapshots = config.TRAIN.SNAPSHOTS\n", - "dataset_root = config.DATASET.ROOT\n", - "model_pretrained = None" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# override the configs if papermill was used\n", - "if 'PRETRAINED' in config.MODEL.keys():\n", - " opts = [\"DATASET.ROOT\", dataset_root, \"MODEL.PRETRAINED\", model_pretrained]\n", - "else:\n", - " opts = [\"DATASET.ROOT\", dataset_root]\n", - " \n", - "config.merge_from_list(opts) \n", - " \n", - "print(config)\n", - "validate_config_paths(config)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# reduce number of test images if running a dummy model\n", - "if max_epochs<2:\n", - " N_EVALUATE=3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## F3 data set " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's visualize a few sections of the F3 data set. The processed F3 data set is stored as a 3D numpy array. Let's view slices of the data along inline and crossline directions. " - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of inline slices: 401\n", - "Number of crossline slices: 701\n", - "Depth dimension : 255\n" - ] - } - ], - "source": [ - "# Load training data and labels\n", - "train_seismic = np.load(path.join(dataset_root, \"train/train_seismic.npy\"))\n", - "train_labels = np.load(path.join(dataset_root, \"train/train_labels.npy\"))\n", - "\n", - "print(f\"Number of inline slices: {train_seismic.shape[0]}\")\n", - "print(f\"Number of crossline slices: {train_seismic.shape[1]}\")\n", - "print(f\"Depth dimension : {train_seismic.shape[2]}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "1aadea98bda8458fbc03782571b1d4b7", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Viewer(geometries=[], gradient_opacity=0.22, point_sets=[], rendered_image=" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "idx = 100\n", - "x_in = train_seismic[idx, :, :].swapaxes(0, 1)\n", - "x_inl = train_labels[idx, :, :].swapaxes(0, 1)\n", - "\n", - "plot_aline(x_in, x_inl, xlabel=\"crossline (relative)\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's plot a __crossline__ slice." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "x_cr = train_seismic[:, idx, :].swapaxes(0, 1)\n", - "x_crl = train_labels[:, idx, :].swapaxes(0, 1)\n", - "\n", - "plot_aline(x_cr, x_crl, xlabel=\"inline (relative)\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Model training" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-12-09 04:08:24,354 - cv_lib.utils - INFO - logging.conf configuration file was loaded.\n" - ] - } - ], - "source": [ - "# Set up logging\n", - "load_log_configuration(config.LOG_CONFIG)\n", - "logger = logging.getLogger(__name__)\n", - "logger.debug(config.WORKERS)\n", - "torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up data augmentation\n", - "\n", - "Let's define our data augmentation pipeline, which includes basic transformations, such as _data normalization, resizing, and padding_ if necessary. " - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "# Setup Augmentations\n", - "basic_aug = Compose(\n", - " [\n", - " Normalize(\n", - " mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1\n", - " ),\n", - " Resize(\n", - " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", - " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", - " always_apply=True,\n", - " ),\n", - " PadIfNeeded(\n", - " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", - " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", - " border_mode=cv2.BORDER_CONSTANT,\n", - " always_apply=True,\n", - " mask_value=255,\n", - " ),\n", - " ]\n", - ")\n", - "\n", - "if config.TRAIN.AUGMENTATION:\n", - " train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)])\n", - "else:\n", - " train_aug = basic_aug" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load the data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For training the model, we will use a patch-based approach. Rather than using entire sections (crosslines or inlines) of the data, we extract a large number of small patches from the sections, and use the patches as our data. This allows us to generate larger set of images for training, but is also a more feasible approach for large seismic volumes. \n", - "\n", - "We are using a custom patch data loader from our __`deepseismic_interpretation`__ library for generating and loading patches from seismic section data." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "This no longer pads the volume\n" - ] - } - ], - "source": [ - "TrainPatchLoader = get_patch_loader(config)\n", - "\n", - "train_set = TrainPatchLoader(\n", - " dataset_root,\n", - " split=\"train\",\n", - " is_transform=True,\n", - " stride=config.TRAIN.STRIDE,\n", - " patch_size=config.TRAIN.PATCH_SIZE,\n", - " augmentations=train_aug,\n", - ")\n", - "\n", - "\n", - "train_loader = data.DataLoader(\n", - " train_set,\n", - " batch_size=config.TRAIN.BATCH_SIZE_PER_GPU,\n", - " num_workers=config.WORKERS,\n", - " shuffle=True,\n", - ")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up model training" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's define a model to train, an optimization algorithm, and a loss function. \n", - "\n", - "Note that the model is loaded from our __`cv_lib`__ library, using the name of the model as specified in the configuration file. To load a different model, either change the `MODEL.NAME` field in the configuration file, or create a new one corresponding to the model you wish to train." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "# load a model\n", - "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", - "\n", - "# Send to GPU if available\n", - "model = model.to(device)\n", - "\n", - "# SGD optimizer\n", - "optimizer = torch.optim.SGD(\n", - " model.parameters(),\n", - " lr=config.TRAIN.MAX_LR,\n", - " momentum=config.TRAIN.MOMENTUM,\n", - " weight_decay=config.TRAIN.WEIGHT_DECAY,\n", - ")\n", - "\n", - "# learning rate scheduler\n", - "scheduler_step = max_epochs // max_snapshots\n", - "snapshot_duration = scheduler_step * len(train_loader)\n", - "scheduler = CosineAnnealingScheduler(\n", - " optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration\n", - ")\n", - "\n", - "# weights are inversely proportional to the frequency of the classes in the training set\n", - "class_weights = torch.tensor(\n", - " config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False\n", - ")\n", - "\n", - "# loss function\n", - "criterion = torch.nn.CrossEntropyLoss(\n", - " weight=class_weights, ignore_index=255, reduction=\"mean\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Training the model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We use [ignite](https://pytorch.org/ignite/index.html) framework to create training and validation loops in our codebase. Ignite provides an easy way to create compact training/validation loops without too much boilerplate code.\n", - "\n", - "In this notebook, we demonstrate the use of ignite on the training loop only. We create a training engine `trainer` that loops multiple times over the training dataset and updates model parameters. In addition, we add various events to the trainer, using an event system, that allows us to interact with the engine on each step of the run, such as, when the trainer is started/completed, when the epoch is started/completed and so on. \n", - "\n", - "In the cell below, we use event handlers to add the following events to the training loop:\n", - "- log training output\n", - "- log and schedule learning rate and\n", - "- periodically save model to disk." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# create training engine\n", - "trainer = create_supervised_trainer(\n", - " model, optimizer, criterion, prepare_batch, device=device\n", - ")\n", - "\n", - "# add learning rate scheduler\n", - "trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)\n", - "\n", - "# add logging of traininig output\n", - "trainer.add_event_handler(\n", - " Events.ITERATION_COMPLETED,\n", - " logging_handlers.log_training_output(log_interval=config.PRINT_FREQ),\n", - ")\n", - "\n", - "# add logging of learning rate\n", - "trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer))\n", - "\n", - "# add model checkpointing\n", - "output_dir = path.join(config.OUTPUT_DIR, config.TRAIN.MODEL_DIR)\n", - "checkpoint_handler = ModelCheckpoint(\n", - " output_dir, \"model\", save_interval=1, n_saved=10, create_dir=True, require_empty=False\n", - ")\n", - "trainer.add_event_handler(\n", - " Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model}\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Limit the number of datapoints if we happen to be in test mode" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "if max_iterations is not None: \n", - " train_loader = take(max_iterations, train_loader)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Start the training engine run." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-12-09 04:08:27,209 - ignite.engine.engine.Engine - INFO - Engine run starting with max_epochs=1.\n", - "2019-12-09 04:08:27,211 - cv_lib.event_handlers.logging_handlers - INFO - lr - [0.02]\n", - "2019-12-09 04:08:31,265 - ignite.engine.engine.Engine - INFO - Epoch[1] Complete. Time taken: 00:00:04\n", - "2019-12-09 04:08:32,319 - ignite.engine.engine.Engine - INFO - Engine run complete. Time taken 00:00:05\n" - ] - }, - { - "data": { - "text/plain": [ - "State:\n", - "\toutput: \n", - "\tbatch: \n", - "\tdataloader: \n", - "\tmax_epochs: 1\n", - "\tmetrics: \n", - "\titeration: 3\n", - "\tepoch: 1" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trainer.run(train_loader, max_epochs=max_epochs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Evaluation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will next evaluate the performance of the model by looking at how well it predicts facies labels on samples from the test set.\n", - "\n", - "We will use the following evaluation metrics:\n", - "\n", - "- Pixel Accuracy (PA)\n", - "- Class Accuracy (CA)\n", - "- Mean Class Accuracy (MCA)\n", - "- Frequency Weighted intersection-over-union (FW IoU)\n", - "- Mean IoU (MIoU)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's first load the model saved previously." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "with open(CONFIG_FILE, \"rt\") as f_read:\n", - " config = yacs.config.load_cfg(f_read)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", - "if max_epochs>1: \n", - " model.load_state_dict(torch.load(config.TEST.MODEL_PATH), strict=False)\n", - "else:\n", - " model.load_state_dict(torch.load(path.join(output_dir, \"model_patch_deconvnet_skip_1.pth\")), strict=False)\n", - "model = model.to(device)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we load the test data and define the augmentations on it. " - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "# Augmentation\n", - "section_aug = Compose(\n", - " [Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1)]\n", - ")\n", - "\n", - "patch_aug = Compose(\n", - " [\n", - " Resize(\n", - " config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT,\n", - " config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH,\n", - " always_apply=True,\n", - " ),\n", - " PadIfNeeded(\n", - " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", - " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", - " border_mode=cv2.BORDER_CONSTANT,\n", - " always_apply=True,\n", - " mask_value=255,\n", - " ),\n", - " ]\n", - ")\n", - "\n", - "# Process test data\n", - "pre_processing = compose_processing_pipeline(config.TRAIN.DEPTH, aug=patch_aug)\n", - "output_processing = output_processing_pipeline(config)\n", - "\n", - "# Select the test split\n", - "split = \"test1\" if \"both\" in config.TEST.SPLIT else config.TEST.SPLIT\n", - "\n", - "labels = np.load(path.join(dataset_root, \"test_once\", split + \"_labels.npy\"))\n", - "section_file = path.join(dataset_root, \"splits\", \"section_\" + split + \".txt\")\n", - "write_section_file(labels, section_file, config)\n", - "\n", - "# Load test data\n", - "TestSectionLoader = get_test_loader(config)\n", - "test_set = TestSectionLoader(\n", - " dataset_root, split=split, is_transform=True, augmentations=section_aug\n", - ")\n", - "# needed to fix this bug in pytorch https://github.com/pytorch/pytorch/issues/973\n", - "# one of the workers will quit prematurely\n", - "torch.multiprocessing.set_sharing_strategy('file_system')\n", - "test_loader = data.DataLoader(\n", - " test_set, batch_size=1, num_workers=config.WORKERS, shuffle=False\n", - ")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Predict segmentation mask on the test data\n", - "\n", - "For demonstration purposes and efficiency, we will only use a subset of the test data to predict segmentation mask on. More precisely, we will score `N_EVALUATE` images. If you would like to evaluate more images, set this variable to the desired number of images." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2019-12-09 04:08:36,547 - __main__ - INFO - split: test1, section: 0\n", - "2019-12-09 04:08:41,543 - __main__ - INFO - split: test1, section: 1\n", - "2019-12-09 04:08:42,067 - __main__ - INFO - split: test1, section: 2\n" - ] - } - ], - "source": [ - "CLASS_NAMES = [\n", - " \"upper_ns\",\n", - " \"middle_ns\",\n", - " \"lower_ns\",\n", - " \"rijnland_chalk\",\n", - " \"scruff\",\n", - " \"zechstein\",\n", - "]\n", - "\n", - "n_classes = len(CLASS_NAMES)\n", - "\n", - "# keep only N_EVALUATE sections to score\n", - "test_subset = random.sample(list(test_loader), N_EVALUATE)\n", - "\n", - "results = list()\n", - "running_metrics_split = runningScore(n_classes)\n", - "\n", - "# testing mode\n", - "with torch.no_grad():\n", - " model.eval()\n", - " # loop over testing data\n", - " for i, (images, labels) in enumerate(test_subset):\n", - " logger.info(f\"split: {split}, section: {i}\")\n", - " outputs = patch_label_2d(\n", - " model,\n", - " images,\n", - " pre_processing,\n", - " output_processing,\n", - " config.TRAIN.PATCH_SIZE,\n", - " config.TEST.TEST_STRIDE,\n", - " config.VALIDATION.BATCH_SIZE_PER_GPU,\n", - " device,\n", - " n_classes,\n", - " )\n", - "\n", - " pred = outputs.detach().max(1)[1].numpy()\n", - " gt = labels.numpy()\n", - " \n", - " # update evaluation metrics\n", - " running_metrics_split.update(gt, pred)\n", - " \n", - " # keep ground truth and result for plotting\n", - " results.append((np.squeeze(gt), np.squeeze(pred)))\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's view the obtained metrics on this subset of test images. Note that we trained our model for for a small number of epochs, for demonstration purposes, so the performance results here are not meant to be representative. \n", - "\n", - "The performance exceed the ones shown here when the models are trained properly. For the full report on benchmarking performance results, please refer to the [README.md](../../../README.md) file." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Pixel Acc: 0.522\n", - " upper_ns_accuracy 0.000\n", - " middle_ns_accuracy 0.000\n", - " lower_ns_accuracy 0.999\n", - " rijnland_chalk_accuracy 0.000\n", - " scruff_accuracy 0.001\n", - " zechstein_accuracy nan\n", - "Mean Class Acc: 0.200\n", - "Freq Weighted IoU: 0.273\n", - "Mean IoU: 0.105\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "invalid value encountered in true_divide\n", - "invalid value encountered in true_divide\n" - ] - } - ], - "source": [ - "# get scores\n", - "score, _ = running_metrics_split.get_scores()\n", - "\n", - "# Log split results\n", - "print(f'Pixel Acc: {score[\"Pixel Acc: \"]:.3f}')\n", - "for cdx, class_name in enumerate(CLASS_NAMES):\n", - " print(f' {class_name}_accuracy {score[\"Class Accuracy: \"][cdx]:.3f}')\n", - "\n", - "print(f'Mean Class Acc: {score[\"Mean Class Acc: \"]:.3f}')\n", - "print(f'Freq Weighted IoU: {score[\"Freq Weighted IoU: \"]:.3f}')\n", - "print(f'Mean IoU: {score[\"Mean IoU: \"]:0.3f}')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Visualize predictions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's visualize the predictions on entire test sections. Note that the crosslines and inlines have different dimensions, however we were able to use them jointly for our network training and evaluation, since we were using smaller patches from the sections, whose size we can control via hyperparameter in the experiment configuration file. " - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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AACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAA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0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAnihKmAAAgAElEQVQATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACgCYEHAADQhMADAABoQuABAAA0IfAAAACaEHgAAABNCDwAAIAmBB4AAEATAg8AAKAJgQcAANCEwAMAAGhC4AEAADQh8AAAAJoQeAAAAE0IPAAAgCYEHgAAQBMCDwAAoAmBBwAA0ITAAwAAaELgAQAANCHwAAAAmhB4AAAATQg8AACAJgQeAABAEwIPAACg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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "fig = plt.figure(figsize=(15,50))\n", - "\n", - "nplot = min(N_EVALUATE, 10)\n", - "for idx in range(nplot):\n", - " # plot actual\n", - " plt.subplot(nplot, 2, 2*(idx+1)-1)\n", - " plt.imshow(results[idx][0])\n", - " # plot predicted\n", - " plt.subplot(nplot, 2, 2*(idx+1))\n", - " plt.imshow(results[idx][1])\n", - "\n", - "f_axes = fig.axes\n", - "_ = f_axes[0].set_title('Actual')\n", - "_ = f_axes[1].set_title('Predicted') " - ] - } - ], - "metadata": { - "celltoolbar": "Tags", - "kernelspec": { - "display_name": "Python [conda env:seismic-interpretation]", - "language": "python", - "name": "conda-env-seismic-interpretation-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb b/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb deleted file mode 100644 index 0033a0be..00000000 --- a/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb +++ /dev/null @@ -1,677 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# HRNet training and validation on numpy dataset" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this notebook, we demonstrate how to train an HRNet model for facies prediction using [Penobscot](https://zenodo.org/record/1341774#.XepaaUB2vOg) dataset. The Penobscot 3D seismic dataset was acquired in the Scotian shelf, offshore Nova Scotia, Canada. Please refer to the top-level [README.md](../../../README.md) file to download and prepare this dataset for the experiments. \n", - "\n", - "The data expected in this notebook needs to be in the form of two 3D numpy arrays. One array will contain the seismic information, the other the mask. The network will be trained to take a 2D patch of data from the seismic block and learn to predict the 2D mask patch associated with it." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Environment setup\n", - "\n", - "* *Conda enviornment*: To set up the conda environment, please follow the instructions in the top-level [README.md](../../../README.md) file. To register the conda environment in Jupyter, run:\n", - "`python -m ipykernel install --user --name envname`\n", - "* *Dataset* : Please download the dataset using the [download script](../../scripts/download_penobscot.sh) and execute the [data preparation script](../../scripts/prepare_penobscot.py) to prepare te data for training and evaluations. Finally, update the [config file](configs/hrnet.yaml) (DATA.ROOT variable) to reflect where the data is stored. \n", - "* *Pre-trained model*: Please download the HRNET model pre-trained on ImageNet data as per instructions in [README.md](../../../README.md) file. Update the [config file](configs/hrnet.yaml) (MODEL.PRETRAINED variable) to reflect the path where the model snapshot is stored. Alternatively, you can leave the variable empty to start training from scratch. \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Library imports" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "import logging.config\n", - "from os import path\n", - "\n", - "import cv2\n", - "import numpy as np\n", - "import yacs.config\n", - "import torch\n", - "from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize\n", - "from cv_lib.utils import load_log_configuration\n", - "from cv_lib.event_handlers import (\n", - " SnapshotHandler,\n", - " logging_handlers,\n", - " tensorboard_handlers,\n", - ")\n", - "from cv_lib.event_handlers.logging_handlers import Evaluator\n", - "from cv_lib.event_handlers.tensorboard_handlers import (\n", - " create_image_writer,\n", - " create_summary_writer,\n", - ")\n", - "from cv_lib.segmentation import models, extract_metric_from\n", - "from cv_lib.segmentation.metrics import (\n", - " pixelwise_accuracy,\n", - " class_accuracy,\n", - " mean_class_accuracy,\n", - " class_iou,\n", - " mean_iou,\n", - ")\n", - "from cv_lib.segmentation.dutchf3.utils import (\n", - " current_datetime,\n", - " generate_path,\n", - " np_to_tb,\n", - ")\n", - "from cv_lib.segmentation.penobscot.engine import (\n", - " create_supervised_evaluator,\n", - " create_supervised_trainer,\n", - ")\n", - "from deepseismic_interpretation.penobscot.data import PenobscotInlinePatchDataset\n", - "from deepseismic_interpretation.dutchf3.data import decode_segmap\n", - "from ignite.contrib.handlers import CosineAnnealingScheduler\n", - "from ignite.engine import Events\n", - "from ignite.metrics import Loss\n", - "from ignite.utils import convert_tensor\n", - "from toolz import compose\n", - "from torch.utils import data\n", - "from itkwidgets import view\n", - "from utilities import plot_aline, validate_config_paths, download_pretrained_model\n", - "from toolz import take\n", - "\n", - "\n", - "mask_value = 255\n", - "_SEG_COLOURS = np.asarray(\n", - " [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123]]\n", - ")\n", - "\n", - "# experiment configuration file\n", - "CONFIG_FILE = \"./configs/hrnet.yaml\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def _prepare_batch(batch, device=None, non_blocking=False):\n", - " x, y, ids, patch_locations = batch\n", - " return (\n", - " convert_tensor(x, device=device, non_blocking=non_blocking),\n", - " convert_tensor(y, device=device, non_blocking=non_blocking),\n", - " ids,\n", - " patch_locations,\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Experiment configuration file\n", - "We use configuration files to specify experiment configuration, such as hyperparameters used in training and evaluation, as well as other experiment settings. We provide several configuration files for this notebook, under `./configs`, mainly differing in the DNN architecture used for defining the model.\n", - "\n", - "Modify the `CONFIG_FILE` variable above if you would like to run the experiment using a different configuration file." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "with open(CONFIG_FILE, \"rt\") as f_read:\n", - " config = yacs.config.load_cfg(f_read)\n", - "\n", - "print(f'Configuration loaded. Please check that the DATASET.ROOT:{config.DATASET.ROOT} points to your data location.')\n", - "print(f'To modify any of the options, please edit the configuration file {CONFIG_FILE} and reload. \\n')\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "parameters" - ] - }, - "outputs": [], - "source": [ - "# The number of datapoints you want to run in training or validation per batch \n", - "# Setting to None will run whole dataset\n", - "# useful for integration tests with a setting of something like 3\n", - "# Use only if you want to check things are running and don't want to run\n", - "# through whole dataset\n", - "max_iterations = None \n", - "# The number of epochs to run in training\n", - "max_epochs = config.TRAIN.END_EPOCH \n", - "max_snapshots = config.TRAIN.SNAPSHOTS\n", - "dataset_root = config.DATASET.ROOT\n", - "model_pretrained = config.MODEL.PRETRAINED" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# override the configs if papermill was used\n", - "opts = [\"DATASET.ROOT\", dataset_root, \"MODEL.PRETRAINED\", model_pretrained]\n", - " \n", - "config.merge_from_list(opts)\n", - "config = download_pretrained_model(config)\n", - "\n", - "# update model pretrained (in case it was changed when the pretrained model was downloaded)\n", - "model_pretrained = config.MODEL.PRETRAINED \n", - "\n", - "print(config)\n", - "validate_config_paths(config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Load Dataset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from toolz import pipe\n", - "import glob\n", - "from PIL import Image" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image_dir = os.path.join(dataset_root, \"inlines\")\n", - "mask_dir = os.path.join(dataset_root, \"masks\")\n", - "\n", - "image_iter = pipe(os.path.join(image_dir, \"*.tiff\"), glob.iglob,)\n", - "\n", - "_open_to_array = compose(np.array, Image.open)\n", - "\n", - "\n", - "def open_image_mask(image_path):\n", - " return pipe(image_path, _open_to_array)\n", - "\n", - "\n", - "def _mask_filename(imagepath):\n", - " file_part = os.path.splitext(os.path.split(imagepath)[-1].strip())[0]\n", - " return os.path.join(mask_dir, file_part + \"_mask.png\")\n", - "\n", - "\n", - "image_list = sorted(list(image_iter))\n", - "image_list_array = [_open_to_array(i) for i in image_list]\n", - "mask_list_array = [pipe(i, _mask_filename, _open_to_array) for i in image_list]\n", - "mask = np.stack(mask_list_array, axis=0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualization\n", - "Let's visualize the dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "view(mask, slicing_planes=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's view slices of the data along inline and crossline directions." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "idx = 100\n", - "x_in = image_list_array[idx]\n", - "x_inl = mask_list_array[idx]\n", - "\n", - "plot_aline(x_in, x_inl, xlabel=\"inline\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Model training" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Setup logging\n", - "load_log_configuration(config.LOG_CONFIG)\n", - "logger = logging.getLogger(__name__)\n", - "logger.debug(config.WORKERS)\n", - "scheduler_step = max_epochs // max_snapshots\n", - "torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK\n", - "\n", - "torch.manual_seed(config.SEED)\n", - "if torch.cuda.is_available():\n", - " torch.cuda.manual_seed_all(config.SEED)\n", - "np.random.seed(seed=config.SEED)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up data augmentation\n", - "\n", - "Let's define our data augmentation pipeline, which includes basic transformations, such as _data normalization, resizing, and padding_ if necessary.\n", - "The padding is carried out twice becuase if we split the inline or crossline slice into multiple patches then some of these patches will be at the edge of the slice and may not contain a full patch worth of data. To compensate to this and have same size patches in the batch (a requirement) we need to pad them.\n", - "So our basic augmentation is:\n", - "- Normalize\n", - "- Pad if needed to initial size\n", - "- Resize to a larger size\n", - "- Pad further if necessary" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Setup Augmentations\n", - "basic_aug = Compose(\n", - " [\n", - " Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=config.TRAIN.MAX,),\n", - " PadIfNeeded(\n", - " min_height=config.TRAIN.PATCH_SIZE,\n", - " min_width=config.TRAIN.PATCH_SIZE,\n", - " border_mode=cv2.BORDER_CONSTANT,\n", - " always_apply=True,\n", - " mask_value=mask_value,\n", - " value=0,\n", - " ),\n", - " Resize(config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True,),\n", - " PadIfNeeded(\n", - " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", - " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", - " border_mode=cv2.BORDER_CONSTANT,\n", - " always_apply=True,\n", - " mask_value=mask_value,\n", - " value=0,\n", - " ),\n", - " ]\n", - ")\n", - "if config.TRAIN.AUGMENTATION:\n", - " train_aug = Compose([basic_aug, HorizontalFlip(p=0.5)])\n", - " val_aug = basic_aug\n", - "else:\n", - " train_aug = val_aug = basic_aug" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load the data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For training the model, we will use a patch-based approach. Rather than using entire sections (crosslines or inlines) of the data, we extract a large number of small patches from the sections, and use the patches as our data. This allows us to generate larger set of images for training, but is also a more feasible approach for large seismic volumes.\n", - "\n", - "We are using a custom patch data loader from our __`deepseismic_interpretation`__ library for generating and loading patches from seismic section data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "train_set = PenobscotInlinePatchDataset(\n", - " dataset_root,\n", - " config.TRAIN.PATCH_SIZE,\n", - " config.TRAIN.STRIDE,\n", - " split=\"train\",\n", - " transforms=train_aug,\n", - " n_channels=config.MODEL.IN_CHANNELS,\n", - " complete_patches_only=config.TRAIN.COMPLETE_PATCHES_ONLY,\n", - ")\n", - "\n", - "val_set = PenobscotInlinePatchDataset(\n", - " dataset_root,\n", - " config.TRAIN.PATCH_SIZE,\n", - " config.TRAIN.STRIDE,\n", - " split=\"val\",\n", - " transforms=val_aug,\n", - " n_channels=config.MODEL.IN_CHANNELS,\n", - " complete_patches_only=config.VALIDATION.COMPLETE_PATCHES_ONLY,\n", - ")\n", - "\n", - "logger.info(train_set)\n", - "logger.info(val_set)\n", - "\n", - "n_classes = train_set.n_classes\n", - "train_loader = data.DataLoader(\n", - " train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True,\n", - ")\n", - "\n", - "val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up model training\n", - "Next, let's define a model to train, an optimization algorithm, and a loss function.\n", - "\n", - "Note that the model is loaded from our __`cv_lib`__ library, using the name of the model as specified in the configuration file. To load a different model, either change the `MODEL.NAME` field in the configuration file, or create a new one corresponding to the model you wish to train." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# our models currently work through common config file, in order to run this notebook from Papermill\n", - "# in our tests we need to update the config with papermill setting. \n", - "config.MODEL.PRETRAINED = model_pretrained\n", - "model = getattr(models, config.MODEL.NAME).get_seg_model(config)\n", - "\n", - "device = \"cpu\"\n", - "if torch.cuda.is_available():\n", - " device = \"cuda\"\n", - "model = model.to(device) # Send to GPU\n", - "\n", - "optimizer = torch.optim.SGD(\n", - " model.parameters(), lr=config.TRAIN.MAX_LR, momentum=config.TRAIN.MOMENTUM, weight_decay=config.TRAIN.WEIGHT_DECAY,\n", - ")\n", - "\n", - "output_dir = generate_path(config.OUTPUT_DIR, config.MODEL.NAME, current_datetime(),)\n", - "summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR))\n", - "snapshot_duration = scheduler_step * len(train_loader)\n", - "scheduler = CosineAnnealingScheduler(optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration)\n", - "\n", - "criterion = torch.nn.CrossEntropyLoss(ignore_index=mask_value, reduction=\"mean\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Training the model\n", - "We use [ignite](https://pytorch.org/ignite/index.html) framework to create training and validation loops in our codebase. Ignite provides an easy way to create compact training/validation loops without too much boilerplate code.\n", - "\n", - "In this notebook, we demonstrate the use of ignite on the training loop only. We create a training engine `trainer` that loops multiple times over the training dataset and updates model parameters. In addition, we add various events to the trainer, using an event system, that allows us to interact with the engine on each step of the run, such as, when the trainer is started/completed, when the epoch is started/completed and so on.\n", - "\n", - "In the cell below, we use event handlers to add the following events to the training loop:\n", - "- log training output\n", - "- log and schedule learning rate and\n", - "- periodically save model to disk." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "trainer = create_supervised_trainer(model, optimizer, criterion, _prepare_batch, device=device)\n", - "\n", - "trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)\n", - "\n", - "trainer.add_event_handler(\n", - " Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ),\n", - ")\n", - "trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer))\n", - "trainer.add_event_handler(\n", - " Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, \"epoch\"),\n", - ")\n", - "trainer.add_event_handler(\n", - " Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def _select_pred_and_mask(model_out_dict):\n", - " return (model_out_dict[\"y_pred\"].squeeze(), model_out_dict[\"mask\"].squeeze())\n", - "\n", - "\n", - "evaluator = create_supervised_evaluator(\n", - " model,\n", - " _prepare_batch,\n", - " metrics={\n", - " \"pixacc\": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", - " \"nll\": Loss(criterion, output_transform=_select_pred_and_mask),\n", - " \"cacc\": class_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", - " \"mca\": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", - " \"ciou\": class_iou(n_classes, output_transform=_select_pred_and_mask),\n", - " \"mIoU\": mean_iou(n_classes, output_transform=_select_pred_and_mask),\n", - " },\n", - " device=device,\n", - ")\n", - "\n", - "if max_iterations is not None:\n", - " val_loader = take(max_iterations, val_loader)\n", - "\n", - "# Set the validation run to start on the epoch completion of the training run\n", - "trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader))\n", - "\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED,\n", - " logging_handlers.log_metrics(\n", - " \"Validation results\",\n", - " metrics_dict={\n", - " \"nll\": \"Avg loss :\",\n", - " \"pixacc\": \"Pixelwise Accuracy :\",\n", - " \"mca\": \"Avg Class Accuracy :\",\n", - " \"mIoU\": \"Avg Class IoU :\",\n", - " },\n", - " ),\n", - ")\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED,\n", - " tensorboard_handlers.log_metrics(\n", - " summary_writer,\n", - " trainer,\n", - " \"epoch\",\n", - " metrics_dict={\n", - " \"mIoU\": \"Validation/mIoU\",\n", - " \"nll\": \"Validation/Loss\",\n", - " \"mca\": \"Validation/MCA\",\n", - " \"pixacc\": \"Validation/Pixel_Acc\",\n", - " },\n", - " ),\n", - ")\n", - "\n", - "\n", - "def _select_max(pred_tensor):\n", - " return pred_tensor.max(1)[1]\n", - "\n", - "\n", - "def _tensor_to_numpy(pred_tensor):\n", - " return pred_tensor.squeeze().cpu().numpy()\n", - "\n", - "\n", - "transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy,)\n", - "\n", - "transform_pred = compose(transform_func, _select_max)\n", - "\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED, create_image_writer(summary_writer, \"Validation/Image\", \"image\"),\n", - ")\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED,\n", - " create_image_writer(summary_writer, \"Validation/Mask\", \"mask\", transform_func=transform_func),\n", - ")\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED,\n", - " create_image_writer(summary_writer, \"Validation/Pred\", \"y_pred\", transform_func=transform_pred),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Checkpointing\n", - "Below we define the function that will save the best performing models based on mean IoU." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def snapshot_function():\n", - " return (trainer.state.iteration % snapshot_duration) == 0\n", - "\n", - "\n", - "checkpoint_handler = SnapshotHandler(\n", - " path.join(output_dir, config.TRAIN.MODEL_DIR), config.MODEL.NAME, extract_metric_from(\"mIoU\"), snapshot_function,\n", - ")\n", - "evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {\"model\": model})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Training\n", - "Start the training engine run." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if max_iterations is not None:\n", - " train_loader = take(max_iterations, train_loader)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "logger.info(\"Starting training\")\n", - "trainer.run(train_loader, max_epochs=max_epochs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Tensorboard\n", - "Using tensorboard for monitoring runs can be quite enlightening. Just ensure that the appropriate port is open on the VM so you can access it. Below we have the command for running tensorboard in your notebook. You can as easily view it in a seperate browser window by pointing the browser to the appropriate location and port." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if max_epochs>1:\n", - " %load_ext tensorboard" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if max_epochs>1:\n", - " %tensorboard --logdir outputs --port 6007 --host 0.0.0.0" - ] - } - ], - "metadata": { - "celltoolbar": "Tags", - "kernelspec": { - "display_name": "seismic-interpretation", - "language": "python", - "name": "seismic-interpretation" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/examples/interpretation/notebooks/configs/hrnet.yaml b/examples/interpretation/notebooks/configs/hrnet.yaml deleted file mode 100644 index 97b0585f..00000000 --- a/examples/interpretation/notebooks/configs/hrnet.yaml +++ /dev/null @@ -1,119 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: "outputs" -LOG_DIR: "log" -WORKERS: 4 -PRINT_FREQ: 50 - -LOG_CONFIG: logging.conf -SEED: 2019 - - -DATASET: - NUM_CLASSES: 7 - ROOT: "/home/username/data/penobscot" - CLASS_WEIGHTS: [0.02630481, 0.05448931, 0.0811898 , 0.01866496, 0.15868563, 0.0875993 , 0.5730662] - INLINE_HEIGHT: 1501 - INLINE_WIDTH: 481 - - -MODEL: - NAME: seg_hrnet - IN_CHANNELS: 3 - PRETRAINED: "/home/username/models/hrnetv2_w48_imagenet_pretrained.pth" - EXTRA: - FINAL_CONV_KERNEL: 1 - STAGE2: - NUM_MODULES: 1 - NUM_BRANCHES: 2 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - FUSE_METHOD: SUM - STAGE3: - NUM_MODULES: 4 - NUM_BRANCHES: 3 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - FUSE_METHOD: SUM - STAGE4: - NUM_MODULES: 3 - NUM_BRANCHES: 4 - BLOCK: BASIC - NUM_BLOCKS: - - 4 - - 4 - - 4 - - 4 - NUM_CHANNELS: - - 48 - - 96 - - 192 - - 384 - FUSE_METHOD: SUM - -TRAIN: - COMPLETE_PATCHES_ONLY: True - BATCH_SIZE_PER_GPU: 32 - BEGIN_EPOCH: 0 - # leave at 300 for full training, for demo purposes leave at 1 - # END_EPOCH: 300 - END_EPOCH: 1 - # these are the default learning rates to reproduce model performance - # MIN_LR: 0.0001 - # MAX_LR: 0.02 - # for demo purposes with pre-trained models, set these to something small - MIN_LR: 0.000000001 - MAX_LR: 0.000000001 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - # change to the following to repro model results - # SNAPSHOTS: 5 - # demo mode snapshot - SNAPSHOTS: 1 - AUGMENTATION: True - DEPTH: "none" # Options are none, patch and section - STRIDE: 64 - PATCH_SIZE: 128 - AUGMENTATIONS: - RESIZE: - HEIGHT: 256 - WIDTH: 256 - PAD: - HEIGHT: 256 - WIDTH: 256 - MEAN: [-0.0001777, 0.49, -0.0000688] # First value is for images, second for depth and then combination of both - STD: [0.14076 , 0.2717, 0.06286] - MAX: 1 - MODEL_DIR: "/home/username/models" - - -VALIDATION: - BATCH_SIZE_PER_GPU: 128 - COMPLETE_PATCHES_ONLY: True - -TEST: - COMPLETE_PATCHES_ONLY: False - # Uncomment this if you want to test a model: - # MODEL_PATH: "/your/trained/model/path/model_name.pth" # must be an absolute path - AUGMENTATIONS: - RESIZE: - HEIGHT: 256 - WIDTH: 256 - PAD: - HEIGHT: 256 - WIDTH: 256 diff --git a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml b/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml deleted file mode 100644 index e7016570..00000000 --- a/examples/interpretation/notebooks/configs/patch_deconvnet_skip.yaml +++ /dev/null @@ -1,60 +0,0 @@ -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: "output" -LOG_DIR: "log" -WORKERS: 4 -PRINT_FREQ: 50 -LOG_CONFIG: logging.conf -SEED: 2019 - -DATASET: - NUM_CLASSES: 6 - ROOT: "/home/username/data/dutch/data" - CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - -MODEL: - NAME: patch_deconvnet_skip - IN_CHANNELS: 1 - - -TRAIN: - BATCH_SIZE_PER_GPU: 64 - BEGIN_EPOCH: 0 - END_EPOCH: 100 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "none" # Options are No, Patch and Section - STRIDE: 50 - PATCH_SIZE: 99 - AUGMENTATIONS: - RESIZE: - HEIGHT: 99 - WIDTH: 99 - PAD: - HEIGHT: 99 - WIDTH: 99 - MEAN: 0.0009997 - STD: 0.20977 - MODEL_DIR: "models" - -VALIDATION: - BATCH_SIZE_PER_GPU: 512 - -TEST: - # Uncomment this if you want to test a model: - # MODEL_PATH: "/your/trained/model/path/model_name.pth" # must be an absolute path - TEST_STRIDE: 10 - SPLIT: "test1" # Can be both, test1, test2 - INLINE: True - CROSSLINE: True - POST_PROCESSING: - SIZE: 99 # - CROP_PIXELS: 0 # Number of pixels to crop top, bottom, left and right - diff --git a/examples/interpretation/notebooks/configs/unet.yaml b/examples/interpretation/notebooks/configs/unet.yaml deleted file mode 100644 index 4c48f16f..00000000 --- a/examples/interpretation/notebooks/configs/unet.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# UNet configuration - -CUDNN: - BENCHMARK: true - DETERMINISTIC: false - ENABLED: true -GPUS: (0,) -OUTPUT_DIR: "output" -LOG_DIR: "log" -WORKERS: 4 -PRINT_FREQ: 50 -LOG_CONFIG: logging.conf -SEED: 2019 - - -DATASET: - NUM_CLASSES: 6 - ROOT: "/home/username/data/dutch/data" - CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] - -MODEL: - NAME: resnet_unet - IN_CHANNELS: 3 - - -TRAIN: - BATCH_SIZE_PER_GPU: 16 - BEGIN_EPOCH: 0 - END_EPOCH: 10 - MIN_LR: 0.001 - MAX_LR: 0.02 - MOMENTUM: 0.9 - WEIGHT_DECAY: 0.0001 - SNAPSHOTS: 5 - AUGMENTATION: True - DEPTH: "section" # Options are No, Patch and Section - STRIDE: 50 - PATCH_SIZE: 100 - AUGMENTATIONS: - RESIZE: - HEIGHT: 200 - WIDTH: 200 - PAD: - HEIGHT: 256 - WIDTH: 256 - MEAN: 0.0009997 - STD: 0.20977 - MODEL_DIR: "models" - -TEST: - # Uncomment this if you want to test a model: - # MODEL_PATH: "/your/trained/model/path/model_name.pth" # must be an absolute path - TEST_STRIDE: 10 - SPLIT: "Both" # Can be Both, Test1, Test2 - INLINE: True - CROSSLINE: True - POST_PROCESSING: - SIZE: 128 - CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right - diff --git a/examples/interpretation/notebooks/logging.conf b/examples/interpretation/notebooks/logging.conf deleted file mode 100644 index 56334fc4..00000000 --- a/examples/interpretation/notebooks/logging.conf +++ /dev/null @@ -1,34 +0,0 @@ -[loggers] -keys=root,__main__,event_handlers - -[handlers] -keys=consoleHandler - -[formatters] -keys=simpleFormatter - -[logger_root] -level=INFO -handlers=consoleHandler - -[logger___main__] -level=INFO -handlers=consoleHandler -qualname=__main__ -propagate=0 - -[logger_event_handlers] -level=INFO -handlers=consoleHandler -qualname=event_handlers -propagate=0 - -[handler_consoleHandler] -class=StreamHandler -level=INFO -formatter=simpleFormatter -args=(sys.stdout,) - -[formatter_simpleFormatter] -format=%(asctime)s - %(name)s - %(levelname)s - %(message)s - diff --git a/examples/interpretation/notebooks/logging.conf b/examples/interpretation/notebooks/logging.conf new file mode 120000 index 00000000..f8087ef4 --- /dev/null +++ b/examples/interpretation/notebooks/logging.conf @@ -0,0 +1 @@ +../../../experiments/interpretation/dutchf3_patch/local/logging.conf \ No newline at end of file diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index d238a626..cc91523a 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -23,9 +23,9 @@ def __init__(self, n_classes): def _fast_hist(self, label_true, label_pred, n_class): mask = (label_true >= 0) & (label_true < n_class) - hist = np.bincount(n_class * label_true[mask].astype(int) + label_pred[mask], minlength=n_class ** 2,).reshape( - n_class, n_class - ) + hist = np.bincount( + n_class * label_true[mask].astype(int) + label_pred[mask], minlength=n_class ** 2, + ).reshape(n_class, n_class) return hist def update(self, label_trues, label_preds): @@ -152,7 +152,9 @@ def compose_processing_pipeline(depth, aug=None): def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): - hdc_wdx_generator = itertools.product(range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride)) + hdc_wdx_generator = itertools.product( + range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride) + ) for batch_indexes in itertoolz.partition_all(batch_size, hdc_wdx_generator): yield batch_indexes @@ -164,7 +166,9 @@ def output_processing_pipeline(config, output): _, _, h, w = output.shape if config.TEST.POST_PROCESSING.SIZE != h or config.TEST.POST_PROCESSING.SIZE != w: output = F.interpolate( - output, size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE), mode="bilinear", + output, + size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE), + mode="bilinear", ) if config.TEST.POST_PROCESSING.CROP_PIXELS > 0: @@ -179,7 +183,15 @@ def output_processing_pipeline(config, output): def patch_label_2d( - model, img, pre_processing, output_processing, patch_size, stride, batch_size, device, num_classes, + model, + img, + pre_processing, + output_processing, + patch_size, + stride, + batch_size, + device, + num_classes, ): """Processes a whole section""" img = torch.squeeze(img) @@ -193,14 +205,19 @@ def patch_label_2d( # generate output: for batch_indexes in _generate_batches(h, w, ps, patch_size, stride, batch_size=batch_size): batch = torch.stack( - [pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing) for hdx, wdx in batch_indexes], + [ + pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing) + for hdx, wdx in batch_indexes + ], dim=0, ) model_output = model(batch.to(device)) for (hdx, wdx), output in zip(batch_indexes, model_output.detach().cpu()): output = output_processing(output) - output_p[:, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size] += output + output_p[ + :, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size + ] += output # crop the output_p in the middle output = output_p[:, :, ps:-ps, ps:-ps] @@ -245,32 +262,38 @@ def plot_aline(aline, labels, xlabel, ylabel="depth"): plt.xlabel(xlabel) plt.title("Label") + def validate_config_paths(config): """Checks that all paths in the config file are valid""" # TODO: this is currently hardcoded, in the future, its better to have a more generic solution. # Make sure DATASET.ROOT directory exist: - assert os.path.isdir(config.DATASET.ROOT), \ - "The DATASET.ROOT specified in the config file is not a valid directory." \ + assert os.path.isdir(config.DATASET.ROOT), ( + "The DATASET.ROOT specified in the config file is not a valid directory." f" Please make sure this path is correct: {config.DATASET.ROOT}" + ) - # if a pretrained model path is specified in the config, it should exist: - if 'PRETRAINED' in config.MODEL.keys(): - assert os.path.isfile(config.MODEL.PRETRAINED), \ - "A pretrained model is specified in the config file but does not exist." \ + # if a pretrained model path is specified in the config, it should exist: + if "PRETRAINED" in config.MODEL.keys(): + assert os.path.isfile(config.MODEL.PRETRAINED), ( + "A pretrained model is specified in the config file but does not exist." f" Please make sure this path is correct: {config.MODEL.PRETRAINED}" + ) - # if a test model path is specified in the config, it should exist: - if 'TEST' in config.keys(): - if 'MODEL_PATH' in config.TEST.keys(): - assert os.path.isfile(config.TEST.MODEL_PATH), \ - "The TEST.MODEL_PATH specified in the config file does not exist." \ + # if a test model path is specified in the config, it should exist: + if "TEST" in config.keys(): + if "MODEL_PATH" in config.TEST.keys(): + assert os.path.isfile(config.TEST.MODEL_PATH), ( + "The TEST.MODEL_PATH specified in the config file does not exist." f" Please make sure this path is correct: {config.TEST.MODEL_PATH}" - # Furthermore, if this is a HRNet model, the pretrained model path should exist if the test model is specified: + ) + # Furthermore, if this is a HRNet model, the pretrained model path should exist if the test model is specified: if "hrnet" in config.MODEL.NAME: - assert os.path.isfile(config.MODEL.PRETRAINED), \ - "For an HRNet model, you should specify the MODEL.PRETRAINED path" \ + assert os.path.isfile(config.MODEL.PRETRAINED), ( + "For an HRNet model, you should specify the MODEL.PRETRAINED path" " in the config file if the TEST.MODEL_PATH is also specified." + ) + def download_pretrained_model(config): """ @@ -301,8 +324,10 @@ def download_pretrained_model(config): elif "penobscot" in config.DATASET.ROOT: dataset = "penobscot" else: - raise NameError("Unknown dataset name. Only dutch f3 and penobscot are currently supported.") - + raise NameError( + "Unknown dataset name. Only dutch f3 and penobscot are currently supported." + ) + if "hrnet" in config.MODEL.NAME: model = "hrnet" elif "deconvnet" in config.MODEL.NAME: @@ -310,7 +335,9 @@ def download_pretrained_model(config): elif "unet" in config.MODEL.NAME: model = "unet" else: - raise NameError("Unknown model name. Only hrnet, deconvnet, and unet are currently supported.") + raise NameError( + "Unknown model name. Only hrnet, deconvnet, and unet are currently supported." + ) # check if the user already supplied a URL, otherwise figure out the URL if validators.url(config.MODEL.PRETRAINED): @@ -319,38 +346,73 @@ def download_pretrained_model(config): elif os.path.isfile(config.MODEL.PRETRAINED): url = None print(f"Will use user-supplied file on local disk of '{config.MODEL.PRETRAINED}'") - else: + else: # As more pretrained models are added, add their URLs below: if dataset == "penobscot": if model == "hrnet": - # TODO: the code should check if the model uses patches or sections. + # TODO: the code should check if the model uses patches or sections. url = "https://deepseismicsharedstore.blob.core.windows.net/master-public-models/penobscot_hrnet_patch_section_depth.pth" - # add other models here .. - # elif dataset == "dutch": - # add other models here .. + else: + raise NotImplementedError( + "We don't store a pretrained model for Dutch F3 for this model combination yet." + ) + # add other models here .. + elif dataset == "dutch": + # add other models here .. + if model == "hrnet" and config.TRAIN.DEPTH == "section": + url = "https://deepseismicsharedstore.blob.core.windows.net/master-public-models/dutchf3_hrnet_patch_section_depth.pth" + elif model == "hrnet" and config.TRAIN.DEPTH == "patch": + url = "https://deepseismicsharedstore.blob.core.windows.net/master-public-models/dutchf3_hrnet_patch_patch_depth.pth" + elif ( + model == "deconvnet" + and "skip" in config.MODEL.NAME + and config.TRAIN.DEPTH == "none" + ): + url = "http://deepseismicsharedstore.blob.core.windows.net/master-public-models/dutchf3_deconvnetskip_patch_no_depth.pth" + + elif ( + model == "deconvnet" + and "skip" not in config.MODEL.NAME + and config.TRAIN.DEPTH == "none" + ): + url = "http://deepseismicsharedstore.blob.core.windows.net/master-public-models/dutchf3_deconvnet_patch_no_depth.pth" + elif model == "unet" and config.TRAIN.DEPTH == "section": + url = "http://deepseismicsharedstore.blob.core.windows.net/master-public-models/dutchf3_seresnetunet_patch_section_depth.pth" + else: + raise NotImplementedError( + "We don't store a pretrained model for Dutch F3 for this model combination yet." + ) else: - raise NotImplementedError("We don't store a pretrained model for this dataset/model combination yet.") + raise NotImplementedError( + "We don't store a pretrained model for this dataset/model combination yet." + ) print(f"Could not find a user-supplied URL, downloading from '{url}'") # make sure the model_dir directory is writeable model_dir = config.TRAIN.MODEL_DIR - if not os.path.isdir(os.path.dirname(model_dir)) or not os.access(os.path.dirname(model_dir), os.W_OK): - print (f"Cannot write to TRAIN.MODEL_DIR={config.TRAIN.MODEL_DIR}") + if not os.path.isdir(os.path.dirname(model_dir)) or not os.access( + os.path.dirname(model_dir), os.W_OK + ): + print(f"Cannot write to TRAIN.MODEL_DIR={config.TRAIN.MODEL_DIR}") home = str(pathlib.Path.home()) model_dir = os.path.join(home, "models") - print (f"Will write to TRAIN.MODEL_DIR={model_dir}") + print(f"Will write to TRAIN.MODEL_DIR={model_dir}") if not os.path.isdir(model_dir): os.makedirs(model_dir) if url: # Download the pretrained model: - pretrained_model_path = os.path.join(model_dir, "pretrained_" + dataset + "_" + model + ".pth") - + pretrained_model_path = os.path.join( + model_dir, "pretrained_" + dataset + "_" + model + ".pth" + ) + # always redownload the model - print(f"Downloading the pretrained model to '{pretrained_model_path}'. This will take a few mintues.. \n") + print( + f"Downloading the pretrained model to '{pretrained_model_path}'. This will take a few mintues.. \n" + ) urllib.request.urlretrieve(url, pretrained_model_path) print("Model successfully downloaded.. \n") else: @@ -359,7 +421,14 @@ def download_pretrained_model(config): # Update config MODEL.PRETRAINED # TODO: Only HRNet uses a pretrained model currently. - opts = ["MODEL.PRETRAINED", pretrained_model_path, "TRAIN.MODEL_DIR", model_dir] + opts = [ + "MODEL.PRETRAINED", + pretrained_model_path, + "TRAIN.MODEL_DIR", + model_dir, + "TEST.MODEL_PATH", + pretrained_model_path, + ] config.merge_from_list(opts) - + return config diff --git a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml index 435be500..9d705df2 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml @@ -13,14 +13,14 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/dutchf3 + ROOT: "/home/username/data/dutch/data" CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: NAME: seg_hrnet IN_CHANNELS: 3 - PRETRAINED: '/mnt/hrnet_pretrained/image_classification/hrnetv2_w48_imagenet_pretrained.pth' + PRETRAINED: "" EXTRA: FINAL_CONV_KERNEL: 1 STAGE2: diff --git a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml index d47a2c8d..d14ea134 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml @@ -12,7 +12,7 @@ SEED: 2019 DATASET: NUM_CLASSES: 6 - ROOT: /mnt/dutchf3 + ROOT: /mnt/dutchf3 CLASS_WEIGHTS: [0.7151, 0.8811, 0.5156, 0.9346, 0.9683, 0.9852] MODEL: diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index 60a9ca00..7696518b 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -10,8 +10,6 @@ """ Modified version of the Alaudah testing script Runs only on single GPU - -Estimated time to run on single V100: 5 hours """ import itertools @@ -25,9 +23,16 @@ import numpy as np import torch import torch.nn.functional as F +from PIL import Image from albumentations import Compose, Normalize, PadIfNeeded, Resize from cv_lib.utils import load_log_configuration from cv_lib.segmentation import models +from cv_lib.segmentation.dutchf3.utils import ( + current_datetime, + generate_path, + git_branch, + git_hash, +) from deepseismic_interpretation.dutchf3.data import ( add_patch_depth_channels, get_seismic_labels, @@ -39,6 +44,8 @@ from torch.utils import data from toolz import take +from matplotlib import cm + _CLASS_NAMES = [ "upper_ns", @@ -57,9 +64,9 @@ def __init__(self, n_classes): def _fast_hist(self, label_true, label_pred, n_class): mask = (label_true >= 0) & (label_true < n_class) - hist = np.bincount(n_class * label_true[mask].astype(int) + label_pred[mask], minlength=n_class ** 2,).reshape( - n_class, n_class - ) + hist = np.bincount( + n_class * label_true[mask].astype(int) + label_pred[mask], minlength=n_class ** 2, + ).reshape(n_class, n_class) return hist def update(self, label_trues, label_preds): @@ -99,6 +106,21 @@ def reset(self): self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) +def normalize(array): + """ + Normalizes a segmentation mask array to be in [0,1] range + """ + min = array.min() + return (array - min) / (array.max() - min) + + +def mask_to_disk(mask, fname): + """ + write segmentation mask to disk using a particular colormap + """ + Image.fromarray(cm.gist_earth(normalize(mask), bytes=True)).save(fname) + + def _transform_CHW_to_HWC(numpy_array): return np.moveaxis(numpy_array, 0, -1) @@ -180,7 +202,9 @@ def _compose_processing_pipeline(depth, aug=None): def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): - hdc_wdx_generator = itertools.product(range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride),) + hdc_wdx_generator = itertools.product( + range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride), + ) for batch_indexes in itertoolz.partition_all(batch_size, hdc_wdx_generator): yield batch_indexes @@ -191,7 +215,9 @@ def _output_processing_pipeline(config, output): _, _, h, w = output.shape if config.TEST.POST_PROCESSING.SIZE != h or config.TEST.POST_PROCESSING.SIZE != w: output = F.interpolate( - output, size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE,), mode="bilinear", + output, + size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE,), + mode="bilinear", ) if config.TEST.POST_PROCESSING.CROP_PIXELS > 0: @@ -206,7 +232,15 @@ def _output_processing_pipeline(config, output): def _patch_label_2d( - model, img, pre_processing, output_processing, patch_size, stride, batch_size, device, num_classes, + model, + img, + pre_processing, + output_processing, + patch_size, + stride, + batch_size, + device, + num_classes, ): """Processes a whole section """ @@ -221,14 +255,19 @@ def _patch_label_2d( # generate output: for batch_indexes in _generate_batches(h, w, ps, patch_size, stride, batch_size=batch_size): batch = torch.stack( - [pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing,) for hdx, wdx in batch_indexes], + [ + pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing,) + for hdx, wdx in batch_indexes + ], dim=0, ) model_output = model(batch.to(device)) for (hdx, wdx), output in zip(batch_indexes, model_output.detach().cpu()): output = output_processing(output) - output_p[:, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size,] += output + output_p[ + :, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size, + ] += output # crop the output_p in the middle output = output_p[:, :, ps:-ps, ps:-ps] @@ -253,12 +292,22 @@ def to_image(label_mask, n_classes=6): def _evaluate_split( - split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, debug=False + split, + section_aug, + model, + pre_processing, + output_processing, + device, + running_metrics_overall, + config, + debug=False, ): logger = logging.getLogger(__name__) TestSectionLoader = get_test_loader(config) - test_set = TestSectionLoader(config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug,) + test_set = TestSectionLoader( + config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug, + ) n_classes = test_set.n_classes @@ -268,6 +317,19 @@ def _evaluate_split( logger.info("Running in Debug/Test mode") test_loader = take(1, test_loader) + try: + output_dir = generate_path( + config.OUTPUT_DIR + "_test", + git_branch(), + git_hash(), + config.MODEL.NAME, + current_datetime(), + ) + except TypeError: + output_dir = generate_path( + config.OUTPUT_DIR + "_test", config.MODEL.NAME, current_datetime(), + ) + running_metrics_split = runningScore(n_classes) # testing mode: @@ -295,6 +357,10 @@ def _evaluate_split( running_metrics_split.update(gt, pred) running_metrics_overall.update(gt, pred) + # dump images to disk for review + mask_to_disk(pred.squeeze(), os.path.join(output_dir, f"{i}_pred.png")) + mask_to_disk(gt.squeeze(), os.path.join(output_dir, f"{i}_gt.png")) + # get scores score, class_iou = running_metrics_split.get_scores() @@ -350,12 +416,17 @@ def test(*options, cfg=None, debug=False): running_metrics_overall = runningScore(n_classes) # Augmentation - section_aug = Compose([Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1,)]) + section_aug = Compose( + [Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1,)] + ) + # TODO: make sure that this is consistent with how normalization and agumentation for train.py patch_aug = Compose( [ Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index ad8600d8..e4567826 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -101,7 +101,6 @@ def run(*options, cfg=None, debug=False): load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK torch.manual_seed(config.SEED) @@ -116,12 +115,15 @@ def run(*options, cfg=None, debug=False): PadIfNeeded( min_height=config.TRAIN.PATCH_SIZE, min_width=config.TRAIN.PATCH_SIZE, - border_mode=cv2.BORDER_CONSTANT, + border_mode=0, always_apply=True, mask_value=255, + value=0, ), Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, + config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, + always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, @@ -158,12 +160,16 @@ def run(*options, cfg=None, debug=False): augmentations=val_aug, ) logger.info(val_set) - n_classes = train_set.n_classes train_loader = data.DataLoader( - train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True, + train_set, + batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, + num_workers=config.WORKERS, + shuffle=True, + ) + val_loader = data.DataLoader( + val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, ) - val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -179,15 +185,12 @@ def run(*options, cfg=None, debug=False): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - try: - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) - except TypeError: - output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) - - summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) - + # learning rate scheduler + scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) + scheduler = CosineAnnealingScheduler( + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration + ) # weights are inversely proportional to the frequency of the classes in the # training set @@ -199,13 +202,40 @@ def run(*options, cfg=None, debug=False): trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + ######################### + # Logging setup below + + try: + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config_file_name, + config.TRAIN.MODEL_DIR, + current_datetime(), + ) + except TypeError: + output_dir = generate_path( + config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(), + ) + + summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) + + # log all training output trainer.add_event_handler( - Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) + + # add logging of learning rate trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + + # log LR to tensorboard trainer.add_event_handler( Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), ) + + # log training summary to tensorboard as well trainer.add_event_handler( Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer), ) @@ -213,12 +243,25 @@ def run(*options, cfg=None, debug=False): def _select_pred_and_mask(model_out_dict): return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) + def _select_max(pred_tensor): + return pred_tensor.max(1)[1] + + def _tensor_to_numpy(pred_tensor): + return pred_tensor.squeeze().cpu().numpy() + + def snapshot_function(): + return (trainer.state.iteration % snapshot_duration) == 0 + + n_classes = train_set.n_classes + evaluator = create_supervised_evaluator( model, prepare_batch, metrics={ "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "pixacc": pixelwise_accuracy(n_classes, output_transform=_select_pred_and_mask, device=device), + "pixacc": pixelwise_accuracy( + n_classes, output_transform=_select_pred_and_mask, device=device + ), "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask), "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask), "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask), @@ -262,12 +305,6 @@ def _select_pred_and_mask(model_out_dict): ), ) - def _select_max(pred_tensor): - return pred_tensor.max(1)[1] - - def _tensor_to_numpy(pred_tensor): - return pred_tensor.squeeze().cpu().numpy() - transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy) transform_pred = compose(transform_func, _select_max) @@ -277,21 +314,19 @@ def _tensor_to_numpy(pred_tensor): ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Mask", "mask", transform_func=transform_func), + create_image_writer( + summary_writer, "Validation/Mask", "mask", transform_func=transform_func + ), ) evaluator.add_event_handler( Events.EPOCH_COMPLETED, - create_image_writer(summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred), + create_image_writer( + summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred + ), ) - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - checkpoint_handler = SnapshotHandler( - output_dir, - config.MODEL.NAME, - extract_metric_from("mIoU"), - snapshot_function, + output_dir, config.MODEL.NAME, extract_metric_from("mIoU"), snapshot_function, ) evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) diff --git a/experiments/interpretation/penobscot/local/train.py b/experiments/interpretation/penobscot/local/train.py index e615140b..eea5361b 100644 --- a/experiments/interpretation/penobscot/local/train.py +++ b/experiments/interpretation/penobscot/local/train.py @@ -194,7 +194,9 @@ def run(*options, cfg=None, debug=False): ) try: - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) + output_dir = generate_path( + config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(), + ) except TypeError: output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) @@ -300,12 +302,7 @@ def _tensor_to_numpy(pred_tensor): def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 - checkpoint_handler = SnapshotHandler( - output_dir, - config.MODEL.NAME, - extract_metric_from("mIoU"), - snapshot_function, - ) + checkpoint_handler = SnapshotHandler(output_dir, config.MODEL.NAME, extract_metric_from("mIoU"), snapshot_function,) evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index 7d272028..4cd710ad 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -246,7 +246,17 @@ def get_random_batch( class SectionLoader(data.Dataset): - def __init__(self, data_dir, split="train", is_transform=True, augmentations=None): + """ + Base class for section data loader + :param str data_dir: Root directory for training/test data + :param str split: split file to use for loading patches + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + :param str seismic_path: Override file path for seismic data + :param str label_path: Override file path for label data + """ + def __init__(self, data_dir, split="train", is_transform=True, augmentations=None, + seismic_path=None, label_path=None): self.split = split self.data_dir = data_dir self.is_transform = is_transform @@ -355,13 +365,34 @@ def transform(self, img, lbl): class TrainSectionLoader(SectionLoader): - def __init__(self, data_dir, split="train", is_transform=True, augmentations=None): + """ + Training data loader for sections + :param str data_dir: Root directory for training/test data + :param str split: split file to use for loading patches + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + :param str seismic_path: Override file path for seismic data + :param str label_path: Override file path for label data + """ + def __init__(self, data_dir, split="train", is_transform=True, augmentations=None, + seismic_path=None, label_path=None): super(TrainSectionLoader, self).__init__( data_dir, split=split, is_transform=is_transform, augmentations=augmentations, + seismic_path=seismic_path, label_path=label_path ) - self.seismic = np.load(_train_data_for(self.data_dir)) - self.labels = np.load(_train_labels_for(self.data_dir)) + if seismic_path is not None and label_path is not None: + # Load npy files (seismc and corresponding labels) from provided + # location (path) + if not path.isfile(seismic_path): + raise Exception(f"{seismic_path} does not exist") + if not path.isfile(label_path): + raise Exception(f"{label_path} does not exist") + self.seismic = np.load(seismic_path) + self.labels = np.load(label_path) + else: + self.seismic = np.load(_train_data_for(self.data_dir)) + self.labels = np.load(_train_labels_for(self.data_dir)) # reading the file names for split txt_path = path.join(self.data_dir, "splits", "section_" + split + ".txt") @@ -371,9 +402,20 @@ def __init__(self, data_dir, split="train", is_transform=True, augmentations=Non class TrainSectionLoaderWithDepth(TrainSectionLoader): - def __init__(self, data_dir, split="train", is_transform=True, augmentations=None): + """ + Section data loader that includes additional channel for depth + :param str data_dir: Root directory for training/test data + :param str split: split file to use for loading patches + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + :param str seismic_path: Override file path for seismic data + :param str label_path: Override file path for label data + """ + def __init__(self, data_dir, split="train", is_transform=True, augmentations=None, + seismic_path=None, label_path=None): super(TrainSectionLoaderWithDepth, self).__init__( data_dir, split=split, is_transform=is_transform, augmentations=augmentations, + seismic_path=seismic_path, label_path=label_path ) self.seismic = add_section_depth_channels(self.seismic) # NCWH @@ -447,9 +489,19 @@ def __getitem__(self, index): class TestSectionLoader(SectionLoader): - def __init__(self, data_dir, split="test1", is_transform=True, augmentations=None): + """ + Test data loader for sections + :param str data_dir: Root directory for training/test data + :param str split: split file to use for loading patches + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + :param str seismic_path: Override file path for seismic data + :param str label_path: Override file path for label data + """ + def __init__(self, data_dir, split = "test1", is_transform = True, augmentations = None, + seismic_path = None, label_path = None): super(TestSectionLoader, self).__init__( - data_dir, split=split, is_transform=is_transform, augmentations=augmentations, + data_dir, split=split, is_transform=is_transform, augmentations=augmentations, ) if "test1" in self.split: @@ -458,6 +510,15 @@ def __init__(self, data_dir, split="test1", is_transform=True, augmentations=Non elif "test2" in self.split: self.seismic = np.load(_test2_data_for(self.data_dir)) self.labels = np.load(_test2_labels_for(self.data_dir)) + elif seismic_path is not None and label_path is not None: + # Load npy files (seismc and corresponding labels) from provided + # location (path) + if not path.isfile(seismic_path): + raise Exception(f"{seismic_path} does not exist") + if not path.isfile(label_path): + raise Exception(f"{label_path} does not exist") + self.seismic = np.load(seismic_path) + self.labels = np.load(label_path) # We are in test mode. Only read the given split. The other one might not # be available. @@ -468,9 +529,20 @@ def __init__(self, data_dir, split="test1", is_transform=True, augmentations=Non class TestSectionLoaderWithDepth(TestSectionLoader): - def __init__(self, data_dir, split="test1", is_transform=True, augmentations=None): + """ + Test data loader for sections that includes additional channel for depth + :param str data_dir: Root directory for training/test data + :param str split: split file to use for loading patches + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + :param str seismic_path: Override file path for seismic data + :param str label_path: Override file path for label data + """ + def __init__(self, data_dir, split="test1", is_transform=True, augmentations=None, + seismic_path = None, label_path = None): super(TestSectionLoaderWithDepth, self).__init__( data_dir, split=split, is_transform=is_transform, augmentations=augmentations, + seismic_path = seismic_path, label_path = label_path ) self.seismic = add_section_depth_channels(self.seismic) # NCWH @@ -518,10 +590,18 @@ def _transform_WH_to_HW(numpy_array): class PatchLoader(data.Dataset): """ - Data loader for the patch-based deconvnet + Base Data loader for the patch-based deconvnet + :param str data_dir: Root directory for training/test data + :param int stride: training data stride + :param int patch_size: Size of patch for training + :param str split: split file to use for loading patches + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + :param str seismic_path: Override file path for seismic data + :param str label_path: Override file path for label data """ - - def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None): + def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None, + seismic_path=None, label_path=None): self.data_dir = data_dir self.is_transform = is_transform self.augmentations = augmentations @@ -576,7 +656,16 @@ def transform(self, img, lbl): class TestPatchLoader(PatchLoader): - def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None): + """ + Test Data loader for the patch-based deconvnet + :param str data_dir: Root directory for training/test data + :param int stride: training data stride + :param int patch_size: Size of patch for training + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + """ + def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None, + txt_path=None): super(TestPatchLoader, self).__init__( data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, ) @@ -587,25 +676,51 @@ def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmen # We are in test mode. Only read the given split. The other one might not # be available. - self.split = "test1" # TODO: Fix this can also be test2 - txt_path = path.join(self.data_dir, "splits", "patch_" + self.split + ".txt") + # If txt_path is not provided, it will be assumed as below. Otherwise, provided path will be used for + # loading txt file and create patches. + if not txt_path: + self.split = "test1" # TODO: Fix this can also be test2 + txt_path = path.join(self.data_dir, "splits", "patch_" + self.split + ".txt") patch_list = tuple(open(txt_path, "r")) patch_list = [id_.rstrip() for id_ in patch_list] self.patches = patch_list class TrainPatchLoader(PatchLoader): + """ + Train data loader for the patch-based deconvnet + :param str data_dir: Root directory for training/test data + :param int stride: training data stride + :param int patch_size: Size of patch for training + :param str split: split file to use for loading patches + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + :param str seismic_path: Override file path for seismic data + :param str label_path: Override file path for label data + """ def __init__( self, data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, + seismic_path=None, label_path=None ): super(TrainPatchLoader, self).__init__( data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, + seismic_path=seismic_path, label_path=label_path ) # self.seismic = self.pad_volume(np.load(seismic_path)) # self.labels = self.pad_volume(np.load(labels_path)) warnings.warn("This no longer pads the volume") - self.seismic = np.load(_train_data_for(self.data_dir)) - self.labels = np.load(_train_labels_for(self.data_dir)) + if seismic_path is not None and label_path is not None: + # Load npy files (seismc and corresponding labels) from provided + # location (path) + if not path.isfile(seismic_path): + raise Exception(f"{seismic_path} does not exist") + if not path.isfile(label_path): + raise Exception(f"{label_path} does not exist") + self.seismic = np.load(seismic_path) + self.labels = np.load(label_path) + else: + self.seismic = np.load(_train_data_for(self.data_dir)) + self.labels = np.load(_train_labels_for(self.data_dir)) # We are in train/val mode. Most likely the test splits are not saved yet, # so don't attempt to load them. self.split = split @@ -617,11 +732,24 @@ def __init__( class TrainPatchLoaderWithDepth(TrainPatchLoader): + """ + Train data loader for the patch-based deconvnet with patch depth channel + :param str data_dir: Root directory for training/test data + :param int stride: training data stride + :param int patch_size: Size of patch for training + :param str split: split file to use for loading patches + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + :param str seismic_path: Override file path for seismic data + :param str label_path: Override file path for label data + """ def __init__( self, data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, + seismic_path=None, label_path=None ): super(TrainPatchLoaderWithDepth, self).__init__( - data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, + data_dir, split=split, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, + seismic_path=seismic_path, label_path=label_path ) def __getitem__(self, index): @@ -664,8 +792,20 @@ def _transform_HWC_to_CHW(numpy_array): class TrainPatchLoaderWithSectionDepth(TrainPatchLoader): + """ + Train data loader for the patch-based deconvnet section depth channel + :param str data_dir: Root directory for training/test data + :param int stride: training data stride + :param int patch_size: Size of patch for training + :param str split: split file to use for loading patches + :param bool is_transform: Transform patch to dimensions expected by PyTorch + :param list augmentations: Data augmentations to apply to patches + :param str seismic_path: Override file path for seismic data + :param str label_path: Override file path for label data + """ def __init__( self, data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, + seismic_path=None, label_path=None ): super(TrainPatchLoaderWithSectionDepth, self).__init__( data_dir, @@ -674,6 +814,8 @@ def __init__( patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, + seismic_path = seismic_path, + label_path = label_path ) self.seismic = add_section_depth_channels(self.seismic) diff --git a/interpretation/deepseismic_interpretation/dutchf3/tests/test_dataloaders.py b/interpretation/deepseismic_interpretation/dutchf3/tests/test_dataloaders.py new file mode 100644 index 00000000..181fc825 --- /dev/null +++ b/interpretation/deepseismic_interpretation/dutchf3/tests/test_dataloaders.py @@ -0,0 +1,325 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +""" +Tests for TrainLoader and TestLoader classes when overriding the file names of the seismic and label data. +""" + +import tempfile +import numpy as np +from interpretation.deepseismic_interpretation.dutchf3.data import get_test_loader, TrainPatchLoaderWithDepth, TrainSectionLoaderWithDepth +import pytest +import yacs.config +import os + +# npy files dimensions +IL = 5 +XL = 10 +D = 8 + +CONFIG_FILE = "./examples/interpretation/notebooks/configs/unet.yaml" +with open(CONFIG_FILE, "rt") as f_read: + config = yacs.config.load_cfg(f_read) + + +def generate_npy_files(path, data): + np.save(path, data) + + +def assert_dimensions(test_section_loader): + assert test_section_loader.labels.shape[0] == IL + assert test_section_loader.labels.shape[1] == XL + assert test_section_loader.labels.shape[2] == D + + # Because add_section_depth_channels method add + # 2 extra channels to a 1 channel section + assert test_section_loader.seismic.shape[0] == IL + assert test_section_loader.seismic.shape[2] == XL + assert test_section_loader.seismic.shape[3] == D + + +def test_TestSectionLoader_should_load_data_from_test1_set(): + with open(CONFIG_FILE, "rt") as f_read: + config = yacs.config.load_cfg(f_read) + + with tempfile.TemporaryDirectory() as data_dir: + os.makedirs(os.path.join(data_dir, "test_once")) + os.makedirs(os.path.join(data_dir, "splits")) + + seimic = np.zeros([IL, XL, D]) + generate_npy_files(os.path.join(data_dir, "test_once", "test1_seismic.npy"), seimic) + + labels = np.ones([IL, XL, D]) + generate_npy_files(os.path.join(data_dir, "test_once", "test1_labels.npy"), labels) + + txt_path = os.path.join(data_dir, "splits", "section_test1.txt") + open(txt_path, 'a').close() + + TestSectionLoader = get_test_loader(config) + test_set = TestSectionLoader(data_dir = data_dir, split = 'test1') + + assert_dimensions(test_set) + + +def test_TestSectionLoader_should_load_data_from_test2_set(): + with tempfile.TemporaryDirectory() as data_dir: + os.makedirs(os.path.join(data_dir, "test_once")) + os.makedirs(os.path.join(data_dir, "splits")) + + seimic = np.zeros([IL, XL, D]) + generate_npy_files(os.path.join(data_dir, "test_once", "test2_seismic.npy"), seimic) + + A = np.load(os.path.join(data_dir, "test_once", "test2_seismic.npy")) + + labels = np.ones([IL, XL, D]) + generate_npy_files(os.path.join(data_dir, "test_once", "test2_labels.npy"), labels) + + txt_path = os.path.join(data_dir, "splits", "section_test2.txt") + open(txt_path, 'a').close() + + TestSectionLoader = get_test_loader(config) + test_set = TestSectionLoader(data_dir = data_dir, split = 'test2') + + assert_dimensions(test_set) + + +def test_TestSectionLoader_should_load_data_from_path_override_data(): + with tempfile.TemporaryDirectory() as data_dir: + os.makedirs(os.path.join(data_dir, "volume_name")) + os.makedirs(os.path.join(data_dir, "splits")) + + seimic = np.zeros([IL, XL, D]) + generate_npy_files(os.path.join(data_dir, "volume_name", "seismic.npy"), seimic) + + labels = np.ones([IL, XL, D]) + generate_npy_files(os.path.join(data_dir, "volume_name", "labels.npy"), labels) + + txt_path = os.path.join(data_dir, "splits", "section_volume_name.txt") + open(txt_path, 'a').close() + + TestSectionLoader = get_test_loader(config) + test_set = TestSectionLoader(data_dir = data_dir, + split = "volume_name", + is_transform = True, + augmentations = None, + seismic_path = os.path.join(data_dir, "volume_name", "seismic.npy"), + label_path = os.path.join(data_dir, "volume_name", "labels.npy")) + + assert_dimensions(test_set) + +def test_TrainSectionLoaderWithDepth_should_fail_on_empty_file_names(tmpdir): + """ + Check for exception when files do not exist + """ + + # Test + with pytest.raises(Exception) as excinfo: + + _ = TrainSectionLoaderWithDepth( + data_dir = tmpdir, + split = "volume_name", + is_transform=True, + augmentations=None, + seismic_path = "", + label_path = "" + ) + assert "does not exist" in str(excinfo.value) + + +def test_TrainSectionLoaderWithDepth_should_fail_on_missing_seismic_file(tmpdir): + """ + Check for exception when training param is empty + """ + # Setup + os.makedirs(os.path.join(tmpdir, "volume_name")) + os.makedirs(os.path.join(tmpdir, "splits")) + + labels = np.ones([IL, XL, D]) + generate_npy_files(os.path.join(tmpdir, "volume_name", "labels.npy"), labels) + + txt_path = os.path.join(tmpdir, "splits", "patch_volume_name.txt") + open(txt_path, 'a').close() + + # Test + with pytest.raises(Exception) as excinfo: + + _ = TrainSectionLoaderWithDepth( + data_dir = tmpdir, + split = "volume_name", + is_transform=True, + augmentations=None, + seismic_path=os.path.join(tmpdir, "volume_name", "seismic.npy"), + label_path=os.path.join(tmpdir, "volume_name", "labels.npy") + ) + assert "does not exist" in str(excinfo.value) + + +def test_TrainSectionLoaderWithDepth_should_fail_on_missing_label_file(tmpdir): + """ + Check for exception when training param is empty + """ + # Setup + os.makedirs(os.path.join(tmpdir, "volume_name")) + os.makedirs(os.path.join(tmpdir, "splits")) + + labels = np.ones([IL, XL, D]) + generate_npy_files(os.path.join(tmpdir, "volume_name", "labels.npy"), labels) + + txt_path = os.path.join(tmpdir, "splits", "patch_volume_name.txt") + open(txt_path, 'a').close() + + # Test + with pytest.raises(Exception) as excinfo: + + _ = TrainSectionLoaderWithDepth( + data_dir = tmpdir, + split = "volume_name", + is_transform=True, + augmentations=None, + seismic_path=os.path.join(tmpdir, "volume_name", "seismic.npy"), + label_path=os.path.join(tmpdir, "volume_name", "labels.npy") + ) + assert "does not exist" in str(excinfo.value) + + +def test_TrainSectionLoaderWithDepth_should_load_with_one_train_and_label_file(tmpdir): + """ + Check for successful class instantiation w/ single npy file for train & label + """ + # Setup + os.makedirs(os.path.join(tmpdir, "volume_name")) + os.makedirs(os.path.join(tmpdir, "splits")) + + seimic = np.zeros([IL, XL, D]) + generate_npy_files(os.path.join(tmpdir, "volume_name", "seismic.npy"), seimic) + + labels = np.ones([IL, XL, D]) + generate_npy_files(os.path.join(tmpdir, "volume_name", "labels.npy"), labels) + + txt_path = os.path.join(tmpdir, "splits", "section_volume_name.txt") + open(txt_path, 'a').close() + + # Test + train_set = TrainSectionLoaderWithDepth( + data_dir = tmpdir, + split = "volume_name", + is_transform=True, + augmentations=None, + seismic_path=os.path.join(tmpdir, "volume_name", "seismic.npy"), + label_path=os.path.join(tmpdir, "volume_name", "labels.npy") + ) + + assert train_set.labels.shape == (IL, XL, D) + assert train_set.seismic.shape == (IL, 3, XL, D) + + +def test_TrainPatchLoaderWithDepth_should_fail_on_empty_file_names(tmpdir): + """ + Check for exception when files do not exist + """ + # Test + with pytest.raises(Exception) as excinfo: + + _ = TrainPatchLoaderWithDepth( + data_dir = tmpdir, + split = "volume_name", + is_transform=True, + stride=25, + patch_size=100, + augmentations=None, + seismic_path = "", + label_path = "" + ) + assert "does not exist" in str(excinfo.value) + + +def test_TrainPatchLoaderWithDepth_should_fail_on_missing_seismic_file(tmpdir): + """ + Check for exception when training param is empty + """ + # Setup + os.makedirs(os.path.join(tmpdir, "volume_name")) + os.makedirs(os.path.join(tmpdir, "splits")) + + labels = np.ones([IL, XL, D]) + generate_npy_files(os.path.join(tmpdir, "volume_name", "labels.npy"), labels) + + txt_path = os.path.join(tmpdir, "splits", "patch_volume_name.txt") + open(txt_path, 'a').close() + + # Test + with pytest.raises(Exception) as excinfo: + + _ = TrainPatchLoaderWithDepth( + data_dir = tmpdir, + split = "volume_name", + is_transform=True, + stride=25, + patch_size=100, + augmentations=None, + seismic_path=os.path.join(tmpdir, "volume_name", "seismic.npy"), + label_path=os.path.join(tmpdir, "volume_name", "labels.npy") + ) + assert "does not exist" in str(excinfo.value) + + +def test_TrainPatchLoaderWithDepth_should_fail_on_missing_label_file(tmpdir): + """ + Check for exception when training param is empty + """ + # Setup + os.makedirs(os.path.join(tmpdir, "volume_name")) + os.makedirs(os.path.join(tmpdir, "splits")) + + seimic = np.zeros([IL, XL, D]) + generate_npy_files(os.path.join(tmpdir, "volume_name", "seismic.npy"), seimic) + + txt_path = os.path.join(tmpdir, "splits", "patch_volume_name.txt") + open(txt_path, 'a').close() + + # Test + with pytest.raises(Exception) as excinfo: + + _ = TrainPatchLoaderWithDepth( + data_dir = tmpdir, + split = "volume_name", + is_transform=True, + stride=25, + patch_size=100, + augmentations=None, + seismic_path=os.path.join(tmpdir, "volume_name", "seismic.npy"), + label_path=os.path.join(tmpdir, "volume_name", "labels.npy") + ) + assert "does not exist" in str(excinfo.value) + + +def test_TrainPatchLoaderWithDepth_should_load_with_one_train_and_label_file(tmpdir): + """ + Check for successful class instantiation w/ single npy file for train & label + """ + # Setup + os.makedirs(os.path.join(tmpdir, "volume_name")) + os.makedirs(os.path.join(tmpdir, "splits")) + + seimic = np.zeros([IL, XL, D]) + generate_npy_files(os.path.join(tmpdir, "volume_name", "seismic.npy"), seimic) + + labels = np.ones([IL, XL, D]) + generate_npy_files(os.path.join(tmpdir, "volume_name", "labels.npy"), labels) + + txt_path = os.path.join(tmpdir, "splits", "patch_volume_name.txt") + open(txt_path, 'a').close() + + # Test + train_set = TrainPatchLoaderWithDepth( + data_dir = tmpdir, + split = "volume_name", + is_transform=True, + stride=25, + patch_size=100, + augmentations=None, + seismic_path=os.path.join(tmpdir, "volume_name", "seismic.npy"), + label_path=os.path.join(tmpdir, "volume_name", "labels.npy") + ) + + assert train_set.labels.shape == (IL, XL, D) + assert train_set.seismic.shape == (IL, XL, D) diff --git a/scripts/prepare_dutchf3.py b/scripts/prepare_dutchf3.py index be0a60eb..5c5a4e08 100644 --- a/scripts/prepare_dutchf3.py +++ b/scripts/prepare_dutchf3.py @@ -9,49 +9,62 @@ import logging.config import math import warnings -from os import path +from os import path, mkdir import fire import numpy as np from sklearn.model_selection import train_test_split -def _get_splits_path(data_dir): - return path.join(data_dir, "splits") - - -def _get_labels_path(data_dir): - return path.join(data_dir, "train", "train_labels.npy") - - -def _write_split_files(splits_path, train_list, test_list, loader_type): - file_object = open(path.join(splits_path, loader_type + "_train_val.txt"), "w") - file_object.write("\n".join(train_list + test_list)) +def _write_split_files(splits_path, train_list, val_list, loader_type): + if not path.isdir(splits_path): + mkdir(splits_path) + file_object = open(path.join(splits_path, + loader_type + "_train_val.txt"), "w") + file_object.write("\n".join(train_list + val_list)) file_object.close() - file_object = open(path.join(splits_path, loader_type + "_train.txt"), "w") + file_object = open(path.join(splits_path, + loader_type + "_train.txt"), "w") file_object.write("\n".join(train_list)) file_object.close() - file_object = open(path.join(splits_path, loader_type + "_val.txt"), "w") - file_object.write("\n".join(test_list)) + file_object = open(path.join(splits_path, + loader_type + "_val.txt"), "w") + file_object.write("\n".join(val_list)) file_object.close() -def _get_aline_range(aline, per_val): - # Inline sections - test_aline = math.floor(aline * per_val / 2) - test_aline_range = itertools.chain(range(0, test_aline), range(aline - test_aline, aline)) - train_aline_range = range(test_aline, aline - test_aline) +def _get_aline_range(aline, per_val, slice_steps): + try: + if slice_steps < 1: + raise ValueError('slice_steps cannot be zero or a negative number') + # Inline and Crossline sections + val_aline = math.floor(aline * per_val / 2) + val_aline_range = itertools.chain(range(0, val_aline), + range(aline - val_aline, aline)) + train_aline_range = range(val_aline, aline - val_aline, slice_steps) - return train_aline_range, test_aline_range + print("aline: ", aline) + print("val_aline: ", val_aline) + return train_aline_range, val_aline_range + except (Exception, ValueError): + raise -def split_section_train_val(data_dir, per_val=0.2, log_config=None): +def split_section_train_val(data_dir, output_dir, label_file, per_val=0.2, + log_config=None, slice_steps=1): """Generate train and validation files for Netherlands F3 dataset. Args: data_dir (str): data directory path - per_val (float, optional): the fraction of the volume to use for validation. - Defaults to 0.2. + output_dir (str): directory under data_dir to store the split files + label_file (str): npy files with labels. Stored in data_dir + per_val (float, optional): the fraction of the volume to use for + validation. Defaults to 0.2. + log_config (str): path to log configurations + slice_steps (int): increment to the slices count. + If slice_steps > 1 the function will skip: + slice_steps - 1 slice. + Defaults to 1, do not skip any slice. """ if log_config is not None: @@ -62,39 +75,50 @@ def split_section_train_val(data_dir, per_val=0.2, log_config=None): logger.info("Splitting data into sections .... ") logger.info(f"Reading data from {data_dir}") - labels_path = _get_labels_path(data_dir) - logger.info(f"Loading {labels_path}") - labels = np.load(labels_path) + logger.info(f"Loading {label_file}") + labels = np.load(label_file) logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") iline, xline, _ = labels.shape # Inline sections - train_iline_range, test_iline_range = _get_aline_range(iline, per_val) + train_iline_range, val_iline_range = _get_aline_range(iline, + per_val, + slice_steps) train_i_list = ["i_" + str(i) for i in train_iline_range] - test_i_list = ["i_" + str(i) for i in test_iline_range] + val_i_list = ["i_" + str(i) for i in val_iline_range] # Xline sections - train_xline_range, test_xline_range = _get_aline_range(xline, per_val) + train_xline_range, val_xline_range = _get_aline_range(xline, + per_val, + slice_steps) train_x_list = ["x_" + str(x) for x in train_xline_range] - test_x_list = ["x_" + str(x) for x in test_xline_range] + val_x_list = ["x_" + str(x) for x in val_xline_range] train_list = train_x_list + train_i_list - test_list = test_x_list + test_i_list + val_list = val_x_list + val_i_list # write to files to disk - splits_path = _get_splits_path(data_dir) - _write_split_files(splits_path, train_list, test_list, "section") + logger.info(f"Writing {output_dir}") + _write_split_files(output_dir, train_list, val_list, "section") -def split_patch_train_val(data_dir, stride, patch_size, per_val=0.2, log_config=None): +def split_patch_train_val(data_dir, output_dir, label_file, stride, patch_size, + slice_steps=1, per_val=0.2, log_config=None): """Generate train and validation files for Netherlands F3 dataset. Args: data_dir (str): data directory path + output_dir (str): directory under data_dir to store the split files + label_file (str): npy files with labels. Stored in data_dir stride (int): stride to use when sectioning of the volume patch_size (int): size of patch to extract - per_val (float, optional): the fraction of the volume to use for validation. - Defaults to 0.2. + per_val (float, optional): the fraction of the volume to use for + validation. Defaults to 0.2. + log_config (str): path to log configurations + slice_steps (int): increment to the slices count. + If slice_steps > 1 the function will skip: + slice_steps - 1 slice. + Defaults to 1, do not skip any slice. """ if log_config is not None: @@ -105,66 +129,112 @@ def split_patch_train_val(data_dir, stride, patch_size, per_val=0.2, log_config= logger.info("Splitting data into patches .... ") logger.info(f"Reading data from {data_dir}") - labels_path = _get_labels_path(data_dir) - logger.info(f"Loading {labels_path}") - labels = np.load(labels_path) + logger.info(f"Loading {label_file}") + labels = np.load(label_file) logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") iline, xline, depth = labels.shape # Inline sections - train_iline_range, test_iline_range = _get_aline_range(iline, per_val) + train_iline_range, val_iline_range = _get_aline_range(iline, + per_val, + slice_steps) # Xline sections - train_xline_range, test_xline_range = _get_aline_range(xline, per_val) + train_xline_range, val_xline_range = _get_aline_range(xline, + per_val, + slice_steps) # Generate patches from sections - # Process inlines - horz_locations = range(0, xline - patch_size + 1, stride) - vert_locations = range(0, depth - patch_size + 1, stride) - logger.debug("Generating Inline patches") - logger.debug(horz_locations) + # Vertical locations is common to all patches processed + vert_locations = range(0, depth - patch_size, patch_size) logger.debug(vert_locations) + # Process inlines def _i_extract_patches(iline_range, horz_locations, vert_locations): for i in iline_range: - locations = ([j, k] for j in horz_locations for k in vert_locations) + locations = ([j, k] for j in horz_locations + for k in vert_locations) for j, k in locations: yield "i_" + str(i) + "_" + str(j) + "_" + str(k) - test_iline_range = list(test_iline_range) - test_i_list = list(_i_extract_patches(test_iline_range, horz_locations, vert_locations)) - train_i_list = list(_i_extract_patches(train_iline_range, horz_locations, vert_locations)) + # Process inlines - train + logger.debug("Generating Inline patches") + logger.debug("Generating Inline patches - Train") + # iline = xline x depth + val_iline = math.floor(xline * per_val / 2) + logger.debug(val_iline) + + # Process ilines - train + horz_locations_train = range(val_iline, xline - val_iline, max(1,patch_size)) + logger.debug(horz_locations_train) + train_i_list = list(_i_extract_patches(train_iline_range, + horz_locations_train, + vert_locations)) + + # val_iline - define size of the validation set for the fist part + val_iline_range = list(val_iline_range) + + # Process inlines - validation + horz_locations_val = itertools.chain(range(0, val_iline, max(1,patch_size)), + range(xline - val_iline, xline, max(1,patch_size))) + val_iline_range = list(val_iline_range) + val_i_list = list(_i_extract_patches(val_iline_range, + horz_locations_val, + vert_locations)) + logger.debug(train_iline_range) - logger.debug(test_iline_range) + logger.debug(val_iline_range) # Process crosslines - horz_locations = range(0, iline - patch_size + 1, stride) - vert_locations = range(0, depth - patch_size + 1, stride) - logger.debug("Generating Crossline patches") - logger.debug(horz_locations) - logger.debug(vert_locations) - def _x_extract_patches(xline_range, horz_locations, vert_locations): for j in xline_range: - locations = ([i, k] for i in horz_locations for k in vert_locations) + locations = ([i, k] for i in horz_locations + for k in vert_locations) for i, k in locations: yield "x_" + str(i) + "_" + str(j) + "_" + str(k) - test_xline_range = list(test_xline_range) - test_x_list = list(_x_extract_patches(test_xline_range, horz_locations, vert_locations)) - train_x_list = list(_x_extract_patches(train_xline_range, horz_locations, vert_locations)) + + logger.debug("Generating Crossline patches") + logger.debug("Generating Crossline patches - Train") + # xline = iline x depth + val_xline = math.floor(iline * per_val / 2) + logger.debug(val_xline) + + # Process xlines - train + horz_locations_train = range(val_xline, iline - val_xline, max(1,patch_size)) + logger.debug(horz_locations_train) + train_x_list = list(_x_extract_patches(train_xline_range, + horz_locations_train, + vert_locations)) + + # val_xline - define size of the validation set for the fist part + val_xline_range = list(val_xline_range) + + # Process xlines - validation + horz_locations_val = itertools.chain(range(0, val_xline, max(1,patch_size)), + range(iline - val_xline, iline, max(1,patch_size))) + val_xline_range = list(val_xline_range) + val_x_list = list(_x_extract_patches(val_xline_range, + horz_locations_val, + vert_locations)) + logger.debug(train_xline_range) - logger.debug(test_xline_range) + logger.debug(val_xline_range) train_list = train_x_list + train_i_list - test_list = test_x_list + test_i_list + val_list = val_x_list + val_i_list - # write to files to disk: - # NOTE: This isn't quite right we should calculate the patches again for the whole volume - splits_path = _get_splits_path(data_dir) - _write_split_files(splits_path, train_list, test_list, "patch") + logger.debug(train_list) + logger.debug(val_list) -_LOADER_TYPES = {"section": split_section_train_val, "patch": split_patch_train_val} + # write to files to disk: + # NOTE: This isn't quite right we should calculate the patches + # again for the whole volume + logger.info(f"Writing {output_dir}") + _write_split_files(output_dir, train_list, val_list, "patch") + +_LOADER_TYPES = {"section": split_section_train_val, + "patch": split_patch_train_val} def get_split_function(loader_type): @@ -253,48 +323,89 @@ def split_alaudah_et_al_19(data_dir, stride, patch_size, fraction_validation=0.2 list_train_val = i_list + x_list - # create train and test splits: - train_list, test_list = train_test_split(list_train_val, test_size=fraction_validation, shuffle=True) + # create train and validation splits: + train_list, val_list = train_test_split(list_train_val, val_size=fraction_validation, shuffle=True) # write to files to disk: splits_path = _get_splits_path(data_dir) - _write_split_files(splits_path, train_list, test_list, loader_type) + _write_split_files(splits_path, train_list, val_list, loader_type) # TODO: Try https://github.com/Chilipp/docrep for doscstring reuse class SplitTrainValCLI(object): - def section(self, data_dir, per_val=0.2, log_config="logging.conf"): - """Generate section based train and validation files for Netherlands F3 dataset. + def section(self, data_dir, label_file, per_val=0.2, + log_config="logging.conf", output_dir=None, + slice_steps=1): + """Generate section based train and validation files for Netherlands F3 + dataset. Args: data_dir (str): data directory path - per_val (float, optional): the fraction of the volume to use for validation. - Defaults to 0.2. + output_dir (str): directory under data_dir to store the split files + label_file (str): npy files with labels. Stored in data_dir + per_val (float, optional): the fraction of the volume to use for + validation. Defaults to 0.2. log_config (str): path to log configurations + slice_steps (int): increment to the slices count. + If slice_steps > 1 the function will skip: + slice_steps - 1 slice. + Defaults to 1, do not skip any slice. """ - return split_section_train_val(data_dir, per_val=per_val, log_config=log_config) - - def patch(self, data_dir, stride, patch_size, per_val=0.2, log_config="logging.conf"): - """Generate patch based train and validation files for Netherlands F3 dataset. + if data_dir is not None: + label_file = path.join(data_dir, label_file) + output_dir = path.join(data_dir, output_dir) + return split_section_train_val(data_dir=data_dir, + output_dir=output_dir, + label_file=label_file, + slice_steps=slice_steps, + per_val=per_val, + log_config=log_config) + + def patch(self, label_file, stride, patch_size, + per_val=0.2, log_config="logging.conf", + data_dir=None, output_dir=None, slice_steps=1): + """Generate train and validation files for Netherlands F3 dataset. Args: data_dir (str): data directory path + output_dir (str): directory under data_dir to store the split files + label_file (str): npy files with labels. Stored in data_dir stride (int): stride to use when sectioning of the volume patch_size (int): size of patch to extract - per_val (float, optional): the fraction of the volume to use for validation. - Defaults to 0.2. + per_val (float, optional): the fraction of the volume to use for + validation. Defaults to 0.2. log_config (str): path to log configurations + slice_steps (int): increment to the slices count. + If slice_steps > 1 the function will skip: + slice_steps - 1 slice. + Defaults to 1, do not skip any slice. """ - return split_patch_train_val(data_dir, stride, patch_size, per_val=per_val, log_config=log_config) + if data_dir is not None: + label_file = path.join(data_dir, label_file) + output_dir = path.join(data_dir, output_dir) + + return split_patch_train_val(data_dir=data_dir, + output_dir=output_dir, + label_file=label_file, + stride=stride, + patch_size=patch_size, + slice_steps=slice_steps, + per_val=per_val, + log_config=log_config) if __name__ == "__main__": """Example: - python prepare_data.py split_train_val section --data-dir=/mnt/dutch + python prepare_data.py split_train_val section --data_dir=data \ + --label_file=label_file.npy --output_dir=splits --slice_steps=2 or - python prepare_data.py split_train_val patch --data-dir=/mnt/dutch --stride=50 --patch_size=100 - + python prepare_dutchf3.py split_train_val patch --data_dir=data \ + --label_file=label_file.npy --output_dir=splits --stride=50 \ + --patch_size=100 --slice_steps=2 """ fire.Fire( - {"split_train_val": SplitTrainValCLI, "split_alaudah_et_al_19": split_alaudah_et_al_19,} - ) + {"split_train_val": SplitTrainValCLI} + # commenting the following line as this was not updated with + # the new parameters names + # "split_alaudah_et_al_19": split_alaudah_et_al_19} + ) \ No newline at end of file diff --git a/tests/cicd/aml_build.yml b/tests/cicd/aml_build.yml new file mode 100644 index 00000000..a443e124 --- /dev/null +++ b/tests/cicd/aml_build.yml @@ -0,0 +1,54 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Pull request against these branches will trigger this build +pr: +- master +- staging +- contrib + +# Any commit to this branch will trigger the build. +trigger: +- master +- staging +- contrib + +jobs: + +# partially disable setup for now - done manually on build VM +- job: setup + timeoutInMinutes: 10 + displayName: Setup + pool: + name: deepseismicagentpool + + steps: + - bash: | + # terminate as soon as any internal script fails + set -e + + echo "Running setup..." + pwd + ls + git branch + uname -ra + + # ENABLE ALL FOLLOWING CODE WHEN YOU'RE READY TO ADD AML BUILD - disabled right now + # ./scripts/env_reinstall.sh + # use hardcoded root for now because not sure how env changes under ADO policy + # DATA_ROOT="/home/alfred/data_dynamic" + # ./tests/cicd/src/scripts/get_data_for_builds.sh ${DATA_ROOT} + # copy your model files like so - using dummy file to illustrate + # azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name + +- job: AML_job_placeholder + dependsOn: setup + timeoutInMinutes: 5 + displayName: AML job placeholder + pool: + name: deepseismicagentpool + steps: + - bash: | + # UNCOMMENT THIS WHEN YOU HAVE UNCOMMENTED THE SETUP JOB + # source activate seismic-interpretation + echo "TADA!!" diff --git a/tests/cicd/component_governance.yml b/tests/cicd/component_governance.yml index cae6b7a9..172fb40a 100644 --- a/tests/cicd/component_governance.yml +++ b/tests/cicd/component_governance.yml @@ -10,10 +10,12 @@ pr: - master - staging +- contrib trigger: - master - staging +- contrib pool: vmImage: 'ubuntu-latest' diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 4e0f145c..7a574389 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -5,14 +5,15 @@ pr: - master - staging +- contrib # Any commit to this branch will trigger the build. trigger: - master - staging +- contrib jobs: -# partially disable setup for now - done manually on build VM - job: setup timeoutInMinutes: 10 displayName: Setup @@ -40,19 +41,44 @@ jobs: azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name -- job: unit_tests_job +- job: cv_lib_unit_tests_job + dependsOn: setup + timeoutInMinutes: 5 + displayName: cv_lib Unit Tests Job + pool: + name: deepseismicagentpool + steps: + - bash: | + echo "Starting cv_lib unit tests" + source activate seismic-interpretation + pytest --durations=0 cv_lib/tests/ + echo "cv_lib unit test job passed" + +- job: scripts_unit_tests_job dependsOn: setup timeoutInMinutes: 5 displayName: Unit Tests Job pool: name: deepseismicagentpool steps: + - bash: | + echo "Starting scripts unit tests" + source activate seismic-interpretation + pytest --durations=0 tests/ + echo "Script unit test job passed" + +- job: dutchf3_unit_tests_job + dependsOn: setup + timeoutInMinutes: 5 + displayName: DutchF3 Unit Tests Job + pool: + name: deepseismicagentpool + steps: - bash: | echo "Starting unit tests" source activate seismic-interpretation - pytest --durations=0 cv_lib/tests/ + pytest --durations=0 interpretation/deepseismic_interpretation/dutchf3/tests echo "Unit test job passed" - ################################################################################################### # LOCAL PATCH JOBS @@ -69,7 +95,7 @@ jobs: conda env list source activate seismic-interpretation # run the tests - cd experiments/interpretation/penobscot/local + cd experiments/interpretation/penobscot/local python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ diff --git a/tests/cicd/notebooks_build.yml b/tests/cicd/notebooks_build.yml index 3d394cea..90387e6c 100644 --- a/tests/cicd/notebooks_build.yml +++ b/tests/cicd/notebooks_build.yml @@ -5,15 +5,16 @@ pr: - master - staging +- contrib # Any commit to this branch will trigger the build. trigger: - master - staging +- contrib jobs: -# partially disable setup for now - done manually on build VM - job: setup timeoutInMinutes: 10 displayName: Setup @@ -41,20 +42,6 @@ jobs: # copy your model files like so - using dummy file to illustrate azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name -- job: HRNET_demo - dependsOn: setup - timeoutInMinutes: 5 - displayName: HRNET demo - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - pytest -s tests/cicd/src/notebook_integration_tests.py \ - --nbname examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb \ - --dataset_root /home/alfred/data_dynamic/penobscot \ - --model_pretrained /home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth - - job: F3_block_training_and_evaluation_local dependsOn: setup timeoutInMinutes: 5 @@ -62,9 +49,9 @@ jobs: pool: name: deepseismicagentpool steps: - - bash: | - source activate seismic-interpretation + - bash: | + source activate seismic-interpretation pytest -s tests/cicd/src/notebook_integration_tests.py \ - --nbname examples/interpretation/notebooks/F3_block_training_and_evaluation_local.ipynb \ + --nbname examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb \ --dataset_root /home/alfred/data_dynamic/dutch_f3/data \ - --model_pretrained /home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth + --model_pretrained download diff --git a/tests/cicd/src/notebook_integration_tests.py b/tests/cicd/src/notebook_integration_tests.py index 6a705e41..98ac84d8 100644 --- a/tests/cicd/src/notebook_integration_tests.py +++ b/tests/cicd/src/notebook_integration_tests.py @@ -15,9 +15,9 @@ def test_notebook_run(nbname, dataset_root, model_pretrained, cwd): OUTPUT_NOTEBOOK, kernel_name=KERNEL_NAME, parameters={ - "max_iterations": 3, "max_epochs": 1, "max_snapshots": 1, + "papermill": True, "dataset_root": dataset_root, "model_pretrained": model_pretrained, }, diff --git a/tests/cicd/src/scripts/get_data_for_builds.sh b/tests/cicd/src/scripts/get_data_for_builds.sh index d78e566b..e9302e14 100755 --- a/tests/cicd/src/scripts/get_data_for_builds.sh +++ b/tests/cicd/src/scripts/get_data_for_builds.sh @@ -39,5 +39,5 @@ DATA_F3="${DATA_F3}/data" # test preprocessing scripts cd scripts python prepare_penobscot.py split_inline --data-dir=${DATA_PENOBSCOT} --val-ratio=.1 --test-ratio=.2 -python prepare_dutchf3.py split_train_val section --data-dir=${DATA_F3} -python prepare_dutchf3.py split_train_val patch --data-dir=${DATA_F3} --stride=50 --patch_size=100 +python prepare_dutchf3.py split_train_val section --data_dir=${DATA_F3} --label_file=train/train_labels.npy --output_dir=splits +python prepare_dutchf3.py split_train_val patch --data_dir=${DATA_F3} --label_file=train/train_labels.npy --output_dir=splits --stride=50 --patch_size=100 diff --git a/tests/test_prepare_dutchf3.py b/tests/test_prepare_dutchf3.py new file mode 100644 index 00000000..1484ee49 --- /dev/null +++ b/tests/test_prepare_dutchf3.py @@ -0,0 +1,318 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +"""Test the extract functions against a variety of SEGY files and trace_header scenarioes +""" +import pytest +import numpy as np +import pandas as pd +import tempfile +import scripts.prepare_dutchf3 as prep_dutchf3 +import math + +# Setup +OUTPUT = None +ILINE = 551 +XLINE = 1008 +DEPTH = 351 +ALINE = np.zeros((ILINE, XLINE, DEPTH)) +STRIDE = 100 +PATCH = 50 +PER_VAL = 0.2 +LOG_CONFIG = None + + +def test_get_aline_range_step_one(): + + """check if it includes the step in the range if step = 1 + """ + SLICE_STEPS = 1 + + # Test + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + + assert str(output_iline[0].step) == str(SLICE_STEPS) + assert str(output_xline[0].step) == str(SLICE_STEPS) + + +def test_get_aline_range_step_zero(): + + """check if a ValueError exception is raised when slice_steps = 0 + """ + with pytest.raises(ValueError, match=r'slice_steps cannot be zero or a negative number'): + SLICE_STEPS = 0 + + # Test + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + + assert output_iline + assert output_xline + + +def test_get_aline_range_negative_step(): + + """check if a ValueError exception is raised when slice_steps = -1 + """ + with pytest.raises(ValueError, match='slice_steps cannot be zero or a negative number'): + SLICE_STEPS = -1 + + # Test + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + + assert output_iline + assert output_xline + + +def test_get_aline_range_float_step(): + + """check if a ValueError exception is raised when slice_steps = 1.1 + """ + with pytest.raises(TypeError, match="'float' object cannot be interpreted as an integer"): + SLICE_STEPS = 1. + + # Test + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + + assert output_iline + assert output_xline + + +def test_get_aline_range_single_digit_step(): + + """check if it includes the step in the range if 1 < step < 10 + """ + SLICE_STEPS = 1 + # Test + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + + assert str(output_iline[0].step) == str(SLICE_STEPS) + assert str(output_xline[0].step) == str(SLICE_STEPS) + + +def test_get_aline_range_double_digit_step(): + + """check if it includes the step in the range if step > 10 + """ + SLICE_STEPS = 17 + # Test + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + + assert str(output_iline[0].step) == str(SLICE_STEPS) + assert str(output_xline[0].step) == str(SLICE_STEPS) + + +def test_prepare_dutchf3_patch_step_1(): + + """check a complete run for the script in case further changes are needed + """ + # setting a value to SLICE_STEPS as needed to test the values + SLICE_STEPS = 1 + + # use a temp dir that will be discarded at the end of the execution + with tempfile.TemporaryDirectory() as tmpdirname: + + # saving the file to be used by the script + label_file = tmpdirname + '/label_file.npy' + np.save(label_file, ALINE) + + # stting the output directory to be used by the script + output = tmpdirname + '/split' + + # calling the main function of the script without SLICE_STEPS, to check default value + prep_dutchf3.split_patch_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file, + slice_steps=SLICE_STEPS, stride=STRIDE, patch_size=PATCH, per_val=PER_VAL,log_config=LOG_CONFIG) + + # reading the file and splitting the data + patch_train = pd.read_csv(output + '/patch_train.txt', header=None, names=['row', 'a', 'b']) + patch_train = pd.DataFrame(patch_train.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + + # test patch_train and slice_steps=2 + y = list(sorted(set(patch_train.y.astype(int)))) + x = list(sorted(set(patch_train.x.astype(int)))) + assert (int(y[1]) - int(y[0])) == SLICE_STEPS + assert (int(x[1]) - int(x[0])) == SLICE_STEPS + + # reading the file and splitting the data + patch_val = pd.read_csv(output + '/patch_val.txt', header=None, names=['row', 'a', 'b']) + patch_val = pd.DataFrame(patch_val.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + + # test patch_val and slice_steps=2 + y = list(sorted(set(patch_val.y.astype(int)))) + x = list(sorted(set(patch_val.x.astype(int)))) + assert (int(y[1]) - int(y[0])) != SLICE_STEPS + assert (int(x[1]) - int(x[0])) != SLICE_STEPS + + # test validation set is, at least, PER_VAL + PER_VAL_CHK = len(set(patch_train.y))/(len(set(patch_train.y))+len(set(patch_val.y))) * 100 + assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + PER_VAL_CHK = len(set(patch_train.x))/(len(set(patch_train.x))+len(set(patch_val.x))) * 100 + assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + + +def test_prepare_dutchf3_patch_step_2(): + + """check a complete run for the script in case further changes are needed + """ + # setting a value to SLICE_STEPS as needed to test the values + SLICE_STEPS = 2 + + # use a temp dir that will be discarded at the end of the execution + with tempfile.TemporaryDirectory() as tmpdirname: + + # saving the file to be used by the script + label_file = tmpdirname + '/label_file.npy' + np.save(label_file, ALINE) + + # stting the output directory to be used by the script + output = tmpdirname + '/split' + + # calling the main function of the script without SLICE_STEPS, to check default value + prep_dutchf3.split_patch_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file, + slice_steps=SLICE_STEPS, stride=STRIDE, patch_size=PATCH, per_val=PER_VAL,log_config=LOG_CONFIG) + + # reading the file and splitting the data + patch_train = pd.read_csv(output + '/patch_train.txt', header=None, names=['row', 'a', 'b']) + patch_train = pd.DataFrame(patch_train.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + + # test patch_train and slice_steps=2 + y = list(sorted(set(patch_train.y.astype(int)))) + x = list(sorted(set(patch_train.x.astype(int)))) + assert (int(y[1]) - int(y[0])) == SLICE_STEPS + assert (int(x[1]) - int(x[0])) == SLICE_STEPS + + # reading the file and splitting the data + patch_val = pd.read_csv(output + '/patch_val.txt', header=None, names=['row', 'a', 'b']) + patch_val = pd.DataFrame(patch_val.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + + # test patch_val and slice_steps=2 + y = list(sorted(set(patch_val.y.astype(int)))) + x = list(sorted(set(patch_val.x.astype(int)))) + assert (int(y[1]) - int(y[0])) != SLICE_STEPS + assert (int(x[1]) - int(x[0])) != SLICE_STEPS + + # test validation set is, at least, PER_VAL + PER_VAL_CHK = len(set(patch_train.y))/(len(set(patch_train.y))+len(set(patch_val.y))) * 100 + assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + PER_VAL_CHK = len(set(patch_train.x))/(len(set(patch_train.x))+len(set(patch_val.x))) * 100 + assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + + +def test_prepare_dutchf3_patch_train_and_test_sets(): + + """check a complete run for the script in case further changes are needed + """ + # setting a value to SLICE_STEPS as needed to test the values + SLICE_STEPS = 1 + + # use a temp dir that will be discarded at the end of the execution + with tempfile.TemporaryDirectory() as tmpdirname: + + # saving the file to be used by the script + label_file = tmpdirname + '/label_file.npy' + np.save(label_file, ALINE) + + # stting the output directory to be used by the script + output = tmpdirname + '/split' + + # calling the main function of the script without SLICE_STEPS, to check default value + prep_dutchf3.split_patch_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file, + slice_steps=SLICE_STEPS, stride=STRIDE, patch_size=PATCH, per_val=PER_VAL,log_config=LOG_CONFIG) + + # reading the file and splitting the data + patch_train = pd.read_csv(output + '/patch_train.txt', header=None, names=['row', 'a', 'b']) + patch_train = pd.DataFrame(patch_train.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + + # reading the file and splitting the data + patch_val = pd.read_csv(output + '/patch_val.txt', header=None, names=['row', 'a', 'b']) + patch_val = pd.DataFrame(patch_val.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + + y_train = set(patch_train.y) + x_train = set(patch_train.x) + y_val = set(patch_val.y) + x_val = set(patch_val.x) + + # The sets must not contain values common to both + assert y_train & y_val == set() + assert x_train & x_val == set() + + # test validation set is, at least, PER_VAL + PER_VAL_CHK = len(set(patch_train.y))/(len(set(patch_train.y))+len(set(patch_val.y))) * 100 + assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + PER_VAL_CHK = len(set(patch_train.x))/(len(set(patch_train.x))+len(set(patch_val.x))) * 100 + assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + +def test_prepare_dutchf3_section_step_1(): + + """check a complete run for the script in case further changes are needed + """ + # setting a value to SLICE_STEPS as needed to test the values + SLICE_STEPS = 1 + + # use a temp dir that will be discarded at the end of the execution + with tempfile.TemporaryDirectory() as tmpdirname: + + # saving the file to be used by the script + label_file = tmpdirname + '/label_file.npy' + np.save(label_file, ALINE) + + # stting the output directory to be used by the script + output = tmpdirname + '/split' + + # calling the main function of the script without SLICE_STEPS, to check default value + prep_dutchf3.split_section_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file,slice_steps=SLICE_STEPS, per_val=PER_VAL, log_config=LOG_CONFIG) + + # reading the file and splitting the data + section_train = pd.read_csv(output + '/section_train.txt', header=None, names=['row']) + section_train = pd.DataFrame(section_train.row.str.split('_').tolist(), columns=['aline', 'section']) + + section_val = pd.read_csv(output + '/section_val.txt', header=None, names=['row']) + section_val = pd.DataFrame(section_val.row.str.split('_').tolist(), columns=['aline', 'section']) + + # test + assert (float(section_train.section[1]) - float(section_train.section[0])) % float(SLICE_STEPS) == 0.0 + assert (float(section_val.section[1]) - float(section_val.section[0])) % float(SLICE_STEPS) == 0.0 + + # test validation set is, at least, PER_VAL + PER_VAL_CHK = len(section_val)/(len(section_val)+len(section_train)) * 100 + assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + +def test_prepare_dutchf3_section_step_2(): + + """check a complete run for the script in case further changes are needed + """ + # setting a value to SLICE_STEPS as needed to test the values + SLICE_STEPS = 2 + + # use a temp dir that will be discarded at the end of the execution + with tempfile.TemporaryDirectory() as tmpdirname: + + # saving the file to be used by the script + label_file = tmpdirname + '/label_file.npy' + np.save(label_file, ALINE) + + # stting the output directory to be used by the script + output = tmpdirname + '/split' + + # calling the main function of the script without SLICE_STEPS, to check default value + prep_dutchf3.split_section_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file, + slice_steps=SLICE_STEPS, per_val=PER_VAL, log_config=LOG_CONFIG) + + # reading the file and splitting the data + section_train = pd.read_csv(output + '/section_train.txt', header=None, names=['row']) + section_train = pd.DataFrame(section_train.row.str.split('_').tolist(), columns=['aline', 'section']) + + section_val = pd.read_csv(output + '/section_val.txt', header=None, names=['row']) + section_val = pd.DataFrame(section_val.row.str.split('_').tolist(), columns=['aline', 'section']) + + # test + assert (float(section_train.section[1]) - float(section_train.section[0])) % float(SLICE_STEPS) == 0.0 + assert (float(section_val.section[1]) - float(section_val.section[0])) % float(SLICE_STEPS) > 0.0 + + # test validation set is, at least, PER_VAL + PER_VAL_CHK = len(section_val)/(len(section_val)+len(section_train)) * 100 + assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) \ No newline at end of file From 8aaf468def4023922f93778cac05da99c1a7fa6c Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 6 Apr 2020 09:33:33 -0400 Subject: [PATCH 190/207] added docker image test build (#242) * added docker image test build * increased Docker image build timeout --- tests/cicd/main_build.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 7a574389..d3cf2f34 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -14,6 +14,29 @@ trigger: - contrib jobs: + +################################################################################################### +# Docker test +################################################################################################### + +- job: docker_image_build_job + timeoutInMinutes: 15 + displayName: docker image build + pool: + name: deepseismicagentpool + steps: + - bash: | + echo "cleaning up old Docker image" + if [[ "$(docker images -q seismic-deeplearning:latest 2> /dev/null)" == "" ]]; then + echo "no previous image found" + else + echo "removing previous image" + docker rmi seismic-deeplearning + fi + # test building the image + cd docker + docker build -t seismic-deeplearning . + - job: setup timeoutInMinutes: 10 displayName: Setup @@ -40,6 +63,9 @@ jobs: # copy your model files like so - using dummy file to illustrate azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name +################################################################################################### +# Unit Test JOBS +################################################################################################### - job: cv_lib_unit_tests_job dependsOn: setup From c9c2773a6449fd299d45dda7a0e3f3f660e377e9 Mon Sep 17 00:00:00 2001 From: yalaudah Date: Tue, 7 Apr 2020 12:37:46 -0500 Subject: [PATCH 191/207] update notebook seeds (#247) --- ..._patch_model_training_and_evaluation.ipynb | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb index cc1ff3a7..66fc0dba 100644 --- a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb +++ b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb @@ -117,17 +117,7 @@ "outputs": [], "source": [ "import numpy as np\n", - "\n", - "np.random.seed(seed=1234)\n", - "\n", "import torch\n", - "\n", - "torch.backends.cudnn.benchmark = True\n", - "torch.manual_seed(1234)\n", - "if torch.cuda.is_available():\n", - " torch.cuda.manual_seed_all(1234)\n", - "\n", - "# now we can import the rest of the libraries\n", "import logging\n", "import logging.config\n", "from os import path\n", @@ -315,6 +305,22 @@ " config.merge_from_list(opts)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fix random seeds, and set CUDNN benchmark mode:\n", + "torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK\n", + "\n", + "# Fix random seeds:\n", + "torch.manual_seed(config.SEED)\n", + "if torch.cuda.is_available():\n", + " torch.cuda.manual_seed_all(config.SEED)\n", + "np.random.seed(seed=config.SEED)" + ] + }, { "cell_type": "code", "execution_count": null, From 79d19710dac4fccc42e6fbfbe9b85c7157224782 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 7 Apr 2020 20:52:07 -0400 Subject: [PATCH 192/207] re-wrote experiment test builds to run in parallel on single 4-GPU VM (#246) * re-wrote experiment test builds to run in parallel on single 4-GPU VM * fixed yaml typo * fixed another yaml typo * added more descriptive build names * fixed another yaml typo * changed build names and added tee log splitting * added wait -n * added wait termination condition * fixed path typo * added code to manually block on PIDs * added ADO fixes to collect PIDs for wait; changed component governance build pool * added manual handling of return codes * fixed parallel distributed tests * build typo --- tests/cicd/component_governance.yml | 2 +- tests/cicd/main_build.yml | 563 ++++++++++++++++------------ 2 files changed, 318 insertions(+), 247 deletions(-) diff --git a/tests/cicd/component_governance.yml b/tests/cicd/component_governance.yml index 172fb40a..0d3f9158 100644 --- a/tests/cicd/component_governance.yml +++ b/tests/cicd/component_governance.yml @@ -18,7 +18,7 @@ trigger: - contrib pool: - vmImage: 'ubuntu-latest' + name: deepseismicagentpool steps: - task: ComponentGovernanceComponentDetection@0 diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index d3cf2f34..b0d641f1 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -13,30 +13,20 @@ trigger: - staging - contrib +################################################################################################### +# The pre-requisite for these jobs is to have 4 GPUs on your virtual machine (K80 or better) +# Jobs are daisy-chained by stages - more relevant stages come first (the ones we're currently +# working on): +# - if they fail no point in running anything else +# - each stage _can_ have parallel jobs but that's not always needed for fast execution +################################################################################################### + jobs: ################################################################################################### -# Docker test +# Stage 1: Setup ################################################################################################### -- job: docker_image_build_job - timeoutInMinutes: 15 - displayName: docker image build - pool: - name: deepseismicagentpool - steps: - - bash: | - echo "cleaning up old Docker image" - if [[ "$(docker images -q seismic-deeplearning:latest 2> /dev/null)" == "" ]]; then - echo "no previous image found" - else - echo "removing previous image" - docker rmi seismic-deeplearning - fi - # test building the image - cd docker - docker build -t seismic-deeplearning . - - job: setup timeoutInMinutes: 10 displayName: Setup @@ -63,321 +53,402 @@ jobs: # copy your model files like so - using dummy file to illustrate azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name + ################################################################################################### -# Unit Test JOBS +# Stage 2: fast unit tests ################################################################################################### -- job: cv_lib_unit_tests_job - dependsOn: setup - timeoutInMinutes: 5 - displayName: cv_lib Unit Tests Job - pool: - name: deepseismicagentpool - steps: - - bash: | - echo "Starting cv_lib unit tests" - source activate seismic-interpretation - pytest --durations=0 cv_lib/tests/ - echo "cv_lib unit test job passed" - - job: scripts_unit_tests_job dependsOn: setup timeoutInMinutes: 5 - displayName: Unit Tests Job + displayName: Unit Tests pool: name: deepseismicagentpool steps: - bash: | + set -e echo "Starting scripts unit tests" source activate seismic-interpretation pytest --durations=0 tests/ echo "Script unit test job passed" -- job: dutchf3_unit_tests_job - dependsOn: setup +- job: cv_lib_unit_tests_job + dependsOn: scripts_unit_tests_job timeoutInMinutes: 5 - displayName: DutchF3 Unit Tests Job + displayName: cv_lib Unit Tests pool: name: deepseismicagentpool steps: - bash: | - echo "Starting unit tests" + set -e + echo "Starting cv_lib unit tests" source activate seismic-interpretation - pytest --durations=0 interpretation/deepseismic_interpretation/dutchf3/tests - echo "Unit test job passed" + pytest --durations=0 cv_lib/tests/ + echo "cv_lib unit test job passed" + ################################################################################################### -# LOCAL PATCH JOBS +# Stage 3: Dutch F3 patch models: deconvnet, unet, HRNet patch depth, HRNet section depth ################################################################################################### -- job: hrnet_penobscot - dependsOn: setup - timeoutInMinutes: 5 - displayName: hrnet penobscot +- job: dutchf3_patch + dependsOn: cv_lib_unit_tests_job + timeoutInMinutes: 20 + displayName: Dutch F3 patch local pool: name: deepseismicagentpool steps: - bash: | - conda env list + source activate seismic-interpretation - # run the tests - cd experiments/interpretation/penobscot/local - python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + + # disable auto error handling as we flag it manually + set +e + + cd experiments/interpretation/dutchf3_patch/local + + # Create a temporary directory to store the statuses + dir=$(mktemp -d) + + pids= + export CUDA_VISIBLE_DEVICES=0 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + export CUDA_VISIBLE_DEVICES=1 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ - 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/hrnet.yaml --debug - # find the latest model which we just trained - model=$(ls -td output/hrnet/section_depth/* | head -1) - echo ${model} - # # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' \ - 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ - 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ - --cfg=configs/hrnet.yaml --debug - -- job: seresnet_unet_penobscot - dependsOn: setup - timeoutInMinutes: 5 - displayName: seresnet_unet penobscot - pool: - name: deepseismicagentpool - steps: - - bash: | - conda env list - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/penobscot/local - python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + export CUDA_VISIBLE_DEVICES=2 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/seresnet_unet.yaml --debug - # find the latest model which we just trained - model=$(ls -td output/seresnet_unet/section_depth/* | head -1) - echo ${model} - # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug - -- job: hrnet_dutchf3_patch - dependsOn: setup - timeoutInMinutes: 5 - displayName: hrnet dutchf3 patch - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + export CUDA_VISIBLE_DEVICES=3 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/hrnet.yaml --debug + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + + wait $pids || exit 1 + + # check if any of the models had an error during execution + # Get return information for each pid + for file in "$dir"/*; do + printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" + [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" + done + + # Remove the temporary directory + rm -r "$dir" + + echo "All models finished training - start scoring" + + # Create a temporary directory to store the statuses + dir=$(mktemp -d) + + pids= + export CUDA_VISIBLE_DEVICES=0 # find the latest model which we just trained - model=$(ls -td output/hrnet/section_depth/* | head -1) - echo ${model} + model=$(ls -td output/patch_deconvnet/no_depth/* | head -1) # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ - 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ - --cfg=configs/hrnet.yaml --debug - -- job: unet_dutchf3 - dependsOn: setup - timeoutInMinutes: 5 - displayName: unet dutchf3 - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/unet.yaml --debug + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ + 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_1.pth \ + --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + export CUDA_VISIBLE_DEVICES=1 # find the latest model which we just trained - model=$(ls -td output/unet/section_depth/* | head -1) - echo ${model} + model=$(ls -td output/unet/section_depth/* | head -1) # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/unet.yaml --debug - -- job: seresnet_unet_dutchf3 - dependsOn: setup - timeoutInMinutes: 5 - displayName: seresnet unet dutchf3 - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/seresnet_unet.yaml --debug + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ + 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth \ + --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + export CUDA_VISIBLE_DEVICES=2 # find the latest model which we just trained model=$(ls -td output/seresnet_unet/section_depth/* | head -1) # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth --cfg=configs/seresnet_unet.yaml --debug + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ + 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth \ + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + export CUDA_VISIBLE_DEVICES=3 + # find the latest model which we just trained + model=$(ls -td output/hrnet/section_depth/* | head -1) + # try running the test script + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" -- job: patch_deconvnet_dutchf3 - dependsOn: setup - timeoutInMinutes: 5 - displayName: patch deconvnet dutchf3 + # wait for completion + wait $pids || exit 1 + + # check if any of the models had an error during execution + # Get return information for each pid + for file in "$dir"/*; do + printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" + [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" + done + + # Remove the temporary directory + rm -r "$dir" + + echo "PASSED" + +################################################################################################### +# Stage 4: DISTRIBUTED PATCH JOBS (in pairs, 2 GPUs at a time per model) +################################################################################################### + +- job: dutchf3_patch_dist + dependsOn: dutchf3_patch + timeoutInMinutes: 25 + displayName: Dutch F3 patch distributed pool: name: deepseismicagentpool steps: - bash: | + source activate seismic-interpretation + + # we're manually collecting and re-throwing process run exit codes + set +e + + # number of GPUs to test each model on, except last one + NGPU=2 + # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + cd experiments/interpretation/dutchf3_patch/distributed + + # Create a temporary directory to store the statuses + dir=$(mktemp -d) + + pids= + export CUDA_VISIBLE_DEVICES=0,1 + { python -m torch.distributed.launch --master_port 6000 --nproc_per_node=$NGPU train.py \ + 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/patch_deconvnet.yaml --debug - # find the latest model which we just trained - model=$(ls -td output/patch_deconvnet/no_depth/* | head -1) - # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_1.pth --cfg=configs/patch_deconvnet.yaml --debug + --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" -- job: patch_deconvnet_skip_dutchf3 - dependsOn: setup - timeoutInMinutes: 5 - displayName: patch deconvnet skip dutchf3 - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/local - python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + export CUDA_VISIBLE_DEVICES=2,3 + { python -m torch.distributed.launch --master_port 7000 --nproc_per_node=$NGPU train.py \ + 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/patch_deconvnet_skip.yaml --debug - # find the latest model which we just trained - model=$(ls -td output/patch_deconvnet_skip/no_depth/* | head -1) - # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'VALIDATION.BATCH_SIZE_PER_GPU' 1 'TEST.MODEL_PATH' ${model}/patch_deconvnet_skip_running_model_1.pth --cfg=configs/patch_deconvnet_skip.yaml --debug + --cfg=configs/patch_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + wait $pids || exit 1 -################################################################################################### -# DISTRIBUTED PATCH JOBS -################################################################################################### + # check if any of the models had an error during execution + # Get return information for each pid + for file in "$dir"/*; do + printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" + [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" + done -- job: hrnet_dutchf3_dist - dependsOn: setup - timeoutInMinutes: 5 - displayName: hrnet dutchf3 distributed - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ + pids= + export CUDA_VISIBLE_DEVICES=0,1 + { python -m torch.distributed.launch --master_port 6000 --nproc_per_node=$NGPU train.py \ 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ - 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/hrnet.yaml --debug + --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" -- job: unet_dutchf3_dist - dependsOn: setup - timeoutInMinutes: 5 - displayName: unet dutchf3 distributed - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ + export CUDA_VISIBLE_DEVICES=2,3 + { python -m torch.distributed.launch --master_port 7000 --nproc_per_node=$NGPU train.py \ 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/unet.yaml --debug + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" -- job: seresnet_unet_dutchf3_dist - dependsOn: setup - timeoutInMinutes: 5 - displayName: seresnet unet dutchf3 distributed - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ + wait $pids || exit 1 + + # check if any of the models had an error during execution + # Get return information for each pid + for file in "$dir"/*; do + printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" + [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" + done + + # Remove the temporary directory + rm -r "$dir" + + # we only have 5 models, so just let the last most important one use all remaining GPUs + # re-enable error code flagging + set -e + NGPU=4 + export CUDA_VISIBLE_DEVICES=0,1,2,3 + python -m torch.distributed.launch --master_port 6000 --nproc_per_node=$NGPU train.py \ 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/seresnet_unet.yaml --debug + --cfg=configs/hrnet.yaml --debug -- job: patch_deconvnet_dutchf3_dist - dependsOn: setup - timeoutInMinutes: 5 - displayName: patch deconvnet dutchf3 distributed - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ - 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'none' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/patch_deconvnet.yaml --debug + echo "PASSED" -- job: patch_deconvnet_skip_dutchf3_dist - dependsOn: setup - timeoutInMinutes: 5 - displayName: patch deconvnet skip dutchf3 distributed +################################################################################################### +# Stage 5: Docker container +################################################################################################### + +- job: docker_image_build_job + dependsOn: dutchf3_patch_dist + timeoutInMinutes: 15 + displayName: Docker image build pool: name: deepseismicagentpool steps: - bash: | - source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - python -m torch.distributed.launch --nproc_per_node=$(nproc) train.py \ - 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'none' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/patch_deconvnet_skip.yaml --debug + # terminate as soon as any internal script fails + set -e + + echo "cleaning up old Docker image" + if [[ "$(docker images -q seismic-deeplearning:latest 2> /dev/null)" == "" ]]; then + echo "no previous image found" + else + echo "removing previous image" + docker rmi seismic-deeplearning + fi + # test building the image + cd docker + docker build -t seismic-deeplearning . ################################################################################################### -# LOCAL SECTION JOBS +# Stage 6: remaining code - Penobscot, section and deconvnet_skip model for F3 ################################################################################################### -- job: section_deconvnet_skip - dependsOn: setup - timeoutInMinutes: 5 - displayName: section deconvnet skip +- job: penobscot_et_al + dependsOn: docker_image_build_job + timeoutInMinutes: 20 + displayName: Penobscot et al pool: name: deepseismicagentpool steps: - bash: | + source activate seismic-interpretation - # run the tests - cd experiments/interpretation/dutchf3_section/local - python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + + # disable auto error handling as we flag it manually + set +e + + cd experiments/interpretation/penobscot/local + + # Create a temporary directory to store the statuses + dir=$(mktemp -d) + + pids= + export CUDA_VISIBLE_DEVICES=0 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + + export CUDA_VISIBLE_DEVICES=1 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + + export CUDA_VISIBLE_DEVICES=2 + cd ../../../../experiments/interpretation/dutchf3_patch/local + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/section_deconvnet_skip.yaml --debug + --cfg=configs/patch_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + + export CUDA_VISIBLE_DEVICES=3 + cd ../../../../experiments/interpretation/dutchf3_section/local + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/section_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + cd ../../../../ + + wait $pids || exit 1 + + # check if any of the models had an error during execution + # Get return information for each pid + for file in "$dir"/*; do + printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" + [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" + done + + # Remove the temporary directory + rm -r "$dir" + + echo "Models finished training" + + # Create a temporary directory to store the statuses + dir=$(mktemp -d) + + cd experiments/interpretation/penobscot/local + pids= + export CUDA_VISIBLE_DEVICES=0 + # find the latest model which we just trained + model=$(ls -td output/seresnet_unet/section_depth/* | head -1) + # try running the test script + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' \ + 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth \ + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + export CUDA_VISIBLE_DEVICES=1 + # find the latest model which we just trained + model=$(ls -td output/hrnet/section_depth/* | head -1) + echo ${model} + # # try running the test script + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + export CUDA_VISIBLE_DEVICES=2 + cd ../../../../experiments/interpretation/dutchf3_patch/local + # find the latest model which we just trained + model=$(ls -td output/patch_deconvnet_skip/no_depth/* | head -1) + # try running the test script + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ + 'TEST.MODEL_PATH' ${model}/patch_deconvnet_skip_running_model_1.pth \ + --cfg=configs/patch_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + export CUDA_VISIBLE_DEVICES=3 + cd ../../../../experiments/interpretation/dutchf3_section/local # find the latest model which we just trained model=$(ls -td output/section_deconvnet_skip/no_depth/* | head -1) echo ${model} # try running the test script - python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TEST.MODEL_PATH' ${model}/section_deconvnet_skip_running_model_1.pth --cfg=configs/section_deconvnet_skip.yaml --debug - - + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ + 'TEST.MODEL_PATH' ${model}/section_deconvnet_skip_running_model_1.pth \ + --cfg=configs/section_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + pids+=" $!" + cd ../../../../ + wait $pids || exit 1 + + # check if any of the models had an error during execution + # Get return information for each pid + for file in "$dir"/*; do + printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" + [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" + done + + # Remove the temporary directory + rm -r "$dir" + + echo "PASSED" From 2f84832ba5abfb0836569463f9a0197459650339 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 13 Apr 2020 14:58:05 -0400 Subject: [PATCH 193/207] correctness branch setup (#251) * created correctnes branch, trimmed experiments to Dutch F3 only * trivial change to re-trigger build * dummy PR to re-trigger malfunctioning builds --- README.md | 4 - .../interpretation/penobscot/README.md | 0 .../penobscot/local/configs/hrnet.yaml | 0 .../local/configs/seresnet_unet.yaml | 0 .../interpretation/penobscot/local/default.py | 0 .../penobscot/local/logging.conf | 0 .../interpretation/penobscot/local/test.py | 0 .../interpretation/penobscot/local/test.sh | 0 .../interpretation/penobscot/local/train.py | 0 .../interpretation/penobscot/local/train.sh | 0 tests/cicd/main_build.yml | 157 +----------------- 11 files changed, 3 insertions(+), 158 deletions(-) rename {experiments => contrib/experiments}/interpretation/penobscot/README.md (100%) rename {experiments => contrib/experiments}/interpretation/penobscot/local/configs/hrnet.yaml (100%) rename {experiments => contrib/experiments}/interpretation/penobscot/local/configs/seresnet_unet.yaml (100%) rename {experiments => contrib/experiments}/interpretation/penobscot/local/default.py (100%) rename {experiments => contrib/experiments}/interpretation/penobscot/local/logging.conf (100%) rename {experiments => contrib/experiments}/interpretation/penobscot/local/test.py (100%) rename {experiments => contrib/experiments}/interpretation/penobscot/local/test.sh (100%) rename {experiments => contrib/experiments}/interpretation/penobscot/local/train.py (100%) rename {experiments => contrib/experiments}/interpretation/penobscot/local/train.sh (100%) diff --git a/README.md b/README.md index 369e2143..cf8886a7 100644 --- a/README.md +++ b/README.md @@ -419,7 +419,3 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l 5. Navigate back to the Virtual Machine view in Step 2 and click the Start button to start the virtual machine.
- - - - diff --git a/experiments/interpretation/penobscot/README.md b/contrib/experiments/interpretation/penobscot/README.md similarity index 100% rename from experiments/interpretation/penobscot/README.md rename to contrib/experiments/interpretation/penobscot/README.md diff --git a/experiments/interpretation/penobscot/local/configs/hrnet.yaml b/contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml similarity index 100% rename from experiments/interpretation/penobscot/local/configs/hrnet.yaml rename to contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml diff --git a/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml b/contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml similarity index 100% rename from experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml rename to contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml diff --git a/experiments/interpretation/penobscot/local/default.py b/contrib/experiments/interpretation/penobscot/local/default.py similarity index 100% rename from experiments/interpretation/penobscot/local/default.py rename to contrib/experiments/interpretation/penobscot/local/default.py diff --git a/experiments/interpretation/penobscot/local/logging.conf b/contrib/experiments/interpretation/penobscot/local/logging.conf similarity index 100% rename from experiments/interpretation/penobscot/local/logging.conf rename to contrib/experiments/interpretation/penobscot/local/logging.conf diff --git a/experiments/interpretation/penobscot/local/test.py b/contrib/experiments/interpretation/penobscot/local/test.py similarity index 100% rename from experiments/interpretation/penobscot/local/test.py rename to contrib/experiments/interpretation/penobscot/local/test.py diff --git a/experiments/interpretation/penobscot/local/test.sh b/contrib/experiments/interpretation/penobscot/local/test.sh similarity index 100% rename from experiments/interpretation/penobscot/local/test.sh rename to contrib/experiments/interpretation/penobscot/local/test.sh diff --git a/experiments/interpretation/penobscot/local/train.py b/contrib/experiments/interpretation/penobscot/local/train.py similarity index 100% rename from experiments/interpretation/penobscot/local/train.py rename to contrib/experiments/interpretation/penobscot/local/train.py diff --git a/experiments/interpretation/penobscot/local/train.sh b/contrib/experiments/interpretation/penobscot/local/train.sh similarity index 100% rename from experiments/interpretation/penobscot/local/train.sh rename to contrib/experiments/interpretation/penobscot/local/train.sh diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index b0d641f1..f4372ce3 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -6,17 +6,19 @@ pr: - master - staging - contrib +- correctness # Any commit to this branch will trigger the build. trigger: - master - staging - contrib +- correctness ################################################################################################### # The pre-requisite for these jobs is to have 4 GPUs on your virtual machine (K80 or better) # Jobs are daisy-chained by stages - more relevant stages come first (the ones we're currently -# working on): +# working on): # - if they fail no point in running anything else # - each stage _can_ have parallel jobs but that's not always needed for fast execution ################################################################################################### @@ -299,156 +301,3 @@ jobs: --cfg=configs/hrnet.yaml --debug echo "PASSED" - -################################################################################################### -# Stage 5: Docker container -################################################################################################### - -- job: docker_image_build_job - dependsOn: dutchf3_patch_dist - timeoutInMinutes: 15 - displayName: Docker image build - pool: - name: deepseismicagentpool - steps: - - bash: | - # terminate as soon as any internal script fails - set -e - - echo "cleaning up old Docker image" - if [[ "$(docker images -q seismic-deeplearning:latest 2> /dev/null)" == "" ]]; then - echo "no previous image found" - else - echo "removing previous image" - docker rmi seismic-deeplearning - fi - # test building the image - cd docker - docker build -t seismic-deeplearning . - -################################################################################################### -# Stage 6: remaining code - Penobscot, section and deconvnet_skip model for F3 -################################################################################################### - -- job: penobscot_et_al - dependsOn: docker_image_build_job - timeoutInMinutes: 20 - displayName: Penobscot et al - pool: - name: deepseismicagentpool - steps: - - bash: | - - source activate seismic-interpretation - - # disable auto error handling as we flag it manually - set +e - - cd experiments/interpretation/penobscot/local - - # Create a temporary directory to store the statuses - dir=$(mktemp -d) - - pids= - export CUDA_VISIBLE_DEVICES=0 - { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - - export CUDA_VISIBLE_DEVICES=1 - { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - - export CUDA_VISIBLE_DEVICES=2 - cd ../../../../experiments/interpretation/dutchf3_patch/local - { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'none' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/patch_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - - export CUDA_VISIBLE_DEVICES=3 - cd ../../../../experiments/interpretation/dutchf3_section/local - { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'none' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/section_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - cd ../../../../ - - wait $pids || exit 1 - - # check if any of the models had an error during execution - # Get return information for each pid - for file in "$dir"/*; do - printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" - [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" - done - - # Remove the temporary directory - rm -r "$dir" - - echo "Models finished training" - - # Create a temporary directory to store the statuses - dir=$(mktemp -d) - - cd experiments/interpretation/penobscot/local - pids= - export CUDA_VISIBLE_DEVICES=0 - # find the latest model which we just trained - model=$(ls -td output/seresnet_unet/section_depth/* | head -1) - # try running the test script - { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' \ - 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth \ - --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - export CUDA_VISIBLE_DEVICES=1 - # find the latest model which we just trained - model=$(ls -td output/hrnet/section_depth/* | head -1) - echo ${model} - # # try running the test script - { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/penobscot' \ - 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ - 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ - --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - export CUDA_VISIBLE_DEVICES=2 - cd ../../../../experiments/interpretation/dutchf3_patch/local - # find the latest model which we just trained - model=$(ls -td output/patch_deconvnet_skip/no_depth/* | head -1) - # try running the test script - { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model}/patch_deconvnet_skip_running_model_1.pth \ - --cfg=configs/patch_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - export CUDA_VISIBLE_DEVICES=3 - cd ../../../../experiments/interpretation/dutchf3_section/local - # find the latest model which we just trained - model=$(ls -td output/section_deconvnet_skip/no_depth/* | head -1) - echo ${model} - # try running the test script - { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model}/section_deconvnet_skip_running_model_1.pth \ - --cfg=configs/section_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - cd ../../../../ - wait $pids || exit 1 - - # check if any of the models had an error during execution - # Get return information for each pid - for file in "$dir"/*; do - printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" - [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" - done - - # Remove the temporary directory - rm -r "$dir" - - echo "PASSED" From 741f37f27e17f34535a2a6eebb3c2cc19e6dda56 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Thu, 16 Apr 2020 14:26:12 -0400 Subject: [PATCH 194/207] reducing scope further (#258) * created correctnes branch, trimmed experiments to Dutch F3 only * trivial change to re-trigger build * dummy PR to re-trigger malfunctioning builds * reducing scope of the correctness branch further * added branch triggers --- .../distributed/configs/hrnet.yaml | 0 .../distributed/configs/patch_deconvnet.yaml | 0 .../configs/patch_deconvnet_skip.yaml | 0 .../distributed/configs/seresnet_unet.yaml | 0 .../distributed/configs/unet.yaml | 0 .../dutchf3_patch/distributed/default.py | 0 .../dutchf3_patch/distributed/logging.conf | 0 .../dutchf3_patch/distributed/run.sh | 0 .../dutchf3_patch/distributed/train.py | 0 .../dutchf3_patch/distributed/train.sh | 0 .../interpretation/dutchf3_section/README.md | 0 .../local/configs/section_deconvnet_skip.yaml | 0 .../dutchf3_section/local/default.py | 0 .../dutchf3_section/local/logging.conf | 0 .../dutchf3_section/local/test.py | 0 .../dutchf3_section/local/train.py | 0 tests/cicd/component_governance.yml | 2 + tests/cicd/main_build.yml | 96 ------------------- tests/cicd/notebooks_build.yml | 2 + 19 files changed, 4 insertions(+), 96 deletions(-) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/configs/unet.yaml (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/default.py (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/logging.conf (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/run.sh (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/train.py (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_patch/distributed/train.sh (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_section/README.md (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_section/local/default.py (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_section/local/logging.conf (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_section/local/test.py (100%) rename {experiments => contrib/experiments}/interpretation/dutchf3_section/local/train.py (100%) diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml rename to contrib/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml rename to contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml rename to contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml rename to contrib/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml diff --git a/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml rename to contrib/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml diff --git a/experiments/interpretation/dutchf3_patch/distributed/default.py b/contrib/experiments/interpretation/dutchf3_patch/distributed/default.py similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/default.py rename to contrib/experiments/interpretation/dutchf3_patch/distributed/default.py diff --git a/experiments/interpretation/dutchf3_patch/distributed/logging.conf b/contrib/experiments/interpretation/dutchf3_patch/distributed/logging.conf similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/logging.conf rename to contrib/experiments/interpretation/dutchf3_patch/distributed/logging.conf diff --git a/experiments/interpretation/dutchf3_patch/distributed/run.sh b/contrib/experiments/interpretation/dutchf3_patch/distributed/run.sh similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/run.sh rename to contrib/experiments/interpretation/dutchf3_patch/distributed/run.sh diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.py b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/train.py rename to contrib/experiments/interpretation/dutchf3_patch/distributed/train.py diff --git a/experiments/interpretation/dutchf3_patch/distributed/train.sh b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.sh similarity index 100% rename from experiments/interpretation/dutchf3_patch/distributed/train.sh rename to contrib/experiments/interpretation/dutchf3_patch/distributed/train.sh diff --git a/experiments/interpretation/dutchf3_section/README.md b/contrib/experiments/interpretation/dutchf3_section/README.md similarity index 100% rename from experiments/interpretation/dutchf3_section/README.md rename to contrib/experiments/interpretation/dutchf3_section/README.md diff --git a/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml b/contrib/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml similarity index 100% rename from experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml rename to contrib/experiments/interpretation/dutchf3_section/local/configs/section_deconvnet_skip.yaml diff --git a/experiments/interpretation/dutchf3_section/local/default.py b/contrib/experiments/interpretation/dutchf3_section/local/default.py similarity index 100% rename from experiments/interpretation/dutchf3_section/local/default.py rename to contrib/experiments/interpretation/dutchf3_section/local/default.py diff --git a/experiments/interpretation/dutchf3_section/local/logging.conf b/contrib/experiments/interpretation/dutchf3_section/local/logging.conf similarity index 100% rename from experiments/interpretation/dutchf3_section/local/logging.conf rename to contrib/experiments/interpretation/dutchf3_section/local/logging.conf diff --git a/experiments/interpretation/dutchf3_section/local/test.py b/contrib/experiments/interpretation/dutchf3_section/local/test.py similarity index 100% rename from experiments/interpretation/dutchf3_section/local/test.py rename to contrib/experiments/interpretation/dutchf3_section/local/test.py diff --git a/experiments/interpretation/dutchf3_section/local/train.py b/contrib/experiments/interpretation/dutchf3_section/local/train.py similarity index 100% rename from experiments/interpretation/dutchf3_section/local/train.py rename to contrib/experiments/interpretation/dutchf3_section/local/train.py diff --git a/tests/cicd/component_governance.yml b/tests/cicd/component_governance.yml index 0d3f9158..959bd056 100644 --- a/tests/cicd/component_governance.yml +++ b/tests/cicd/component_governance.yml @@ -11,11 +11,13 @@ pr: - master - staging - contrib +- correctness trigger: - master - staging - contrib +- correctness pool: name: deepseismicagentpool diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index f4372ce3..bb65fb15 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -205,99 +205,3 @@ jobs: rm -r "$dir" echo "PASSED" - -################################################################################################### -# Stage 4: DISTRIBUTED PATCH JOBS (in pairs, 2 GPUs at a time per model) -################################################################################################### - -- job: dutchf3_patch_dist - dependsOn: dutchf3_patch - timeoutInMinutes: 25 - displayName: Dutch F3 patch distributed - pool: - name: deepseismicagentpool - steps: - - bash: | - - source activate seismic-interpretation - - # we're manually collecting and re-throwing process run exit codes - set +e - - # number of GPUs to test each model on, except last one - NGPU=2 - - # run the tests - cd experiments/interpretation/dutchf3_patch/distributed - - # Create a temporary directory to store the statuses - dir=$(mktemp -d) - - pids= - export CUDA_VISIBLE_DEVICES=0,1 - { python -m torch.distributed.launch --master_port 6000 --nproc_per_node=$NGPU train.py \ - 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'none' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - - export CUDA_VISIBLE_DEVICES=2,3 - { python -m torch.distributed.launch --master_port 7000 --nproc_per_node=$NGPU train.py \ - 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'none' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/patch_deconvnet_skip.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - - wait $pids || exit 1 - - # check if any of the models had an error during execution - # Get return information for each pid - for file in "$dir"/*; do - printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" - [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" - done - - pids= - export CUDA_VISIBLE_DEVICES=0,1 - { python -m torch.distributed.launch --master_port 6000 --nproc_per_node=$NGPU train.py \ - 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - - export CUDA_VISIBLE_DEVICES=2,3 - { python -m torch.distributed.launch --master_port 7000 --nproc_per_node=$NGPU train.py \ - 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & - pids+=" $!" - - wait $pids || exit 1 - - # check if any of the models had an error during execution - # Get return information for each pid - for file in "$dir"/*; do - printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" - [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" - done - - # Remove the temporary directory - rm -r "$dir" - - # we only have 5 models, so just let the last most important one use all remaining GPUs - # re-enable error code flagging - set -e - NGPU=4 - export CUDA_VISIBLE_DEVICES=0,1,2,3 - python -m torch.distributed.launch --master_port 6000 --nproc_per_node=$NGPU train.py \ - 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/hrnet.yaml --debug - - echo "PASSED" diff --git a/tests/cicd/notebooks_build.yml b/tests/cicd/notebooks_build.yml index 90387e6c..3cf7d1ef 100644 --- a/tests/cicd/notebooks_build.yml +++ b/tests/cicd/notebooks_build.yml @@ -6,12 +6,14 @@ pr: - master - staging - contrib +- correctness # Any commit to this branch will trigger the build. trigger: - master - staging - contrib +- correctness jobs: From 2058da174961916ebcc0f58e7f1ac309ff6a0606 Mon Sep 17 00:00:00 2001 From: Max Kaznady Date: Fri, 17 Apr 2020 00:11:37 +0000 Subject: [PATCH 195/207] hotfixing correctness - broken DropBox download link --- scripts/download_dutch_f3.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/download_dutch_f3.sh b/scripts/download_dutch_f3.sh index ec41ff1b..be83befa 100755 --- a/scripts/download_dutch_f3.sh +++ b/scripts/download_dutch_f3.sh @@ -13,7 +13,7 @@ fi; echo "Extracting to $1" # Download the files: temp_file=$(mktemp -d)/data.zip -wget -o /dev/null -O $temp_file https://www.dropbox.com/s/p6cbgbocxwj04sw/data.zip?dl=1 +wget -o /dev/null -O $temp_file https://zenodo.org/record/3755060/files/data.zip # Check that the md5 checksum matches to varify file integrity echo "Expected output: MD5(data.zip)= bc5932279831a95c0b244fd765376d85" From 47c4e3523c4fb09b20fe88acdae133750cce7367 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Fri, 17 Apr 2020 10:55:57 -0400 Subject: [PATCH 196/207] 214 Ignite 0.3.0 upgrade (#261) * upgraded to Ignite 0.3.0 and fixed upgrade compatibility * added seeds and modified notebook for ignite 0.3.0 * updated code and tests to work with ignite 0.3.0 * made code consistent with Ignite 0.3.0 as much as possible * fixed iterator epoch_length bug by subsetting validation set * applied same fix to the notebook * bugfix in distributed train.py * increased distributed tests to 2 batched - hoping for one batch per GPU * resolved rebase conflict * added seeds and modified notebook for ignite 0.3.0 * updated code and tests to work with ignite 0.3.0 * made code consistent with Ignite 0.3.0 as much as possible * fixed iterator epoch_length bug by subsetting validation set * applied same fix to the notebook * bugfix in distributed train.py * increased distributed tests to 2 batched - hoping for one batch per GPU --- .../dutchf3_patch/distributed/train.py | 23 ++++---- .../dutchf3_section/local/train.py | 15 +++-- .../penobscot/local/configs/hrnet.yaml | 2 +- .../local/configs/seresnet_unet.yaml | 2 +- .../interpretation/penobscot/local/test.py | 7 +-- .../interpretation/penobscot/local/train.py | 18 +++--- environment/anaconda/local/environment.yml | 2 +- ..._patch_model_training_and_evaluation.ipynb | 58 ++++++++++++------- .../dutchf3_patch/local/test.py | 1 - .../dutchf3_patch/local/train.py | 23 ++++---- tests/cicd/main_build.yml | 8 +-- 11 files changed, 82 insertions(+), 77 deletions(-) diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py index d5259937..9d1a2631 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -72,7 +72,6 @@ from ignite.utils import convert_tensor from toolz import compose, curry from torch.utils import data -from toolz import take def prepare_batch(batch, device=None, non_blocking=False): @@ -179,6 +178,10 @@ def run(*options, cfg=None, local_rank=0, debug=False): patch_size=config.TRAIN.PATCH_SIZE, augmentations=val_aug, ) + + if debug: + val_set = data.Subset(val_set, range(3)) + logger.info(f"Validation examples {len(val_set)}") n_classes = train_set.n_classes @@ -258,16 +261,13 @@ def _select_pred_and_mask(model_out_dict): device=device, ) - # Set the validation run to start on the epoch completion of the training run - if debug: - logger.info("Running Validation in Debug/Test mode") - val_loader = take(3, val_loader) + # Set the validation run to start on the epoch completion of the training run trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) if local_rank == 0: # Run only on master process trainer.add_event_handler( - Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU), ) trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) @@ -340,12 +340,11 @@ def snapshot_function(): evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") - - if debug: - logger.info("Running Training in Debug/Test mode") - train_loader = take(3, train_loader) - - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + + if debug: + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = config.TRAIN.BATCH_SIZE_PER_GPU*2, seed = config.SEED) + else: + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = len(train_loader), seed = config.SEED) if __name__ == "__main__": diff --git a/contrib/experiments/interpretation/dutchf3_section/local/train.py b/contrib/experiments/interpretation/dutchf3_section/local/train.py index dd8c5b44..b216268e 100644 --- a/contrib/experiments/interpretation/dutchf3_section/local/train.py +++ b/contrib/experiments/interpretation/dutchf3_section/local/train.py @@ -50,7 +50,6 @@ from ignite.metrics import Loss from toolz import compose from torch.utils import data -from toolz import take def prepare_batch(batch, device="cuda", non_blocking=False): @@ -132,6 +131,9 @@ def __len__(self): shuffle=False, ) + if debug: + val_set = data.Subset(val_set, range(3)) + val_loader = data.DataLoader( val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, @@ -176,7 +178,7 @@ def __len__(self): trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) trainer.add_event_handler( - Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU), ) trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) @@ -206,9 +208,6 @@ def _select_pred_and_mask(model_out_dict): device=device, ) - if debug: - logger.info("Running Validation in Debug/Test mode") - val_loader = take(3, val_loader) trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) evaluator.add_event_handler( @@ -279,9 +278,9 @@ def snapshot_function(): logger.info("Starting training") if debug: - logger.info("Running Validation in Debug/Test mode") - train_loader = take(3, train_loader) - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = config.TRAIN.BATCH_SIZE_PER_GPU, seed = config.SEED) + else: + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = len(train_loader), seed = config.SEED) if __name__ == "__main__": diff --git a/contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml b/contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml index 06d2d765..ba4b3967 100644 --- a/contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml +++ b/contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml @@ -92,7 +92,7 @@ TRAIN: VALIDATION: - BATCH_SIZE_PER_GPU: 128 + BATCH_SIZE_PER_GPU: 32 COMPLETE_PATCHES_ONLY: True TEST: diff --git a/contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml b/contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml index 29c61936..3ba4d807 100644 --- a/contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml +++ b/contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml @@ -49,7 +49,7 @@ TRAIN: VALIDATION: - BATCH_SIZE_PER_GPU: 32 + BATCH_SIZE_PER_GPU: 16 COMPLETE_PATCHES_ONLY: True TEST: diff --git a/contrib/experiments/interpretation/penobscot/local/test.py b/contrib/experiments/interpretation/penobscot/local/test.py index b8928c1e..8687085a 100644 --- a/contrib/experiments/interpretation/penobscot/local/test.py +++ b/contrib/experiments/interpretation/penobscot/local/test.py @@ -283,10 +283,9 @@ def _tensor_to_numpy(pred_tensor): logger.info("Starting training") if debug: - logger.info("Running in Debug/Test mode") - test_loader = take(3, test_loader) - - evaluator.run(test_loader, max_epochs=1) + evaluator.run(test_loader, max_epochs=1, epoch_length = 1) + else: + evaluator.run(test_loader, max_epochs=1, epoch_length = len(test_loader)) # Log top N and bottom N inlines in terms of IoU to tensorboard inline_ious = inline_mean_iou.iou_per_inline() diff --git a/contrib/experiments/interpretation/penobscot/local/train.py b/contrib/experiments/interpretation/penobscot/local/train.py index eea5361b..22394b53 100644 --- a/contrib/experiments/interpretation/penobscot/local/train.py +++ b/contrib/experiments/interpretation/penobscot/local/train.py @@ -64,7 +64,6 @@ from default import _C as config from default import update_config -from toolz import take mask_value = 255 @@ -180,7 +179,10 @@ def run(*options, cfg=None, debug=False): train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True, ) - val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS,) + if debug: + val_set = data.Subset(val_set, range(3)) + + val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -215,7 +217,7 @@ def run(*options, cfg=None, debug=False): trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) trainer.add_event_handler( - Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU), ) trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) trainer.add_event_handler( @@ -243,9 +245,6 @@ def _select_pred_and_mask(model_out_dict): ) # Set the validation run to start on the epoch completion of the training run - if debug: - logger.info("Running Validation in Debug/Test mode") - val_loader = take(3, val_loader) trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) evaluator.add_event_handler( @@ -307,10 +306,9 @@ def snapshot_function(): logger.info("Starting training") if debug: - logger.info("Running Training in Debug/Test mode") - train_loader = take(3, train_loader) - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) - + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = config.TRAIN.BATCH_SIZE_PER_GPU, seed = config.SEED) + else: + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = len(train_loader), seed = config.SEED) if __name__ == "__main__": fire.Fire(run) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 22f2f114..de0d65af 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -21,7 +21,7 @@ dependencies: - papermill>=1.0.1 - pip: - segyio==1.8.8 - - pytorch-ignite==0.3.0.dev20191105 # pre-release until stable available + - pytorch-ignite==0.3.0 - fire==0.2.1 - toolz==0.10.0 - tabulate==0.8.2 diff --git a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb index 66fc0dba..de4e8542 100644 --- a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb +++ b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb @@ -136,7 +136,7 @@ "from ignite.engine import Events\n", "from ignite.metrics import Loss\n", "from ignite.utils import convert_tensor\n", - "from toolz import compose, take\n", + "from toolz import compose\n", "from torch.utils import data\n", "\n", "from cv_lib.utils import load_log_configuration\n", @@ -533,18 +533,14 @@ " patch_size=config.TRAIN.PATCH_SIZE,\n", " augmentations=val_aug,\n", ")\n", - "logger.info(val_set)\n", "\n", - "snapshot_duration = scheduler_step * len(train_set)\n", + "# TODO: workaround for Ignite 0.3.0 bug as epoch_lengh in trainer.run method below doesn't apply to validation set\n", + "if papermill:\n", + " val_set = data.Subset(val_set, range(3))\n", + "elif DEMO:\n", + " val_set = data.Subset(val_set, range(config.VALIDATION.BATCH_SIZE_PER_GPU))\n", "\n", - "# use the following code when this error\n", - "# https://stackoverflow.com/questions/53810497/indexerror-when-iterating-my-dataset-using-dataloader-in-pytorch\n", - "# has been fixed and remove the toolz.take workaround at the bottom of this cell\n", - "# if DEMO:\n", - "# count = config.TRAIN.BATCH_SIZE_PER_GPU * 10\n", - "# train_set = data.Subset(train_set, list(range(count)))\n", - "# val_set = data.Subset(val_set, list(range(config.VALIDATION.BATCH_SIZE_PER_GPU)))\n", - "# snapshot_duration = scheduler_step * count\n", + "logger.info(val_set)\n", "\n", "train_loader = data.DataLoader(\n", " train_set,\n", @@ -556,15 +552,33 @@ " val_set,\n", " batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU,\n", " num_workers=config.WORKERS,\n", - ")\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following code defines the snapshot duration in batches over which we snapshot training models to disk. Variable `scheduler_step` defines how many epochs we have in a snapshot and multiplying that by the number of data points per epoch gives us the number of datapoints which we have per snapshot." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# if we're running in test mode, just run 2 batches\n", + "if papermill:\n", + " train_len = config.TRAIN.BATCH_SIZE_PER_GPU*2 \n", + "# if we're running in demo mode, just run 10 batches to fine-tune the model\n", + "elif DEMO:\n", + " train_len = config.TRAIN.BATCH_SIZE_PER_GPU*10 \n", + "# if we're not in test or demo modes, run the entire loop\n", + "else:\n", + " train_len = len(train_loader)\n", "\n", - "# the commented out data.Subset step above causes the Ignite engine to fail, so this \n", - "# is the workaround for now which sets batch size to 10\n", - "if DEMO:\n", - " count = max(20 if papermill else 100, config.TRAIN.BATCH_SIZE_PER_GPU)\n", - " train_loader = take(count, train_loader)\n", - " val_loader = take(config.VALIDATION.BATCH_SIZE_PER_GPU, val_loader)\n", - " snapshot_duration = scheduler_step * count" + "snapshot_duration = scheduler_step * train_len" ] }, { @@ -700,7 +714,7 @@ "# add logging of training output\n", "trainer.add_event_handler(\n", " Events.ITERATION_COMPLETED,\n", - " logging_handlers.log_training_output(log_interval=config.PRINT_FREQ),\n", + " logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU),\n", ")\n", "\n", "# add logging of learning rate\n", @@ -862,7 +876,7 @@ "metadata": {}, "outputs": [], "source": [ - "trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH)" + "trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length=train_len, seed = config.SEED)" ] }, { @@ -924,7 +938,7 @@ "outputs": [], "source": [ "# use the model which we just fine-tuned\n", - "opts = [\"TEST.MODEL_PATH\", path.join(output_dir, \"model_f3_nb_seg_hrnet_1.pth\")]\n", + "opts = [\"TEST.MODEL_PATH\", path.join(output_dir, f\"model_f3_nb_seg_hrnet_{train_len}.pth\")]\n", "# uncomment the line below to use the pre-trained model instead\n", "# opts = [\"TEST.MODEL_PATH\", config.MODEL.PRETRAINED]\n", "config.merge_from_list(opts)" diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index 7696518b..3d6eebc7 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -56,7 +56,6 @@ "zechstein", ] - class runningScore(object): def __init__(self, n_classes): self.n_classes = n_classes diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index e4567826..9c77d713 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -65,7 +65,6 @@ from default import _C as config from default import update_config -from toolz import take def prepare_batch(batch, device=None, non_blocking=False): @@ -167,6 +166,10 @@ def run(*options, cfg=None, debug=False): num_workers=config.WORKERS, shuffle=True, ) + + if debug: + val_set = data.Subset(val_set, range(3)) + val_loader = data.DataLoader( val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, ) @@ -224,7 +227,7 @@ def run(*options, cfg=None, debug=False): # log all training output trainer.add_event_handler( Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), + logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU), ) # add logging of learning rate @@ -269,12 +272,7 @@ def snapshot_function(): }, device=device, ) - - # Set the validation run to start on the epoch completion of the training run - if debug: - logger.info("Running Validation in Debug/Test mode") - val_loader = take(3, val_loader) - + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) evaluator.add_event_handler( @@ -330,12 +328,11 @@ def snapshot_function(): ) evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) - logger.info("Starting training") + logger.info("Starting training") if debug: - logger.info("Running Training in Debug/Test mode") - train_loader = take(3, train_loader) - - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = config.TRAIN.BATCH_SIZE_PER_GPU, seed = config.SEED) + else: + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = len(train_loader), seed = config.SEED) if __name__ == "__main__": diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index bb65fb15..3a343233 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -162,7 +162,7 @@ jobs: model=$(ls -td output/patch_deconvnet/no_depth/* | head -1) # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_1.pth \ + 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_0.*.pth \ --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & pids+=" $!" export CUDA_VISIBLE_DEVICES=1 @@ -170,7 +170,7 @@ jobs: model=$(ls -td output/unet/section_depth/* | head -1) # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth \ + 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_0.*.pth \ --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & pids+=" $!" export CUDA_VISIBLE_DEVICES=2 @@ -178,7 +178,7 @@ jobs: model=$(ls -td output/seresnet_unet/section_depth/* | head -1) # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_1.pth \ + 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_0.*.pth \ --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & pids+=" $!" export CUDA_VISIBLE_DEVICES=3 @@ -187,7 +187,7 @@ jobs: # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ - 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_1.pth \ + 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_0.*.pth \ --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & pids+=" $!" From a9ec8f34d4acd770b86f733edf3f2d46e8fa68f8 Mon Sep 17 00:00:00 2001 From: yalaudah Date: Sat, 18 Apr 2020 01:06:29 +0000 Subject: [PATCH 197/207] update docker readme (#262) Co-authored-by: maxkazmsft --- docker/README.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/docker/README.md b/docker/README.md index 00d13fe4..e2ccfacb 100644 --- a/docker/README.md +++ b/docker/README.md @@ -8,20 +8,8 @@ If you are using an Azure Virtual Machine to run this code, you can download the ```bash scp hrnetv2_w48_imagenet_pretrained.pth @:/home//seismic-deeplearning/docker/hrnetv2_w48_imagenet_pretrained.pth ``` - Once you have the model downloaded (ideally under the `docker` directory), you can process to build the Docker image. -# Install `nvidia-docker` -In order to give the Docker container access to the GPU, we need to install [`nvidia-docker`](https://github.com/NVIDIA/nvidia-docker). Please follow the instructions [here](https://github.com/NVIDIA/nvidia-docker#quickstart). For an Ubuntu 16.04/18.04 based system, run the following commands: - -```bash -curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - -curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list - -sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit -sudo systemctl restart docker -``` - # Build the Docker image: In the `docker` directory, run the following command to build the Docker image and tag it as `seismic-deeplearning`: @@ -34,8 +22,7 @@ This process will take a few minutes to complete. # Run the Docker image: Once the Docker image is built, you can run it anytime using the following command: ```bash -sudo nvidia-docker run --rm -it -p 9000:9000 -p 9001:9001 --shm-size 11G --mount type=bind,source=$PWD/hrnetv2_w48_imagenet_pretrained.pth,target=/home/username/models/hrnetv2_w48_imagenet_pretrained.pth seismic-deeplearning +sudo docker run --rm -it -p 9000:9000 -p 9001:9001 --gpus=all --shm-size 11G --mount type=bind,source=$PWD/hrnetv2_w48_imagenet_pretrained.pth,target=/home/username/models/hrnetv2_w48_imagenet_pretrained.pth seismic-deeplearning ``` - If you have saved the pretrained model in a different directory, make sure you replace `$PWD/hrnetv2_w48_imagenet_pretrained.pth` with the **absolute** path to the pretrained HRNet model. The command above will run a jupyter notebook server that you can access by clicking on the link in your terminal. You can then navigate to the notebook that you would like to run. From cf336ee059612e621550475e34bc943b0498dbaa Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Tue, 21 Apr 2020 08:57:02 -0400 Subject: [PATCH 198/207] tagged all TODOs with issues on github (and created issues) (#278) * created correctnes branch, trimmed experiments to Dutch F3 only * trivial change to re-trigger build * dummy PR to re-trigger malfunctioning builds * resolved merge conflict * flagged all non-contrib TODO with github issues * resolved rebase conflict * resolved merge conflict * cleaned up archaic voxel code --- .../dutchf3_patch/distributed/train.py | 13 +- .../dutchf3_section/local/test.py | 1 + .../interpretation/notebooks/utilities.py | 3 + .../dutchf3_patch/local/default.py | 1 + .../dutchf3_patch/local/test.py | 1 + .../dutchf3/data.py | 268 +----------------- .../dutchf3/utils/batch.py | 114 -------- .../models/texture_net.py | 1 + .../penobscot/metrics.py | 4 +- scripts/prepare_dutchf3.py | 1 - 10 files changed, 27 insertions(+), 380 deletions(-) diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py index 9d1a2631..bc28249a 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -24,6 +24,7 @@ import cv2 import fire import numpy as np +import toolz import torch from albumentations import Compose, HorizontalFlip, Normalize, Resize, PadIfNeeded from cv_lib.utils import load_log_configuration @@ -167,8 +168,7 @@ def run(*options, cfg=None, local_rank=0, debug=False): stride=config.TRAIN.STRIDE, patch_size=config.TRAIN.PATCH_SIZE, augmentations=train_aug, - ) - logger.info(f"Training examples {len(train_set)}") + ) val_set = TrainPatchLoader( config.DATASET.ROOT, @@ -185,6 +185,13 @@ def run(*options, cfg=None, local_rank=0, debug=False): logger.info(f"Validation examples {len(val_set)}") n_classes = train_set.n_classes + #if debug: + #val_set = data.Subset(val_set, range(config.VALIDATION.BATCH_SIZE_PER_GPU)) + #train_set = data.Subset(train_set, range(config.TRAIN.BATCH_SIZE_PER_GPU*2)) + + logger.info(f"Training examples {len(train_set)}") + logger.info(f"Validation examples {len(val_set)}") + train_sampler = torch.utils.data.distributed.DistributedSampler(train_set, num_replicas=world_size, rank=local_rank) train_loader = data.DataLoader( @@ -220,6 +227,8 @@ def run(*options, cfg=None, local_rank=0, debug=False): model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[device], find_unused_parameters=True) snapshot_duration = scheduler_step * len(train_loader) + if debug: + snapshot_duration = 2 warmup_duration = 5 * len(train_loader) warmup_scheduler = LinearCyclicalScheduler( optimizer, diff --git a/contrib/experiments/interpretation/dutchf3_section/local/test.py b/contrib/experiments/interpretation/dutchf3_section/local/test.py index 5b4d6858..384ac64b 100644 --- a/contrib/experiments/interpretation/dutchf3_section/local/test.py +++ b/contrib/experiments/interpretation/dutchf3_section/local/test.py @@ -7,6 +7,7 @@ Modified version of the Alaudah testing script # TODO: Needs to be improved. Needs to be able to run across multiple GPUs and better # factoring around the loader +# issue: https://github.com/microsoft/seismic-deeplearning/issues/268 """ import logging diff --git a/examples/interpretation/notebooks/utilities.py b/examples/interpretation/notebooks/utilities.py index cc91523a..f0d3b9e3 100644 --- a/examples/interpretation/notebooks/utilities.py +++ b/examples/interpretation/notebooks/utilities.py @@ -266,6 +266,7 @@ def plot_aline(aline, labels, xlabel, ylabel="depth"): def validate_config_paths(config): """Checks that all paths in the config file are valid""" # TODO: this is currently hardcoded, in the future, its better to have a more generic solution. + # issue https://github.com/microsoft/seismic-deeplearning/issues/265 # Make sure DATASET.ROOT directory exist: assert os.path.isdir(config.DATASET.ROOT), ( @@ -351,6 +352,7 @@ def download_pretrained_model(config): if dataset == "penobscot": if model == "hrnet": # TODO: the code should check if the model uses patches or sections. + # issue: https://github.com/microsoft/seismic-deeplearning/issues/266 url = "https://deepseismicsharedstore.blob.core.windows.net/master-public-models/penobscot_hrnet_patch_section_depth.pth" else: raise NotImplementedError( @@ -421,6 +423,7 @@ def download_pretrained_model(config): # Update config MODEL.PRETRAINED # TODO: Only HRNet uses a pretrained model currently. + # issue https://github.com/microsoft/seismic-deeplearning/issues/267 opts = [ "MODEL.PRETRAINED", pretrained_model_path, diff --git a/experiments/interpretation/dutchf3_patch/local/default.py b/experiments/interpretation/dutchf3_patch/local/default.py index e34627a8..aac539ea 100644 --- a/experiments/interpretation/dutchf3_patch/local/default.py +++ b/experiments/interpretation/dutchf3_patch/local/default.py @@ -58,6 +58,7 @@ _C.TRAIN.PATCH_SIZE = 99 _C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 _C.TRAIN.STD = 0.20977 # 0.20976548783479299 # TODO: Should we apply std scaling? +# issue: https://github.com/microsoft/seismic-deeplearning/issues/269 _C.TRAIN.DEPTH = "no" # Options are None, Patch and Section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index 3d6eebc7..ee4c0b09 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -420,6 +420,7 @@ def test(*options, cfg=None, debug=False): ) # TODO: make sure that this is consistent with how normalization and agumentation for train.py + # issue: https://github.com/microsoft/seismic-deeplearning/issues/270 patch_aug = Compose( [ Resize( diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index 4cd710ad..e11dd059 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -18,11 +18,7 @@ interpolate_to_fit_data, parse_labels_in_image, get_coordinates_for_slice, - get_grid, - augment_flip, - augment_rot_xy, - augment_rot_z, - augment_stretch, + get_grid, rand_int, trilinear_interpolation, ) @@ -52,46 +48,6 @@ def _test2_labels_for(data_dir): return path.join(data_dir, "test_once", "test2_labels.npy") -def readSEGY(filename): - """[summary] - Read the segy file and return the data as a numpy array and a dictionary describing what has been read in. - - Arguments: - filename {str} -- .segy file location. - - Returns: - [type] -- 3D segy data as numy array and a dictionary with metadata information - """ - - # TODO: we really need to add logging to this repo - print("Loading data cube from", filename, "with:") - - # Read full data cube - data = segyio.tools.cube(filename) - - # Put temporal axis first - data = np.moveaxis(data, -1, 0) - - # Make data cube fast to acess - data = np.ascontiguousarray(data, "float32") - - # Read meta data - segyfile = segyio.open(filename, "r") - print(" Crosslines: ", segyfile.xlines[0], ":", segyfile.xlines[-1]) - print(" Inlines: ", segyfile.ilines[0], ":", segyfile.ilines[-1]) - print(" Timeslices: ", "1", ":", data.shape[0]) - - # Make dict with cube-info - data_info = {} - data_info["crossline_start"] = segyfile.xlines[0] - data_info["inline_start"] = segyfile.ilines[0] - data_info["timeslice_start"] = 1 # Todo: read this from segy - data_info["shape"] = data.shape - # Read dt and other params needed to do create a new - - return data, data_info - - def read_labels(fname, data_info): """ Read labels from an image. @@ -156,95 +112,6 @@ def read_labels(fname, data_info): return label_imgs, label_coordinates - -def get_random_batch( - data_cube, - label_coordinates, - im_size, - batch_size, - index, - random_flip=False, - random_stretch=None, - random_rot_xy=None, - random_rot_z=None, -): - """ - Returns a batch of augmented samples with center pixels randomly drawn from label_coordinates - - Args: - data_cube: 3D numpy array with floating point velocity values - label_coordinates: 3D coordinates of the labeled training slice - im_size: size of the 3D voxel which we're cutting out around each label_coordinate - batch_size: size of the batch - index: element index of this element in a batch - random_flip: bool to perform random voxel flip - random_stretch: bool to enable random stretch - random_rot_xy: bool to enable random rotation of the voxel around dim-0 and dim-1 - random_rot_z: bool to enable random rotation around dim-2 - - Returns: - a tuple of batch numpy array array of data with dimension - (batch, 1, data_cube.shape[0], data_cube.shape[1], data_cube.shape[2]) and the associated labels as an array - of size (batch). - """ - - # always generate only one datapoint - batch_size controls class balance - num_batch_size = 1 - - # Make 3 im_size elements - if isinstance(im_size, int): - im_size = [im_size, im_size, im_size] - - # Output arrays - batch = np.zeros([num_batch_size, 1, im_size[0], im_size[1], im_size[2]]) - ret_labels = np.zeros([num_batch_size]) - - class_keys = list(label_coordinates) - n_classes = len(class_keys) - - # We seek to have a balanced batch with equally many samples from each class. - # get total number of samples per class - samples_per_class = batch_size // n_classes - # figure out index relative to zero (not sequentially counting points) - index = index - batch_size * (index // batch_size) - # figure out which class to sample for this datapoint - class_ind = index // samples_per_class - - # Start by getting a grid centered around (0,0,0) - grid = get_grid(im_size) - - # Apply random flip - if random_flip: - grid = augment_flip(grid) - - # Apply random rotations - if random_rot_xy: - grid = augment_rot_xy(grid, random_rot_xy) - if random_rot_z: - grid = augment_rot_z(grid, random_rot_z) - - # Apply random stretch - if random_stretch: - grid = augment_stretch(grid, random_stretch) - - # Pick random location from the label_coordinates for this class: - coords_for_class = label_coordinates[class_keys[class_ind]] - random_index = rand_int(0, coords_for_class.shape[1]) - coord = coords_for_class[:, random_index : random_index + 1] - - # Move grid to be centered around this location - grid += coord - - # Interpolate samples at grid from the data: - sample = trilinear_interpolation(data_cube, grid) - - # Insert in output arrays - ret_labels[0] = class_ind - batch[0, 0, :, :, :] = np.reshape(sample, (im_size[0], im_size[1], im_size[2])) - - return batch, ret_labels - - class SectionLoader(data.Dataset): """ Base class for section data loader @@ -298,72 +165,6 @@ def transform(self, img, lbl): return torch.from_numpy(img).float(), torch.from_numpy(lbl).long() -class VoxelLoader(data.Dataset): - def __init__( - self, root_path, filename, window_size=65, split="train", n_classes=2, gen_coord_list=False, len=None, - ): - - assert split == "train" or split == "val" - - # location of the file - self.root_path = root_path - self.split = split - self.n_classes = n_classes - self.window_size = window_size - self.coord_list = None - self.filename = filename - self.full_filename = path.join(root_path, filename) - - # Read 3D cube - # NOTE: we cannot pass this data manually as serialization of data into each python process is costly, - # so each worker has to load the data on its own. - self.data, self.data_info = readSEGY(self.full_filename) - if len: - self.len = len - else: - self.len = self.data.size - self.labels = None - - if gen_coord_list: - # generate a list of coordinates to index the entire voxel - # memory footprint of this isn't large yet, so not need to wrap as a generator - nx, ny, nz = self.data.shape - x_list = range(self.window_size, nx - self.window_size) - y_list = range(self.window_size, ny - self.window_size) - z_list = range(self.window_size, nz - self.window_size) - - print("-- generating coord list --") - # TODO: is there any way to use a generator with pyTorch data loader? - self.coord_list = list(itertools.product(x_list, y_list, z_list)) - - def __len__(self): - return self.len - - def __getitem__(self, index): - - # TODO: can we specify a pixel mathematically by index? - pixel = self.coord_list[index] - x, y, z = pixel - # TODO: current bottleneck - can we slice out voxels any faster - small_cube = self.data[ - x - self.window : x + self.window + 1, - y - self.window : y + self.window + 1, - z - self.window : z + self.window + 1, - ] - - return small_cube[np.newaxis, :, :, :], pixel - - # TODO: do we need a transformer for voxels? - """ - def transform(self, img, lbl): - # to be in the BxCxHxW that PyTorch uses: - lbl = np.expand_dims(lbl, 0) - if len(img.shape) == 2: - img = np.expand_dims(img, 0) - return torch.from_numpy(img).float(), torch.from_numpy(lbl).long() - """ - - class TrainSectionLoader(SectionLoader): """ Training data loader for sections @@ -447,47 +248,6 @@ def __getitem__(self, index): return im, lbl -class TrainVoxelWaldelandLoader(VoxelLoader): - def __init__( - self, root_path, filename, split="train", window_size=65, batch_size=None, len=None, - ): - super(TrainVoxelWaldelandLoader, self).__init__( - root_path, filename, split=split, window_size=window_size, len=len - ) - - label_fname = None - if split == "train": - label_fname = path.join(self.root_path, "inline_339.png") - elif split == "val": - label_fname = path.join(self.root_path, "inline_405.png") - else: - raise Exception("undefined split") - - self.class_imgs, self.coordinates = read_labels(label_fname, self.data_info) - - self.batch_size = batch_size if batch_size else 1 - - def __getitem__(self, index): - # print(index) - batch, labels = get_random_batch( - self.data, - self.coordinates, - self.window_size, - self.batch_size, - index, - random_flip=True, - random_stretch=0.2, - random_rot_xy=180, - random_rot_z=15, - ) - - return batch, labels - - -# TODO: write TrainVoxelLoaderWithDepth -TrainVoxelLoaderWithDepth = TrainVoxelWaldelandLoader - - class TestSectionLoader(SectionLoader): """ Test data loader for sections @@ -574,15 +334,6 @@ def __getitem__(self, index): return im, lbl -class TestVoxelWaldelandLoader(VoxelLoader): - def __init__(self, data_dir, split="test"): - super(TestVoxelWaldelandLoader, self).__init__(data_dir, split=split) - - -# TODO: write TestVoxelLoaderWithDepth -TestVoxelLoaderWithDepth = TestVoxelWaldelandLoader - - def _transform_WH_to_HW(numpy_array): assert len(numpy_array.shape) >= 2, "This method needs at least 2D arrays" return np.swapaxes(numpy_array, -2, -1) @@ -627,6 +378,7 @@ def __getitem__(self, index): # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 # TODO: Remember we are cancelling the shift since we no longer pad + # issue: https://github.com/microsoft/seismic-deeplearning/issues/273 shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift @@ -680,6 +432,7 @@ def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmen # loading txt file and create patches. if not txt_path: self.split = "test1" # TODO: Fix this can also be test2 + # issue: https://github.com/microsoft/seismic-deeplearning/issues/274 txt_path = path.join(self.data_dir, "splits", "patch_" + self.split + ".txt") patch_list = tuple(open(txt_path, "r")) patch_list = [id_.rstrip() for id_ in patch_list] @@ -760,6 +513,7 @@ def __getitem__(self, index): # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 # TODO: Remember we are cancelling the shift since we no longer pad + # issue https://github.com/microsoft/seismic-deeplearning/issues/273 shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift @@ -772,6 +526,7 @@ def __getitem__(self, index): im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) # TODO: Add check for rotation augmentations and raise warning if found + # issue: https://github.com/microsoft/seismic-deeplearning/issues/275 if self.augmentations is not None: augmented_dict = self.augmentations(image=im, mask=lbl) im, lbl = augmented_dict["image"], augmented_dict["mask"] @@ -827,6 +582,7 @@ def __getitem__(self, index): # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 # TODO: Remember we are cancelling the shift since we no longer pad + # issue: https://github.com/microsoft/seismic-deeplearning/issues/273 shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift if direction == "i": @@ -862,9 +618,6 @@ def __repr__(self): _TRAIN_SECTION_LOADERS = {"section": TrainSectionLoaderWithDepth} -_TRAIN_VOXEL_LOADERS = {"voxel": TrainVoxelLoaderWithDepth} - - def get_patch_loader(cfg): assert cfg.TRAIN.DEPTH in [ "section", @@ -884,15 +637,6 @@ def get_section_loader(cfg): return _TRAIN_SECTION_LOADERS.get(cfg.TRAIN.DEPTH, TrainSectionLoader) -def get_voxel_loader(cfg): - assert cfg.TRAIN.DEPTH in [ - "voxel", - "none", - ], f"Depth {cfg.TRAIN.DEPTH} not supported for section data. \ - Valid values: voxel, none." - return _TRAIN_SECTION_LOADERS.get(cfg.TRAIN.DEPTH, TrainVoxelWaldelandLoader) - - _TEST_LOADERS = {"section": TestSectionLoaderWithDepth} diff --git a/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py b/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py index 8ebc6790..110abaf5 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py +++ b/interpretation/deepseismic_interpretation/dutchf3/utils/batch.py @@ -182,63 +182,6 @@ def augment_flip(grid): return grid -def augment_stretch(grid, stretch_factor): - """ - Random stretch/scale - - Args: - grid: 3D coordinate grid of the voxel - stretch_factor: this is actually a boolean which triggers stretching - TODO: change this to just call the function and not do -1,1 in rand_float - - Returns: - stretched grid coordinates - """ - stretch = rand_float(-stretch_factor, stretch_factor) - grid *= 1 + stretch - return grid - - -def augment_rot_xy(grid, random_rot_xy): - """ - Random rotation - - Args: - grid: coordinate grid list of 3D points - random_rot_xy: this is actually a boolean which triggers rotation - TODO: change this to just call the function and not do -1,1 in rand_float - - Returns: - randomly rotated grid - """ - theta = np.deg2rad(rand_float(-random_rot_xy, random_rot_xy)) - x = grid[2, :] * np.cos(theta) - grid[1, :] * np.sin(theta) - y = grid[2, :] * np.sin(theta) + grid[1, :] * np.cos(theta) - grid[1, :] = x - grid[2, :] = y - return grid - - -def augment_rot_z(grid, random_rot_z): - """ - Random tilt around z-axis (dim-2) - - Args: - grid: coordinate grid list of 3D points - random_rot_z: this is actually a boolean which triggers rotation - TODO: change this to just call the function and not do -1,1 in rand_float - - Returns: - randomly tilted coordinate grid - """ - theta = np.deg2rad(rand_float(-random_rot_z, random_rot_z)) - z = grid[0, :] * np.cos(theta) - grid[1, :] * np.sin(theta) - x = grid[0, :] * np.sin(theta) + grid[1, :] * np.cos(theta) - grid[0, :] = z - grid[1, :] = x - return grid - - def trilinear_interpolation(input_array, indices): """ Linear interpolation @@ -343,63 +286,6 @@ def rand_bool(): return bool(np.random.randint(0, 2)) -def augment_stretch(grid, stretch_factor): - """ - Random stretch/scale - - Args: - grid: 3D coordinate grid of the voxel - stretch_factor: this is actually a boolean which triggers stretching - TODO: change this to just call the function and not do -1,1 in rand_float - - Returns: - stretched grid coordinates - """ - stretch = rand_float(-stretch_factor, stretch_factor) - grid *= 1 + stretch - return grid - - -def augment_rot_xy(grid, random_rot_xy): - """ - Random rotation - - Args: - grid: coordinate grid list of 3D points - random_rot_xy: this is actually a boolean which triggers rotation - TODO: change this to just call the function and not do -1,1 in rand_float - - Returns: - randomly rotated grid - """ - theta = np.deg2rad(rand_float(-random_rot_xy, random_rot_xy)) - x = grid[2, :] * np.cos(theta) - grid[1, :] * np.sin(theta) - y = grid[2, :] * np.sin(theta) + grid[1, :] * np.cos(theta) - grid[1, :] = x - grid[2, :] = y - return grid - - -def augment_rot_z(grid, random_rot_z): - """ - Random tilt around z-axis (dim-2) - - Args: - grid: coordinate grid list of 3D points - random_rot_z: this is actually a boolean which triggers rotation - TODO: change this to just call the function and not do -1,1 in rand_float - - Returns: - randomly tilted coordinate grid - """ - theta = np.deg2rad(rand_float(-random_rot_z, random_rot_z)) - z = grid[0, :] * np.cos(theta) - grid[1, :] * np.sin(theta) - x = grid[0, :] * np.sin(theta) + grid[1, :] * np.cos(theta) - grid[0, :] = z - grid[1, :] = x - return grid - - def trilinear_interpolation(input_array, indices): """ Linear interpolation diff --git a/interpretation/deepseismic_interpretation/models/texture_net.py b/interpretation/deepseismic_interpretation/models/texture_net.py index da5371d5..3bc3b1da 100644 --- a/interpretation/deepseismic_interpretation/models/texture_net.py +++ b/interpretation/deepseismic_interpretation/models/texture_net.py @@ -7,6 +7,7 @@ from torch import nn # TODO; set chanels from yaml config file +# issue: https://github.com/microsoft/seismic-deeplearning/issues/277 class TextureNet(nn.Module): def __init__(self, n_classes=2): super(TextureNet, self).__init__() diff --git a/interpretation/deepseismic_interpretation/penobscot/metrics.py b/interpretation/deepseismic_interpretation/penobscot/metrics.py index 846faacc..3411e145 100644 --- a/interpretation/deepseismic_interpretation/penobscot/metrics.py +++ b/interpretation/deepseismic_interpretation/penobscot/metrics.py @@ -18,7 +18,7 @@ def _torch_hist(label_true, label_pred, n_class): Returns: [type]: [description] """ - # TODO Add exceptions + assert len(label_true.shape) == 1, "Labels need to be 1D" assert len(label_pred.shape) == 1, "Predictions need to be 1D" mask = (label_true >= 0) & (label_true < n_class) @@ -34,6 +34,7 @@ def _default_tensor(image_height, image_width, pad_value=255): # TODO: make output transform unpad and scale down mask # scale up y_pred and remove padding +# issue: https://github.com/microsoft/seismic-deeplearning/issues/276 class InlineMeanIoU(Metric): """Compute Mean IoU for Inline @@ -95,6 +96,7 @@ def reset(self): def update(self, output): y_pred, y, ids, patch_locations = output # TODO: Make assertion exception + # issue: https://github.com/microsoft/seismic-deeplearning/issues/276 max_prediction = y_pred.max(1)[1].squeeze() assert y.shape == max_prediction.shape, "Shape not the same" diff --git a/scripts/prepare_dutchf3.py b/scripts/prepare_dutchf3.py index 5c5a4e08..33209074 100644 --- a/scripts/prepare_dutchf3.py +++ b/scripts/prepare_dutchf3.py @@ -331,7 +331,6 @@ def split_alaudah_et_al_19(data_dir, stride, patch_size, fraction_validation=0.2 _write_split_files(splits_path, train_list, val_list, loader_type) -# TODO: Try https://github.com/Chilipp/docrep for doscstring reuse class SplitTrainValCLI(object): def section(self, data_dir, label_file, per_val=0.2, log_config="logging.conf", output_dir=None, From e0de54ca237a557c609fc6032c7eb1d076d5e1ea Mon Sep 17 00:00:00 2001 From: yalaudah Date: Wed, 22 Apr 2020 14:42:43 +0000 Subject: [PATCH 199/207] Refactoring train.py, removing OpenCV, adding training results to Tensborboard, bug fixes (#264) I think moving forward, we'll use smaller PRs. But here are the changes in this one: Fixes issue #236 that involves rewriting a big portion of train.py such that: All the tensorboard event handlers are organized in tensorboard_handlers.py and only called in train.py to log training and validation results in Tensorboard The code logs the same results for training and validation. Also, it adds the class IoU score as well. All single-use functions (e.g. _select_max, _tensor_to_numpy, _select_pred_and_mask) are lambda functions now The code is organized into more meaningful "chunks".. e.g. all the optimizer-related code should be together if possible, same thing for logging, configuration, loaders, tensorboard, ..etc. In addition: Fixed a visualization bug where the seismic images where not normalized correctly. This solves Issue #217. Fixed a visualization bug where the predictions where not masked where the input image was padded. This improves the ability to visually inspect and evaluate the results. This solves Issue #230. Fixes a potential issue where Tensorboard can crash when a large training batchsize is used. Now the number of images visualized in Tensorboard from every batch has an upper limit. Completely removed OpenCV as a dependency from the DeepSeismic Repo. It was only used in a small part of the code where it wasn't really necessary, and OpenCV is a huge library. Fixes Issue #218 where the epoch number for the images in Tensorboard was always logged as 1 (therefore, not allowing use to see the epoch number of the different results in Tensorboard. Removes the HorovodLRScheduler class since its no longer used Removes toolz.take from Debug mode, and uses PyTorch's native Subset() dataset class Changes default patch size for the HRNet model to 256 In addition to several other minor changes Co-authored-by: Yazeed Alaudah Co-authored-by: Ubuntu Co-authored-by: Max Kaznady --- AUTHORS.md | 3 +- README.md | 10 +- .../distributed/configs/hrnet.yaml | 3 +- .../distributed/configs/patch_deconvnet.yaml | 2 +- .../configs/patch_deconvnet_skip.yaml | 2 +- .../distributed/configs/seresnet_unet.yaml | 2 +- .../distributed/configs/unet.yaml | 2 +- .../dutchf3_patch/distributed/default.py | 3 +- .../dutchf3_patch/distributed/train.py | 118 ++++---- .../dutchf3_section/local/default.py | 3 +- .../dutchf3_section/local/train.py | 6 +- .../dutchf3_voxel/configs/texture_net.yaml | 2 +- .../interpretation/dutchf3_voxel/default.py | 4 +- .../interpretation/dutchf3_voxel/train.py | 2 +- .../penobscot/local/configs/hrnet.yaml | 3 +- .../local/configs/seresnet_unet.yaml | 2 +- .../interpretation/penobscot/local/default.py | 3 +- .../interpretation/penobscot/local/test.py | 41 +-- .../interpretation/penobscot/local/train.py | 63 ++--- .../cv_lib/event_handlers/logging_handlers.py | 43 +-- .../event_handlers/tensorboard_handlers.py | 76 ++++-- environment/anaconda/local/environment.yml | 1 - environment/docker/apex/dockerfile | 2 +- environment/docker/horovod/dockerfile | 2 +- ..._patch_model_training_and_evaluation.ipynb | 9 +- .../dutchf3_patch/local/configs/hrnet.yaml | 11 +- .../local/configs/patch_deconvnet.yaml | 2 +- .../local/configs/patch_deconvnet_skip.yaml | 2 +- .../local/configs/seresnet_unet.yaml | 2 +- .../dutchf3_patch/local/configs/unet.yaml | 2 +- .../dutchf3_patch/local/default.py | 6 +- .../dutchf3_patch/local/test.py | 95 ++----- .../dutchf3_patch/local/train.py | 253 ++++++------------ .../dutchf3/data.py | 14 +- tests/cicd/main_build.yml | 25 +- 35 files changed, 325 insertions(+), 494 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index cb2995fa..b903ddb4 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -9,14 +9,15 @@ Contributors (sorted alphabetically) ------------------------------------- To contributors: please add your name to the list when you submit a patch to the project. +* Yazeed Alaudah * Ashish Bhatia +* Sharat Chikkerur * Daniel Ciborowski * George Iordanescu * Ilia Karmanov * Max Kaznady * Vanja Paunic * Mathew Salvaris -* Sharat Chikkerur * Wee Hyong Tok ## How to be a contributor to the repository diff --git a/README.md b/README.md index cf8886a7..44d60b5a 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ for the Penobscot dataset follow the same instructions but navigate to the [peno ## Contributing -This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [https://cla.opensource.microsoft.com](https://cla.opensource.microsoft.com). ### Submitting a Pull Request @@ -321,7 +321,7 @@ A typical output will be: someusername@somevm:/projects/DeepSeismic$ which python /anaconda/envs/py35/bin/python ``` -which will indicate that anaconda folder is __/anaconda__. We'll refer to this location in the instructions below, but you should update the commands according to your local anaconda folder. +which will indicate that anaconda folder is `__/anaconda__`. We'll refer to this location in the instructions below, but you should update the commands according to your local anaconda folder.
Data Science Virtual Machine conda package installation errors @@ -339,7 +339,7 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l
Data Science Virtual Machine conda package installation warnings - It could happen that while creating the conda environment defined by environment/anaconda/local/environment.yml on an Ubuntu DSVM, one can get multiple warnings like so: + It could happen that while creating the conda environment defined by `environment/anaconda/local/environment.yml` on an Ubuntu DSVM, one can get multiple warnings like so: ``` WARNING conda.gateways.disk.delete:unlink_or_rename_to_trash(140): Could not remove or rename /anaconda/pkgs/ipywidgets-7.5.1-py_0/site-packages/ipywidgets-7.5.1.dist-info/LICENSE. Please remove this file manually (you may need to reboot to free file handles) ``` @@ -350,7 +350,7 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l sudo chown -R $USER /anaconda ``` - After these command completes, try creating the conda environment in __environment/anaconda/local/environment.yml__ again. + After these command completes, try creating the conda environment in `__environment/anaconda/local/environment.yml__` again.
@@ -395,7 +395,7 @@ which will indicate that anaconda folder is __/anaconda__. We'll refer to this l
GPU out of memory errors - You should be able to see how much GPU memory your process is using by running + You should be able to see how much GPU memory your process is using by running: ```bash nvidia-smi ``` diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml index 04ad6479..fe3995f6 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/hrnet.yaml @@ -9,6 +9,7 @@ WORKERS: 4 PRINT_FREQ: 10 LOG_CONFIG: logging.conf SEED: 2019 +OPENCV_BORDER_CONSTANT: 0 DATASET: @@ -73,7 +74,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "section" #"patch" # Options are No, Patch and Section + DEPTH: "section" #"patch" # Options are none, patch, and section STRIDE: 50 PATCH_SIZE: 100 AUGMENTATIONS: diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml index eb89ff00..fa1d6add 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet.yaml @@ -30,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" #"patch" # Options are None, Patch and Section + DEPTH: "none" #"patch" # Options are none, patch, and section STRIDE: 50 PATCH_SIZE: 99 AUGMENTATIONS: diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml index eb89ff00..fa1d6add 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/patch_deconvnet_skip.yaml @@ -30,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" #"patch" # Options are None, Patch and Section + DEPTH: "none" #"patch" # Options are none, patch, and section STRIDE: 50 PATCH_SIZE: 99 AUGMENTATIONS: diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml index d0b8126f..9bc10d34 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/seresnet_unet.yaml @@ -30,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "section" # Options are No, Patch and Section + DEPTH: "section" # Options are none, patch, and section STRIDE: 50 PATCH_SIZE: 100 AUGMENTATIONS: diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml index 2843e62c..3fe5f439 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/configs/unet.yaml @@ -33,7 +33,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "section" # Options are No, Patch and Section + DEPTH: "section" # Options are none, patch, and section STRIDE: 50 PATCH_SIZE: 100 AUGMENTATIONS: diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/default.py b/contrib/experiments/interpretation/dutchf3_patch/distributed/default.py index bf23527b..34d3c4d3 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/default.py +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/default.py @@ -20,6 +20,7 @@ _C.PIN_MEMORY = True _C.LOG_CONFIG = "logging.conf" _C.SEED = 42 +_C.OPENCV_BORDER_CONSTANT = 0 # Cudnn related params _C.CUDNN = CN() @@ -58,7 +59,7 @@ _C.TRAIN.PATCH_SIZE = 99 _C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 _C.TRAIN.STD = 0.21 # 0.20976548783479299 -_C.TRAIN.DEPTH = "None" # Options are None, Patch and Section +_C.TRAIN.DEPTH = "none" # Options are: none, patch, and section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py index bc28249a..33bb0045 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -21,59 +21,30 @@ import os from os import path -import cv2 import fire import numpy as np import toolz import torch -from albumentations import Compose, HorizontalFlip, Normalize, Resize, PadIfNeeded -from cv_lib.utils import load_log_configuration -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) -from cv_lib.segmentation import models -from cv_lib.segmentation import extract_metric_from -from deepseismic_interpretation.dutchf3.data import get_patch_loader, decode_segmap -from cv_lib.segmentation.dutchf3.engine import ( - create_supervised_evaluator, - create_supervised_trainer, -) - -from ignite.metrics import Loss -from cv_lib.segmentation.metrics import ( - pixelwise_accuracy, - class_accuracy, - mean_class_accuracy, - class_iou, - mean_iou, -) - -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, - np_to_tb, -) -from default import _C as config -from default import update_config -from ignite.contrib.handlers import ( - ConcatScheduler, - CosineAnnealingScheduler, - LinearCyclicalScheduler, -) +from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize +from ignite.contrib.handlers import ConcatScheduler, CosineAnnealingScheduler, LinearCyclicalScheduler from ignite.engine import Events +from ignite.metrics import Loss from ignite.utils import convert_tensor from toolz import compose, curry from torch.utils import data +from cv_lib.event_handlers import SnapshotHandler, logging_handlers, tensorboard_handlers +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import create_image_writer, create_summary_writer +from cv_lib.segmentation import extract_metric_from, models +from cv_lib.segmentation.dutchf3.engine import create_supervised_evaluator, create_supervised_trainer +from cv_lib.segmentation.dutchf3.utils import current_datetime, generate_path, git_branch, git_hash, np_to_tb +from cv_lib.segmentation.metrics import class_accuracy, class_iou, mean_class_accuracy, mean_iou, pixelwise_accuracy +from cv_lib.utils import load_log_configuration +from deepseismic_interpretation.dutchf3.data import decode_segmap, get_patch_loader +from default import _C as config +from default import update_config + def prepare_batch(batch, device=None, non_blocking=False): x, y = batch @@ -123,7 +94,7 @@ def run(*options, cfg=None, local_rank=0, debug=False): # provide environment variables, and requires that you use init_method=`env://`. torch.distributed.init_process_group(backend="nccl", init_method="env://") - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + epochs_per_cycle = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK torch.manual_seed(config.SEED) @@ -137,7 +108,7 @@ def run(*options, cfg=None, local_rank=0, debug=False): PadIfNeeded( min_height=config.TRAIN.PATCH_SIZE, min_width=config.TRAIN.PATCH_SIZE, - border_mode=cv2.BORDER_CONSTANT, + border_mode=config.OPENCV_BORDER_CONSTANT, always_apply=True, mask_value=255, ), @@ -147,7 +118,7 @@ def run(*options, cfg=None, local_rank=0, debug=False): PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, + border_mode=config.OPENCV_BORDER_CONSTANT, always_apply=True, mask_value=255, ), @@ -185,15 +156,16 @@ def run(*options, cfg=None, local_rank=0, debug=False): logger.info(f"Validation examples {len(val_set)}") n_classes = train_set.n_classes - #if debug: - #val_set = data.Subset(val_set, range(config.VALIDATION.BATCH_SIZE_PER_GPU)) - #train_set = data.Subset(train_set, range(config.TRAIN.BATCH_SIZE_PER_GPU*2)) + if debug: + logger.info("Running in debug mode..") + train_set = data.Subset(train_set, list(range(4))) + val_set = data.Subset(val_set, list(range(4))) logger.info(f"Training examples {len(train_set)}") logger.info(f"Validation examples {len(val_set)}") - train_sampler = torch.utils.data.distributed.DistributedSampler(train_set, num_replicas=world_size, rank=local_rank) + train_sampler = torch.utils.data.distributed.DistributedSampler(train_set, num_replicas=world_size, rank=local_rank) train_loader = data.DataLoader( train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, sampler=train_sampler, ) @@ -226,9 +198,7 @@ def run(*options, cfg=None, local_rank=0, debug=False): model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[device], find_unused_parameters=True) - snapshot_duration = scheduler_step * len(train_loader) - if debug: - snapshot_duration = 2 + snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2*len(train_loader) warmup_duration = 5 * len(train_loader) warmup_scheduler = LinearCyclicalScheduler( optimizer, @@ -238,7 +208,7 @@ def run(*options, cfg=None, local_rank=0, debug=False): cycle_size=10 * len(train_loader), ) cosine_scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR * world_size, config.TRAIN.MIN_LR * world_size, snapshot_duration, + optimizer, "lr", config.TRAIN.MAX_LR * world_size, config.TRAIN.MIN_LR * world_size, cycle_size=snapshot_duration, ) scheduler = ConcatScheduler(schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration]) @@ -270,18 +240,27 @@ def _select_pred_and_mask(model_out_dict): device=device, ) - # Set the validation run to start on the epoch completion of the training run + # Set the validation run to start on the epoch completion of the training run + trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) if local_rank == 0: # Run only on master process trainer.add_event_handler( - Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU), + Events.ITERATION_COMPLETED, + logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU), ) - trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) + trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) try: - output_dir = generate_path(config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) + output_dir = generate_path( + config.OUTPUT_DIR, + git_branch(), + git_hash(), + config_file_name, + config.TRAIN.MODEL_DIR, + current_datetime(), + ) except TypeError: output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) @@ -322,9 +301,7 @@ def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy) - transform_pred = compose(transform_func, _select_max) - evaluator.add_event_handler( Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Validation/Image", "image"), ) @@ -341,19 +318,22 @@ def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 checkpoint_handler = SnapshotHandler( - output_dir, - config.MODEL.NAME, - extract_metric_from("mIoU"), - snapshot_function, + output_dir, config.MODEL.NAME, extract_metric_from("mIoU"), snapshot_function, ) evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) - logger.info("Starting training") - + if debug: - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = config.TRAIN.BATCH_SIZE_PER_GPU*2, seed = config.SEED) + trainer.run( + train_loader, + max_epochs=config.TRAIN.END_EPOCH, + epoch_length=config.TRAIN.BATCH_SIZE_PER_GPU * 2, + seed=config.SEED, + ) else: - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = len(train_loader), seed = config.SEED) + trainer.run( + train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length=len(train_loader), seed=config.SEED + ) if __name__ == "__main__": diff --git a/contrib/experiments/interpretation/dutchf3_section/local/default.py b/contrib/experiments/interpretation/dutchf3_section/local/default.py index 5e296295..2b4888d2 100644 --- a/contrib/experiments/interpretation/dutchf3_section/local/default.py +++ b/contrib/experiments/interpretation/dutchf3_section/local/default.py @@ -21,6 +21,7 @@ _C.PIN_MEMORY = True _C.LOG_CONFIG = "./logging.conf" # Logging config file relative to the experiment _C.SEED = 42 +_C.OPENCV_BORDER_CONSTANT = 0 # Cudnn related params _C.CUDNN = CN() @@ -55,7 +56,7 @@ _C.TRAIN.AUGMENTATION = True _C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 _C.TRAIN.STD = 0.20977 # 0.20976548783479299 -_C.TRAIN.DEPTH = "none" # Options are 'none', 'patch' and 'section' +_C.TRAIN.DEPTH = "none" # Options are: none, patch, and section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 diff --git a/contrib/experiments/interpretation/dutchf3_section/local/train.py b/contrib/experiments/interpretation/dutchf3_section/local/train.py index b216268e..5a9b4900 100644 --- a/contrib/experiments/interpretation/dutchf3_section/local/train.py +++ b/contrib/experiments/interpretation/dutchf3_section/local/train.py @@ -84,7 +84,7 @@ def run(*options, cfg=None, debug=False): load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + epochs_per_cycle = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK torch.manual_seed(config.SEED) @@ -164,8 +164,8 @@ def __len__(self): summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) - snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) + snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2*len(train_loader) + scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, cycle_size=snapshot_duration) # weights are inversely proportional to the frequency of the classes in # the training set diff --git a/contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml b/contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml index aeeffb86..3ff72dca 100644 --- a/contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml +++ b/contrib/experiments/interpretation/dutchf3_voxel/configs/texture_net.yaml @@ -29,7 +29,7 @@ TRAIN: LR: 0.02 MOMENTUM: 0.9 WEIGHT_DECAY: 0.0001 - DEPTH: "voxel" # Options are No, Patch, Section and Voxel + DEPTH: "voxel" # Options are none, patch, section and voxel MODEL_DIR: "models" VALIDATION: diff --git a/contrib/experiments/interpretation/dutchf3_voxel/default.py b/contrib/experiments/interpretation/dutchf3_voxel/default.py index 100da598..bcf84731 100644 --- a/contrib/experiments/interpretation/dutchf3_voxel/default.py +++ b/contrib/experiments/interpretation/dutchf3_voxel/default.py @@ -24,6 +24,8 @@ _C.PRINT_FREQ = 20 _C.LOG_CONFIG = "logging.conf" _C.SEED = 42 +_C.OPENCV_BORDER_CONSTANT = 0 + # size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only _C.WINDOW_SIZE = 65 @@ -50,7 +52,7 @@ _C.TRAIN.LR = 0.01 _C.TRAIN.MOMENTUM = 0.9 _C.TRAIN.WEIGHT_DECAY = 0.0001 -_C.TRAIN.DEPTH = "voxel" # Options are None, Patch and Section +_C.TRAIN.DEPTH = "voxel" # Options are none, patch and section _C.TRAIN.MODEL_DIR = "models" # This will be a subdirectory inside OUTPUT_DIR # validation diff --git a/contrib/experiments/interpretation/dutchf3_voxel/train.py b/contrib/experiments/interpretation/dutchf3_voxel/train.py index bd8cdf4b..3864e38f 100644 --- a/contrib/experiments/interpretation/dutchf3_voxel/train.py +++ b/contrib/experiments/interpretation/dutchf3_voxel/train.py @@ -208,7 +208,7 @@ def _select_pred_and_mask(model_out): summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) - snapshot_duration = 1 + snapshot_duration = 2 def snapshot_function(): return (trainer.state.iteration % snapshot_duration) == 0 diff --git a/contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml b/contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml index ba4b3967..7c711177 100644 --- a/contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml +++ b/contrib/experiments/interpretation/penobscot/local/configs/hrnet.yaml @@ -9,6 +9,7 @@ WORKERS: 4 PRINT_FREQ: 10 LOG_CONFIG: logging.conf SEED: 2019 +OPENCV_BORDER_CONSTANT: 0 DATASET: @@ -75,7 +76,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "patch" # Options are none, patch and section + DEPTH: "patch" # Options are none, patch, and section STRIDE: 64 PATCH_SIZE: 128 AUGMENTATIONS: diff --git a/contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml b/contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml index 3ba4d807..800cf4ce 100644 --- a/contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml +++ b/contrib/experiments/interpretation/penobscot/local/configs/seresnet_unet.yaml @@ -32,7 +32,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "patch" # Options are none, patch and section + DEPTH: "patch" # Options are none, patch, and section STRIDE: 64 PATCH_SIZE: 128 AUGMENTATIONS: diff --git a/contrib/experiments/interpretation/penobscot/local/default.py b/contrib/experiments/interpretation/penobscot/local/default.py index fa8e540e..d72946ce 100644 --- a/contrib/experiments/interpretation/penobscot/local/default.py +++ b/contrib/experiments/interpretation/penobscot/local/default.py @@ -21,6 +21,7 @@ _C.PIN_MEMORY = True _C.LOG_CONFIG = "logging.conf" _C.SEED = 42 +_C.OPENCV_BORDER_CONSTANT = 0 # size of voxel cube: WINDOW_SIZE x WINDOW_SIZE x WINDOW_SIZE; used for 3D models only _C.WINDOW_SIZE = 65 @@ -72,7 +73,7 @@ _C.TRAIN.MEAN = [-0.0001777, 0.49, -0.0000688] # 0.0009996710808862074 _C.TRAIN.STD = [0.14076, 0.2717, 0.06286] # 0.20976548783479299 _C.TRAIN.MAX = 1 -_C.TRAIN.DEPTH = "patch" # Options are none, patch and section +_C.TRAIN.DEPTH = "patch" # Options are none, patch, and section # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 diff --git a/contrib/experiments/interpretation/penobscot/local/test.py b/contrib/experiments/interpretation/penobscot/local/test.py index 8687085a..073c72f1 100644 --- a/contrib/experiments/interpretation/penobscot/local/test.py +++ b/contrib/experiments/interpretation/penobscot/local/test.py @@ -18,45 +18,30 @@ from itertools import chain from os import path -import cv2 import fire import numpy as np import torch import torchvision from albumentations import Compose, Normalize, PadIfNeeded, Resize -from cv_lib.utils import load_log_configuration +from ignite.engine import Events +from ignite.metrics import Loss +from ignite.utils import convert_tensor +from toolz import compose, tail, take +from toolz.sandbox.core import unzip +from torch.utils import data + from cv_lib.event_handlers import logging_handlers, tensorboard_handlers -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) +from cv_lib.event_handlers.tensorboard_handlers import create_image_writer, create_summary_writer from cv_lib.segmentation import models -from cv_lib.segmentation.metrics import ( - pixelwise_accuracy, - class_accuracy, - mean_class_accuracy, - class_iou, - mean_iou, -) -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, - np_to_tb, -) +from cv_lib.segmentation.dutchf3.utils import current_datetime, generate_path, git_branch, git_hash, np_to_tb +from cv_lib.segmentation.metrics import class_accuracy, class_iou, mean_class_accuracy, mean_iou, pixelwise_accuracy from cv_lib.segmentation.penobscot.engine import create_supervised_evaluator +from cv_lib.utils import load_log_configuration from deepseismic_interpretation.dutchf3.data import decode_segmap from deepseismic_interpretation.penobscot.data import get_patch_dataset from deepseismic_interpretation.penobscot.metrics import InlineMeanIoU from default import _C as config from default import update_config -from ignite.engine import Events -from ignite.metrics import Loss -from ignite.utils import convert_tensor -from toolz import compose, tail, take -from toolz.sandbox.core import unzip -from torch.utils import data def _prepare_batch(batch, device=None, non_blocking=False): @@ -139,7 +124,7 @@ def run(*options, cfg=None, debug=False): PadIfNeeded( min_height=config.TRAIN.PATCH_SIZE, min_width=config.TRAIN.PATCH_SIZE, - border_mode=cv2.BORDER_CONSTANT, + border_mode=config.OPENCV_BORDER_CONSTANT, always_apply=True, mask_value=mask_value, value=0, @@ -150,7 +135,7 @@ def run(*options, cfg=None, debug=False): PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, + border_mode=config.OPENCV_BORDER_CONSTANT, always_apply=True, mask_value=mask_value, value=0, diff --git a/contrib/experiments/interpretation/penobscot/local/train.py b/contrib/experiments/interpretation/penobscot/local/train.py index 22394b53..04f563b7 100644 --- a/contrib/experiments/interpretation/penobscot/local/train.py +++ b/contrib/experiments/interpretation/penobscot/local/train.py @@ -17,7 +17,6 @@ import logging.config from os import path -import cv2 import fire import numpy as np import torch @@ -29,43 +28,19 @@ from toolz import compose from torch.utils import data +from cv_lib.event_handlers import SnapshotHandler, logging_handlers, tensorboard_handlers +from cv_lib.event_handlers.logging_handlers import Evaluator +from cv_lib.event_handlers.tensorboard_handlers import create_image_writer, create_summary_writer +from cv_lib.segmentation import extract_metric_from, models +from cv_lib.segmentation.dutchf3.utils import current_datetime, generate_path, git_branch, git_hash, np_to_tb +from cv_lib.segmentation.metrics import class_accuracy, class_iou, mean_class_accuracy, mean_iou, pixelwise_accuracy +from cv_lib.segmentation.penobscot.engine import create_supervised_evaluator, create_supervised_trainer +from cv_lib.utils import load_log_configuration from deepseismic_interpretation.dutchf3.data import decode_segmap from deepseismic_interpretation.penobscot.data import get_patch_dataset -from cv_lib.utils import load_log_configuration -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) -from cv_lib.segmentation import models, extract_metric_from -from cv_lib.segmentation.penobscot.engine import ( - create_supervised_evaluator, - create_supervised_trainer, -) -from cv_lib.segmentation.metrics import ( - pixelwise_accuracy, - class_accuracy, - mean_class_accuracy, - class_iou, - mean_iou, -) -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, - np_to_tb, -) - from default import _C as config from default import update_config - mask_value = 255 _SEG_COLOURS = np.asarray( [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123],] @@ -107,7 +82,7 @@ def run(*options, cfg=None, debug=False): load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + epochs_per_cycle = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK torch.manual_seed(config.SEED) @@ -126,7 +101,7 @@ def run(*options, cfg=None, debug=False): PadIfNeeded( min_height=config.TRAIN.PATCH_SIZE, min_width=config.TRAIN.PATCH_SIZE, - border_mode=cv2.BORDER_CONSTANT, + border_mode=config.OPENCV_BORDER_CONSTANT, always_apply=True, mask_value=mask_value, value=0, @@ -137,7 +112,7 @@ def run(*options, cfg=None, debug=False): PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, + border_mode=config.OPENCV_BORDER_CONSTANT, always_apply=True, mask_value=mask_value, value=0, @@ -182,7 +157,7 @@ def run(*options, cfg=None, debug=False): if debug: val_set = data.Subset(val_set, range(3)) - val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS) + val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS) model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -203,8 +178,8 @@ def run(*options, cfg=None, debug=False): output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) - snapshot_duration = scheduler_step * len(train_loader) - scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration) + snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2*len(train_loader) + scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, cycle_size=snapshot_duration) # weights are inversely proportional to the frequency of the classes in # the training set @@ -306,9 +281,15 @@ def snapshot_function(): logger.info("Starting training") if debug: - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = config.TRAIN.BATCH_SIZE_PER_GPU, seed = config.SEED) + trainer.run( + train_loader, + max_epochs=config.TRAIN.END_EPOCH, + epoch_length=config.TRAIN.BATCH_SIZE_PER_GPU, + seed=config.SEED, + ) else: - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = len(train_loader), seed = config.SEED) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length=len(train_loader), seed=config.SEED) + if __name__ == "__main__": fire.Fire(run) diff --git a/cv_lib/cv_lib/event_handlers/logging_handlers.py b/cv_lib/cv_lib/event_handlers/logging_handlers.py index b7c41651..ea883a36 100644 --- a/cv_lib/cv_lib/event_handlers/logging_handlers.py +++ b/cv_lib/cv_lib/event_handlers/logging_handlers.py @@ -25,11 +25,8 @@ def log_lr(optimizer, engine): logger.info(f"lr - {lr}") -_DEFAULT_METRICS = {"pixacc": "Avg accuracy :", "nll": "Avg loss :"} - - @curry -def log_metrics(log_msg, engine, metrics_dict=_DEFAULT_METRICS): +def log_metrics(log_msg, engine, metrics_dict={"pixacc": "Avg accuracy :", "nll": "Avg loss :"}): logger = logging.getLogger(__name__) metrics = engine.state.metrics metrics_msg = " ".join([f"{metrics_dict[k]} {metrics[k]:.2f}" for k in metrics_dict]) @@ -44,6 +41,7 @@ def log_class_metrics(log_msg, engine, metrics_dict): logger.info(f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}]\n" + metrics_msg) +# TODO: remove Evaluator once other train.py scripts are updated class Evaluator: def __init__(self, evaluation_engine, data_loader): self._evaluation_engine = evaluation_engine @@ -51,40 +49,3 @@ def __init__(self, evaluation_engine, data_loader): def __call__(self, engine): self._evaluation_engine.run(self._data_loader) - - -class HorovodLRScheduler: - """ - Horovod: using `lr = base_lr * hvd.size()` from the very beginning leads to worse final - accuracy. Scale the learning rate `lr = base_lr` ---> `lr = base_lr * hvd.size()` during - the first five epochs. See https://arxiv.org/abs/1706.02677 for details. - After the warmup reduce learning rate by 10 on the 30th, 60th and 80th epochs. - """ - - def __init__( - self, base_lr, warmup_epochs, cluster_size, data_loader, optimizer, batches_per_allreduce, - ): - self._warmup_epochs = warmup_epochs - self._cluster_size = cluster_size - self._data_loader = data_loader - self._optimizer = optimizer - self._base_lr = base_lr - self._batches_per_allreduce = batches_per_allreduce - self._logger = logging.getLogger(__name__) - - def __call__(self, engine): - epoch = engine.state.epoch - if epoch < self._warmup_epochs: - epoch += float(engine.state.iteration + 1) / len(self._data_loader) - lr_adj = 1.0 / self._cluster_size * (epoch * (self._cluster_size - 1) / self._warmup_epochs + 1) - elif epoch < 30: - lr_adj = 1.0 - elif epoch < 60: - lr_adj = 1e-1 - elif epoch < 80: - lr_adj = 1e-2 - else: - lr_adj = 1e-3 - for param_group in self._optimizer.param_groups: - param_group["lr"] = self._base_lr * self._cluster_size * self._batches_per_allreduce * lr_adj - self._logger.debug(f"Adjust learning rate {param_group['lr']}") diff --git a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py index a9ba5f4c..30cb5fc4 100644 --- a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py +++ b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py @@ -1,12 +1,14 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -from toolz import curry import torchvision +from tensorboardX import SummaryWriter import logging import logging.config +from toolz import curry -from tensorboardX import SummaryWriter +from cv_lib.segmentation.dutchf3.utils import np_to_tb +from deepseismic_interpretation.dutchf3.data import decode_segmap def create_summary_writer(log_dir): @@ -14,18 +16,29 @@ def create_summary_writer(log_dir): return writer +def _transform_image(output_tensor): + output_tensor = output_tensor.cpu() + return torchvision.utils.make_grid(output_tensor, normalize=True, scale_each=True) + + +def _transform_pred(output_tensor, n_classes): + output_tensor = output_tensor.squeeze().cpu().numpy() + decoded = decode_segmap(output_tensor, n_classes) + return torchvision.utils.make_grid(np_to_tb(decoded), normalize=False, scale_each=False) + + def _log_model_output(log_label, summary_writer, engine): summary_writer.add_scalar(log_label, engine.state.output["loss"], engine.state.iteration) @curry def log_training_output(summary_writer, engine): - _log_model_output("training/loss", summary_writer, engine) + _log_model_output("Training/loss", summary_writer, engine) @curry def log_validation_output(summary_writer, engine): - _log_model_output("validation/loss", summary_writer, engine) + _log_model_output("Validation/loss", summary_writer, engine) @curry @@ -42,31 +55,60 @@ def log_lr(summary_writer, optimizer, log_interval, engine): summary_writer.add_scalar("lr", lr[0], getattr(engine.state, log_interval)) -_DEFAULT_METRICS = {"accuracy": "Avg accuracy :", "nll": "Avg loss :"} - - +# TODO: This is deprecated, and will be removed in the future. @curry -def log_metrics(summary_writer, train_engine, log_interval, engine, metrics_dict=_DEFAULT_METRICS): +def log_metrics(summary_writer, train_engine, log_interval, engine, metrics_dict={"pixacc": "Avg accuracy :", "nll": "Avg loss :"}): metrics = engine.state.metrics for m in metrics_dict: - summary_writer.add_scalar( - metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval) - ) + summary_writer.add_scalar(metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval)) -def create_image_writer( - summary_writer, label, output_variable, normalize=False, transform_func=lambda x: x -): +# TODO: This is deprecated, and will be removed in the future. +def create_image_writer(summary_writer, label, output_variable, normalize=False, transform_func=lambda x: x): logger = logging.getLogger(__name__) + logger.warning( + "create_image_writer() in tensorboard_handlers.py is deprecated, and will be removed in a future update." + ) def write_to(engine): try: data_tensor = transform_func(engine.state.output[output_variable]) - image_grid = torchvision.utils.make_grid( - data_tensor, normalize=normalize, scale_each=True - ) + image_grid = torchvision.utils.make_grid(data_tensor, normalize=normalize, scale_each=True) summary_writer.add_image(label, image_grid, engine.state.epoch) except KeyError: logger.warning("Predictions and or ground truth labels not available to report") return write_to + + +def log_results(engine, evaluator, summary_writer, n_classes, stage): + epoch = engine.state.epoch + metrics = evaluator.state.metrics + outputs = evaluator.state.output + + # Log Metrics: + summary_writer.add_scalar(f"{stage}/mIoU", metrics["mIoU"], epoch) + summary_writer.add_scalar(f"{stage}/nll", metrics["nll"], epoch) + summary_writer.add_scalar(f"{stage}/mca", metrics["mca"], epoch) + summary_writer.add_scalar(f"{stage}/pixacc", metrics["pixacc"], epoch) + + for i in range(n_classes): + summary_writer.add_scalar(f"{stage}/IoU_class_" + str(i), metrics["ciou"][i], epoch) + + # Log Images: + image = outputs["image"] + mask = outputs["mask"] + y_pred = outputs["y_pred"].max(1, keepdim=True)[1] + VISUALIZATION_LIMIT = 8 + + if evaluator.state.batch[0].shape[0] > VISUALIZATION_LIMIT: + image = image[:VISUALIZATION_LIMIT] + mask = mask[:VISUALIZATION_LIMIT] + y_pred = y_pred[:VISUALIZATION_LIMIT] + + # Mask out the region in y_pred where padding exists in the mask: + y_pred[mask == 255] = 255 + + summary_writer.add_image(f"{stage}/Image", _transform_image(image), epoch) + summary_writer.add_image(f"{stage}/Mask", _transform_pred(mask, n_classes), epoch) + summary_writer.add_image(f"{stage}/Pred", _transform_pred(y_pred, n_classes), epoch) diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index de0d65af..40b28a34 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -11,7 +11,6 @@ dependencies: - ipykernel - torchvision>=0.5.0 - pandas==0.25.3 - - opencv==4.1.2 - scikit-learn==0.21.3 - tensorflow==2.0 - opt-einsum>=2.3.2 diff --git a/environment/docker/apex/dockerfile b/environment/docker/apex/dockerfile index 3becd3c4..9dcf5615 100644 --- a/environment/docker/apex/dockerfile +++ b/environment/docker/apex/dockerfile @@ -10,7 +10,7 @@ RUN git clone https://github.com/NVIDIA/apex && \ cd apex && \ pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./ -RUN pip install toolz pytorch-ignite torchvision pandas opencv-python fire tensorboardx scikit-learn yacs +RUN pip install toolz pytorch-ignite torchvision pandas fire tensorboardx scikit-learn yacs WORKDIR /workspace CMD /bin/bash \ No newline at end of file diff --git a/environment/docker/horovod/dockerfile b/environment/docker/horovod/dockerfile index 0e12f455..04ed2f67 100644 --- a/environment/docker/horovod/dockerfile +++ b/environment/docker/horovod/dockerfile @@ -60,7 +60,7 @@ RUN pip install future typing RUN pip install numpy RUN pip install https://download.pytorch.org/whl/cu100/torch-${PYTORCH_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl \ https://download.pytorch.org/whl/cu100/torchvision-${TORCHVISION_VERSION}-$(python -c "import wheel.pep425tags as w; print('-'.join(w.get_supported()[0]))").whl -RUN pip install --no-cache-dir torchvision h5py toolz pytorch-ignite pandas opencv-python fire tensorboardx scikit-learn tqdm yacs albumentations gitpython +RUN pip install --no-cache-dir torchvision h5py toolz pytorch-ignite pandas fire tensorboardx scikit-learn tqdm yacs albumentations gitpython COPY ComputerVision_fork/contrib /contrib RUN pip install -e /contrib COPY DeepSeismic /DeepSeismic diff --git a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb index de4e8542..6f830701 100644 --- a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb +++ b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb @@ -473,7 +473,7 @@ " PadIfNeeded(\n", " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", - " border_mode=cv2.BORDER_CONSTANT,\n", + " border_mode=config.OPENCV_BORDER_CONSTANT,\n", " always_apply=True,\n", " mask_value=255,\n", " ),\n", @@ -534,7 +534,6 @@ " augmentations=val_aug,\n", ")\n", "\n", - "# TODO: workaround for Ignite 0.3.0 bug as epoch_lengh in trainer.run method below doesn't apply to validation set\n", "if papermill:\n", " val_set = data.Subset(val_set, range(3))\n", "elif DEMO:\n", @@ -578,7 +577,7 @@ "else:\n", " train_len = len(train_loader)\n", "\n", - "snapshot_duration = scheduler_step * train_len" + "snapshot_duration = scheduler_step * train_len if not papermill else 2*len(train_loader)" ] }, { @@ -643,7 +642,7 @@ "\n", "# learning rate scheduler\n", "scheduler = CosineAnnealingScheduler(\n", - " optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration\n", + " optimizer, \"lr\", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, cycle_size=snapshot_duration\n", ")\n", "\n", "# weights are inversely proportional to the frequency of the classes in the training set\n", @@ -984,7 +983,7 @@ " PadIfNeeded(\n", " min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT,\n", " min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH,\n", - " border_mode=cv2.BORDER_CONSTANT,\n", + " border_mode=config.OPENCV_BORDER_CONSTANT,\n", " always_apply=True,\n", " mask_value=255,\n", " ),\n", diff --git a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml index 9d705df2..52263bbf 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/hrnet.yaml @@ -9,6 +9,7 @@ WORKERS: 4 PRINT_FREQ: 10 LOG_CONFIG: logging.conf SEED: 2019 +OPENCV_BORDER_CONSTANT: 0 DATASET: @@ -73,7 +74,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "section" #"patch" # Options are No, Patch and Section + DEPTH: "section" # Options are: none, patch, and section STRIDE: 50 PATCH_SIZE: 100 AUGMENTATIONS: @@ -82,7 +83,7 @@ TRAIN: WIDTH: 200 PAD: HEIGHT: 256 - WIDTH: 256 + WIDTH: 256 MEAN: 0.0009997 # 0.0009996710808862074 STD: 0.20977 # 0.20976548783479299 MODEL_DIR: "models" @@ -91,12 +92,12 @@ TRAIN: VALIDATION: BATCH_SIZE_PER_GPU: 128 -TEST: +TEST: MODEL_PATH: "/data/home/mat/repos/DeepSeismic/experiments/interpretation/dutchf3_patch/local/output/staging/0d1d2bbf9685995a0515ca1d9de90f9bcec0db90/seg_hrnet/Dec20_233535/models/seg_hrnet_running_model_33.pth" TEST_STRIDE: 10 SPLIT: 'Both' # Can be Both, Test1, Test2 INLINE: True CROSSLINE: True - POST_PROCESSING: - SIZE: 128 # + POST_PROCESSING: + SIZE: 128 # CROP_PIXELS: 14 # Number of pixels to crop top, bottom, left and right diff --git a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml index 7c695f96..35787b95 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet.yaml @@ -29,7 +29,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" # Options are None, Patch and Section + DEPTH: "none" # Options are none, patch, and section STRIDE: 50 PATCH_SIZE: 99 AUGMENTATIONS: diff --git a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml index d14ea134..46fab9f6 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/patch_deconvnet_skip.yaml @@ -29,7 +29,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "none" #"patch" # Options are None, Patch and Section + DEPTH: "none" #"patch" # Options are none, patch, and section STRIDE: 50 PATCH_SIZE: 99 AUGMENTATIONS: diff --git a/experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml index d0b8126f..9bc10d34 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/seresnet_unet.yaml @@ -30,7 +30,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "section" # Options are No, Patch and Section + DEPTH: "section" # Options are none, patch, and section STRIDE: 50 PATCH_SIZE: 100 AUGMENTATIONS: diff --git a/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml b/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml index c31157bf..3a8ee71a 100644 --- a/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml +++ b/experiments/interpretation/dutchf3_patch/local/configs/unet.yaml @@ -33,7 +33,7 @@ TRAIN: WEIGHT_DECAY: 0.0001 SNAPSHOTS: 5 AUGMENTATION: True - DEPTH: "section" # Options are No, Patch and Section + DEPTH: "section" # Options are none, patch, and section STRIDE: 50 PATCH_SIZE: 100 AUGMENTATIONS: diff --git a/experiments/interpretation/dutchf3_patch/local/default.py b/experiments/interpretation/dutchf3_patch/local/default.py index aac539ea..f2cadfc1 100644 --- a/experiments/interpretation/dutchf3_patch/local/default.py +++ b/experiments/interpretation/dutchf3_patch/local/default.py @@ -20,7 +20,7 @@ _C.PIN_MEMORY = True _C.LOG_CONFIG = "logging.conf" _C.SEED = 42 - +_C.OPENCV_BORDER_CONSTANT = 0 # Cudnn related params _C.CUDNN = CN() @@ -58,8 +58,8 @@ _C.TRAIN.PATCH_SIZE = 99 _C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 _C.TRAIN.STD = 0.20977 # 0.20976548783479299 # TODO: Should we apply std scaling? -# issue: https://github.com/microsoft/seismic-deeplearning/issues/269 -_C.TRAIN.DEPTH = "no" # Options are None, Patch and Section +_C.TRAIN.DEPTH = "none" # Options are: none, patch, and section + # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index ee4c0b09..631fd4f4 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -18,34 +18,22 @@ import os from os import path -import cv2 import fire import numpy as np import torch import torch.nn.functional as F -from PIL import Image from albumentations import Compose, Normalize, PadIfNeeded, Resize -from cv_lib.utils import load_log_configuration +from matplotlib import cm +from PIL import Image +from toolz import compose, curry, itertoolz, pipe, take +from torch.utils import data + from cv_lib.segmentation import models -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, -) -from deepseismic_interpretation.dutchf3.data import ( - add_patch_depth_channels, - get_seismic_labels, - get_test_loader, -) +from cv_lib.segmentation.dutchf3.utils import current_datetime, generate_path, git_branch, git_hash +from cv_lib.utils import load_log_configuration +from deepseismic_interpretation.dutchf3.data import add_patch_depth_channels, get_seismic_labels, get_test_loader from default import _C as config from default import update_config -from toolz import compose, curry, itertoolz, pipe -from torch.utils import data -from toolz import take - -from matplotlib import cm - _CLASS_NAMES = [ "upper_ns", @@ -63,9 +51,9 @@ def __init__(self, n_classes): def _fast_hist(self, label_true, label_pred, n_class): mask = (label_true >= 0) & (label_true < n_class) - hist = np.bincount( - n_class * label_true[mask].astype(int) + label_pred[mask], minlength=n_class ** 2, - ).reshape(n_class, n_class) + hist = np.bincount(n_class * label_true[mask].astype(int) + label_pred[mask], minlength=n_class ** 2,).reshape( + n_class, n_class + ) return hist def update(self, label_trues, label_preds): @@ -201,9 +189,7 @@ def _compose_processing_pipeline(depth, aug=None): def _generate_batches(h, w, ps, patch_size, stride, batch_size=64): - hdc_wdx_generator = itertools.product( - range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride), - ) + hdc_wdx_generator = itertools.product(range(0, h - patch_size + ps, stride), range(0, w - patch_size + ps, stride),) for batch_indexes in itertoolz.partition_all(batch_size, hdc_wdx_generator): yield batch_indexes @@ -214,9 +200,7 @@ def _output_processing_pipeline(config, output): _, _, h, w = output.shape if config.TEST.POST_PROCESSING.SIZE != h or config.TEST.POST_PROCESSING.SIZE != w: output = F.interpolate( - output, - size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE,), - mode="bilinear", + output, size=(config.TEST.POST_PROCESSING.SIZE, config.TEST.POST_PROCESSING.SIZE,), mode="bilinear", ) if config.TEST.POST_PROCESSING.CROP_PIXELS > 0: @@ -231,15 +215,7 @@ def _output_processing_pipeline(config, output): def _patch_label_2d( - model, - img, - pre_processing, - output_processing, - patch_size, - stride, - batch_size, - device, - num_classes, + model, img, pre_processing, output_processing, patch_size, stride, batch_size, device, num_classes, ): """Processes a whole section """ @@ -254,19 +230,14 @@ def _patch_label_2d( # generate output: for batch_indexes in _generate_batches(h, w, ps, patch_size, stride, batch_size=batch_size): batch = torch.stack( - [ - pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing,) - for hdx, wdx in batch_indexes - ], + [pipe(img_p, _extract_patch(hdx, wdx, ps, patch_size), pre_processing,) for hdx, wdx in batch_indexes], dim=0, ) model_output = model(batch.to(device)) for (hdx, wdx), output in zip(batch_indexes, model_output.detach().cpu()): output = output_processing(output) - output_p[ - :, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size, - ] += output + output_p[:, :, hdx + ps : hdx + ps + patch_size, wdx + ps : wdx + ps + patch_size,] += output # crop the output_p in the middle output = output_p[:, :, ps:-ps, ps:-ps] @@ -291,22 +262,12 @@ def to_image(label_mask, n_classes=6): def _evaluate_split( - split, - section_aug, - model, - pre_processing, - output_processing, - device, - running_metrics_overall, - config, - debug=False, + split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, debug=False, ): logger = logging.getLogger(__name__) TestSectionLoader = get_test_loader(config) - test_set = TestSectionLoader( - config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug, - ) + test_set = TestSectionLoader(config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug,) n_classes = test_set.n_classes @@ -318,16 +279,10 @@ def _evaluate_split( try: output_dir = generate_path( - config.OUTPUT_DIR + "_test", - git_branch(), - git_hash(), - config.MODEL.NAME, - current_datetime(), + config.OUTPUT_DIR + "_test", git_branch(), git_hash(), config.MODEL.NAME, current_datetime(), ) except TypeError: - output_dir = generate_path( - config.OUTPUT_DIR + "_test", config.MODEL.NAME, current_datetime(), - ) + output_dir = generate_path(config.OUTPUT_DIR + "_test", config.MODEL.NAME, current_datetime(),) running_metrics_split = runningScore(n_classes) @@ -415,23 +370,19 @@ def test(*options, cfg=None, debug=False): running_metrics_overall = runningScore(n_classes) # Augmentation - section_aug = Compose( - [Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1,)] - ) + section_aug = Compose([Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1,)]) # TODO: make sure that this is consistent with how normalization and agumentation for train.py # issue: https://github.com/microsoft/seismic-deeplearning/issues/270 patch_aug = Compose( [ Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, + border_mode=config.OPENCV_BORDER_CONSTANT, always_apply=True, mask_value=255, ), diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index 9c77d713..14dcf726 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -17,52 +17,24 @@ import logging.config from os import path -import cv2 import fire import numpy as np import torch +from torch.utils import data from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize from ignite.contrib.handlers import CosineAnnealingScheduler from ignite.engine import Events from ignite.metrics import Loss from ignite.utils import convert_tensor -from toolz import compose -from torch.utils import data -from deepseismic_interpretation.dutchf3.data import get_patch_loader, decode_segmap +from cv_lib.event_handlers import SnapshotHandler, logging_handlers, tensorboard_handlers +from cv_lib.event_handlers.tensorboard_handlers import create_summary_writer, log_results +from cv_lib.segmentation import extract_metric_from, models +from cv_lib.segmentation.dutchf3.engine import create_supervised_evaluator, create_supervised_trainer +from cv_lib.segmentation.dutchf3.utils import current_datetime, generate_path, git_branch, git_hash +from cv_lib.segmentation.metrics import class_accuracy, class_iou, mean_class_accuracy, mean_iou, pixelwise_accuracy from cv_lib.utils import load_log_configuration -from cv_lib.event_handlers import ( - SnapshotHandler, - logging_handlers, - tensorboard_handlers, -) -from cv_lib.event_handlers.logging_handlers import Evaluator -from cv_lib.event_handlers.tensorboard_handlers import ( - create_image_writer, - create_summary_writer, -) -from cv_lib.segmentation import models, extract_metric_from -from cv_lib.segmentation.dutchf3.engine import ( - create_supervised_evaluator, - create_supervised_trainer, -) - -from cv_lib.segmentation.metrics import ( - pixelwise_accuracy, - class_accuracy, - mean_class_accuracy, - class_iou, - mean_iou, -) - -from cv_lib.segmentation.dutchf3.utils import ( - current_datetime, - generate_path, - git_branch, - git_hash, - np_to_tb, -) - +from deepseismic_interpretation.dutchf3.data import get_patch_loader from default import _C as config from default import update_config @@ -90,44 +62,50 @@ def run(*options, cfg=None, debug=False): cfg (str, optional): Location of config file to load. Defaults to None. debug (bool): Places scripts in debug/test mode and only executes a few iterations """ - + # Configuration: update_config(config, options=options, config_file=cfg) - - # we will write the model under outputs / config_file_name / model_dir + # The model will be saved under: outputs// config_file_name = "default_config" if not cfg else cfg.split("/")[-1].split(".")[0] + try: + output_dir = generate_path( + config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(), + ) + except TypeError: + output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) - # Start logging + # Logging: load_log_configuration(config.LOG_CONFIG) logger = logging.getLogger(__name__) logger.debug(config.WORKERS) + + # Set CUDNN benchmark mode: torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + # Fix random seeds: torch.manual_seed(config.SEED) if torch.cuda.is_available(): torch.cuda.manual_seed_all(config.SEED) np.random.seed(seed=config.SEED) - # Setup Augmentations + # Augmentation: basic_aug = Compose( [ Normalize(mean=(config.TRAIN.MEAN,), std=(config.TRAIN.STD,), max_pixel_value=1), PadIfNeeded( min_height=config.TRAIN.PATCH_SIZE, min_width=config.TRAIN.PATCH_SIZE, - border_mode=0, + border_mode=config.OPENCV_BORDER_CONSTANT, always_apply=True, mask_value=255, value=0, ), Resize( - config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, - config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, - always_apply=True, + config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True, ), PadIfNeeded( min_height=config.TRAIN.AUGMENTATIONS.PAD.HEIGHT, min_width=config.TRAIN.AUGMENTATIONS.PAD.WIDTH, - border_mode=cv2.BORDER_CONSTANT, + border_mode=config.OPENCV_BORDER_CONSTANT, always_apply=True, mask_value=255, ), @@ -139,8 +117,8 @@ def run(*options, cfg=None, debug=False): else: train_aug = val_aug = basic_aug + # Training and Validation Loaders: TrainPatchLoader = get_patch_loader(config) - train_set = TrainPatchLoader( config.DATASET.ROOT, split="train", @@ -150,6 +128,7 @@ def run(*options, cfg=None, debug=False): augmentations=train_aug, ) logger.info(train_set) + n_classes = train_set.n_classes val_set = TrainPatchLoader( config.DATASET.ROOT, split="val", @@ -160,27 +139,22 @@ def run(*options, cfg=None, debug=False): ) logger.info(val_set) - train_loader = data.DataLoader( - train_set, - batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, - num_workers=config.WORKERS, - shuffle=True, - ) - if debug: - val_set = data.Subset(val_set, range(3)) + logger.info("Running in debug mode..") + train_set = data.Subset(train_set, list(range(4))) + val_set = data.Subset(val_set, list(range(4))) - val_loader = data.DataLoader( - val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, + train_loader = data.DataLoader( + train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True ) + val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS) + # Model: model = getattr(models, config.MODEL.NAME).get_seg_model(config) + device = "cuda" if torch.cuda.is_available() else "cpu" + model = model.to(device) - device = "cpu" - if torch.cuda.is_available(): - device = "cuda" - model = model.to(device) # Send to GPU - + # Optimizer and LR Scheduler: optimizer = torch.optim.SGD( model.parameters(), lr=config.TRAIN.MAX_LR, @@ -188,92 +162,44 @@ def run(*options, cfg=None, debug=False): weight_decay=config.TRAIN.WEIGHT_DECAY, ) - # learning rate scheduler - scheduler_step = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - snapshot_duration = scheduler_step * len(train_loader) + epochs_per_cycle = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS + snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2*len(train_loader) scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, snapshot_duration + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, cycle_size=snapshot_duration ) - # weights are inversely proportional to the frequency of the classes in the - # training set + # Tensorboard writer: + summary_writer = create_summary_writer(log_dir=path.join(output_dir, "logs")) + + # class weights are inversely proportional to the frequency of the classes in the training set class_weights = torch.tensor(config.DATASET.CLASS_WEIGHTS, device=device, requires_grad=False) + # Loss: criterion = torch.nn.CrossEntropyLoss(weight=class_weights, ignore_index=255, reduction="mean") + # Ignite trainer and evaluator: trainer = create_supervised_trainer(model, optimizer, criterion, prepare_batch, device=device) - - trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) - - ######################### - # Logging setup below - - try: - output_dir = generate_path( - config.OUTPUT_DIR, - git_branch(), - git_hash(), - config_file_name, - config.TRAIN.MODEL_DIR, - current_datetime(), - ) - except TypeError: - output_dir = generate_path( - config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(), - ) - - summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) - - # log all training output - trainer.add_event_handler( - Events.ITERATION_COMPLETED, - logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU), - ) - - # add logging of learning rate - trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer)) - - # log LR to tensorboard - trainer.add_event_handler( - Events.EPOCH_STARTED, tensorboard_handlers.log_lr(summary_writer, optimizer, "epoch"), - ) - - # log training summary to tensorboard as well - trainer.add_event_handler( - Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer), - ) - - def _select_pred_and_mask(model_out_dict): - return (model_out_dict["y_pred"].squeeze(), model_out_dict["mask"].squeeze()) - - def _select_max(pred_tensor): - return pred_tensor.max(1)[1] - - def _tensor_to_numpy(pred_tensor): - return pred_tensor.squeeze().cpu().numpy() - - def snapshot_function(): - return (trainer.state.iteration % snapshot_duration) == 0 - - n_classes = train_set.n_classes - + transform_fn = lambda output_dict: (output_dict["y_pred"].squeeze(), output_dict["mask"].squeeze()) evaluator = create_supervised_evaluator( model, prepare_batch, metrics={ - "nll": Loss(criterion, output_transform=_select_pred_and_mask), - "pixacc": pixelwise_accuracy( - n_classes, output_transform=_select_pred_and_mask, device=device - ), - "cacc": class_accuracy(n_classes, output_transform=_select_pred_and_mask), - "mca": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask), - "ciou": class_iou(n_classes, output_transform=_select_pred_and_mask), - "mIoU": mean_iou(n_classes, output_transform=_select_pred_and_mask), + "nll": Loss(criterion, output_transform=transform_fn), + "pixacc": pixelwise_accuracy(n_classes, output_transform=transform_fn, device=device), + "cacc": class_accuracy(n_classes, output_transform=transform_fn), + "mca": mean_class_accuracy(n_classes, output_transform=transform_fn), + "ciou": class_iou(n_classes, output_transform=transform_fn), + "mIoU": mean_iou(n_classes, output_transform=transform_fn), }, device=device, ) - - trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader)) + trainer.add_event_handler(Events.ITERATION_STARTED, scheduler) + + # Logging: + trainer.add_event_handler( + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU), + ) + trainer.add_event_handler(Events.EPOCH_COMPLETED, logging_handlers.log_lr(optimizer)) evaluator.add_event_handler( Events.EPOCH_COMPLETED, @@ -288,51 +214,40 @@ def snapshot_function(): ), ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - tensorboard_handlers.log_metrics( - summary_writer, - trainer, - "epoch", - metrics_dict={ - "mIoU": "Validation/mIoU", - "nll": "Validation/Loss", - "mca": "Validation/MCA", - "pixacc": "Validation/Pixel_Acc", - }, - ), - ) - - transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy) + # Tensorboard and Logging: + trainer.add_event_handler(Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer)) + trainer.add_event_handler(Events.ITERATION_COMPLETED, tensorboard_handlers.log_validation_output(summary_writer)) - transform_pred = compose(transform_func, _select_max) + @trainer.on(Events.EPOCH_COMPLETED) + def log_training_results(engine): + evaluator.run(train_loader) + log_results(engine, evaluator, summary_writer, n_classes, stage="Training") - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, create_image_writer(summary_writer, "Validation/Image", "image"), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Mask", "mask", transform_func=transform_func - ), - ) - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - create_image_writer( - summary_writer, "Validation/Pred", "y_pred", transform_func=transform_pred - ), - ) + @trainer.on(Events.EPOCH_COMPLETED) + def log_validation_results(engine): + evaluator.run(val_loader) + log_results(engine, evaluator, summary_writer, n_classes, stage="Validation") + # Checkpointing: checkpoint_handler = SnapshotHandler( - output_dir, config.MODEL.NAME, extract_metric_from("mIoU"), snapshot_function, + output_dir, + config.MODEL.NAME, + extract_metric_from("mIoU"), + lambda: (trainer.state.iteration % snapshot_duration) == 0, ) evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) - logger.info("Starting training") + logger.info("Starting training") if debug: - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = config.TRAIN.BATCH_SIZE_PER_GPU, seed = config.SEED) + trainer.run( + train_loader, + max_epochs=config.TRAIN.END_EPOCH, + epoch_length=config.TRAIN.BATCH_SIZE_PER_GPU, + seed=config.SEED, + ) else: - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = len(train_loader), seed = config.SEED) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length=len(train_loader), seed=config.SEED) + summary_writer.close() if __name__ == "__main__": diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index e11dd059..4517f510 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -579,12 +579,14 @@ def __getitem__(self, index): patch_name = self.patches[index] direction, idx, xdx, ddx = patch_name.split(sep="_") + # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 # TODO: Remember we are cancelling the shift since we no longer pad # issue: https://github.com/microsoft/seismic-deeplearning/issues/273 shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift + if direction == "i": im = self.seismic[idx, :, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size] lbl = self.labels[idx, xdx : xdx + self.patch_size, ddx : ddx + self.patch_size] @@ -604,11 +606,11 @@ def __getitem__(self, index): if self.is_transform: im, lbl = self.transform(im, lbl) return im, lbl - + def __repr__(self): unique, counts = np.unique(self.labels, return_counts=True) - ratio = counts/np.sum(counts) - return "\n".join(f"{lbl}: {cnt} [{rat}]"for lbl, cnt, rat in zip(unique, counts, ratio)) + ratio = counts / np.sum(counts) + return "\n".join(f"{lbl}: {cnt} [{rat}]" for lbl, cnt, rat in zip(unique, counts, ratio)) _TRAIN_PATCH_LOADERS = { @@ -619,7 +621,7 @@ def __repr__(self): _TRAIN_SECTION_LOADERS = {"section": TrainSectionLoaderWithDepth} def get_patch_loader(cfg): - assert cfg.TRAIN.DEPTH in [ + assert str(cfg.TRAIN.DEPTH).lower() in [ "section", "patch", "none", @@ -629,7 +631,7 @@ def get_patch_loader(cfg): def get_section_loader(cfg): - assert cfg.TRAIN.DEPTH in [ + assert str(cfg.TRAIN.DEPTH).lower() in [ "section", "none", ], f"Depth {cfg.TRAIN.DEPTH} not supported for section data. \ @@ -693,7 +695,7 @@ def get_seismic_labels(): @curry -def decode_segmap(label_mask, n_classes=6, label_colours=get_seismic_labels()): +def decode_segmap(label_mask, n_classes, label_colours=get_seismic_labels()): """Decode segmentation class labels into a colour image Args: label_mask (np.ndarray): an (N,H,W) array of integer values denoting diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 3a343233..bfc9b026 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -159,35 +159,42 @@ jobs: pids= export CUDA_VISIBLE_DEVICES=0 # find the latest model which we just trained - model=$(ls -td output/patch_deconvnet/no_depth/* | head -1) + model_dir=$(ls -td output/patch_deconvnet/no_depth/* | head -1) + model=$(ls -t ${model_dir}/*.pth | head -1) # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_0.*.pth \ + 'TEST.MODEL_PATH' ${model} \ --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & pids+=" $!" export CUDA_VISIBLE_DEVICES=1 # find the latest model which we just trained - model=$(ls -td output/unet/section_depth/* | head -1) + model_dir=$(ls -td output/unet/section_depth/* | head -1) + model=$(ls -t ${model_dir}/*.pth | head -1) + # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_0.*.pth \ + 'TEST.MODEL_PATH' ${model} \ --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & pids+=" $!" export CUDA_VISIBLE_DEVICES=2 # find the latest model which we just trained - model=$(ls -td output/seresnet_unet/section_depth/* | head -1) + model_dir=$(ls -td output/seresnet_unet/section_depth/* | head -1) + model=$(ls -t ${model_dir}/*.pth | head -1) + # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_0.*.pth \ + 'TEST.MODEL_PATH' ${model} \ --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & pids+=" $!" export CUDA_VISIBLE_DEVICES=3 # find the latest model which we just trained - model=$(ls -td output/hrnet/section_depth/* | head -1) + model_dir=$(ls -td output/hrnet/section_depth/* | head -1) + model=$(ls -t ${model_dir}/*.pth | head -1) + # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ - 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_0.*.pth \ + 'TEST.MODEL_PATH' ${model} \ --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & pids+=" $!" @@ -204,4 +211,4 @@ jobs: # Remove the temporary directory rm -r "$dir" - echo "PASSED" + echo "PASSED" \ No newline at end of file From be94d9aca61a014dd936c91cc42ec0c21e4f98be Mon Sep 17 00:00:00 2001 From: yalaudah Date: Tue, 28 Apr 2020 15:18:15 +0000 Subject: [PATCH 200/207] Fixes training/validation overlap #143, #233, #253, and #259 (#282) --- NOTICE.txt | 2 +- README.md | 48 +- cgmanifest.json | 2 +- .../dutchf3_patch/distributed/train.py | 15 +- .../dutchf3_section/local/test.py | 2 +- .../dutchf3_section/local/train.py | 15 +- .../interpretation/penobscot/local/test.py | 4 +- .../interpretation/penobscot/local/train.py | 6 +- contrib/scripts/ablation.sh | 8 +- .../event_handlers/tensorboard_handlers.py | 4 +- docker/Dockerfile | 4 +- ..._patch_model_training_and_evaluation.ipynb | 2 +- .../dutchf3_patch/local/default.py | 1 - .../dutchf3_patch/local/test.py | 3 +- .../dutchf3_patch/local/train.py | 2 +- .../dutchf3/data.py | 139 ++++-- scripts/prepare_dutchf3.py | 412 +++++++++--------- scripts/prepare_penobscot.py | 2 +- tests/cicd/src/scripts/get_data_for_builds.sh | 4 +- tests/test_prepare_dutchf3.py | 385 ++++++++++------ 20 files changed, 620 insertions(+), 440 deletions(-) diff --git a/NOTICE.txt b/NOTICE.txt index 6dc34351..fe7884f1 100755 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1949,7 +1949,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------- -olivesgatech/facies_classification_benchmark 12102683a1ae78f8fbc953823c35a43b151194b3 - MIT +yalaudah/facies_classification_benchmark 12102683a1ae78f8fbc953823c35a43b151194b3 - MIT Copyright (c) 2017 Meet Pragnesh Shah Copyright (c) 2010-2018 Benjamin Peterson diff --git a/README.md b/README.md index 44d60b5a..a4559c20 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ from the root of DeepSeismic repo. ### Dataset download and preparation -This repository provides examples on how to run seismic interpretation on two publicly available annotated seismic datasets: [Penobscot](https://zenodo.org/record/1341774) and [F3 Netherlands](https://github.com/olivesgatech/facies_classification_benchmark). Their respective sizes (uncompressed on disk in your folder after downloading and pre-processing) are: +This repository provides examples on how to run seismic interpretation on two publicly available annotated seismic datasets: [Penobscot](https://zenodo.org/record/1341774) and [F3 Netherlands](https://github.com/yalaudah/facies_classification_benchmark). Their respective sizes (uncompressed on disk in your folder after downloading and pre-processing) are: - **Penobscot**: 7.9 GB - **Dutch F3**: 2.2 GB @@ -142,12 +142,12 @@ To prepare the data for the experiments (e.g. split into train/val/test), please cd scripts # For section-based experiments -python prepare_dutchf3.py split_train_val section --data_dir=${data_dir} --label_file=train/train_labels.npy --output_dir=splits +python prepare_dutchf3.py split_train_val section --data_dir=${data_dir} --label_file=train/train_labels.npy --output_dir=splits --split_direction=both # For patch-based experiments python prepare_dutchf3.py split_train_val patch --data_dir=${data_dir} --label_file=train/train_labels.npy --output_dir=splits \ ---stride=50 --patch_size=100 +--stride=50 --patch_size=100 --split_direction=both # go back to repo root cd .. @@ -244,24 +244,24 @@ Below are the results from the models contained in this repo. To run them check #### Netherlands F3 -| Source | Experiment | PA | FW IoU | MCA | V100 (16GB) training time | -|------------------|-----------------------------------|-------------|--------------|------------|-----------------------------| -| Alaudah et al.| Section-based | 0.905 | 0.817 | .832 | N/A | -| | Patch-based | 0.852 | 0.743 | .689 | N/A | -| DeepSeismic | Patch-based+fixed | .875 | .784 | .740 | 08h 54min | -| | SEResNet UNet+section depth | .910 | .841 | .809 | 55h 02min | -| | HRNet(patch)+patch_depth | .884 | .795 | .739 | 67h 41min | -| | HRNet(patch)+section_depth | .900 | .820 | .767 | 55h 08min | +| Source | Experiment | PA | FW IoU | MCA | V100 (16GB) training time | +| -------------- | --------------------------- | ----- | ------ | ---- | ------------------------- | +| Alaudah et al. | Section-based | 0.905 | 0.817 | .832 | N/A | +| | Patch-based | 0.852 | 0.743 | .689 | N/A | +| DeepSeismic | Patch-based+fixed | .875 | .784 | .740 | 08h 54min | +| | SEResNet UNet+section depth | .910 | .841 | .809 | 55h 02min | +| | HRNet(patch)+patch_depth | .884 | .795 | .739 | 67h 41min | +| | HRNet(patch)+section_depth | .900 | .820 | .767 | 55h 08min | #### Penobscot Trained and tested on the full dataset. Inlines with artifacts were left in for training, validation and testing. The dataset was split 70% training, 10% validation and 20% test. The results below are from the test set -| Source | Experiment | PA | mIoU | MCA | V100 (16GB) training time | -|------------------|-------------------------------------|-------------|--------------|------------|-----------------------------| -| DeepSeismic | SEResNet UNet + section depth | 0.72 | .35 | .47 | 92h 59min | -| | HRNet(patch) + section depth | 0.91 | .75 | .85 | 80h 50min | +| Source | Experiment | PA | mIoU | MCA | V100 (16GB) training time | +| ----------- | ----------------------------- | ---- | ---- | --- | ------------------------- | +| DeepSeismic | SEResNet UNet + section depth | 0.72 | .35 | .47 | 92h 59min | +| | HRNet(patch) + section depth | 0.91 | .75 | .85 | 80h 50min | ![Best Penobscot SEResNet](assets/penobscot_seresnet_best.png "Best performing inlines, Mask and Predictions from SEResNet") ![Worst Penobscot SEResNet](assets/penobscot_seresnet_worst.png "Worst performing inlines Mask and Predictions from SEResNet") @@ -298,16 +298,16 @@ When you submit a pull request, a CLA bot will automatically determine whether y This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## Build Status -| Build | Branch | Status | -| --- | --- | --- | +| Build | Branch | Status | +| -------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Legal Compliance** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=124&branchName=staging) | -| **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=124&branchName=master) | -| **Core Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=staging) | -| **Core Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=master) | -| **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=staging) | -| **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=master) | -| **Azure ML Tests** | staging | TODO add badge link | -| **Azure ML Tests** | master | TODO add badge link | +| **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=124&branchName=master) | +| **Core Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=staging) | +| **Core Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=master) | +| **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=staging) | +| **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=master) | +| **Azure ML Tests** | staging | TODO add badge link | +| **Azure ML Tests** | master | TODO add badge link | # Troubleshooting diff --git a/cgmanifest.json b/cgmanifest.json index d647c543..d83c6bfd 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -3,7 +3,7 @@ "component": { "type": "git", "git": { - "repositoryUrl": "https://github.com/olivesgatech/facies_classification_benchmark", + "repositoryUrl": "https://github.com/yalaudah/facies_classification_benchmark", "commitHash": "12102683a1ae78f8fbc953823c35a43b151194b3" } }, diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py index 33bb0045..e52970ed 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -139,7 +139,7 @@ def run(*options, cfg=None, local_rank=0, debug=False): stride=config.TRAIN.STRIDE, patch_size=config.TRAIN.PATCH_SIZE, augmentations=train_aug, - ) + ) val_set = TrainPatchLoader( config.DATASET.ROOT, @@ -160,10 +160,9 @@ def run(*options, cfg=None, local_rank=0, debug=False): logger.info("Running in debug mode..") train_set = data.Subset(train_set, list(range(4))) val_set = data.Subset(val_set, list(range(4))) - - logger.info(f"Training examples {len(train_set)}") - logger.info(f"Validation examples {len(val_set)}") + logger.info(f"Training examples {len(train_set)}") + logger.info(f"Validation examples {len(val_set)}") train_sampler = torch.utils.data.distributed.DistributedSampler(train_set, num_replicas=world_size, rank=local_rank) train_loader = data.DataLoader( @@ -198,7 +197,7 @@ def run(*options, cfg=None, local_rank=0, debug=False): model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[device], find_unused_parameters=True) - snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2*len(train_loader) + snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2 * len(train_loader) warmup_duration = 5 * len(train_loader) warmup_scheduler = LinearCyclicalScheduler( optimizer, @@ -208,7 +207,11 @@ def run(*options, cfg=None, local_rank=0, debug=False): cycle_size=10 * len(train_loader), ) cosine_scheduler = CosineAnnealingScheduler( - optimizer, "lr", config.TRAIN.MAX_LR * world_size, config.TRAIN.MIN_LR * world_size, cycle_size=snapshot_duration, + optimizer, + "lr", + config.TRAIN.MAX_LR * world_size, + config.TRAIN.MIN_LR * world_size, + cycle_size=snapshot_duration, ) scheduler = ConcatScheduler(schedulers=[warmup_scheduler, cosine_scheduler], durations=[warmup_duration]) diff --git a/contrib/experiments/interpretation/dutchf3_section/local/test.py b/contrib/experiments/interpretation/dutchf3_section/local/test.py index 384ac64b..3bc4cabb 100644 --- a/contrib/experiments/interpretation/dutchf3_section/local/test.py +++ b/contrib/experiments/interpretation/dutchf3_section/local/test.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 -# url: https://github.com/olivesgatech/facies_classification_benchmark +# url: https://github.com/yalaudah/facies_classification_benchmark """ Modified version of the Alaudah testing script diff --git a/contrib/experiments/interpretation/dutchf3_section/local/train.py b/contrib/experiments/interpretation/dutchf3_section/local/train.py index 5a9b4900..484bbb4f 100644 --- a/contrib/experiments/interpretation/dutchf3_section/local/train.py +++ b/contrib/experiments/interpretation/dutchf3_section/local/train.py @@ -164,8 +164,10 @@ def __len__(self): summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) - snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2*len(train_loader) - scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, cycle_size=snapshot_duration) + snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2 * len(train_loader) + scheduler = CosineAnnealingScheduler( + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, cycle_size=snapshot_duration + ) # weights are inversely proportional to the frequency of the classes in # the training set @@ -278,9 +280,14 @@ def snapshot_function(): logger.info("Starting training") if debug: - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = config.TRAIN.BATCH_SIZE_PER_GPU, seed = config.SEED) + trainer.run( + train_loader, + max_epochs=config.TRAIN.END_EPOCH, + epoch_length=config.TRAIN.BATCH_SIZE_PER_GPU, + seed=config.SEED, + ) else: - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length = len(train_loader), seed = config.SEED) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length=len(train_loader), seed=config.SEED) if __name__ == "__main__": diff --git a/contrib/experiments/interpretation/penobscot/local/test.py b/contrib/experiments/interpretation/penobscot/local/test.py index 073c72f1..0bf11667 100644 --- a/contrib/experiments/interpretation/penobscot/local/test.py +++ b/contrib/experiments/interpretation/penobscot/local/test.py @@ -268,9 +268,9 @@ def _tensor_to_numpy(pred_tensor): logger.info("Starting training") if debug: - evaluator.run(test_loader, max_epochs=1, epoch_length = 1) + evaluator.run(test_loader, max_epochs=1, epoch_length=1) else: - evaluator.run(test_loader, max_epochs=1, epoch_length = len(test_loader)) + evaluator.run(test_loader, max_epochs=1, epoch_length=len(test_loader)) # Log top N and bottom N inlines in terms of IoU to tensorboard inline_ious = inline_mean_iou.iou_per_inline() diff --git a/contrib/experiments/interpretation/penobscot/local/train.py b/contrib/experiments/interpretation/penobscot/local/train.py index 04f563b7..b48de2ad 100644 --- a/contrib/experiments/interpretation/penobscot/local/train.py +++ b/contrib/experiments/interpretation/penobscot/local/train.py @@ -178,8 +178,10 @@ def run(*options, cfg=None, debug=False): output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR)) - snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2*len(train_loader) - scheduler = CosineAnnealingScheduler(optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, cycle_size=snapshot_duration) + snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2 * len(train_loader) + scheduler = CosineAnnealingScheduler( + optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, cycle_size=snapshot_duration + ) # weights are inversely proportional to the frequency of the classes in # the training set diff --git a/contrib/scripts/ablation.sh b/contrib/scripts/ablation.sh index 6d5a0245..3e57a6e5 100755 --- a/contrib/scripts/ablation.sh +++ b/contrib/scripts/ablation.sh @@ -3,22 +3,22 @@ source activate seismic-interpretation # Patch_Size 100: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=100 +python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=100 --split_direction=both python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 100 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 100 --cfg 'configs/hrnet.yaml' # Patch_Size 150: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=150 +python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=150 --split_direction=both python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 150 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 150 --cfg 'configs/hrnet.yaml' # Patch_Size 200: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=200 +python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=200 --split_direction=both python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 200 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 200 --cfg 'configs/hrnet.yaml' # Patch_Size 250: Patch vs Section Depth -python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=250 +python scripts/prepare_dutchf3.py split_train_val patch --data_dir=/mnt/dutch --stride=50 --patch_size=250 --split_direction=both python train.py OUTPUT_DIR /data/output/hrnet_patch TRAIN.DEPTH patch TRAIN.PATCH_SIZE 250 TRAIN.AUGMENTATIONS.RESIZE.HEIGHT 250 TRAIN.AUGMENTATIONS.RESIZE.WIDTH 250 --cfg 'configs/hrnet.yaml' python train.py OUTPUT_DIR /data/output/hrnet_section TRAIN.DEPTH section TRAIN.PATCH_SIZE 250 TRAIN.AUGMENTATIONS.RESIZE.HEIGHT 250 TRAIN.AUGMENTATIONS.RESIZE.WIDTH 250 --cfg 'configs/hrnet.yaml' diff --git a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py index 30cb5fc4..625e11d1 100644 --- a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py +++ b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py @@ -57,7 +57,9 @@ def log_lr(summary_writer, optimizer, log_interval, engine): # TODO: This is deprecated, and will be removed in the future. @curry -def log_metrics(summary_writer, train_engine, log_interval, engine, metrics_dict={"pixacc": "Avg accuracy :", "nll": "Avg loss :"}): +def log_metrics( + summary_writer, train_engine, log_interval, engine, metrics_dict={"pixacc": "Avg accuracy :", "nll": "Avg loss :"} +): metrics = engine.state.metrics for m in metrics_dict: summary_writer.add_scalar(metrics_dict[m], metrics[m], getattr(train_engine.state, log_interval)) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9a7414c9..f81cf19f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -47,8 +47,8 @@ RUN data_dir="/home/username/data/dutch" && \ ./scripts/download_dutch_f3.sh "$data_dir" && \ cd scripts && \ source activate seismic-interpretation && \ - python prepare_dutchf3.py split_train_val section --data-dir=${data_dir}/data --label_file=train/train_labels.npy --output_dir=splits && \ - python prepare_dutchf3.py split_train_val patch --data-dir=${data_dir}/data --label_file=train/train_labels.npy --output_dir=splits --stride=50 --patch_size=100 && \ + python prepare_dutchf3.py split_train_val section --data-dir=${data_dir}/data --label_file=train/train_labels.npy --output_dir=splits --split_direction=both && \ + python prepare_dutchf3.py split_train_val patch --data-dir=${data_dir}/data --label_file=train/train_labels.npy --output_dir=splits --stride=50 --patch_size=100 --split_direction=both && \ cd .. # Run notebook diff --git a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb index 6f830701..c11014c5 100644 --- a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb +++ b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb @@ -22,7 +22,7 @@ "source": [ "Seismic interpretation, also referred to as facies classification, is a task of determining types of rock in the earth’s subsurface, given seismic data. Seismic interpretation is used as a standard approach for determining precise locations of oil deposits for drilling, therefore reducing risks and potential losses. In recent years, there has been a great interest in using fully-supervised deep learning models for seismic interpretation. \n", "\n", - "In this notebook, we demonstrate how to train a deep neural network for facies prediction using F3 Netherlands dataset. The F3 block is located in the North Sea off the shores of Netherlands. The dataset contains 6 classes (facies or lithostratigraphic units), all of which are of varying thickness (class imbalance). Processed data is available in numpy format as a `401 x 701 x 255` array. The processed F3 data is made available by [Alaudah et al. 2019](https://github.com/olivesgatech/facies_classification_benchmark).\n", + "In this notebook, we demonstrate how to train a deep neural network for facies prediction using F3 Netherlands dataset. The F3 block is located in the North Sea off the shores of Netherlands. The dataset contains 6 classes (facies or lithostratigraphic units), all of which are of varying thickness (class imbalance). Processed data is available in numpy format as a `401 x 701 x 255` array. The processed F3 data is made available by [Alaudah et al. 2019](https://github.com/yalaudah/facies_classification_benchmark).\n", "\n", "We specifically demonstrate a patch-based model approach, where we process a patch of an inline or crossline slice, instead of the entire slice." ] diff --git a/experiments/interpretation/dutchf3_patch/local/default.py b/experiments/interpretation/dutchf3_patch/local/default.py index f2cadfc1..4a4c74af 100644 --- a/experiments/interpretation/dutchf3_patch/local/default.py +++ b/experiments/interpretation/dutchf3_patch/local/default.py @@ -59,7 +59,6 @@ _C.TRAIN.MEAN = 0.0009997 # 0.0009996710808862074 _C.TRAIN.STD = 0.20977 # 0.20976548783479299 # TODO: Should we apply std scaling? _C.TRAIN.DEPTH = "none" # Options are: none, patch, and section - # None adds no depth information and the num of channels remains at 1 # Patch adds depth per patch so is simply the height of that patch from 0 to 1, channels=3 # Section adds depth per section so contains depth information for the whole section, channels=3 diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index 631fd4f4..f56162cb 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. # commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 -# url: https://github.com/olivesgatech/facies_classification_benchmark +# url: https://github.com/yalaudah/facies_classification_benchmark # # To Test: # python test.py TRAIN.END_EPOCH 1 TRAIN.SNAPSHOTS 1 --cfg "configs/hrnet.yaml" --debug @@ -44,6 +44,7 @@ "zechstein", ] + class runningScore(object): def __init__(self, n_classes): self.n_classes = n_classes diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index 14dcf726..bb51b1d9 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -163,7 +163,7 @@ def run(*options, cfg=None, debug=False): ) epochs_per_cycle = config.TRAIN.END_EPOCH // config.TRAIN.SNAPSHOTS - snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2*len(train_loader) + snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2 * len(train_loader) scheduler = CosineAnnealingScheduler( optimizer, "lr", config.TRAIN.MAX_LR, config.TRAIN.MIN_LR, cycle_size=snapshot_duration ) diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index 4517f510..c76b29ef 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -18,7 +18,7 @@ interpolate_to_fit_data, parse_labels_in_image, get_coordinates_for_slice, - get_grid, + get_grid, rand_int, trilinear_interpolation, ) @@ -112,6 +112,7 @@ def read_labels(fname, data_info): return label_imgs, label_coordinates + class SectionLoader(data.Dataset): """ Base class for section data loader @@ -122,8 +123,10 @@ class SectionLoader(data.Dataset): :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data """ - def __init__(self, data_dir, split="train", is_transform=True, augmentations=None, - seismic_path=None, label_path=None): + + def __init__( + self, data_dir, split="train", is_transform=True, augmentations=None, seismic_path=None, label_path=None + ): self.split = split self.data_dir = data_dir self.is_transform = is_transform @@ -175,11 +178,17 @@ class TrainSectionLoader(SectionLoader): :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data """ - def __init__(self, data_dir, split="train", is_transform=True, augmentations=None, - seismic_path=None, label_path=None): + + def __init__( + self, data_dir, split="train", is_transform=True, augmentations=None, seismic_path=None, label_path=None + ): super(TrainSectionLoader, self).__init__( - data_dir, split=split, is_transform=is_transform, augmentations=augmentations, - seismic_path=seismic_path, label_path=label_path + data_dir, + split=split, + is_transform=is_transform, + augmentations=augmentations, + seismic_path=seismic_path, + label_path=label_path, ) if seismic_path is not None and label_path is not None: @@ -212,11 +221,17 @@ class TrainSectionLoaderWithDepth(TrainSectionLoader): :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data """ - def __init__(self, data_dir, split="train", is_transform=True, augmentations=None, - seismic_path=None, label_path=None): + + def __init__( + self, data_dir, split="train", is_transform=True, augmentations=None, seismic_path=None, label_path=None + ): super(TrainSectionLoaderWithDepth, self).__init__( - data_dir, split=split, is_transform=is_transform, augmentations=augmentations, - seismic_path=seismic_path, label_path=label_path + data_dir, + split=split, + is_transform=is_transform, + augmentations=augmentations, + seismic_path=seismic_path, + label_path=label_path, ) self.seismic = add_section_depth_channels(self.seismic) # NCWH @@ -258,10 +273,12 @@ class TestSectionLoader(SectionLoader): :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data """ - def __init__(self, data_dir, split = "test1", is_transform = True, augmentations = None, - seismic_path = None, label_path = None): + + def __init__( + self, data_dir, split="test1", is_transform=True, augmentations=None, seismic_path=None, label_path=None + ): super(TestSectionLoader, self).__init__( - data_dir, split=split, is_transform=is_transform, augmentations=augmentations, + data_dir, split=split, is_transform=is_transform, augmentations=augmentations, ) if "test1" in self.split: @@ -298,11 +315,17 @@ class TestSectionLoaderWithDepth(TestSectionLoader): :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data """ - def __init__(self, data_dir, split="test1", is_transform=True, augmentations=None, - seismic_path = None, label_path = None): + + def __init__( + self, data_dir, split="test1", is_transform=True, augmentations=None, seismic_path=None, label_path=None + ): super(TestSectionLoaderWithDepth, self).__init__( - data_dir, split=split, is_transform=is_transform, augmentations=augmentations, - seismic_path = seismic_path, label_path = label_path + data_dir, + split=split, + is_transform=is_transform, + augmentations=augmentations, + seismic_path=seismic_path, + label_path=label_path, ) self.seismic = add_section_depth_channels(self.seismic) # NCWH @@ -351,8 +374,17 @@ class PatchLoader(data.Dataset): :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data """ - def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None, - seismic_path=None, label_path=None): + + def __init__( + self, + data_dir, + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=None, + label_path=None, + ): self.data_dir = data_dir self.is_transform = is_transform self.augmentations = augmentations @@ -416,8 +448,8 @@ class TestPatchLoader(PatchLoader): :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches """ - def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None, - txt_path=None): + + def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None, txt_path=None): super(TestPatchLoader, self).__init__( data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, ) @@ -428,7 +460,7 @@ def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmen # We are in test mode. Only read the given split. The other one might not # be available. - # If txt_path is not provided, it will be assumed as below. Otherwise, provided path will be used for + # If txt_path is not provided, it will be assumed as below. Otherwise, provided path will be used for # loading txt file and create patches. if not txt_path: self.split = "test1" # TODO: Fix this can also be test2 @@ -451,13 +483,26 @@ class TrainPatchLoader(PatchLoader): :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data """ + def __init__( - self, data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, - seismic_path=None, label_path=None + self, + data_dir, + split="train", + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=None, + label_path=None, ): super(TrainPatchLoader, self).__init__( - data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, - seismic_path=seismic_path, label_path=label_path + data_dir, + stride=stride, + patch_size=patch_size, + is_transform=is_transform, + augmentations=augmentations, + seismic_path=seismic_path, + label_path=label_path, ) # self.seismic = self.pad_volume(np.load(seismic_path)) # self.labels = self.pad_volume(np.load(labels_path)) @@ -496,13 +541,27 @@ class TrainPatchLoaderWithDepth(TrainPatchLoader): :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data """ + def __init__( - self, data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, - seismic_path=None, label_path=None + self, + data_dir, + split="train", + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=None, + label_path=None, ): super(TrainPatchLoaderWithDepth, self).__init__( - data_dir, split=split, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, - seismic_path=seismic_path, label_path=label_path + data_dir, + split=split, + stride=stride, + patch_size=patch_size, + is_transform=is_transform, + augmentations=augmentations, + seismic_path=seismic_path, + label_path=label_path, ) def __getitem__(self, index): @@ -558,9 +617,17 @@ class TrainPatchLoaderWithSectionDepth(TrainPatchLoader): :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data """ + def __init__( - self, data_dir, split="train", stride=30, patch_size=99, is_transform=True, augmentations=None, - seismic_path=None, label_path=None + self, + data_dir, + split="train", + stride=30, + patch_size=99, + is_transform=True, + augmentations=None, + seismic_path=None, + label_path=None, ): super(TrainPatchLoaderWithSectionDepth, self).__init__( data_dir, @@ -569,8 +636,8 @@ def __init__( patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, - seismic_path = seismic_path, - label_path = label_path + seismic_path=seismic_path, + label_path=label_path, ) self.seismic = add_section_depth_channels(self.seismic) @@ -579,7 +646,6 @@ def __getitem__(self, index): patch_name = self.patches[index] direction, idx, xdx, ddx = patch_name.split(sep="_") - # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 # TODO: Remember we are cancelling the shift since we no longer pad @@ -620,6 +686,7 @@ def __repr__(self): _TRAIN_SECTION_LOADERS = {"section": TrainSectionLoaderWithDepth} + def get_patch_loader(cfg): assert str(cfg.TRAIN.DEPTH).lower() in [ "section", diff --git a/scripts/prepare_dutchf3.py b/scripts/prepare_dutchf3.py index 33209074..7157214d 100644 --- a/scripts/prepare_dutchf3.py +++ b/scripts/prepare_dutchf3.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. # commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 -# url: https://github.com/olivesgatech/facies_classification_benchmark +# url: https://github.com/yalaudah/facies_classification_benchmark """Script to generate train and validation sets for Netherlands F3 dataset """ import itertools @@ -16,244 +16,188 @@ from sklearn.model_selection import train_test_split +def _get_splits_path(data_dir): + return path.join(data_dir, "splits") + + +def _get_labels_path(data_dir): + return path.join(data_dir, "train", "train_labels.npy") + + +def get_split_function(loader_type): + return _LOADER_TYPES.get(loader_type, split_patch_train_val) + + +def run_split_func(loader_type, *args, **kwargs): + split_func = get_split_function(loader_type) + split_func(*args, **kwargs) + + def _write_split_files(splits_path, train_list, val_list, loader_type): if not path.isdir(splits_path): mkdir(splits_path) - file_object = open(path.join(splits_path, - loader_type + "_train_val.txt"), "w") + file_object = open(path.join(splits_path, loader_type + "_train_val.txt"), "w") file_object.write("\n".join(train_list + val_list)) file_object.close() - file_object = open(path.join(splits_path, - loader_type + "_train.txt"), "w") + file_object = open(path.join(splits_path, loader_type + "_train.txt"), "w") file_object.write("\n".join(train_list)) file_object.close() - file_object = open(path.join(splits_path, - loader_type + "_val.txt"), "w") + file_object = open(path.join(splits_path, loader_type + "_val.txt"), "w") file_object.write("\n".join(val_list)) file_object.close() -def _get_aline_range(aline, per_val, slice_steps): +def _get_aline_range(aline, per_val, section_stride=1): + """ + Args: + aline (int): number of seismic sections in the inline or + crossline directions + per_val (float): the fraction of the volume to use for + validation. Defaults to 0.2. + section_stride (int): the stride of the sections in the training data. + If greater than 1, this function will skip (section_stride-1) between each section + Defaults to 1, do not skip any section. + """ try: - if slice_steps < 1: - raise ValueError('slice_steps cannot be zero or a negative number') - # Inline and Crossline sections + if section_stride < 1: + raise ValueError("section_stride cannot be zero or a negative number") + + if per_val < 0 or per_val >= 1: + raise ValueError("Validation percentage (per_val) should be a number in the range [0,1).") + val_aline = math.floor(aline * per_val / 2) - val_aline_range = itertools.chain(range(0, val_aline), - range(aline - val_aline, aline)) - train_aline_range = range(val_aline, aline - val_aline, slice_steps) + val_range = itertools.chain(range(0, val_aline), range(aline - val_aline, aline)) + train_range = range(val_aline, aline - val_aline, section_stride) - print("aline: ", aline) - print("val_aline: ", val_aline) - return train_aline_range, val_aline_range + return train_range, val_range except (Exception, ValueError): raise -def split_section_train_val(data_dir, output_dir, label_file, per_val=0.2, - log_config=None, slice_steps=1): +def split_section_train_val(label_file, split_direction, per_val=0.2, log_config=None, section_stride=1): """Generate train and validation files for Netherlands F3 dataset. Args: - data_dir (str): data directory path - output_dir (str): directory under data_dir to store the split files label_file (str): npy files with labels. Stored in data_dir + split_direction (str): Direction in which to split the data into + train & val. Use "inline" or "crossline". per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. log_config (str): path to log configurations - slice_steps (int): increment to the slices count. - If slice_steps > 1 the function will skip: - slice_steps - 1 slice. - Defaults to 1, do not skip any slice. + section_stride (int): the stride of the sections in the training data. + If greater than 1, this function will skip (section_stride-1) between each section + Defaults to 1, do not skip any section. """ if log_config is not None: logging.config.fileConfig(log_config) logger = logging.getLogger(__name__) - logger.info("Splitting data into sections .... ") - logger.info(f"Reading data from {data_dir}") - logger.info(f"Loading {label_file}") + labels = np.load(label_file) logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") + iline, xline, _ = labels.shape # TODO: Must make sure in the future, all new datasets conform to this order. - iline, xline, _ = labels.shape - # Inline sections - train_iline_range, val_iline_range = _get_aline_range(iline, - per_val, - slice_steps) - train_i_list = ["i_" + str(i) for i in train_iline_range] - val_i_list = ["i_" + str(i) for i in val_iline_range] + logger.info(f"Splitting in {split_direction} direction.. ") + if split_direction.lower() == "inline": + num_sections = iline + index = "i" + elif split_direction.lower() == "crossline": + num_sections = xline + index = "x" + else: + raise ValueError(f"Unknown split_direction {split_direction}") - # Xline sections - train_xline_range, val_xline_range = _get_aline_range(xline, - per_val, - slice_steps) - train_x_list = ["x_" + str(x) for x in train_xline_range] - val_x_list = ["x_" + str(x) for x in val_xline_range] + train_range, val_range = _get_aline_range(num_sections, per_val, section_stride) + train_list = [f"{index}_" + str(section) for section in train_range] + val_list = [f"{index}_" + str(section) for section in val_range] - train_list = train_x_list + train_i_list - val_list = val_x_list + val_i_list + return train_list, val_list - # write to files to disk - logger.info(f"Writing {output_dir}") - _write_split_files(output_dir, train_list, val_list, "section") - -def split_patch_train_val(data_dir, output_dir, label_file, stride, patch_size, - slice_steps=1, per_val=0.2, log_config=None): +def split_patch_train_val( + label_file, patch_stride, patch_size, split_direction, section_stride=1, per_val=0.2, log_config=None, +): """Generate train and validation files for Netherlands F3 dataset. Args: - data_dir (str): data directory path - output_dir (str): directory under data_dir to store the split files label_file (str): npy files with labels. Stored in data_dir - stride (int): stride to use when sectioning of the volume + patch_stride (int): stride to use when sampling patches patch_size (int): size of patch to extract + split_direction (str): Direction in which to split the data into + train & val. Use "inline" or "crossline". + section_stride (int): increment to the slices count. + If section_stride > 1 the function will skip: + section_stride - 1 sections in the training data. + Defaults to 1, do not skip any slice. per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. log_config (str): path to log configurations - slice_steps (int): increment to the slices count. - If slice_steps > 1 the function will skip: - slice_steps - 1 slice. - Defaults to 1, do not skip any slice. """ if log_config is not None: logging.config.fileConfig(log_config) logger = logging.getLogger(__name__) - - logger.info("Splitting data into patches .... ") - logger.info(f"Reading data from {data_dir}") - + logger.info(f"Splitting data into patches along {split_direction} direction .. ") logger.info(f"Loading {label_file}") labels = np.load(label_file) logger.debug(f"Data shape [iline|xline|depth] {labels.shape}") iline, xline, depth = labels.shape - # Inline sections - train_iline_range, val_iline_range = _get_aline_range(iline, - per_val, - slice_steps) - - # Xline sections - train_xline_range, val_xline_range = _get_aline_range(xline, - per_val, - slice_steps) - - # Generate patches from sections - # Vertical locations is common to all patches processed - vert_locations = range(0, depth - patch_size, patch_size) - logger.debug(vert_locations) - - # Process inlines - def _i_extract_patches(iline_range, horz_locations, vert_locations): - for i in iline_range: - locations = ([j, k] for j in horz_locations - for k in vert_locations) - for j, k in locations: - yield "i_" + str(i) + "_" + str(j) + "_" + str(k) - - # Process inlines - train - logger.debug("Generating Inline patches") - logger.debug("Generating Inline patches - Train") - # iline = xline x depth - val_iline = math.floor(xline * per_val / 2) - logger.debug(val_iline) - - # Process ilines - train - horz_locations_train = range(val_iline, xline - val_iline, max(1,patch_size)) - logger.debug(horz_locations_train) - train_i_list = list(_i_extract_patches(train_iline_range, - horz_locations_train, - vert_locations)) - - # val_iline - define size of the validation set for the fist part - val_iline_range = list(val_iline_range) - - # Process inlines - validation - horz_locations_val = itertools.chain(range(0, val_iline, max(1,patch_size)), - range(xline - val_iline, xline, max(1,patch_size))) - val_iline_range = list(val_iline_range) - val_i_list = list(_i_extract_patches(val_iline_range, - horz_locations_val, - vert_locations)) - - logger.debug(train_iline_range) - logger.debug(val_iline_range) - - # Process crosslines - def _x_extract_patches(xline_range, horz_locations, vert_locations): - for j in xline_range: - locations = ([i, k] for i in horz_locations - for k in vert_locations) - for i, k in locations: - yield "x_" + str(i) + "_" + str(j) + "_" + str(k) - - logger.debug("Generating Crossline patches") - logger.debug("Generating Crossline patches - Train") - # xline = iline x depth - val_xline = math.floor(iline * per_val / 2) - logger.debug(val_xline) - - # Process xlines - train - horz_locations_train = range(val_xline, iline - val_xline, max(1,patch_size)) - logger.debug(horz_locations_train) - train_x_list = list(_x_extract_patches(train_xline_range, - horz_locations_train, - vert_locations)) - - # val_xline - define size of the validation set for the fist part - val_xline_range = list(val_xline_range) - - # Process xlines - validation - horz_locations_val = itertools.chain(range(0, val_xline, max(1,patch_size)), - range(iline - val_xline, iline, max(1,patch_size))) - val_xline_range = list(val_xline_range) - val_x_list = list(_x_extract_patches(val_xline_range, - horz_locations_val, - vert_locations)) - - logger.debug(train_xline_range) - logger.debug(val_xline_range) - - train_list = train_x_list + train_i_list - val_list = val_x_list + val_i_list + split_direction = split_direction.lower() + if split_direction == "inline": + num_sections, section_length = iline, xline + elif split_direction == "crossline": + num_sections, section_length = xline, iline + else: + raise ValueError(f"Unknown split_direction: {split_direction}") + + train_range, val_range = _get_aline_range(num_sections, per_val, section_stride) + vert_locations = range(0, depth, patch_stride) + horz_locations = range(0, section_length, patch_stride) + logger.debug(vert_locations) + logger.debug(horz_locations) + + # Process sections: + def _extract_patches(sections_range, direction, horz_locations, vert_locations): + locations = itertools.product(sections_range, horz_locations, vert_locations) + if direction == "inline": + idx, xdx, ddx = 0, 1, 2 + dir = "i" + elif direction == "crossline": + idx, xdx, ddx = 1, 0, 2 + dir = "x" + + for loc in locations: # iline xline depth + yield f"{dir}_" + str(loc[idx]) + "_" + str(loc[xdx]) + "_" + str(loc[ddx]) + + # Process sections - train + logger.debug("Generating patches..") + train_list = list(_extract_patches(train_range, split_direction, horz_locations, vert_locations)) + val_list = list(_extract_patches(val_range, split_direction, horz_locations, vert_locations)) + + logger.debug(train_range) + logger.debug(val_range) logger.debug(train_list) logger.debug(val_list) - - # write to files to disk: - # NOTE: This isn't quite right we should calculate the patches - # again for the whole volume - logger.info(f"Writing {output_dir}") - _write_split_files(output_dir, train_list, val_list, "patch") - -_LOADER_TYPES = {"section": split_section_train_val, - "patch": split_patch_train_val} + return train_list, val_list -def get_split_function(loader_type): - return _LOADER_TYPES.get(loader_type, split_patch_train_val) - - -def run_split_func(loader_type, *args, **kwargs): - split_func = get_split_function(loader_type) - split_func(*args, **kwargs) - - -def split_alaudah_et_al_19(data_dir, stride, patch_size, fraction_validation=0.2, loader_type="patch", log_config=None): +def split_alaudah_et_al_19( + data_dir, patch_stride, patch_size, fraction_validation=0.2, loader_type="patch", log_config=None +): """Generate train and validation files (with overlap) for Netherlands F3 dataset. - The original split method from https://github.com/olivesgatech/facies_classification_benchmark + The original split method from https://github.com/yalaudah/facies_classification_benchmark DON'T USE, SEE NOTES BELOW Args: data_dir (str): data directory path - stride (int): stride to use when sectioning of the volume + patch_stride (int): stride to use when sampling patches patch_size (int): size of patch to extract fraction_validation (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. @@ -293,8 +237,8 @@ def split_alaudah_et_al_19(data_dir, stride, patch_size, fraction_validation=0.2 x_list = ["x_" + str(x) for x in range(xline)] elif loader_type == "patch": i_list = [] - horz_locations = range(0, xline - patch_size + 1, stride) - vert_locations = range(0, depth - patch_size + 1, stride) + horz_locations = range(0, xline - patch_size + 1, patch_stride) + vert_locations = range(0, depth - patch_size + 1, patch_stride) logger.debug("Generating Inline patches") logger.debug(horz_locations) logger.debug(vert_locations) @@ -309,8 +253,8 @@ def split_alaudah_et_al_19(data_dir, stride, patch_size, fraction_validation=0.2 i_list = list(itertools.chain(*i_list)) x_list = [] - horz_locations = range(0, iline - patch_size + 1, stride) - vert_locations = range(0, depth - patch_size + 1, stride) + horz_locations = range(0, iline - patch_size + 1, patch_stride) + vert_locations = range(0, depth - patch_size + 1, patch_stride) for j in range(xline): # for every xline: # images are references by top-left corner: @@ -332,9 +276,16 @@ def split_alaudah_et_al_19(data_dir, stride, patch_size, fraction_validation=0.2 class SplitTrainValCLI(object): - def section(self, data_dir, label_file, per_val=0.2, - log_config="logging.conf", output_dir=None, - slice_steps=1): + def section( + self, + data_dir, + label_file, + split_direction, + per_val=0.2, + log_config="logging.conf", + output_dir=None, + section_stride=1, + ): """Generate section based train and validation files for Netherlands F3 dataset. @@ -342,69 +293,108 @@ def section(self, data_dir, label_file, per_val=0.2, data_dir (str): data directory path output_dir (str): directory under data_dir to store the split files label_file (str): npy files with labels. Stored in data_dir + split_direction (int): Direction in which to split the data into + train & val. Use "inline" or "crossline", or "both". per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. log_config (str): path to log configurations - slice_steps (int): increment to the slices count. - If slice_steps > 1 the function will skip: - slice_steps - 1 slice. - Defaults to 1, do not skip any slice. + section_stride (int): the stride of the sections in the training data. + If greater than 1, this function will skip (section_stride-1) between each section + Defaults to 1, do not skip any section. """ if data_dir is not None: label_file = path.join(data_dir, label_file) - output_dir = path.join(data_dir, output_dir) - return split_section_train_val(data_dir=data_dir, - output_dir=output_dir, - label_file=label_file, - slice_steps=slice_steps, - per_val=per_val, - log_config=log_config) - - def patch(self, label_file, stride, patch_size, - per_val=0.2, log_config="logging.conf", - data_dir=None, output_dir=None, slice_steps=1): + output_dir = path.join(data_dir, output_dir) + + if split_direction.lower() == "both": + train_list_i, val_list_i = split_section_train_val( + label_file, "inline", per_val, log_config, section_stride + ) + train_list_x, val_list_x = split_section_train_val( + label_file, "crossline", per_val, log_config, section_stride + ) + # concatenate the two lists: + train_list = train_list_i + train_list_x + val_list = val_list_i + val_list_x + elif split_direction.lower() in ["inline", "crossline"]: + train_list, val_list = split_section_train_val( + label_file, split_direction, per_val, log_config, section_stride + ) + else: + raise ValueError(f"Unknown split_direction: {split_direction}") + # write to files to disk + _write_split_files(output_dir, train_list, val_list, "section") + + def patch( + self, + label_file, + stride, + patch_size, + split_direction, + per_val=0.2, + log_config="logging.conf", + data_dir=None, + output_dir=None, + section_stride=1, + ): """Generate train and validation files for Netherlands F3 dataset. Args: data_dir (str): data directory path output_dir (str): directory under data_dir to store the split files label_file (str): npy files with labels. Stored in data_dir - stride (int): stride to use when sectioning of the volume + stride (int): stride to use when sampling patches patch_size (int): size of patch to extract per_val (float, optional): the fraction of the volume to use for validation. Defaults to 0.2. log_config (str): path to log configurations - slice_steps (int): increment to the slices count. - If slice_steps > 1 the function will skip: - slice_steps - 1 slice. - Defaults to 1, do not skip any slice. + split_direction (int): Direction in which to split the data into + train & val. Use "inline" or "crossline", or "both". + section_stride (int): the stride of the sections in the training data. + If greater than 1, this function will skip (section_stride-1) between each section + Defaults to 1, do not skip any section. """ if data_dir is not None: label_file = path.join(data_dir, label_file) - output_dir = path.join(data_dir, output_dir) + output_dir = path.join(data_dir, output_dir) + + if split_direction.lower() == "both": + train_list_i, val_list_i = split_patch_train_val( + label_file, stride, patch_size, "inline", section_stride, per_val, log_config + ) + + train_list_x, val_list_x = split_patch_train_val( + label_file, stride, patch_size, "crossline", section_stride, per_val, log_config + ) + # concatenate the two lists: + train_list = train_list_i + train_list_x + val_list = val_list_i + val_list_x + elif split_direction.lower() in ["inline", "crossline"]: + train_list, val_list = split_patch_train_val( + label_file, stride, patch_size, split_direction, section_stride, per_val, log_config + ) + else: + raise ValueError(f"Unknown split_direction: {split_direction}") + + # write to files to disk: + _write_split_files(output_dir, train_list, val_list, "patch") + print(f"Successfully created the splits files in {output_dir}") - return split_patch_train_val(data_dir=data_dir, - output_dir=output_dir, - label_file=label_file, - stride=stride, - patch_size=patch_size, - slice_steps=slice_steps, - per_val=per_val, - log_config=log_config) +_LOADER_TYPES = {"section": split_section_train_val, "patch": split_patch_train_val} if __name__ == "__main__": """Example: python prepare_data.py split_train_val section --data_dir=data \ - --label_file=label_file.npy --output_dir=splits --slice_steps=2 + --label_file=label_file.npy --output_dir=splits --split_direction=both --section_stride=2 or python prepare_dutchf3.py split_train_val patch --data_dir=data \ --label_file=label_file.npy --output_dir=splits --stride=50 \ - --patch_size=100 --slice_steps=2 + --patch_size=100 --split_direction=both --section_stride=2 """ fire.Fire( {"split_train_val": SplitTrainValCLI} - # commenting the following line as this was not updated with - # the new parameters names - # "split_alaudah_et_al_19": split_alaudah_et_al_19} - ) \ No newline at end of file + # commenting the following line as this was not updated with + # the new parameters names + # "split_alaudah_et_al_19": split_alaudah_et_al_19} + ) diff --git a/scripts/prepare_penobscot.py b/scripts/prepare_penobscot.py index 754993be..8f164d82 100644 --- a/scripts/prepare_penobscot.py +++ b/scripts/prepare_penobscot.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. # commitHash: c76bf579a0d5090ebd32426907d051d499f3e847 -# url: https://github.com/olivesgatech/facies_classification_benchmark +# url: https://github.com/yalaudah/facies_classification_benchmark """Script to generate train and validation sets for Netherlands F3 dataset """ import itertools diff --git a/tests/cicd/src/scripts/get_data_for_builds.sh b/tests/cicd/src/scripts/get_data_for_builds.sh index e9302e14..f3610fa9 100755 --- a/tests/cicd/src/scripts/get_data_for_builds.sh +++ b/tests/cicd/src/scripts/get_data_for_builds.sh @@ -39,5 +39,5 @@ DATA_F3="${DATA_F3}/data" # test preprocessing scripts cd scripts python prepare_penobscot.py split_inline --data-dir=${DATA_PENOBSCOT} --val-ratio=.1 --test-ratio=.2 -python prepare_dutchf3.py split_train_val section --data_dir=${DATA_F3} --label_file=train/train_labels.npy --output_dir=splits -python prepare_dutchf3.py split_train_val patch --data_dir=${DATA_F3} --label_file=train/train_labels.npy --output_dir=splits --stride=50 --patch_size=100 +python prepare_dutchf3.py split_train_val section --data_dir=${DATA_F3} --label_file=train/train_labels.npy --output_dir=splits --split_direction=both +python prepare_dutchf3.py split_train_val patch --data_dir=${DATA_F3} --label_file=train/train_labels.npy --output_dir=splits --stride=50 --patch_size=100 --split_direction=both diff --git a/tests/test_prepare_dutchf3.py b/tests/test_prepare_dutchf3.py index 1484ee49..34349e29 100644 --- a/tests/test_prepare_dutchf3.py +++ b/tests/test_prepare_dutchf3.py @@ -2,12 +2,15 @@ # Licensed under the MIT License. """Test the extract functions against a variety of SEGY files and trace_header scenarioes """ -import pytest +import math +import os.path as path +import tempfile + import numpy as np import pandas as pd -import tempfile +import pytest + import scripts.prepare_dutchf3 as prep_dutchf3 -import math # Setup OUTPUT = None @@ -15,8 +18,8 @@ XLINE = 1008 DEPTH = 351 ALINE = np.zeros((ILINE, XLINE, DEPTH)) -STRIDE = 100 -PATCH = 50 +STRIDE = 50 +PATCH = 100 PER_VAL = 0.2 LOG_CONFIG = None @@ -25,26 +28,26 @@ def test_get_aline_range_step_one(): """check if it includes the step in the range if step = 1 """ - SLICE_STEPS = 1 + SECTION_STRIDE = 1 # Test - output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) - output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SECTION_STRIDE) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SECTION_STRIDE) - assert str(output_iline[0].step) == str(SLICE_STEPS) - assert str(output_xline[0].step) == str(SLICE_STEPS) + assert str(output_iline[0].step) == str(SECTION_STRIDE) + assert str(output_xline[0].step) == str(SECTION_STRIDE) def test_get_aline_range_step_zero(): - """check if a ValueError exception is raised when slice_steps = 0 + """check if a ValueError exception is raised when section_stride = 0 """ - with pytest.raises(ValueError, match=r'slice_steps cannot be zero or a negative number'): - SLICE_STEPS = 0 + with pytest.raises(ValueError, match="section_stride cannot be zero or a negative number"): + SECTION_STRIDE = 0 # Test - output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) - output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SECTION_STRIDE) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SECTION_STRIDE) assert output_iline assert output_xline @@ -52,14 +55,14 @@ def test_get_aline_range_step_zero(): def test_get_aline_range_negative_step(): - """check if a ValueError exception is raised when slice_steps = -1 + """check if a ValueError exception is raised when section_stride = -1 """ - with pytest.raises(ValueError, match='slice_steps cannot be zero or a negative number'): - SLICE_STEPS = -1 + with pytest.raises(ValueError, match="section_stride cannot be zero or a negative number"): + SECTION_STRIDE = -1 # Test - output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) - output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SECTION_STRIDE) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SECTION_STRIDE) assert output_iline assert output_xline @@ -67,14 +70,14 @@ def test_get_aline_range_negative_step(): def test_get_aline_range_float_step(): - """check if a ValueError exception is raised when slice_steps = 1.1 + """check if a ValueError exception is raised when section_stride = 1.1 """ with pytest.raises(TypeError, match="'float' object cannot be interpreted as an integer"): - SLICE_STEPS = 1. + SECTION_STRIDE = 1.0 # Test - output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) - output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SECTION_STRIDE) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SECTION_STRIDE) assert output_iline assert output_xline @@ -84,235 +87,341 @@ def test_get_aline_range_single_digit_step(): """check if it includes the step in the range if 1 < step < 10 """ - SLICE_STEPS = 1 + SECTION_STRIDE = 1 # Test - output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) - output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SECTION_STRIDE) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SECTION_STRIDE) - assert str(output_iline[0].step) == str(SLICE_STEPS) - assert str(output_xline[0].step) == str(SLICE_STEPS) + assert str(output_iline[0].step) == str(SECTION_STRIDE) + assert str(output_xline[0].step) == str(SECTION_STRIDE) def test_get_aline_range_double_digit_step(): """check if it includes the step in the range if step > 10 """ - SLICE_STEPS = 17 + SECTION_STRIDE = 17 # Test - output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SLICE_STEPS) - output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SLICE_STEPS) + output_iline = prep_dutchf3._get_aline_range(ILINE, PER_VAL, SECTION_STRIDE) + output_xline = prep_dutchf3._get_aline_range(XLINE, PER_VAL, SECTION_STRIDE) - assert str(output_iline[0].step) == str(SLICE_STEPS) - assert str(output_xline[0].step) == str(SLICE_STEPS) + assert str(output_iline[0].step) == str(SECTION_STRIDE) + assert str(output_xline[0].step) == str(SECTION_STRIDE) def test_prepare_dutchf3_patch_step_1(): """check a complete run for the script in case further changes are needed """ - # setting a value to SLICE_STEPS as needed to test the values - SLICE_STEPS = 1 + # setting a value to SECTION_STRIDE as needed to test the values + SECTION_STRIDE = 1 + DIRECTION = "inline" # use a temp dir that will be discarded at the end of the execution with tempfile.TemporaryDirectory() as tmpdirname: # saving the file to be used by the script - label_file = tmpdirname + '/label_file.npy' + label_file = tmpdirname + "/label_file.npy" np.save(label_file, ALINE) # stting the output directory to be used by the script - output = tmpdirname + '/split' - - # calling the main function of the script without SLICE_STEPS, to check default value - prep_dutchf3.split_patch_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file, - slice_steps=SLICE_STEPS, stride=STRIDE, patch_size=PATCH, per_val=PER_VAL,log_config=LOG_CONFIG) + output = tmpdirname + "/split" + + # calling the main function of the script without SECTION_STRIDE, to check default value + train_list, val_list = prep_dutchf3.split_patch_train_val( + label_file=label_file, + section_stride=SECTION_STRIDE, + patch_stride=STRIDE, + split_direction=DIRECTION, + patch_size=PATCH, + per_val=PER_VAL, + log_config=LOG_CONFIG, + ) + prep_dutchf3._write_split_files(output, train_list, val_list, "patch") # reading the file and splitting the data - patch_train = pd.read_csv(output + '/patch_train.txt', header=None, names=['row', 'a', 'b']) - patch_train = pd.DataFrame(patch_train.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + patch_train = pd.read_csv(output + "/patch_train.txt", header=None, names=["row"]) + patch_train = pd.DataFrame(patch_train.row.str.split("_").tolist(), columns=["dir", "i", "x", "d"]) - # test patch_train and slice_steps=2 - y = list(sorted(set(patch_train.y.astype(int)))) + # test patch_train and section_stride=2 x = list(sorted(set(patch_train.x.astype(int)))) - assert (int(y[1]) - int(y[0])) == SLICE_STEPS - assert (int(x[1]) - int(x[0])) == SLICE_STEPS + i = list(sorted(set(patch_train.i.astype(int)))) + + if DIRECTION == "crossline": + assert x[1] - x[0] == SECTION_STRIDE + assert i[1] - i[0] == STRIDE + elif DIRECTION == "inline": + assert x[1] - x[0] == STRIDE + assert i[1] - i[0] == SECTION_STRIDE # reading the file and splitting the data - patch_val = pd.read_csv(output + '/patch_val.txt', header=None, names=['row', 'a', 'b']) - patch_val = pd.DataFrame(patch_val.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + patch_val = pd.read_csv(output + "/patch_val.txt", header=None, names=["row"]) + patch_val = pd.DataFrame(patch_val.row.str.split("_").tolist(), columns=["dir", "i", "x", "d"]) - # test patch_val and slice_steps=2 - y = list(sorted(set(patch_val.y.astype(int)))) + # test patch_val and section_stride=2 x = list(sorted(set(patch_val.x.astype(int)))) - assert (int(y[1]) - int(y[0])) != SLICE_STEPS - assert (int(x[1]) - int(x[0])) != SLICE_STEPS + i = list(sorted(set(patch_val.i.astype(int)))) + + if DIRECTION == "crossline": + assert x[1] - x[0] == 1 # SECTION_STRIDE is only used in training. + assert i[1] - i[0] == STRIDE + elif DIRECTION == "inline": + assert x[1] - x[0] == STRIDE + assert i[1] - i[0] == 1 # test validation set is, at least, PER_VAL - PER_VAL_CHK = len(set(patch_train.y))/(len(set(patch_train.y))+len(set(patch_val.y))) * 100 - assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) - PER_VAL_CHK = len(set(patch_train.x))/(len(set(patch_train.x))+len(set(patch_val.x))) * 100 - assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + PER_VAL_CHK = len(set(patch_train.x)) / (len(set(patch_train.x)) + len(set(patch_val.x))) * 100 + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) + PER_VAL_CHK = len(set(patch_train.i)) / (len(set(patch_train.i)) + len(set(patch_val.i))) * 100 + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) def test_prepare_dutchf3_patch_step_2(): """check a complete run for the script in case further changes are needed """ - # setting a value to SLICE_STEPS as needed to test the values - SLICE_STEPS = 2 + # setting a value to SECTION_STRIDE as needed to test the values + SECTION_STRIDE = 2 + DIRECTION = "crossline" # use a temp dir that will be discarded at the end of the execution with tempfile.TemporaryDirectory() as tmpdirname: # saving the file to be used by the script - label_file = tmpdirname + '/label_file.npy' + label_file = tmpdirname + "/label_file.npy" np.save(label_file, ALINE) # stting the output directory to be used by the script - output = tmpdirname + '/split' - - # calling the main function of the script without SLICE_STEPS, to check default value - prep_dutchf3.split_patch_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file, - slice_steps=SLICE_STEPS, stride=STRIDE, patch_size=PATCH, per_val=PER_VAL,log_config=LOG_CONFIG) + output = tmpdirname + "/split" + + # calling the main function of the script without SECTION_STRIDE, to check default value + train_list, val_list = prep_dutchf3.split_patch_train_val( + label_file=label_file, + section_stride=SECTION_STRIDE, + patch_stride=STRIDE, + split_direction=DIRECTION, + patch_size=PATCH, + per_val=PER_VAL, + log_config=LOG_CONFIG, + ) + prep_dutchf3._write_split_files(output, train_list, val_list, "patch") # reading the file and splitting the data - patch_train = pd.read_csv(output + '/patch_train.txt', header=None, names=['row', 'a', 'b']) - patch_train = pd.DataFrame(patch_train.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + patch_train = pd.read_csv(output + "/patch_train.txt", header=None, names=["row"]) + patch_train = pd.DataFrame(patch_train.row.str.split("_").tolist(), columns=["dir", "i", "x", "d"]) - # test patch_train and slice_steps=2 - y = list(sorted(set(patch_train.y.astype(int)))) + # test patch_train and section_stride=2 x = list(sorted(set(patch_train.x.astype(int)))) - assert (int(y[1]) - int(y[0])) == SLICE_STEPS - assert (int(x[1]) - int(x[0])) == SLICE_STEPS + i = list(sorted(set(patch_train.i.astype(int)))) + + if DIRECTION == "crossline": + assert x[1] - x[0] == SECTION_STRIDE + assert i[1] - i[0] == STRIDE + elif DIRECTION == "inline": + assert x[1] - x[0] == STRIDE + assert i[1] - i[0] == SECTION_STRIDE # reading the file and splitting the data - patch_val = pd.read_csv(output + '/patch_val.txt', header=None, names=['row', 'a', 'b']) - patch_val = pd.DataFrame(patch_val.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + patch_val = pd.read_csv(output + "/patch_val.txt", header=None, names=["row"]) + patch_val = pd.DataFrame(patch_val.row.str.split("_").tolist(), columns=["dir", "i", "x", "d"]) - # test patch_val and slice_steps=2 - y = list(sorted(set(patch_val.y.astype(int)))) + # test patch_val and section_stride=2 x = list(sorted(set(patch_val.x.astype(int)))) - assert (int(y[1]) - int(y[0])) != SLICE_STEPS - assert (int(x[1]) - int(x[0])) != SLICE_STEPS + i = list(sorted(set(patch_val.i.astype(int)))) + + if DIRECTION == "crossline": + assert x[1] - x[0] == 1 # SECTION_STRIDE is only used in training. + assert i[1] - i[0] == STRIDE + elif DIRECTION == "inline": + assert x[1] - x[0] == STRIDE + assert i[1] - i[0] == 1 # test validation set is, at least, PER_VAL - PER_VAL_CHK = len(set(patch_train.y))/(len(set(patch_train.y))+len(set(patch_val.y))) * 100 - assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) - PER_VAL_CHK = len(set(patch_train.x))/(len(set(patch_train.x))+len(set(patch_val.x))) * 100 - assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + PER_VAL_CHK = len(set(patch_train.x)) / (len(set(patch_train.x)) + len(set(patch_val.x))) * 100 + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) + PER_VAL_CHK = len(set(patch_train.i)) / (len(set(patch_train.i)) + len(set(patch_val.i))) * 100 + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) -def test_prepare_dutchf3_patch_train_and_test_sets(): +def test_prepare_dutchf3_patch_train_and_test_sets_inline(): """check a complete run for the script in case further changes are needed """ - # setting a value to SLICE_STEPS as needed to test the values - SLICE_STEPS = 1 + # setting a value to SECTION_STRIDE as needed to test the values + SECTION_STRIDE = 1 + DIRECTION = "inline" # use a temp dir that will be discarded at the end of the execution with tempfile.TemporaryDirectory() as tmpdirname: # saving the file to be used by the script - label_file = tmpdirname + '/label_file.npy' + label_file = tmpdirname + "/label_file.npy" np.save(label_file, ALINE) # stting the output directory to be used by the script - output = tmpdirname + '/split' + output = tmpdirname + "/split" + + # calling the main function of the script without SECTION_STRIDE, to check default value + train_list, val_list = prep_dutchf3.split_patch_train_val( + label_file=label_file, + section_stride=SECTION_STRIDE, + patch_stride=STRIDE, + split_direction=DIRECTION, + patch_size=PATCH, + per_val=PER_VAL, + log_config=LOG_CONFIG, + ) + prep_dutchf3._write_split_files(output, train_list, val_list, "patch") - # calling the main function of the script without SLICE_STEPS, to check default value - prep_dutchf3.split_patch_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file, - slice_steps=SLICE_STEPS, stride=STRIDE, patch_size=PATCH, per_val=PER_VAL,log_config=LOG_CONFIG) + # reading the file and splitting the data + patch_train = pd.read_csv(output + "/patch_train.txt", header=None, names=["row"]) + patch_train = patch_train.row.tolist() # reading the file and splitting the data - patch_train = pd.read_csv(output + '/patch_train.txt', header=None, names=['row', 'a', 'b']) - patch_train = pd.DataFrame(patch_train.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + patch_val = pd.read_csv(output + "/patch_val.txt", header=None, names=["row"]) + patch_val = patch_val.row.tolist() + + # assert patches are unique + assert set(patch_train) & set(patch_val) == set() + + # test validation set is, at least, PER_VAL + PER_VAL_CHK = 100 * len(patch_train) / (len(patch_train) + len(patch_val)) + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) + PER_VAL_CHK = 100 * len(patch_train) / (len(patch_train) + len(patch_val)) + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) + + +def test_prepare_dutchf3_patch_train_and_test_sets_crossline(): + + """check a complete run for the script in case further changes are needed + """ + # setting a value to SECTION_STRIDE as needed to test the values + SECTION_STRIDE = 1 + DIRECTION = "crossline" + + # use a temp dir that will be discarded at the end of the execution + with tempfile.TemporaryDirectory() as tmpdirname: + + # saving the file to be used by the script + label_file = tmpdirname + "/label_file.npy" + np.save(label_file, ALINE) + + # stting the output directory to be used by the script + output = tmpdirname + "/split" + + # calling the main function of the script without SECTION_STRIDE, to check default value + train_list, val_list = prep_dutchf3.split_patch_train_val( + label_file=label_file, + section_stride=SECTION_STRIDE, + patch_stride=STRIDE, + split_direction=DIRECTION, + patch_size=PATCH, + per_val=PER_VAL, + log_config=LOG_CONFIG, + ) + prep_dutchf3._write_split_files(output, train_list, val_list, "patch") # reading the file and splitting the data - patch_val = pd.read_csv(output + '/patch_val.txt', header=None, names=['row', 'a', 'b']) - patch_val = pd.DataFrame(patch_val.row.str.split('_').tolist(), columns=['aline', 'x', 'y', 'z']) + patch_train = pd.read_csv(output + "/patch_train.txt", header=None, names=["row"]) + patch_train = patch_train.row.tolist() - y_train = set(patch_train.y) - x_train = set(patch_train.x) - y_val = set(patch_val.y) - x_val = set(patch_val.x) + # reading the file and splitting the data + patch_val = pd.read_csv(output + "/patch_val.txt", header=None, names=["row"]) + patch_val = patch_val.row.tolist() - # The sets must not contain values common to both - assert y_train & y_val == set() - assert x_train & x_val == set() + # assert patches are unique + assert set(patch_train) & set(patch_val) == set() # test validation set is, at least, PER_VAL - PER_VAL_CHK = len(set(patch_train.y))/(len(set(patch_train.y))+len(set(patch_val.y))) * 100 - assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) - PER_VAL_CHK = len(set(patch_train.x))/(len(set(patch_train.x))+len(set(patch_val.x))) * 100 - assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + PER_VAL_CHK = 100 * len(patch_train) / (len(patch_train) + len(patch_val)) + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) + PER_VAL_CHK = 100 * len(patch_train) / (len(patch_train) + len(patch_val)) + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) -def test_prepare_dutchf3_section_step_1(): + +def test_prepare_dutchf3_section_step_1_crossline(): """check a complete run for the script in case further changes are needed """ - # setting a value to SLICE_STEPS as needed to test the values - SLICE_STEPS = 1 + # setting a value to SECTION_STRIDE as needed to test the values + SECTION_STRIDE = 2 + DIRECTION = "crossline" # use a temp dir that will be discarded at the end of the execution with tempfile.TemporaryDirectory() as tmpdirname: # saving the file to be used by the script - label_file = tmpdirname + '/label_file.npy' + label_file = tmpdirname + "/label_file.npy" np.save(label_file, ALINE) # stting the output directory to be used by the script - output = tmpdirname + '/split' - - # calling the main function of the script without SLICE_STEPS, to check default value - prep_dutchf3.split_section_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file,slice_steps=SLICE_STEPS, per_val=PER_VAL, log_config=LOG_CONFIG) + output = tmpdirname + "/split" + + # calling the main function of the script without SECTION_STRIDE, to check default value + train_list, val_list = prep_dutchf3.split_section_train_val( + label_file=label_file, + section_stride=SECTION_STRIDE, + split_direction=DIRECTION, + per_val=PER_VAL, + log_config=LOG_CONFIG, + ) + prep_dutchf3._write_split_files(output, train_list, val_list, "section") # reading the file and splitting the data - section_train = pd.read_csv(output + '/section_train.txt', header=None, names=['row']) - section_train = pd.DataFrame(section_train.row.str.split('_').tolist(), columns=['aline', 'section']) + section_train = pd.read_csv(output + "/section_train.txt", header=None, names=["row"]) + section_train = pd.DataFrame(section_train.row.str.split("_").tolist(), columns=["dir", "section"]) - section_val = pd.read_csv(output + '/section_val.txt', header=None, names=['row']) - section_val = pd.DataFrame(section_val.row.str.split('_').tolist(), columns=['aline', 'section']) + section_val = pd.read_csv(output + "/section_val.txt", header=None, names=["row"]) + section_val = pd.DataFrame(section_val.row.str.split("_").tolist(), columns=["dir", "section"]) # test - assert (float(section_train.section[1]) - float(section_train.section[0])) % float(SLICE_STEPS) == 0.0 - assert (float(section_val.section[1]) - float(section_val.section[0])) % float(SLICE_STEPS) == 0.0 + assert (float(section_train.section[1]) - float(section_train.section[0])) % float(SECTION_STRIDE) == 0.0 + assert (float(section_val.section[1]) - float(section_val.section[0])) % float(SECTION_STRIDE) > 0.0 # test validation set is, at least, PER_VAL - PER_VAL_CHK = len(section_val)/(len(section_val)+len(section_train)) * 100 - assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) + PER_VAL_CHK = len(section_val) / (len(section_val) + len(section_train)) * 100 + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) -def test_prepare_dutchf3_section_step_2(): + +def test_prepare_dutchf3_section_step_2_inline(): """check a complete run for the script in case further changes are needed """ - # setting a value to SLICE_STEPS as needed to test the values - SLICE_STEPS = 2 + # setting a value to SECTION_STRIDE as needed to test the values + SECTION_STRIDE = 1 + DIRECTION = "inline" # use a temp dir that will be discarded at the end of the execution with tempfile.TemporaryDirectory() as tmpdirname: # saving the file to be used by the script - label_file = tmpdirname + '/label_file.npy' + label_file = tmpdirname + "/label_file.npy" np.save(label_file, ALINE) # stting the output directory to be used by the script - output = tmpdirname + '/split' - - # calling the main function of the script without SLICE_STEPS, to check default value - prep_dutchf3.split_section_train_val(data_dir=tmpdirname, output_dir=output, label_file=label_file, - slice_steps=SLICE_STEPS, per_val=PER_VAL, log_config=LOG_CONFIG) + output = tmpdirname + "/split" + + # calling the main function of the script without SECTION_STRIDE, to check default value + train_list, val_list = prep_dutchf3.split_section_train_val( + label_file=label_file, + section_stride=SECTION_STRIDE, + split_direction=DIRECTION, + per_val=PER_VAL, + log_config=LOG_CONFIG, + ) + prep_dutchf3._write_split_files(output, train_list, val_list, "section") # reading the file and splitting the data - section_train = pd.read_csv(output + '/section_train.txt', header=None, names=['row']) - section_train = pd.DataFrame(section_train.row.str.split('_').tolist(), columns=['aline', 'section']) + section_train = pd.read_csv(output + "/section_train.txt", header=None, names=["row"]) + section_train = pd.DataFrame(section_train.row.str.split("_").tolist(), columns=["dir", "section"]) - section_val = pd.read_csv(output + '/section_val.txt', header=None, names=['row']) - section_val = pd.DataFrame(section_val.row.str.split('_').tolist(), columns=['aline', 'section']) + section_val = pd.read_csv(output + "/section_val.txt", header=None, names=["row"]) + section_val = pd.DataFrame(section_val.row.str.split("_").tolist(), columns=["dir", "section"]) # test - assert (float(section_train.section[1]) - float(section_train.section[0])) % float(SLICE_STEPS) == 0.0 - assert (float(section_val.section[1]) - float(section_val.section[0])) % float(SLICE_STEPS) > 0.0 + assert (float(section_train.section[1]) - float(section_train.section[0])) % float(SECTION_STRIDE) == 0.0 + assert (float(section_val.section[1]) - float(section_val.section[0])) % float(SECTION_STRIDE) == 0.0 # test validation set is, at least, PER_VAL - PER_VAL_CHK = len(section_val)/(len(section_val)+len(section_train)) * 100 - assert round(PER_VAL_CHK,0) >= int(PER_VAL * 100) \ No newline at end of file + PER_VAL_CHK = len(section_val) / (len(section_val) + len(section_train)) * 100 + assert round(PER_VAL_CHK, 0) >= int(PER_VAL * 100) From bb5304d2464930cfc3412f77a5ac3a82e5be813c Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Wed, 29 Apr 2020 11:43:24 -0400 Subject: [PATCH 201/207] Correctness single GPU switch (#290) * resolved rebase conflict * resolved merge conflict * resolved rebase conflict * resolved merge conflict * reverted multi-GPU builds to run on single GPU --- .../penobscot/local/logging.conf | 34 ------------------- tests/cicd/main_build.yml | 33 +++++++++--------- 2 files changed, 17 insertions(+), 50 deletions(-) delete mode 100644 contrib/experiments/interpretation/penobscot/local/logging.conf diff --git a/contrib/experiments/interpretation/penobscot/local/logging.conf b/contrib/experiments/interpretation/penobscot/local/logging.conf deleted file mode 100644 index 56334fc4..00000000 --- a/contrib/experiments/interpretation/penobscot/local/logging.conf +++ /dev/null @@ -1,34 +0,0 @@ -[loggers] -keys=root,__main__,event_handlers - -[handlers] -keys=consoleHandler - -[formatters] -keys=simpleFormatter - -[logger_root] -level=INFO -handlers=consoleHandler - -[logger___main__] -level=INFO -handlers=consoleHandler -qualname=__main__ -propagate=0 - -[logger_event_handlers] -level=INFO -handlers=consoleHandler -qualname=event_handlers -propagate=0 - -[handler_consoleHandler] -class=StreamHandler -level=INFO -formatter=simpleFormatter -args=(sys.stdout,) - -[formatter_simpleFormatter] -format=%(asctime)s - %(name)s - %(levelname)s - %(message)s - diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index bfc9b026..f1a33f61 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -91,6 +91,7 @@ jobs: ################################################################################################### # Stage 3: Dutch F3 patch models: deconvnet, unet, HRNet patch depth, HRNet section depth +# CAUTION: reverted these builds to single-GPU leaving new multi-GPU code in to be reverted later ################################################################################################### - job: dutchf3_patch @@ -113,30 +114,30 @@ jobs: dir=$(mktemp -d) pids= - export CUDA_VISIBLE_DEVICES=0 + # export CUDA_VISIBLE_DEVICES=0 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" - export CUDA_VISIBLE_DEVICES=1 + # export CUDA_VISIBLE_DEVICES=1 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" - export CUDA_VISIBLE_DEVICES=2 + # export CUDA_VISIBLE_DEVICES=2 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" - export CUDA_VISIBLE_DEVICES=3 + # export CUDA_VISIBLE_DEVICES=3 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" wait $pids || exit 1 @@ -157,16 +158,16 @@ jobs: dir=$(mktemp -d) pids= - export CUDA_VISIBLE_DEVICES=0 + # export CUDA_VISIBLE_DEVICES=0 # find the latest model which we just trained model_dir=$(ls -td output/patch_deconvnet/no_depth/* | head -1) model=$(ls -t ${model_dir}/*.pth | head -1) # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ 'TEST.MODEL_PATH' ${model} \ - --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" - export CUDA_VISIBLE_DEVICES=1 + # export CUDA_VISIBLE_DEVICES=1 # find the latest model which we just trained model_dir=$(ls -td output/unet/section_depth/* | head -1) model=$(ls -t ${model_dir}/*.pth | head -1) @@ -174,9 +175,9 @@ jobs: # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ 'TEST.MODEL_PATH' ${model} \ - --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" - export CUDA_VISIBLE_DEVICES=2 + # export CUDA_VISIBLE_DEVICES=2 # find the latest model which we just trained model_dir=$(ls -td output/seresnet_unet/section_depth/* | head -1) model=$(ls -t ${model_dir}/*.pth | head -1) @@ -184,9 +185,9 @@ jobs: # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ 'TEST.MODEL_PATH' ${model} \ - --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" - export CUDA_VISIBLE_DEVICES=3 + # export CUDA_VISIBLE_DEVICES=3 # find the latest model which we just trained model_dir=$(ls -td output/hrnet/section_depth/* | head -1) model=$(ls -t ${model_dir}/*.pth | head -1) @@ -195,7 +196,7 @@ jobs: { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'TEST.MODEL_PATH' ${model} \ - --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } & + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # wait for completion From 2ed51adf516ee1758d2975ed13f247bbca331578 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Wed, 29 Apr 2020 17:28:20 -0400 Subject: [PATCH 202/207] 249r3 (#283) * resolved rebase conflict * resolved merge conflict * resolved rebase conflict * resolved merge conflict * wrote the bulk of checkerboard example * finished checkerboard generator * resolved merge conflict * resolved rebase conflict * got binary dataset to run * finished first implementation mockup - commit before rebase * made sure rebase went well manually * added new files * resolved PR comments and made tests work * fixed build error * fixed build VM errors * more fixes to get the test to pass * fixed n_classes issue in data.py * fixed notebook as well * cleared notebook run cell * trivial commit to restart builds * addressed PR comments * moved notebook tests to main build pipeline * fixed checkerboard label precision * relaxed performance tests for now * resolved merge conflict * resolved merge conflict * fixed build error * resolved merge conflicts * fixed another merge mistake --- README.md | 1 + conftest.py | 0 .../dutchf3_patch/distributed/train.py | 19 +- .../penobscot/local/logging.conf | 34 +++ .../cv_lib/event_handlers/logging_handlers.py | 10 +- ..._patch_model_training_and_evaluation.ipynb | 6 +- .../dutchf3_patch/local/test.py | 49 +++- .../dutchf3_patch/local/train.py | 16 +- .../dutchf3/data.py | 93 ++++++-- scripts/gen_checkerboard.py | 197 ++++++++++++++++ tests/cicd/main_build.yml | 217 ++++++++++++++++-- tests/cicd/notebooks_build.yml | 59 ----- tests/cicd/src/check_performance.py | 97 ++++++++ tests/cicd/src/scripts/get_data_for_builds.sh | 18 +- 14 files changed, 674 insertions(+), 142 deletions(-) create mode 100644 conftest.py create mode 100644 contrib/experiments/interpretation/penobscot/local/logging.conf create mode 100644 scripts/gen_checkerboard.py delete mode 100644 tests/cicd/notebooks_build.yml create mode 100644 tests/cicd/src/check_performance.py diff --git a/README.md b/README.md index a4559c20..81f6920f 100644 --- a/README.md +++ b/README.md @@ -419,3 +419,4 @@ which will indicate that anaconda folder is `__/anaconda__`. We'll refer to this 5. Navigate back to the Virtual Machine view in Step 2 and click the Start button to start the virtual machine.
+ diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..e69de29b diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py index e52970ed..7f3a136e 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -139,7 +139,7 @@ def run(*options, cfg=None, local_rank=0, debug=False): stride=config.TRAIN.STRIDE, patch_size=config.TRAIN.PATCH_SIZE, augmentations=train_aug, - ) + ) val_set = TrainPatchLoader( config.DATASET.ROOT, @@ -150,21 +150,18 @@ def run(*options, cfg=None, local_rank=0, debug=False): augmentations=val_aug, ) - if debug: - val_set = data.Subset(val_set, range(3)) - logger.info(f"Validation examples {len(val_set)}") n_classes = train_set.n_classes if debug: - logger.info("Running in debug mode..") - train_set = data.Subset(train_set, list(range(4))) - val_set = data.Subset(val_set, list(range(4))) - + val_set = data.Subset(val_set, range(config.VALIDATION.BATCH_SIZE_PER_GPU)) + train_set = data.Subset(train_set, range(config.TRAIN.BATCH_SIZE_PER_GPU*2)) + logger.info(f"Training examples {len(train_set)}") - logger.info(f"Validation examples {len(val_set)}") + logger.info(f"Validation examples {len(val_set)}") train_sampler = torch.utils.data.distributed.DistributedSampler(train_set, num_replicas=world_size, rank=local_rank) + train_loader = data.DataLoader( train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, sampler=train_sampler, ) @@ -197,8 +194,10 @@ def run(*options, cfg=None, local_rank=0, debug=False): model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[device], find_unused_parameters=True) - snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2 * len(train_loader) + snapshot_duration = epochs_per_cycle * len(train_loader) if not debug else 2*len(train_loader) + warmup_duration = 5 * len(train_loader) + warmup_scheduler = LinearCyclicalScheduler( optimizer, "lr", diff --git a/contrib/experiments/interpretation/penobscot/local/logging.conf b/contrib/experiments/interpretation/penobscot/local/logging.conf new file mode 100644 index 00000000..56334fc4 --- /dev/null +++ b/contrib/experiments/interpretation/penobscot/local/logging.conf @@ -0,0 +1,34 @@ +[loggers] +keys=root,__main__,event_handlers + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[logger___main__] +level=INFO +handlers=consoleHandler +qualname=__main__ +propagate=0 + +[logger_event_handlers] +level=INFO +handlers=consoleHandler +qualname=event_handlers +propagate=0 + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s + diff --git a/cv_lib/cv_lib/event_handlers/logging_handlers.py b/cv_lib/cv_lib/event_handlers/logging_handlers.py index ea883a36..97f382bc 100644 --- a/cv_lib/cv_lib/event_handlers/logging_handlers.py +++ b/cv_lib/cv_lib/event_handlers/logging_handlers.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. - +import json import logging import logging.config from toolz import curry @@ -26,10 +26,14 @@ def log_lr(optimizer, engine): @curry -def log_metrics(log_msg, engine, metrics_dict={"pixacc": "Avg accuracy :", "nll": "Avg loss :"}): +def log_metrics(log_msg, engine, metrics_dict={"pixacc": "Avg accuracy :", "nll": "Avg loss :"}, fname=None): logger = logging.getLogger(__name__) metrics = engine.state.metrics - metrics_msg = " ".join([f"{metrics_dict[k]} {metrics[k]:.2f}" for k in metrics_dict]) + metrics_msg = " ".join([f"{metrics_dict[k]} {metrics[k]:.4f}" for k in metrics_dict]) + if fname: + with open(fname, "w") as fid: + output_dict = {metrics_dict[k]: float(metrics[k]) for k in metrics_dict} + json.dump(output_dict, fid) logger.info(f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}] " + metrics_msg) diff --git a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb index c11014c5..1748e45c 100644 --- a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb +++ b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb @@ -517,6 +517,7 @@ "\n", "train_set = TrainPatchLoader(\n", " config.DATASET.ROOT,\n", + " config.DATASET.NUM_CLASSES,\n", " split=\"train\",\n", " is_transform=True,\n", " stride=config.TRAIN.STRIDE,\n", @@ -527,6 +528,7 @@ "logger.info(train_set)\n", "val_set = TrainPatchLoader(\n", " config.DATASET.ROOT,\n", + " config.DATASET.NUM_CLASSES,\n", " split=\"val\",\n", " is_transform=True,\n", " stride=config.TRAIN.STRIDE,\n", @@ -551,7 +553,7 @@ " val_set,\n", " batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU,\n", " num_workers=config.WORKERS,\n", - ")\n" + ")" ] }, { @@ -1004,7 +1006,7 @@ "# Load test data\n", "TestSectionLoader = get_test_loader(config)\n", "test_set = TestSectionLoader(\n", - " config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug\n", + " config.DATASET.ROOT, config.DATASET.NUM_CLASSES, split=split, is_transform=True, augmentations=section_aug\n", ")\n", "# needed to fix this bug in pytorch https://github.com/pytorch/pytorch/issues/973\n", "# one of the workers will quit prematurely\n", diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index f56162cb..4162d0d4 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -13,6 +13,7 @@ """ import itertools +import json import logging import logging.config import os @@ -268,7 +269,13 @@ def _evaluate_split( logger = logging.getLogger(__name__) TestSectionLoader = get_test_loader(config) - test_set = TestSectionLoader(config.DATASET.ROOT, split=split, is_transform=True, augmentations=section_aug,) + test_set = TestSectionLoader( + config.DATASET.ROOT, + config.DATASET.NUM_CLASSES, + split=split, + is_transform=True, + augmentations=section_aug + ) n_classes = test_set.n_classes @@ -276,7 +283,7 @@ def _evaluate_split( if debug: logger.info("Running in Debug/Test mode") - test_loader = take(1, test_loader) + test_loader = take(2, test_loader) try: output_dir = generate_path( @@ -321,8 +328,12 @@ def _evaluate_split( # Log split results logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') - for cdx, class_name in enumerate(_CLASS_NAMES): - logger.info(f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}') + if debug: + for cdx in range(n_classes): + logger.info(f' Class_{cdx}_accuracy {score["Class Accuracy: "][cdx]:.3f}') + else: + for cdx, class_name in enumerate(_CLASS_NAMES): + logger.info(f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}') logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') @@ -414,17 +425,35 @@ def test(*options, cfg=None, debug=False): score, class_iou = running_metrics_overall.get_scores() logger.info("--------------- FINAL RESULTS -----------------") - logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.3f}') - for cdx, class_name in enumerate(_CLASS_NAMES): - logger.info(f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.3f}') - logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.3f}') - logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.3f}') - logger.info(f'Mean IoU: {score["Mean IoU: "]:0.3f}') + logger.info(f'Pixel Acc: {score["Pixel Acc: "]:.4f}') + + if debug: + for cdx in range(n_classes): + logger.info(f' Class_{cdx}_accuracy {score["Class Accuracy: "][cdx]:.3f}') + else: + for cdx, class_name in enumerate(_CLASS_NAMES): + logger.info(f' {class_name}_accuracy {score["Class Accuracy: "][cdx]:.4f}') + + logger.info(f'Mean Class Acc: {score["Mean Class Acc: "]:.4f}') + logger.info(f'Freq Weighted IoU: {score["Freq Weighted IoU: "]:.4f}') + logger.info(f'Mean IoU: {score["Mean IoU: "]:0.4f}') # Save confusion matrix: confusion = score["confusion_matrix"] np.savetxt(path.join(log_dir, "confusion.csv"), confusion, delimiter=" ") + if debug: + config_file_name = "default_config" if not cfg else cfg.split("/")[-1].split(".")[0] + fname = f"metrics_test_{config_file_name}_{config.TRAIN.MODEL_DIR}.json" + with open(fname, "w") as fid: + json.dump( + { + metric: score[metric] + for metric in ["Pixel Acc: ", "Mean Class Acc: ", "Freq Weighted IoU: ", "Mean IoU: "] + }, + fid, + ) + if __name__ == "__main__": fire.Fire(test) diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index bb51b1d9..9e26f7a2 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -59,11 +59,12 @@ def run(*options, cfg=None, debug=False): *options (str,int ,optional): Options used to overide what is loaded from the config. To see what options are available consult default.py - cfg (str, optional): Location of config file to load. Defaults to None. + cfg (str, optional): Location of config file to load. Defaults to None. debug (bool): Places scripts in debug/test mode and only executes a few iterations """ # Configuration: - update_config(config, options=options, config_file=cfg) + update_config(config, options=options, config_file=cfg) + # The model will be saved under: outputs// config_file_name = "default_config" if not cfg else cfg.split("/")[-1].split(".")[0] try: @@ -71,7 +72,7 @@ def run(*options, cfg=None, debug=False): config.OUTPUT_DIR, git_branch(), git_hash(), config_file_name, config.TRAIN.MODEL_DIR, current_datetime(), ) except TypeError: - output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) + output_dir = generate_path(config.OUTPUT_DIR, config_file_name, config.TRAIN.MODEL_DIR, current_datetime(),) # Logging: load_log_configuration(config.LOG_CONFIG) @@ -81,6 +82,9 @@ def run(*options, cfg=None, debug=False): # Set CUDNN benchmark mode: torch.backends.cudnn.benchmark = config.CUDNN.BENCHMARK + # we will write the model under outputs / config_file_name / model_dir + config_file_name = "default_config" if not cfg else cfg.split("/")[-1].split(".")[0] + # Fix random seeds: torch.manual_seed(config.SEED) if torch.cuda.is_available(): @@ -121,16 +125,18 @@ def run(*options, cfg=None, debug=False): TrainPatchLoader = get_patch_loader(config) train_set = TrainPatchLoader( config.DATASET.ROOT, + config.DATASET.NUM_CLASSES, split="train", is_transform=True, stride=config.TRAIN.STRIDE, patch_size=config.TRAIN.PATCH_SIZE, - augmentations=train_aug, + augmentations=train_aug ) logger.info(train_set) n_classes = train_set.n_classes val_set = TrainPatchLoader( config.DATASET.ROOT, + config.DATASET.NUM_CLASSES, split="val", is_transform=True, stride=config.TRAIN.STRIDE, @@ -201,6 +207,7 @@ def run(*options, cfg=None, debug=False): ) trainer.add_event_handler(Events.EPOCH_COMPLETED, logging_handlers.log_lr(optimizer)) + fname = f"metrics_{config_file_name}_{config.TRAIN.MODEL_DIR}.json" if debug else None evaluator.add_event_handler( Events.EPOCH_COMPLETED, logging_handlers.log_metrics( @@ -211,6 +218,7 @@ def run(*options, cfg=None, debug=False): "mca": "Avg Class Accuracy :", "mIoU": "Avg Class IoU :", }, + fname=fname, ), ) diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index c76b29ef..01f42f14 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -17,10 +17,7 @@ from deepseismic_interpretation.dutchf3.utils.batch import ( interpolate_to_fit_data, parse_labels_in_image, - get_coordinates_for_slice, - get_grid, - rand_int, - trilinear_interpolation, + get_coordinates_for_slice ) @@ -117,21 +114,25 @@ class SectionLoader(data.Dataset): """ Base class for section data loader :param str data_dir: Root directory for training/test data + :param str n_classes: number of segmentation mask classes :param str split: split file to use for loading patches :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches - :param str seismic_path: Override file path for seismic data - :param str label_path: Override file path for label data """ def __init__( - self, data_dir, split="train", is_transform=True, augmentations=None, seismic_path=None, label_path=None + self, + data_dir, + n_classes, + split="train", + is_transform=True, + augmentations=None ): self.split = split self.data_dir = data_dir self.is_transform = is_transform self.augmentations = augmentations - self.n_classes = 6 + self.n_classes = n_classes self.sections = list() def __len__(self): @@ -172,6 +173,7 @@ class TrainSectionLoader(SectionLoader): """ Training data loader for sections :param str data_dir: Root directory for training/test data + :param str n_classes: number of segmentation mask classes :param str split: split file to use for loading patches :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches @@ -180,10 +182,18 @@ class TrainSectionLoader(SectionLoader): """ def __init__( - self, data_dir, split="train", is_transform=True, augmentations=None, seismic_path=None, label_path=None + self, + data_dir, + n_classes, + split="train", + is_transform=True, + augmentations=None, + seismic_path=None, + label_path=None, ): super(TrainSectionLoader, self).__init__( data_dir, + n_classes, split=split, is_transform=is_transform, augmentations=augmentations, @@ -215,6 +225,7 @@ class TrainSectionLoaderWithDepth(TrainSectionLoader): """ Section data loader that includes additional channel for depth :param str data_dir: Root directory for training/test data + :param str n_classes: number of segmentation mask classes :param str split: split file to use for loading patches :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches @@ -223,10 +234,18 @@ class TrainSectionLoaderWithDepth(TrainSectionLoader): """ def __init__( - self, data_dir, split="train", is_transform=True, augmentations=None, seismic_path=None, label_path=None + self, + data_dir, + n_classes, + split="train", + is_transform=True, + augmentations=None, + seismic_path=None, + label_path=None, ): super(TrainSectionLoaderWithDepth, self).__init__( data_dir, + n_classes, split=split, is_transform=is_transform, augmentations=augmentations, @@ -267,6 +286,7 @@ class TestSectionLoader(SectionLoader): """ Test data loader for sections :param str data_dir: Root directory for training/test data + :param str n_classes: number of segmentation mask classes :param str split: split file to use for loading patches :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches @@ -275,10 +295,17 @@ class TestSectionLoader(SectionLoader): """ def __init__( - self, data_dir, split="test1", is_transform=True, augmentations=None, seismic_path=None, label_path=None + self, + data_dir, + n_classes, + split="test1", + is_transform=True, + augmentations=None, + seismic_path=None, + label_path=None, ): super(TestSectionLoader, self).__init__( - data_dir, split=split, is_transform=is_transform, augmentations=augmentations, + data_dir, n_classes, split=split, is_transform=is_transform, augmentations=augmentations ) if "test1" in self.split: @@ -309,6 +336,7 @@ class TestSectionLoaderWithDepth(TestSectionLoader): """ Test data loader for sections that includes additional channel for depth :param str data_dir: Root directory for training/test data + :param str n_classes: number of segmentation mask classes :param str split: split file to use for loading patches :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches @@ -317,10 +345,18 @@ class TestSectionLoaderWithDepth(TestSectionLoader): """ def __init__( - self, data_dir, split="test1", is_transform=True, augmentations=None, seismic_path=None, label_path=None + self, + data_dir, + n_classes, + split="test1", + is_transform=True, + augmentations=None, + seismic_path=None, + label_path=None, ): super(TestSectionLoaderWithDepth, self).__init__( data_dir, + n_classes, split=split, is_transform=is_transform, augmentations=augmentations, @@ -366,6 +402,7 @@ class PatchLoader(data.Dataset): """ Base Data loader for the patch-based deconvnet :param str data_dir: Root directory for training/test data + :param str n_classes: number of segmentation mask classes :param int stride: training data stride :param int patch_size: Size of patch for training :param str split: split file to use for loading patches @@ -378,17 +415,16 @@ class PatchLoader(data.Dataset): def __init__( self, data_dir, + n_classes, stride=30, patch_size=99, is_transform=True, - augmentations=None, - seismic_path=None, - label_path=None, + augmentations=None ): self.data_dir = data_dir self.is_transform = is_transform self.augmentations = augmentations - self.n_classes = 6 + self.n_classes = n_classes self.patches = list() self.patch_size = patch_size self.stride = stride @@ -443,15 +479,21 @@ class TestPatchLoader(PatchLoader): """ Test Data loader for the patch-based deconvnet :param str data_dir: Root directory for training/test data + :param str n_classes: number of segmentation mask classes :param int stride: training data stride :param int patch_size: Size of patch for training :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches """ - def __init__(self, data_dir, stride=30, patch_size=99, is_transform=True, augmentations=None, txt_path=None): + def __init__(self, data_dir, n_classes, stride=30, patch_size=99, is_transform=True, augmentations=None): super(TestPatchLoader, self).__init__( - data_dir, stride=stride, patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, + data_dir, + n_classes, + stride=stride, + patch_size=patch_size, + is_transform=is_transform, + augmentations=augmentations, ) ## Warning: this is not used or tested raise NotImplementedError("This class is not correctly implemented.") @@ -487,6 +529,7 @@ class TrainPatchLoader(PatchLoader): def __init__( self, data_dir, + n_classes, split="train", stride=30, patch_size=99, @@ -497,15 +540,13 @@ def __init__( ): super(TrainPatchLoader, self).__init__( data_dir, + n_classes, stride=stride, patch_size=patch_size, is_transform=is_transform, - augmentations=augmentations, - seismic_path=seismic_path, - label_path=label_path, + augmentations=augmentations ) - # self.seismic = self.pad_volume(np.load(seismic_path)) - # self.labels = self.pad_volume(np.load(labels_path)) + warnings.warn("This no longer pads the volume") if seismic_path is not None and label_path is not None: # Load npy files (seismc and corresponding labels) from provided @@ -621,6 +662,7 @@ class TrainPatchLoaderWithSectionDepth(TrainPatchLoader): def __init__( self, data_dir, + n_classes, split="train", stride=30, patch_size=99, @@ -631,6 +673,7 @@ def __init__( ): super(TrainPatchLoaderWithSectionDepth, self).__init__( data_dir, + n_classes, split=split, stride=stride, patch_size=patch_size, @@ -762,7 +805,7 @@ def get_seismic_labels(): @curry -def decode_segmap(label_mask, n_classes, label_colours=get_seismic_labels()): +def decode_segmap(label_mask, n_classes=None, label_colours=get_seismic_labels()): """Decode segmentation class labels into a colour image Args: label_mask (np.ndarray): an (N,H,W) array of integer values denoting diff --git a/scripts/gen_checkerboard.py b/scripts/gen_checkerboard.py new file mode 100644 index 00000000..3e0d0349 --- /dev/null +++ b/scripts/gen_checkerboard.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +""" Please see the def main() function for code description.""" + +""" libraries """ + +import numpy as np +import sys +import os + +np.set_printoptions(linewidth=200) +import logging + +# toggle to WARNING when running in production, or use CLI +logging.getLogger().setLevel(logging.DEBUG) +# logging.getLogger().setLevel(logging.WARNING) +import argparse + +parser = argparse.ArgumentParser() + +""" useful information when running from a GIT folder.""" +myname = os.path.realpath(__file__) +mypath = os.path.dirname(myname) +myname = os.path.basename(myname) + + +def make_box(n_inlines, n_crosslines, n_depth, box_size): + """ + Makes a 3D box in checkerboard pattern. + + :param n_inlines: dim x + :param n_crosslines: dim y + :param n_depth: dim z + :return: numpy array + """ + # inline by crossline by depth + zero_patch = np.ones((box_size, box_size)) * WHITE + one_patch = np.ones((box_size, box_size)) * BLACK + + stride = np.hstack((zero_patch, one_patch)) + + # come up with a 2D inline image + nx, ny = stride.shape + + step_col = int(np.ceil(n_crosslines / float(ny))) + step_row = int(np.ceil(n_inlines / float(nx) / 2)) + + # move in the crossline direction + crossline_band = np.hstack((stride,) * step_col) + # multiplying by negative one flips the sign + neg_crossline_band = -1 * crossline_band + + checker_stripe = np.vstack((crossline_band, neg_crossline_band)) + + # move down a section + checker_image = np.vstack((checker_stripe,) * step_row) + + # trim excess + checker_image = checker_image[0:n_inlines, 0:n_crosslines] + + # now make a box with alternating checkers + checker_box = np.ones((n_inlines, n_crosslines, box_size * 2)) + checker_box[:, :, 0:box_size] = checker_image[:, :, np.newaxis] + # now invert the colors + checker_box[:, :, box_size:] = -1 * checker_image[:, :, np.newaxis] + + # stack boxes depth wise + step_depth = int(np.ceil(n_depth / float(box_size) / 2)) + final_box = np.concatenate((checker_box,) * step_depth, axis=2) + + # trim excess again + return final_box[0:n_inlines, 0:n_crosslines, 0:n_depth] + + +def mkdir(path): + """ + Create a directory helper function + """ + if not os.path.isdir(path): + os.mkdir(path) + + +def main(args): + """ + + Generates checkerboard dataset based on Dutch F3 in Alaudah format. + + Pre-requisite: valid Dutch F3 dataset in Alaudah format. + + """ + + logging.info("loading data") + + train_seismic = np.load(os.path.join(args.dataroot, "train", "train_seismic.npy")) + train_labels = np.load(os.path.join(args.dataroot, "train", "train_labels.npy")) + test1_seismic = np.load(os.path.join(args.dataroot, "test_once", "test1_seismic.npy")) + test1_labels = np.load(os.path.join(args.dataroot, "test_once", "test1_labels.npy")) + test2_seismic = np.load(os.path.join(args.dataroot, "test_once", "test2_seismic.npy")) + test2_labels = np.load(os.path.join(args.dataroot, "test_once", "test2_labels.npy")) + + assert train_seismic.shape == train_labels.shape + assert train_seismic.min() == WHITE + assert train_seismic.max() == BLACK + assert train_labels.min() == 0 + # this is the number of classes in Alaudah's Dutch F3 dataset + assert train_labels.max() == 5 + + assert test1_seismic.shape == test1_labels.shape + assert test1_seismic.min() == WHITE + assert test1_seismic.max() == BLACK + assert test1_labels.min() == 0 + # this is the number of classes in Alaudah's Dutch F3 dataset + assert test1_labels.max() == 5 + + assert test2_seismic.shape == test2_labels.shape + assert test2_seismic.min() == WHITE + assert test2_seismic.max() == BLACK + assert test2_labels.min() == 0 + # this is the number of classes in Alaudah's Dutch F3 dataset + assert test2_labels.max() == 5 + + logging.info("train checkerbox") + n_inlines, n_crosslines, n_depth = train_seismic.shape + checkerboard_train_seismic = make_box(n_inlines, n_crosslines, n_depth, args.box_size) + checkerboard_train_seismic = checkerboard_train_seismic.astype(train_seismic.dtype) + checkerboard_train_labels = checkerboard_train_seismic.astype(train_labels.dtype) + # labels are integers and start from zero + checkerboard_train_labels[checkerboard_train_seismic < WHITE_LABEL] = WHITE_LABEL + + # create checkerbox + logging.info("test1 checkerbox") + n_inlines, n_crosslines, n_depth = test1_seismic.shape + checkerboard_test1_seismic = make_box(n_inlines, n_crosslines, n_depth, args.box_size) + checkerboard_test1_seismic = checkerboard_test1_seismic.astype(test1_seismic.dtype) + checkerboard_test1_labels = checkerboard_test1_seismic.astype(test1_labels.dtype) + # labels are integers and start from zero + checkerboard_test1_labels[checkerboard_test1_seismic < WHITE_LABEL] = WHITE_LABEL + + logging.info("test2 checkerbox") + n_inlines, n_crosslines, n_depth = test2_seismic.shape + checkerboard_test2_seismic = make_box(n_inlines, n_crosslines, n_depth, args.box_size) + checkerboard_test2_seismic = checkerboard_test2_seismic.astype(test2_seismic.dtype) + checkerboard_test2_labels = checkerboard_test2_seismic.astype(test2_labels.dtype) + # labels are integers and start from zero + checkerboard_test2_labels[checkerboard_test2_seismic < WHITE_LABEL] = WHITE_LABEL + + logging.info("writing data to disk") + mkdir(args.dataout) + mkdir(os.path.join(args.dataout, "data")) + mkdir(os.path.join(args.dataout, "data", "splits")) + mkdir(os.path.join(args.dataout, "data", "train")) + mkdir(os.path.join(args.dataout, "data", "test_once")) + + np.save(os.path.join(args.dataout, "data", "train", "train_seismic.npy"), checkerboard_train_seismic) + np.save(os.path.join(args.dataout, "data", "train", "train_labels.npy"), checkerboard_train_labels) + + np.save(os.path.join(args.dataout, "data", "test_once", "test1_seismic.npy"), checkerboard_test1_seismic) + np.save(os.path.join(args.dataout, "data", "test_once", "test1_labels.npy"), checkerboard_test1_labels) + + np.save(os.path.join(args.dataout, "data", "test_once", "test2_seismic.npy"), checkerboard_test2_seismic) + np.save(os.path.join(args.dataout, "data", "test_once", "test2_labels.npy"), checkerboard_test2_labels) + + logging.info("all done") + + +""" GLOBAL VARIABLES """ +WHITE = -1 +BLACK = 1 +WHITE_LABEL = 0 + +parser.add_argument("--dataroot", help="Root location of the input data", type=str, required=True) +parser.add_argument("--dataout", help="Root location of the output data", type=str, required=True) +parser.add_argument("--box_size", help="Size of the bounding box", type=int, required=False, default=100) +parser.add_argument("--debug", help="Turn on debug mode", type=bool, required=False, default=False) + +""" main wrapper with profiler """ +if __name__ == "__main__": + main(parser.parse_args()) + +# pretty printing of the stack +""" + try: + logging.info('before main') + main(parser.parse_args()) + logging.info('after main') + except: + for frame in traceback.extract_tb(sys.exc_info()[2]): + fname,lineno,fn,text = frame + print ("Error in %s on line %d" % (fname, lineno)) +""" +# optionally enable profiling information +# import cProfile +# name = +# cProfile.run('main.run()', name + '.prof') +# import pstats +# p = pstats.Stats(name + '.prof') +# p.sort_stats('cumulative').print_stats(10) +# p.sort_stats('time').print_stats() diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index f1a33f61..875c1c05 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -88,6 +88,151 @@ jobs: pytest --durations=0 cv_lib/tests/ echo "cv_lib unit test job passed" +################################################################################################### +# Stage 3: Dutch F3 patch models on checkerboard test set: +# deconvnet, unet, HRNet patch depth, HRNet section depth +# CAUTION: reverted these builds to single-GPU leaving new multi-GPU code in to be reverted later +################################################################################################### + +- job: checkerboard_dutchf3_patch + dependsOn: cv_lib_unit_tests_job + timeoutInMinutes: 20 + displayName: Checkerboard Dutch F3 patch local + pool: + name: deepseismicagentpool + steps: + - bash: | + + source activate seismic-interpretation + + # disable auto error handling as we flag it manually + set +e + + cd experiments/interpretation/dutchf3_patch/local + + # Create a temporary directory to store the statuses + dir=$(mktemp -d) + + pids= + # export CUDA_VISIBLE_DEVICES=0 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + pids+=" $!" + # export CUDA_VISIBLE_DEVICES=1 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + pids+=" $!" + # export CUDA_VISIBLE_DEVICES=2 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + pids+=" $!" + # export CUDA_VISIBLE_DEVICES=3 + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ + 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + pids+=" $!" + + wait $pids || exit 1 + + # check if any of the models had an error during execution + # Get return information for each pid + for file in "$dir"/*; do + printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" + [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" + done + + # Remove the temporary directory + rm -r "$dir" + + # check validation set performance + set -e + python ../../../../tests/cicd/src/check_performance.py --infile metrics_patch_deconvnet_no_depth.json + python ../../../../tests/cicd/src/check_performance.py --infile metrics_unet_section_depth.json + python ../../../../tests/cicd/src/check_performance.py --infile metrics_seresnet_unet_section_depth.json + python ../../../../tests/cicd/src/check_performance.py --infile metrics_hrnet_section_depth.json + set +e + echo "All models finished training - start scoring" + + # Create a temporary directory to store the statuses + dir=$(mktemp -d) + + pids= + # export CUDA_VISIBLE_DEVICES=0 + # find the latest model which we just trained + model=$(ls -td output/patch_deconvnet/no_depth/* | head -1) + # try running the test script + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' \ + 'TEST.SPLIT' 'Both' 'TRAIN.MODEL_DIR' 'no_depth' \ + 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ + 'TEST.MODEL_PATH' ${model}/patch_deconvnet_running_model_0.*.pth \ + --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + pids+=" $!" + # export CUDA_VISIBLE_DEVICES=1 + # find the latest model which we just trained + model=$(ls -td output/unet/section_depth/* | head -1) + # try running the test script + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' \ + 'TEST.SPLIT' 'Both' 'TRAIN.MODEL_DIR' 'section_depth' \ + 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ + 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_0.*.pth \ + --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + pids+=" $!" + # export CUDA_VISIBLE_DEVICES=2 + # find the latest model which we just trained + model=$(ls -td output/seresnet_unet/section_depth/* | head -1) + # try running the test script + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' \ + 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ + 'TEST.SPLIT' 'Both' 'TRAIN.MODEL_DIR' 'section_depth' \ + 'TEST.MODEL_PATH' ${model}/resnet_unet_running_model_0.*.pth \ + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + pids+=" $!" + # export CUDA_VISIBLE_DEVICES=3 + # find the latest model which we just trained + model=$(ls -td output/hrnet/section_depth/* | head -1) + # try running the test script + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' \ + 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ + 'TEST.SPLIT' 'Both' 'TRAIN.MODEL_DIR' 'section_depth' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'TEST.MODEL_PATH' ${model}/seg_hrnet_running_model_0.*.pth \ + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + pids+=" $!" + + # wait for completion + wait $pids || exit 1 + + # check if any of the models had an error during execution + # Get return information for each pid + for file in "$dir"/*; do + printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")" + [[ "$(<"$file")" -ne "0" ]] && exit 1 || echo "pass" + done + + # Remove the temporary directory + rm -r "$dir" + + # check test set performance + set -e + python ../../../../tests/cicd/src/check_performance.py --infile metrics_test_patch_deconvnet_no_depth.json --test + python ../../../../tests/cicd/src/check_performance.py --infile metrics_test_unet_section_depth.json --test + python ../../../../tests/cicd/src/check_performance.py --infile metrics_test_seresnet_unet_section_depth.json --test + python ../../../../tests/cicd/src/check_performance.py --infile metrics_test_hrnet_section_depth.json --test + + echo "PASSED" + ################################################################################################### # Stage 3: Dutch F3 patch models: deconvnet, unet, HRNet patch depth, HRNet section depth @@ -95,7 +240,7 @@ jobs: ################################################################################################### - job: dutchf3_patch - dependsOn: cv_lib_unit_tests_job + dependsOn: checkerboard_dutchf3_patch timeoutInMinutes: 20 displayName: Dutch F3 patch local pool: @@ -116,28 +261,28 @@ jobs: pids= # export CUDA_VISIBLE_DEVICES=0 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'none' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ - --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + 'TRAIN.DEPTH' 'none' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ + --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=1 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=2 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + 'TRAIN.DEPTH' 'section' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=3 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ - 'TRAIN.DEPTH' 'section' \ - 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ - 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ - --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + 'TRAIN.DEPTH' 'section' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" wait $pids || exit 1 @@ -164,8 +309,9 @@ jobs: model=$(ls -t ${model_dir}/*.pth | head -1) # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model} \ - --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + 'TEST.SPLIT' 'Both' 'TRAIN.MODEL_DIR' 'no_depth' \ + 'TEST.MODEL_PATH' ${model} \ + --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=1 # find the latest model which we just trained @@ -173,9 +319,10 @@ jobs: model=$(ls -t ${model_dir}/*.pth | head -1) # try running the test script - { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model} \ - --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ + 'TEST.SPLIT' 'Both' 'TRAIN.MODEL_DIR' 'section_depth' \ + 'TEST.MODEL_PATH' ${model} \ + --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=2 # find the latest model which we just trained @@ -184,8 +331,9 @@ jobs: # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'TEST.MODEL_PATH' ${model} \ - --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + 'TEST.SPLIT' 'Both' 'TRAIN.MODEL_DIR' 'section_depth' \ + 'TEST.MODEL_PATH' ${model} \ + --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=3 # find the latest model which we just trained @@ -194,9 +342,10 @@ jobs: # try running the test script { python test.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' \ - 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ - 'TEST.MODEL_PATH' ${model} \ - --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } + 'TEST.SPLIT' 'Both' 'TRAIN.MODEL_DIR' 'section_depth' \ + 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ + 'TEST.MODEL_PATH' ${model} \ + --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # wait for completion @@ -212,4 +361,22 @@ jobs: # Remove the temporary directory rm -r "$dir" - echo "PASSED" \ No newline at end of file + echo "PASSED" + +################################################################################################### +# Stage 5: Notebook tests +################################################################################################### + +- job: F3_block_training_and_evaluation_local_notebook + dependsOn: dutchf3_patch + timeoutInMinutes: 5 + displayName: F3 block training and evaluation local notebook + pool: + name: deepseismicagentpool + steps: + - bash: | + source activate seismic-interpretation + pytest -s tests/cicd/src/notebook_integration_tests.py \ + --nbname examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb \ + --dataset_root /home/alfred/data_dynamic/dutch_f3/data \ + --model_pretrained download diff --git a/tests/cicd/notebooks_build.yml b/tests/cicd/notebooks_build.yml deleted file mode 100644 index 3cf7d1ef..00000000 --- a/tests/cicd/notebooks_build.yml +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -# Pull request against these branches will trigger this build -pr: -- master -- staging -- contrib -- correctness - -# Any commit to this branch will trigger the build. -trigger: -- master -- staging -- contrib -- correctness - -jobs: - -- job: setup - timeoutInMinutes: 10 - displayName: Setup - pool: - name: deepseismicagentpool - - steps: - - bash: | - # terminate as soon as any internal script fails - set -e - - echo "Running setup..." - pwd - ls - git branch - uname -ra - - ./scripts/env_reinstall.sh - - # use hardcoded root for now because not sure how env changes under ADO policy - DATA_ROOT="/home/alfred/data_dynamic" - - ./tests/cicd/src/scripts/get_data_for_builds.sh ${DATA_ROOT} - - # copy your model files like so - using dummy file to illustrate - azcopy --quiet --source:https://$(storagename).blob.core.windows.net/models/model --source-key $(storagekey) --destination /home/alfred/models/your_model_name - -- job: F3_block_training_and_evaluation_local - dependsOn: setup - timeoutInMinutes: 5 - displayName: F3 block training and evaluation local - pool: - name: deepseismicagentpool - steps: - - bash: | - source activate seismic-interpretation - pytest -s tests/cicd/src/notebook_integration_tests.py \ - --nbname examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb \ - --dataset_root /home/alfred/data_dynamic/dutch_f3/data \ - --model_pretrained download diff --git a/tests/cicd/src/check_performance.py b/tests/cicd/src/check_performance.py new file mode 100644 index 00000000..5df69dda --- /dev/null +++ b/tests/cicd/src/check_performance.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" Please see the def main() function for code description.""" +import json + +""" libraries """ + +import numpy as np +import sys +import os + +np.set_printoptions(linewidth=200) +import logging + +# toggle to WARNING when running in production, or use CLI +logging.getLogger().setLevel(logging.DEBUG) +# logging.getLogger().setLevel(logging.WARNING) +import argparse + +parser = argparse.ArgumentParser() + +""" useful information when running from a GIT folder.""" +myname = os.path.realpath(__file__) +mypath = os.path.dirname(myname) +myname = os.path.basename(myname) + + +def main(args): + """ + + Check to see whether performance metrics are within range on both validation + and test sets. + + """ + + logging.info("loading data") + + with open(args.infile, 'r') as fp: + data = json.load(fp) + + if args.test: + # process training set results + assert data["Pixel Acc: "] > 0.0 + assert data["Pixel Acc: "] <= 1.0 + # TODO make these into proper tests + # assert data["Pixel Acc: "] == 1.0 + # TODO: add more tests as we fix performance + # assert data["Mean Class Acc: "] == 1.0 + # assert data["Freq Weighted IoU: "] == 1.0 + # assert data["Mean IoU: "] == 1.0 + + else: + # process validation results + assert data['Pixelwise Accuracy :'] > 0.0 + assert data['Pixelwise Accuracy :'] <= 1.0 + # TODO make these into proper tests + # assert data['Pixelwise Accuracy :'] == 1.0 + # TODO: add more tests as we fix performance + # assert data['Avg loss :'] < 1e-3 + + + logging.info("all done") + + +""" GLOBAL VARIABLES """ + + +""" cmd-line arguments """ +parser.add_argument("--infile", help="Location of the file which has the metrics", type=str, required=True) +parser.add_argument( + "--test", + help="Flag to indicate that these are test set results - validation by default", + action="store_true" +) + +""" main wrapper with profiler """ +if __name__ == "__main__": + main(parser.parse_args()) + +# pretty printing of the stack +""" + try: + logging.info('before main') + main(parser.parse_args()) + logging.info('after main') + except: + for frame in traceback.extract_tb(sys.exc_info()[2]): + fname,lineno,fn,text = frame + print ("Error in %s on line %d" % (fname, lineno)) +""" +# optionally enable profiling information +# import cProfile +# name = +# cProfile.run('main.run()', name + '.prof') +# import pstats +# p = pstats.Stats(name + '.prof') +# p.sort_stats('cumulative').print_stats(10) +# p.sort_stats('time').print_stats() diff --git a/tests/cicd/src/scripts/get_data_for_builds.sh b/tests/cicd/src/scripts/get_data_for_builds.sh index f3610fa9..46dc1e95 100755 --- a/tests/cicd/src/scripts/get_data_for_builds.sh +++ b/tests/cicd/src/scripts/get_data_for_builds.sh @@ -16,6 +16,7 @@ source activate seismic-interpretation # these have to match the rest of the build jobs unless we want to # define this in ADO pipelines +DATA_CHECKERBOARD="${DATA_ROOT}/checkerboard" DATA_F3="${DATA_ROOT}/dutch_f3" DATA_PENOBSCOT="${DATA_ROOT}/penobscot" @@ -28,16 +29,25 @@ mkdir -p "${DATA_F3}" mkdir -p "${DATA_PENOBSCOT}" # test download scripts in parallel -./scripts/download_penobscot.sh "${DATA_PENOBSCOT}" & +./scripts/download_penobscot.sh "${DATA_PENOBSCOT}" & ./scripts/download_dutch_f3.sh "${DATA_F3}" & - wait # change imposed by download script DATA_F3="${DATA_F3}/data" -# test preprocessing scripts cd scripts + +python gen_checkerboard.py --dataroot ${DATA_F3} --dataout ${DATA_CHECKERBOARD} + +# finished data download and generation + +# test preprocessing scripts python prepare_penobscot.py split_inline --data-dir=${DATA_PENOBSCOT} --val-ratio=.1 --test-ratio=.2 python prepare_dutchf3.py split_train_val section --data_dir=${DATA_F3} --label_file=train/train_labels.npy --output_dir=splits --split_direction=both -python prepare_dutchf3.py split_train_val patch --data_dir=${DATA_F3} --label_file=train/train_labels.npy --output_dir=splits --stride=50 --patch_size=100 --split_direction=both +python prepare_dutchf3.py split_train_val patch --data_dir=${DATA_F3} --label_file=train/train_labels.npy --output_dir=splits --stride=50 --patch_size=100 --split_direction=both + +DATA_CHECKERBOARD="${DATA_CHECKERBOARD}/data" +# repeat for checkerboard dataset +python prepare_dutchf3.py split_train_val section --data_dir=${DATA_CHECKERBOARD} --label_file=train/train_labels.npy --output_dir=splits --split_direction=both +python prepare_dutchf3.py split_train_val patch --data_dir=${DATA_CHECKERBOARD} --label_file=train/train_labels.npy --output_dir=splits --stride=50 --patch_size=100 --split_direction=both From fcbb792c2fc837040ea45edb05fb5c48a993a7ba Mon Sep 17 00:00:00 2001 From: yalaudah Date: Mon, 4 May 2020 16:40:18 +0000 Subject: [PATCH 203/207] enabling development on docker (#291) --- docker/Dockerfile | 15 +++++++++------ docker/README.md | 4 ++-- environment/anaconda/local/environment.yml | 1 + 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index f81cf19f..7e05f919 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,6 +13,8 @@ ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 ENV PATH /opt/conda/bin:$PATH SHELL ["/bin/bash", "-c"] +WORKDIR /home/username + # Install Anaconda and download the seismic-deeplearning repo RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \ /bin/bash ~/miniconda.sh -b -p /opt/conda && \ @@ -23,9 +25,8 @@ RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86 wget --quiet https://github.com/microsoft/seismic-deeplearning/archive/staging.zip -O staging.zip && \ unzip staging.zip && rm staging.zip -WORKDIR seismic-deeplearning-staging - -RUN conda env create -n seismic-interpretation --file environment/anaconda/local/environment.yml && \ +RUN cd seismic-deeplearning-staging && \ + conda env create -n seismic-interpretation --file environment/anaconda/local/environment.yml && \ source activate seismic-interpretation && \ python -m ipykernel install --user --name seismic-interpretation && \ pip install -e interpretation && \ @@ -33,7 +34,8 @@ RUN conda env create -n seismic-interpretation --file environment/anaconda/local # TODO: add back in later when Penobscot notebook is available # Download Penobscot dataset: -# RUN data_dir="/home/username/data/penobscot" && \ +# RUN cd seismic-deeplearning-staging && \ +# data_dir="/home/username/data/penobscot" && \ # mkdir -p "$data_dir" && \ # ./scripts/download_penobscot.sh "$data_dir" && \ # cd scripts && \ @@ -42,7 +44,8 @@ RUN conda env create -n seismic-interpretation --file environment/anaconda/local # cd .. # Download F3 dataset: -RUN data_dir="/home/username/data/dutch" && \ +RUN cd seismic-deeplearning-staging && \ + data_dir="/home/username/data/dutch" && \ mkdir -p "$data_dir" && \ ./scripts/download_dutch_f3.sh "$data_dir" && \ cd scripts && \ @@ -57,4 +60,4 @@ EXPOSE 9000/tcp EXPOSE 9001/tcp CMD source activate seismic-interpretation && \ - jupyter notebook --allow-root --ip 0.0.0.0 --port 9000 + jupyter lab --allow-root --ip 0.0.0.0 --port 9000 diff --git a/docker/README.md b/docker/README.md index e2ccfacb..ce5bb37e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -22,7 +22,7 @@ This process will take a few minutes to complete. # Run the Docker image: Once the Docker image is built, you can run it anytime using the following command: ```bash -sudo docker run --rm -it -p 9000:9000 -p 9001:9001 --gpus=all --shm-size 11G --mount type=bind,source=$PWD/hrnetv2_w48_imagenet_pretrained.pth,target=/home/username/models/hrnetv2_w48_imagenet_pretrained.pth seismic-deeplearning +sudo docker run --rm -it -p 9000:9000 -p 9001:9001 --gpus=all --shm-size 11G --mount type=bind,source=$PWD/hrnetv2_w48_imagenet_pretrained.pth,target=/home/models/hrnetv2_w48_imagenet_pretrained.pth -v ~/:/home/username seismic-deeplearning ``` -If you have saved the pretrained model in a different directory, make sure you replace `$PWD/hrnetv2_w48_imagenet_pretrained.pth` with the **absolute** path to the pretrained HRNet model. The command above will run a jupyter notebook server that you can access by clicking on the link in your terminal. You can then navigate to the notebook that you would like to run. +If you have saved the pretrained model in a different directory, make sure you replace `$PWD/hrnetv2_w48_imagenet_pretrained.pth` with the **absolute** path to the pretrained HRNet model. The command above will run a Jupyter Lab instance that you can access by clicking on the link in your terminal. You can then navigate to the notebook that you would like to run. By default, running the command above would mount your home directory to the Docker container, allowing you to access your files and data from within Jupyter Lab. diff --git a/environment/anaconda/local/environment.yml b/environment/anaconda/local/environment.yml index 40b28a34..10ad22f4 100644 --- a/environment/anaconda/local/environment.yml +++ b/environment/anaconda/local/environment.yml @@ -18,6 +18,7 @@ dependencies: - itkwidgets==0.23.1 - pytest - papermill>=1.0.1 + - jupyterlab - pip: - segyio==1.8.8 - pytorch-ignite==0.3.0 From 7c31915dd2231e139248ccb358556ae3abda1170 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 4 May 2020 21:55:02 -0400 Subject: [PATCH 204/207] 289: correctness metrics and tighter tests (#293) * resolved rebase conflict * resolved merge conflict * resolved rebase conflict * resolved merge conflict * wrote the bulk of checkerboard example * finished checkerboard generator * resolved merge conflict * resolved rebase conflict * got binary dataset to run * finished first implementation mockup - commit before rebase * made sure rebase went well manually * added new files * resolved PR comments and made tests work * fixed build error * fixed build VM errors * more fixes to get the test to pass * fixed n_classes issue in data.py * fixed notebook as well * cleared notebook run cell * trivial commit to restart builds * addressed PR comments * moved notebook tests to main build pipeline * fixed checkerboard label precision * relaxed performance tests for now * resolved merge conflict * resolved merge conflict * fixed build error * resolved merge conflicts * fixed another merge mistake * resolved rebase conflict * resolved rebase 2 * resolved merge conflict * resolved merge conflict * adding new logging * added better logging - cleaner - debugged metrics on checkerboard dataset * resolved rebase conflict * resolved merge conflict * resolved merge conflict * resolved merge conflict * resolved rebase 2 * resolved merge conflict * updated notebook with the changes * addressed PR comments * addressed another PR comment --- .../dutchf3_patch/distributed/train.py | 1 - cv_lib/cv_lib/event_handlers/__init__.py | 3 +- .../cv_lib/event_handlers/logging_handlers.py | 20 +- ..._patch_model_training_and_evaluation.ipynb | 188 ++++++------------ .../dutchf3_patch/local/default.py | 8 +- .../dutchf3_patch/local/train.py | 64 +++--- tests/cicd/main_build.yml | 19 +- 7 files changed, 122 insertions(+), 181 deletions(-) diff --git a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py index 7f3a136e..34f1157a 100644 --- a/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py +++ b/contrib/experiments/interpretation/dutchf3_patch/distributed/train.py @@ -23,7 +23,6 @@ import fire import numpy as np -import toolz import torch from albumentations import Compose, HorizontalFlip, Normalize, PadIfNeeded, Resize from ignite.contrib.handlers import ConcatScheduler, CosineAnnealingScheduler, LinearCyclicalScheduler diff --git a/cv_lib/cv_lib/event_handlers/__init__.py b/cv_lib/cv_lib/event_handlers/__init__.py index 589bbd86..8bd8567f 100644 --- a/cv_lib/cv_lib/event_handlers/__init__.py +++ b/cv_lib/cv_lib/event_handlers/__init__.py @@ -31,8 +31,7 @@ def _create_checkpoint_handler(self): def __call__(self, engine, to_save): self._checkpoint_handler(engine, to_save) if self._snapshot_function(): - files = glob.glob(os.path.join(self._model_save_location, self._running_model_prefix + "*")) - print(files) + files = glob.glob(os.path.join(self._model_save_location, self._running_model_prefix + "*")) name_postfix = os.path.basename(files[0]).lstrip(self._running_model_prefix) copyfile( files[0], diff --git a/cv_lib/cv_lib/event_handlers/logging_handlers.py b/cv_lib/cv_lib/event_handlers/logging_handlers.py index 97f382bc..de354760 100644 --- a/cv_lib/cv_lib/event_handlers/logging_handlers.py +++ b/cv_lib/cv_lib/event_handlers/logging_handlers.py @@ -26,15 +26,21 @@ def log_lr(optimizer, engine): @curry -def log_metrics(log_msg, engine, metrics_dict={"pixacc": "Avg accuracy :", "nll": "Avg loss :"}, fname=None): +def log_metrics( + engine, + evaluator, + metrics_dict={ + "nll": "Avg loss :", + "pixacc": "Pixelwise Accuracy :", + "mca": "Avg Class Accuracy :", + "mIoU": "Avg Class IoU :", + }, + stage="undefined", +): logger = logging.getLogger(__name__) - metrics = engine.state.metrics + metrics = evaluator.state.metrics metrics_msg = " ".join([f"{metrics_dict[k]} {metrics[k]:.4f}" for k in metrics_dict]) - if fname: - with open(fname, "w") as fid: - output_dict = {metrics_dict[k]: float(metrics[k]) for k in metrics_dict} - json.dump(output_dict, fid) - logger.info(f"{log_msg} - Epoch {engine.state.epoch} [{engine.state.max_epochs}] " + metrics_msg) + logger.info(f"{stage} - Epoch {engine.state.epoch} [{engine.state.max_epochs}] " + metrics_msg) @curry diff --git a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb index 1748e45c..28351947 100644 --- a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb +++ b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb @@ -143,10 +143,7 @@ "from cv_lib.event_handlers import SnapshotHandler, logging_handlers\n", "from cv_lib.event_handlers.logging_handlers import Evaluator\n", "from cv_lib.event_handlers import tensorboard_handlers\n", - "from cv_lib.event_handlers.tensorboard_handlers import (\n", - " create_image_writer,\n", - " create_summary_writer, \n", - ")\n", + "from cv_lib.event_handlers.tensorboard_handlers import create_summary_writer\n", "from cv_lib.segmentation import models\n", "from cv_lib.segmentation.dutchf3.engine import (\n", " create_supervised_evaluator,\n", @@ -537,6 +534,7 @@ ")\n", "\n", "if papermill:\n", + " train_set = data.Subset(train_set, range(3))\n", " val_set = data.Subset(val_set, range(3))\n", "elif DEMO:\n", " val_set = data.Subset(val_set, range(config.VALIDATION.BATCH_SIZE_PER_GPU))\n", @@ -571,15 +569,15 @@ "source": [ "# if we're running in test mode, just run 2 batches\n", "if papermill:\n", - " train_len = config.TRAIN.BATCH_SIZE_PER_GPU*2 \n", - "# if we're running in demo mode, just run 10 batches to fine-tune the model\n", + " train_len = 2\n", + "# if we're running in demo mode, just run 20 batches to fine-tune the model\n", "elif DEMO:\n", - " train_len = config.TRAIN.BATCH_SIZE_PER_GPU*10 \n", + " train_len = 20\n", "# if we're not in test or demo modes, run the entire loop\n", "else:\n", " train_len = len(train_loader)\n", "\n", - "snapshot_duration = scheduler_step * train_len if not papermill else 2*len(train_loader)" + "snapshot_duration = scheduler_step * train_len if not papermill else train_len" ] }, { @@ -678,10 +676,7 @@ "# create training engine\n", "trainer = create_supervised_trainer(\n", " model, optimizer, criterion, prepare_batch, device=device\n", - ")\n", - "\n", - "# add learning rate scheduler\n", - "trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)" + ")" ] }, { @@ -710,35 +705,16 @@ "generate_path(output_dir)\n", "\n", "# define main summary writer which logs all model summaries\n", - "summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR))\n", - "\n", - "# add logging of training output\n", - "trainer.add_event_handler(\n", - " Events.ITERATION_COMPLETED,\n", - " logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU),\n", - ")\n", - "\n", - "# add logging of learning rate\n", - "trainer.add_event_handler(Events.EPOCH_STARTED, logging_handlers.log_lr(optimizer))\n", - "\n", - "# log learning rate to tensorboard\n", - "trainer.add_event_handler(\n", - " Events.EPOCH_STARTED,\n", - " tensorboard_handlers.log_lr(summary_writer, optimizer, \"epoch\"),\n", - ")\n", - "\n", - "# log training summary to tensorboard as well\n", - "trainer.add_event_handler(\n", - " Events.ITERATION_COMPLETED,\n", - " tensorboard_handlers.log_training_output(summary_writer),\n", - ")" + "summary_writer = create_summary_writer(log_dir=path.join(output_dir, config.LOG_DIR))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We also checkpoint models and snapshot them to disk with every training epoch." + "Next we need to score the model on validation set as it's training. To do this we need to add helper functions to manipulate data into the required shape just as we've done to prepare each batch for training at the beginning of this notebook.\n", + "\n", + "We also set up evaluation metrics which we want to record on the training set." ] }, { @@ -747,28 +723,52 @@ "metadata": {}, "outputs": [], "source": [ - "# add model checkpointing\n", - "checkpoint_handler = ModelCheckpoint(\n", - " output_dir,\n", - " \"model_f3_nb\",\n", - " save_interval=1,\n", - " n_saved=1,\n", - " create_dir=True,\n", - " require_empty=False,\n", + "transform_fn = lambda output_dict: (output_dict[\"y_pred\"].squeeze(), output_dict[\"mask\"].squeeze())\n", + "evaluator = create_supervised_evaluator(\n", + " model,\n", + " prepare_batch,\n", + " metrics={\n", + " \"nll\": Loss(criterion, output_transform=transform_fn),\n", + " \"pixacc\": pixelwise_accuracy(n_classes, output_transform=transform_fn, device=device),\n", + " \"cacc\": class_accuracy(n_classes, output_transform=transform_fn),\n", + " \"mca\": mean_class_accuracy(n_classes, output_transform=transform_fn),\n", + " \"ciou\": class_iou(n_classes, output_transform=transform_fn),\n", + " \"mIoU\": mean_iou(n_classes, output_transform=transform_fn),\n", + " },\n", + " device=device,\n", ")\n", + "trainer.add_event_handler(Events.ITERATION_STARTED, scheduler)\n", "\n", + "# Logging:\n", "trainer.add_event_handler(\n", - " Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model}\n", - ")" + " Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ),\n", + ")\n", + "trainer.add_event_handler(Events.EPOCH_COMPLETED, logging_handlers.log_lr(optimizer))\n", + "\n", + "# Tensorboard and Logging:\n", + "trainer.add_event_handler(Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer))\n", + "trainer.add_event_handler(Events.ITERATION_COMPLETED, tensorboard_handlers.log_validation_output(summary_writer))\n", + "\n", + "# add specific logger which also triggers printed metrics on test set\n", + "@trainer.on(Events.EPOCH_COMPLETED)\n", + "def log_training_results(engine):\n", + " evaluator.run(train_loader)\n", + " tensorboard_handlers.log_results(engine, evaluator, summary_writer, n_classes, stage=\"Training\")\n", + " logging_handlers.log_metrics(engine, evaluator, stage=\"Training\")\n", + "\n", + "# add specific logger which also triggers printed metrics on validation set\n", + "@trainer.on(Events.EPOCH_COMPLETED)\n", + "def log_validation_results(engine):\n", + " evaluator.run(val_loader)\n", + " tensorboard_handlers.log_results(engine, evaluator, summary_writer, n_classes, stage=\"Validation\")\n", + " logging_handlers.log_metrics(engine, evaluator, stage=\"Validation\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Next we need to score the model on validation set as it's training. To do this we need to add helper functions to manipulate data into the required shape just as we've done to prepare each batch for training at the beginning of this notebook.\n", - "\n", - "We also set up evaluation metrics which we want to record on the training set." + "We also checkpoint models and snapshot them to disk with every training epoch." ] }, { @@ -777,90 +777,18 @@ "metadata": {}, "outputs": [], "source": [ - "# helper function for\n", - "def _select_pred_and_mask(model_out_dict):\n", - " return (model_out_dict[\"y_pred\"].squeeze(), model_out_dict[\"mask\"].squeeze())\n", - "\n", - "\n", - "def _select_max(pred_tensor):\n", - " return pred_tensor.max(1)[1]\n", - "\n", - "\n", - "def _tensor_to_numpy(pred_tensor):\n", - " return pred_tensor.squeeze().cpu().numpy()\n", - "\n", - "\n", - "def snapshot_function():\n", - " return (trainer.state.iteration % snapshot_duration) == 0\n", - "\n", - "evaluator = create_supervised_evaluator(\n", - " model,\n", - " prepare_batch,\n", - " metrics={\n", - " \"nll\": Loss(criterion, output_transform=_select_pred_and_mask),\n", - " \"pixacc\": pixelwise_accuracy(\n", - " n_classes, output_transform=_select_pred_and_mask, device=device\n", - " ),\n", - " \"cacc\": class_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", - " \"mca\": mean_class_accuracy(n_classes, output_transform=_select_pred_and_mask),\n", - " \"ciou\": class_iou(n_classes, output_transform=_select_pred_and_mask),\n", - " \"mIoU\": mean_iou(n_classes, output_transform=_select_pred_and_mask),\n", - " },\n", - " device=device,\n", - ")\n", - "\n", - "trainer.add_event_handler(Events.EPOCH_COMPLETED, Evaluator(evaluator, val_loader))\n", - "\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED,\n", - " logging_handlers.log_metrics(\n", - " \"Validation results\",\n", - " metrics_dict={\n", - " \"nll\": \"Avg loss :\",\n", - " \"pixacc\": \"Pixelwise Accuracy :\",\n", - " \"mca\": \"Avg Class Accuracy :\",\n", - " \"mIoU\": \"Avg Class IoU :\",\n", - " },\n", - " ),\n", - ")\n", - "\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED,\n", - " tensorboard_handlers.log_metrics(\n", - " summary_writer,\n", - " trainer,\n", - " \"epoch\",\n", - " metrics_dict={\n", - " \"mIoU\": \"Validation/mIoU\",\n", - " \"nll\": \"Validation/Loss\",\n", - " \"mca\": \"Validation/MCA\",\n", - " \"pixacc\": \"Validation/Pixel_Acc\",\n", - " },\n", - " ),\n", - ")\n", - "\n", - "\n", - "transform_func = compose(np_to_tb, decode_segmap(n_classes=n_classes), _tensor_to_numpy)\n", - "\n", - "transform_pred = compose(transform_func, _select_max)\n", - "\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED,\n", - " create_image_writer(summary_writer, \"Validation/Image\", \"image\"),\n", - ")\n", - "\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED,\n", - " create_image_writer(\n", - " summary_writer, \"Validation/Mask\", \"mask\", transform_func=transform_func\n", - " ),\n", + "# add model checkpointing\n", + "checkpoint_handler = ModelCheckpoint(\n", + " output_dir,\n", + " \"model_f3_nb\",\n", + " save_interval=1,\n", + " n_saved=1,\n", + " create_dir=True,\n", + " require_empty=False,\n", ")\n", "\n", - "evaluator.add_event_handler(\n", - " Events.EPOCH_COMPLETED,\n", - " create_image_writer(\n", - " summary_writer, \"Validation/Pred\", \"y_pred\", transform_func=transform_pred\n", - " ),\n", + "trainer.add_event_handler(\n", + " Events.EPOCH_COMPLETED, checkpoint_handler, {config.MODEL.NAME: model}\n", ")" ] }, diff --git a/experiments/interpretation/dutchf3_patch/local/default.py b/experiments/interpretation/dutchf3_patch/local/default.py index 4a4c74af..0322d5b1 100644 --- a/experiments/interpretation/dutchf3_patch/local/default.py +++ b/experiments/interpretation/dutchf3_patch/local/default.py @@ -11,8 +11,10 @@ _C = CN() -_C.OUTPUT_DIR = "output" # This will be the base directory for all output, such as logs and saved models -_C.LOG_DIR = "" # This will be a subdirectory inside OUTPUT_DIR +# This will be the base directory for all output, such as logs and saved models +_C.OUTPUT_DIR = "output" +# This will be a subdirectory inside OUTPUT_DIR +_C.LOG_DIR = "" _C.GPUS = (0,) _C.WORKERS = 4 _C.PRINT_FREQ = 20 @@ -21,6 +23,8 @@ _C.LOG_CONFIG = "logging.conf" _C.SEED = 42 _C.OPENCV_BORDER_CONSTANT = 0 +# number of batches to use in test/debug mode +_C.NUM_DEBUG_BATCHES = 1 # Cudnn related params _C.CUDNN = CN() diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index 9e26f7a2..1d4e3348 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -12,7 +12,7 @@ Time to run on single V100 for 300 epochs: 4.5 days """ - +import json import logging import logging.config from os import path @@ -28,7 +28,7 @@ from ignite.utils import convert_tensor from cv_lib.event_handlers import SnapshotHandler, logging_handlers, tensorboard_handlers -from cv_lib.event_handlers.tensorboard_handlers import create_summary_writer, log_results +from cv_lib.event_handlers.tensorboard_handlers import create_summary_writer from cv_lib.segmentation import extract_metric_from, models from cv_lib.segmentation.dutchf3.engine import create_supervised_evaluator, create_supervised_trainer from cv_lib.segmentation.dutchf3.utils import current_datetime, generate_path, git_branch, git_hash @@ -53,6 +53,7 @@ def run(*options, cfg=None, debug=False): Notes: Options can be passed in via the options argument and loaded from the cfg file Options from default.py will be overridden by options loaded from cfg file + Options from default.py will be overridden by options loaded from cfg file Options passed in via options argument will override option loaded from cfg file Args: @@ -63,7 +64,7 @@ def run(*options, cfg=None, debug=False): debug (bool): Places scripts in debug/test mode and only executes a few iterations """ # Configuration: - update_config(config, options=options, config_file=cfg) + update_config(config, options=options, config_file=cfg) # The model will be saved under: outputs// config_file_name = "default_config" if not cfg else cfg.split("/")[-1].split(".")[0] @@ -147,13 +148,15 @@ def run(*options, cfg=None, debug=False): if debug: logger.info("Running in debug mode..") - train_set = data.Subset(train_set, list(range(4))) - val_set = data.Subset(val_set, list(range(4))) + train_set = data.Subset(train_set, range(config.TRAIN.BATCH_SIZE_PER_GPU*config.NUM_DEBUG_BATCHES)) + val_set = data.Subset(val_set, range(config.VALIDATION.BATCH_SIZE_PER_GPU)) train_loader = data.DataLoader( train_set, batch_size=config.TRAIN.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS, shuffle=True ) - val_loader = data.DataLoader(val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=config.WORKERS) + val_loader = data.DataLoader( + val_set, batch_size=config.VALIDATION.BATCH_SIZE_PER_GPU, num_workers=1 + ) # config.WORKERS) # Model: model = getattr(models, config.MODEL.NAME).get_seg_model(config) @@ -203,40 +206,38 @@ def run(*options, cfg=None, debug=False): # Logging: trainer.add_event_handler( - Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.TRAIN.BATCH_SIZE_PER_GPU), + Events.ITERATION_COMPLETED, logging_handlers.log_training_output(log_interval=config.PRINT_FREQ), ) trainer.add_event_handler(Events.EPOCH_COMPLETED, logging_handlers.log_lr(optimizer)) - fname = f"metrics_{config_file_name}_{config.TRAIN.MODEL_DIR}.json" if debug else None - evaluator.add_event_handler( - Events.EPOCH_COMPLETED, - logging_handlers.log_metrics( - "Validation results", - metrics_dict={ - "nll": "Avg loss :", - "pixacc": "Pixelwise Accuracy :", - "mca": "Avg Class Accuracy :", - "mIoU": "Avg Class IoU :", - }, - fname=fname, - ), - ) - # Tensorboard and Logging: trainer.add_event_handler(Events.ITERATION_COMPLETED, tensorboard_handlers.log_training_output(summary_writer)) trainer.add_event_handler(Events.ITERATION_COMPLETED, tensorboard_handlers.log_validation_output(summary_writer)) + # add specific logger which also triggers printed metrics on training set @trainer.on(Events.EPOCH_COMPLETED) def log_training_results(engine): evaluator.run(train_loader) - log_results(engine, evaluator, summary_writer, n_classes, stage="Training") + tensorboard_handlers.log_results(engine, evaluator, summary_writer, n_classes, stage="Training") + logging_handlers.log_metrics(engine, evaluator, stage="Training") + # add specific logger which also triggers printed metrics on validation set @trainer.on(Events.EPOCH_COMPLETED) def log_validation_results(engine): evaluator.run(val_loader) - log_results(engine, evaluator, summary_writer, n_classes, stage="Validation") - - # Checkpointing: + tensorboard_handlers.log_results(engine, evaluator, summary_writer, n_classes, stage="Validation") + logging_handlers.log_metrics(engine, evaluator, stage="Validation") + # dump validation set metrics at the very end for debugging purposes + if engine.state.epoch == config.TRAIN.END_EPOCH and debug: + fname = f"metrics_test_{config_file_name}_{config.TRAIN.MODEL_DIR}.json" + metrics = evaluator.state.metrics + out_dict = {x: metrics[x] for x in ["nll", "pixacc", "mca", "mIoU"]} + with open(fname, "w") as fid: + json.dump(out_dict, fid) + log_msg = " ".join(f"{k}: {out_dict[k]}" for k in out_dict.keys()) + logging.info(log_msg) + + # Checkpointing: snapshotting trained models to disk checkpoint_handler = SnapshotHandler( output_dir, config.MODEL.NAME, @@ -246,15 +247,8 @@ def log_validation_results(engine): evaluator.add_event_handler(Events.EPOCH_COMPLETED, checkpoint_handler, {"model": model}) logger.info("Starting training") - if debug: - trainer.run( - train_loader, - max_epochs=config.TRAIN.END_EPOCH, - epoch_length=config.TRAIN.BATCH_SIZE_PER_GPU, - seed=config.SEED, - ) - else: - trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length=len(train_loader), seed=config.SEED) + trainer.run(train_loader, max_epochs=config.TRAIN.END_EPOCH, epoch_length=len(train_loader), seed=config.SEED) + summary_writer.close() diff --git a/tests/cicd/main_build.yml b/tests/cicd/main_build.yml index 875c1c05..4a0b8f2b 100644 --- a/tests/cicd/main_build.yml +++ b/tests/cicd/main_build.yml @@ -113,30 +113,37 @@ jobs: # Create a temporary directory to store the statuses dir=$(mktemp -d) + # we are running a single batch in debug mode through, so increase the + # number of epochs to obtain a representative set of results + pids= # export CUDA_VISIBLE_DEVICES=0 - { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' \ + 'NUM_DEBUG_BATCHES' 5 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ 'TRAIN.DEPTH' 'none' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=1 - { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' \ + 'NUM_DEBUG_BATCHES' 5 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=2 - { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' \ + 'NUM_DEBUG_BATCHES' 5 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ 'TRAIN.DEPTH' 'section' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=3 - { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ + { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/checkerboard/data' \ + 'NUM_DEBUG_BATCHES' 5 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'DATASET.NUM_CLASSES' 2 'DATASET.CLASS_WEIGHTS' '[1.0, 1.0]' \ 'TRAIN.DEPTH' 'section' \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ @@ -262,24 +269,28 @@ jobs: # export CUDA_VISIBLE_DEVICES=0 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'none' \ + 'TRAIN.BATCH_SIZE_PER_GPU' 2 'VALIDATION.BATCH_SIZE_PER_GPU' 2 \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'no_depth' \ --cfg=configs/patch_deconvnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=1 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ + 'TRAIN.BATCH_SIZE_PER_GPU' 2 'VALIDATION.BATCH_SIZE_PER_GPU' 2 \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=2 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ + 'TRAIN.BATCH_SIZE_PER_GPU' 2 'VALIDATION.BATCH_SIZE_PER_GPU' 2 \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/seresnet_unet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } pids+=" $!" # export CUDA_VISIBLE_DEVICES=3 { python train.py 'DATASET.ROOT' '/home/alfred/data_dynamic/dutch_f3/data' 'TRAIN.END_EPOCH' 1 'TRAIN.SNAPSHOTS' 1 \ 'TRAIN.DEPTH' 'section' \ + 'TRAIN.BATCH_SIZE_PER_GPU' 2 'VALIDATION.BATCH_SIZE_PER_GPU' 2 \ 'MODEL.PRETRAINED' '/home/alfred/models/hrnetv2_w48_imagenet_pretrained.pth' \ 'OUTPUT_DIR' 'output' 'TRAIN.MODEL_DIR' 'section_depth' \ --cfg=configs/hrnet.yaml --debug ; echo "$?" > "$dir/$BASHPID"; } From 29ab55150b96827a308dfa5630358c834e6629a9 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Fri, 8 May 2020 16:32:40 -0400 Subject: [PATCH 205/207] uniform colormap and correctness tests (#295) * correctness code good for PR review * addressed PR comments --- .../interpretation/penobscot/local/test.py | 7 +- .../interpretation/penobscot/local/train.py | 6 +- .../event_handlers/tensorboard_handlers.py | 11 +- cv_lib/cv_lib/segmentation/dutchf3/utils.py | 6 - cv_lib/cv_lib/segmentation/utils.py | 30 ----- cv_lib/cv_lib/utils.py | 53 ++++++++ ..._patch_model_training_and_evaluation.ipynb | 10 +- .../dutchf3_patch/local/test.py | 42 +----- .../dutchf3_patch/local/train.py | 13 +- .../dutchf3/data.py | 124 ++++++++---------- tests/cicd/src/check_performance.py | 8 +- 11 files changed, 136 insertions(+), 174 deletions(-) diff --git a/contrib/experiments/interpretation/penobscot/local/test.py b/contrib/experiments/interpretation/penobscot/local/test.py index 0bf11667..e56b82de 100644 --- a/contrib/experiments/interpretation/penobscot/local/test.py +++ b/contrib/experiments/interpretation/penobscot/local/test.py @@ -74,11 +74,6 @@ def _scale_from(config): return int(scale_height) -_SEG_COLOURS = np.asarray( - [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123],] -) - - def _log_tensor_to_tensorboard(images_tensor, identifier, summary_writer, evaluator): image_grid = torchvision.utils.make_grid(images_tensor, normalize=False, scale_each=False, nrow=2) summary_writer.add_image(identifier, image_grid, evaluator.state.epoch) @@ -250,7 +245,7 @@ def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() transform_func = compose( - np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy, + np_to_tb, decode_segmap, _tensor_to_numpy, ) transform_pred = compose(transform_func, _select_max) diff --git a/contrib/experiments/interpretation/penobscot/local/train.py b/contrib/experiments/interpretation/penobscot/local/train.py index b48de2ad..eeeff141 100644 --- a/contrib/experiments/interpretation/penobscot/local/train.py +++ b/contrib/experiments/interpretation/penobscot/local/train.py @@ -42,10 +42,6 @@ from default import update_config mask_value = 255 -_SEG_COLOURS = np.asarray( - [[241, 238, 246], [208, 209, 230], [166, 189, 219], [116, 169, 207], [54, 144, 192], [5, 112, 176], [3, 78, 123],] -) - def _prepare_batch(batch, device=None, non_blocking=False): x, y, ids, patch_locations = batch @@ -258,7 +254,7 @@ def _tensor_to_numpy(pred_tensor): return pred_tensor.squeeze().cpu().numpy() transform_func = compose( - np_to_tb, decode_segmap(n_classes=n_classes, label_colours=_SEG_COLOURS), _tensor_to_numpy, + np_to_tb, decode_segmap, _tensor_to_numpy, ) transform_pred = compose(transform_func, _select_max) diff --git a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py index 625e11d1..d3df7f31 100644 --- a/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py +++ b/cv_lib/cv_lib/event_handlers/tensorboard_handlers.py @@ -8,8 +8,7 @@ from toolz import curry from cv_lib.segmentation.dutchf3.utils import np_to_tb -from deepseismic_interpretation.dutchf3.data import decode_segmap - +from cv_lib.utils import decode_segmap def create_summary_writer(log_dir): writer = SummaryWriter(logdir=log_dir) @@ -21,9 +20,9 @@ def _transform_image(output_tensor): return torchvision.utils.make_grid(output_tensor, normalize=True, scale_each=True) -def _transform_pred(output_tensor, n_classes): +def _transform_pred(output_tensor): output_tensor = output_tensor.squeeze().cpu().numpy() - decoded = decode_segmap(output_tensor, n_classes) + decoded = decode_segmap(output_tensor) return torchvision.utils.make_grid(np_to_tb(decoded), normalize=False, scale_each=False) @@ -112,5 +111,5 @@ def log_results(engine, evaluator, summary_writer, n_classes, stage): y_pred[mask == 255] = 255 summary_writer.add_image(f"{stage}/Image", _transform_image(image), epoch) - summary_writer.add_image(f"{stage}/Mask", _transform_pred(mask, n_classes), epoch) - summary_writer.add_image(f"{stage}/Pred", _transform_pred(y_pred, n_classes), epoch) + summary_writer.add_image(f"{stage}/Mask", _transform_pred(mask), epoch) + summary_writer.add_image(f"{stage}/Pred", _transform_pred(y_pred), epoch) diff --git a/cv_lib/cv_lib/segmentation/dutchf3/utils.py b/cv_lib/cv_lib/segmentation/dutchf3/utils.py index adad1e97..f00cae8c 100644 --- a/cv_lib/cv_lib/segmentation/dutchf3/utils.py +++ b/cv_lib/cv_lib/segmentation/dutchf3/utils.py @@ -38,9 +38,3 @@ def git_hash(): repo = Repo(search_parent_directories=True) return repo.active_branch.commit.hexsha - -def generate_path(base_path, *directories): - path = os.path.join(base_path, *directories) - if not os.path.exists(path): - os.makedirs(path) - return path diff --git a/cv_lib/cv_lib/segmentation/utils.py b/cv_lib/cv_lib/segmentation/utils.py index 07951e88..9c68d398 100644 --- a/cv_lib/cv_lib/segmentation/utils.py +++ b/cv_lib/cv_lib/segmentation/utils.py @@ -2,38 +2,8 @@ # Licensed under the MIT License. import numpy as np -from deepseismic_interpretation.dutchf3.data import decode_segmap -from os import path -from PIL import Image -from toolz import pipe - def _chw_to_hwc(image_array_numpy): return np.moveaxis(image_array_numpy, 0, -1) -def save_images(pred_dict, output_dir, num_classes, colours, extra_identifier=""): - for id in pred_dict: - save_image( - pred_dict[id].unsqueeze(0).cpu().numpy(), - output_dir, - num_classes, - colours, - extra_identifier=extra_identifier, - ) - - -def save_image(image_numpy_array, output_dir, num_classes, colours, extra_identifier=""): - """Save segmentation map as image - - Args: - image_numpy_array (numpy.Array): numpy array that represents an image - output_dir ([type]): - num_classes ([type]): [description] - colours ([type]): [description] - extra_identifier (str, optional): [description]. Defaults to "". - """ - im_array = decode_segmap(image_numpy_array, n_classes=num_classes, label_colours=colours,) - im = pipe((im_array * 255).astype(np.uint8).squeeze(), _chw_to_hwc, Image.fromarray,) - filename = path.join(output_dir, f"{id}_{extra_identifier}.png") - im.save(filename) diff --git a/cv_lib/cv_lib/utils.py b/cv_lib/cv_lib/utils.py index d3e41aeb..8af56d16 100644 --- a/cv_lib/cv_lib/utils.py +++ b/cv_lib/cv_lib/utils.py @@ -1,6 +1,52 @@ + +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + import os import logging +from PIL import Image +import numpy as np +from matplotlib import pyplot as plt + +def normalize(array): + """ + Normalizes a segmentation mask array to be in [0,1] range + for use with PIL.Image + """ + min = array.min() + return (array - min) / (array.max() - min) +def mask_to_disk(mask, fname, cmap_name="Paired"): + """ + write segmentation mask to disk using a particular colormap + """ + cmap = plt.get_cmap(cmap_name) + Image.fromarray(cmap(normalize(mask), bytes=True)).save(fname) + +def image_to_disk(mask, fname, cmap_name="seismic"): + """ + write segmentation image to disk using a particular colormap + """ + cmap = plt.get_cmap(cmap_name) + Image.fromarray(cmap(normalize(mask), bytes=True)).save(fname) + +def decode_segmap(label_mask, colormap_name="Paired"): + """ + Decode segmentation class labels into a colour image + Args: + label_mask (np.ndarray): an (N,H,W) array of integer values denoting + the class label at each spatial location. + Returns: + (np.ndarray): the resulting decoded color image (NCHW). + """ + out = np.zeros((label_mask.shape[0], 3, label_mask.shape[1], label_mask.shape[2])) + cmap = plt.get_cmap(colormap_name) + # loop over the batch + for i in range(label_mask.shape[0]): + im = Image.fromarray(cmap(normalize(label_mask[i, :, :]), bytes=True)).convert("RGB") + out[i, :, :, :] = np.array(im).swapaxes(0, 2).swapaxes(1, 2) + + return out def load_log_configuration(log_config_file): """ @@ -17,3 +63,10 @@ def load_log_configuration(log_config_file): logging.getLogger(__name__).error("Failed to load configuration from %s!", log_config_file) logging.getLogger(__name__).debug(str(e), exc_info=True) raise e + + +def generate_path(base_path, *directories): + path = os.path.join(base_path, *directories) + if not os.path.exists(path): + os.makedirs(path) + return path diff --git a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb index 28351947..bffbe756 100644 --- a/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb +++ b/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb @@ -159,16 +159,16 @@ ")\n", "\n", "from cv_lib.segmentation.dutchf3.utils import (\n", - " current_datetime,\n", - " generate_path,\n", + " current_datetime, \n", " git_branch,\n", " git_hash,\n", " np_to_tb,\n", ")\n", "\n", + "from cv_lib.utils import generate_path\n", + "\n", "from deepseismic_interpretation.dutchf3.data import (\n", - " get_patch_loader,\n", - " decode_segmap,\n", + " get_patch_loader, \n", " get_test_loader,\n", ")\n", "\n", @@ -1100,4 +1100,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/experiments/interpretation/dutchf3_patch/local/test.py b/experiments/interpretation/dutchf3_patch/local/test.py index 4162d0d4..6aa68062 100644 --- a/experiments/interpretation/dutchf3_patch/local/test.py +++ b/experiments/interpretation/dutchf3_patch/local/test.py @@ -24,15 +24,14 @@ import torch import torch.nn.functional as F from albumentations import Compose, Normalize, PadIfNeeded, Resize -from matplotlib import cm -from PIL import Image from toolz import compose, curry, itertoolz, pipe, take from torch.utils import data from cv_lib.segmentation import models -from cv_lib.segmentation.dutchf3.utils import current_datetime, generate_path, git_branch, git_hash -from cv_lib.utils import load_log_configuration -from deepseismic_interpretation.dutchf3.data import add_patch_depth_channels, get_seismic_labels, get_test_loader +from cv_lib.segmentation.dutchf3.utils import current_datetime, git_branch, git_hash + +from cv_lib.utils import load_log_configuration, mask_to_disk, generate_path +from deepseismic_interpretation.dutchf3.data import add_patch_depth_channels, get_test_loader from default import _C as config from default import update_config @@ -95,21 +94,6 @@ def reset(self): self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) -def normalize(array): - """ - Normalizes a segmentation mask array to be in [0,1] range - """ - min = array.min() - return (array - min) / (array.max() - min) - - -def mask_to_disk(mask, fname): - """ - write segmentation mask to disk using a particular colormap - """ - Image.fromarray(cm.gist_earth(normalize(mask), bytes=True)).save(fname) - - def _transform_CHW_to_HWC(numpy_array): return np.moveaxis(numpy_array, 0, -1) @@ -245,24 +229,6 @@ def _patch_label_2d( output = output_p[:, :, ps:-ps, ps:-ps] return output - -@curry -def to_image(label_mask, n_classes=6): - label_colours = get_seismic_labels() - r = label_mask.copy() - g = label_mask.copy() - b = label_mask.copy() - for ll in range(0, n_classes): - r[label_mask == ll] = label_colours[ll, 0] - g[label_mask == ll] = label_colours[ll, 1] - b[label_mask == ll] = label_colours[ll, 2] - rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3)) - rgb[:, :, :, 0] = r - rgb[:, :, :, 1] = g - rgb[:, :, :, 2] = b - return rgb - - def _evaluate_split( split, section_aug, model, pre_processing, output_processing, device, running_metrics_overall, config, debug=False, ): diff --git a/experiments/interpretation/dutchf3_patch/local/train.py b/experiments/interpretation/dutchf3_patch/local/train.py index 1d4e3348..c180f2c5 100644 --- a/experiments/interpretation/dutchf3_patch/local/train.py +++ b/experiments/interpretation/dutchf3_patch/local/train.py @@ -31,9 +31,9 @@ from cv_lib.event_handlers.tensorboard_handlers import create_summary_writer from cv_lib.segmentation import extract_metric_from, models from cv_lib.segmentation.dutchf3.engine import create_supervised_evaluator, create_supervised_trainer -from cv_lib.segmentation.dutchf3.utils import current_datetime, generate_path, git_branch, git_hash +from cv_lib.segmentation.dutchf3.utils import current_datetime, git_branch, git_hash from cv_lib.segmentation.metrics import class_accuracy, class_iou, mean_class_accuracy, mean_iou, pixelwise_accuracy -from cv_lib.utils import load_log_configuration +from cv_lib.utils import load_log_configuration, generate_path from deepseismic_interpretation.dutchf3.data import get_patch_loader from default import _C as config from default import update_config @@ -124,6 +124,7 @@ def run(*options, cfg=None, debug=False): # Training and Validation Loaders: TrainPatchLoader = get_patch_loader(config) + logging.info(f"Using {TrainPatchLoader}") train_set = TrainPatchLoader( config.DATASET.ROOT, config.DATASET.NUM_CLASSES, @@ -131,7 +132,9 @@ def run(*options, cfg=None, debug=False): is_transform=True, stride=config.TRAIN.STRIDE, patch_size=config.TRAIN.PATCH_SIZE, - augmentations=train_aug + augmentations=train_aug, + #augmentations=Resize(config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True), + debug=True ) logger.info(train_set) n_classes = train_set.n_classes @@ -143,6 +146,8 @@ def run(*options, cfg=None, debug=False): stride=config.TRAIN.STRIDE, patch_size=config.TRAIN.PATCH_SIZE, augmentations=val_aug, + #augmentations=Resize(config.TRAIN.AUGMENTATIONS.RESIZE.HEIGHT, config.TRAIN.AUGMENTATIONS.RESIZE.WIDTH, always_apply=True), + debug=True ) logger.info(val_set) @@ -229,7 +234,7 @@ def log_validation_results(engine): logging_handlers.log_metrics(engine, evaluator, stage="Validation") # dump validation set metrics at the very end for debugging purposes if engine.state.epoch == config.TRAIN.END_EPOCH and debug: - fname = f"metrics_test_{config_file_name}_{config.TRAIN.MODEL_DIR}.json" + fname = f"metrics_{config_file_name}_{config.TRAIN.MODEL_DIR}.json" metrics = evaluator.state.metrics out_dict = {x: metrics[x] for x in ["nll", "pixacc", "mca", "mIoU"]} with open(fname, "w") as fid: diff --git a/interpretation/deepseismic_interpretation/dutchf3/data.py b/interpretation/deepseismic_interpretation/dutchf3/data.py index 01f42f14..e580b4bb 100644 --- a/interpretation/deepseismic_interpretation/dutchf3/data.py +++ b/interpretation/deepseismic_interpretation/dutchf3/data.py @@ -6,6 +6,10 @@ import segyio from os import path import scipy +from cv_lib.utils import generate_path, mask_to_disk, image_to_disk + +from matplotlib import pyplot as plt +from PIL import Image # bugfix for scipy imports import scipy.misc @@ -17,7 +21,7 @@ from deepseismic_interpretation.dutchf3.utils.batch import ( interpolate_to_fit_data, parse_labels_in_image, - get_coordinates_for_slice + get_coordinates_for_slice, ) @@ -118,16 +122,10 @@ class SectionLoader(data.Dataset): :param str split: split file to use for loading patches :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches + :param bool debug: enable debugging output """ - def __init__( - self, - data_dir, - n_classes, - split="train", - is_transform=True, - augmentations=None - ): + def __init__(self, data_dir, n_classes, split="train", is_transform=True, augmentations=None, debug=False): self.split = split self.data_dir = data_dir self.is_transform = is_transform @@ -179,6 +177,7 @@ class TrainSectionLoader(SectionLoader): :param list augmentations: Data augmentations to apply to patches :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data + :param bool debug: enable debugging output """ def __init__( @@ -190,6 +189,7 @@ def __init__( augmentations=None, seismic_path=None, label_path=None, + debug=False, ): super(TrainSectionLoader, self).__init__( data_dir, @@ -199,6 +199,7 @@ def __init__( augmentations=augmentations, seismic_path=seismic_path, label_path=label_path, + debug=debug, ) if seismic_path is not None and label_path is not None: @@ -231,6 +232,7 @@ class TrainSectionLoaderWithDepth(TrainSectionLoader): :param list augmentations: Data augmentations to apply to patches :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data + :param bool debug: enable debugging output """ def __init__( @@ -242,6 +244,7 @@ def __init__( augmentations=None, seismic_path=None, label_path=None, + debug=False, ): super(TrainSectionLoaderWithDepth, self).__init__( data_dir, @@ -251,6 +254,7 @@ def __init__( augmentations=augmentations, seismic_path=seismic_path, label_path=label_path, + debug=debug, ) self.seismic = add_section_depth_channels(self.seismic) # NCWH @@ -292,6 +296,7 @@ class TestSectionLoader(SectionLoader): :param list augmentations: Data augmentations to apply to patches :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data + :param bool debug: enable debugging output """ def __init__( @@ -303,9 +308,10 @@ def __init__( augmentations=None, seismic_path=None, label_path=None, + debug=False, ): super(TestSectionLoader, self).__init__( - data_dir, n_classes, split=split, is_transform=is_transform, augmentations=augmentations + data_dir, n_classes, split=split, is_transform=is_transform, augmentations=augmentations, debug=debug, ) if "test1" in self.split: @@ -342,6 +348,7 @@ class TestSectionLoaderWithDepth(TestSectionLoader): :param list augmentations: Data augmentations to apply to patches :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data + :param bool debug: enable debugging output """ def __init__( @@ -353,6 +360,7 @@ def __init__( augmentations=None, seismic_path=None, label_path=None, + debug=False, ): super(TestSectionLoaderWithDepth, self).__init__( data_dir, @@ -362,6 +370,7 @@ def __init__( augmentations=augmentations, seismic_path=seismic_path, label_path=label_path, + debug=debug, ) self.seismic = add_section_depth_channels(self.seismic) # NCWH @@ -408,18 +417,11 @@ class PatchLoader(data.Dataset): :param str split: split file to use for loading patches :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches - :param str seismic_path: Override file path for seismic data - :param str label_path: Override file path for label data + :param bool debug: enable debugging output """ def __init__( - self, - data_dir, - n_classes, - stride=30, - patch_size=99, - is_transform=True, - augmentations=None + self, data_dir, n_classes, stride=30, patch_size=99, is_transform=True, augmentations=None, debug=False, ): self.data_dir = data_dir self.is_transform = is_transform @@ -428,6 +430,7 @@ def __init__( self.patches = list() self.patch_size = patch_size self.stride = stride + self.debug=debug def pad_volume(self, volume): """ @@ -445,8 +448,7 @@ def __getitem__(self, index): # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 - # TODO: Remember we are cancelling the shift since we no longer pad - # issue: https://github.com/microsoft/seismic-deeplearning/issues/273 + # Remember we are cancelling the shift since we no longer pad shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift @@ -463,6 +465,13 @@ def __getitem__(self, index): augmented_dict = self.augmentations(image=im, mask=lbl) im, lbl = augmented_dict["image"], augmented_dict["mask"] + # dump images and labels to disk + if self.debug: + outdir = f"patchLoader_{self.split}_{'aug' if self.augmentations is not None else 'noaug'}" + generate_path(outdir) + image_to_disk(im, f"{outdir}/{index}_img.png") + mask_to_disk(lbl, f"{outdir}/{index}_lbl.png") + if self.is_transform: im, lbl = self.transform(im, lbl) return im, lbl @@ -484,9 +493,12 @@ class TestPatchLoader(PatchLoader): :param int patch_size: Size of patch for training :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches + :param bool debug: enable debugging output """ - def __init__(self, data_dir, n_classes, stride=30, patch_size=99, is_transform=True, augmentations=None): + def __init__( + self, data_dir, n_classes, stride=30, patch_size=99, is_transform=True, augmentations=None, debug=False + ): super(TestPatchLoader, self).__init__( data_dir, n_classes, @@ -494,20 +506,13 @@ def __init__(self, data_dir, n_classes, stride=30, patch_size=99, is_transform=T patch_size=patch_size, is_transform=is_transform, augmentations=augmentations, + debug=debug, ) ## Warning: this is not used or tested raise NotImplementedError("This class is not correctly implemented.") self.seismic = np.load(_train_data_for(self.data_dir)) self.labels = np.load(_train_labels_for(self.data_dir)) - # We are in test mode. Only read the given split. The other one might not - # be available. - # If txt_path is not provided, it will be assumed as below. Otherwise, provided path will be used for - # loading txt file and create patches. - if not txt_path: - self.split = "test1" # TODO: Fix this can also be test2 - # issue: https://github.com/microsoft/seismic-deeplearning/issues/274 - txt_path = path.join(self.data_dir, "splits", "patch_" + self.split + ".txt") patch_list = tuple(open(txt_path, "r")) patch_list = [id_.rstrip() for id_ in patch_list] self.patches = patch_list @@ -522,8 +527,7 @@ class TrainPatchLoader(PatchLoader): :param str split: split file to use for loading patches :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches - :param str seismic_path: Override file path for seismic data - :param str label_path: Override file path for label data + :param bool debug: enable debugging output """ def __init__( @@ -537,6 +541,7 @@ def __init__( augmentations=None, seismic_path=None, label_path=None, + debug=False, ): super(TrainPatchLoader, self).__init__( data_dir, @@ -544,7 +549,8 @@ def __init__( stride=stride, patch_size=patch_size, is_transform=is_transform, - augmentations=augmentations + augmentations=augmentations, + debug=debug, ) warnings.warn("This no longer pads the volume") @@ -579,8 +585,7 @@ class TrainPatchLoaderWithDepth(TrainPatchLoader): :param str split: split file to use for loading patches :param bool is_transform: Transform patch to dimensions expected by PyTorch :param list augmentations: Data augmentations to apply to patches - :param str seismic_path: Override file path for seismic data - :param str label_path: Override file path for label data + :param bool debug: enable debugging output """ def __init__( @@ -593,6 +598,7 @@ def __init__( augmentations=None, seismic_path=None, label_path=None, + debug=False, ): super(TrainPatchLoaderWithDepth, self).__init__( data_dir, @@ -603,6 +609,7 @@ def __init__( augmentations=augmentations, seismic_path=seismic_path, label_path=label_path, + debug=debug, ) def __getitem__(self, index): @@ -612,8 +619,7 @@ def __getitem__(self, index): # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 - # TODO: Remember we are cancelling the shift since we no longer pad - # issue https://github.com/microsoft/seismic-deeplearning/issues/273 + # Remember we are cancelling the shift since we no longer pad shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift @@ -625,8 +631,6 @@ def __getitem__(self, index): lbl = self.labels[idx : idx + self.patch_size, xdx, ddx : ddx + self.patch_size] im, lbl = _transform_WH_to_HW(im), _transform_WH_to_HW(lbl) - # TODO: Add check for rotation augmentations and raise warning if found - # issue: https://github.com/microsoft/seismic-deeplearning/issues/275 if self.augmentations is not None: augmented_dict = self.augmentations(image=im, mask=lbl) im, lbl = augmented_dict["image"], augmented_dict["mask"] @@ -657,6 +661,7 @@ class TrainPatchLoaderWithSectionDepth(TrainPatchLoader): :param list augmentations: Data augmentations to apply to patches :param str seismic_path: Override file path for seismic data :param str label_path: Override file path for label data + :param bool debug: enable debugging output """ def __init__( @@ -670,6 +675,7 @@ def __init__( augmentations=None, seismic_path=None, label_path=None, + debug=False, ): super(TrainPatchLoaderWithSectionDepth, self).__init__( data_dir, @@ -681,6 +687,7 @@ def __init__( augmentations=augmentations, seismic_path=seismic_path, label_path=label_path, + debug=debug, ) self.seismic = add_section_depth_channels(self.seismic) @@ -691,8 +698,7 @@ def __getitem__(self, index): # Shift offsets the padding that is added in training # shift = self.patch_size if "test" not in self.split else 0 - # TODO: Remember we are cancelling the shift since we no longer pad - # issue: https://github.com/microsoft/seismic-deeplearning/issues/273 + # Remember we are cancelling the shift since we no longer pad shift = 0 idx, xdx, ddx = int(idx) + shift, int(xdx) + shift, int(ddx) + shift @@ -712,6 +718,13 @@ def __getitem__(self, index): im, lbl = augmented_dict["image"], augmented_dict["mask"] im = _transform_HWC_to_CHW(im) + # dump images and labels to disk + if self.debug: + outdir = f"patchLoaderWithSectionDepth_{self.split}_{'aug' if self.augmentations is not None else 'noaug'}" + generate_path(outdir) + image_to_disk(im[0,:,:], f"{outdir}/{index}_img.png") + mask_to_disk(lbl, f"{outdir}/{index}_lbl.png") + if self.is_transform: im, lbl = self.transform(im, lbl) return im, lbl @@ -796,32 +809,3 @@ def add_section_depth_channels(sections_numpy): image[1, :, :, row] = const image[2] = image[0] * image[1] return np.swapaxes(image, 0, 1) - - -def get_seismic_labels(): - return np.asarray( - [[69, 117, 180], [145, 191, 219], [224, 243, 248], [254, 224, 144], [252, 141, 89], [215, 48, 39]] - ) - - -@curry -def decode_segmap(label_mask, n_classes=None, label_colours=get_seismic_labels()): - """Decode segmentation class labels into a colour image - Args: - label_mask (np.ndarray): an (N,H,W) array of integer values denoting - the class label at each spatial location. - Returns: - (np.ndarray): the resulting decoded color image (NCHW). - """ - r = label_mask.copy() - g = label_mask.copy() - b = label_mask.copy() - for ll in range(0, n_classes): - r[label_mask == ll] = label_colours[ll, 0] - g[label_mask == ll] = label_colours[ll, 1] - b[label_mask == ll] = label_colours[ll, 2] - rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], label_mask.shape[2], 3)) - rgb[:, :, :, 0] = r / 255.0 - rgb[:, :, :, 1] = g / 255.0 - rgb[:, :, :, 2] = b / 255.0 - return np.transpose(rgb, (0, 3, 1, 2)) diff --git a/tests/cicd/src/check_performance.py b/tests/cicd/src/check_performance.py index 5df69dda..925e6ad6 100644 --- a/tests/cicd/src/check_performance.py +++ b/tests/cicd/src/check_performance.py @@ -50,12 +50,12 @@ def main(args): else: # process validation results - assert data['Pixelwise Accuracy :'] > 0.0 - assert data['Pixelwise Accuracy :'] <= 1.0 + assert data['pixacc'] > 0.0 + assert data['pixacc'] <= 1.0 # TODO make these into proper tests - # assert data['Pixelwise Accuracy :'] == 1.0 + # assert data['pixacc'] == 1.0 # TODO: add more tests as we fix performance - # assert data['Avg loss :'] < 1e-3 + # assert data['mIoU'] < 1e-3 logging.info("all done") From 168031f26399114c01b2bc63eb884424f119ae34 Mon Sep 17 00:00:00 2001 From: maxkazmsft Date: Mon, 11 May 2020 19:43:10 -0400 Subject: [PATCH 206/207] V0.2 release README update (#300) * updated readme for v0.2 release --- README.md | 102 ++++++++++++------------------------------------------ 1 file changed, 23 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 81f6920f..7100bc48 100644 --- a/README.md +++ b/README.md @@ -5,30 +5,31 @@ This repository shows you how to perform seismic imaging and interpretation on A The repository provides sample notebooks, data loaders for seismic data, utilities, and out-of-the-box ML pipelines, organized as follows: - **sample notebooks**: these can be found in the `examples` folder - they are standard Jupyter notebooks which highlight how to use the codebase by walking the user through a set of pre-made examples -- **experiments**: the goal is to provide runnable Python scripts that train and test (score) our machine learning models in the `experiments` folder. The models themselves are swappable, meaning a single train script can be used to run a different model on the same dataset by simply swapping out the configuration file which defines the model. Experiments are organized by model types and datasets - for example, "2D segmentation on Dutch F3 dataset", "2D segmentation on Penobscot dataset" and "3D segmentation on Penobscot dataset" are all different experiments. As another example, if one is swapping 2D segmentation models on the Dutch F3 dataset, one would just point the train and test scripts to a different configuration file within the same experiment. +- **experiments**: the goal is to provide runnable Python scripts that train and test (score) our machine learning models in the `experiments` folder. The models themselves are swappable, meaning a single train script can be used to run a different model on the same dataset by simply swapping out the configuration file which defines the model. - **pip installable utilities**: we provide `cv_lib` and `deepseismic_interpretation` utilities (more info below) which are used by both sample notebooks and experiments mentioned above -DeepSeismic currently focuses on Seismic Interpretation (3D segmentation aka facies classification) with experimental code provided around Seismic Imaging. +DeepSeismic currently focuses on Seismic Interpretation (3D segmentation aka facies classification) with experimental code provided around Seismic Imaging in the contrib folder. ### Quick Start +Our repo is Docker-enabled and we provide a Docker file which you can use to quickly demo our codebase. If you are in a hurry and just can't wait to run our code, follow the [Docker README](https://github.com/microsoft/seismic-deeplearning/blob/master/docker/README.md) to build and run our repo from [Dockerfile](https://github.com/microsoft/seismic-deeplearning/blob/master/docker/Dockerfile). + +For developers, we offer a more hands-on Quick Start below. + +#### Dev Quick Start There are two ways to get started with the DeepSeismic codebase, which currently focuses on Interpretation: -- if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/master/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) +- if you'd like to get an idea of how our interpretation (segmentation) models are used, simply review the [HRNet demo notebook](https://github.com/microsoft/seismic-deeplearning/blob/master/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb) - to run the code, you'll need to set up a compute environment (which includes setting up a GPU-enabled Linux VM and downloading the appropriate Anaconda Python packages) and download the datasets which you'd like to work with - detailed steps for doing this are provided in the next `Interpretation` section below. If you run into any problems, chances are your problem has already been solved in the [Troubleshooting](#troubleshooting) section. -### Pre-run notebooks - -Notebooks stored in the repository have output intentionally displaced - you can find full auto-generated versions of the notebooks here: -- **HRNet Penobscot demo**: [[HTML](https://deepseismicstore.blob.core.windows.net/shared/HRNet_Penobscot_demo_notebook.html)] [[.ipynb](https://deepseismicstore.blob.core.windows.net/shared/HRNet_Penobscot_demo_notebook.ipynb)] -- **Dutch F3 dataset**: [[HTML](https://deepseismicstore.blob.core.windows.net/shared/F3_block_training_and_evaluation_local.html)] [[.ipynb](https://deepseismicstore.blob.core.windows.net/shared/F3_block_training_and_evaluation_local.ipynb)] +The notebook is designed to be run in demo mode by default using a pre-trained model in under 5 minutes on any reasonable Deep Learning GPU such as nVidia K80/P40/P100/V100/TitanV. ### Azure Machine Learning [Azure Machine Learning](https://docs.microsoft.com/en-us/azure/machine-learning/) enables you to train and deploy your machine learning models and pipelines at scale, ane leverage open-source Python frameworks, such as PyTorch, TensorFlow, and scikit-learn. If you are looking at getting started with using the code in this repository with Azure Machine Learning, refer to [Azure Machine Learning How-to](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml) to get started. ## Interpretation -For seismic interpretation, the repository consists of extensible machine learning pipelines, that shows how you can leverage state-of-the-art segmentation algorithms (UNet, SEResNET, HRNet) for seismic interpretation, and also benchmarking results from running these algorithms using various seismic datasets (Dutch F3, and Penobscot). +For seismic interpretation, the repository consists of extensible machine learning pipelines, that shows how you can leverage state-of-the-art segmentation algorithms (UNet, SEResNET, HRNet) for seismic interpretation. To run examples available on the repo, please follow instructions below to: 1) [Set up the environment](#setting-up-environment) @@ -79,36 +80,13 @@ from the root of DeepSeismic repo. ### Dataset download and preparation -This repository provides examples on how to run seismic interpretation on two publicly available annotated seismic datasets: [Penobscot](https://zenodo.org/record/1341774) and [F3 Netherlands](https://github.com/yalaudah/facies_classification_benchmark). Their respective sizes (uncompressed on disk in your folder after downloading and pre-processing) are: -- **Penobscot**: 7.9 GB -- **Dutch F3**: 2.2 GB +This repository provides examples on how to run seismic interpretation on Dutch F3 publicly available annotated seismic dataset [Dutch F3](https://github.com/yalaudah/facies_classification_benchmark), which is about 2.2GB in size. Please make sure you have enough disk space to download either dataset. -We have experiments and notebooks which use either one dataset or the other. Depending on which experiment/notebook you want to run you'll need to download the corresponding dataset. We suggest you start by looking at [HRNet demo notebook](https://github.com/microsoft/DeepSeismic/blob/master/examples/interpretation/notebooks/HRNet_Penobscot_demo_notebook.ipynb) which requires the Penobscot dataset. - -#### Penobscot -To download the Penobscot dataset run the [download_penobscot.sh](scripts/download_penobscot.sh) script, e.g. - -``` -data_dir="$HOME/data/penobscot" -mkdir -p "$data_dir" -./scripts/download_penobscot.sh "$data_dir" -``` - -Note that the specified download location should be configured with appropriate `write` permissions. On some Linux virtual machines, you may want to place the data into `/mnt` or `/data` folder so you have to make sure you have write access. - -To make things easier, we suggested you use your home directory where you might run out of space. If this happens on an [Azure Data Science Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/) you can resize the disk quite easily from [Azure Portal](https://portal.azure.com) - please see the [Troubleshooting](#troubleshooting) section at the end of this README regarding [how to do this](#how-to-resize-data-science-virtual-machine-disk). - -To prepare the data for the experiments (e.g. split into train/val/test), please run the following script (modifying arguments as desired): - -``` -cd scripts -python prepare_penobscot.py split_inline --data-dir=$data_dir --val-ratio=.1 --test-ratio=.2 -cd .. -``` +We have experiments and notebooks which use either one dataset or the other. Depending on which experiment/notebook you want to run you'll need to download the corresponding dataset. We suggest you start by looking at [HRNet demo notebook](https://github.com/microsoft/seismic-deeplearning/blob/master/examples/interpretation/notebooks/Dutch_F3_patch_model_training_and_evaluation.ipynb) which requires the Dutch F3 dataset. -#### F3 Netherlands +#### Dutch F3 Netherlands dataset prep To download the F3 Netherlands dataset for 2D experiments, please follow the data download instructions at [this github repository](https://github.com/yalaudah/facies_classification_benchmark) (section Dataset). Atternatively, you can use the [download script](scripts/download_dutch_f3.sh) @@ -141,10 +119,6 @@ To prepare the data for the experiments (e.g. split into train/val/test), please # change working directory to scripts folder cd scripts -# For section-based experiments -python prepare_dutchf3.py split_train_val section --data_dir=${data_dir} --label_file=train/train_labels.npy --output_dir=splits --split_direction=both - - # For patch-based experiments python prepare_dutchf3.py split_train_val patch --data_dir=${data_dir} --label_file=train/train_labels.npy --output_dir=splits \ --stride=50 --patch_size=100 --split_direction=both @@ -177,12 +151,10 @@ This will enable your notebook with a Black formatter button, which then clicked #### Experiments -We also provide scripts for a number of experiments we conducted using different segmentation approaches. These experiments are available under `experiments/interpretation`, and can be used as examples. Within each experiment start from the `train.sh` and `test.sh` scripts under the `local/` (single GPU) and `distributed/` (multiple GPUs) directories, which invoke the corresponding python scripts, `train.py` and `test.py`. Take a look at the experiment configurations (see Experiment Configuration Files section below) for experiment options and modify if necessary. +We also provide scripts for a number of experiments we conducted using different segmentation approaches. These experiments are available under `experiments/interpretation`, and can be used as examples. Within each experiment start from the `train.sh` and `test.sh` scripts under the `local/` directory, which invoke the corresponding python scripts, `train.py` and `test.py`. Take a look at the experiment configurations (see Experiment Configuration Files section below) for experiment options and modify if necessary. -Please refer to individual experiment README files for more information. -- [Penobscot](experiments/interpretation/penobscot/README.md) +This release currently supports Dutch F3 local execution - [F3 Netherlands Patch](experiments/interpretation/dutchf3_patch/README.md) -- [F3 Netherlands Section](experiments/interpretation/dutchf3_section/README.md) #### Configuration Files We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manage configuration options for the experiments. There are three ways to pass arguments to the experiment scripts (e.g. train.py or test.py): @@ -204,9 +176,13 @@ We use [YACS](https://github.com/rbgirshick/yacs) configuration library to manag ### Pretrained Models -#### HRNet +There are two types of pre-trained models used by this repo: +1. pre-trained models trained on non-seismic Computer Vision datasets which we fine-tune for the seismic domain through re-training on seismic data +2. models which we already trained on seismic data - these are downloaded automatically by our code if needed (again, please see the notebook for a demo above regarding how this is done). + +#### HRNet ImageNet weights model -To achieve the same results as the benchmarks above you will need to download the HRNet model [pretrained](https://github.com/HRNet/HRNet-Image-Classification) on ImageNet. We are specifically using the [HRNet-W48-C](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk) pre-trained model; other HRNet variants are also available [here](https://github.com/HRNet/HRNet-Image-Classification) - you can navigate to those from the [main HRNet landing page](https://github.com/HRNet/HRNet-Object-Detection) for object detection. +To enable training from scratch on seismic data and to achieve the same results as the benchmarks quoted below you will need to download the HRNet model [pretrained](https://github.com/HRNet/HRNet-Image-Classification) on ImageNet. We are specifically using the [HRNet-W48-C](https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk) pre-trained model; other HRNet variants are also available [here](https://github.com/HRNet/HRNet-Image-Classification) - you can navigate to those from the [main HRNet landing page](https://github.com/HRNet/HRNet-Object-Detection) for object detection. Unfortunately, the OneDrive location which is used to host the model is using a temporary authentication token, so there is no way for us to script up model download. There are two ways to upload and use the pre-trained HRNet model on DS VM: - download the model to your local drive using a web browser of your choice and then upload the model to the DS VM using something like `scp`; navigate to Portal and copy DS VM's public IP from the Overview panel of your DS VM (you can search your DS VM by name in the search bar of the Portal) then use `scp local_model_location username@DS_VM_public_IP:./model/save/path` to upload @@ -238,11 +214,9 @@ segyviewer "${HOME}/data/dutchf3/data.segy" #### Dense Labels -This section contains benchmarks of different algorithms for seismic interpretation on 3D seismic datasets with densely-annotated data. +This section contains benchmarks of different algorithms for seismic interpretation on 3D seismic datasets with densely-annotated data. We currently only support single-GPU Dutch F3 dataset benchmarks with this release. -Below are the results from the models contained in this repo. To run them check the instructions in folder. Alternatively, take a look in for how to run them on your own dataset - -#### Netherlands F3 +#### Dutch F3 | Source | Experiment | PA | FW IoU | MCA | V100 (16GB) training time | | -------------- | --------------------------- | ----- | ------ | ---- | ------------------------- | @@ -253,38 +227,12 @@ Below are the results from the models contained in this repo. To run them check | | HRNet(patch)+patch_depth | .884 | .795 | .739 | 67h 41min | | | HRNet(patch)+section_depth | .900 | .820 | .767 | 55h 08min | -#### Penobscot - -Trained and tested on the full dataset. Inlines with artifacts were left in for training, validation and testing. -The dataset was split 70% training, 10% validation and 20% test. The results below are from the test set - -| Source | Experiment | PA | mIoU | MCA | V100 (16GB) training time | -| ----------- | ----------------------------- | ---- | ---- | --- | ------------------------- | -| DeepSeismic | SEResNet UNet + section depth | 0.72 | .35 | .47 | 92h 59min | -| | HRNet(patch) + section depth | 0.91 | .75 | .85 | 80h 50min | - -![Best Penobscot SEResNet](assets/penobscot_seresnet_best.png "Best performing inlines, Mask and Predictions from SEResNet") -![Worst Penobscot SEResNet](assets/penobscot_seresnet_worst.png "Worst performing inlines Mask and Predictions from SEResNet") #### Reproduce benchmarks In order to reproduce the benchmarks, you will need to navigate to the [experiments](experiments) folder. In there, each of the experiments are split into different folders. To run the Netherlands F3 experiment navigate to the [dutchf3_patch/local](experiments/dutchf3_patch/local) folder. In there is a training script [([train.sh](experiments/dutchf3_patch/local/train.sh)) which will run the training for any configuration you pass in. Once you have run the training you will need to run the [test.sh](experiments/dutchf3_patch/local/test.sh) script. Make sure you specify the path to the best performing model from your training run, either by passing it in as an argument or altering the YACS config file. -To reproduce the benchmarks -for the Penobscot dataset follow the same instructions but navigate to the [penobscot](penobscot) folder. - -#### Scripts -- [parallel_training.sh](scripts/parallel_training.sh): Script to launch multiple jobs in parallel. Used mainly for local hyperparameter tuning. Look at the script for further instructions - -- [kill_windows.sh](scripts/kill_windows.sh): Script to kill multiple tmux windows. Used to kill jobs that parallel_training.sh might have started. - -- [run_all.sh](scripts/run_all.sh): similar to `parallel_training.sh` above, provides a multiprocess execution on an ND40 VM with 8 GPUs. Designed to work with `test_all.sh` script below. Trains 8 models concurrently. - -- [run_distributed.sh](scripts/run_distributed.sh): sequentially launches distributed training jobs, which should produce the same results on Dutch F3 dataset with patch based methods as the single-GPU training (just takes less time per model to train). Also designed to work with `test_all.sh` script below. - -- [test_all.sh](scripts/test_all.sh): after running `run_all.sh` and `run_distributed.sh` scripts above, this script scores single-GPU-trained and multi-GPU-trained models in the repo to reproduce the results given in the table. - ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [https://cla.opensource.microsoft.com](https://cla.opensource.microsoft.com). @@ -304,10 +252,6 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope | **Legal Compliance** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.ComponentGovernance%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=124&branchName=master) | | **Core Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=staging) | | **Core Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Tests%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=126&branchName=master) | -| **Notebook Tests** | staging | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=staging)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=staging) | -| **Notebook Tests** | master | [![Build Status](https://dev.azure.com/best-practices/deepseismic/_apis/build/status/microsoft.Notebooks%20(seismic-deeplearning)?branchName=master)](https://dev.azure.com/best-practices/deepseismic/_build/latest?definitionId=125&branchName=master) | -| **Azure ML Tests** | staging | TODO add badge link | -| **Azure ML Tests** | master | TODO add badge link | # Troubleshooting From 9ded9a5b5fdd47cbf77f105bc4b5d9892155c27a Mon Sep 17 00:00:00 2001 From: yalaudah Date: Tue, 19 May 2020 22:05:00 +0000 Subject: [PATCH 207/207] bug fix (#296) --- docker/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7e05f919..22d50ef4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,8 +13,6 @@ ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 ENV PATH /opt/conda/bin:$PATH SHELL ["/bin/bash", "-c"] -WORKDIR /home/username - # Install Anaconda and download the seismic-deeplearning repo RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \ /bin/bash ~/miniconda.sh -b -p /opt/conda && \