From a8c74806bffce7c3e5c67ecaf99c4b08f3d6c63e Mon Sep 17 00:00:00 2001 From: balbasty Date: Thu, 23 Jan 2025 11:31:53 +0000 Subject: [PATCH 1/6] FEAT(F.intensity): functional form of intensity transforms --- cornucopia/functional/__init__.py | 0 cornucopia/functional/intensity.py | 1310 ++++++++++++++++++++++++++++ 2 files changed, 1310 insertions(+) create mode 100644 cornucopia/functional/__init__.py create mode 100644 cornucopia/functional/intensity.py diff --git a/cornucopia/functional/__init__.py b/cornucopia/functional/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cornucopia/functional/intensity.py b/cornucopia/functional/intensity.py new file mode 100644 index 0000000..571969d --- /dev/null +++ b/cornucopia/functional/intensity.py @@ -0,0 +1,1310 @@ +__all__ = [ + "add_value", + "sub_value", + "mul_value", + "div_value", + "addmul_value", + "fill_value", + "clip_value", + "add_field", + "sub_field", + "mul_field", + "div_field", + "spline_upsample", + "spline_upsample_like", + "gamma_transform", + "z_transform", + "quantile_transform", + "affine_intensity_transform", + "random_field_uniform", + "random_field_gaussian", + "random_field_lognormal", + "random_field_uniform_like", + "random_field_gaussian_like", + "random_field_lognormal_like", +] +# stdlib +from typing import Union, Mapping, Sequence, Optional, Callable + +# external +import torch +import interpol +import torch.nn.functional as F + +# internal +from ..baseutils import prepare_output, returns_update, return_requires +from ..utils.smart_inplace import add_, mul_, pow_, div_, exp_ + + +Tensor = torch.Tensor +Value = Union[float, Tensor] +Output = Union[Tensor, Mapping[Tensor], Sequence[Tensor]] + + +def _unsqz_spatial(x: Value, ndim: int) -> Value: + if torch.is_tensor(x): + x = x[(Ellipsis,) + (None,) * ndim] + return x + + +def binop_value( + op: Callable[[Tensor, Value], Output], + input: Tensor, + value: Value, + **kwargs +) -> Output: + """ + Add a value to the input. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + value : float | ([C],) tensor + Input value. + It can have multiple channels but no spatial dimensions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "value"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + output = op(input, _unsqz_spatial(value, input.ndim - 1)) + kwargs.setdefault("value_name", "value") + kwargs.setdefault("returns", "output") + return prepare_output( + {"input": input, "output": output, kwargs["value"]: value}, + kwargs["returns"] + ) + + +def add_value(input: Tensor, value: Value, **kwargs) -> Output: + """ + Add a value to the input. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + value : float | ([C],) tensor + Input value. + It can have multiple channels but no spatial dimensions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "value"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + return binop_value(torch.add, input, value, **kwargs) + + +def sub_value(input: Tensor, value: Value, **kwargs) -> Output: + """ + Subtract a value to the input. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + value : float | ([C],) tensor + Input value. + It can have multiple channels but no spatial dimensions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "value"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + return binop_value(torch.sub, input, value, **kwargs) + + +def mul_value(input: Tensor, value: Value, **kwargs) -> Output: + """ + Multiply the input with a value. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + value : float | ([C],) tensor + Input value. + It can have multiple channels but no spatial dimensions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "value"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + return binop_value(torch.mul, input, value, **kwargs) + + +def div_value(input: Tensor, value: Value, **kwargs) -> Output: + """ + Divide the input by a value. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + value : float | ([C],) tensor + Input value. + It can have multiple channels but no spatial dimensions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "value"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + return binop_value(torch.div, input, value, **kwargs) + + +def addmul_value( + input: Tensor, scale: Value, offset: Value, **kwargs +) -> Output: + """ + Affine transform of the input values: `output = input * scale + offset`. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + scale : float | ([C],) tensor + Input scale. + It can have multiple channels but no spatial dimensions. + offset : float | ([C],) tensor + Input offset. + It can have multiple channels but no spatial dimensions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "scale", "offset"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + output = ( + input * + _unsqz_spatial(scale, input.ndim - 1) + + _unsqz_spatial(offset, input.ndim - 1) + ) + kwargs.setdefault("scale_name", "scale") + kwargs.setdefault("offset_name", "offset") + kwargs.setdefault("returns", "output") + return prepare_output({ + "input": input, + "output": output, + kwargs["scale_name"]: scale, + kwargs["offset_name"]: offset, + }, kwargs["returns"]) + + +def binop_field( + op: Callable[[Tensor, Tensor], Output], + input: Tensor, + field: Tensor, + order: int = 3, + prefilter: bool = True, + **kwargs +) -> Output: + """ + Apply a binary operation between the input and a field. + + The field gets resized to the input's shape if needed. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + field : ([C], *sptial) tensor + Input field. It must have spatial dimensions. + order : int + Spline order, if the field needs to be upsampled. + prefilter : bool + If `False`, assume that the input contains spline coefficients, + and returns the interpolated field. + If `True`, assume that the input contains low-resolution values + and convert them first to spline coefficients (= "prefilter"), + before computing the interpolated field. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "field", "input_field"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + # NOTE: if `field` already has the correct size and does not contain + # spline coefficients, `spline_upsample_like` does nothing + # and returns the input field as is. + input_field = field + field = spline_upsample_like(field, input, order, prefilter, copy=False) + output = op(input, field) + + kwargs.setdefault("field_name", "field") + kwargs.setdefault("returns", "output") + return prepare_output({ + "input": input, + "output": output, + kwargs["field_name"]: field, + "input_" + kwargs["field_name"]: input_field + }, kwargs["returns"]) + + +def add_field( + input: Tensor, + field: Tensor, + order: int = 3, + prefilter: bool = True, + **kwargs +) -> Output: + """ + Add a field to the input. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + field : ([C], *sptial) tensor + Input field. It must have spatial dimensions. + order : int + Spline order, if the field needs to be upsampled. + prefilter : bool + If `False`, assume that the input contains spline coefficients, + and returns the interpolated field. + If `True`, assume that the input contains low-resolution values + and convert them first to spline coefficients (= "prefilter"), + before computing the interpolated field. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "field", "input_field"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + return binop_field(torch.add, input, field, order, prefilter, **kwargs) + + +def sub_field( + input: Tensor, + field: Tensor, + order: int = 3, + prefilter: bool = True, + **kwargs +) -> Output: + """ + Subtract a field to the input. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + field : ([C], *sptial) tensor + Input field. It must have spatial dimensions. + order : int + Spline order, if the field needs to be upsampled. + prefilter : bool + If `False`, assume that the input contains spline coefficients, + and returns the interpolated field. + If `True`, assume that the input contains low-resolution values + and convert them first to spline coefficients (= "prefilter"), + before computing the interpolated field. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "field", "input_field"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + return binop_field(torch.sub, input, field, order, prefilter, **kwargs) + + +def mul_field( + input: Tensor, + field: Tensor, + order: int = 3, + prefilter: bool = True, + **kwargs +) -> Output: + """ + Multiply athe inout with a field. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + field : ([C], *sptial) tensor + Input field. It must have spatial dimensions. + order : int + Spline order, if the field needs to be upsampled. + prefilter : bool + If `False`, assume that the input contains spline coefficients, + and returns the interpolated field. + If `True`, assume that the input contains low-resolution values + and convert them first to spline coefficients (= "prefilter"), + before computing the interpolated field. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "field", "input_field"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + return binop_field(torch.mul, input, field, order, prefilter, **kwargs) + + +def div_field( + input: Tensor, + field: Tensor, + order: int = 3, + prefilter: bool = True, + **kwargs +) -> Output: + """ + Divide the input by a field. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + field : ([C], *sptial) tensor + Input field. It must have spatial dimensions. + order : int + Spline order, if the field needs to be upsampled. + prefilter : bool + If `False`, assume that the input contains spline coefficients, + and returns the interpolated field. + If `True`, assume that the input contains low-resolution values + and convert them first to spline coefficients (= "prefilter"), + before computing the interpolated field. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "field", "input_field"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + return binop_field(torch.div, input, field, order, prefilter, **kwargs) + + +def fill_value(input: Tensor, mask: Tensor, value: Value, **kwargs) -> Output: + """ + Set a value at masked locations. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + mask : ([C], *spatial) tensor + Input mask. + value : float | ([C],) tensor + Input value. + If `mask` has a channel dimension, must be a scalar. + Otherwise, can be a vetor of length `C`. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "mask", "value"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + # Multiple value case -- must fill one channel at a time. + if torch.is_tensor(value) and len(value) > 1: + + # Checks + if mask.ndim == input.ndim and len(mask) > 1: + raise ValueError( + "If mask has a channel dimension, value must be a scalar." + ) + if len(value) != len(input): + raise ValueError( + "Number of values does not match the number of channels." + ) + if mask.ndim == input.ndim: + mask_nochannel = mask.squeeze(0) + else: + mask_nochannel = mask + + # Fill per channel + output = input.clone() + for c in range(len(input)): + output[c].masked_fill_(mask_nochannel, value[c]) + + # Single value case -- can use `masked_fill`` out-of-the-box + else: + output = input.masked_fill(mask, value) + + kwargs.setdefault("value_name", "value") + kwargs.setdefault("mask_name", "mask") + kwargs.setdefault("returns", "output") + return prepare_output({ + "input": input, + "output": output, + kwargs["value_name"]: value, + kwargs["mask_name"]: mask, + }, kwargs["returns"]) + + +def clip_value( + input: Tensor, + vmin: Optional[Value] = None, + vmax: Optional[Value] = None, + **kwargs, +) -> Output: + """ + Clip extreme values. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + vmin : float | ([C],) tensor + Minimum value. + It can have multiple channels but no spatial dimensions. + vmax : float | ([C],) tensor + Maximum value. + It can have multiple channels but no spatial dimensions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "vmin", "vmax"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + + """ + ndim = input.ndim - 1 + output = input.clip(_unsqz_spatial(vmin, ndim), _unsqz_spatial(vmax, ndim)) + kwargs.setdefault("returns", "output") + return prepare_output( + {"input": input, "output": output, "vmin": vmin, "vmax": vmax}, + kwargs["returns"] + ) + + +def spline_upsample( + input: Tensor, + shape: Sequence[int], + order: int = 3, + prefilter: bool = True, + copy: bool = True, + **kwargs +) -> Output: + """ + Upsample a field of spline coefficients. + + Parameters + ---------- + input : (C, *spatial) tensor + Input spline coefficients (or values if `prefilter=True`) + shape : list[int] + Target spatial shape + order : int + Spline order + prefilter : bool + If `False`, assume that the input contains spline coefficients, + and returns the interpolated field. + If `True`, assume that the input contains low-resolution values + and convert them first to spline coefficients (= "prefilter"), + before computing the interpolated field. + copy : bool + In cases where the output matches the input (the input and target + shapes are identical, and no prefilter is required), the input + tensor is returned when `copy=False`, and a copy is made when + `copy=True`. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "coeff"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *shape) tensor + Output tensor. + """ + returns = kwargs.pop("returns", "output") + + ndim = input.ndim - 1 + coeff = input + + same_shape = (tuple(shape) == input.shape[1:]) + nothing_to_do = same_shape and (prefilter or order <= 1) + need_prefilter = prefilter and (order > 1) + + # 1) Nothing to do + if nothing_to_do: + output = input.clone() if copy else input + if need_prefilter and ("coeff" in return_requires(returns)): + coeff = interpol.spline_coeff_nd(input, order, dim=ndim) + + # 2) Use torch.inteprolate (faster) + elif order == 1: + mode = ("trilinear" if len(shape) == 3 else + "bilinear" if len(shape) == 2 else + "linear") + output = F.interpolate( + input[None], shape, mode=mode, align_corners=True + )[0] + + # 3) Use interpol + else: + if prefilter: + coeff = interpol.spline_coeff_nd(input, order, dim=ndim) + output = interpol.resize( + coeff, shape=shape, interpolation=order, prefilter=False + ) + + return prepare_output( + {"input": input, "output": output, "coeff": coeff}, + returns + ) + + +def spline_upsample_like( + input: Tensor, + like: Tensor, + order: int = 3, + prefilter: bool = True, + copy: bool = True, + **kwargs +) -> Output: + """ + Upsample a field of spline coefficients. + + Parameters + ---------- + input : (C, *spatial) tensor + Input spline coefficients (or values if `prefilter=True`) + like : (C, *shape) tensor + Target tensor. + order : int + Spline order + prefilter : bool + If `False`, assume that the input contains spline coefficients, + and returns the interpolated field. + If `True`, assume that the input contains low-resolution values + and convert them first to spline coefficients (= "prefilter"), + before computing the interpolated field. + copy : bool + In cases where the output matches the input (the input and target + shapes are identical, and no prefilter is required), the input + tensor is returned when `copy=False`, and a copy is made when + `copy=True`. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "coeff", "like"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *shape) tensor + Output tensor. + + """ + kwargs.setdefault("returns", "output") + kwargs.setdefault("order", order) + kwargs.setdefault("prefilter", prefilter) + kwargs.setdefault("copy", copy) + output = spline_upsample(input, like.shape[1:], **kwargs) + output = returns_update(like, "like", output, kwargs["returns"]) + + +def gamma_transform( + input: Tensor, + gamma: Value = 1, + vmin: Optional[Value] = None, + vmax: Optional[Value] = None, + per_channel: bool = False, + **kwargs +) -> Output: + """ + Apply a Gamma transformation: + + ```python + rscled = (input - vmin) / (vmax - vmin) + xfrmed = rscled ** gamma + output = xfrmed * (vmax - vmin) + vmin + ``` + + Parameters + ---------- + input : tensor + Input tensor. + gamma : float | ([C],) tensor + Gamma coefficient. + It can have multiple channels but no spatial dimensions. + vmin : float | ([C],) tensor | None + Minimum value. + It can have multiple channels but no spatial dimensions. + If `None`, compute the input's minimum. + vmax : float | ([C],) tensor | None + Maximum value. + It can have multiple channels but no spatial dimensions. + If `None`, compute the input's maximum. + per_channel : bool + This parameter is only used when `vmin=None` or `vmax=None`. + If `True`, the min/max of each input channel is used. + If `False, the global min/max of the input tensor is used. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "gamma", "vmin", "vmax"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *shape) tensor + Output tensor. + """ + ndim = input.ndim - 1 + + if vmin is None: + if per_channel: + vmin = input.reshape([len(input), -1]).min(-1).values + else: + vmin = input.min() + + if vmax is None: + if per_channel: + vmax = input.reshape([len(input), -1]).max(-1).values + else: + vmax = input.max() + + vmin_ = _unsqz_spatial(vmin, ndim) + vmax_ = _unsqz_spatial(vmax, ndim) + gamma_ = _unsqz_spatial(gamma, ndim) + + output = div_((input - vmin_), (vmax_ - vmin_).clamp_min_(1e-8)) + output = pow_(output, gamma_) + if getattr(gamma_, 'requires_grad', False): + # When gamma requires grad, mul_(y, vmax-vmin) is happy + # to overwrite y, but we cant because we need y to + # backprop through pow. So we need an explicit branch. + output = output * (vmax_ - vmin_) + vmin_ + else: + output = add_(mul_(output, vmax_ - vmin_), vmin_) + + kwargs.setdefault("returns", "output") + return prepare_output({ + "input": input, + "output": output, + "vmin": vmin, + "vmax": vmax, + "gamma": gamma, + }, kwargs["returns"]) + + +def z_transform( + input: Tensor, + mu: Value = 0, + sigma: Value = 1, + per_channel: bool = False, + **kwargs +) -> Output: + """ + Apply a Z transformation: + + ```python + output = ((input - mean(input)) / std(input)) * sigma + mu + ``` + + Parameters + ---------- + input : tensor + Input tensor. + mu : float | ([C],) tensor + Target mean. + It can have multiple channels but no spatial dimensions. + sigma : float | ([C],) tensor + Target standard deviation. + It can have multiple channels but no spatial dimensions. + per_channel : bool + If `True`, compute the mean/std of each input channel. + If `False, the global mean/std of the input tensor is used. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "mu", "sigma"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *shape) tensor + Output tensor. + """ + ndim = input.ndim - 1 + + if per_channel: + mu0 = input.reshape([len(input), -1]).mean(-1) + else: + mu0 = input.mean() + + if per_channel: + sigma0 = input.reshape([len(input), -1]).std(-1) + else: + sigma0 = input.std() + + mu0 = _unsqz_spatial(mu0, ndim) + sigma0 = _unsqz_spatial(sigma0, ndim) + mu_ = _unsqz_spatial(mu, ndim) + sigma_ = _unsqz_spatial(sigma, ndim) + + output = div_((input - mu0), sigma0.clamp_min_(1e-8)) + output = add_(mul_(input, mu_), sigma_) + + kwargs.setdefault("returns", "output") + return prepare_output({ + "input": input, + "output": output, + "mu": mu, + "sigma": sigma, + }, kwargs["returns"]) + + +def quantile_transform( + input: Tensor, + pmin: Value = 0.01, + pmax: Value = 0.99, + vmin: Value = 0, + vmax: Value = 1, + per_channel: bool = False, + max_samples: Optional[int] = 10000, + **kwargs +) -> Output: + """ + Apply a quantile transformation: + + ```python + qmin = quantile(input, pmin) + qmax = quantile(input, pmax) + rscled = (input - pmin) / (pmax - pmin) + output = rscled * (vmax - vmin) + vmin + ``` + + Parameters + ---------- + input : tensor + Input tensor. + pmin : float | ([C],) tensor + Lower quantile. + It can have multiple channels but no spatial dimensions. + pmax : float | ([C],) tensor + Upper quantile. + It can have multiple channels but no spatial dimensions. + vmin : float | ([C],) tensor + Minimum output value. + It can have multiple channels but no spatial dimensions. + vmax : float | ([C],) tensor + Maximum output value. + It can have multiple channels but no spatial dimensions. + per_channel : bool + This parameter is only used when `vmin=None` or `vmax=None`. + If `True`, the qmin/qmax of each input channel is used. + If `False, the global qmin/qmax of the input tensor is used. + max_samples : int | None + Maximum number of samples to use to estimate quantiles. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "pmin", "pmax", "qmin", "qmax", "vmin", "vmax"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *shape) tensor + Output tensor. + + """ # noqa: E501 + ndim = input.ndim - 1 + C = len(input) + + # Select a subset of values to compute the quantiles + # (discard inf/nan/zeros + take random sample for speed) + input_ = input.reshape([len(input), -1]) + input_ = input_[:, (input_ != 0) & input_.isfinite()] + if (max_samples is not None) and (max_samples < input_.shape[1]): + index_ = torch.randperm(input_.shape[1], device=input_.device) + index_ = index_[:max_samples] + input_ = input_[:, index_] + + # Compute lower quantile + pmin_ = pmin + if torch.is_tensor(pmin_) and pmin_.shape: + pmin_ = torch.expand(pmin_, [len(input)]) + qmin = torch.stack([ + torch.quantile(input[c], pmin_[c]) for c in range(C) + ]) + else: + qdim = (-1 if per_channel else None) + qmin = torch.quantile(input_, pmin_, dim=qdim) + + # Compute upper quantile + pmax_ = pmax + if torch.is_tensor(pmax_) and pmax_.shape: + pmax_ = torch.expand(pmax_, [len(input)]) + qmax = torch.stack([ + torch.quantile(input[c], pmax_[c]) for c in range(C) + ]) + else: + qdim = (-1 if per_channel else None) + qmax = torch.quantile(input_, pmin_, dim=qdim) + + qmin_ = _unsqz_spatial(qmin, ndim) + qmax_ = _unsqz_spatial(qmax, ndim) + vmin_ = _unsqz_spatial(vmin, ndim) + vmax_ = _unsqz_spatial(vmax, ndim) + + # Transform + output = div_((input - qmin_), (qmax_ - qmin_).clamp_min_(1e-8)) + output = add_(mul_(output, vmax_ - vmin_), vmin_) + + kwargs.setdefault("returns", "output") + return prepare_output({ + "input": input, + "output": output, + "vmin": vmin, + "vmax": vmax, + "pmin": pmin, + "pmax": pmax, + "qmin": qmin, + "qmax": qmax, + }, kwargs["returns"]) + + +def affine_intensity_transform( + input: Tensor, + imin: Value, + imax: Value, + omin: Value = 0, + omax: Value = 1, + clip: bool = False, + **kwargs +) -> Output: + """ + Apply an affine transform that maps pairs of values: + + ```python + rscled = (input - imin) / (imax - imin) + output = rscled * (omax - omin) + omin + ``` + + Parameters + ---------- + input : tensor + Input tensor. + imin : float | ([C],) tensor + Minimum input value. + It can have multiple channels but no spatial dimensions. + imax : float | ([C],) tensor + Maximum input value. + It can have multiple channels but no spatial dimensions. + omin : float | ([C],) tensor + Minimum output value. + It can have multiple channels but no spatial dimensions. + omax : float | ([C],) tensor + Maximum output value. + It can have multiple channels but no spatial dimensions. + clip : bool + Clip values outside of the range. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "imin", "imax", "omin", "omax"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *shape) tensor + Output tensor. + + """ # noqa: E501 + ndim = input.ndim - 1 + + imin_ = _unsqz_spatial(imin, ndim) + imax_ = _unsqz_spatial(imax, ndim) + omin_ = _unsqz_spatial(omin, ndim) + omax_ = _unsqz_spatial(imax, ndim) + + # Transform + output = div_((input - imin_), (imax_ - imin_).clamp_min_(1e-8)) + output = add_(mul_(output, omax_ - omin_), omin_) + + if clip: + output = output.clip_(omin, omax) + + kwargs.setdefault("returns", "output") + return prepare_output({ + "input": input, + "output": output, + "imin": imin, + "imax": imax, + "omin": omin, + "omax": omax, + }, kwargs["returns"]) + + +def random_field_uniform( + shape: Sequence[int], + vmin: Value = 0, + vmax: Value = 1, + **kwargs +) -> Output: + """ + Sample a random field from a uniform distribution + + Parameters + ---------- + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + vmin : float | ([C],) tensor + Minimum value. + vmax : float | ([C],) tensor + Maximum value. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "vmin", "vmax"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + dtype = kwargs.get("dtype", vmin.get("dtype", vmax.get("dtype", None))) + device = kwargs.get("device", vmin.get("device", vmax.get("device", None))) + if not dtype or not dtype.is_floating_point: + dtype = torch.get_default_dtype() + + ndim = len(shape) - 1 + vmin_ = _unsqz_spatial(vmin, ndim) + vmax_ = _unsqz_spatial(vmax, ndim) + + output = torch.rand(shape, dtype=dtype, device=device) + output = add_(mul_(output, (vmax_ - vmin_)), vmin_) + + kwargs.setdefault("returns", "output") + return prepare_output({ + "output": output, + "vmin": vmin, + "vmax": vmax, + }, kwargs["returns"]) + + +def random_field_gaussian( + shape: Sequence[int], + mu: Value = 0, + sigma: Value = 1, + **kwargs +) -> Output: + """ + Sample a random field from a Gaussian distribution + + Parameters + ---------- + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + mu : float | ([C],) tensor + Mean. + sigma : float | ([C],) tensor + Standard deviation. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "mu", "sigma"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + dtype = kwargs.get("dtype", mu.get("dtype", sigma.get("dtype", None))) + device = kwargs.get("device", mu.get("device", sigma.get("device", None))) + if not dtype or not dtype.is_floating_point: + dtype = torch.get_default_dtype() + + ndim = len(shape) - 1 + mu_ = _unsqz_spatial(mu, ndim) + sigma_ = _unsqz_spatial(sigma, ndim) + + output = torch.randn(shape, dtype=dtype, device=device) + output = add_(mul_(output, sigma_), mu_) + + kwargs.setdefault("returns", "output") + return prepare_output({ + "output": output, + "mu": mu, + "sigma": sigma, + }, kwargs["returns"]) + + +def random_field_lognormal( + shape: Sequence[int], + mu: Value = 0, + sigma: Value = 1, + **kwargs +) -> Output: + """ + Sample a random field from a Gaussian distribution + + Parameters + ---------- + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + mu : float | ([C],) tensor + Mean of log. + sigma : float | ([C],) tensor + Standard deviation of log. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "mu", "sigma"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + dtype = kwargs.get("dtype", mu.get("dtype", sigma.get("dtype", None))) + device = kwargs.get("device", mu.get("device", sigma.get("device", None))) + if not dtype or not dtype.is_floating_point: + dtype = torch.get_default_dtype() + + ndim = len(shape) - 1 + mu_ = _unsqz_spatial(mu, ndim) + sigma_ = _unsqz_spatial(sigma, ndim) + + output = torch.randn(shape, dtype=dtype, device=device) + output = exp_(add_(mul_(output, sigma_), mu_)) + + kwargs.setdefault("returns", "output") + return prepare_output({ + "output": output, + "mu": mu, + "sigma": sigma, + }, kwargs["returns"]) + + +def _random_field_like( + func: Callable, + input: Tensor, + shape: Optional[Sequence[int]] = None, + *args, + **kwargs +) -> Output: + """ + Helper to sample a random field from a distribution + + Parameters + ---------- + func : callable + Sampling function + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + *args + `func`'s other parameters. + + Other Parameters + ---------------- + returns : [list or dict of] {"input", "output", ...} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + kwargs.setdefault("returns", "output") + + # copy shape + if shape is None: + shape = input.shape + shape = torch.Size(shape) + # if pure spatial shape, copy channels + if len(shape) == input.ndim - 1: + shape = input.shape[:1] + shape + + # copy dtype/device + dtype = kwargs.get("dtype", None) or input.dtype + device = kwargs.get("device", None) or input.device + if not dtype.is_floating_point: + dtype = torch.get_default_dtype() + kwargs["dtype"] = dtype + kwargs["device"] = device + + # sample field + output = func(shape, *args, **kwargs) + + return returns_update(input, "input", output, kwargs["returns"]) + + +def random_field_uniform_like( + input: Tensor, + shape: Optional[Sequence[int]] = None, + vmin: Value = 0, + vmax: Value = 1, + **kwargs +) -> Output: + """ + Sample a random field from a uniform distribution + + Parameters + ---------- + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + vmin : float | ([C],) tensor + Minimum value. + vmax : float | ([C],) tensor + Maximum value. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "vmin", "vmax"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + return _random_field_like( + random_field_uniform, input, shape, vmin, vmax, **kwargs + ) + + +def random_field_gaussian_like( + input: Tensor, + shape: Optional[Sequence[int]] = None, + mu: Value = 0, + sigma: Value = 1, + **kwargs +) -> Output: + """ + Sample a random field from a gaussian distribution + + Parameters + ---------- + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + mu : float | ([C],) tensor + Mean. + sigma : float | ([C],) tensor + Standard deviation. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "mu", "sigma"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + return _random_field_like( + random_field_gaussian, input, shape, mu, sigma, **kwargs + ) + + +def random_field_lognormal_like( + input: Tensor, + shape: Optional[Sequence[int]] = None, + mu: Value = 0, + sigma: Value = 1, + **kwargs +) -> Output: + """ + Sample a random field from a log-normal distribution + + Parameters + ---------- + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + mu : float | ([C],) tensor + Mean of log. + sigma : float | ([C],) tensor + Standard deviation of log. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "mu", "sigma"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + return _random_field_like( + random_field_lognormal, input, shape, mu, sigma, **kwargs + ) From 56da06fdafdd972bfe30f271105c6cc6f714528f Mon Sep 17 00:00:00 2001 From: balbasty Date: Fri, 24 Jan 2025 09:53:33 +0000 Subject: [PATCH 2/6] WIP --- cornucopia/__init__.py | 1 + cornucopia/functional/__init__.py | 5 + cornucopia/functional/_utils.py | 61 ++ cornucopia/functional/intensity.py | 404 ++------- cornucopia/functional/random.py | 764 ++++++++++++++++++ cornucopia/geometric.py | 2 +- cornucopia/intensity.py | 2 +- cornucopia/kspace.py | 2 +- cornucopia/labels.py | 2 +- cornucopia/noise.py | 2 +- cornucopia/qmri.py | 2 +- cornucopia/random.py | 2 +- cornucopia/utils/b0.py | 2 +- cornucopia/utils/distributions.py | 533 ++++++++++++ .../utils/{smart_inplace.py => smart_math.py} | 126 ++- 15 files changed, 1583 insertions(+), 327 deletions(-) create mode 100644 cornucopia/functional/_utils.py create mode 100644 cornucopia/functional/random.py create mode 100644 cornucopia/utils/distributions.py rename cornucopia/utils/{smart_inplace.py => smart_math.py} (60%) diff --git a/cornucopia/__init__.py b/cornucopia/__init__.py index 0edb41c..3fc8b23 100755 --- a/cornucopia/__init__.py +++ b/cornucopia/__init__.py @@ -31,6 +31,7 @@ """ +from . import functional # noqa: F401 from . import random # noqa: F401 from . import ctx # noqa: F401 from . import base # noqa: F401 diff --git a/cornucopia/functional/__init__.py b/cornucopia/functional/__init__.py index e69de29..a69a4bf 100644 --- a/cornucopia/functional/__init__.py +++ b/cornucopia/functional/__init__.py @@ -0,0 +1,5 @@ +from . import random # noqa: F401 +from . import intensity # noqa: F401 + +from .random import * # noqa: F401,F403 +from .intensity import * # noqa: F401,F403 diff --git a/cornucopia/functional/_utils.py b/cornucopia/functional/_utils.py new file mode 100644 index 0000000..8cec32b --- /dev/null +++ b/cornucopia/functional/_utils.py @@ -0,0 +1,61 @@ + +# stdlib +from typing import Union, Mapping, Sequence + +# external +import torch + + +Tensor = torch.Tensor +Value = Union[float, Tensor] +Output = Union[Tensor, Mapping[Tensor], Sequence[Tensor]] + + +def _unsqz_spatial(x: Value, ndim: int) -> Value: + if torch.is_tensor(x): + x = x[(Ellipsis,) + (None,) * ndim] + return x + + +def _backend( + *tensors_or_dtypes_or_devices, dtype=None, device=None, **kwargs +): + if dtype and device: + return + for tensor_or_dtype_or_device in tensors_or_dtypes_or_devices: + if torch.is_tensor(tensor_or_dtype_or_device): + dtype = dtype or tensor_or_dtype_or_device.dtype + device = device or tensor_or_dtype_or_device.device + elif isinstance(tensor_or_dtype_or_device, torch.device): + dtype = dtype or tensor_or_dtype_or_device + elif isinstance(tensor_or_dtype_or_device, torch.device): + device = device or tensor_or_dtype_or_device + elif isinstance(tensor_or_dtype_or_device, str): + device = device or torch.device(tensor_or_dtype_or_device) + if dtype and device: + return + return dict(dtype=dtype, device=device) + + +def _backend_float( + *tensors_or_dtypes_or_devices, dtype=None, device=None, **kwargs +): + if dtype and device: + return + for tensor_or_dtype_or_device in tensors_or_dtypes_or_devices: + if torch.is_tensor(tensor_or_dtype_or_device): + if tensor_or_dtype_or_device.dtype.is_floating_point: + dtype = dtype or tensor_or_dtype_or_device.dtype + device = device or tensor_or_dtype_or_device.device + elif isinstance(tensor_or_dtype_or_device, torch.device): + if tensor_or_dtype_or_device.is_floating_point: + dtype = dtype or tensor_or_dtype_or_device + elif isinstance(tensor_or_dtype_or_device, torch.device): + device = device or tensor_or_dtype_or_device + elif isinstance(tensor_or_dtype_or_device, str): + device = device or torch.device(tensor_or_dtype_or_device) + if dtype and device: + return + if dtype is None or not dtype.is_floating_point: + dtype = torch.get_default_dtype() + return dict(dtype=dtype, device=device) diff --git a/cornucopia/functional/intensity.py b/cornucopia/functional/intensity.py index 571969d..f44d437 100644 --- a/cornucopia/functional/intensity.py +++ b/cornucopia/functional/intensity.py @@ -15,13 +15,8 @@ "gamma_transform", "z_transform", "quantile_transform", + "minmax_transform", "affine_intensity_transform", - "random_field_uniform", - "random_field_gaussian", - "random_field_lognormal", - "random_field_uniform_like", - "random_field_gaussian_like", - "random_field_lognormal_like", ] # stdlib from typing import Union, Mapping, Sequence, Optional, Callable @@ -33,7 +28,8 @@ # internal from ..baseutils import prepare_output, returns_update, return_requires -from ..utils.smart_inplace import add_, mul_, pow_, div_, exp_ +from ..utils.smart_math import add_, mul_, pow_, div_ +from ._utils import _unsqz_spatial Tensor = torch.Tensor @@ -41,12 +37,6 @@ Output = Union[Tensor, Mapping[Tensor], Sequence[Tensor]] -def _unsqz_spatial(x: Value, ndim: int) -> Value: - if torch.is_tensor(x): - x = x[(Ellipsis,) + (None,) * ndim] - return x - - def binop_value( op: Callable[[Tensor, Value], Output], input: Tensor, @@ -243,7 +233,7 @@ def binop_field( """ Apply a binary operation between the input and a field. - The field gets resized to the input's shape if needed. + The field gets resized to the input"s shape if needed. Parameters ---------- @@ -703,11 +693,11 @@ def gamma_transform( vmin : float | ([C],) tensor | None Minimum value. It can have multiple channels but no spatial dimensions. - If `None`, compute the input's minimum. + If `None`, compute the input"s minimum. vmax : float | ([C],) tensor | None Maximum value. It can have multiple channels but no spatial dimensions. - If `None`, compute the input's maximum. + If `None`, compute the input"s maximum. per_channel : bool This parameter is only used when `vmin=None` or `vmax=None`. If `True`, the min/max of each input channel is used. @@ -743,7 +733,7 @@ def gamma_transform( output = div_((input - vmin_), (vmax_ - vmin_).clamp_min_(1e-8)) output = pow_(output, gamma_) - if getattr(gamma_, 'requires_grad', False): + if getattr(gamma_, "requires_grad", False): # When gamma requires grad, mul_(y, vmax-vmin) is happy # to overwrite y, but we cant because we need y to # backprop through pow. So we need an explicit branch. @@ -888,7 +878,7 @@ def quantile_transform( # Select a subset of values to compute the quantiles # (discard inf/nan/zeros + take random sample for speed) input_ = input.reshape([len(input), -1]) - input_ = input_[:, (input_ != 0) & input_.isfinite()] + input_ = input_[:, (input_ != 0).all(0) & input_.isfinite().all(0)] if (max_samples is not None) and (max_samples < input_.shape[1]): index_ = torch.randperm(input_.shape[1], device=input_.device) index_ = index_[:max_samples] @@ -938,6 +928,84 @@ def quantile_transform( }, kwargs["returns"]) +def minmax_transform( + input: Tensor, + vmin: Value = 0, + vmax: Value = 1, + per_channel: bool = False, + **kwargs +) -> Output: + """ + Apply a min-max transformation: + + ```python + rscled = (input - input.mean()) / (input.max() - input.min()) + output = rscled * (vmax - vmin) + vmin + ``` + + Parameters + ---------- + input : tensor + Input tensor. + vmin : float | ([C],) tensor + Minimum output value. + It can have multiple channels but no spatial dimensions. + vmax : float | ([C],) tensor + Maximum output value. + It can have multiple channels but no spatial dimensions. + per_channel : bool + This parameter is only used when `vmin=None` or `vmax=None`. + If `True`, the qmin/qmax of each input channel is used. + If `False, the global qmin/qmax of the input tensor is used. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "imin", "imax", "vmin", "vmax"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *shape) tensor + Output tensor. + + """ # noqa: E501 + ndim = input.ndim - 1 + C = len(input) + + # Compute min/max + if per_channel: + imin, imax = [], [] + for c in range(C): + input_ = input[c] + input_ = input_[input_.isfinite()] + imin.append(input_.min()) + imax.append(input_.max()) + imin = torch.stack(imin) + imax = torch.stack(imax) + else: + imin = input_.min() + imax = input_.max() + + imin_ = _unsqz_spatial(imin, ndim) + imax_ = _unsqz_spatial(imax, ndim) + vmin_ = _unsqz_spatial(vmin, ndim) + vmax_ = _unsqz_spatial(vmax, ndim) + + # Transform + output = div_((input - imin_), (imax_ - imin_).clamp_min_(1e-8)) + output = add_(mul_(output, vmax_ - vmin_), vmin_) + + kwargs.setdefault("returns", "output") + return prepare_output({ + "input": input, + "output": output, + "vmin": vmin, + "vmax": vmax, + "imin": imin, + "imax": imax, + }, kwargs["returns"]) + + def affine_intensity_transform( input: Tensor, imin: Value, @@ -1008,303 +1076,3 @@ def affine_intensity_transform( "omin": omin, "omax": omax, }, kwargs["returns"]) - - -def random_field_uniform( - shape: Sequence[int], - vmin: Value = 0, - vmax: Value = 1, - **kwargs -) -> Output: - """ - Sample a random field from a uniform distribution - - Parameters - ---------- - shape : list[int] - Output shape, including the channel dimension (!!): (C, *spatial). - vmin : float | ([C],) tensor - Minimum value. - vmax : float | ([C],) tensor - Maximum value. - - Other Parameters - ---------------- - returns : [list or dict of] {"output", "vmin", "vmax"} - - Returns - ------- - output : (*shape) tensor - Output tensor. - """ - dtype = kwargs.get("dtype", vmin.get("dtype", vmax.get("dtype", None))) - device = kwargs.get("device", vmin.get("device", vmax.get("device", None))) - if not dtype or not dtype.is_floating_point: - dtype = torch.get_default_dtype() - - ndim = len(shape) - 1 - vmin_ = _unsqz_spatial(vmin, ndim) - vmax_ = _unsqz_spatial(vmax, ndim) - - output = torch.rand(shape, dtype=dtype, device=device) - output = add_(mul_(output, (vmax_ - vmin_)), vmin_) - - kwargs.setdefault("returns", "output") - return prepare_output({ - "output": output, - "vmin": vmin, - "vmax": vmax, - }, kwargs["returns"]) - - -def random_field_gaussian( - shape: Sequence[int], - mu: Value = 0, - sigma: Value = 1, - **kwargs -) -> Output: - """ - Sample a random field from a Gaussian distribution - - Parameters - ---------- - shape : list[int] - Output shape, including the channel dimension (!!): (C, *spatial). - mu : float | ([C],) tensor - Mean. - sigma : float | ([C],) tensor - Standard deviation. - - Other Parameters - ---------------- - returns : [list or dict of] {"output", "mu", "sigma"} - - Returns - ------- - output : (*shape) tensor - Output tensor. - """ - dtype = kwargs.get("dtype", mu.get("dtype", sigma.get("dtype", None))) - device = kwargs.get("device", mu.get("device", sigma.get("device", None))) - if not dtype or not dtype.is_floating_point: - dtype = torch.get_default_dtype() - - ndim = len(shape) - 1 - mu_ = _unsqz_spatial(mu, ndim) - sigma_ = _unsqz_spatial(sigma, ndim) - - output = torch.randn(shape, dtype=dtype, device=device) - output = add_(mul_(output, sigma_), mu_) - - kwargs.setdefault("returns", "output") - return prepare_output({ - "output": output, - "mu": mu, - "sigma": sigma, - }, kwargs["returns"]) - - -def random_field_lognormal( - shape: Sequence[int], - mu: Value = 0, - sigma: Value = 1, - **kwargs -) -> Output: - """ - Sample a random field from a Gaussian distribution - - Parameters - ---------- - shape : list[int] - Output shape, including the channel dimension (!!): (C, *spatial). - mu : float | ([C],) tensor - Mean of log. - sigma : float | ([C],) tensor - Standard deviation of log. - - Other Parameters - ---------------- - returns : [list or dict of] {"output", "mu", "sigma"} - - Returns - ------- - output : (*shape) tensor - Output tensor. - """ - dtype = kwargs.get("dtype", mu.get("dtype", sigma.get("dtype", None))) - device = kwargs.get("device", mu.get("device", sigma.get("device", None))) - if not dtype or not dtype.is_floating_point: - dtype = torch.get_default_dtype() - - ndim = len(shape) - 1 - mu_ = _unsqz_spatial(mu, ndim) - sigma_ = _unsqz_spatial(sigma, ndim) - - output = torch.randn(shape, dtype=dtype, device=device) - output = exp_(add_(mul_(output, sigma_), mu_)) - - kwargs.setdefault("returns", "output") - return prepare_output({ - "output": output, - "mu": mu, - "sigma": sigma, - }, kwargs["returns"]) - - -def _random_field_like( - func: Callable, - input: Tensor, - shape: Optional[Sequence[int]] = None, - *args, - **kwargs -) -> Output: - """ - Helper to sample a random field from a distribution - - Parameters - ---------- - func : callable - Sampling function - input : tensor - Tensor from which to copy the data type, device and shape - shape : list[int] | None - Output shape. Same as input by default. - *args - `func`'s other parameters. - - Other Parameters - ---------------- - returns : [list or dict of] {"input", "output", ...} - - Returns - ------- - output : (*shape) tensor - Output tensor. - """ - kwargs.setdefault("returns", "output") - - # copy shape - if shape is None: - shape = input.shape - shape = torch.Size(shape) - # if pure spatial shape, copy channels - if len(shape) == input.ndim - 1: - shape = input.shape[:1] + shape - - # copy dtype/device - dtype = kwargs.get("dtype", None) or input.dtype - device = kwargs.get("device", None) or input.device - if not dtype.is_floating_point: - dtype = torch.get_default_dtype() - kwargs["dtype"] = dtype - kwargs["device"] = device - - # sample field - output = func(shape, *args, **kwargs) - - return returns_update(input, "input", output, kwargs["returns"]) - - -def random_field_uniform_like( - input: Tensor, - shape: Optional[Sequence[int]] = None, - vmin: Value = 0, - vmax: Value = 1, - **kwargs -) -> Output: - """ - Sample a random field from a uniform distribution - - Parameters - ---------- - input : tensor - Tensor from which to copy the data type, device and shape - shape : list[int] | None - Output shape. Same as input by default. - vmin : float | ([C],) tensor - Minimum value. - vmax : float | ([C],) tensor - Maximum value. - - Other Parameters - ---------------- - returns : [list or dict of] {"output", "input", "vmin", "vmax"} - - Returns - ------- - output : (*shape) tensor - Output tensor. - """ - return _random_field_like( - random_field_uniform, input, shape, vmin, vmax, **kwargs - ) - - -def random_field_gaussian_like( - input: Tensor, - shape: Optional[Sequence[int]] = None, - mu: Value = 0, - sigma: Value = 1, - **kwargs -) -> Output: - """ - Sample a random field from a gaussian distribution - - Parameters - ---------- - input : tensor - Tensor from which to copy the data type, device and shape - shape : list[int] | None - Output shape. Same as input by default. - mu : float | ([C],) tensor - Mean. - sigma : float | ([C],) tensor - Standard deviation. - - Other Parameters - ---------------- - returns : [list or dict of] {"output", "input", "mu", "sigma"} - - Returns - ------- - output : (*shape) tensor - Output tensor. - """ - return _random_field_like( - random_field_gaussian, input, shape, mu, sigma, **kwargs - ) - - -def random_field_lognormal_like( - input: Tensor, - shape: Optional[Sequence[int]] = None, - mu: Value = 0, - sigma: Value = 1, - **kwargs -) -> Output: - """ - Sample a random field from a log-normal distribution - - Parameters - ---------- - input : tensor - Tensor from which to copy the data type, device and shape - shape : list[int] | None - Output shape. Same as input by default. - mu : float | ([C],) tensor - Mean of log. - sigma : float | ([C],) tensor - Standard deviation of log. - - Other Parameters - ---------------- - returns : [list or dict of] {"output", "input", "mu", "sigma"} - - Returns - ------- - output : (*shape) tensor - Output tensor. - """ - return _random_field_like( - random_field_lognormal, input, shape, mu, sigma, **kwargs - ) diff --git a/cornucopia/functional/random.py b/cornucopia/functional/random.py new file mode 100644 index 0000000..054df37 --- /dev/null +++ b/cornucopia/functional/random.py @@ -0,0 +1,764 @@ +__all__ = [ + "random_field_uniform", + "random_field_gaussian", + "random_field_lognormal", + "random_field_gamma", + "random_field_uniform_like", + "random_field_gaussian_like", + "random_field_lognormal_like", + "random_field_gamma_like", +] +# stdlib +from typing import Sequence, Optional, Callable, Union, Mapping + +# external +import torch + +# internal +from ..baseutils import prepare_output, returns_update +from ..utils import smart_math as math +from ..utils.distributions import ( + uniform_parameters, + gaussian_parameters, + lognormal_parameters, + gamma_parameters, + generalized_normal_parameters, +) +from ._utils import _unsqz_spatial, _backend_float + + +Tensor = torch.Tensor +Value = Union[float, Tensor] +Output = Union[Tensor, Mapping[Tensor], Sequence[Tensor]] + +LOG2 = math.log(2) +FWHM_FACTOR = (8 * LOG2) ** 0.5 # gaussian: fwhm = FWHM_FACTOR * sigma + + +def random_field(name: str, shape: Sequence[int], **kwargs) -> Output: + """ + Sample a random field from a probability distribution + + Parameters + ---------- + name : {"uniform", "gaussian", "gamma", "lognormal", "generalized-gaussian"} + Distribution name. + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + + Other Parameters + ---------------- + mean : float | ([C],) tensor + Mean. + std : float | ([C],) tensor + Standard deviation. + peak : float | ([C],) tensor + Peak. + fwhm : float | ([C],) tensor + Width. + vmin, vmax, alpha, beta, mu, sigma : float | ([C],) tensor + Distribution-specific parameters + returns : [list or dict of] {"output", "mean", "std", "peak", "fwhm", ...} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ # noqa: E501 + name = name.lower() + if name == "uniform": + return random_field_uniform(shape, **kwargs) + if name in ("normal", "gaussian"): + return random_field_gaussian(shape, **kwargs) + if name == "gamma": + return random_field_gamma(shape, **kwargs) + if name in ("lognormal", "log-normal"): + return random_field_lognormal(shape, **kwargs) + if name in ("generalized-normal", "generalized-gaussian"): + return random_field_generalized_normal(shape, **kwargs) + + +def random_field_uniform( + shape: Sequence[int], + vmin: Optional[Value] = None, + vmax: Optional[Value] = None, + **kwargs +) -> Output: + """ + Sample a random field from a uniform distribution + + !!! note "Parameters" + Two parameterizations can be used: + + * `(vmin, vmax)` is the distribution"s natural parameterization, + where `vmin` is the lower bound and `vmax` is the upper bound. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. Alternatively, the width of the distribution + `fwhm` can be used in place of `std`. + + By default, the `(vmin, vmax)` parameterization is used. To use, + the other one, `mean` and `std` (or `fwhm`) must be explicity set as + keyword arguments, and neither `vmin` nor `vmax` must be used. + + Parameters + ---------- + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + vmin : float | ([C],) tensor, default=0 + Minimum value. + vmax : float | ([C],) tensor, default=1 + Maximum value. + + Other Parameters + ---------------- + mean : float | ([C],) tensor + Mean. + std : float | ([C],) tensor + Standard deviation. + fwhm : float | ([C],) tensor + Width. + returns : [list or dict of] {"output", "vmin", "vmax"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + prm = uniform_parameters(vmin=vmin, vmax=vmax, **kwargs) + vmin, vmax = prm["vmin"], prm["vmax"] + + ndim = len(shape) - 1 + vmin_ = _unsqz_spatial(vmin, ndim) + vmax_ = _unsqz_spatial(vmax, ndim) + + backend = _backend_float(vmin, vmax, **kwargs) + output = torch.rand(shape, **backend) + output = math.add_(math.mul_(output, (vmax_ - vmin_)), vmin_) + + kwargs.setdefault("returns", "output") + return prepare_output({"output": output, **prm}, kwargs["returns"]) + + +def random_field_gaussian( + shape: Sequence[int], + mean: Optional[Value] = None, + std: Optional[Value] = None, + **kwargs +) -> Output: + """ + Sample a random field from a Gaussian distribution + + Parameters + ---------- + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + mean : float | ([C],) tensor, default=0 + Mean. + std : float | ([C],) tensor, default=1 + Standard deviation. + + Other Parameters + ---------------- + fwhm : float | ([C],) tensor + The Full-width at half maximum can be specifed in place of the std. + returns : [list or dict of] {"output", "mu", "sigma"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + prm = gaussian_parameters(mean=mean, std=std, **kwargs) + mean, std = prm["mean"], prm["std"] + + ndim = len(shape) - 1 + mean_ = _unsqz_spatial(mean, ndim) + std_ = _unsqz_spatial(std, ndim) + + backend = _backend_float(mean, std, **kwargs) + output = torch.randn(shape, **backend) + output = math.add_(math.mul_(output, std_), mean_) + + kwargs.setdefault("returns", "output") + return prepare_output({"output": output, **prm}, kwargs["returns"]) + + +def random_field_lognormal( + shape: Sequence[int], + mean: Optional[Value] = None, + std: Optional[Value] = None, + **kwargs +) -> Output: + """ + Sample a random field from a log-normal distribution + + !!! note "Parameters" + Three parameterizations can be used: + + * `(mu, sigma)` is the distribution"s natural parameterization, + where `mu` is the mean of the log of the data and `sigma` is + the standard deviation of the log of the data. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. Alternatively, the width of the distribution + `fwhm` can be used in place of `std`. + * `(peak, fwhm)` is a shape-based parameterization, where + `peak` is the location of the mode of the distribution, + and `fwhm` is the full-width at half-maximum of the distribution. + + By default, the `(mean, std)` parameterization is used. To use, + the other one, `mu` and `sigma` must be explicity set as + keyword arguments, and neither `mean` nor `std` must be used. + + Parameters + ---------- + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + mean : float | ([C],) tensor, default=1 + Mean of the distribution. + (!! mean(x) != {mu == mean(log(x))}). + std : float | ([C],) tensor, default=1 + Standard deviation of the distribution. + (!! std(x) != {sigma == std(log(x))}). + + Other Parameters + ---------------- + peak : float | ([C],) tensor + Location of the peak of the distribution. + fwhm : float | ([C],) tensor + Standard deviation. + mu : float | ([C],) tensor + Mean of the log. + sigma : float | ([C],) tensor + Standard deviation of the log. + returns : [list or dict of] {"output", "mean", "std", "fwhm", "peak", "mu", "sigma"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ # noqa: E501 + prm = lognormal_parameters(mean=mean, sts=std, **kwargs) + mu, sigma = prm["mu"], prm["sigma"] + + ndim = len(shape) - 1 + mu_ = _unsqz_spatial(mu, ndim) + sigma_ = _unsqz_spatial(sigma, ndim) + + backend = _backend_float(mu_, sigma_, **kwargs) + output = torch.randn(shape, **backend) + output = math.exp_(math.add_(math.mul_(output, sigma_), mu_)) + + kwargs.setdefault("returns", "output") + return prepare_output({"output": output, **prm}, kwargs["returns"]) + + +def random_field_gamma( + shape: Sequence[int], + mean: Optional[Value] = None, + std: Optional[Value] = None, + **kwargs +) -> Output: + """ + Sample a random field from a Gamma distribution + + !!! note "Parameters" + Two parameterizations can be used: + + * `(alpha, beta)` is the distribution"s natural parameterization, + where `alpha` is the shape parameter and `beta` is the rate + parameter. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. + * `(peak, fwhm)` is a shape-based parameterization, where + `peak` is the location of the mode of the distribution, + and `fwhm` is the full-width at half-maximum of the distribution. + Since the `fwhm` of the Gamma distribution does not have a + nicely tracktable form, we use a Laplace approximation, which + only exists for alpha > 1. + + By default, the `(mean, std)` parameterization is used. To use, + the other one, `alpha` and `beta` must be explicity set as + keyword arguments, and neither `mean` nor `std` must be used. + + Parameters + ---------- + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + mean : float | ([C],) tensor, default=1 + Mean. + std : float | ([C],) tensor, default=1 + Standard deviation. + + Other Parameters + ---------------- + alpha : float | ([C],) tensor + Shape parameter. + beta : float | ([C],) tensor + Rate parameter. + peak : float | ([C],) tensor + Mode of the distribution. + fwhm : float | ([C],) tensor + Full-width at half-maximum. + returns : [list or dict of] {"output", "mean", "std", "alpha", "beta"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + prm = gamma_parameters(mean=mean, std=std, **kwargs) + alpha, beta = prm["alpha"], prm["beta"] + + backend = _backend_float(alpha, beta) + alpha_ = torch.as_tensor(alpha, **backend) + beta_ = torch.as_tensor(beta, **backend) + alpha_ = alpha_.expand(shape[:1]) + beta_ = beta_.expand(shape[:1]) + + output = torch.distributions.Gamma(alpha_, beta_).rsample(shape[1:]) + + kwargs.setdefault("returns", "output") + return prepare_output({"output": output, **prm}, kwargs["returns"]) + + +def random_field_generalized_normal( + shape: Sequence[int], + mean: Optional[Value] = None, + std: Optional[Value] = None, + beta: Value = 2, + **kwargs +) -> Output: + """ + Sample a random field from a Generalized Normal distribution. + + !!! note "Parameters" + Three parameterizations can be used: + + * `(mu, alpha)` is the distribution's natural parameterization, + where `mu` is the mean and `alpha` is the scale parameter. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. + * `(peak, fwhm)` is a shape-based parameterization, where + `peak` is the location of the mode of the distribution, + and `fwhm` is the full-width at half-maximum of the distribution. + Note that the Gamma distribution does not have a maximum when + `alpha < 1`, and therefore no FWHM as well. When `alpha > 1`, + the FWHM does not have a nicely tracktable form, so we use the + Laplace approximation instead (i.e., the FWHM of the best + approximating Gaussian at its peak). + + In Generalized Normal distributions, the mean and peak all equal `mu`. + Furthermore, the distribution is parameterized by a shape parameter + `beta`, with the following special cases: + + * `beta = 0`: Dirac[mu] + * `beta = 1`: Laplace[mu, b=alpha] + * `beta = 2`: Normal[mu, sigma=alpha/sqrt(2)] + * `beta = inf`: Uniform[a=mu-alpha, b=mu+alpha] + + Parameters + ---------- + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + mean : float | ([C],) tensor, default=0 + Mean. + std : float | ([C],) tensor, default=1 + Standard deviation. + beta : float | ([C],) tensor, default=2 + Shape parameter. + + Other Parameters + ---------------- + peak : float | ([C],) tensor + The mode of the distribution can be specified in place of the mean. + fwhm : float | ([C],) tensor + The Full-width at half maximum can be specifed in place of the std. + alpha : float | ([C],) tensor + The scale parameter can be specifed in place of the std. + returns : [list or dict of] {"output", "mean", "std", "peak", "fwhm", "alpha", "beta"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ # noqa: E501 + # https://blogs.sas.com/content/iml/2016/09/21/simulate-generalized-gaussian-sas.html + + ndim = len(shape) - 1 + + # Default in `generalized_normal_parameters` is `alpha=1`, whereas + # the default in `random_field_generalized_normal` is `std=1`. + if kwargs.get("alpha", None) is None and kwargs.get("fwhm", None) is None: + std = 1 if std is None else std + + kwargs["beta"], kwargs["mean"], kwargs["std"] = beta, mean, std + prm = generalized_normal_parameters(**kwargs) + + mean, std, alpha, beta = prm["mean"], prm["std"], prm["alpha"], prm["beta"] + backend = _backend_float(mean, std, alpha, beta, **kwargs) + + mean_ = _unsqz_spatial(mean, ndim) + std_ = _unsqz_spatial(std, ndim) + + b = math.exp(0.5*(math.gammaln(3/beta) - math.gammaln(1/beta))) + sign = random_field_uniform(shape, **backend) > 0.5 + output = random_field_gamma(shape, alpha=1/alpha, beta=1/b, **backend) + output = math.mul_(output, 2 * sign - 1) + output = math.add_(math.mul_(output, std_), mean_) + + kwargs.setdefault("returns", "output") + return prepare_output({"output": output, **prm}, kwargs["returns"]) + + +def random_field_like( + name: str, + input: Tensor, + shape: Sequence[int], + **kwargs +) -> Output: + """ + Sample a random field from a probability distribution + + Parameters + ---------- + name : {"uniform", "gaussian", "gamma", "lognormal", "generalized-gaussian"} + Distribution name. + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] + Output shape, including the channel dimension (!!): (C, *spatial). + + Other Parameters + ---------------- + mean : float | ([C],) tensor + Mean. + std : float | ([C],) tensor + Standard deviation. + peak : float | ([C],) tensor + Peak. + fwhm : float | ([C],) tensor + Width. + vmin, vmax, alpha, beta, mu, sigma : float | ([C],) tensor + Distribution-specific parameters + returns : [list or dict of] {"output", "mean", "std", "peak", "fwhm", ...} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ # noqa: E501 + name = name.lower() + if name == "uniform": + return random_field_uniform_like(input, shape, **kwargs) + if name in ("normal", "gaussian"): + return random_field_gaussian_like(input, shape, **kwargs) + if name == "gamma": + return random_field_gamma_like(input, shape, **kwargs) + if name in ("lognormal", "log-normal"): + return random_field_lognormal_like(input, shape, **kwargs) + if name in ("generalized-normal", "generalized-gaussian"): + return random_field_generalized_normal_like(input, shape, **kwargs) + + +def _random_field_like( + func: Callable, + input: Tensor, + shape: Optional[Sequence[int]] = None, + *args, + **kwargs +) -> Output: + """ + Helper to sample a random field from a distribution + + Parameters + ---------- + func : callable + Sampling function + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + *args + `func`"s other parameters. + + Other Parameters + ---------------- + returns : [list or dict of] {"input", "output", ...} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + kwargs.setdefault("returns", "output") + + # copy shape + if shape is None: + shape = input.shape + shape = torch.Size(shape) + # if pure spatial shape, copy channels + if len(shape) == input.ndim - 1: + shape = input.shape[:1] + shape + + # copy dtype/device + dtype = kwargs.get("dtype", None) or input.dtype + device = kwargs.get("device", None) or input.device + if not dtype.is_floating_point: + dtype = torch.get_default_dtype() + kwargs["dtype"] = dtype + kwargs["device"] = device + + # sample field + output = func(shape, *args, **kwargs) + + return returns_update(input, "input", output, kwargs["returns"]) + + +def random_field_uniform_like( + input: Tensor, + shape: Optional[Sequence[int]] = None, + vmin: Value = 0, + vmax: Value = 1, + **kwargs +) -> Output: + """ + Sample a random field from a uniform distribution + + !!! note "Parameters" + Two parameterizations can be used: + * `(vmin, vmax)` is the distribution"s natural parameterization, + where `vmin` is the lower bound and `vmax` is the upper bound. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. Alternatively, the width of the distribution + `fwhm` can be used in place of `std`. + + By default, the `(vmin, vmax)` parameterization is used. To use, + the other one, `mean` and `std` (or `fwhm`) must be explicity set as + keyword arguments, and neither `vmin` nor `vmax` must be used. + + Parameters + ---------- + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + vmin : float | ([C],) tensor + Minimum value. + vmax : float | ([C],) tensor + Maximum value. + + Other Parameters + ---------------- + mean : float | ([C],) tensor + Mean. + std : float | ([C],) tensor + Standard deviation. + fwhm : float | ([C],) tensor + Width. + returns : [list or dict of] {"output", "input", "vmin", "vmax", "mean", "std", "fwhm"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ # noqa: E501 + return _random_field_like( + random_field_uniform, input, shape, vmin, vmax, **kwargs + ) + + +def random_field_gaussian_like( + input: Tensor, + shape: Optional[Sequence[int]] = None, + mu: Value = 0, + sigma: Value = 1, + **kwargs +) -> Output: + """ + Sample a random field from a gaussian distribution + + Parameters + ---------- + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + mu : float | ([C],) tensor + Mean. + sigma : float | ([C],) tensor + Standard deviation. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "mu", "sigma"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + return _random_field_like( + random_field_gaussian, input, shape, mu, sigma, **kwargs + ) + + +def random_field_lognormal_like( + input: Tensor, + shape: Optional[Sequence[int]] = None, + mu: Value = 0, + sigma: Value = 1, + **kwargs +) -> Output: + """ + Sample a random field from a log-normal distribution + + Parameters + ---------- + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + mu : float | ([C],) tensor + Mean of log. + sigma : float | ([C],) tensor + Standard deviation of log. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "mu", "sigma"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + return _random_field_like( + random_field_lognormal, input, shape, mu, sigma, **kwargs + ) + + +def random_field_gamma_like( + input: Tensor, + shape: Optional[Sequence[int]] = None, + mean: Optional[Value] = None, + std: Optional[Value] = None, + **kwargs +) -> Output: + """ + Sample a random field from a Gamma distribution + + !!! note "Parameters" + Two parameterizations can be used: + * `(alpha, beta)` is the distribution"s natural parameterization, + where `alpha` is the shape parameter and `beta` is the rate + parameter. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. + + By default, the `(mean, std)` parameterization is used. To use, + the other one, `alpha` and `beta` must be explicity set as + keyword arguments, and neither `mean` nor `std` must be used. + + Parameters + ---------- + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + mean : float | ([C],) tensor + Mean. + std : float | ([C],) tensor + Standard deviation. + + Other Parameters + ---------------- + alpha : float | ([C],) tensor + Shape parameter. + beta : float | ([C],) tensor + Rate parameter. + returns : [list or dict of] {"output", "mean", "std", "alpha", "beta"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ + return _random_field_like( + random_field_lognormal, input, shape, mean, std, **kwargs + ) + + +def random_field_generalized_normal_like( + input: Tensor, + shape: Optional[Sequence[int]] = None, + mean: Optional[Value] = None, + std: Optional[Value] = None, + beta: Value = 2, + **kwargs +) -> Output: + """ + Sample a random field from a Generalized Gaussian distribution + + !!! note "Parameters" + Three parameterizations can be used: + + * `(mu, alpha)` is the distribution's natural parameterization, + where `mu` is the mean and `alpha` is the scale parameter. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. + * `(peak, fwhm)` is a shape-based parameterization, where + `peak` is the location of the mode of the distribution, + and `fwhm` is the full-width at half-maximum of the distribution. + Note that the Gamma distribution does not have a maximum when + `alpha < 1`, and therefore no FWHM as well. When `alpha > 1`, + the FWHM does not have a nicely tracktable form, so we use the + Laplace approximation instead (i.e., the FWHM of the best + approximating Gaussian at its peak). + + In Generalized Normal distributions, the mean and peak all equal `mu`. + Furthermore, the distribution is parameterized by a shape parameter + `beta`, with the following special cases: + + * `beta = 0`: Dirac[mu] + * `beta = 1`: Laplace[mu, b=alpha] + * `beta = 2`: Normal[mu, sigma=alpha/sqrt(2)] + * `beta = inf`: Uniform[a=mu-alpha, b=mu+alpha] + + Parameters + ---------- + input : tensor + Tensor from which to copy the data type, device and shape + shape : list[int] | None + Output shape. Same as input by default. + mean : float | ([C],) tensor, default=0 + Mean. + std : float | ([C],) tensor, default=1 + Standard deviation. + beta : float | ([C],) tensor, default=2 + Shape parameter. + + Other Parameters + ---------------- + peak : float | ([C],) tensor + The mode of the distribution can be specified in place of the mean. + fwhm : float | ([C],) tensor + The Full-width at half maximum can be specifed in place of the std. + alpha : float | ([C],) tensor + The scale parameter can be specifed in place of the std. + returns : [list or dict of] {"output", "mean", "std", "peak", "fwhm", "alpha", "beta"} + + Returns + ------- + output : (*shape) tensor + Output tensor. + """ # noqa: E501 + return _random_field_like( + random_field_lognormal, input, shape, mean, std, beta, **kwargs + ) diff --git a/cornucopia/geometric.py b/cornucopia/geometric.py index 07d185f..78b0244 100755 --- a/cornucopia/geometric.py +++ b/cornucopia/geometric.py @@ -21,7 +21,7 @@ from .random import Sampler, Uniform, RandInt, Fixed, make_range from .utils import warps from .utils.py import ensure_list, cast_like, make_vector -from .utils.smart_inplace import add_ +from .utils.smart_math import add_ class ElasticTransform(NonFinalTransform): diff --git a/cornucopia/intensity.py b/cornucopia/intensity.py index b717897..fa42abc 100755 --- a/cornucopia/intensity.py +++ b/cornucopia/intensity.py @@ -29,7 +29,7 @@ from .special import RandomizedTransform, SequentialTransform from .random import Sampler, Uniform, RandInt, Fixed, make_range from .utils.py import ensure_list, positive_index -from .utils.smart_inplace import add_, mul_, div_, pow_ +from .utils.smart_math import add_, mul_, div_, pow_ class OpConstTransform(FinalTransform): diff --git a/cornucopia/kspace.py b/cornucopia/kspace.py index 06c8156..7211494 100755 --- a/cornucopia/kspace.py +++ b/cornucopia/kspace.py @@ -14,7 +14,7 @@ from .geometric import RandomAffineTransform from .random import Fixed from .utils.warps import identity -from .utils.smart_inplace import sqrt_, square_, abs_, mul_, exp_, sub_, add_ +from .utils.smart_math import sqrt_, square_, abs_, mul_, exp_, sub_, add_ from . import ctx diff --git a/cornucopia/labels.py b/cornucopia/labels.py index 7d47e63..6ab0986 100755 --- a/cornucopia/labels.py +++ b/cornucopia/labels.py @@ -32,7 +32,7 @@ from .utils.conv import smoothnd from .utils.py import ensure_list, make_vector from .utils.morpho import bounded_distance -from .utils.smart_inplace import mul_, div_, add_, sub_ +from .utils.smart_math import mul_, div_, add_, sub_ from . import ctx diff --git a/cornucopia/noise.py b/cornucopia/noise.py index d752bb1..dbaac7f 100755 --- a/cornucopia/noise.py +++ b/cornucopia/noise.py @@ -15,7 +15,7 @@ from .special import RandomizedTransform from .intensity import MulFieldTransform, AddValueTransform, MulValueTransform from .random import Uniform, RandInt, Fixed, make_range -from .utils.smart_inplace import mul_, add_, sqrt_ +from .utils.smart_math import mul_, add_, sqrt_ from . import ctx diff --git a/cornucopia/qmri.py b/cornucopia/qmri.py index f3df192..c998de3 100755 --- a/cornucopia/qmri.py +++ b/cornucopia/qmri.py @@ -24,7 +24,7 @@ ) from .random import Sampler, Uniform, RandInt, make_range from .utils.py import ensure_list, make_vector -from .utils.smart_inplace import exp_, div_ +from .utils.smart_math import exp_, div_ from .utils import b0 diff --git a/cornucopia/random.py b/cornucopia/random.py index 747482c..f6e2dc5 100755 --- a/cornucopia/random.py +++ b/cornucopia/random.py @@ -13,7 +13,7 @@ import torch from numbers import Number from .utils.py import ensure_list -from .utils.smart_inplace import add_, mul_, exp_ +from .utils.smart_math import add_, mul_, exp_ class Sampler: diff --git a/cornucopia/utils/b0.py b/cornucopia/utils/b0.py index e74d7e4..0be1683 100755 --- a/cornucopia/utils/b0.py +++ b/cornucopia/utils/b0.py @@ -13,7 +13,7 @@ import itertools from .warps import identity as identity_grid, cartesian_grid from .py import prod, make_vector -from .smart_inplace import mul_, div_ +from .smart_math import mul_, div_ r""" diff --git a/cornucopia/utils/distributions.py b/cornucopia/utils/distributions.py new file mode 100644 index 0000000..09e8141 --- /dev/null +++ b/cornucopia/utils/distributions.py @@ -0,0 +1,533 @@ + +import smart_math as math + +LOG2 = math.log(2) +FWHM_FACTOR = (8 * LOG2) ** 0.5 # gaussian: fwhm = FWHM_FACTOR * sigma + + +_PARAMETERIZATIONS = {} + + +def _register_parameterization(names): + if isinstance(names, str): + names = [names] + + def wrapper(func): + for name in names: + _PARAMETERIZATIONS[name] = func + return func + + return wrapper + + +def distribution_parameters(name: str, **kwargs) -> dict: + """ + Compute the natural parameters of a distribution from any parameterization. + + Defaults depend in the distribution,. + + Parameters + ---------- + name : {"uniform", "gaussian", "lognormal", "gamma", "generalized-normal"} + Distribution name. + + Parameters common to most distribution + -------------------------------------- + mean : float | tensor + Mean. + std : float | tensor + Standard deviation. + peak : float | tensor + Location of the mode. + fwhm : float | tensor + Full width at half-maximum. + + Parameters of the Uniform distribution + -------------------------------------- + vmin, a : float | tensor + Lower bound. + vmax, a : float | tensor + Upper bound. + + Parameters of the Gaussian distribution + --------------------------------------- + mu : float | tensor + Alias for `mean`. + sigma : float | tensor + Alias for `std`. + + Parameters of the Gamma distribution + ------------------------------------ + alpha : float | tensor + Shape parameter. + beta : float | tensor + Rate parameter. + + Parameters of the Log-Normal distribution + ----------------------------------------- + mu : float | tensor + Mean of the log of the data. + sigma : float | tensor + Standard-deviation of the log of the data. + + Parameters of the Generalized Normal distribution + ------------------------------------------------- + mu : float | tensor + Alias for `mean`. + alpha : float | tensor + Scale parameter. + beta : float | tensor + Shape parameter. + + Returns + ------- + dict + + """ + func = _PARAMETERIZATIONS[name.lower()] + return func(**kwargs) + + +@_register_parameterization("uniform") +def uniform_parameters(**kwargs) -> dict: + """ + Compute the parameters of a uniform distribution from any + parameterization. + + Two parameterizations can be used: + + * `(vmin, vmax)` is the distribution's natural parameterization, + where `vmin` is the lower bound and `vmax` is the upper bound. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean (or center) of the distribution and `std` its + standard deviation. Alternatively, the width of the distribution + `fwhm = sqrt(12) * std` can be used in place of `std`. + + By default, the `(vmin, vmax)` parameterization is used. To use, + the other one, `mean` and `std` (or `fwhm`) must be explicity set as + keyword arguments, and neither `vmin` nor `vmax` must be used. + + Note that we also accept `peak` as an alias for `mean`. The peak + is ill defined for the uniform distibution, but can be defined as + matching the mean by interpreting the uniform distribution as the + limit of the generalized Gaussian distribution when the shape + parameter goes to infinity. + + Parameters + ---------- + vmin, a : float | tensor, default=0 + Lower bound. + vmax, b : float | tensor, default=1 + Upper bound. + mean, mu : float | tensor + Mean: `mean = (a + b) / 2` + std, sigma : float | tensor + Standard deviation: `std = (b - a) / sqrt(12)` + fwhm : float | tensor + Width: `fwhm = (b - a)` + + Returns + ------- + dict + with keys {"a", "b", "vmin", "vmax", "mean", "std", "fwhm"} + + """ + vmin = kwargs.get("a", kwargs.get("vmin", None)) + vmax = kwargs.get("b", kwargs.get("vmax", None)) + mean = kwargs.pop("mean", kwargs.pop("mu", None)) + std = kwargs.pop("std", kwargs.pop("sigma", None)) + fwhm = kwargs.pop("fwhm", None) + + if (mean is not None) or (std is not None) or (fwhm is not None): + if ((mean is None) or (std is None and fwhm is None)): + raise ValueError( + "(mean, std) must either both be used, or neither be used" + ) + if (vmin is not None) or (vmax is not None): + raise ValueError( + "Cannot mix (mean, std) and (vmin, vmax) parameters" + ) + if fwhm is None: + fwhm = (12**0.5) * std + else: + std = fwhm / (12**0.5) + (vmin, vmax) = (mean - fwhm / 2, mean + fwhm / 2) + else: + vmin = 0 if vmin is None else vmin + vmax = 1 if vmax is None else vmax + mean = (vmin + vmax) / 2 + fwhm = (vmax - vmin) + std = fwhm / (12**0.5) + + return dict( + vmin=vmin, + vmax=vmax, + a=vmin, + b=vmax, + mean=mean, + mu=mean, + peak=mean, + std=std, + sigma=std, + fwhm=fwhm, + ) + + +@_register_parameterization(["gaussian", "normal"]) +def gaussian_parameters(**kwargs) -> dict: + """ + Compute the parameters of a Gaussian distribution from any + parameterization. + + Parameters + ---------- + mean, mu, peak : float | tensor, default=0 + Mean. + std, sigma : float | tensor, default=1 + Standard deviation. + fwhm : float | tensor + Full-width at half maximum: `fwhm = sqrt(8 * log(2)) * std` + + Returns + ------- + dict + with keys {"mu", "sigma", "mean", "std", "peak", "fwhm"} + + """ + mean = kwargs.pop("mean", kwargs.pop("mu", kwargs.pop("peak", None))) + std = kwargs.pop("std", kwargs.pop("sigma", None)) + fwhm = kwargs.pop("fwhm", None) + + mean = 0 if mean is None else mean + std = 1 if std is None else std + + if fwhm is None: + fwhm = FWHM_FACTOR * std + else: + std = fwhm / FWHM_FACTOR + + return dict( + mean=mean, + mu=mean, + peak=mean, + std=std, + sigma=std, + fwhm=fwhm, + ) + + +@_register_parameterization(["lognormal", "log-normal"]) +def lognormal_parameters(**kwargs) -> dict: + """ + Compute the parameters of a log-normal distribution from any + parameterization. + + Three parameterizations can be used: + + * `(mu, sigma)` is the distribution's natural parameterization, + where `mu` is the mean of the log of the data and `sigma` is + the standard deviation of the log of the data. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. + * `(peak, fwhm)` is a shape-based parameterization, where + `peak` is the location of the mode of the distribution, + and `fwhm` is the full-width at half-maximum of the distribution. + + By default, the `(mean, std)` parameterization is used. To use, + the other one, `mu` and `sigma` (or `peak` and `fwhm`) must be + explicity set as keyword arguments, and neither `mean` nor `std` + must be used. + + Parameters + ---------- + mean : float tensor, default=1 + Mean. + std : float | tensor, default=1 + Standard deviation. + peak : float | tensor + Mode. + fwhm : float | tensor + Full-width at half maximum. + mu : float | tensor + Mean of the log. + sigma : float | tensor + Standard deviation of the log. + + Returns + ------- + dict + with keys {"mu", "sigma", "mean", "std", "peak", "fwhm"} + + """ + # NOTE + # FWHM of lognormal taken here: + # http://openafox.com/science/peak-function-derivations.html#lognormal + + mean = kwargs.pop("mean", None) + std = kwargs.pop("std", None) + fwhm = kwargs.pop("fwhm", None) + peak = kwargs.pop("peak", None) + mu = kwargs.pop("mu", None) + sigma = kwargs.pop("sigma", None) + + if (mu is not None) or (sigma is not None): + if ((mu is None) or (sigma is None)): + raise ValueError( + "(mu, sigma) must either both be used, or neither be used" + ) + if (mean is not None) or (std is not None): + raise ValueError( + "(mean, std) cannot be set if (mu, sigma) is set." + ) + if (peak is None) or (fwhm is not None): + raise ValueError( + "(peak, fwhm) cannot be set if (mu, sigma) is set." + ) + sigma2 = sigma * sigma + mean = math.exp(mu + 0.5 * sigma2) + peak = math.exp(mu - sigma2) + std = mean * math.sqrt(math.exp(sigma2) - 1) + fwhm = math.exp(sigma * FWHM_FACTOR / 2) + fwhm = peak * (fwhm - 1/fwhm) + elif (peak is not None) or (fwhm is not None): + if ((peak is None) or (fwhm is None)): + raise ValueError( + "(peak, fwhm) must either both be used, or neither be used" + ) + if (mean is not None) or (std is not None): + raise ValueError( + "(mean, std) cannot be set if (mu, sigma) is set." + ) + # fwhm/peak = tmp - 1/tmp + # tmp = exp(sigma * FWHM_FACTOR / 2) + # => fp = fwhm / peak + # => tmp = 0.5 * (fp + (fp**2 + 4) ** 0.5) + # => sigma = 2 * log(tmp) / FWHM_FACTOR + sigma = fwhm / peak + sigma = 0.5 * (sigma + (sigma * sigma + 4) ** 0.5) + sigma = 2 * math.log(sigma) / FWHM_FACTOR + mu = math.log(peak) + sigma * sigma + mean = math.exp(mu + 0.5 * sigma2) + std = mean * math.sqrt(math.exp(sigma2) - 1) + else: + mean = 1 if mean is None else mean + std = 1 if std is None else std + # mean = math.exp(mu + 0.5 * sigma2) + # std = mean * math.sqrt(math.exp(sigma2) - 1) + # => std/mean = math.sqrt(math.exp(sigma2) - 1) + # => sigma2 = log((std/mean)**2 + 1) + # => mu = log(mean) - 0.5 * sigma2 + sigma2 = math.log(math.square(std/mean) + 1) + sigma = math.sqrt(sigma2) + mu = math.log(mean) - 0.5 * sigma2 + peak = math.exp(mu - sigma2) + fwhm = math.exp(sigma * FWHM_FACTOR / 2) + fwhm = peak * (fwhm - 1/fwhm) + + return dict( + mean=mean, + std=std, + peak=peak, + fwhm=fwhm, + mu=mu, + sigma=sigma, + ) + + +@_register_parameterization(["gamma"]) +def gamma_parameters(**kwargs) -> dict: + """ + Compute the parameters of a Gamma distribution from any + parameterization. + + Three parameterizations can be used: + + * `(alpha, beta)` is the distribution's natural parameterization, + where `alpha` is the shape parameter and `beta` is the rate + parameter. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. + * `(peak, fwhm)` is a shape-based parameterization, where + `peak` is the location of the mode of the distribution, + and `fwhm` is the full-width at half-maximum of the distribution. + Note that the Gamma distribution does not have a maximum when + `alpha < 1`, and therefore no FWHM as well. When `alpha > 1`, + the FWHM does not have a nicely tracktable form, so we use the + Laplace approximation instead (i.e., the FWHM of the best + approximating Gaussian at its peak). + + By default, the `(mean, std)` parameterization is used. To use, + the other one, `alpha` and `beta` must be explicity set as + keyword arguments, and neither `mean` nor `std` must be used. + + Parameters + ---------- + mean, mu : float tensor, default=1 + Mean. + std, sigma : float | tensor, default=1 + Standard deviation. + peak : float | tensor + Mode. + fwhm : float | tensor + Full-width at half maximum. + alpha : float | tensor + Shape parameter. + beta : float | tensor + Rate parameter. + + Returns + ------- + dict + with keys {"alpha", "beta", "mean", "std", "peak", "fwhm"} + + """ + mean = kwargs.pop("mu", kwargs.pop("mean", None)) + std = kwargs.pop("sigma", kwargs.pop("std", None)) + alpha = kwargs.pop("alpha", None) + beta = kwargs.pop("beta", None) + peak = kwargs.pop("peak", None) + fwhm = kwargs.pop("fwhm", None) + + if (alpha is not None) or (beta is not None): + if ((alpha is None) or (beta is None)): + raise ValueError( + "(alpha, beta) must either both be used, or neither be used" + ) + if (mean is not None) or (std is not None): + raise ValueError( + "(mean, std) cannot be set if (alpha, beta) is set." + ) + if (peak is not None) or (fwhm is not None): + raise ValueError( + "(peak, fwhm) cannot be set if (alpha, beta) is set." + ) + mean = alpha / beta + std = alpha**0.5 / beta + peak = (math.max(alpha, 1) - 1) / beta + laplace_sigma2 = (math.max(alpha, 1) - 1) / beta**2 + laplace_sigma = laplace_sigma2 ** 0.5 + fwhm = laplace_sigma * FWHM_FACTOR + elif (peak is not None) or (fwhm is not None): + if (mean is not None) or (std is not None): + raise ValueError( + "(peak, fwhm) cannot be set if (alpha, beta) is set." + ) + # peak = (alpha - 1) / beta + # sigma2 = (alpha - 1) / beta**2 + laplace_sigma = fwhm / FWHM_FACTOR + laplace_sigma2 = laplace_sigma * laplace_sigma + beta = peak / laplace_sigma2 + alpha = 1 + peak * beta + mean = alpha / beta + std = alpha**0.5 / beta + else: + mean = 1 if mean is None else mean + std = 1 if std is None else std + var = std * std + beta = mean / var + alpha = mean * beta + laplace_sigma2 = (math.max(alpha, 1) - 1) / beta**2 + laplace_sigma = laplace_sigma2 ** 0.5 + fwhm = laplace_sigma * FWHM_FACTOR + + return dict( + mean=mean, + std=std, + mu=mean, + sigma=std, + peak=peak, + fwhm=fwhm, + alpha=alpha, + beta=beta, + ) + + +@_register_parameterization(["generalized-gaussian", "generalized-normal"]) +def generalized_normal_parameters(**kwargs) -> dict: + """ + Compute the parameters of a generalized Gaussian distribution from + any parameterization. + + Three parameterizations can be used: + + * `(mu, alpha)` is the distribution's natural parameterization, + where `mu` is the mean and `alpha` is the scale parameter. + * `(mean, std)` is a moment-based parameterization, where + `mean` is the mean of the distribution and `std` its + standard deviation. + * `(peak, fwhm)` is a shape-based parameterization, where + `peak` is the location of the mode of the distribution, + and `fwhm` is the full-width at half-maximum of the distribution. + Note that the Gamma distribution does not have a maximum when + `alpha < 1`, and therefore no FWHM as well. When `alpha > 1`, + the FWHM does not have a nicely tracktable form, so we use the + Laplace approximation instead (i.e., the FWHM of the best + approximating Gaussian at its peak). + + In Generalized Normal distributions, the mean and peak all equal `mu`. + Furthermore, the distribution is parameterized by a shape parameter + `beta`, with the following special cases: + + * `beta = 0`: Dirac[mu] + * `beta = 1`: Laplace[mu, b=alpha] + * `beta = 2`: Normal[mu, sigma=alpha/sqrt(2)] + * `beta = inf`: Uniform[a=mu-alpha, b=mu+alpha] + + Parameters + ---------- + beta : float | tensor, default=2 + Shape parameter (1 -> Laplace, 2 -> Gaussian). + alpha : float | tensor, default=1 + Scale parameter (Laplace -> b, Gaussian -> sigma/sqrt(2)) + mean, mu, peak : float | tensor, default=0 + Mean. + std : float | tensor + Standard deviation. + fwhm : float | tensor + Full-width at half maximum. + + Returns + ------- + dict + with keys {"mu", "sigma", "mean", "std", "peak", "fwhm"} + + """ + mean = kwargs.pop("mean", kwargs.pop("mu", kwargs.pop("peak", None))) + beta = kwargs.pop("beta", None) + alpha = kwargs.pop("alpha", None) + std = kwargs.pop("std", None) + fwhm = kwargs.pop("fwhm", None) + + mean = 0 if mean is None else mean + beta = 2 if beta is None else beta + + if sum([(alpha is not None), (std is not None), (fwhm is not None)]) > 1: + raise ValueError("Only one of `{alpha, std, fwhm}` should be used.") + + if sum([(alpha is not None), (std is not None), (fwhm is not None)]) == 0: + alpha = 1 + + stdfac = math.exp(0.5*(math.gammaln(3/beta) - math.gammaln(1/beta))) + + if fwhm is not None: + alpha = fwhm / (2 * (LOG2 ** (1/beta))) + std = alpha * stdfac + elif std is not None: + alpha = std / stdfac + fwhm = 2 * alpha * (LOG2 ** (1/beta)) + else: + alpha = 1 if alpha is None else alpha + std = alpha * stdfac + fwhm = 2 * alpha * (LOG2 ** (1/beta)) + + return dict( + beta=beta, + mean=mean, + peak=mean, + mu=mean, + alpha=alpha, + std=std, + fwhm=fwhm, + ) diff --git a/cornucopia/utils/smart_inplace.py b/cornucopia/utils/smart_math.py similarity index 60% rename from cornucopia/utils/smart_inplace.py rename to cornucopia/utils/smart_math.py index 99e4edc..303e4ab 100644 --- a/cornucopia/utils/smart_inplace.py +++ b/cornucopia/utils/smart_math.py @@ -39,6 +39,10 @@ import math import torch +_abs = abs +_min = min +_max = max + def add_(x, y, **kwargs): # d(x+a*y)/dx = 1 @@ -50,6 +54,12 @@ def add_(x, y, **kwargs): return x.add_(y, **kwargs) +def add(x, y, **kwargs): + if not torch.is_tensor(x): + return x + y * kwargs.get('alpha', 1) + return x.add(y, **kwargs) + + def sub_(x, y, **kwargs): # d(x-a*y)/dx = 1 # d(x-a*y)/dy = -a @@ -60,6 +70,12 @@ def sub_(x, y, **kwargs): return x.sub_(y, **kwargs) +def sub(x, y, **kwargs): + if not torch.is_tensor(x): + return x - y * kwargs.get('alpha', 1) + return x.sub(y, **kwargs) + + def mul_(x, y, **kwargs): # d(x*y)/dx = y # d(x*y)/dy = x @@ -72,6 +88,12 @@ def mul_(x, y, **kwargs): ) +def mul(x, y, **kwargs): + if not torch.is_tensor(x): + return x * y + return x.mul(y, **kwargs) + + def div_(x, y, **kwargs): # d(x/y)/dx = 1/y # d(x/y)/dy = -x/y**2 @@ -84,6 +106,12 @@ def div_(x, y, **kwargs): ) +def div(x, y, **kwargs): + if not torch.is_tensor(x): + return x / y + return x.div(y, **kwargs) + + def pow_(x, y, **kwargs): # d(x**y)/dx = y * x**(y-1) # d(x**y)/dy = (x**y) * log(|x|) * sign(x)**y @@ -94,6 +122,12 @@ def pow_(x, y, **kwargs): return x.pow(y, **kwargs) if not inplace else x.pow_(y, **kwargs) +def pow(x, y, **kwargs): + if not torch.is_tensor(x): + return x ** y + return x.pow(y, **kwargs) + + def square_(x, **kwargs): # d(x**2)/dx = 2*x # -> we can overwrite x if we do not backprop through x @@ -102,6 +136,12 @@ def square_(x, **kwargs): return x.square(**kwargs) if x.requires_grad else x.square_(**kwargs) +def square(x, **kwargs): + if not torch.is_tensor(x): + return x * x + return x.square(**kwargs) + + def sqrt_(x, **kwargs): # d(x**0.5)/dx = 0.5*x # -> we can overwrite x if we do not backprop through x @@ -110,6 +150,14 @@ def sqrt_(x, **kwargs): return x.sqrt(**kwargs) if x.requires_grad else x.sqrt_(**kwargs) +def sqrt(x, **kwargs): + # d(x**0.5)/dx = 0.5*x + # -> we can overwrite x if we do not backprop through x + if not torch.is_tensor(x): + return x ** 0.5 + return x.sqrt(**kwargs) + + def atan2_(x, y, **kwargs): if not torch.is_tensor(x) and not torch.is_tensor(y): return math.atan2(x, y) @@ -121,12 +169,28 @@ def atan2_(x, y, **kwargs): return x.atan2(y, **kwargs) if not inplace else x.atan2_(y, **kwargs) +def atan2(x, y, **kwargs): + if not torch.is_tensor(x) and not torch.is_tensor(y): + return math.atan2(x, y) + if not torch.is_tensor(x): + x = torch.as_tensor(x, dtype=y.dtype, device=y.device) + if not torch.is_tensor(y): + y = torch.as_tensor(y, dtype=x.dtype, device=x.device) + return x.atan2(y, **kwargs) + + def neg_(x, **kwargs): if not torch.is_tensor(x): return -x return x.neg_(**kwargs) +def neg(x, **kwargs): + if not torch.is_tensor(x): + return -x + return x.neg(**kwargs) + + def reciprocal_(x, **kwargs): if not torch.is_tensor(x): return 1/x @@ -136,25 +200,85 @@ def reciprocal_(x, **kwargs): ) +def reciprocal(x, **kwargs): + if not torch.is_tensor(x): + return 1/x + return x.reciprocal(**kwargs) + + def abs_(x, **kwargs): if not torch.is_tensor(x): - return abs(x) + return _abs(x) return x.abs(**kwargs) if x.requires_grad else x.abs_(**kwargs) +def abs(x, **kwargs): + if not torch.is_tensor(x): + return _abs(x) + return x.abs(**kwargs) + + def exp_(x, **kwargs): if not torch.is_tensor(x): return math.exp(x) return x.exp(**kwargs) if x.requires_grad else x.exp_(**kwargs) +def exp(x, **kwargs): + if not torch.is_tensor(x): + return math.exp(x) + return x.exp(**kwargs) + + def log_(x, **kwargs): if not torch.is_tensor(x): return math.log(x) return x.log(**kwargs) if x.requires_grad else x.log_(**kwargs) +def log(x, **kwargs): + if not torch.is_tensor(x): + return math.log(x) + return x.log(**kwargs) + + def atan_(x, **kwargs): if not torch.is_tensor(x): return math.atan(x) return x.atan(**kwargs) if x.requires_grad else x.atan_(**kwargs) + + +def atan(x, **kwargs): + if not torch.is_tensor(x): + return math.atan(x) + return x.atan(**kwargs) + + +def min(x, y): + if not torch.is_tensor(x) and not torch.is_tensor(y): + return _min(x, y) + elif torch.is_tensor(x) and torch.is_tensor(y): + return torch.minimum(x, y) + elif torch.is_tensor(x): + return x.clamp_max(y) + else: + assert torch.is_tensor(y) + return y.clamp_max(x) + + +def max(x, y): + if not torch.is_tensor(x) and not torch.is_tensor(y): + return _max(x, y) + elif torch.is_tensor(x) and torch.is_tensor(y): + return torch.maximum(x, y) + elif torch.is_tensor(x): + return x.clamp_min(y) + else: + assert torch.is_tensor(y) + return y.clamp_min(x) + + +def gammaln(x): + if torch.is_tensor(x): + return math.lgamma(x) + return torch.special.gammaln(x) From 1fb342a1ff07398c93e096c7226bc4cff20b6ad7 Mon Sep 17 00:00:00 2001 From: balbasty Date: Fri, 24 Jan 2025 15:29:10 +0000 Subject: [PATCH 3/6] WIP(functional): noise + fov --- cornucopia/functional/_utils.py | 109 ++++- cornucopia/functional/fov.py | 719 +++++++++++++++++++++++++++++ cornucopia/functional/intensity.py | 171 ++++++- cornucopia/functional/noise.py | 144 ++++++ cornucopia/functional/random.py | 18 +- cornucopia/utils/distributions.py | 63 +-- cornucopia/utils/smart_math.py | 68 ++- 7 files changed, 1247 insertions(+), 45 deletions(-) create mode 100644 cornucopia/functional/fov.py create mode 100644 cornucopia/functional/noise.py diff --git a/cornucopia/functional/_utils.py b/cornucopia/functional/_utils.py index 8cec32b..0fa5383 100644 --- a/cornucopia/functional/_utils.py +++ b/cornucopia/functional/_utils.py @@ -1,14 +1,16 @@ # stdlib -from typing import Union, Mapping, Sequence +from typing import Union, Mapping, Sequence, TypeVar # external import torch +T = TypeVar('T') Tensor = torch.Tensor Value = Union[float, Tensor] Output = Union[Tensor, Mapping[Tensor], Sequence[Tensor]] +OneOrMore = Union[T, Sequence[T]] def _unsqz_spatial(x: Value, ndim: int) -> Value: @@ -59,3 +61,108 @@ def _backend_float( if dtype is None or not dtype.is_floating_point: dtype = torch.get_default_dtype() return dict(dtype=dtype, device=device) + + +def _affine2axes(affine): + """ + Compute mappings between voxel (ijk) and anatomical (RAS) axes + + Parameters + ---------- + affine : (D, D) array, optional + Affine matrix (linear part only) + + Returns + ------- + vox2anat : (D,) list[{"LR", "RL", "AP", "PA", "IS", "SI}] + Anatomical axis and polarity of each voxel axis + anat2vox : dict[str, tuple[int, str]] + Voxel axis and polarity of each anatomical axis. + Keys are in `{"LR", "RL", "AP", "PA", "IS", "SI"}`. Values are in + `{(0, "+"), (0, "-"), (1, "+"), (1, "-"), (2, "+"), (2, "-")}` + """ + if affine is None: + # Assume RAS + return ( + ["LR", "PA", "IS"], + {"LR": (0, "+"), "RL": (0, "-"), + "PA": (1, "+"), "AP": (1, "-"), + "IS": (2, "+"), "SI": (2, "-")} + ) + + affine = torch.as_tensor(affine) + ndim = len(affine) + + voxel_size = (affine**2).sum(0)**0.5 + affine = affine / voxel_size + + # add noise to avoid issues if there's a 45 deg angle somewhere + affine = affine + (torch.rand([ndim, ndim]).to(affine) - 0.5) * 1e-5 + + # project onto canonical axes + onehot = affine.square().round().int() + index = [onehot[:, i].tolist().index(1) for i in range(ndim)] + sign = [ + -1 if affine[index[i], i] < 0 else 1 + for i in range(ndim) + ] + anatnames = ['LR', 'PA', 'IS'][:ndim] + voxnames = list(range(ndim)) + + vox2anat = [ + anatnames[index[i]][::-1] if sign[i] else index[i] + for i in range(ndim) + ] + anat2vox = {} + if 'LR' in vox2anat: + anat2vox['LR'] = (voxnames[vox2anat.index('LR')], '+') + anat2vox['RL'] = (voxnames[vox2anat.index('LR')], '-') + else: + anat2vox['RL'] = (voxnames[vox2anat.index('RL')], '+') + anat2vox['LR'] = (voxnames[vox2anat.index('RL')], '-') + if 'PA' in vox2anat: + anat2vox['PA'] = (voxnames[vox2anat.index('PA')], '+') + anat2vox['AP'] = (voxnames[vox2anat.index('PA')], '-') + else: + anat2vox['AP'] = (voxnames[vox2anat.index('AP')], '+') + anat2vox['PA'] = (voxnames[vox2anat.index('AP')], '-') + if 'IS' in vox2anat: + anat2vox['IS'] = (voxnames[vox2anat.index('IS')], '+') + anat2vox['SI'] = (voxnames[vox2anat.index('IS')], '-') + else: + anat2vox['SI'] = (voxnames[vox2anat.index('SI')], '+') + anat2vox['IS'] = (voxnames[vox2anat.index('SI')], '-') + + return vox2anat, anat2vox + + +def _affine2layout(affine) -> str: + vox2anat, _ = _affine2axes(affine) + return "".join(name[-1:] for name in vox2anat) + + +def _axis_name2index(axes, layout): + if not isinstance(layout, (str, list)): + layout = _affine2layout(layout) + if isinstance(layout, str): + layout = [ + {"L": "R", "P": "A", "I": "S"}.get(ax, ax) + for ax in layout.upper() + ] + if isinstance(axes, int): + return axes + if isinstance(axes, str): + axes = axes[0].upper() + axes = {"L": "R", "P": "A", "I": "S"}.get(axes, axes) + return layout.index(axes) + if isinstance(axes, (list, tuple)): + return type(axes)( + _axis_name2index(ax, layout) + for ax in axes + ) + if isinstance(axes, dict): + return type(axes)({ + k: _axis_name2index(ax, layout) + for k, ax in axes.items() + }) + return axes diff --git a/cornucopia/functional/fov.py b/cornucopia/functional/fov.py new file mode 100644 index 0000000..ca94680 --- /dev/null +++ b/cornucopia/functional/fov.py @@ -0,0 +1,719 @@ +__all__ = [ + "flip", + "random_flip", + "perm", + "random_perm", + "rot90", + "rot180", + "random_orient", + "ensure_pow2", + "pad", + "crop", + "patch", + "random_patch", +] +import random +import itertools +from typing import Optional, Union, Tuple + +import torch + +from ..baseutils import prepare_output, returns_update +from ..utils.py import ensure_list, make_vector, prod +from ..utils.padding import pad as _pad +from ..utils import smart_math as math +from ._utils import ( + Tensor, OneOrMore, Output, _affine2layout, _axis_name2index +) + + +def flip( + input: Tensor, + axes: Optional[OneOrMore[Union[int, str]]] = None, + orient: Union[str, Tensor] = "RAS", + copy: bool = False, + **kwargs +) -> Output: + """ + Flip one or more spatial axes. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + axes : [list of] (int | {"LR", "AP", "IS"}) + Axes to flip, by index or by name. + Indices correspond to spatial axes only (0 = first spatial dim, etc.) + If None, flip all spatial axes. + orient : str or tensor + Tensor layout (`{"RAS", "LPS", ...}`) or orient matrix. + copy : bool + Copy the input even if no axes are flipped. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "axes"} + + Returns + ------- + out : (C, *spatial) tensor + Output tensor. + """ + ndim = input.ndim - 1 + + axes_ = axes + if axes_ is None: + axes_ = list(range(ndim)) + axes_ = ensure_list(axes_) + + if len(axes) == 0: + + output = input.clone() if copy else input + + else: + + # str to index + if any(isinstance(ax, str) for ax in axes_): + axes_ = _axis_name2index(axes_, orient) + + # neg to pos + axes_ = [ndim + ax if ax < 0 else ax for ax in axes_] + + # flip + output = input.flip([1 + ax for ax in axes_]) + + return prepare_output( + {"output": output, "input": input, "axes": axes}, + kwargs.pop("returns", "output") + ) + + +def random_flip( + input: Tensor, + axes: Optional[OneOrMore[Union[int, str]]] = None, + orient: Union[Tensor, str] = "RAS", + copy: bool = False, + **kwargs +) -> Output: + """ + Flip one or more spatial axes. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + axes : [list of] (int | {"LR", "AP", "IS"}) + Axes that can be flipped, by index or by name. + Indices correspond to spatial axes only (0 = first spatial dim, etc.) + If None, all spatial axes can be flipped. + orient : str or tensor + Tensor layout (`{"RAS", "LPS", ...}`) or orient matrix. + copy : bool + Copy the input even if no axes are flipped. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "axes"} + + Returns + ------- + out : (C, *spatial) tensor + Output tensor. + """ + ndim = input.ndim - 1 + + if axes is None: + axes = list(range(ndim)) + axes = list(ensure_list(axes)) + + # sample axes to flip + random.shuffle(axes) + axes = axes[:random.randint(0, len(axes))] + + return flip(input, axes, orient, copy, **kwargs) + + +def perm( + input: Tensor, + perm: Optional[OneOrMore[Union[int, str]]] = None, + orient: Union[str, Tensor] = "RAS", + copy: bool = False, + **kwargs +) -> Output: + """ + Permute one or more spatial axes. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + perm : [list of] (int | {"LR", "AP", "IS"}) + Axes permutation, by index or by name. + Indices correspond to spatial axes only (0 = first spatial dim, etc.) + If None, inverse axis order. + orient : str or tensor + Tensor layout (`{"RAS", "LPS", ...}`) or orient matrix. + copy : bool + Copy the input (rather than returning a view). + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "perm"} + + Returns + ------- + out : (C, *spatial) tensor + Output tensor. + """ + ndim = input.ndim - 1 + + perm_ = perm + if perm_ is None: + perm_ = list(range(ndim))[::-1] + perm_ = ensure_list(perm_) + + # str to index + if any(isinstance(ax, str) for ax in perm_): + perm_ = _axis_name2index(perm_, orient) + + # neg to pos + perm_ = [ndim + ax if ax < 0 else ax for ax in perm_] + + # permute + output = input.permute(0, *[1 + ax for ax in perm]) + + if copy: + output = output.clone() + + return prepare_output( + {"output": output, "input": input, "perm": perm}, + kwargs.pop("returns", "output") + ) + + +def random_perm( + input: Tensor, + axes: Optional[OneOrMore[Union[int, str]]] = None, + orient: Union[str, Tensor] = "RAS", + copy: bool = False, + **kwargs +) -> Output: + """ + Flip one or more spatial axes. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + axes : [list of] (int | {"LR", "AP", "IS"}) + Axes that can be permuted, by index or by name. + Indices correspond to spatial axes only (0 = first spatial dim, etc.) + If None, all spatial axes can be flipped. + orient : str or tensor + Tensor layout (`{"RAS", "LPS", ...}`) or orient matrix. + copy : bool + Copy the input (rather than returning a view). + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "perm"} + + Returns + ------- + out : (C, *spatial) tensor + Output tensor. + """ + ndim = input.ndim - 1 + if axes is None: + axes = list(range(ndim)) + axes = list(ensure_list(axes)) + + # replace strings with integers + if any(isinstance(ax, str) for ax in axes): + axes = _axis_name2index(axes, orient) + + # sample axes to flip + prm_axes = list(axes) + random.shuffle(prm_axes) + + # build full permutation + all_axes = list(range(ndim)) + for i, ax in zip(axes, prm_axes): + all_axes[i] = ax + + return perm(input, all_axes, copy=copy, **kwargs) + + +def rot90( + input: Tensor, + plane: OneOrMore[Union[Tuple[int, int], str]] = (0, 1), + negative: OneOrMore[bool] = False, + double: OneOrMore[bool] = False, + orient: Union[str, Tensor] = "RAS", + copy: bool = False, + **kwargs +) -> Output: + """ + Rotate 90 degrees about an axis. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + plane : [list of] ((int, int) | {"axial", "coronal", "sagittal"}) + Rotation plane. + negative : [list of] bool + Rotate by -90 deg instead of 90 deg. + double : [list of] bool + Rotate by 180 deg instead of 90 deg. + orient : str or tensor + Tensor layout (`{"RAS", "LPS", ...}`) or orient matrix. + copy : bool + Always copy the input (even if a view could be returned). + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "plane", "negative", "double"} + + Returns + ------- + out : (C, *spatial) tensor + Output tensor. + """ # noqa: E501 + ndim = input.ndim - 1 + + if plane is None or len(plane) == 0: + + output = input.clone() if copy else input + + else: + + plane_ = plane + if isinstance(plane_, str) or isinstance(plane_[0], int): + plane_ = [plane_] + + plane_ = list(ensure_list(plane_)) + negative_ = ensure_list(negative, len(plane_)) + double_ = ensure_list(double, len(plane_)) + + # Convert named planes to indices + if any(isinstance(p, str) for p in plane_): + if not isinstance(orient, str): + orient = _affine2layout(orient) + orient = [ + {"L": "R", "P": "A", "I": "S"}.get(ax, ax) + for ax in orient.upper() + ] + + for i, p in enumerate(plane_): + if not isinstance(p, str): + continue + p = p[0].lower() + if p == "c": + plane_[i] = (orient.index("R"), orient.index("S")) + elif p == "a": + plane_[i] = (orient.index("R"), orient.index("P")) + elif p == "s": + plane_[i] = (orient.index("P"), orient.index("S")) + + # Apply all rotations sequentially + for p, n, d in zip(plane_, negative_, double_): + # neg to pos + add 1 for channel dimension + p = [1 + (ndim + ax if ax < 0 else ax) for ax in p] + + # add 1 for channel dimension + if d: + # 180 deg rotation == flip both axes + output = input.flip(p) + else: + # 90 deg rotation == flip one axis, permute axes, then flip one + output = input.transpose(*p) + output = output.flip(p[1 if n else 0]) + + return prepare_output( + {"output": output, "input": input, + "plane": plane, "negative": negative, "double": double}, + kwargs.pop("returns", "output") + ) + + +def rot180( + input: Tensor, + plane: OneOrMore[Union[Tuple[int, int], str]] = (0, 1), + orient: Union[str, Tensor] = "RAS", + copy: bool = False, + **kwargs +) -> Output: + """ + Rotate 180 degrees about an axis. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + plane : [list of] ((int, int) | {"axial", "coronal", "sagittal"}) + Rotation plane. + orient : str or tensor + Tensor layout (`{"RAS", "LPS", ...}`) or orient matrix. + copy : bool + Always copy the input (even if a view could be returned). + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "plane"} + + Returns + ------- + out : (C, *spatial) tensor + Output tensor. + """ # noqa: E501 + return rot90(input, plane, double=True, orient=orient, copy=copy, **kwargs) + + +def random_orient( + input: Tensor, + posdet: bool = True, + copy: bool = False, + **kwargs +) -> Output: + """ + Randomly reorient a tensor. + + Each pose has equal probability. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + posdet : bool + Only accept transformations with a positive determinant. + copy : bool + Always copy the input (even if a view could be returned). + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "perm", "flip"} + + Returns + ------- + out : (C, *spatial) tensor + Output tensor. + """ + ndim = input.ndim - 1 + + def det(transformation): + perm, flip = transformation + det_perm = torch.eye(ndim)[perm].det() + det_flip = prod(flip) + return det_perm * det_flip + + # find all possible transformations + perms = itertools.permutations(range(ndim)) + flips = itertools.product([True, False], repeat=ndim) + xforms = itertools.product(perms, flips) + if posdet: + xforms = (xform for xform in xforms if det(xform) > 0) + + # sample transformation + xforms = list(xforms) + nforms = len(xforms) + perm_, flip_ = xforms[random.randint(0, nforms-1)] + flip_ = [i for i, f in enumerate(flip_) if f] + + # apply transformation + output = flip(perm(input, perm_), flip_, copy=copy) + + return prepare_output( + {"output": output, "input": input, "perm": perm_, "flip": flip_}, + kwargs.pop("returns", "output") + ) + + +def ensure_pow2( + input: Tensor, + exponent: int = 1, + bound: str = 'zero', + **kwargs +) -> Output: + """ + Pad the volume such that the tensor shape can be divided by `2**exponent`. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + exponent : [list of] int + Exponent of the power of two. + bound : [list of] str + Boundary conditions used for padding. + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + """ + shape = input.shape[1:] + exponent = ensure_list(exponent, len(shape)) + bigshape = [max(2 ** e, s) for e, s in zip(exponent, shape)] + return patch(input, bigshape, bound=bound, **kwargs) + + +def pad( + input: Tensor, + size: OneOrMore[Union[int, Tuple[int, int]]], + unit: str = "vox", + bound: str = "zero", + side: Optional[str] = "both", + **kwargs +) -> Output: + """ + Pad (or crop) a tensor. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + size : [list of] (int | tuple[int, int]) + Padding (or cropping, if negative) per dimension. + Tuples can be used to indicate different values on the left and right. + unit : {"vox", "pct"} + Unit of the padding size (voxels or percentage of the field of view). + bound : [list of] str + Boundary conditions used for padding. + side : {"pre", "post", "both"} + Apply padding on only one side, or on both. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "size"} + + Returns + ------- + output : (C, *spatial) + Output tensor + """ + ndim = input.ndim - 1 + + size_ = size + if isinstance(size_, (tuple, int, float)): + size_ = [size_] * ndim + size_ = ensure_list(size_) + size_ = max(0, ndim-len(size_)) * [0] + size_ + size_ = size_[:ndim] + + # fill left/right + size_ = [ + (p, p) if isinstance(p, (int, float)) and side == "both" else + (p, 0) if isinstance(p, (int, float)) and side == "pre" else + (0, p) if isinstance(p, (int, float)) and side == "post" else + p for p in size_ + ] + # convert to voxels + if unit[0].lower() == "v": + size_ = [ + (int(round(q*s)) for q in p) + for p, s in zip(size_, input.shape[1:]) + ] + # convert to `pad` format + size_ = [q for p in size_ for q in p] + # add channel dimension + size_ = [0, 0] + size_ + + # apply padding + output = _pad(input, size_, mode=bound) + + return prepare_output( + {"output": output, "input": input, "size": size}, + kwargs.pop("returns", "output") + ) + + +def crop( + input: Tensor, + size: OneOrMore[Union[int, Tuple[int, int]]], + unit: str = "vox", + bound: str = "zero", + side: Optional[str] = "both", + copy: bool = False, + **kwargs +) -> Output: + """ + Crop (or pad) a tensor. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + size : [list of] (int | tuple[int, int]) + Cropping (or padding, if negative) per dimension. + Tuples can be used to indicate different values on the left and right. + unit : {"vox", "pct"} + Unit of the padding size (voxels or percentage of the field of view). + bound : [list of] str + Boundary conditions used for padding. + side : {"pre", "post", "both"} + Apply cropping on only one side, or on both. + copy : bool + Return a copy rather than a view. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "size"} + + Returns + ------- + output : (C, *spatial) + Output tensor + """ + kwargs.setdefault("returns", "output") + ndim = input.ndim - 1 + + size_ = size + if isinstance(size_, (tuple, int, float)): + size_ = [size_] * ndim + size_ = ensure_list(size_) + size_ = max(0, ndim-len(size_)) * [0] + size_ + size_ = size_[:ndim] + + # If negative size, defer to pad (with opposite size) + if any( + (x < 0) if isinstance(x, int) else any(y < 0 for y in x) + for x in size_ + ): + size_ = [ + (-x) if isinstance(x, int) else tuple(-y for y in x) + for x in size_ + ] + output = pad(input, size, bound, side, **kwargs) + return returns_update(size, "size", output, kwargs["returns"]) + + # Otherwise, use slices + + # fill left/right + size_ = [ + (p, p) if isinstance(p, (int, float)) and side == "both" else + (p, 0) if isinstance(p, (int, float)) and side == "pre" else + (0, p) if isinstance(p, (int, float)) and side == "post" else + p for p in size_ + ] + # convert to voxels + if unit[0].lower() == "v": + size_ = [ + (int(round(q*s)) for q in p) + for p, s in zip(size_, input.shape[1:]) + ] + # convert to slicer + slicer = tuple( + slice(s[0], (-s[1]) or None) + for s in size_ + ) + output = input[(Ellipsis,) + slicer] + + if copy: + output = output.clone() + + return prepare_output( + {"output": output, "input": input, "size": size}, + kwargs.pop("returns", "output") + ) + + +def patch( + input: Tensor, + shape: OneOrMore[int] = 64, + center: OneOrMore[float] = 0, + bound: str = "zero", + copy: bool = False, + **kwargs +) -> Output: + """ + Extract a patch from the volume. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + shape : [list of] int + Patch shape + center : [list of] float + Patch center, in relative coordinates -1..1 + bound : str + Boundary condition in case padding is needed + copy : bool + Return a copy rather than a view. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "center"} + + Returns + ------- + output : (C, *shape) + Output tensor + + """ + # NOTE not differentiable wrt `center`, since we force the patch to + # be aligned with the input lattice. If we want differentiability, + # we need to use some sort of interpolation. + + ndim = input.ndim - 1 + ishape = input.shape[1:] + shape_ = ensure_list(shape, ndim) + center_ = make_vector(center, ndim).tolist() + center_ = [(c + 1) / 2 * (s - 1) for c, s in zip(center_, ishape)] + crop_size = [] + for ss, cc, sv in zip(shape_, center_, ishape): + first = int(math.floor(cc - ss/2)) + last = first + ss + left, right = first, sv - last + crop_size.append((left, right)) + + output = crop(input, crop_size, bound=bound, copy=copy) + + return prepare_output( + {"output": output, "input": input, "center": center}, + kwargs.pop("returns", "output") + ) + + +def random_patch( + input: Tensor, + shape: OneOrMore[int] = 64, + bound: str = "zero", + copy: bool = False, + **kwargs +) -> Output: + """ + Extract a random patch from the volume. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + shape : [list of] int + Patch shape + bound : str + Boundary condition in case padding is needed + (only needed if the input shape is smaller than the patch shape). + copy : bool + Return a copy rather than a view. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "center"} + + Returns + ------- + output : (C, *shape) + Output tensor + + """ + ishape = input.shape[1:] + shape_ = ensure_list(shape, len(ishape)) + min_center = [max(p/s - 1, -1) for p, s in zip(shape_, ishape)] + max_center = [min(1 - p/s, 1) for p, s in zip(shape_, ishape)] + center = [ + random.random() * (mx - mn) + mn + for mn, mx in zip(min_center, max_center) + ] + return patch(input, shape, center, bound, copy, **kwargs) diff --git a/cornucopia/functional/intensity.py b/cornucopia/functional/intensity.py index f44d437..71bf6fa 100644 --- a/cornucopia/functional/intensity.py +++ b/cornucopia/functional/intensity.py @@ -17,9 +17,11 @@ "quantile_transform", "minmax_transform", "affine_intensity_transform", + "add_smooth_random_field", + "mul_smooth_random_field", ] # stdlib -from typing import Union, Mapping, Sequence, Optional, Callable +from typing import Sequence, Optional, Callable # external import torch @@ -29,12 +31,9 @@ # internal from ..baseutils import prepare_output, returns_update, return_requires from ..utils.smart_math import add_, mul_, pow_, div_ -from ._utils import _unsqz_spatial - - -Tensor = torch.Tensor -Value = Union[float, Tensor] -Output = Union[Tensor, Mapping[Tensor], Sequence[Tensor]] +from ..utils.py import ensure_list +from ._utils import _unsqz_spatial, Tensor, Value, Output, OneOrMore +from .random import random_field_like def binop_value( @@ -1076,3 +1075,161 @@ def affine_intensity_transform( "omin": omin, "omax": omax, }, kwargs["returns"]) + + +def add_smooth_random_field( + input: Tensor, + shape: OneOrMore[int] = 8, + mean: Optional[Value] = None, + fwhm: Optional[Value] = None, + distrib: str = "uniform", + order: int = 3, + shared: bool = False, + **kwargs +) -> Output: + """ + Add a smooth random field to the input. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor + shape : int | list[int] + Shape of the low-resolution spline coefficients (lower = smoother) + mean : float | ([C],) tensor, default=0 + Distribution mean. + fwhm : float | ([C],) tensor, default=1 + Distribution full width at half-maximum. + distrib : {"uniform", "gaussian", "generalized"} + Probability distribution. + order : int + Spline order + shared : bool + Apply the same field to all channels. + If True, probability parameters must be scalars. + + Other Parameters + ---------------- + peak, std, vmin, vmax, alpha, beta : float | ([C],) tensor + Other parameters of the probability distribution. + returns : [list or dict of] {"output", "input", "coeff", "field"} + Values to return. + + Returns + ------- + output : (C, *spatial) tensor + + """ # noqa: E501 + if ( + (mean is None) and + (kwargs.get("mu", None) is not None) and + (kwargs.get("peak", None) is not None) and + (kwargs.get("vmin", None) is not None) and + (kwargs.get("vmax", None) is not None) + ): + mean = 0 + + if ( + (fwhm is None) and + (kwargs.get("sigma", None) is not None) and + (kwargs.get("std", None) is not None) and + (kwargs.get("vmin", None) is not None) and + (kwargs.get("vmax", None) is not None) and + (kwargs.get("alpha", None) is not None) + ): + fwhm = 1 + + ndim = input.ndim - 1 + shape = tuple(ensure_list(shape, ndim)) + if shared: + shape = (1,) + shape + else: + shape = input.shape[:-1] + shape + + returns = kwargs.pop("returns", "output") + kwargs["mean"] = mean + kwargs["fwhm"] = fwhm + coeff = random_field_like(distrib, input, shape, **kwargs) + output = add_field(input, coeff, order, prefilter=False, returns=returns) + + output = returns_update(coeff, "coeff", output, returns) + return output + + +def mul_smooth_random_field( + input: Tensor, + shape: OneOrMore[int] = 8, + mean: Optional[Value] = None, + fwhm: Optional[Value] = None, + distrib: str = "uniform", + order: int = 3, + shared: bool = False, + **kwargs +) -> Output: + """ + Multiple the input with a (positive) smooth random field. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor + shape : int | list[int] + Shape of the low-resolution spline coefficients (lower = smoother) + mean : float | ([C],) tensor, default=1 + Distribution mean. + fwhm : float | ([C],) tensor, default=1 + Distribution full width at half-maximum. + distrib : {"uniform", "gamma", "lognormal"} + Probability distribution. + order : int + Spline order + shared : bool + Apply the same field to all channels. + If True, probability parameters must be scalars. + + Other Parameters + ---------------- + peak, std, vmin, vmax, alpha, beta, mu, sigma : float | ([C],) tensor + Other parameters of the probability distribution. + returns : [list or dict of] {"output", "input", "coeff", "field"} + Values to return. + + Returns + ------- + output : (C, *spatial) tensor + + """ # noqa: E501 + if ( + (mean is None) and + (kwargs.get("mu", None) is not None) and + (kwargs.get("peak", None) is not None) and + (kwargs.get("vmin", None) is not None) and + (kwargs.get("vmax", None) is not None) + ): + mean = 1 + + if ( + (fwhm is None) and + (kwargs.get("sigma", None) is not None) and + (kwargs.get("std", None) is not None) and + (kwargs.get("vmin", None) is not None) and + (kwargs.get("vmax", None) is not None) and + (kwargs.get("alpha", None) is not None) + ): + fwhm = 1 + + ndim = input.ndim - 1 + shape = tuple(ensure_list(shape, ndim)) + if shared: + shape = (1,) + shape + else: + shape = input.shape[:-1] + shape + + returns = kwargs.pop("returns", "output") + kwargs["mean"] = mean + kwargs["fwhm"] = fwhm + coeff = random_field_like(distrib, input, shape, **kwargs) + output = add_field(input, coeff, order, prefilter=False, returns=returns) + + output = returns_update(coeff, "coeff", output, returns) + return output diff --git a/cornucopia/functional/noise.py b/cornucopia/functional/noise.py new file mode 100644 index 0000000..f108eb4 --- /dev/null +++ b/cornucopia/functional/noise.py @@ -0,0 +1,144 @@ +__all__ = [ + "noisify_gaussian", + "noisify_gamma", + "noisify_chi", +] +from typing import Optional + +from ..baseutils import prepare_output +from ..utils import smart_math as math +from ._utils import Tensor, Value, Output, _unsqz_spatial +from .random import random_field_gaussian_like + + +def noisify_gaussian( + input: Tensor, + std: Value = 0.1, + gfactor: Optional[Tensor] = None, + **kwargs +) -> Output: + """ + Apply additive Gaussian noise + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + std : float | ([C],) tensor + Standard deviation + gfactor : ([C], *spatial) tensor + Gfactor map that scales noise locally. + + Other Parameters + ---------------- + returns : {"output", "input", "noise"} + + Returns + ------- + output : (C, *spatial) tensor + Output tensor + + """ + noise = random_field_gaussian_like(input, std=std) + if gfactor is not None: + noise = math.mul_(noise, gfactor) + output = math.add_(noise, input) + return prepare_output( + {"input": input, "output": output, "noise": noise, "gfactor": gfactor}, + kwargs.pop("returns", "output") + ) + + +def noisify_gamma( + input: Tensor, + std: Value = 0.1, + gfactor: Optional[Tensor] = None, + **kwargs +) -> Output: + """ + Apply multiplicative Gamma noise + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + std : float | ([C],) tensor + Standard deviation + gfactor : ([C], *spatial) tensor + Gfactor map that scales noise locally. + + Other Parameters + ---------------- + returns : {"output", "input", "noise"} + + Returns + ------- + output : (C, *spatial) tensor + Output tensor + + """ + noise = random_field_gaussian_like(input, std=std) + if gfactor is not None: + noise = math.mul_(noise, gfactor) + output = math.mul_(noise, input) + return prepare_output( + {"input": input, "output": output, "noise": noise}, + kwargs.pop("returns", "output") + ) + + +def noisify_chi( + input: Tensor, + std: Value = 0.1, + df: int = 2, + gfactor: Optional[Tensor] = None, + **kwargs +) -> Output: + """ + Apply non-central Chi noise + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + std : float | ([C],) tensor + Standard deviation. + df : int + Number of independant noise sources. + gfactor : ([C], *spatial) tensor + Gfactor map that scales noise locally. + + Other Parameters + ---------------- + returns : {"output", "input", "noise"} + + Returns + ------- + output : (C, *spatial) tensor + Output tensor + + """ + # generate Chi-squared noise + noise = 0 + for _ in range(df): + noise += random_field_gaussian_like(input).square_() + + # scale to reach target variance + mu = math.sqrt(2) * math.gamma((df+1)/2) / math.gamma(df/2) + scale = (std * std) / (df - mu*mu) + noise = math.mul_(noise, _unsqz_spatial(scale, input.ndim-1)) + + # gfactor scaling (squared because noise is squared) + if gfactor is not None: + noise = math.mul_(math.mul_(noise, gfactor), gfactor) + + # apply noise + output = math.sqrt_(math.add_(input.square(), noise)) + + # sqrt to get Chi noise + noise = math.sqrt_(noise) + + return prepare_output( + {"input": input, "output": output, "noise": noise}, + kwargs.pop("returns", "output") + ) diff --git a/cornucopia/functional/random.py b/cornucopia/functional/random.py index 054df37..85bd846 100644 --- a/cornucopia/functional/random.py +++ b/cornucopia/functional/random.py @@ -41,7 +41,7 @@ def random_field(name: str, shape: Sequence[int], **kwargs) -> Output: Parameters ---------- - name : {"uniform", "gaussian", "gamma", "lognormal", "generalized-gaussian"} + name : {"uniform", "gaussian", "gamma", "lognormal", "generalized"} Distribution name. shape : list[int] Output shape, including the channel dimension (!!): (C, *spatial). @@ -74,8 +74,8 @@ def random_field(name: str, shape: Sequence[int], **kwargs) -> Output: return random_field_gamma(shape, **kwargs) if name in ("lognormal", "log-normal"): return random_field_lognormal(shape, **kwargs) - if name in ("generalized-normal", "generalized-gaussian"): - return random_field_generalized_normal(shape, **kwargs) + if name in ("generalized", "generalised"): + return random_field_generalized(shape, **kwargs) def random_field_uniform( @@ -324,7 +324,7 @@ def random_field_gamma( return prepare_output({"output": output, **prm}, kwargs["returns"]) -def random_field_generalized_normal( +def random_field_generalized( shape: Sequence[int], mean: Optional[Value] = None, std: Optional[Value] = None, @@ -425,7 +425,7 @@ def random_field_like( Parameters ---------- - name : {"uniform", "gaussian", "gamma", "lognormal", "generalized-gaussian"} + name : {"uniform", "gaussian", "gamma", "lognormal", "generalized"} Distribution name. input : tensor Tensor from which to copy the data type, device and shape @@ -460,8 +460,8 @@ def random_field_like( return random_field_gamma_like(input, shape, **kwargs) if name in ("lognormal", "log-normal"): return random_field_lognormal_like(input, shape, **kwargs) - if name in ("generalized-normal", "generalized-gaussian"): - return random_field_generalized_normal_like(input, shape, **kwargs) + if name in ("generalized", "generalised"): + return random_field_generalized_like(input, shape, **kwargs) def _random_field_like( @@ -694,7 +694,7 @@ def random_field_gamma_like( ) -def random_field_generalized_normal_like( +def random_field_generalized_like( input: Tensor, shape: Optional[Sequence[int]] = None, mean: Optional[Value] = None, @@ -760,5 +760,5 @@ def random_field_generalized_normal_like( Output tensor. """ # noqa: E501 return _random_field_like( - random_field_lognormal, input, shape, mean, std, beta, **kwargs + random_field_generalized, input, shape, mean, std, beta, **kwargs ) diff --git a/cornucopia/utils/distributions.py b/cornucopia/utils/distributions.py index 09e8141..09eddfd 100644 --- a/cornucopia/utils/distributions.py +++ b/cornucopia/utils/distributions.py @@ -20,6 +20,15 @@ def wrapper(func): return wrapper +def _get_prm(*names, **kwargs): + value = None + for name in names: + value = kwargs.get(name, None) + if value is not None: + break + return value + + def distribution_parameters(name: str, **kwargs) -> dict: """ Compute the natural parameters of a distribution from any parameterization. @@ -28,7 +37,7 @@ def distribution_parameters(name: str, **kwargs) -> dict: Parameters ---------- - name : {"uniform", "gaussian", "lognormal", "gamma", "generalized-normal"} + name : {"uniform", "gaussian", "lognormal", "gamma", "generalized"} Distribution name. Parameters common to most distribution @@ -132,11 +141,11 @@ def uniform_parameters(**kwargs) -> dict: with keys {"a", "b", "vmin", "vmax", "mean", "std", "fwhm"} """ - vmin = kwargs.get("a", kwargs.get("vmin", None)) - vmax = kwargs.get("b", kwargs.get("vmax", None)) - mean = kwargs.pop("mean", kwargs.pop("mu", None)) - std = kwargs.pop("std", kwargs.pop("sigma", None)) - fwhm = kwargs.pop("fwhm", None) + vmin = _get_prm("a", "vmin", **kwargs) + vmax = _get_prm("b", "vmax", **kwargs) + mean = _get_prm("mean", "mu", **kwargs) + std = _get_prm("std", "sigma", **kwargs) + fwhm = _get_prm("fwhm", **kwargs) if (mean is not None) or (std is not None) or (fwhm is not None): if ((mean is None) or (std is None and fwhm is None)): @@ -194,9 +203,9 @@ def gaussian_parameters(**kwargs) -> dict: with keys {"mu", "sigma", "mean", "std", "peak", "fwhm"} """ - mean = kwargs.pop("mean", kwargs.pop("mu", kwargs.pop("peak", None))) - std = kwargs.pop("std", kwargs.pop("sigma", None)) - fwhm = kwargs.pop("fwhm", None) + mean = _get_prm("mean", "mu", "peak", **kwargs) + std = _get_prm("std", "sigma", **kwargs) + fwhm = _get_prm("fwhm", **kwargs) mean = 0 if mean is None else mean std = 1 if std is None else std @@ -264,12 +273,12 @@ def lognormal_parameters(**kwargs) -> dict: # FWHM of lognormal taken here: # http://openafox.com/science/peak-function-derivations.html#lognormal - mean = kwargs.pop("mean", None) - std = kwargs.pop("std", None) - fwhm = kwargs.pop("fwhm", None) - peak = kwargs.pop("peak", None) - mu = kwargs.pop("mu", None) - sigma = kwargs.pop("sigma", None) + mean = _get_prm("mean", **kwargs) + std = _get_prm("std", **kwargs) + fwhm = _get_prm("fwhm", **kwargs) + peak = _get_prm("peak", **kwargs) + mu = _get_prm("mu", **kwargs) + sigma = _get_prm("sigma", **kwargs) if (mu is not None) or (sigma is not None): if ((mu is None) or (sigma is None)): @@ -383,12 +392,12 @@ def gamma_parameters(**kwargs) -> dict: with keys {"alpha", "beta", "mean", "std", "peak", "fwhm"} """ - mean = kwargs.pop("mu", kwargs.pop("mean", None)) - std = kwargs.pop("sigma", kwargs.pop("std", None)) - alpha = kwargs.pop("alpha", None) - beta = kwargs.pop("beta", None) - peak = kwargs.pop("peak", None) - fwhm = kwargs.pop("fwhm", None) + mean = _get_prm("mu", "mean", **kwargs) + std = _get_prm("sigma", "std", **kwargs) + alpha = _get_prm("alpha", **kwargs) + beta = _get_prm("beta", **kwargs) + peak = _get_prm("peak", **kwargs) + fwhm = _get_prm("fwhm", **kwargs) if (alpha is not None) or (beta is not None): if ((alpha is None) or (beta is None)): @@ -444,7 +453,7 @@ def gamma_parameters(**kwargs) -> dict: ) -@_register_parameterization(["generalized-gaussian", "generalized-normal"]) +@_register_parameterization(["generalized", "generalised"]) def generalized_normal_parameters(**kwargs) -> dict: """ Compute the parameters of a generalized Gaussian distribution from @@ -494,11 +503,11 @@ def generalized_normal_parameters(**kwargs) -> dict: with keys {"mu", "sigma", "mean", "std", "peak", "fwhm"} """ - mean = kwargs.pop("mean", kwargs.pop("mu", kwargs.pop("peak", None))) - beta = kwargs.pop("beta", None) - alpha = kwargs.pop("alpha", None) - std = kwargs.pop("std", None) - fwhm = kwargs.pop("fwhm", None) + mean = _get_prm("mu", "mean", "peak", **kwargs) + beta = _get_prm("beta", **kwargs) + alpha = _get_prm("alpha", **kwargs) + std = _get_prm("std", **kwargs) + fwhm = _get_prm("fwhm", **kwargs) mean = 0 if mean is None else mean beta = 2 if beta is None else beta diff --git a/cornucopia/utils/smart_math.py b/cornucopia/utils/smart_math.py index 303e4ab..5ce88ac 100644 --- a/cornucopia/utils/smart_math.py +++ b/cornucopia/utils/smart_math.py @@ -44,6 +44,15 @@ _max = max +def _shape_compat(x, y): + if not torch.is_tensor(y): + return True + ndim = x.ndim + if y.ndim > ndim: + return False + return all(dx >= dy for dx, dy in zip(x.shape[-ndim:], y.shape[-ndim:])) + + def add_(x, y, **kwargs): # d(x+a*y)/dx = 1 # d(x+a*y)/dy = a @@ -51,6 +60,8 @@ def add_(x, y, **kwargs): # -> we can overwrite x if not torch.is_tensor(x): return x + y * kwargs.get('alpha', 1) + if not _shape_compat(x, y): + return x.add(y, **kwargs) return x.add_(y, **kwargs) @@ -67,6 +78,8 @@ def sub_(x, y, **kwargs): # -> we can overwrite x if not torch.is_tensor(x): return x - y * kwargs.get('alpha', 1) + if not _shape_compat(x, y): + return x.sub(y, **kwargs) return x.sub_(y, **kwargs) @@ -82,6 +95,8 @@ def mul_(x, y, **kwargs): # -> we can overwrite x if we do not backprop through y if not torch.is_tensor(x): return x * y + if not _shape_compat(x, y): + return x.mul(y, **kwargs) return ( x.mul(y, **kwargs) if getattr(y, 'requires_grad', False) else x.mul_(y, **kwargs) @@ -100,6 +115,8 @@ def div_(x, y, **kwargs): # -> we can overwrite x if we do not backprop through y if not torch.is_tensor(x): return x / y + if not _shape_compat(x, y): + return x.div(y, **kwargs) return ( x.div(y, **kwargs) if getattr(y, 'requires_grad', False) else x.div_(y, **kwargs) @@ -118,6 +135,8 @@ def pow_(x, y, **kwargs): # -> we can overwrite x if we do not backprop through x or y if not torch.is_tensor(x): return x ** y + if not _shape_compat(x, y): + return x.pow(y, **kwargs) inplace = not (x.requires_grad or getattr(y, 'requires_grad', False)) return x.pow(y, **kwargs) if not inplace else x.pow_(y, **kwargs) @@ -165,8 +184,10 @@ def atan2_(x, y, **kwargs): x = torch.as_tensor(x, dtype=y.dtype, device=y.device) if not torch.is_tensor(y): y = torch.as_tensor(y, dtype=x.dtype, device=x.device) + if not _shape_compat(x, y): + return x.atan2(y, **kwargs) inplace = not (x.requires_grad or y.requires_grad) - return x.atan2(y, **kwargs) if not inplace else x.atan2_(y, **kwargs) + return x.atan2_(y, **kwargs) if inplace else x.atan2(y, **kwargs) def atan2(x, y, **kwargs): @@ -282,3 +303,48 @@ def gammaln(x): if torch.is_tensor(x): return math.lgamma(x) return torch.special.gammaln(x) + + +def gamma(x): + # !!! Assumes x is positive + return exp_(gammaln(x)) + + +def floor(x, to=None): + if torch.is_tensor(x): + to = { + int: torch.long, + float: torch.float, + complex: torch.complex32 + }.get(to, to) + return x.floor().to(dtype=to) + to = { + torch.int: int, + torch.long: int, + torch.float: float, + torch.double: float, + torch.complex32: complex, + torch.complex64: complex, + None: (lambda x: x) + }.get(to, to) + return to(math.floor(x)) + + +def ceil(x, to=None): + if torch.is_tensor(x): + to = { + int: torch.long, + float: torch.float, + complex: torch.complex32 + }.get(to, to) + return x.ceil().to(dtype=to) + to = { + torch.int: int, + torch.long: int, + torch.float: float, + torch.double: float, + torch.complex32: complex, + torch.complex64: complex, + None: (lambda x: x) + }.get(to, to) + return to(math.ceil(x)) From f74e13fd0168a9b7d06c2919ce1914cc67d3fa91 Mon Sep 17 00:00:00 2001 From: balbasty Date: Fri, 24 Jan 2025 17:58:14 +0000 Subject: [PATCH 4/6] WIP(functional): geometric --- cornucopia/functional/geometric.py | 537 +++++++++++++++++++++++++++++ cornucopia/functional/intensity.py | 36 +- 2 files changed, 555 insertions(+), 18 deletions(-) create mode 100644 cornucopia/functional/geometric.py diff --git a/cornucopia/functional/geometric.py b/cornucopia/functional/geometric.py new file mode 100644 index 0000000..8789e15 --- /dev/null +++ b/cornucopia/functional/geometric.py @@ -0,0 +1,537 @@ +__all__ = [ + "exp_velocity", + "apply_flow", + "apply_random_flow", + "make_affine_matrix", + "make_affine_flow", + "apply_affine_matrix", + "apply_affine", +] +from typing import Optional, Sequence +import torch +from ..baseutils import prepare_output, returns_update +from ..utils import warps, smart_math as math +from ..utils.py import ensure_list, make_vector +from ._utils import Tensor, Output, OneOrMore, Value, _backend_float +from .random import random_field_like +from .intensity import spline_upsample_like + + +def exp_velocity( + input: Tensor, + steps: int = 8, + copy: bool = False, + **kwargs +) -> Output: + """ + Exponentiate a stationary velocity field (SVF) by squaring and scaling. + + Parameters + ---------- + input : (C, *spatial, D) tensor + Input velocity field + steps : int + Number of squaring and scaling steps + copy : bool + If steps == 0, force a copy. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input"} + + Returns + ------- + output : (C, *spatial, D) tensor + Exponentiated velocity field + """ + if steps: + output = warps.exp_velocity(input, steps) + elif copy: + output = input.clone() + else: + output = input + return prepare_output( + {"output": output, "input": input}, + kwargs.get("returns", "output") + ) + + +def apply_flow(input: Tensor, flow: Tensor, **kwargs) -> Output: + """ + Apply a flow field to an image. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + flow : (C, *spatial, D) tensor + Input flow field. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "flow"} + + Returns + ------- + output : (C, *spatial) tensor + Output tensor. + """ + has_identity = kwargs.get("has_identity", False) + output = warps.apply_flow( + input[:, None], flow, has_identity, padding_mode="border" + )[:, 0] + return prepare_output( + {"output": output, "input": input, "flow": flow}, + kwargs.get("returns", "output") + ) + + +def apply_random_flow( + input: Tensor, + std: Optional[Value] = None, + unit: str = "pct", + shape: OneOrMore[int] = 5, + steps: int = 0, + order: int = 3, + distrib: str = "uniform", + shared: bool = True, + zero_center: bool = False, + **kwargs +) -> Output: + """ + Apply a random flow field to an image. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + std : float | ([C],) tensor, default=0.06 + Standard deviation of the flow (or velocity) field. + unit: {"vox", "pct"} + Unit of the flow field (voxels or percent of field-of-view) + shape : [list of] int + Size of coarse tensor of spline coefficients. + steps : int + Number of integration steps. + order : int + Spline order. + distrib : {"uniform", "gaussian", "generalized"} + Probability distribution. + shared : bool + Apply the same flow field to all channels. + If True, probability parameters must be scalars. + zero_center : bool + Subtract its mean displacement to the flow field so that + it has an empirical mean of zero. + + Other Parameters + ---------------- + peak, std, vmin, vmax, alpha, beta, mu, sigma : float | ([C],) tensor + Other parameters of the probability distribution. + returns : [list or dict of] {"output", "input", "coeff", "svf", "flow"} + Values to return. + + Returns + ------- + output : (C, *spatial) tensor + + """ + returns = kwargs.pop("returns", "output") + + ndim = input.ndim - 1 + C = len(input) + CF = 1 if shared else C + shape = [CF] + ensure_list(shape, ndim) + [ndim] + + if ( + (kwargs.get("mean", None) is None) and + (kwargs.get("mu", None) is None) and + (kwargs.get("peak", None) is None) and + (kwargs.get("vmin", None) is None) and + (kwargs.get("vmax", None) is None) + ): + kwargs["mean"] = 0 + + if ( + (std is None) and + (kwargs.get("sigma", None) is None) and + (kwargs.get("std", None) is None) and + (kwargs.get("vmin", None) is None) and + (kwargs.get("vmax", None) is None) and + (kwargs.get("alpha", None) is None) + ): + std = 0.06 + kwargs["std"] = std + + # sample spline coefficients + coeff = random_field_like(distrib, shape, **kwargs) + + # rescale values + if unit[0].lower() != "v": + scale = make_vector(input.shape[1:]).to(coeff) + coeff = math.mul_(coeff, scale) + + # upsample to image size + svf = coeff.movedim(-1, 0).reshape((-1,) + coeff.shape[1:-1]) + svf = spline_upsample_like(svf, input, order, prefilter=False) + svf = svf.reshape((ndim, CF) + input.shape[1:]).movedim(0, -1) + + # exponentiate + flow = exp_velocity(svf, steps) + + # zero center + if zero_center: + mean_flow = flow.reshape([CF, -1, ndim]).mean(1) + mean_flow = mean_flow.reshape([CF] + [1] * ndim + [ndim]) + flow = math.sub_(flow, mean_flow) + + # apply + output = apply_flow(input, flow) + + return prepare_output({ + "output": output, + "input": input, + "flow": flow, + "svf": svf, + "coeff": coeff, + }, returns) + + +def make_affine_matrix( + translations: OneOrMore[Value], + rotations: OneOrMore[Value], + zooms: OneOrMore[Value], + shears: OneOrMore[Value], + **kwargs +) -> Output: + """ + Build an affine matrix from its parameters. + + Parameters + ---------- + translations : ([D],) (float | tensor) + rotations : ([D*(D-1)/2],) (float | tensor) + zooms : ([D],) (float | tensor) + shears : ([D*(D-1)/2],) (float | tensor) + + Other Parameters + ---------------- + returns : [list or dict of] {"output", ...} + Values to return. + + Returns + ------- + matrix : (D+1, D+1) tensor + + """ + T_ = make_vector(translations) + R_ = make_vector(rotations) + Z_ = make_vector(zooms) + S_ = make_vector(shears) + + backend = _backend_float(T_, R_, Z_, S_) + + # Guess dimensionality + if len(T_) > 1: + ndim = len(T_) + elif len(Z_) > 1: + ndim = len(Z_) + elif len(R_) > 1: + k = len(R_) + ndim = int(round(((8 * k)**0.5 + 1)/2)) + elif len(S_) > 1: + k = len(S_) + ndim = int(round(((8 * k)**0.5 + 1)/2)) + else: + ndim = kwargs["ndim"] + ndim = kwargs.get("ndim", ndim) + + # Pad parameters + # Default: zoom -> replicate, others -> zero + + Z_ = make_vector(Z_, ndim, **backend) + T_ = make_vector(T_, ndim, **backend, default=0) + S_ = make_vector(S_, ndim * (ndim - 1) // 2, **backend, default=0) + R_ = make_vector(R_, ndim * (ndim - 1) // 2, **backend, default=0) + R_ = R_ * (math.pi/180) + + # identity + E = torch.eye(ndim+1, **backend) + + # zooms + Z = E.clone() + Z.diagonal(0, -1, -2)[:-1].copy_(1 + Z_) + + # translations + T = E.clone() + T[:ndim, -1] = T_ + + if ndim == 2: + + # shear + S = E.clone() + S[0, 1] = S[1, 0] = S_[0] + + # rotation + R = E.clone() + R[0, 0] = R[1, 1] = R_[0].cos() + R[0, 1] = R_[0].sin() + R[1, 0] = -R[0, 1] + + elif ndim == 3: + + # shears + Sz = E.clone() + Sz[0, 1] = Sz[1, 0] = shears[0] + Sy = E.clone() + Sy[0, 2] = Sz[2, 0] = shears[1] + Sx = E.clone() + Sx[1, 2] = Sz[2, 1] = shears[2] + S = Sx @ Sy @ Sz + + # rotations + Rz = E.clone() + Rz[0, 0] = Rz[1, 1] = rotations[0].cos() + Rz[0, 1] = rotations[0].sin() + Rz[1, 0] = -Rz[0, 1] + Ry = E.clone() + Ry[0, 0] = Ry[2, 2] = rotations[1].cos() + Ry[0, 2] = rotations[1].sin() + Ry[2, 0] = -Ry[0, 2] + Rx = E.clone() + Rx[1, 1] = Rx[2, 2] = rotations[2].cos() + Rx[1, 2] = rotations[2].sin() + Rx[2, 1] = -Rx[1, 2] + R = Rx @ Ry @ Rz + + A = T @ R @ S @ Z + return prepare_output({ + "output": A, + "translations": translations, + "rotations": rotations, + "shears": shears, + "zooms": zooms, + }, kwargs.get("returns", "output")) + + +def make_affine_flow( + matrix: Tensor, + shape: Sequence[int], + unit: str = "pct", + **kwargs +) -> Output: + """ + Convert an affine matrix to a flow field. + + Parameters + ---------- + matrix : ([C], ndim+1, ndim+1) tensor + Input affine matrix. + shape : list[int] + Spatial shape + unit : {"vox", "pct"} + Unit of the translation component. + + Returns + ------- + flow : ([C], *spatial, ndim) tensor + Flow field + """ + ndim = input.shape[-1] - 1 + + A = input.clone() + + # scale translation + if unit[0].lower() != "v": + A[..., :-1, -1] *= make_vector(shape).to(A) + + # apply transform at the center of the field of view + offset = torch.as_tensor([(n-1)/2 for n in shape]).to(A) + F = torch.eye(ndim+1).to(A) + F[:-1, -1] = offset + A = F.matmul(A).matmul(F.inverse()) + + A = A.to( + dtype=kwargs.get("dtype", None), + device=kwargs.get("device", None) + ) + + # convert to flow field + flow = warps.affine_flow(A, shape) + + return prepare_output( + {"flow": flow, "output": flow, "matrix": matrix, "input": matrix}, + kwargs.get("returns", "output") + ) + + +def apply_affine_matrix( + input: Tensor, + matrix: Tensor, + unit: str = "pct", + **kwargs +) -> Output: + """ + Apply an affine transformation encoded by a matrix. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + matrix : ([C], ndim+1, ndim+1) tensor + Input affine matrix. + unit : {"vox", "pct"} + Unit of the translation component. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "flow", "matrix"} + Values to return. + + Returns + ------- + output : (C, *spatial) tensor + Output tensor + """ + dtype = input.dtype + if not dtype.is_floating_point: + dtype = torch.get_default_dtype() + backend = dict(dtype=dtype, device=input.device) + + # convert to flow field + flow = make_affine_flow(matrix, input.shape[1:], unit, **backend) + + # apply flow field + output = apply_flow(input, flow) + + return prepare_output({ + "output": output, + "input": input, + "matrix": matrix, + "flow": flow, + }, kwargs.get("returns", "output")) + + +def apply_affine( + input: Tensor, + translations: OneOrMore[Value], + rotations: OneOrMore[Value], + zooms: OneOrMore[Value], + shears: OneOrMore[Value], + unit: str = "pct", + **kwargs +) -> Output: + """ + Apply an affine transformation. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + translations : ([C, D]) (float | tensor) + Translations. + rotations : ([C, D*(D-1)/2]) (float | tensor) + Rotations. + zooms : ([C, D]) (float | tensor) + Zooms. + shears : ([C, D*(D-1)/2]) (float | tensor) + Shears. + unit : {"vox", "pct"} + Unit of the translations. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "flow", "matrix", ...} + Values to return. + + Returns + ------- + output : (C, *spatial) tensor + Output tensor + """ + ndim = input.ndim - 1 + C = len(input) + + T = torch.as_tensor(translations).expand([C, ndim]) + R = torch.as_tensor(rotations).expand([C, (ndim*(ndim-1))//2]) + Z = torch.as_tensor(zooms).expand([C, ndim]) + S = torch.as_tensor(shears).expand([C, (ndim*(ndim-1))//2]) + + # Build matrix + matrix = torch.stack([ + make_affine_matrix(T1, R1, Z1, S1) + for T1, R1, Z1, S1 in zip(T, R, Z, S) + ]) + + # Apply transform + output = apply_affine_matrix(input, matrix, unit, **kwargs) + + returns = kwargs.get("returns", "output") + output = returns_update(translations, "translations", output, returns) + output = returns_update(rotations, "rotations", output, returns) + output = returns_update(zooms, "zooms", output, returns) + output = returns_update(shears, "shears", output, returns) + return output + + +def apply_random_affine( + input: Tensor, + translations: 0.06, + rotations: 9, + zooms: 0.08, + shears: 0.07, + distrib: str = "uniform", + distribz: str = "uniform", + statistic: str = "std", + unit: str = "pct", + iso: bool = False, + shared: bool = True, + **kwargs, +) -> Output: + """ + Apply a random affine transformation. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + translations : ([C],) (float | tensor) + Scale of random translations. + rotations : ([C],) (float | tensor) + Scale of random rotations. + zooms : ([C],) (float | tensor) + Scale of random zooms. + shears : ([C],) (float | tensor) + Scale of random shears. + distrib : [dict of] {"uniform", "gaussian"} + Probability distribution over T/R/S (with mean 0). + distribz : [dict of] {"uniform", "lognormal", "gamma"} + Probability distribution over zooms (with mean 1). + statistic : {"std", "fwhm"} + Which statistics to use as "scale parameter". + unit : {"vox", "pct"} + Unit of the translations. + shared : bool + Apply the same transform to all channels. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "flow", "matrix", ...} + Values to return. + + Returns + ------- + output : (C, *spatial) tensor + Output tensor + """ # noqa: E501 + D = input.ndim - 1 + D2 = (D*(D-1))//2 + DZ = 1 if iso else D + C = 1 if shared else len(input) + + T = random_field_like(distrib, input, [C, D], **{statistic: translations}) + R = random_field_like(distrib, input, [C, D2], **{statistic: rotations}) + Z = random_field_like(distribz, input, [C, DZ], **{statistic: zooms}) + S = random_field_like(distrib, input, [C, D2], **{statistic: shears}) + + return apply_affine(input, T, R, Z, S, unit, **kwargs) diff --git a/cornucopia/functional/intensity.py b/cornucopia/functional/intensity.py index 71bf6fa..edf7baf 100644 --- a/cornucopia/functional/intensity.py +++ b/cornucopia/functional/intensity.py @@ -1122,20 +1122,20 @@ def add_smooth_random_field( """ # noqa: E501 if ( (mean is None) and - (kwargs.get("mu", None) is not None) and - (kwargs.get("peak", None) is not None) and - (kwargs.get("vmin", None) is not None) and - (kwargs.get("vmax", None) is not None) + (kwargs.get("mu", None) is None) and + (kwargs.get("peak", None) is None) and + (kwargs.get("vmin", None) is None) and + (kwargs.get("vmax", None) is None) ): mean = 0 if ( (fwhm is None) and - (kwargs.get("sigma", None) is not None) and - (kwargs.get("std", None) is not None) and - (kwargs.get("vmin", None) is not None) and - (kwargs.get("vmax", None) is not None) and - (kwargs.get("alpha", None) is not None) + (kwargs.get("sigma", None) is None) and + (kwargs.get("std", None) is None) and + (kwargs.get("vmin", None) is None) and + (kwargs.get("vmax", None) is None) and + (kwargs.get("alpha", None) is None) ): fwhm = 1 @@ -1201,20 +1201,20 @@ def mul_smooth_random_field( """ # noqa: E501 if ( (mean is None) and - (kwargs.get("mu", None) is not None) and - (kwargs.get("peak", None) is not None) and - (kwargs.get("vmin", None) is not None) and - (kwargs.get("vmax", None) is not None) + (kwargs.get("mu", None) is None) and + (kwargs.get("peak", None) is None) and + (kwargs.get("vmin", None) is None) and + (kwargs.get("vmax", None) is None) ): mean = 1 if ( (fwhm is None) and - (kwargs.get("sigma", None) is not None) and - (kwargs.get("std", None) is not None) and - (kwargs.get("vmin", None) is not None) and - (kwargs.get("vmax", None) is not None) and - (kwargs.get("alpha", None) is not None) + (kwargs.get("sigma", None) is None) and + (kwargs.get("std", None) is None) and + (kwargs.get("vmin", None) is None) and + (kwargs.get("vmax", None) is None) and + (kwargs.get("alpha", None) is None) ): fwhm = 1 From 433e45a9a8f7c31997852e38576fa0327983d8d4 Mon Sep 17 00:00:00 2001 From: balbasty Date: Thu, 27 Mar 2025 16:52:24 +0000 Subject: [PATCH 5/6] WIP --- cornucopia/baseutils.py | 3 + cornucopia/functional/__init__.py | 13 +- cornucopia/functional/_utils.py | 2 +- cornucopia/functional/fov.py | 14 +- cornucopia/functional/geometric.py | 193 +++++++++++++++- cornucopia/functional/intensity.py | 27 +-- cornucopia/functional/noise.py | 6 +- cornucopia/functional/psf.py | 356 +++++++++++++++++++++++++++++ cornucopia/functional/random.py | 17 +- cornucopia/utils/conv.py | 74 +++--- cornucopia/utils/distributions.py | 2 +- docs/examples/functional.ipynb | 222 ++++++++++++++++++ 12 files changed, 859 insertions(+), 70 deletions(-) create mode 100644 cornucopia/functional/psf.py create mode 100644 docs/examples/functional.ipynb diff --git a/cornucopia/baseutils.py b/cornucopia/baseutils.py index eb3b2e3..727e5e4 100755 --- a/cornucopia/baseutils.py +++ b/cornucopia/baseutils.py @@ -220,6 +220,9 @@ class Returned: def __init__(self, obj): self.obj = obj + def __call__(self): + return self.obj + class VirtualTensor: """Virtual tensor used to recursively compute final transforms""" diff --git a/cornucopia/functional/__init__.py b/cornucopia/functional/__init__.py index a69a4bf..17adaac 100644 --- a/cornucopia/functional/__init__.py +++ b/cornucopia/functional/__init__.py @@ -1,5 +1,14 @@ -from . import random # noqa: F401 + +from . import fov # noqa: F401 +from . import geometric # noqa: F401 from . import intensity # noqa: F401 +from . import noise # noqa: F401 +from . import psf # noqa: F401 +from . import random # noqa: F401 -from .random import * # noqa: F401,F403 +from .fov import * # noqa: F401,F403 +from .geometric import * # noqa: F401,F403 from .intensity import * # noqa: F401,F403 +from .noise import * # noqa: F401,F403 +from .psf import * # noqa: F401,F403 +from .random import * # noqa: F401,F403 diff --git a/cornucopia/functional/_utils.py b/cornucopia/functional/_utils.py index 0fa5383..fbe4a7e 100644 --- a/cornucopia/functional/_utils.py +++ b/cornucopia/functional/_utils.py @@ -9,7 +9,7 @@ T = TypeVar('T') Tensor = torch.Tensor Value = Union[float, Tensor] -Output = Union[Tensor, Mapping[Tensor], Sequence[Tensor]] +Output = Union[Tensor, Mapping[str, Tensor], Sequence[Tensor]] OneOrMore = Union[T, Sequence[T]] diff --git a/cornucopia/functional/fov.py b/cornucopia/functional/fov.py index ca94680..ceb7861 100644 --- a/cornucopia/functional/fov.py +++ b/cornucopia/functional/fov.py @@ -85,7 +85,7 @@ def flip( return prepare_output( {"output": output, "input": input, "axes": axes}, kwargs.pop("returns", "output") - ) + )() def random_flip( @@ -188,7 +188,7 @@ def perm( return prepare_output( {"output": output, "input": input, "perm": perm}, kwargs.pop("returns", "output") - ) + )() def random_perm( @@ -334,7 +334,7 @@ def rot90( {"output": output, "input": input, "plane": plane, "negative": negative, "double": double}, kwargs.pop("returns", "output") - ) + )() def rot180( @@ -426,7 +426,7 @@ def det(transformation): return prepare_output( {"output": output, "input": input, "perm": perm_, "flip": flip_}, kwargs.pop("returns", "output") - ) + )() def ensure_pow2( @@ -525,7 +525,7 @@ def pad( return prepare_output( {"output": output, "input": input, "size": size}, kwargs.pop("returns", "output") - ) + )() def crop( @@ -615,7 +615,7 @@ def crop( return prepare_output( {"output": output, "input": input, "size": size}, kwargs.pop("returns", "output") - ) + )() def patch( @@ -673,7 +673,7 @@ def patch( return prepare_output( {"output": output, "input": input, "center": center}, kwargs.pop("returns", "output") - ) + )() def random_patch( diff --git a/cornucopia/functional/geometric.py b/cornucopia/functional/geometric.py index 8789e15..00b8a88 100644 --- a/cornucopia/functional/geometric.py +++ b/cornucopia/functional/geometric.py @@ -6,9 +6,18 @@ "make_affine_flow", "apply_affine_matrix", "apply_affine", + "apply_random_affine", + "apply_random_affine_elastic", ] +# std +import random from typing import Optional, Sequence + +# external import torch +import interpol + +# internal from ..baseutils import prepare_output, returns_update from ..utils import warps, smart_math as math from ..utils.py import ensure_list, make_vector @@ -53,7 +62,7 @@ def exp_velocity( return prepare_output( {"output": output, "input": input}, kwargs.get("returns", "output") - ) + )() def apply_flow(input: Tensor, flow: Tensor, **kwargs) -> Output: @@ -83,7 +92,7 @@ def apply_flow(input: Tensor, flow: Tensor, **kwargs) -> Output: return prepare_output( {"output": output, "input": input, "flow": flow}, kwargs.get("returns", "output") - ) + )() def apply_random_flow( @@ -194,7 +203,7 @@ def apply_random_flow( "flow": flow, "svf": svf, "coeff": coeff, - }, returns) + }, returns)() def make_affine_matrix( @@ -311,7 +320,7 @@ def make_affine_matrix( "rotations": rotations, "shears": shears, "zooms": zooms, - }, kwargs.get("returns", "output")) + }, kwargs.get("returns", "output"))() def make_affine_flow( @@ -362,7 +371,7 @@ def make_affine_flow( return prepare_output( {"flow": flow, "output": flow, "matrix": matrix, "input": matrix}, kwargs.get("returns", "output") - ) + )() def apply_affine_matrix( @@ -409,7 +418,7 @@ def apply_affine_matrix( "input": input, "matrix": matrix, "flow": flow, - }, kwargs.get("returns", "output")) + }, kwargs.get("returns", "output"))() def apply_affine( @@ -471,7 +480,7 @@ def apply_affine( output = returns_update(rotations, "rotations", output, returns) output = returns_update(zooms, "zooms", output, returns) output = returns_update(shears, "shears", output, returns) - return output + return output() def apply_random_affine( @@ -535,3 +544,173 @@ def apply_random_affine( S = random_field_like(distrib, input, [C, D2], **{statistic: shears}) return apply_affine(input, T, R, Z, S, unit, **kwargs) + + +def apply_random_affine_elastic( + input: Tensor, + elastic: 0.06, + translations: 0.06, + rotations: 9, + zooms: 0.08, + shears: 0.07, + shape: OneOrMore[int] = 5, + patch: Optional[OneOrMore[int]] = None, + distrib: str = "uniform", + distribz: str = "uniform", + statistic: str = "std", + steps: int = 0, + order: int = 3, + unit: str = "pct", + iso: bool = False, + shared: bool = True, + **kwargs, +) -> Output: + """ + Apply a random affine + elastic transformation. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + elastic : ([C],) (float | tensor) + Scale of random elastic flow. + translations : ([C],) (float | tensor) + Scale of random translations. + rotations : ([C],) (float | tensor) + Scale of random rotations. + zooms : ([C],) (float | tensor) + Scale of random zooms. + shears : ([C],) (float | tensor) + Scale of random shears. + patch : [list of] int + Size of random patch to extract + distrib : [dict of] {"uniform", "gaussian"} + Probability distribution over T/R/S (with mean 0). + distribz : [dict of] {"uniform", "lognormal", "gamma"} + Probability distribution over zooms (with mean 1). + statistic : {"std", "fwhm"} + Which statistics to use as "scale parameter". + steps : int + Number of integration steps. + order : int + Spline order. + unit : {"vox", "pct"} + Translation and deformation unit. + shared : bool + Apply the same transform to all channels. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "flow", "matrix", ...} + Values to return. + + Returns + ------- + output : (C, *spatial) tensor + Output tensor + """ # noqa: E501 + + # backend + dtype = input.dtype + if not dtype.is_floating_point: + dtype = torch.get_default_dtype() + backend = dict(dtype=dtype, device=input.device) + + # size + ishape = input.shape[1:] + D = input.ndim - 1 + D2 = (D*(D-1))//2 + DZ = 1 if iso else D + C = 1 if shared else len(input) + + # sample affine parameters + T = random_field_like(distrib, input, [C, D], **{statistic: translations}) + R = random_field_like(distrib, input, [C, D2], **{statistic: rotations}) + Z = random_field_like(distribz, input, [C, DZ], **{statistic: zooms}) + S = random_field_like(distrib, input, [C, D2], **{statistic: shears}) + + # build matrix + matrix = torch.stack([ + make_affine_matrix(T1, R1, Z1, S1) + for T1, R1, Z1, S1 in zip(T, R, Z, S) + ]) + + # apply transform at the center of the field of view + A = matrix + offset = torch.as_tensor([(n-1)/2 for n in ishape]).to(A) + F = torch.eye(D+1).to(A) + F[:-1, -1] = offset + A = F.matmul(A).matmul(F.inverse()) + + # sample spline coefficients + coeff = random_field_like(distrib, shape, **kwargs) + + # rescale values + if unit[0].lower() != "v": + scale = make_vector(input.shape[1:]).to(coeff) + coeff = math.mul_(coeff, scale) + + if steps: + # upsample to image size + svf = coeff.movedim(-1, 0).reshape((-1,) + coeff.shape[1:-1]) + svf = spline_upsample_like(svf, input, order, prefilter=False) + svf = svf.reshape((D, C) + input.shape[1:]).movedim(0, -1) + + # exponentiate + elastic = exp_velocity(svf, steps) + + # patch size + patch_ = patch + if patch_ is None: + patch_ = ishape + patch_ = ensure_list(patch_, D) + + # 1) start from identity + flow = warps.identity(patch_, **backend) + + if patch: + # 1.b) randomly sample patch location and add offset + patch_origin = [random.randint(0, s-p) for s, p in zip(ishape, patch)] + flow += torch.as_tensor(patch_origin, **backend) + + # 2) apply affine transform + flow = A[:D, :D].matmul(flow.unsqueeze(-1)).squeeze(-1) + flow = math.add_(flow, A[:D, -1]) + + # 3) compose with elastic transform + if steps: + # we sample into the blown up elastic flow, + # which has the size of the full image + tmp = elastic.movedim(-1, -D-1) + tmp = warps.apply_flow(tmp, flow, has_identity=True) + tmp = tmp.movedim(-D-1, -1) + flow = math.add_(tmp, flow) + else: + # we sample into the spline control points + # and must rescale the sampling coordinates accordingly + scale = [(s0-1)/(s1-1) for s0, s1 in zip(shape, ishape)] + scale = torch.as_tensor(scale, **backend) + if order == 1: + # we can use pytorch + tmp = coeff.movedim(-1, -D-1) + tmp = warps.apply_flow(coeff, flow * scale, has_identity=True) + tmp = tmp.movedim(-D-1, -1) + flow = math.add_(tmp, flow) + else: + # we must use torch-interpol + # (for some reason we cannot add inplace here) + tmp = coeff.movedim(-1, -D-1) + interpol.grid_pull(coeff, flow * scale, interpolation=order) + tmp = tmp.movedim(-D-1, -1) + flow = tmp + flow + + # apply + output = apply_flow(input, flow) + + return prepare_output({ + "output": output, + "input": input, + "matrix": matrix, + "coeff": coeff, + "flow": flow, + }, kwargs.get("returns", "output"))() diff --git a/cornucopia/functional/intensity.py b/cornucopia/functional/intensity.py index edf7baf..3008b1b 100644 --- a/cornucopia/functional/intensity.py +++ b/cornucopia/functional/intensity.py @@ -70,7 +70,7 @@ def binop_value( return prepare_output( {"input": input, "output": output, kwargs["value"]: value}, kwargs["returns"] - ) + )() def add_value(input: Tensor, value: Value, **kwargs) -> Output: @@ -218,7 +218,7 @@ def addmul_value( "output": output, kwargs["scale_name"]: scale, kwargs["offset_name"]: offset, - }, kwargs["returns"]) + }, kwargs["returns"])() def binop_field( @@ -274,7 +274,7 @@ def binop_field( "output": output, kwargs["field_name"]: field, "input_" + kwargs["field_name"]: input_field - }, kwargs["returns"]) + }, kwargs["returns"])() def add_field( @@ -493,7 +493,7 @@ def fill_value(input: Tensor, mask: Tensor, value: Value, **kwargs) -> Output: "output": output, kwargs["value_name"]: value, kwargs["mask_name"]: mask, - }, kwargs["returns"]) + }, kwargs["returns"])() def clip_value( @@ -533,7 +533,7 @@ def clip_value( return prepare_output( {"input": input, "output": output, "vmin": vmin, "vmax": vmax}, kwargs["returns"] - ) + )() def spline_upsample( @@ -612,7 +612,7 @@ def spline_upsample( return prepare_output( {"input": input, "output": output, "coeff": coeff}, returns - ) + )() def spline_upsample_like( @@ -663,6 +663,7 @@ def spline_upsample_like( kwargs.setdefault("copy", copy) output = spline_upsample(input, like.shape[1:], **kwargs) output = returns_update(like, "like", output, kwargs["returns"]) + return output() def gamma_transform( @@ -747,7 +748,7 @@ def gamma_transform( "vmin": vmin, "vmax": vmax, "gamma": gamma, - }, kwargs["returns"]) + }, kwargs["returns"])() def z_transform( @@ -814,7 +815,7 @@ def z_transform( "output": output, "mu": mu, "sigma": sigma, - }, kwargs["returns"]) + }, kwargs["returns"])() def quantile_transform( @@ -924,7 +925,7 @@ def quantile_transform( "pmax": pmax, "qmin": qmin, "qmax": qmax, - }, kwargs["returns"]) + }, kwargs["returns"])() def minmax_transform( @@ -1002,7 +1003,7 @@ def minmax_transform( "vmax": vmax, "imin": imin, "imax": imax, - }, kwargs["returns"]) + }, kwargs["returns"])() def affine_intensity_transform( @@ -1074,7 +1075,7 @@ def affine_intensity_transform( "imax": imax, "omin": omin, "omax": omax, - }, kwargs["returns"]) + }, kwargs["returns"])() def add_smooth_random_field( @@ -1153,7 +1154,7 @@ def add_smooth_random_field( output = add_field(input, coeff, order, prefilter=False, returns=returns) output = returns_update(coeff, "coeff", output, returns) - return output + return output() def mul_smooth_random_field( @@ -1232,4 +1233,4 @@ def mul_smooth_random_field( output = add_field(input, coeff, order, prefilter=False, returns=returns) output = returns_update(coeff, "coeff", output, returns) - return output + return output() diff --git a/cornucopia/functional/noise.py b/cornucopia/functional/noise.py index f108eb4..2cafefb 100644 --- a/cornucopia/functional/noise.py +++ b/cornucopia/functional/noise.py @@ -46,7 +46,7 @@ def noisify_gaussian( return prepare_output( {"input": input, "output": output, "noise": noise, "gfactor": gfactor}, kwargs.pop("returns", "output") - ) + )() def noisify_gamma( @@ -84,7 +84,7 @@ def noisify_gamma( return prepare_output( {"input": input, "output": output, "noise": noise}, kwargs.pop("returns", "output") - ) + )() def noisify_chi( @@ -141,4 +141,4 @@ def noisify_chi( return prepare_output( {"input": input, "output": output, "noise": noise}, kwargs.pop("returns", "output") - ) + )() diff --git a/cornucopia/functional/psf.py b/cornucopia/functional/psf.py new file mode 100644 index 0000000..625faad --- /dev/null +++ b/cornucopia/functional/psf.py @@ -0,0 +1,356 @@ +__all__ = [ + "smooth", + "conv", + "conv1d", + "random_kernel", +] +from typing import Union, Sequence, Optional + +import torch + +from ..baseutils import prepare_output +from ..utils.warps import identity +from ..utils.conv import smoothnd, convnd +from ..utils.py import ensure_list +from ..utils import smart_math as math +from ._utils import Tensor, Value, Output, OneOrMore, _axis_name2index +from .random import random_field + + +def smooth( + input: Tensor, + fwhm: Value, + iso: bool = False, + bound: str = "reflect", + **kwargs +) -> Output: + """ + Smooth an image with a Gaussian kernel. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + fwhm : float | ([C, D],) tensor + Full width at half-maximum of the Gaussian kernel. + iso : bool + Isotropic smoothing. + This only matters when `fwhm` is a vector. + If True, it is assumed to be a vector of length `C` (one isotropic + kernel per channel). If False, it is assumed to be a vector or + length `D` (one anisotropic kernel shared across channels). + bound : {"zero", "reflect", "mirror", "circular", ...} + Boundary conditions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "fwhm"} + + Returns + ------- + output : (C, *sptial) tensor + Output tensor. + + """ + ndim = input.ndim - 1 + fwhm = torch.as_tensor(fwhm) + + if fwhm.ndim == 1: + fwhm = fwhm[:, None] if iso else fwhm[None, :] + elif fwhm.ndim == 0: + fwhm = fwhm[None, None] + fwhm = fwhm.expand([len(fwhm), ndim]) + + if len(fwhm) != 1: + output = torch.stack([ + smoothnd(inp1, fwhm=fwhm1) + for inp1, fwhm1 in zip(input, fwhm) + ]) + else: + output = smoothnd(input, fwhm=fwhm[0], bound=bound) + + return prepare_output( + {"output": output, "input": input}, + kwargs.get("returns", "output") + )() + + +def conv( + input: Tensor, + kernel: OneOrMore[Tensor], + bound: str = "reflect", + **kwargs +) -> Output: + """ + Convolve an image with a kernel. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + kernel : [list of] ([[K], C], *kernel_size) tensor + Convolution kernel. + * If its size is `(*kernel_size)`, the same kernel is applied to + all channels. + * If its size is `(C, *kernel_size)`, each channel is convolved + with its own kernel. + * If its size is `(K, C, *kernel_size)`, channels are mixed + by the convolution kernel, and `K` is the output number of + channels. + * If it is a list, it must contain `ndim` 1D kernels, which + will be applied in order along the spatial dimensions, from + left to right. + bound : {"zero", "reflect", "mirror", "circular", ...} + Boundary conditions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "kernel"} + + Returns + ------- + output : (K, *spatial) tensor + Output tensor. + + """ + ndim = input.ndim - 1 + + # separable convolution + if isinstance(kernel, list): + if len(kernel) != ndim: + raise ValueError(f"Expected {ndim} kernels but got {len(kernel)}.") + return conv1d(input, kernel, list(range(ndim), bound=bound), **kwargs) + + # n-dimensional convolution + output = convnd(ndim, input, kernel, bound=bound, padding="same") + return prepare_output( + {"input": input, "output": output, "kernel": kernel}, + kwargs.get("returns", "output") + )() + + +def conv1d( + input: Tensor, + kernel: OneOrMore[Tensor], + axis: OneOrMore[Union[int, str]] = -1, + orient: Union[str, Tensor] = "RAS", + bound: str = "reflect", + **kwargs +) -> Output: + """ + Convolve an image with a 1D kernel along a given dimension. + + Parameters + ---------- + input : (C, *spatial) tensor + Input tensor. + kernel : [list of] ([[K], C], kernel_size) tensor + Convolution kernel. + * If its size is `(kernel_size,)`, the same kernel is applied to + all channels. + * If its size is `(C, kernel_size)`, each channel is convolved + with its own kernel. + * If its size is `(K, C, kernel_size)`, channels are mixed + by the convolution kernel, and `K` is the output number of + channels. + * If it is a list, kernels are applied in sequence, and `axis` + must contain as many axes as kernels. + axes : int | {"LR", "AP", "IS"} + Axes to flip, by index or by name. + Indices correspond to spatial axes only (0 = first spatial dim, etc.) + If None, flip all spatial axes. + orient : str or tensor + Tensor layout (`{"RAS", "LPS", ...}`) or orient matrix. + bound : {"zero", "reflect", "mirror", "circular", ...} + Boundary conditions. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "kernel", "axis"} + + Returns + ------- + output : (K, *spatial) tensor + Output tensor. + + """ + ndim = input.ndim - 1 + + axis_ = axis + if any(isinstance(ax, str) for ax in axis_): + axis_ = _axis_name2index(axis_, orient) + axis_ = ensure_list(axis_) + + axis_ = [ndim + ax if ax < 0 else ax for ax in axis_] + kernel_ = ensure_list(kernel, len(axis_)) + + output = input + for ax, ker in zip(axis_, kernel_): + kernel_size = [1] * ndim + kernel_size[ax] = ker.shape[-1] + ker = ker.reshape(ker.shape[:-1] + tuple(kernel_size)) + + output = conv(ndim, output, ker, bound=bound) + + return prepare_output( + {"input": input, "output": output, "kernel": kernel, "axis": axis}, + kwargs.get("returns", "output") + )() + + +def random_kernel( + shape: Sequence[int], + norm: Optional[float] = 1, + zero_mean: bool = False, + allow_translations: bool = False, + distrib: Optional[str] = "gamma", + **kwargs +) -> Output: + """ + Generate a random convolution kernel. + + !!! example "Examples" + ```python + shape = [1] + [5] * ndim + + # smoothing kernel (positive values, sum to one) + kernel = random_kernel(shape, distrib="gamma") + + # differential kernel (pos and neg values, sum to zero) + kernel = random_kernel(shape, zero_mean=True, distrib="gaussian") + + # purely random kernels -- may shift data + kernel = random_kernel(shape, allow_translations=True, distrib="gaussian") + ``` + + To generate a smoothing kernel: + + + Parameters + ---------- + shape : (C, *spatial) list[int] + Output kernel shape, including the channel dimension. + The spatial size should be odd. + norm : float + Ensure that the kernel has unit norm of order `p`. + If `None`, do not normalize the kernel. + zero_mean : bool + If `True`, ensure that the kernel sums to zero. + allow_translations : bool + If `False`, ensure that the kernel's barycenter is its center. + This ensures that the kernel does not "translate" data. + (otherwise, a kernel such as `[1, 0, 0]`, which implements a + 1-voxel translation, would be valid). + If `True`, any kernel is allowed. + distrib : {"uniform", "gamma", "lognormal", "gaussian", "generalized"} + Probability distribution. + Gamma and lognormal always return positive values (default mean: 1). + Normal and generalized may return negative values (default mean: 0). + The value range returned by uniform depends on its parameters + (default: [0, 1]). + Defaults depend on the other parameters: + * when `sum > 0`, we use `"gamma"` with `mean=1, std=1` + * when `sum == 0`, we use `"gaussian"` with `mean=0, std=0.2` + + Other Parameters + ---------------- + mean, std, peak, fwhm, ... : float | (C,) tensor + Distribution parameters. + dtype : torch.dtype + Output data type. + device : torch.device + Output device. + returns : [list or dict of] {"output"} + Tensors to return. + + Returns + ------- + output : (*shape) tensor + Output kernel. + """ # noqa: E501 + returns = kwargs.pop("returns", "output") + + shape = list(shape) + ndim = len(shape) - 1 + C = shape[0] + if not all(s % 2 for s in shape[1:]): + raise ValueError("Spatial kernel size must be odd.") + + if sum is None: + distrib = distrib or "gaussian" + kwargs.setdefault("std", 0.2) + elif sum == 0: + distrib = distrib or "gaussian" + kwargs.setdefault("std", 0.2) + else: + distrib = distrib or "gamma" + kwargs.setdefault("std", 1) + + # sample values + output = random_field(distrib, shape, **kwargs) + + # undo translation + if not allow_translations: + # compute kernel barycenter + backend = dict(dtype=output.dtype, device=output.device) + size = torch.as_tensor(shape[1:], **backend) + grid = identity(shape[1:], **backend) + grid -= (size - 1) / 2 + bary = output.abs().reshape([C, -1]).matmul(grid.reshape([-1, ndim])) + bary /= output.abs().reshape([C, -1]).sum(-1, keepdim=True) + + # build convolution kernel that applies a translation of `-bary` + # (with linear interpolation) + new_shape = size + 2 * bary.abs().max(0).values + new_shape = new_shape.ceil().long() + new_shape += (1 - new_shape % 2) + new_shape = [C] + new_shape.tolist() + translation_kernels = [] + for c in range(C): + translation_kernel = 1 + for d in range(ndim): + s = new_shape[1+d] + k = torch.zeros([s], **backend) + b = (s - 1) / 2 + bary[c, d] + k[b.floor().long()] = 1 - (b - b.floor()) + k[b.ceil().long()] = 1 - (b.ceil() - b) + translation_kernel = translation_kernel * k + translation_kernel = translation_kernel[..., None] + translation_kernel = translation_kernel[..., 0] + translation_kernels.append(translation_kernel) + translation_kernel = torch.stack(translation_kernels) + + shape = new_shape + translation_kernel = translation_kernel.expand(shape) + + # convolve both kernels + output = convnd(ndim, translation_kernel, output, padding="same") + + # ## DEBUG: check bary + # size = torch.as_tensor(shape[1:], **backend) + # grid = identity(shape[1:], **backend) + # grid -= (size - 1) / 2 + # bary = output.abs().reshape([C, -1]).matmul(grid.reshape([-1, ndim])) + # bary /= output.abs().reshape([C, -1]).sum(-1, keepdim=True) + # print(bary) + + # zero mean + if zero_mean: + mean = output.sum(list(range(-ndim-1, 0)), keepdim=True) + output = math.sub_(output, mean) + + # normalize + if norm is not None: + p = norm + if p == 0: + norm = output.abs().reshape([C, -1]) + norm = norm.max(-1).values + for _ in range(ndim): + norm = norm[..., None] + else: + norm = output.abs().pow(p) + norm = norm.sum(list(range(-ndim-1, 0)), keepdims=True) + norm = output.pow(1/p) + output = math.div_(output, norm) + + return prepare_output({"output": output}, returns)() diff --git a/cornucopia/functional/random.py b/cornucopia/functional/random.py index 85bd846..5dc1835 100644 --- a/cornucopia/functional/random.py +++ b/cornucopia/functional/random.py @@ -29,7 +29,7 @@ Tensor = torch.Tensor Value = Union[float, Tensor] -Output = Union[Tensor, Mapping[Tensor], Sequence[Tensor]] +Output = Union[Tensor, Mapping[str, Tensor], Sequence[Tensor]] LOG2 = math.log(2) FWHM_FACTOR = (8 * LOG2) ** 0.5 # gaussian: fwhm = FWHM_FACTOR * sigma @@ -137,7 +137,7 @@ def random_field_uniform( output = math.add_(math.mul_(output, (vmax_ - vmin_)), vmin_) kwargs.setdefault("returns", "output") - return prepare_output({"output": output, **prm}, kwargs["returns"]) + return prepare_output({"output": output, **prm}, kwargs["returns"])() def random_field_gaussian( @@ -181,7 +181,7 @@ def random_field_gaussian( output = math.add_(math.mul_(output, std_), mean_) kwargs.setdefault("returns", "output") - return prepare_output({"output": output, **prm}, kwargs["returns"]) + return prepare_output({"output": output, **prm}, kwargs["returns"])() def random_field_lognormal( @@ -251,7 +251,7 @@ def random_field_lognormal( output = math.exp_(math.add_(math.mul_(output, sigma_), mu_)) kwargs.setdefault("returns", "output") - return prepare_output({"output": output, **prm}, kwargs["returns"]) + return prepare_output({"output": output, **prm}, kwargs["returns"])() def random_field_gamma( @@ -309,6 +309,7 @@ def random_field_gamma( output : (*shape) tensor Output tensor. """ + ndim = len(shape) - 1 prm = gamma_parameters(mean=mean, std=std, **kwargs) alpha, beta = prm["alpha"], prm["beta"] @@ -319,9 +320,11 @@ def random_field_gamma( beta_ = beta_.expand(shape[:1]) output = torch.distributions.Gamma(alpha_, beta_).rsample(shape[1:]) + for _ in range(ndim): + output = output.movedim(0, -1) kwargs.setdefault("returns", "output") - return prepare_output({"output": output, **prm}, kwargs["returns"]) + return prepare_output({"output": output, **prm}, kwargs["returns"])() def random_field_generalized( @@ -411,7 +414,7 @@ def random_field_generalized( output = math.add_(math.mul_(output, std_), mean_) kwargs.setdefault("returns", "output") - return prepare_output({"output": output, **prm}, kwargs["returns"]) + return prepare_output({"output": output, **prm}, kwargs["returns"])() def random_field_like( @@ -515,7 +518,7 @@ def _random_field_like( # sample field output = func(shape, *args, **kwargs) - return returns_update(input, "input", output, kwargs["returns"]) + return returns_update(input, "input", output, kwargs["returns"])() def random_field_uniform_like( diff --git a/cornucopia/utils/conv.py b/cornucopia/utils/conv.py index aa89c22..d8bfd43 100755 --- a/cornucopia/utils/conv.py +++ b/cornucopia/utils/conv.py @@ -14,7 +14,7 @@ def convnd(ndim, tensor, kernel, bias=None, stride=1, padding=0, bound='zero', Number of spatial dimensions tensor : (*batch, [channel_in,] *spatial_in) tensor Input tensor - kernel : ([channel_in, channel_out,] *kernel_size) tensor + kernel : ([[channel_out,] channel_in,] *kernel_size) tensor Convolution kernel bias : ([channel_out,]) tensor, optional Bias tensor @@ -40,39 +40,48 @@ def convnd(ndim, tensor, kernel, bias=None, stride=1, padding=0, bound='zero', if bias is not None: bias = bias.to(tensor) - # sanity checks + reshape for torch's conv - if kernel.dim() not in (ndim, ndim + 2): - raise ValueError('Kernel shape should be (*kernel_size) or ' - '(channel_in, channel_out, *kernel_size) but ' - 'got {}'.format(kernel.shape)) - has_channels = kernel.dim() == ndim + 2 - channels_in = kernel.shape[0] if has_channels else 1 - channels_out = kernel.shape[1] if has_channels else 1 - kernel_size = kernel.shape[(2*has_channels):] - kernel = kernel.reshape([channels_in, channels_out, *kernel_size]) - batch = tensor.shape[:-(ndim+has_channels)] - spatial_in = tensor.shape[(-ndim):] - if has_channels and tensor.shape[-(ndim+has_channels)] != channels_in: + # check kernel dimensions + if kernel.dim() not in (ndim, ndim + 1, ndim + 2): raise ValueError( - 'Number of input channels not consistent: ' - 'Got {} (kernel) and {} (tensor).'.format( - channels_in, tensor.shape[-(ndim+has_channels)] - ) + f'Kernel shape should be (*kernel_size) or ' + f'(channel_in, *kernel_size) or ' + f'(channel_out, channel_in, *kernel_size) but got ' + f'{kernel.shape}' + ) + + # guess kernel shape + is_diag = kernel.dim() == ndim + 1 + is_full = kernel.dim() == ndim + 2 + channels_out = kernel.shape[0] if is_full else 1 + groups = channels_out if is_diag else 1 + channels_in = kernel.shape[1] if is_full else channels_out + kernel_size = kernel.shape[-ndim:] + kernel = kernel.reshape([channels_out, channels_in//groups, *kernel_size]) + + # guess input shape + batch = tensor.shape[:-(ndim+(is_diag or is_full))] + spatial_in = tensor.shape[-ndim:] + + # check channels match + if (is_diag or is_full) and tensor.shape[-ndim-1] != channels_in: + raise ValueError( + f'Number of input channels not consistent: Got {channels_in} ' + f'(kernel) and {tensor.shape[-ndim-1]} (tensor).' ) tensor = tensor.reshape([-1, channels_in, *spatial_in]) - if bias: + + # reshape bias + if bias is not None: bias = bias.flatten() - if bias.numel() == 1: - bias = bias.expand(channels_out) - elif bias.numel() != channels_out: + if len(bias) == 1: + bias = bias.expand([channels_out]) + elif len(bias) != channels_out: raise ValueError( - 'Number of output channels not consistent: ' - 'Got {} (kernel) and {} (bias).' .format( - channels_out, bias.numel() - ) + f'Number of output channels not consistent: ' + f'Got {channels_out} (kernel) and {bias.numel()} (bias).' ) - # Perform padding + # preprocess padding size dilation = ensure_list(dilation, ndim) padding = ensure_list(padding, ndim) padding = [0 if p == 'valid' else 'same' if p == 'auto' else p @@ -84,20 +93,27 @@ def convnd(ndim, tensor, kernel, bias=None, stride=1, padding=0, bound='zero', raise ValueError('Cannot compute "same" padding ' 'for even-sized kernels.') padding[i] = dilation[i] * (kernel_size[i] // 2) + + # perform padding ourselves if bound != 'zero' and sum(padding) > 0: tensor = pad(tensor, padding, bound, side='both') padding = 0 + # Select convolution function conv_fn = (F.conv1d if ndim == 1 else F.conv2d if ndim == 2 else F.conv3d if ndim == 3 else None) if not conv_fn: raise NotImplementedError('Convolution is only implemented in ' 'dimension 1, 2 or 3.') + + # perform convolution tensor = conv_fn(tensor, kernel, bias, stride=stride, padding=padding, dilation=dilation, groups=groups) - spatial_out = tensor.shape[(-ndim):] - channels_out = [channels_out] if has_channels else [] + + # reshape tensor + spatial_out = tensor.shape[-ndim:] + channels_out = [channels_out] if (is_diag or is_full) else [] tensor = tensor.reshape([*batch, *channels_out, *spatial_out]) return tensor diff --git a/cornucopia/utils/distributions.py b/cornucopia/utils/distributions.py index 09eddfd..11270b2 100644 --- a/cornucopia/utils/distributions.py +++ b/cornucopia/utils/distributions.py @@ -1,5 +1,5 @@ -import smart_math as math +from ..utils import smart_math as math LOG2 = math.log(2) FWHM_FACTOR = (8 * LOG2) ** 0.5 # gaussian: fwhm = FWHM_FACTOR * sigma diff --git a/docs/examples/functional.ipynb b/docs/examples/functional.ipynb new file mode 100644 index 0000000..e64d451 --- /dev/null +++ b/docs/examples/functional.ipynb @@ -0,0 +1,222 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import matplotlib.pyplot as plt\n", + "from cornucopia.utils.py import meshgrid_ij\n", + "import cornucopia.functional as ccf" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAu0AAAMWCAYAAABFnLgJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAC/b0lEQVR4nO39e7TdZXWoj898RS4CieS2d66AxSJii9yUcA23QCKCAq1Wrba1rVSPnhodeKittvacUi2t1KGAHkFqqdXThpsQ7pAENBZBsD31Uh0FE5K9cwMS5agI7t8fHeTnnu+zycyKrnwSnmcM/9ivn/X5vJf5vmuxMp81x42MjIyEiIiIiIh0lv9ve3dARERERESeHT+0i4iIiIh0HD+0i4iIiIh0HD+0i4iIiIh0HD+0i4iIiIh0HD+0i4iIiIh0HD+0i4iIiIh0HD+0i4iIiIh0nF22dwdEREREZOfnRz/6UTz55JPbuxsNu+66a+y+++7buxtbxA/tIiIiIvIL5Uc/+lHsv//+MTw8vL270jA4OBgPPfRQ5z+4+6FdRERERH6hPPnkkzE8PBwrV66M8ePHb+/ubGbTpk0xa9asePLJJ/3QLiIiIiISETF+/PhOfWjfkfBDu4iIiIj0hZGRkRgZGdne3dhMl/qyJfz1GBERERGRjuOHdhERERGRjmN6jIiIiIj0BdNjesdv2kVEREREOo4f2kVEREREOo7pMSIiIiLSF0yP6R2/aRcRERER6Th+aBcRERER6Timx4iIiIhIXzA9pnf8pl1EREREpOP4oV1EREREpOOYHiMiIiIifcH0mN7xm3YRERERkY7jh3YRERERkY5jeoyIiIiI9AXTY3rHb9pFRERERDqOH9pFRERERDqO6TEiIiIi0hdMj+kdv2kXEREREek4fmgXEREREek4pseIiIiISF8wPaZ3/KZdRERERKTj+KFdRERERKTjmB4jIiIiIn3B9Jje8Zt2EREREZGO44d2EREREZGOY3qMiIiIiPQF02N6x2/aRUREREQ6jh/aRUREREQ6jukxIiIiItIXTI/pHb9pFxERERHpOH5oFxERERHpOKbHiIiIiEhfMD2md/ymXURERESk4/ihXURERESk45geIyIiIiJ9wfSY3vGbdhERERGRjuOHdhERERGRjmN6jIiIiIj0BdNjesdv2kVEREREOo4f2kVEREREOo7pMSIiIiLSF0yP6R2/aRcRERER6Th+aBcRERER6Timx4iIiIhIXzA9pnf8pl1EREREpOP4oV1EREREpOOYHiMiIiIifcH0mN7xm3YRERERkY7jh3YRERERkY5jeoyIiIiI9AXTY3rHb9pFdnL+5V/+JV772tfG7NmzY7fddouBgYGYM2dOvOc97xl13dy5c2Pu3Lnbp5NA1/rzi2DJkiUxbty4WLJkyea2xYsXx5/+6Z/+Qp73p3/6pzFu3LhfyL1FROQXix/aRXZibrzxxjj66KNj06ZN8ZGPfCRuvfXW+Nu//ds45phj4gtf+MKoay+55JK45JJLtlNPn5scdthhsXz58jjssMM2ty1evDj+7M/+bDv2SkREuojpMSI7MR/5yEdi//33j1tuuSV22eX/v91f//rXx0c+8pFR1770pS/td/ee84wfPz6OOuqo7d0NEZG+siOlpHQJv2kX2YnZsGFDTJ48edQH9mf4//6/0duf0lEeeeSROPfcc2PvvfeOF77whfHGN74xvvrVr8a4cePiyiuv3Hzdb/3Wb8Vee+0V3/3ud2PBggWx1157xaxZs+I973lP/PjHPx51zz/7sz+LV77ylTFx4sQYP358HHbYYXH55Zf3fIjvt99+ccYZZ8QNN9wQhx56aOyxxx5x0EEHxQ033BAREVdeeWUcdNBBseeee8YrXvGKuO+++0a9/r777ovXv/71sd9++8Uee+wR++23X/zGb/xGfO9732uedc8998ScOXNi9913jxkzZsSf/MmfxKc//ekYN25cPPzww02fbr755jjssMNijz32iJe85CVxxRVXjLpfTo/5rd/6rfjEJz4RERHjxo3b/L+HH344Hn744Wben2HcuHFNSs2NN94YL3/5y2O33XaL/fffPy666CKcv5GRkbjkkkvi5S9/eeyxxx6xzz77xLnnnhv/+Z//+WzTLiIifcZv2kV2YubMmROf/vSn413vele88Y1vjMMOOyye//znl177xBNPxIknnhiPPvpofPjDH44DDjggbr755njd616H1//kJz+JM888M9761rfGe97znli2bFn8+Z//eUyYMCE+8IEPbL7u4Ycfjre97W0xe/bsiIj4yle+Eu985ztj1apVo67bGr7+9a/HBRdcEO9///tjwoQJ8Wd/9mdx9tlnxwUXXBB33HFH/MVf/EWMGzcu3ve+98UZZ5wRDz30UOyxxx6b+3PggQfG61//+pg4cWIMDQ3FpZdeGkceeWR84xvfiMmTJ0dExL/+67/GqaeeGr/8y78cf/d3fxcveMEL4rLLLourrrpqzD695z3vif/xP/5HDAwMxKc//el461vfGgcccEAcf/zx+Jo/+ZM/iSeeeCL++Z//OZYvX765fdq0aTE0NFSejzvuuCPOOuusmDNnTnz+85+Pp59+Oj7ykY/EmjVrmmvf9ra3xZVXXhnvete74sMf/nA8+uij8aEPfSiOPvro+PrXvx4DAwPl54qIyC+QERHZaVm/fv3IscceOxIRIxEx8vznP3/k6KOPHrnwwgtHvv/974+69oQTThg54YQTNv/9iU98YiQiRm666aZR173tbW8biYiRz3zmM5vb3vKWt4xExMj/+T//Z9S1CxYsGDnwwAPH7N/TTz898pOf/GTkQx/60MikSZNGfvrTn47Zn7HYd999R/bYY4+RRx55ZHPbgw8+OBIRI9OmTRt54oknNrdfe+21IxExcv311495v6eeemrkBz/4wciee+458rd/+7eb23/t135tZM899xxZt27dqP6/9KUvHYmIkYceemhUn3bfffeR733ve5vbfvjDH45MnDhx5G1ve9vmtrvuumskIkbuuuuuzW3veMc7Ruhofuihh5p5f4aIGPngBz+4+e9XvvKVI9OnTx/54Q9/uLlt06ZNIxMnThx17+XLl49ExMhf//Vfj7rfypUrR/bYY4+R888/nydJRGQr2bhx40hEjPznf/7nyLp16zrzv//8z/8ciYiRjRs3bu8p2iKmx4jsxEyaNCnuvvvu+OpXvxp/+Zd/GWeddVb8x3/8R1xwwQXxK7/yK7F+/foxX7t06dLYe++94/TTTx/V/hu/8Rt4/bhx4+LVr371qLZf/dVfbdJM7rzzzjjllFNiwoQJ8bznPS+e//znxwc+8IHYsGFDrF27tqdxvvzlL48ZM2Zs/vuggw6KiP9K+XnBC17QtP9sn37wgx/E+973vjjggANil112iV122SX22muveOKJJ+Kb3/zm5uuWLl0aJ5100uZv3iP+K8Xo13/918fs0zP/mhARsfvuu8cv//IvY9rNz5MnnngivvrVr8bZZ58du+++++b2vffeu1mfG264IcaNGxdvetOb4qmnntr8v8HBwTjkkENG/aqNiIhsX0yPEXkOcMQRR8QRRxwREf+VxvK+970vPvrRj8ZHPvKRRkh9hg0bNmBqxFjpEi94wQtGfUiMiNhtt93iRz/60ea/77333pg3b17MnTs3/vf//t8xc+bM2HXXXePaa6+N//W//lf88Ic/7Gl8EydOHPX3rrvu+qztP9unN7zhDXHHHXfEn/zJn8SRRx4Z48ePj3HjxsWCBQtG9Wdr52PSpElN22677dbzGKs89thj8dOf/jQGBweb/y+3rVmzJkZGRsYcw4te9KJfSB9FRGTr8UO7yHOM5z//+fHBD34wPvrRj8b//b//d8zrJk2aFPfee2/TPjw83POzP//5z8fzn//8uOGGG0Z9wL/22mt7vue2sHHjxrjhhhvigx/8YPyP//E/Nrf/+Mc/jkcffXTUtZMmTcKc8G2Zj63hmfnKYu+GDRtG/b3PPvvEuHHjsF+5bfLkyTFu3Li4++67Y7fddmuupzYRkW1hxOJKPWN6jMhOzFjy4jNpH9OnTx/ztSeccEJ8//vfj5tuumlU++c///me+zNu3LjYZZdd4nnPe97mth/+8Ifx93//9z3fc1sYN25cjIyMNB9OP/3pT8fTTz89qu2EE06IO++8c1RK0U9/+tP4p3/6p59rn57pS/5GfmBgIHbffff413/911Ht11133ai/n/mVnKuvvnrUvyh8//vfjy9+8Yujrj3jjDNiZGQkVq1atflfY372f7/yK7/y8xyaiIhsA37TLrITc9ppp8XMmTPj1a9+dbzkJS+Jn/70p/Hggw/GX//1X8dee+0V//2///cxX/uWt7wlPvrRj8ab3vSm+J//83/GAQccEDfddFPccsstEdH+ZGSFV73qVfE3f/M38YY3vCF+//d/PzZs2BAXXXTRdvtGd/z48XH88cfHX/3VX8XkyZNjv/32i6VLl8bll18eL3zhC0dd+/73vz+++MUvxsknnxzvf//7Y4899ojLLrssnnjiiYjobT6IZz4of/jDH4758+fH8573vPjVX/3V2HXXXeNNb3pTXHHFFfFLv/RLccghh8S9994bn/vc55p7/Pmf/3mcfvrpceqpp8Z73vOeePrpp+PDH/5w7LnnnqP+BeGYY46J3//934/f/u3fjvvuuy+OP/742HPPPWNoaCjuueee+JVf+ZX4gz/4g5/LuEREZNvwm3aRnZg//uM/jn322Sc++tGPxplnnhnz58+Pj33sY3HKKafEvffe+6zfpO65555x5513xty5c+P888+Pc845J1asWLG5amr+UFvhpJNOiiuuuCL+7d/+LV796lfH+9///jj33HNHpab0m8997nNx4oknxvnnnx9nn3123HfffXHbbbfFhAkTRl13yCGHxG233RZ77LFHvPnNb47f//3fj4MPPjje/va3R0Q01/fKG97whvjd3/3duOSSS2LOnDlx5JFHxurVqyMi4q//+q/jTW96U3zkIx+Js846K5YvX7759+h/llNPPTWuvfba2LRpU7zuda+LhQsXxjnnnBO/8zu/01z7yU9+Mj7+8Y/HsmXL4vWvf3286lWvig984APxxBNPxCte8Yqfy5hERJ7hmfSYLv1vR2HcyI7UWxHZ7vzFX/xF/PEf/3GsWLEiZs6cub27s92ZN29ePPzww/Ef//Ef27srIiKdZdOmTTFhwoT47ne/G3vvvff27s5mvv/978cBBxwQGzdujPHjx2/v7jwrpseIyJh8/OMfj4iIl7zkJfGTn/wk7rzzzvjYxz4Wb3rTm56TH9gXLlwYhx56aMyaNSseffTR+Id/+Ie47bbb4vLLL9/eXRMRkT5w6aWXxqWXXrq5CvbBBx8cH/jAB2L+/Pljvmbp0qWxcOHC+Pd///eYPn16nH/++XHeeedt9bP90C4iY/KCF7wgPvrRj8bDDz8cP/7xj2P27Nnxvve9L/74j/94e3dtu/D000/HBz7wgRgeHo5x48bFS1/60vj7v//7eNOb3rS9uyYiskPQtZSUre3LzJkz4y//8i/jgAMOiIiIv/u7v4uzzjorHnjggTj44IOb6x966KFYsGBB/N7v/V5cddVV8aUvfSne/va3x5QpU+Kcc87ZqmebHiMiIiIiv1CeSY/5zne+07n0mBe/+MXblB4zceLE+Ku/+qt461vf2vx/73vf++L6668fVazvvPPOi69//euxfPnyrXqOIqqIiIiIyFby9NNPx+c///l44oknYs6cOXjN8uXLY968eaPaTjvttLjvvvviJz/5yVY9z/QYEREREekLXU2P2bRp06j23XbbbcyfI/63f/u3mDNnTvzoRz+KvfbaK6655pp46UtfitcODw83VacHBgbiqaeeivXr18e0adPKffWbdhERERF5TjNr1qyYMGHC5v9deOGFY1574IEHxoMPPhhf+cpX4g/+4A/iLW95S3zjG98Y8/px48aN+vuZ/1DI7VvCb9pFRERE5DnNypUrR+W0P1vRv1133XWziHrEEUfEV7/61fjbv/3b+OQnP9lcOzg4GMPDw6Pa1q5dG7vssktMmjRpq/pY/tA+e/bspu1nS5E/w1NPPdW05UqBGzdubK6h5P+fLcH9DPRfJS94wQuatieffLJp23333Uf9ncuER0TsscceTRuNaZdd2qn76U9/2rTlUugUBHT/H//4x01btWrk//t//69py3lTtHZ77rnnFl8X8V/CReYHP/hB0/azlRfHegbdn+aDCtfQa2ndaZ1zvOV1iuD5prmlmNx1112btrwPnqmk+bNQXFGlTVo/eiatwT777DPqb+o/7b1qnOZ/BhzrtfkZ+Z8mI3hMe+21V+n+eb9HtOtHMhTFEK07rUGV73//+6P+pr1He4rGRHuDxkD7JcdWNdboOtov9Mw1a9Zs8bW0LhQfVWmM4jTP+dq1a5traL6pb3R+0BzRfOS26txWY4bOGUpNyHuNziJ63fOf//ymjc5cmiM6Z/L7C81Zpf9jPZPiOT+D+k/nDp2dFGu0Hyvv57TGNM58nkTE5oJsXaSr6THjx4/vWUQdGRnBtY+ImDNnTnzxi18c1XbrrbfGEUccgfvn2TA9RkRERESkwB/90R/F3XffHQ8//HD827/9W7z//e+PJUuWxBvf+MaIiLjgggvizW9+8+brzzvvvPje974XCxcujG9+85txxRVXxOWXXx7vfe97t/rZpseIiIiIiBRYs2ZN/OZv/mYMDQ3FhAkT4ld/9Vfj5ptvjlNPPTUiIoaGhmLFihWbr99///1j8eLF8e53vzs+8YlPxPTp0+NjH/vYVv9Ge4Qf2kVERESkT3Q1PabKlipgX3nllU3bCSecEF/72te26jmE6TEiIiIiIh1nm75pJ+mFxIws1lSFDvqvH5I9qR8vfOELm7Ys5JBcRtLO5MmTmzYSP0hwyZIBCSk0TpKRCBJ5aOzr1q0b9TetAUk7NN8PPfRQ00a/M0rCTB4rjZPmg+abhFiSkkkuzvNG4yThjOKjKirnNhJQaF1oHil2ad5Iqsn9pddRLJOUR/uW1or2aB4r3Z/miMQ0uj/NW75fVX6luCJZjdad5jLHFs0jnTt0HcUknQGPP/5405bHSmOi/lPM0BpUz+bcDxonxTKJX3Sm0BmwYcOGUX9X9wHFNwmD1A86Z/JeJrGY1o6eSe8vFLs0b3lv0DV01lEbzTf1g36UIp8Djz32WHMN/eIGnQvVH4zI11H8VX94g86x6ntEHgOd89X4k50T02NEREREpC/s6Okx2xPTY0REREREOo4f2kVEREREOo7pMSIiIiLSF0yP6Z3yh/aKvBFRq55HE7R+/fqmjYScakU2krOyHFMVluheJH7QfGQxiGRPaiNItKlWSc3X0b1IQqO5JZGsWl02P5eEn2plVpIISXaiZ+TYIiGK5C+6P8nAJDPmWKC5JWGJqpqSsFoVx/JzqyIZXVeV92gv5/6SaFgVbqmNpLwcC7RnK1WdI3gNSBKjvuWYIem5Wm2R1orGTv3N15FER8+sVkmldaf5ze8vtC50DtMeqor9ua16llaFxMrei2jHWq12TOcT3b8qRuZzl850eh2difR5gdaP1iXv+QMPPLC55md/A/sZ6H2J3jforM/zS/eqntcEjbPyox3VmCRZV3ZOTI8REREREek4pseIiIiISF8wPaZ3/KZdRERERKTj+KFdRERERKTjlNNjSLAiOYYEjiyZkhhDryPBj4QtgiSafD+SPEi6ItGGIBkkiyUkWFHVQKr2WYXExSy7VmUtmqNq5TkSqvbZZ59Rfw8NDZXuRbJupdLpWPcjiStD8U0SE80RrUEWLWlMtAYUf/RaEqWoLcd4dU9RfFeqAEewdJUlLtp7tAY0djpTSIbLMibFC50dJFTSmUjPpPtNmTJl1N8k6VWFfapGTOPKz6TrKP5ov9O6Uxud69/+9rebtnwukOBXFZVpDJXK0bRO1fmgM4CeSTGe47kqtlN8VOOoUnWazh2SmWmOSISmMdA657Piu9/9bnMN/VgBnbl09tN5l9ee1onOMIoFaqPzid7389lJ96I525bPC9sD02N6x2/aRUREREQ6jh/aRUREREQ6jr8eIyIiIiJ9wfSY3il/aKd8QsoXGx4ebtpyLlu1sA/lwFVzlinHK+cObkv+H+XVUk5nznWkXLlqUQjKxaPcwUoeM/W1Uhwqguejmpefr6N5pPxQyhWmXEoquET5j5V1qebt0loNDg42bRmKPxpnpThPBOeg0mvzOtMa05jIIagWHKH8bzpTMtWCLNU4yjnQ9DqKl20pHER51zmOKOeV1njSpEmlvlVzrHM/6BoaJ80bxRoVwcn56xFt3NP96Xyie9FerhQeo7Wj+1ddhmqhozxv9L5H+7HqsND96KzI7920PysxNFYbzRtdl+eS5qzq0lT7m/s2Y8aM5hr6jELQeUqxS+dppQBkNY9edk5MjxERERER6Timx4iIiIhIXzA9pnf8pl1EREREpOP4oV1EREREpOOU02NIpCDJgwoQZLmCpInp06c3bVQAh8SMqhiUX0syTrX4UaVYDEH3pzHRdRWZKoLHnuWbyZMnN9fQfJNER6+lZ1bWhWQnuhdRLfJEZGmJxkkSJMVMVXyryHYUVxQfJCVT4R2SCPMcUcEU2tskWNEYSNgiuTPPOQlz9DqC/nmT+pH3C92/KhvT/em1dH7QnGco/kharxa+qwhsJMdVC36RBE7XVQRhKmJGoiHNUVWoz3uNXkdzRmcF9Y2ovC+ReEn7jM7+6g8H0Brk51aLIdEc0TMptmi/5HOs8qMSEbwGFLvUt1/6pV8a9TcVkqO5rUrgdFbQe1/eQ7QGNM5qgbyuYHpM7/hNu4iIiIhIx/FDu4iIiIhIx/HXY0RERESkL5ge0zt+0y4iIiIi0nG26Zt2ku1INslSG8lfJEGS+FGtPEfXZSmKJCaSukh6oXHSf61liWbDhg3NNURViqI2EmFyf2nt6HXVaogkUJJEk19LIs+jjz5aeubAwEDTRuIb3S/HR1VqrVZOrUhRVXmXqmCSKErrR33LkHRF8U3zUd17RJanKvLuWNB1dM7keaP4JqGSYpmERNoHJBZm0ZLitrrfSdSj6yoiHc3ZqlWrSs8kqtV2c5zS/SnWqtWqSdTLMUNnEZ3X1X5QPFOF1RwzFMvVM5dE5bVr1zZtNNZKddxqtU+KZ7of7bW8J0lKprOO5ojGSWdbPndpPWm+Ka5I1qV+0LgqP9pB0Nhl58T0GBERERHpC6bH9I7pMSIiIiIiHccP7SIiIiIiHcf0GBERERHpC6bH9E75QztJJCRYVYREqg5GbVR5k8Qgku1IEMnSC8kh1QqMJAzSfDz88MOj/h4cHGyuoYAhSYWkOZKWSNLJUhS9jgQdEmFonARJRvkZJHVRtUhaKxKJSQKiZ1TGUK1+SvFH+yWvM/WVBDyKNYLWj+ZyaGho1N8kulL80d6gvlE8U7zl2KI1JtmT4ruy36lvdA3FFZ1rFAu0RymO8ryRSEb3orilOKJxVaS5devWNdeQPEnrTlWtaW/QWZ9lxmpF16qoTP3IbSRUkthJcVqtRErzlvtBsUAxT+caze3UqVN76hvtd4q/ajVYOhNp/XKc0rrQWUcxT3u0Uml92rRpzTWrV69u2giKj2pV5Dwf1ffk6nuE7PiYHiMiIiIi0nFMjxERERGRvrEjpaR0Cb9pFxERERHpOH5oFxERERHpOOX0mGr1UxJhxo8fP+pvkoKqFc5IzqJ/ZiEpJUseuV8RXB2SRDKSY0iqyWIhzWNVxiQZjp5JIl2ey2rFRLo/ycAUC9SWx1oRsyJYjJwxY0bTRjFDMmMef1WerK57RRgkIYr6T/cnQY5kJ5rfvIeoYiLFH/WN5o32Ml2X443mrCq707rQWZRjkqQx6j/tl6qoTP3N80FnEfWf9h4JptVqvjm2aN1p7HQOU/yRfFiptFk9c+mso/mg2M1tdP7R6+h8qlaErlRwpdfROGke6bV0BpCc2uuZSOterR5KY8j9pfij93c6EwnqR94b1C86K+g62rcUz5XPQXQvOp9ob3QZfz2md/ymXURERESk4/ihXURERESk4+xY/6YiIiIiIjsspsf0jt+0i4iIiIh0nPI37SRvkCRRESNJ7iHhjAQXqtJGQh9VlsxyDMksJJvQ/UkGocqSWYIkObMqT1alFHpt/i/JgYGB5hqSPakaIsk39EwaVxZRScYhYY4EuVWrVjVtVTkwzxvFH8VCNebpv9xz/FEsU3XLquxEkISW54iktKogTOtSrUqb57cqelHMV6XhLFDS3iYhkeKD1q86hky10jOdMXRO0hoQeT4q1UrH6htBgi3NUd6jtI9JSKT4o77RWZ/HRf2ie9F80x6iOKJn5HOyGsu0VhSnNJfDw8NNW44FOjcplun+1A+qtlupdkr3pzWg9yASYivVgmmd6DMQrXG1Um0lFqo/PlE5Y2TnwPQYEREREekLpsf0jukxIiIiIiIdxw/tIiIiIiIdp5weQ8Ud6J8UqOhGztOiXK5qviLlwFWLKuQ8uEqRhbGeSblmlB+b817XrFnTXEP5aDNnzmzaKI+vkjtNbdV8/tWrVzdtlFNMeXyUi5gL+dDc0v1pXaiQCOWgkkeQ45n6Wi2aRNfRGDKU50i5yBSndB09k8ZVKbRF46QzgOab7kd+Q+5vNZecoLmcMmVK05bjmXKRaT/SmGhvDA4ONm20HzMUy9U8Zrqu6gLQns/QGUOxQDFJfaOx5rWn+a74GWPdv1LoqOpt0TzS2V892yr7kdaAfCN6bcXpiWjHSucm7Xc6/2jeyKOi+Mt+B32moH7QexD1jfZjjj+Ktcr76lh9o/gjJyY/l9ad2qqfgbqC6TG94zftIiIiIiIdxw/tIiIiIiIdx1+PEREREZG+YHpM7/hNu4iIiIhIx9mm4koECVBZ8qiIIBEsV9B/EZHkQeJblpaq4lu1yA6JJbmgBEk1JFMRNHbqR2WOSNCpConVeSMxLc9lVWqlwhkUH9Q3io/cX5qz6jhJ9KK2PHbaB3R/EsLotdUiO7ngDUnJ9MyKQBjBa0rX5efS/qnMYwSfO48++mjTluU9ivlqcRSKP1oDKoiU+0v7sSrR0ZlCa1Ap1ELrVD2f6D3iscceK702xyTtdxpntQgdrXMeO11TlVppn9F7EAmOuRBgtf90f9obtKYUC3n/UcEveiatO8UMyct0zuS5pH7QM0kApXOBxp6fSfevSr60b6tnZx5DVf4nEVp2TkyPEREREZG+YHpM75geIyIiIiLScfzQLiIiIiLScUyPEREREZG+YHpM75Q/tFN1tyxZRrBYkqUOEm2qshNJGCSD0CJkCYgqJm7cuLFpo7FTdTd6bRbCqsJjtUJitWJaFpRIWCIJkkSeSnXBCJazcnyQFETrSfciYZCuI5knP5fiiuaD5EYSKEnYqtyfXkeSURb3IuqyeL4fjZ36QeteEfzGIo8hV8uNYPmQoL1Bc5THRWOi84mqT9K+pbYVK1ZssW8kjtJ5QhVXKSZpf1eEfRonyaSVipoRHJMUW3ms1R8XqAiVERwf+VyoSs901lHM03shibN5/Shuq+crzUdVqsz3q/4IAd2LxknnMJ3hWfCmZ9L7ElGtSpshoZxigdooTqkf9AMAea2oYjOt+470oVO2DdNjREREREQ6jukxIiIiItIXTI/pHb9pFxERERHpOH5oFxERERHpOOX0GJJIqgJlFt9IjCFRg2QTkjxIzCCxNQsujzzySHMNSUB0f5KuKrIT3asqApK4QlINtWVRisQp6j/JVBQLtO60fnn89M9SNHaqKkkidLViXx4XyXYkYlEbzVulOl+1AiPdv7pfKoImrSetC4l61UqhkydPbtqygEfX0LrQHqJxUj/yGGicVcmS5o3OD9pDef/RutMZRmcu3Z/kZepvfgZdQ3tv/fr1TVtF8IuonTMkKNL5R2s8NDTUtNG6ZGmzUikzor7fK1W5x3pupiKORvAaUIyT4J3nl+5F4yTo/YXO4cr7UFVKptiltaJzrPIDCRSTdObSfqTXUlum+tmj8sMHXcL0mN7xm3YRERERkY7jh3YRERERkY7jr8eIiIiISF8wPaZ3/KZdRERERKTjlL9pJ2GE5B6qhDZr1qxRf5MkRXIISSQEyTH02iy+kTBCldBIPiShaHh4uGnLFQYr1TkjeEwkhJGAUmmj+SHZiSq+0dirYm5+BlUvJPmGYobWheKUYivHKclOtFYkcFHMkKCU543WnURDgp5J46QKl3mOSBCjfUzxQbIa9YPWL895rlgcwRUBqVIoxWm1UnKGhEeK+apwVtkb1C+KSaoaS9WZKbamT5/etOV1oT27Zs2apo2kYYr5qriYx0+xRmcFjZMqXdP98vlB1xBVSZtigeYjx1FVYKU9SvenM3HDhg1NW662S9dQ36o/SEGvpTnKr6X9U6mkOlbfKlVdaW9TP6r7tio0Z6rVsKsVYmXHx/QYEREREekLpsf0jukxIiIiIiIdxw/tIiIiIiIdx/QYEREREekLpsf0zjaJqCS5kZiWZQqSh0iqqcqSJN+QNJIFjooEE1GvBEnzkQWUbRGFSMSiYKO2LBuSFESvq1btpPWrVGutVoiltarKWSQVZRmJxkTCI7VRP0gMyvFH11AsENUKeHS/inBH969WfawKwrkfJMRSdUtaT4qjijxfreRLcioJj1TBtbJHqa9VAZ5ifq+99ipdl+9XPddonNV9QOT3BBo7ncNERQKPaM9Yuobio0r1RwLy+yOtXfWcpL1BsUvvJSSCZyZOnNi0UVzRezy9p9H9MnQu0LpQld5KtdmI9v2A5pbuRf1YtWpV00ZrSvsqzxG979GPENB6ys6J6TEiIiIiIh3H9BgRERER6Qumx/SO37SLiIiIiHSc8jftlGNYLTaQc+oo15kKI1DuFuXsUY4u5ZDlflBOajVHnPLbqB/5tVTYgfJZaT6q+fyU05nnnIq00NxSzh7ls9J8Uz/y/WjslL9JhaUeeeSRpi0XCBnrfrkYT7WIFEHuBRVhysW9aOwUa9Q3uj+9lnJo8/2oHxQfv/RLv9S00RlAucf0jDPOOGPU3xRrlGdLeZ60NyrzS/uY5rZaFI3aaI7yfrz77ruba9atW9e0UUxSfFAs0Bgq96czhopeVYuR0Zrm62jOqsXf6Byjs4jiqPLMagE3ysWmuczvCfS+R+tChXdobulsputyf+l9mvLXaex05h5wwAGl6/I5WX2Po/Wk84+KE+XYpTWm84nij/YGxRG5BvlzFsUQ7bPK3padA9NjRERERKQvmB7TO6bHiIiIiIh0HD+0i4iIiIh0HNNjRERERKQvmB7TO+UP7VQYgQQ8kjWy+EGiEIkxVSmPCguQpJOFHCqUQ2MiIafXIjjVAkkk39B1JNWQKErSS4bkHhJhaAwkI1F/s2BG81gtSkLSKQk5FFu5qAeNk/pGkhhJ2iRs5etILquKl1nWiuCYoWfkuaR9cO655zZttPdov9D+rhTzojHRupDMTWtAsZD7RiIcrQHFAp0LVCyGhNIsrB533HHNNVXBlMZO59/y5cubtjxWituqGE5rXJHiI9rziYReKkZTLXxH98uxWz3/SCqkfUDPpNitFPapSvGVwmkR/CMMef1o/1BMEtX3ZJq3HIO0BvQeR/NGr6XzLu8r+iEI2hvV9ypqo77lGKS9R220p2TnxPQYEREREZGOY3qMiIiIiPQF02N6x2/aRUREREQ6jh/aRUREREQ6Tjk9hsQSkkhIVMkCBwk6JEqS1EX/jEHyDQmaWZKtCIpjQVIKiSV5PkiSqlY5JOlq/fr1TRuJi3nsJMaQyEP9oPmmvpEgl/tBzyTRhp5JUiiJXhVpjuaR4pvmjfpBa5Blbpozmg8SwkiMpJg/4YQTmrYsLdGcbcs/F9K4qG9ZuqL5pv1CVTZJLqvIxdV9TPFH5xhJpyTn5/tV9x6NkyQ0mst58+Y1bZUKsTfddFPTRvFN5ynNW6WyKVWVHBoaatpIqKR5ozXIsUXvcbTfSbSmPVSVevNz6UcfCIqZ6vlBZ3MWwWmf0XwQNB+0BpWq2RRr1P+qAE/9yGcWva76vlStlk5n+PTp00f9PTw8XLrXjiaimh7TO37TLiIiIiLScfzQLiIiIiLScfz1GBERERHpGztSSkqX8Jt2EREREZGOU/6mncQMEs5y1dGIVrggsWRgYKBpI+mKJBJqo/+Ky/2gZz7yyCNN2+TJk5s2En5IqsltJIzQfJCARxUBqTIcSTpZ9KX7ZwkmgiVLgvpGYlqWeUhYohii+Sbhh+aS1iXHMz2zWhGQnlkZF8mCNM5jjz221A8SpUh2yvuWYoGg/V4dA1U7zfu2Wml3zZo1TRtJaCQCZiGWnknQeVKtZFyZX9qzJJxVq5/S3qMKwnkMdJbOnz9/i68bi3vuuadpqwiadO7QeU0xT3NUqTRM60R7m340ga6jqpoVeX7GjBnNNXTO0xlA5061eneWfyuVfCNY9KfYpT1K50J+RnVd6Dp6v6U1yO8HNGe0t+n8oPcben+kucyfearVfatrLDs+pseIiIiISF/w12N6x/QYEREREZGO44d2EREREZGOY3qMiIiIiPQF02N6Z5s+tJOYQZJRFlBIUqF7UVW8tWvXNm1UUZTul2UTEnkqVU0jWHaiZ2ZBhEQhkmXoXg8//HDTRrITVffMY6hWlaRxknxI8g2RxR0S30gyIiGRRNHZs2c3bSSO5TYSrUmwovgmCahSFe+Nb3xjcw2JWbQGJChRbFXEWbo/rSfFJEHXUd/OPvvsUX9XK0HSfNMeqsjodK/K68a6jvZVRZq76667mmuqlXZpP1Kl0Mqa0jipjWKGePnLX960kbx3++23j/qb9g/tPRLIaQ9VqmVW5cnqjzLQWlWEVVp3eo8jqpWuSeDNsms11mhdaG/QOVx5H92WiuEUR/TaPC7qF8nABD2T+lb5kQBag+o5LL84Lrzwwrj66qvjW9/6Vuyxxx5x9NFHx4c//OE48MADx3zNkiVL4sQTT2zav/nNb8ZLXvKS8rNNjxERERERKbB06dJ4xzveEV/5ylfitttui6eeeirmzZuHXyRmvv3tb8fQ0NDm/734xS/eqmebHiMiIiIifWFHT4+5+eabR/39mc98JqZOnRr3339/HH/88c/62qlTp2KGRBW/aRcRERGR5zSbNm0a9T9KayKeSS2eOHHiFq899NBDY9q0aXHyySdjWuSW8EO7iIiIiDynmTVrVkyYMGHz/y688MItvmZkZCQWLlwYxx57bLzsZS8b87pp06bFpz71qVi0aFFcffXVceCBB8bJJ58cy5Yt26o+jhsp/rsASaEkXNDtsvhBsgzJJhWple4fUZO4SDYhGY6kLhoDVd3Lc1SVeyoiYwSPnYSzvC403yQnkShKkPy67777Nm2Vim9UEXB4eLhpo3mrSEZ0HUlSFH9UHZfm7bjjjmvasoBHciaJ0BRrlSqvERxHWWSq7NkIHlOuMBrB80Zxn/dLVSalM4CERFrTPOe0D6qVdqlvJEGSnJr7QTFEcVvl7rvvLvUtxzOdMZSnSRIdVX2kmKQKrnl+6XVLly5t2miO6LV0juXzn+K2WgWY7k/X0VzmOKVv66oVeam/tC60BpXK5QSdRTR2Otsq1YJpP9L7O92f+kY/MJCrv9K5QHFF5x9B6155D6bPFHT+0b6lytHbm02bNsWECRPilltuKVej7gdPPPFEnHbaabFy5cpRa7rbbrvh+9LP8o53vCNuvPHGuOeee2LmzJlb9dxXv/rVMW7cuLj++uvLr/GbdhERERF5TjN+/PhR/9vSB/Z3vvOdcf3118ddd9211R/YIyKOOuqo+M53vrNVr1FEFREREREpMDIyEu985zvjmmuuiSVLlsT+++/f030eeOCBmDZt2la9xg/tIiIiItIXdvRfj3nHO94Rn/vc5+K6666Lvffee3P67oQJEzanL11wwQWxatWq+OxnPxsRERdffHHst99+cfDBB8eTTz4ZV111VSxatCgWLVq0Vc/2Q7uIiIiISIFLL700IiLmzp07qv0zn/lM/NZv/VZERAwNDcWKFSs2/39PPvlkvPe9741Vq1bFHnvsEQcffHDceOONsWDBgq169jaJqCQSkOyURRKSmOh3K0nOqlQdjWDJL4s1lK9EVc+q8mhFFKX+0/2rIgyJKkReKxJ0SKYi4acidUXw+mURtVrNtlI9bqy+kciU5a+BgYHmGiJv0rGg+Mv9oFgjsYnGRPNB8z1v3rymLcfkzx4szzA4OFh6ZnXfUlvuR1UApX1QFbLzvqV7kRxXFetIOKtUSKRYJiGxKv3RuKhvObboXv/yL//StNG5Q2cFCcJEfm5VwqW3ry984QtNG/2YQD6vac9SzJPISKI8CaCVisoULyQfUpzSPqM1oPXLe5nOYXom9Y3mksZOY83zS/eqQvenfZDPGaoOTp+B6IcrqtVrK6IyrQF97qL+kmy8vXlGRL355ps7J6KefvrpsXHjxrJcvL3wm3YRERER6Qs7enrM9sRfjxERERER6Th+aBcRERER6Tjl9Jhq/hHlz+V/eqBccsp3o1xeyiGj3FLKBctUc+Yp94xybSmvNufj0f0pF55yV6lYTLX4TM55o9dR/l81v5JcBoqFnK9YyfcdC4oPyuWleMv5pkcddVTpmdRfKixFY89rT/ng1XzIM844o2mjeaMc1OwfUP46QX3LRUkiOOeXyHuI9gGtZ7XQDO21fD96ZtVhodxYOidpL+c4onihZ1JeLeVT09grRZ7oLKUqfxS7ixcvbtqqRfPyOlDcUm4zzfeZZ57ZtNHZvHz58i3ei3K/aT1p3ujcodfmuSQ3gOK7WvCL8vIpPvJrae/R2U8+E60xxQyd4XmdqR/V/Uj7hfqW14p8BJpbyn+m11Ic0Xmd16Xq9FD8dRnTY3rHb9pFRERERDqOH9pFRERERDqOvx4jIiIiIn3B9Jje8Zt2EREREZGOU/6mnYQtEoNILMnQf9XQ60gUpSIZJKdOnjy5acsy5rYUOiKJhGSnLMyQFERyI81RtaATrUvuGwlFVdGGpBrqB7XlNSWphorKkDxEbST00Xwcd9xxo/6muaW1olgj4YzuVxFsjz/++KaN5DWS1SiOiCw00xrQ3qgIXBF1KbkiH1LfaI/SWtEcTZ8+fdTftE7VwmNU0KR6juX5qArZdH+S0Oi1laJUJPPRWUr9fe1rX1vqx7Jly5q2HG+0djNnzmzaqgWGaOwnnnjiFvtFZyKNia6jc5L2S74fCbF07lR/NIGeSe9VeX/TPqOzlGKG5FTaV7R++bnVQnL0vrFy5cqmrfIDBnR/ej+jzwa0VjTftIfyWtHc0n6n+8vOiSstIiIiItJxzGkXERERkb5gTnvv+E27iIiIiEjH8UO7iIiIiEjHKafHkDRBEgldlwUOkjyqFQ0ff/zxpo0q/ZEMlys1UjXHadOmNW1U8ZKqPtLYH3vssVF/k6BDYhPJN/RaElDotdSWofmmvpHYVK1SmV9LghgJfvRMiqMTTjihaetVzKVKk1Wpi6SrU045ZdTfJESRgEd9o71HUhS15XWpVoKk+Ka5rVTeJEh6plggQY5il/bo0NDQqL+pryTN5n0cwfuxWr0x70eaH7o/xQdBsUV7OT+D4oqge9F+pH4ccsghTVuuyvsP//APzTXVM5Fil/Zo3suHHnpocw3F2p133tm0URVMErdpDPl9qHrG0L6l9atKyZUfkaD5rv5AAp0f9NrcDxoTzQftDRoTzVs+Z6h6K1UkpzY6Pygm6bq89lUZne7fZUyP6R2/aRcRERER6Th+aBcRERER6Tj+eoyIiIiI9AXTY3rHb9pFRERERDrOuJHif2JQJVKSQUhwyW0k2pBsR5D4QZXQSF7JciBVFqP7kyBHY6fXZmmEhLOKIBbBc0t9o7FnAYoERZInq+tCfavIhySv0bpQmOaqphEsFpK8nMdFIiOJTTTfJL9SLGTpiuaHYpnmtipzVyr3UixUq5NWqwoTub+07iTuUZVAuo5kuPwMinna2zS3JBrSviUBL7dtS0VDGgP1l/ZG7getJ0HzXa0ASs/I46draE/deOONTRv9mACtVYakZxIlaY0XLVrUtFUr1eb3COorrR2tMcU87ReSIPP+pjUgCbIqadPZRmuaz0U6J+nMqlZPJvJrq1XK6f2R9ga9lvqbr6NYoL5R1eLvfOc7Tdv2ZtOmTTFhwoS4/vrrUQjeXjzxxBNx5plnxsaNG/E9vkuYHiMiIiIifcH0mN4xPUZEREREpOP4oV1EREREpOOYHiMiIiIifcH0mN4pi6gzZ85s2kieInklSylr1qzZ4jURLNuR+EECEV2XxYdq5c1KRc0IXvgsMlG/SEghqasqDJJklIUWEnSoHwStMckxtH65byR7kshz1llnNW0kXVUr8VVkzGOOOaZpozFRHNH9skxGAiGtS1U6Jei6/Ayab+pbVY6m+KB+5HFR3FI1YuoHifIkceX9QvuYxDoSvQiaS5qPPG+07hTLdH5UJVYaVz7bSBYkoZKqSVPfqpJ9liVJeKRx0jlJsXbTTTc1bVOnTh31d/X8o3UhSJKt7CHqR1VOpb5V4yO/99EZUKl4HsExXxU0814gWZVijWKG3rsrP65Q3Xsk+VYrTFck1up60tyuXbu2advePCOiXnvttZ0TUV/zmtfsECKq6TEiIiIiIh3H9BgRERER6Qumx/SO37SLiIiIiHQcP7SLiIiIiHScbUqPIUGuUimPBJqqqEFUq6NlKYr6X6kaONZrK+JspWJsBEt5JNWQzEECUX4GSTskSdF60mtp7CTRZIGN7jV//vymrVr1kYQtEkuydEXVVUkOJMFqxowZTRutVZaWaH5IhKZxVuUsul+OZ1pjij+SCmndaU1pDFngpfmmvU1rUBVW8/2qVU1pHivVZsd6Ro6PgYGB5pqqpE3PpP5WKkDT/VetWlW6f/WfmGkv55ih+5P8SmOnNhLZ77jjjlF/U/VTEg0pPigWfuM3fqNpu+uuu5q2DRs2jPq7unZVKZT2Mp0fNNZMVYSmvtG6VyTtffbZp7mG5H96Jq0pxWmec1pj2o80doqFaoXVSqVkWoNKxd8uYXpM7/hNu4iIiIhIx/FDu4iIiIhIx/HXY0RERESkL5ge0zvlD+2Uc075YpSPl/NI6XXVAi/Vwi2UQ5aLDVAhEcpbowJGlPtOeXA5d5VyDqtFPShXuJpzntfl0Ucfba6ZNGlS00a5cpSzR2tAeZg5B/qkk05qrqE5quY2E9S3I488couvq+TeRnDMUN5hzq2nvFLaZ+QtvPCFL2zaCMpTzUVlKG+S1qDqolA807wNDg5u8V4UQ0TVicn5w9tyxtDeqOYj5z1E96I5o/xkWmNyDSp7mdaOCkvR+UFnLj2TcpTzc8lRqBY9qa7L2WefPervRYsWNddQLFAb9Y320Mtf/vKmbdmyZU1bpuJojQX5NdS3vC60xnQvej+vFomjGM9tdE7S+UdnAL0n0/3yGVjJtY+on4lV8hxV32+q74Wy42N6jIiIiIhIxzE9RkRERET6gukxveM37SIiIiIiHccP7SIiIiIiHaecHlMV30hUyZD8RbJWVfzIctlYr80CHsk4VaGSBFB6ZhYoSfp77LHHmja6jvpGBUfyOKlvJDdWCzmQAEXzkcXfiIhf//Vf3+IziWohr3nz5jVtJPNkeY/uRUIbSUA0l9SW460qa5HoVRWaKWby2Kv3r7bRfFPfsuhGfaX7T5kypWmjuVyzZs0WryN5ktqqRXZonHQm5mdUi+JQURmC+kZnZx4XCW2rV69u2ug6iiMSyCvyMsUQnTs0b3Qdiah5XU477bTmGhrT4sWLS/2oFr16xSteMervm2++ubmmWvCL2uhMofMpxwK971ULwlXjeXh4uGmbNWvWqL+r+6x6npJYnc91iluKK3o/qIrQFFt5X9G5Q+fTjiii7kgpKV3Cb9pFRERERDqOH9pFRERERDqOvx4jIiIiIn3BX4/pHb9pFxERERHpOOVv2qsV+yrVMkluJKGDBBTqBwk/FaGPKp3SmKgf9FqSGUkkydB8EPRfg7mqZAQLP1lYrchxESz8VAWrc889t2nL80ZiHc0HiUFnnnlm00ZibuV+1aq3VE2vGs9ZwCYRie5FcUVrQHFKbblvAwMDzTXUt8cff7xpo1gjsZrWL88v7VkS2kg8pzEQWcDrtXLoWNA4iTwuEgMp1qpnLsmeVLUzxxvNR/VHCChm6EypVMakeSSxmKpa05lL96uIl7QGJ5xwQtN2//33N210dtL9ct9e//rXN9fcdNNNTVtVnidBk86PfD+KIXoPqlZGp3WpVNutns30TBonvefksVerYdP9qW+0ViSP5vioVn6l/srOiekxIiIiItIXTI/pHdNjREREREQ6jh/aRUREREQ6jukxIiIiItIXTI/pnfKHdpJZSKQgkY5kuAxJHiQsVasmkgySZQ2SMavPJPmGpJR8Hcl8dC+Sb0i0ydJOBPc3rxWtJ0ljtHY0zhNPPLFpo7Hm+Jg2bVpzzdDQUNN21llnNW2PPPJI00biDolHWaCkMdF8VCvl0RrkGKd1J1mL1p3EJuoHPSOvPa0TQWOnOarIZXQ/6j8JmrRvSX6tVGElaEx0rtF1RKVSI42JzjXqB8UHPXPVqlVN2z777DPq72plRbo/xW61Om6WoykWSDol4ZvmoyJoUr+ojcZ01FFHNW3XXnvtFp9Jz6B5PPnkk5u22267rWmrxgyJi/m6yntoBMu1dA7TfqFzMs8RnTsko1d/wKBSHbf6eafX6rsRtQ+KNM7q+SQ7J6bHiIiIiIh0HNNjRERERKQvmB7TO37TLiIiIiLScfzQLiIiIiLSccrpMdWqZ1SRMosTVWGJxI+qFEr9yGINyTIk1ZCcSqIoSZtZGKS+ktxTHWe1+lpeAxJ0qlVSjz322KaN+kvxka8jkYwqnVJ8zJw5s2lbv359qR9Z2NoWyYggISzHEY2pKgPT/ek6kkyzZFWtWlmVo0kepWqcWf6l19E+JiGR5oOqtVYqB9LYKT7omVRVk2Iyj532Nj2zsp4RPE5ag3x+kNBLcVqtuEpnUaVtW/YjXUdrNWnSpFF/09lPr6ucJxFcEfrmm29u2vJYq+LoKaec0rTde++9TRvFM5FjsirF09lPa0z7m8jrQDFP7930zGrV8zx2uhdB96q+j9KPH+RYqFwz1v27jOkxveM37SIiIiIiHccP7SIiIiIiHcdfjxERERGRvmB6TO/4TbuIiIiISMcpf9NOkg5VPiQhp/I6EkZIBiGphgQo+i+nLNaQGEMSEIkl1cqEWYoiiY6q9dG9aI5onFT1MQty1aptCxYsaNpIiqI2mt+8pq961auaa0i0qVbiIxGQZNe8DjSPNN+0ViQB0VwODg6O+rtSnXOsZxK0N0hkyhUYqxIx7ceqNEdrleOjug+oHyTNkaCZX0vxQhV0SfIleZTGOX369KYtVyclsZPuPzAw0LTRvNGaUiwMDw+P+pvk3Wr1Z4IkwlyFNaJdF5rvXqtKjvXMLBHSPqbX0RpXq4dS5egsp1arsNLemDNnTtO2ePHipo3mLZ8fdA6TqEz7keaI5pfO2Bxb9KMJNEfU32q18bx+1P9KdekIfr+p/pBHXgMaO92rKhvLjo/pMSIiIiLSF0yP6R3TY0REREREOo4f2kVEREREOo7pMSIiIiLSF0yP6Z3yh/aqHFiRO0lmoddVZJkIFktI1shSFAmsJMuQnEX9oEp8WVQh+ZAko+o4SaQj2TVLXCS+nX766U0bicUk3xD0jHPOOWfU3zROWnfqB0mQJOVR7Oa5JJGRqu5VhUESiLLcRHFFElNF7o7g+KN4zvNBcUX7sVppkkROkq5yPyqVCiNYQqNYoNjK80H9p3ihdaG5pVig8y7v0apMSmtF5xitAc1vjlOSCumcp3uRdFqR0SNqlZJpv1TlfIqZ3EZ9rVakpDiiWKBxvfa1rx31N4mjJObSGtPZnM/ciIgbb7yxacvrXI3vLNjTvcbqG81vjo/qHqV7VSslZyGb9hTtUYoZev+tVvjN81s9F6rVZmXHx/QYEREREZGOY3qMiIiIiPQF02N6x2/aRUREREQ6Tvmbdsqdpv86oQIYOV+4mjM6derU0v0pr5FyXCdNmrTF11EuXjWnnchzRLly1TzpSjGGCJ7fPB+UV0qvIyh3laCYyfNbjSEqckLzRrmflZxIKo5C+YRUGGzNmjWlfuSxk99A+ZCUq0k58zQflTilvlaLmK1bt65pozFQvOVcbxon5U5Tbiz1jfZyjrdq4Sp6JsUujbMSC9UCbrQuNAaaSzpTcszQfFP/V65c2bRNmTKlaaM5ohz/WbNmjfqb8rXpvCaqhZny/SrF8caiWlCHzpk8R/RMcm6mTZvWtNFZROd1xRejM4Zicu3atU0bndcbNmxo2mhf5WdUi79V/Sjao3mslM9P8U1neNUDq3iCVaeH5kN2TkyPEREREZG+YHpM75geIyIiIiLScfzQLiIiIiLScUyPEREREZG+YHpM75Q/tJNwkcXOCJY1KgUDiKGhoaaNRCmCFiHLJSSbkMREkGREAlEWXGgeSVKpSjUkKFGhhSxiHXvssc01JLOQtEP3pzGceOKJTVsWOasSJ4lpJCqTBERzmWWeaqGwqqhMslAWc6uyIF1H/aDXUszk6+hetO7VwlV0v0phEhon9b8qB1bkL7o/FdUi0ZDaaN2J/FwS/KrFw0jQpJgkGTOvAe2VXBQsgkXD6tlMe7QimdJ+rBTKiahJ5SRs0nsc3Yvmtlr0KsfkvHnzmmvuuOOOpo2EXpL/qb+nnXZa03brrbc2bRmKD4oFeiaJrTTneQ/R+zT1o1rwsFKkkO5FfaV1p5ik9zTqRx47nel0NtNZJDsnpseIiIiIiHQc02NEREREpC+YHtM7ftMuIiIiItJx/NAuIiIiItJxyukxJOSQJEHV0bKQQ/8UQXISCR2VKpsRLLXl6n8VSS+CRRiSSEi0ydIIjak6dpJjaD5ISjn11FNH/U3jJEmqWhXvuOOOa9po3rJcTIIOCW0koVUr/VEs5HUhaZHWmGRd6i+9Nq8fVTCl+SbZk4RHGietVZazaJ1IdqL+0v1pDWhcOQap/xTztDdoDCSJZapxRXFKMTM4ONi0kchZuT/tY5qPqrxM/agIeDROOrMoJikWaM7zWKn/JBWS8EixS+dkht7jqP9VyZLmm85YipnM2Wef3bQtXry4aaOYob7RWuUfDqD7U3xUxUg6OwcGBpq2HM9UdbkqrdN1VNU6j4HWicRigs5rOovoujxHdK5V32+6jOkxveM37SIiIiIiHccP7SIiIiIiHcdfjxERERGRvmB6TO/4TbuIiIiISMfZpm/aK9JpRCuD0H/VkIhFEgZVFK3KU1lwoWuqUl5VNskSUFViqlZ3q8hlERETJkzYYj9IcCFhiaqpUn+psmSec5KIaY1JKCKJiaoEVio1VquT0nxQG8VMnqOJEyc219C6UExW91BFCKN4qVRujGAplGKyIp/TnNEaUMyQEEv9yHNOAhedCxQftC40b7Q3cn/pGjpPaE9RP6oybZ5LWoNp06Y1batWrWraaK1oDSqyP/WV5Fcae5U851RdmmKhWjGXXjtjxoymLT+X9iP1be7cuU0bVTUlgZziLZ8L1fcq6hutX0VAptdSvNDrKBaqFZtz3NM4q/FXreJcqcZM5wm9rirJyo6P6TEiIiIi0hdMj+kd02NERERERDqOH9pFRERERDqO6TEiIiIi0hdMj+md8od2kgNnzZrVtA0PDzdtWWTasGFDcw3JfJVqjhEsuJBQlYWZLGdGsKBDbcQ+++zTtOWxUv9JhqN70XUkWc6bN69pyxINzXd1nCTpVIWcSj9IfKOxV6shkqSTRSaS/qoxSdIVvTYLYdRXet2UKVOaNlqrqvyVJUVaOxIvqY32Owm2tH55zun+VGmX1qBa2TTveVp3ktzo/hR/NM6vfe1rTRutfYbOCpqj9evXN20kS5JIl/cy9eu73/1u01aVJUkiJDkw9616Dm+LKJqFRNo/1NeqtD516tSmjX68Ic8lyYd0ztPYqz+aQGdKrjxK7yPLli1r2mg+qj/yQNdVriGZlPYoxQzt0bz29Ew6dyjmq5VZqR9Z3KaYrFaUl50T02NERERERDqO6TEiIiIi0hdMj+kdv2kXEREREek4fmgXEREREek45fQYkg9JgCKBIwt4JNtVK0GSoLlmzZqmjSSg3DcSdEgsoetI+CHZafLkyaP+XrFiRXMNiZLUj2q1VpJS8nUk4OWKeBERRx99dNNGEhBB/cjCVlXQoXHSfJC4Q2PNzyWRjCQm6gdVHKQx5OqhJMzRulMbiWkkLvZa/Y/mg66jqrRUBXP+/PlNW16XbaluSfNdqaZalT3pzKoKqyeeeGLTlmVDioXbbrutaaO5rVZ9pP2d5432z/Tp05s2knxp7LQ36J+i81rRutA+oPcDmiMiz0e1IiqdATQmEkppDWiOKs+kc+2ss85q2u68886m7ZFHHmna8vpVxVGKNZoPep8moTmPlfY2/YjEtqQ45LM5S7lj3b9aGZ3WvfL5hsZObXQ2dxnTY3rHb9pFRERERApceOGFceSRR8bee+8dU6dOjde85jXx7W9/e4uvW7p0aRx++OGx++67x4te9KK47LLLtvrZfmgXERERESmwdOnSeMc73hFf+cpX4rbbbounnnoq5s2bh/9a9wwPPfRQLFiwII477rh44IEH4o/+6I/iXe96VyxatGirnu2vx4iIiIhI39iRUlIyN99886i/P/OZz8TUqVPj/vvvj+OPPx5fc9lll8Xs2bPj4osvjoiIgw46KO6777646KKL4pxzzik/u/yhnQqmVPPscs5etRgS3YsKHEyaNKlpo9y7HCRU6IJySymflfL4KEct55/lHPex7k/zMTg42LS98pWvbNrID8jzUc0fptxpyp+jtSJyvnC1sA/l0NKmp1xHGkNeP3omxSmNk3L3c45kRLvOq1evbq6hflDeJOVIVvP+8/1WrVpVeiYdLJRfWXEZItr1o/mmom60BpQbWynAQnNGfaW+0f1p7JQnTrnYmVNPPbVpo71H3+788z//c9NGsZXPU8olp/1DxXkoN5vitDJH9MxqzBN0xub5oLmlMdGeov5SnNK85fWjflTHSX2jNaX3uexW0fsjuSnXXXdd00bnB7lbFAv5TCGngnyjqmdBc5T7Qed3db/T/el+NEfZk6nmr1c9M/nF8Iy/Qp+Tn2H58uVNwbLTTjstLr/88vjJT35SKjQW4TftIiIiIvIcJ0vsu+22G35Z8LOMjIzEwoUL49hjj42XvexlY143PDzc/HDDwMBAPPXUU7F+/XqsBE+Y0y4iIiIifeGZX4/p0v8iImbNmhUTJkzY/L8LL7xwi2P5b//tv8W//uu/xj/+4z9u8dr8rzDPPHdrfjnNb9pFRERE5DnNypUrR6Vxbelb9ne+851x/fXXx7Jly2LmzJnPeu3g4GAMDw+Palu7dm3ssssumOI9Fn5oFxEREZHnNOPHj0f3IjMyMhLvfOc745prroklS5bE/vvvv8XXzJkzJ774xS+Oarv11lvjiCOOKOezR2zFh3YSHaiIBYkfuUN0L+o0JfWTmEFCBxW2yPIKyTgk/ND9exVzSZ4kkYykU7qO5pJkp1wsguabisCQ8EP9qAR6RCt2kUBYKYYUwVIyCT8bNmxo2rKYRuOkWKZnUnzQOmfZjiQmoiJVj3U/GsNjjz026m8a04IFC5o2kkIp5qmNZLj8XCpWROtO0D4g+TC3kUBI80FCIt2f7kdia4b+eZReR+cfzdGv/dqvNW033njjFp9Be5vWheRa2gc0LprfLM9XC/tUC99VitXRGVYt4EZ7j94f8zdt9Axad9o/1Dd65lFHHdW0LVu2rGnL3yrSfNDZXJU2aVz0TWaOBRoTnX90hld/oYRymSv3J6G8KufT+ZGvo35UY6HL7OjFld7xjnfE5z73ubjuuuti77333ryvJ0yYsPl8u+CCC2LVqlXx2c9+NiIizjvvvPj4xz8eCxcujN/7vd+L5cuXx+WXX15Kq/lZzGkXERERESlw6aWXxsaNG2Pu3Lkxbdq0zf/7whe+sPmaoaGhWLFixea/999//1i8eHEsWbIkXv7yl8ef//mfx8c+9rGt+rnHCNNjRERERERKVL6Zv/LKK5u2E044Ib72ta9t07P90C4iIiIifWFHT4/ZnpgeIyIiIiLSccrftJPsRG30XyxZSqGKXiRlZHkyIvAH6EmQo/vRczMkO9H9Sb4hGSRLNSQx0b1IZjnhhBO2eP+IVjSMaOeDJFySumgeaZzV6rJ5DUgao7gi8ZcEU1o/Et+y8EPCJq1V9Zm0D/JYq9UiSdalmKxWHMxllmluq3ubxDSKBaoEnMdfmbMIFrGojarjVs4i2nu0D2i/UCyQBJmhNa5W2SRo3s4444wtXnfDDTeU+kHjJNGQ5L2KME0/JEBzRPuRfj6N1jnvIZpbiiGC5ojOD5q3/NzqexfNI617RYqPaOeD7kXz8apXvapp+/u///umjcZA85HPBbqG3vfo/iSxUnzk2KXzj+aM4rva34o8SnuK7k/nn+ycmB4jIiIiIn3B9JjeMT1GRERERKTj+KFdRERERKTjmB4jIiIiIn3B9JjeKX9oJzGIxB0SM7JYQqIXyRUklpCcSrIGiUF5YarVxqpyGfU336/6TILEIJJqqL+5ouMxxxzTXENiXaWSZUTE9OnTm7ZVq1Y1bVmWpGeSYEUxU6msGMHCT75fVXCm/lIbCVu5siT1iwSoxx9/vGmjOCLp6jWveU3TlteUJFxa92olY5rLSgVDuhfFPIl6JHNXpL9qRVe6/yOPPNK07bvvvk0bnYl57el8Xb9+fdNGFRgp5ik+KLbyOTlv3rzmmjvvvLNpq54V1A/ay9RWgeRAmg+K3fzaagVu2rfVasTUt7wu9L5arQBK5w7tgzlz5jRt991336i/qQowve/RfqGxU39pXTIUQ7TuVLmX+kHrl/tBP6JAP/BAc0sxQ2OgMyXPL80tzSOdibJzYnqMiIiIiEjHMT1GRERERPqC6TG94zftIiIiIiIdxw/tIiIiIiIdp5weQ/IGCaAkRGTRplphj8Q6em1VTMvXkRREQhEJpiRiUX+zeFmtMnfooYeWriORpyKqUP/pn4hIdKV5I0GYKnTm66ZMmdJcQ/NNAhTFH80vXZfHStIVQbFGfSMpL8tNJBpSfJNASGM68sgjmzbat3mOKsLwWPei/tJc0muzTFapphzBUhetQaViKb2OBGfa2ySr0flBr819o/OKqPaD5o3iKEuPJPhRJea77757i/eK4HFRddyhoaFRf5MUWpE4x2qrVDymM4zmg/Y2jbNaVTifdyQy0jPpbKa9THNJ98vPpb7S3qMz8eijj27a7r333qaN9lruB8UyxRpVzKXPFbTO+X2IxHCKIZqj6g8M0DPyuZv3BfU1gt/3uozpMb3jN+0iIiIiIh3HD+0iIiIiIh3HX48RERERkb5gekzv+E27iIiIiEjHKX/TTiIFCYMkpWR5hYQiaqN7keRRlVizJEb3ov/iItmO+ksCUX4myUMkxpDYRM+ksZMMd+65527xXiTlkXhEEiS1UT/yuGiNSaSliqsrV65s2oj99tuvacvV7eiZAwMDTRtVJyUBisaVZSGSPQmKK5IDCaoSmOOZBCtaTzoDKI5IACXya2nv0TNJuqI1oH5ksZD6X62KTH2riL/Ujw0bNjTXUPXTSpXXiLogl9eZpD+Kj7lz5zZtN910U9NGVYVJ7szQGlSFbDqzaD7yewQJvfRMolpBmM7ELG7T+yrdi9aT3jfovZBk4OOOO27U38uWLWuuofdCaqO+kXRK65LPTloDOq9J7KTX0g865PvReULvj7QfKRaovxWJnyRi6gfdS3ZOTI8RERERkb5gekzvmB4jIiIiItJx/NAuIiIiItJxyukx1Zy6Sm4z5XhSzhflfxOUl0rkogSUK0z5YpSHSHmTlAeX543GTjmjlBNNbZS3S8/I/aB/DqI2ykMcHh5u2igflF6b55eeSTmBdN20adOaNpojykPP8UbxR4UtaExUTKiaE5mh/p955plNG8Uk7T2Kj5znSTmv1FfKtaX8ZNqPNG95nWk/Uo4uxQIVqqKYzDnW5EpQP2g9KYeWzkTqR3YNpk6d2lxDY6L5pvtTTFb8FxonzQeNc968eU3bl770paaNYjzHLsUQ5fhTnFYK/NH9aJyVYkgR9Vio+EB0/lWfWS2QV/HFKNbo3Kn6aBST9LkinxW0dhTLNEfke9B5l+eo6mKQi0L55dQ3mssM9b9yrnUd02N6x2/aRUREREQ6jh/aRUREREQ6jr8eIyIiIiJ9wfSY3vGbdhERERGRjlP+pp2kGhIuSOjLIgkJGCRXkOBCkiX1jUTR3DeSgkhwIahoDYkq1LfM/Pnzm7aqTEXzsWDBgqYt/5ck9YtkGZKHSJqj+5EEmdeF5pH6Qf8lTBIayVlUTCOvM8laJCxVZUmK8TxHNGennHJK00axTHObC0aN1bccR7T3aB/TGtA4aV1Its7jp0IiVcmcBEraL7mNpD/aUySE0RyRcEvX5bOChLxKEaIILs5WFd/yOUP9qMYfnRV0Ft1yyy1bvB+tAc0tQWtKbRkqdrZ27dqmjd73aJ/RWUTvL3nfVoXKSgE3un8E78d8Bh5//PHNNdddd13Tln/gIYJj7fTTT2/abr311qYtQ2Oi9aR1oTmisyLHOMU3tVGRKvoBAzoDKjJtRVaN4PWUnRPTY0RERESkL5ge0zumx4iIiIiIdBw/tIuIiIiIdBzTY0RERESkL5ge0zvlD+00KBLpSN7LwicJoCQxUQU8ei2JHyTzZKGFBK6BgYGmjeQskkiob3mOaB6pYidJUSSmkchJfctCTrViJwk/tFZVaS6LNSQx0XyQDEf9pXFVRGWq8kriG7WRLESCUo6juXPnNteQeEnjJOmqUnkzoh07iV6Violj9W2//fZr2kjKy3uUYq0qhNG80V7La0CyKsVatQItPZPk5RxH9DpaTzqz6DpaP3pGPisq1UojWG6kdaFKjSeddFLTdsMNN4z6m8Q6qhBL60drRWdijgV67yLBlGKN4rtaPTmL/VURmuKU9ju9j9J1ed5InqRzjeab5oPWimImxyCdC9W9R9dRjOe1p/OV+kHyPL2n0WcIiq085zQ/9F5Fayw7J6bHiIiIiIh0HNNjRERERKQvmB7TO37TLiIiIiLScfzQLiIiIiLSccrpMSRNVCXILMyQ0EaSSlXuIfmwUjmVpBoS60hqpf4SWUIjeYikF5LXSDgjaZNEqSzk0NqRaEP9qMo9JE9lYZVENVpjuhetO42BJKA857TGFPMkkpGIRcJgvh/F9+DgYNNGMUnrR//ER0Jflp1ofmgNaK0qgl8Ex1GG+l+JoQge54YNG5q23F96ZkVgHQt6baVSI8UfiZGVSs8RdXk+Q+IbrR2J8tXqjRTP+byuVDCNqIvEdL885xRDVbGTqFbLzPNWFTapbxRr9H5AY8ixS/FHa0f3omfS+xfFTI6FbanSS+9LtA/yWCs/KhHBe69a8Z1k7rz2dNZVPwd0GdNjesdv2kVEREREOo4f2kVEREREOo6/HiMiIiIifcH0mN7xm3YRERERkY5T/qad5BgSd0hWy3IMCTQk/ZHQQWIJSVz02iwjkZBCbSTVkARJwlaWas4444wtXhPB/SfZhNaAyBJNVTCtSn+0BjRvWSoioYgkSOoviZx03cyZM7f42kmTJjXXkIhF8UHxTLLQcccdN+pv+q97qvpIchmJgHQ/2o85dqvzTTFPomi1emjlddRGFQdJOiURMPeXRDLajzRHdCbSvFWqJ9O6V2Oy2g9avxy71WqwNEcEjZ3O+rPPPnvU39dcc01zTfUHB0gWp/jLa0+i4YQJE0r3pz1Ke4/mt1KVlu5P0P2pvyR35rbqjwvQ+UfvG9SPs846q2m79tprR/1N6059o71McipJyXntKa7oPa4iOEfwHNG48jlDP2hAr6MzUXZOTI8RERERkb5gekzvmB4jIiIiItJx/NAuIiIiItJxTI8RERERkb6xI6WkdInyh3aSCkkyInkqSxgk0dH9SXgkWahaoTNXbiNh5JFHHin1o1p5syKcESRrkRxz0kknNW3VynAZ2kQk99C9SJCjfmTRhsSmakVKkj3pmZXKktUKjCRTTZw4sWmjdcljoL7S2EnqonWhCnuVar50LxIvaY4oFqiNXpsFRxonxTxVZaT9QjJmHhfdq7KPx2ojKbQyl/S6NWvWNG20nrR+dB3NRyXuq2tMZz/JmJX3iJNPPrm55s4772zaaN6qZ2eG9h69L1WFaYpnIp8DdCbSuUPvvzQf9J5J5NfSGpM4ev311zdtFPPVOMpttM8qMmkE7w1a58oz6V4kCNP96X4UM3ntaUy0j+k9TnZOTI8REREREek4pseIiIiISF/w12N6x2/aRUREREQ6jh/aRUREREQ6zjZVRCXZhCSuLHmQgEFyKkliJN9URZv8jBUrVjTXkNxIsgz9cwqJhbmtKidVxRWqjkZrkOeSKudRhVGaD+pbVYLMUi8JRVQ9rlLhdiwqlf1I7qG4qlaMpL7lZ1K8kNhEEhqJddWYyddVxSm6rloZk86K3DeKZaoCTHFK8h4Jj8PDw6P+poqXVEmV9lT1DKB4zvNBr5s+fXrTRnFaHfvQ0FDTlvdj9Z+JaU+tWrWqaaP5Jdk/n4sULxQf1F+KD5JT816mvULyYf5BgwjeB5VqxBHtXFIs0I8hUExSf+kco5jJe57OInodzS3FB52ddE7mmKFx0r2ojfYLfZbJa0+fMyg+6P7b0rf83Gp1ZtrvXcb0mN7xm3YRERERkY7jh3YRERERkY7jr8eIiIiISF8wPaZ3yh/aaVCU40X5Yjm3mfLFiGpuXzWvLOdcUq4cQXno1TxgastQ3hq10XxTvjPNG61LZvbs2U0b5f1TrjrlP1K+Yr6O8iGpqAw5D5RfSfFBbkTOm1y7dm1zDeWf0jxSHimtX44jiitaY4q1am499TfvP+o/tVGhmWphM9qjOY4o15nyNyn+KOYrxd9onShuq7n7tAbkheRcfYpbyuevFjtbvXp10zZr1qymLRd7q46TYpL2Mu1RWtNM1dmgM5z2LfWjMlaKNcpVp3tNmTKlaSP3J88H7Vk6XwnqLzkVFEcZ6geNs1K4L4LPD4qjdevWjfqbzjqKIToDKD4qBeeqBdyqufvVucz+QbW4UvUzlez4mB4jIiIiItJxTI8RERERkb5gekzv+E27iIiIiEjH8UO7iIiIiEjH2ab0GJJBqLBFFoNINCShg+6fxamIeuGCLKoMDg4215CERm0kl5HslCWgarEiktCo6EtFKIpoRSYSY7IAFMHFUWicJMKQPJrnkoQfKhRRFR6pb1OnTm3ashRFsUbrTtedfvrppX7k+KsUIouoS3lVmTELsLROVXlt4sSJTRuNq7JfSCCk+KMx0bpU5o3WmIQ2EsJI9qQ9SgJeltro/tRG8jKNnV67YcOGpi2fA3Qvkt1JjCRBk9aP5jcLvBTfxx57bNO2ZMmSpo2gvbFp06ZRf9M5QYI63Yv6S+teEUWr614RGSNYjqZ1zmcPrSfNEQmatK+oKBXN0cDAwKi/Kf4qxesieK2oLd+P1onme1sKUNF85LON3qerxbK6jOkxveM37SIiIiIiHccP7SIiIiIiHcdfjxERERGRvmB6TO/4TbuIiIiISMcpf9NerQxHQmKWDeleJFeQ+EZCW0V+jWiFFpJZSGClZw4NDTVtJJa86lWvGvV3VT6kOTryyCObtuq85fkgwYXkQ7o/vbZaFS+Pi/pKcirNG0lAJIQNDw83bVmgJMmIBGd6JsUaCdO5b/Rf99UKe5VqsxEsAmYBj9Y4XxPBAiFJoURFaKY1IFGtKn9RLOTYorGTNEYS5/7779+0UTzTHOVzhvY7yYIkRtJ+pLWqVFKsnB0RHLu0LtXzKa89nUVUYZT6QaIhScNZ7qR1qlYipfmm9aNn5PWje1EbxTe9L02bNq1po/MjQ/NI/aD3pXvuuadpozOF4v6Vr3zlqL9vueWW5hqKKxLq6fyg+MtxVJVO6V50fpBMW6lSTjFEYjHNh+ycmB4jIiIiIn3B9JjeMT1GRERERKTj+KFdRERERKTjmB4jIiIiIn3B9JjeKX9oJ/mBRBgSIrJcVxXrqI36QfIrCX1ZJCGphirP0TPpOpKnskhCQhSJriQs0ZhItJkxY0bTlqUXEvdI9CIplOQb6getSxZKSd6ldScZju5Pa5Ur7EVEPPbYY6P+rkpMJBTRHFWq9FYPiqqEm8cUwbJTfi31n+Kb7kXzTfuqUlmSXkeSOY29KqfmcdF60j4muYzWhWKSqqTm+aCxU3xQRcqqIEcxns9wOmOoHyTm0v0pPmje8rrQmOh1tH40BlqXXqH3OIpTen+kfuT7VSvc0n6vxint5VyJmu5flUlJeqY4qryf0zxWqVaqze/BlYrqEXzG0I8QVIXmPB/UVzqb6QyXnRPTY0REREREOo7pMSIiIiLSF0yP6R2/aRcRERER6Th+aBcRERER6Tjl9BgSaEiSoMqHleprdP9ctTKCxZV169aVXpurnFFFVxIeSUCh60hgy88kKa1aBZMEJRJtSM7Kwgw9k9aJRFGSogYHB5s2ktVyf+maaqzR2GmtSJ7K60fiEbWdddZZTRuJQSQS576RxNSrVB3BQhjdL8f9tlRgpH9WJMmNZOs1a9aM+rsa33QdrRWRx0XzQ/2nvUGiHomi9Nos4dKeorOOYoEkNBISKU7z3qBzh6pD0vlaqbgaURMSK9VsIyIWLFjQtN1+++2lZ+Y5p/7TeU3rWa1uXKkaS+cVxQK9B1UhOTW/j1ar6tJ1tFZ0PtG85bWns5/ilORrWgPaa7kfdA3NWXWPVuXiPK6q0EvnTpcxPaZ3/KZdRERERKTj+KFdRERERKTj+OsxIiIiItIXTI/pHb9pFxERERHpOOVv2um/REhAIQkjCxwkRJFEQjIfiZEkuVFbFlpImiXphcQjkghJLMlCTrV6IbURWWgb67W5WiH1vyIMR/D6kTBD8k2W/KivtC4kB5JwS2tQEcfo/tT/KiQoZcmI9g/FLfWfxl6tnJoFNnodrWdVXiZJjHjRi1406m8Sw0leq+5Hmsscb7QGtHa0zwjqB52dObZoTLR2tPcq4nkEi7N5fmkfVCtBkhhJlUJpX+XxV8816i/1g2Irn83VH1GgdalWza6csfQ6EnPpmbRfKuse0a4prRO9/9K60PrR+UGScz4/aL7pXtRG0FzmMdD5R32lcdJ5Ss+kyql5fukspbWjH3SQnRPTY0RERESkL5ge0zumx4iIiIiIdBw/tIuIiIiIdJxyegzlrVG+FeWE59y7ah4YFbYgKNcsF26h59KYqP8E5ftR7l3+Zxd6Jr2O5oPytSm3lIo85bWi+1MOMBX6qOToRtRyDCknlaBcUFp3ul+lQEo1lqlYDM13JW+S1oDWk+5FUBxRkZ3cX7o/va6aX075oJXCPgTNbfYzIjgmK3u5kl8dUevrWP2g3OO8zhSj1H9qo7mt7u/c36q3QGc4nU80v9S3KVOmPGu/IjgmqwVvKvnf1H+KZXKtes1fp75RYaKBgYGmja6jvtFcUnGsnPdPrkQ1p53O4er7fh4DrfvKlSubtmrBJXIX8jlDxQJp79Ec0TlMTgVdVxn70NBQ00ZnYpcxPaZ3/KZdRERERKTj+KFdRERERKTj+OsxIiIiItIXTI/pHb9pFxERERHpOOVv2kkII7GE5MAsQFHBDRL8SOQhWYiKadBrsxxDfSXZjkQ9aqP7ZdmE/ouOhBSSxkiKIvmG5K98PxKWSEIjwYXGTvFBRVnya0lYIsGK1p1eS2tAbZl169Y1bSRdVUVlmrc8v5ViXGNB+4XuR+uc9wtJUhSn1YJfJE9RLORzoCoWU38JWoO8frSn6Oygc6FaXI7WdPr06Vu8hu5PbbRWNHYaV75ftbAUXVfde3Rm5ftVpXua76qMnosO0fyQtFjtB42Bzvp8XtNZWlm7saD+0g81UIxnqucCxUf1DM/9oPcb6iudiSS6Enl+ab7pXrTPaEy0pkS+X3Xs1fcN2fExPUZERERE+oLpMb1jeoyIiIiISMfxQ7uIiIiISMfxQ7uIiIiI9IVn0mO69L+tZdmyZfHqV786pk+fHuPGjYtrr732Wa9fsmRJjBs3rvnft771ra16bjmnnaQXknQeeuihpi2LKjRBJG9UpSjqW6U6H8kbJBSRFEpiCQlyWY4hEYmeSbIJzRFJp9S3LCRWKyvS3FZFLLouj5/mg6AKfuvXry+9lkSpLBXR2EnirIhkESzg5TmnuaX4pv5XJe2KXDZp0qSmjSqAVkUvGjv1N4+L+k/SYlUCpxjP96O5pfUkYZUkNJoPqmZZqbBKa0fjpPO0ui45Bmlu6Zyks4j2O42B+pulULoXnQG0LvRDBxRbuY32NsUt9YPmqFotM4vhJMDTfFTFTopnIp/h9AMP1A86s6rnOp2xlerdFFe0p+j8oPeNPNaq+EtrQNC+ovec3DdaO1qDavVd+fnxxBNPxCGHHBK//du/Heecc075dd/+9rdHvXfkatBbQhFVRERERKTI/PnzY/78+Vv9uqlTp+IXwVVMjxERERGRvrC9U2HGSo/ZtGnTqP/9Iv4F49BDD41p06bFySefHHfddddWv94P7SIiIiLynGbWrFkxYcKEzf+78MILf273njZtWnzqU5+KRYsWxdVXXx0HHnhgnHzyybFs2bKtuo/pMSIiIiLynGblypWj8s3JieiVAw88MA488MDNf8+ZMydWrlwZF110URx//PHl+5Q/tJNsQsLjrFmzmrYsV5CAQUIHyRskpVRFmNwPqtxYEcQiWBDZd999m7b8zyskbJLgt2LFiqbt8MMP3+L9IzjQ8ryRTEXQGpAgTPNRWT+Sd2ldHn744aatWhVvw4YNW3wGSXoUkxR/VdErxyTdv1oVtFpZl0TLHIPVyoq0N6jSJM03rVWeNxKsSFSjZ9JrKU7z2VOVLEm6JwmSxpkly4hWYqW+0t6g+KD5GB4ebtoqojmtMcUV7bPqjwkQeVwk+dK96KyrVNmMaMdO4nK1anH1zZ3OijyuqmRZrZpNz6Qx5POazg56r6UzsZpWUP1hhgzFAu3RaqXQHAu07nTm0n4kAbkq50+dOvVZ+xnBojXJ7l2niwWNxo8fj2fPL4qjjjoqrrrqqq16jekxIiIiIiJ95IEHHohp06Zt1WtMjxERERERKfKDH/wgvvvd727++6GHHooHH3wwJk6cGLNnz44LLrggVq1aFZ/97GcjIuLiiy+O/fbbLw4++OB48skn46qrropFixbFokWLtuq5fmgXERERkb7Qa0GjXxS99OW+++6LE088cfPfCxcujIiIt7zlLXHllVfG0NDQqDTnJ598Mt773vfGqlWrYo899oiDDz44brzxxliwYMFWPdcP7SIiIiIiRebOnfusH/avvPLKUX+ff/75cf7552/zc8sf2kmaILGJ5NSK4EIiWbViJIlvJEFmIadaaY3EGBrD0NBQ05atYJofkphIxiT5i6C5zGOtimQ0dhoDSVEVCY1eR3NLcgj1l9Z0cHCwacvCFsU3PZPio1L5NaJWfZLmm8ZEfaN1qVQwpPmmKqkkQJEgV6nCGtF+u0HjpDmivVGtYJihGCIJjSRFEvyqonKec9or1WqiVOFxxowZTdvq1aubtgydr9XqyTSXFRE6ol1TeiatOwl+dJ7S3qiI4STsr1mzpmkjAZTOgIq0SeteLcZSreZbkYur7wfV/pKgSfs7n1kUQ/T+tXbt2qaNJG26X44j2o80dup/VYSmeMtnbPXzAp3zsnPiN+0iIiIi0hd2hvSY7YW/HiMiIiIi0nH80C4iIiIi0nFMjxERERGRvmB6TO+UP7ST+FGtQpglDJI3qI2qmZEMQpXQKmJGVYil+9NrqZpZ7i/JJySbkOhFfSMRq7IGFKTUD7quWjGMZLJKP7YF6hvJknkuKa5IZKxU64tgoS+vPQmE1SqvJDtRzNC+ys+oxhCtVbWNJNYsFk6ZMqW5hiQ6EgGrlTdzLFQrMtIa0DMpZmjf5jil84SkRYIEzXXr1m3xmRGtBFkVsin+KJ6nT5/etNF+fOSRR0b9TfI4xRVJedVKtXk/0j4geZfeDyhmqG+Vqq7V91WKNRo7yZi0hzIUfzRHdB1VAabXUuzuvffeo/6meaxWZyYqe5Tmkc5SOj/ocwBJuLTXcozTDwLQPqieFbLjY3qMiIiIiEjHMT1GRERERPqC6TG94zftIiIiIiIdxw/tIiIiIiIdZ5sqopKMRNJIlqxI8qDX0T9ZVESeCBYzsjRCVdtoTAT1g+SvLMyQBEOCDl1H0g7Jo3RdFmFInCJ5ktad2kg0JAktV62jioM09kpFwwiWX0nyy7ITURUeSVCi6rj5mdX13GeffZo2qv43MDDQtNFa5ZikuSAZjvYLjZPimeSsPJePP/54cw3NEe13Wne6LgulVE2ZxEs6s2hdSDiriNskEdMepbmleaPKmPTavOerUjztDZoPOhPpnJk9e3bTlqEfJqD4JlGUyDFDcuO2VHml+KO1ykIsnWv0AwYVkTGiXrU4n2M0djoX6PygNlo/2rf5uXTm0hrQexBdR/GcY7JadZnmlp5Zraic+0HPrJ5FXcb0mN7xm3YRERERkY7jh3YRERERkY7jr8eIiIiISF8wPaZ3yh/aKUeNcs0obzJD+X/VSasWd6D823xd9V7VvLhKoQUqOkG5bTS3lYJRY7025zBSLh5BeZmUt0v51JS/mXP2qmOiuaV+0LpTjnxeB8rfpLzPar4p5e3mGK96C5TXSDFD801jyGOl+H700Ue3+Lqx7k+FiCp5nlU3haD5oP7mdadn0nzTetIzqb+Uy1vJH6b4oHtRPivFJOXV5nx7OheGh4ebNiqERfnrtB/prM/vCTR2cgNovinnl8ae15RigdaF2ug9rZpvn/dtNX+dznkqMET3o/7ms4fOV3JAyOmh+1e9jXw/eo+gZ9K60FlEZ2w+n6hQ2MyZM5s2+gxRLdBIMZnjuVq8rlr0T3Z8TI8REREREek4pseIiIiISF8wPaZ3/KZdRERERKTj+KFdRERERKTjlNNjSHohWYjasuRBUisVRqDCOySb0D9tkLiTJTSSJ0noqBaZoMIkuQgJ3YvEGBKsqkJipSgLyUm0dvTMwcHBpo0EW1rn3DfqB4lkxKRJk5o2EulIMMtzTv0g6fTGG29s2ubNm9e0kbhYKbRFfa1CsUBxmoUwWjsSp0iCpDmifUDCVu4vyapV6YpeSxJaPlMoXqpSa7UQEc1vPntI+iNRjfZUdewUC/m5VASLziISEimeq6J5Fprp/YaE2LvvvrtpozOLhOm81+iZdC+aj8r7XgSvX34txRC9R1BMkkBOe5TmI8cz3Z/ek6m/BJ1tdNZnobkq/lI/SFitiOHUVypoR9dVfowjgtcg7xeKSTqf6P2ry5ge0zt+0y4iIiIi0nH80C4iIiIi0nH89RgRERER6Qumx/SO37SLiIiIiHSc8jftJFeQiEUV8LL4MWPGjNIzSeAiqaZasTRXDqTXVSvgVSubZnGHqlZSX+k6qghIQk5FOKN1IqmG5pv+q5QEK5Ki8jMq0uxYfaN+UH/pGVn+qla2ozZad9obWU4leY3ELIqPikwVwWPPgiPFAkmQ1I+qHE1iZO4bxTcJ6jROWr9KVWESbqtVkQmKD5II8zrT/FSFRForkuZIjs79oD1F803zRnNEghxdl89TigWSIGne9t1336Zt1apVTVseK51hdH86Y2g/0hzR/s7jqlZXpTOGzkm6H1X8zPNB606VcOm9itaY9iM9I89vtZIqnQEkeFPV2LwuNLfVyqwU8zR2+lyRx077sVrtWHZOTI8RERERkb5gekzvmB4jIiIiItJx/NAuIiIiItJxTI8RERERkb5gekzvlD+0Vyto5mpmEbUqhCR2klRDMhJJOtRWqRpGAg2JdSRK0f3/6Z/+adTfr371q5trSHwjqeaaa65p2k4++eTSa7NQRfNDsgyJbxTgtKYUH1kgIhmH5B5qW716ddNGohSJYzm2SBCrCrEkYtF1OT5oHkmAor5R/FWl0CxUUf+rkuysWbOaNoojEsKyDEd9JTmQ1oX6RjGTIYGregaQDEfXUd9ynK5YsaK5Zvr06aV7kSBH5yTJklnOp1ggUZnGTrFLMiZVgszrQNdU9yi9l1SqJ9M5QdDepgqxNEdEFi0pbuk9Ylt+OIDmLe81+iEBio977rmnaaM9RDFJ++/LX/7yqL8pbun+FDM0dnpmng+KW3od7T3qR3Vd8hlO96d7kWQuOyemx4iIiIiIdBzTY0RERESkL5ge0zt+0y4iIiIi0nH80C4iIiIi0nG2KT2G/klh9uzZTVsWOKjKHEkq1UpuVWkpS5Uks1SEpYi69JdFS+oryT10HQlWVeEs369abZYEKJIgCYqP3I+qvEYSEK0VrXulIiqtHcladK+vfe1rTdsxxxzTtOWx0jhpDUjGpPmoxlaet21Zd9qPtAYktg4MDIz6m+KlWmmShC2at1wplMZEz6RYIIma5EC6bnh4eNTftI9pj9K65HmMqEuy+RyjvtKPC1BVyaoMR/GRX0v9v/HGG5s2iitaP/qRAJrLDPWV3jey0BvB7xEUH/m66o8oVKtsEiS75vOJBGSaM9p79B5PMjpdl8dP5xrFB/WNzrZK1Wm6P803VWul+KD5XrNmTdOWBXWKb2qrxHKXMD2md/ymXURERESk4/ihXURERESk4/jrMSIiIiLSF0yP6R2/aRcRERER6Tjlb9pJdCCxaWhoqGnLQgsJKdXqbiR5kJRXEURI2iFxj6QUkm/ov9ayYEUyC4lHJK7MnDmzaatWCczPpUqnFSkogueIXksiYJ63avU/kqJIgiSxkOYoy4z0TFp3iisSlWk+8rrQPFL/KWaqYhqtc477qsBVlZcrlXAj2oqLJJzRGUMxQyIgCZR57LRXKNaqFaE3btzYtFGVXprLDK0dxR/tUYKuy+cTjZ32D0HxR+c1jaECrRXFLgmxNPa8R2nPVitS0jipvxS7WZyl90IaE0n81A+iIp/T2Gk/UhvJwLRfKuIs7SmS0av3p/jL51N17HSuUaxRP+hcyPervt9Uqr3LzoHpMSIiIiLSF0yP6R3TY0REREREOo4f2kVEREREOk45PYZyvCj/lvL4cs455SFSrl+1IAblwtI/d+R8McpRq+au0msppzPPB+WyUr4b5StSfhvNN+Vm5jxJyhOk/DzKTaT+Uk4xrVXOuawWE6I8QRp7tRhFztOltaPXUX+rbXkM1X+So3zWXCQoolZEKqJdP9p71DeKK2qjXE3yTvL6UcxXi6hQLi+9NufCUn4oxR/NB7XR3qjknFNfq2ditdgKvTb3l+5Vzeum9wMaO+Xqr1q1atTfNPbKnorgNaD3iByTFH/VgjqV+0fUcr1pHsnPoNx3WuNec86r+dpUJIjWODssERHTp09v2nJ/qR8UCxSntL8pxvNc0npSLFTjg9aF5jKPtfoZi8bUZUyP6R2/aRcRERER6Th+aBcRERER6Tj+eoyIiIiI9AXTY3rHb9pFRERERDpO+Zt2kl5I3KHrsiBChRFIrCOhg8QMEo/oflmWrBZQoCI4JKuRIJKfSYILSXR0f5LLli1b1rQdccQRTVsuRERyY7WQDcmHFAu0LpUiECRdUczQvUg8qohBVEBm0qRJTRsJXDT2q6++uml7wxveMOrvDRs2NNdQfFD80dxWC4lkSJijbx6qsTA8PNy0kTiW7zd58uTmGho7nTHVwj55z1O/KK7oXiRz0x6icyE/g+aW4oPEPToXiMrZTPNIcUWFzWjs9EyKj1zUafHixaV+VIXpSvEjWneKyaqMSWcs7cc853SG0TxWxXB6n6P3l7zXSG68/vrrmzaaI3r/nTp1atNGcZTng85hWk86w2kMNEd5XWjPUhutC+3HagHIfP7T+UcF0HY0EVV6x/QYEREREekbO1JKSpcwPUZEREREpOP4oV1EREREpOOYHiMiIiIifcFfj+md8od2Eh1IzCCRKQs5VYGVhIvvf//7TRtJioODg1vsG1VaI/GD5EAS2EggymNdunRpc82hhx5auj/NUbW/WYil+5MURJXtaN2pH/TaLBCRgEexRnIqCVAkipJglqWlmTNnNteQPET9JcGKZLUbbrhh1N9HH310c02uDBnB616Vhkney4Ij7Z+qREzSFcVW5fyoVi+kM6BaCTL3lyQ9EghJfJs2bVrTRnFK/c1jpzeNaj+qlVkrVUapYjMJc7NmzWraSJylvtG5kAVHWk/qP1ViJiGxUk164sSJzTUUHxW5e6x+VKqM0n6nsZMITdfRjzfQa/N1dP5Vq4/TXNL7/j333NO05X1L/aAx0XW9/hgC7TNadxoTzRGdk5Uq83SW0nttZUyyc2B6jIiIiIhIxzE9RkRERET6gukxveM37SIiIiIiHccP7SIiIiIiHaecHkOCaVUWykIVCWJUQY3+yaJaUZReW6mAR7IJSYV0fxJnszQyZcqU5hqSdmgeSbQhUY8EtiwekRxHa1yVA2lNad6yIEwyDsmkJGfRfND9SBbKAhtVNNx///2btpUrVzZtFH8UCxkS8kg8orWqSEwRvH5ZnCVJiiQ6isnqfqQqhPl+VcGK9igJZxVpnaTqSsXEiIihoaGmjdaU+pb3EM037Z9qVdDqOZbjg/pBsUbyMlXZpPijOM3rQHFFZ8yjjz7atFHMVM5w2gfVypi0b4mKMEjxR8+k+SbBlMZF85slWXomnbnVaq20NyhOcz8o5umsIHqtFEpnHc0HnXUkG9N7PF2X57cqQtO6dBnTY3rHb9pFRERERDqOH9pFRERERDqOvx4jIiIiIn3B9Jje8Zt2EREREZGOU/6mncQjEr1Igly9evWov+m/akgYISGRIMmDKvvlin1UyZLkEBI0STwiKSXLUyQF0TzSfNC8UT+oquakSZO22I/p06c3bSRxVuW9ylhJpqJnVgTnCBasSP7K80Zj+ta3vtW0kWBKlWQrMXPzzTc315xwwglNG1V9zHsqguOoMr8kiNE+oPWkNjorKoIwrROtMT2ThEfaL3ldKF7oXgT1rSqO5bOIxk7rQnFFoh4JpZWqq9RXOotoj9JZRPe75ZZbmrZ8ftAa0/lK81aVD/P96HV07uSzNILfb6rk9y+qLEvna1X4rkqy+f2WYo3Wk0RU6htBZ1buL8UancP0eYHWlPqW15nGTm3VCrT0Awb0WqrknqEx0RrIzonpMSIiIiLSF0yP6R3TY0REREREOo4f2kVEREREOo7pMSIiIiLSF0yP6Z3yh3YSg0hyo0plWUwjcYrkL5LhSKohcYfEjJkzZ476uyo2kQhIFchITMtjIFHt9ttvb9rOOuuspq3a3/vvv79pO/PMM7f4OqqmR1IhVQ8l+YbmKIu5FC8kB5IERPNB80sbMot6FGsU3zRv06ZNa9pITMuvpTHdcMMNTdsZZ5zRtFGsUcyTxJWhftB8kIRG80HPJHkqv7ZayZLiiuKvEuM0Jlp3qn5K15FcRuQYJ4GQzkk6hym+SZakczLfj55ZEfciWAS8++67mzaSZHNbVY6mmKdYo8qpec6r96f4ozWg+KusaVU2ro6d3lsr1cxp7Wi/VMd+3XXXNW20vzNUCbcqIFNMVgRvmu/qmUjxTWdFpWp29Wyuir+y42N6jIiIiIhIxzE9RkRERET6gukxveM37SIiIiIiHaf8TTvlMFaLueTCNXQvykWmPNVqwQe6LucAUj5dNY+exlDJ7aN8bcolp/moFlyqFIyp5uxRLijlGPZaEITWgPJ2KUeXxkB5gmvXrm3ackxWi0NVC21VCjrR2lFc3XrrrU3bvHnzmrZqYZWcc05eAXkcBOWMUn4lnQt57Wk+aA2ob/RMiq2cz0p5wbR21P9tiYU8bzQm2o90BpB3QnuD+pHb6Nwhf4LGfs0115ReS/3Ia0+xUPUb6P1gypQpTVseQ7W4Eo2dxkS5zbQuuY3mjOaD3BGKo2os5P5WPQv6HEDv3dVYyM+lmKT9Tv3Yb7/9mraKk0DjrLhREXym0HsykWO8WqiOrpOdE9NjRERERKQvmB7TO6bHiIiIiIh0HD+0i4iIiIh0HNNjRERERKQvmB7TO+UP7SQ/kERSKcRBwg8JNCTaTJ48uWmj4hEkhGXBhYQlEt9oTCSzEFkmywJkRF1eI0h6obW6+eabR/2diy1F8HyQpEgBTgUrKgUf6JqqlEeiFMUMjStfR3ItSUYkO02cOLFpo3XJBZdITqoWMKKCXLSmtH55raoFkkgGpr1Me4OekdeUBD8SrKoFZGhf5TWl/U4SHcUa7QPaeyS1ZbG1KgtSP0hkp+vo7MmxQPuR+rZ48eKmbWBgoGmjOKK9nCVFuqZ6xtA4KSbzuKqFbAiSZKtieF5nmm/qG42TzqxqIaIlS5aM+pviis5JkrTpnKQ2Il9H5yuty+DgYNNG605zVPmRgKqcSpCwTzGT14X6WhVzZefE9BgRERERkY5jeoyIiIiI9AXTY3rHb9pFRERERDqOH9pFRERERDpOOT2GpJdqW5YwSMog0YZEvUceeaRpI+mP7pclD5KkSHwj6YXuT9BYM/RPM7fcckvTdsoppzRtJNKRMNPr2Gk9SYShcdK65OeSvEZSDa0BSUYUM+vWrdti36riG80HVU2s3I/mrCokUswsWrSoaTvhhBOathwzVelqzZo1TRuJs1T5kOI0i3ok7tHaUXXLqsydn0ExSmtHa0yxQGMg8murlT0JGgNJhLSmGZIFv/KVr5Suo/tT36jKcj5PZ86cucVrIngNKpVfI9qYoTORZEE6J2mtqlWtc7xRLFcrWJMUSuOithy79KMP9EyS3SlmSKqk/ua2ahX06l6mucxxVBWyq/FHAm9FkqXXUXxUfxijK5ge0zt+0y4iIiIi0nH80C4iIiIi0nH89RgRERER6Qumx/SO37SLiIiIiHSc8jftJGaQqEcSRhZyqrInQRIaySBEloBI6CBBh4QiknQq15HYREJidY5I+qP5yP8lec899zTXvOpVryrdn/6rlCSgSpVKkpPo/iSY0lzmqqMRLAfmWKC+0tipHyT90XWZF77whU0bCX60p+i1JCPdeeedTdvZZ5896m8ae1WCJEGTpFPaG/kZVbGOhDCKBdrf+X40Z1Wxnc6KalXXihRKc0ZiGu13ig+a3yyF3nXXXaV+0JgIigXqW54P6iutMfWtsvci2rinSsx0f+pbtdJ15bXV/UNrQHNEZ+y9997btOVzl97zSTqlcZK0Tj86QGPN5wL1g87JtWvXNm0UazQfFSme+kpVoisVuMciV2Om84nmm+ZWdk5MjxERERGRvmB6TO+YHiMiIiIi0nH80C4iIiIi0nFMjxERERGRvmB6TO+UP7STlEcSGkkjWewiWaZa2ZNELGqjZ2Spo1p9koTbSoXHiFZ2omtIKCJZhqrMUcVLGnseK80ZyU7VSrUk05KQk+dj4sSJzTXUf4orqi5IbXS/fB2NifpPIhaNgeYyQwcFPZOuo3ESdF2W0NavX99cc+yxxzZtJDvRfBAkVGUpj/pKIiqtJ60fzVt+ZrUKJslf1SqsdM7kvpH8Wq0cTWcKnR9f/OIXm7YMyZgUk9X9Xq1OSnOeyZJeRG2fRdRkXXo/owrcL37xi5s2im9qo2fksdPZTOcf3Z9inmRJev/K61IVHq+++uqmjeKI5FH6XJH3AonyFEMUH7TuNJf5Oho7vU/TfNO60DMr78E0P/R5ZGhoqGmTXyzLli2Lv/qrv4r7778/hoaG4pprronXvOY1z/qapUuXxsKFC+Pf//3fY/r06XH++efHeeedt1XPNT1GRERERKTIE088EYccckh8/OMfL13/0EMPxYIFC+K4446LBx54IP7oj/4o3vWud8WiRYu26rmmx4iIiIhIX9gZ0mPmz58f8+fPL19/2WWXxezZs+Piiy+OiIiDDjoo7rvvvrjooovinHPOKd/Hb9pFRERERH5BLF++PObNmzeq7bTTTov77rsP0x7Hwm/aRUREROQ5TfYndtttN/SNemF4eLhxQgYGBuKpp56K9evXx7Rp00r3KX9oJ8moWq0ryxUkh1TlJLquWhE1yyBUna4qndJ80HVZUqRrSDYhKY+kq2qltalTp476m+bxlltuadoWLFjQtG3YsKFpI2GQ/skpS3P0X5jTp09v2oaHh5s2girgkaST5UCSBam6KsU3QWua157WmNaT+kbV/ygmKcbzfFA/li1b1rSdfvrpTRsdaNWKgPm59DqKj6qYRqJXvl9Vrq0K6tQ32gd5L1NfSayjdae9d/fddzdt9IzcN4oXWrtqNc7qHOX5pTOdzr/qeUpVi7OgSfcnyZzWgARTOiuoH7m/FC80jwTd/7rrrmvaKJ5pLjM0TroXjZ3OMVrnLKzSMwk6K2gPUYxXfiyDoLWqfAaK4Pe5HFvUj8r7atfpanrMrFmzRrV/8IMfjD/90z/9uT0n75Vnnkt7aCz8pl1EREREntOsXLly1H/o/by+ZY+IGBwcbL58XLt2beyyyy74C09j4Yd2EREREXlOM378ePzXmZ8Hc+bMaX5299Zbb40jjjhiq/6lRBFVRERERPrCM+kxXfrf1vKDH/wgHnzwwXjwwQcj4r9+0vHBBx+MFStWRETEBRdcEG9+85s3X3/eeefF9773vVi4cGF885vfjCuuuCIuv/zyeO9737tVz/WbdhERERGRIvfdd1+ceOKJm/9euHBhRES85S1viSuvvDKGhoY2f4CPiNh///1j8eLF8e53vzs+8YlPxPTp0+NjH/vYVv3cY8RWfGinr+9J/KhUSSVRiKTW6j9TkEBDgku+X7UKa1Wqobb8DMqRooqU9EySUh544IGmjaqk/mzwRLAEQ1XbqB9U7a5S6S+ilQ/pv3AfffTRpq0qG5OIVfmnJxonrRX1g6pPErkfdK+q6Ep7g+ab2rKsu3r16tIz77rrrqaNpE3q29FHH9205fODqppWq5+STEtyT15TqtJI60LPJKGtKuzn/UIxdNtttzVtdObSHqL9WKlQTLIq7YNKRc2x+kbibJ5LqrhKsUZCLPWDhNK8ziTI0nzQ3NI4q9V2848EUD/ozKX3jer6UT/yuOia66+/vmmbMmVK07Zq1aqmjdadzuYcC7T3SOyksdP7Ad2v8iMB1EZxSucHxczKlSubtrzOFAsU89XKwPLzY+7cuc/6Df2VV17ZtJ1wwgnxta99bZue6zftIiIiItI3uvTrMTsS5rSLiIiIiHQcP7SLiIiIiHSccnoM5edRnlalgAflQ1K+G+V5Uu4W5ZJTHl/O2a4WdKJnUn4o5YTnPDjKy6zmHJILQDmulVzbinsQ8V8/SZR5xSte0bTRHFEeZl7T6rpT7iPlClMs0Drn+aD7U84o3YvWgPIfc7467SmKBbqO1pig4kE5z5MKUlEs0L6tFiOjPL58LlB++amnntq00RlDz6SYzPuK9gHFZDX3ndbljjvuaNpyjjXFPM0tFfzKOdERnAtL+yWvPe0f2hs0t3Q+Uc5vpbgNxR+Nk/YorR/t0fzMap40xQztW5rvfffdd4vX0dlB607X3XzzzaV+0Frlta+e6dQ3mjeKI4r7fDZTDJH7Q3uU3qcrRdzIy6GYrLgBERwfNIY8VnrdunXrmjaK+S7T1eJKOwJ+0y4iIiIi0nH80C4iIiIi0nH89RgRERER6Qumx/SO37SLiIiIiHSc8jftJIyQwEbSS5YrSI6j15GoQcIZSTUkjWQJlPpBr6P/CqP+kmiTx0CSComGVACCCkXQHJH4dsopp4z6m/pPz6R1J1mNqAi81QIkNE5qI+GsIumsXbu2aSPpqnpdRaaluCLpitaFrqP4o/2Sr6OYpzWm/pIoRa+lZ+S1pyIt//zP/9y00RrTa4lKkS4qXkJjJwGP1orWJc8HyZm0N2hPDQ8PN23UXzons7BKAl71BwFoP1bORHoGrRPFPMUfnZO0R/Mzqj8uQGcn7bNZs2aVrsvxTLFA/SDZsyqPkrSZ9+3y5cuba2hdSJimmKH3PpJp8zrQfFM/6EcCaC9XfsCAYojmm/Y7jZPEcIq3vG9pPWlMQ0NDTZvsnJgeIyIiIiJ9wfSY3jE9RkRERESk4/ihXURERESk45geIyIiIiJ9wfSY3il/aCfJg4QZEm2y+EEyH8kb9EwSj6htw4YNTVsWP6g6KS0eySYkwpAMl59B9yIZp1LFM4Lnm+6Xof5XRaFrrrmmaTvnnHOaNpKncltV5KF1IeGRZCESW3PskqT36KOPNm0kdZEYSeuXpSJ6Hc0ZCXi5omZEXc7KcUSSVLXKMMmSJBpWKhnTOAcHB5u2SlxFRKxZs6Zpy3uU5oykLpLWKXZp3ii2ckzSPqO2qixOQintjRwLNI+0niR3VwVykiAnT5486m+q+khnAN2/svciWkmWYoFEWlqXahVdem0+B2iNaV1uu+22po1eS+tC8mjuR/U8oTFRrNF80DmW3zOr1Z+pHxRHlaq/dC96HZ1/9AMdFQE+oj0rqlWXaR5l58T0GBERERGRjmN6jIiIiIj0BdNjesdv2kVEREREOo4f2kVEREREOk45PYbkQIIkjPza6j9F0DMrlRUjWBzLIgwJHSR7Uj+qok2GpDEShSZNmtS0kahHwhnNR5aWXvva1zbXkHhJ8k2WxiIibrnllqbt3HPPbdqyHFgRsyLqIioJZ5VKnhRXJAFRFUxaA6JSSZZijSQ3kqir8miWl2nsJOBVhTCab3rGlvoVwfuxWqWXxPDcN4o/Wk/aexSnNG90VmShj+5F801zS21U0ZH6luON5pHWhc46WivajxSTWYame9E5SWOifVsRC2mdiOp7Ia1fJd6o/1/+8pebNprb6nsa7cf8AwMktdK9qj/oUK1cnuODZGOCJFmC1iCLufRDFrRHq9I9xTyR5432I7XR+dRlTI/pHb9pFxERERHpOH5oFxERERHpOP56jIiIiIj0BdNjesdv2kVEREREOk75m3b6L5FqNcQsiJBoQ6IGiSXURtJIpeoZ9YPEGJJ7qDImyV/5tSSzVEUh6i+tAQk/Wd77x3/8x+aa+fPnl+5Fog2N4frrr2/a5s2bN+pvkrqojcZJEhrFBwlmlXUhYalalZb2QRZ9SXikfVAVHqsCaI6jiqi7NfenNaDr8jrT60h4pGqOueryWNflNaBx0tyS6EVjonOH+lapyEvCLY2J9iPtIap8m9eU1oCeSfeqSnkklOa9RveiOaqcuRG1Sp4kVFIs0HxUpfXK++gdd9zRXENxRecTrTudWXfddVfTls8jim+KZZpbkudpLqvvt5VnEhRrNEc5TqvViGluqQorQePMcU/X0A9B7GgiqvSO6TEiIiIi0hdMj+kd02NERERERDqOH9pFRERERDqO6TEiIiIi0hdMj+mdbRJRSfAjaS5LHvQ6ElxIZiEhh66rVNAkIaVamZCqh5KokgUlkqlI6iKxifpWHUMWZqoSE8lUVPWWnklVXfO80XxQ3+iZVcmIJNYsQJEUSoJpNXZp/XJb9f40H3R/ilMSAbMQRvuH1pPmm15L4yLhM4+BpFPaUyS0kbhIQliOIxonSV3V/VIR8SPa+KO5pbWjOaJnUixUKnTSOEn2JBmTxFkaAz0jQ2OidaHzaebMmU0bCY/5BwwoRun9htad1oUq8lLs5n7Q/emcpPmg+aYYpzMrzxFJlvS+SveiWCBoXPm8rlYMp3O+OpcZGjvFB/0IBsU3jaHymYrOedqPVTFXdnxMjxERERER6Timx4iIiIhIXzA9pnf8pl1EREREpOOMGyn+J8aECROatokTJzZtlGdXKRhAOYd0f8qVo3wuyrPLeWXV3GzqB+W007hyzi/ND+UxVwu3VMnzRrlyxOmnn9600Rio2BSFVn7u0Ucf3VxDedK07pSbSOtH85v7Vi1gRHmTNJc0BsqvzGxL0RqKP3pmvo7yN2lMlKtO+Zs035U8d9rHlUIoEbw3aA/l19LcVgt5Ub49xSk9I/eX+k/zQXFajQ8irzPFEPWN9va2uBH5/aVyfkdwfFR8poi2mBKd87TuVd+I8v7Js8jFlCgXnnLmq0XdvvjFLzZttFZ5P9IzCboXrR/1t+KB0b0IGjudRRRH+QykM5H8CdpntG9pnPTafH7Q565qQaq1a9c2bdubTZs2xYQJE+KMM87AvbW9+MlPfhI33HBDbNy4Ef22LmF6jIiIiIj0BdNjesf0GBERERGRjuOHdhERERGRjmN6jIiIiIj0BdNjeqf8oZ2kPyosQPJNpagRiR/Vog2V4iXUD5KHaPGGhoaaNhJLiIcffnjU3yQ5UP9JUqE5qhZWyZJRlrAiWHq5++67m7aTTjqpaaNxkbiTRaxbbrmluebXf/3XmzaKGRKbCLout1FczZgxo2mjfUBSckUSqxbxoT1FwmpVhlu/fv2z9iuC469XwS+CReW8h6hoCI2JRECaN1qDHKe096rCd0UsjqgVjquOc1v6S/fL5yTJmCQkUj9I8KOziOTAHJNUyKbXglER/KMD+TqKbzrD6KyjmKc99PWvf71py+c6xS1Je7Rfbr/99qaNznVav3yO0TpVf/SB3qvoLKoUKaTXVaV7WlOa30pxouo5TND+prYcW9XiiV2SOuUXi+kxIiIiIiIdx/QYEREREekLpsf0jt+0i4iIiIh0HD+0i4iIiIh0nHJFVKrSVhVKSZxoOlKUSamN5EASRLLgQvIGSTUketE4iSw4kiRFY6frSPyldakIZyTS0tyuXr26aSOpa86cOU3btGnTmrYsAVFsUN+OOuqopq1aRZLGlaE5IxGrun702hyTJERVqreOBcl7JNjmeSNJdNWqVU0bSYpVqasiVdLcksxH61mprBjRrgGNvRpD1WqZNIYsEQ4ODpZeR9IwnVk0LqqQmGOGYo3GSX2rVgClCpp57en+FB90DpOgSe9VeVw0jySdUt/o/iTZU9/y+xetAfXjhhtuaNpoDBQzFON5f5OES/udRFeqKlzdo3kM9B5B7/kEzSXt5RxHVdGa1p3ilF5L53X+cYLqD3TQe2GWu7vAMxVRTz/99E7Jsz/5yU/i5ptv3iEqovpNu4iIiIhIx/FDu4iIiIhIx/HXY0RERESkL/jrMb3jN+0iIiIiIh2n/E07SUYkdFSqPFbFS6p6RsIFvZYkhyyNkCRFUg2NkyQPEvXyWEnGIWmWBJ2KuDLWdXkuqYIfSTW0VtT2pS99qWk744wzmra8LiQ2kcR01113NW1nnXVW00bzRjJmRRAmAYpikqiIbyS80HxQfNB+rMp7eQxr1qxpriGRkURoinn61oLmLctkVHmTBC6aN5LtSKTLa0p7m0Sv6hlDa0VycZ5filsaO81tNebpfrkf1Zin+aB5GxgYaNqIPC46X6dOndq0UcxUq2VmyY/2VLX67h133NG00RrQM3Lf6P7XXntt6f60D+g9k+Yorx+dw/QjBHRm0f2rAi/NeeUaema1gnC+rlqVlvY2fa6g62gMOT5oH5DIXX1fkh0f02NEREREpC+YHtM7pseIiIiIiHQcP7SLiIiIiHQc02NEREREpC+YHtM75YqoJBSRREIyUpY8SPYkSBIj+YaqhpH4kUUsklRIFH300UebNnot9SNPL72uUiUvgsUgEpvoGVkwI6mmKrmRyENCGElRp5122qi/qxVASZCr3D+iVmmTxCMSHmnsFB9Ejo/qmKrSFYleFEf5flXxsipTVc+FPH6S3Oh1tM9o7JU4ovUk6Fyg9aM4qoiA1FeSgavCbVU+zEyaNKlpq1Y1pTUgKlVdSaanuKrug8q5Xq1yfffddzdt9IMAdMbSvsoxs3Tp0uYaet+j/UhzRGdzZS/T+y/NLd2LoJgkOToLx7SnqhIu9bdSsZnim95/ad/Sa2mPEvm8pvOP5pti9zvf+U7pmf3kmYqop556aucqot52221WRBURERERkW3H9BgRERER6Qumx/SO37SLiIiIiHQcP7SLiIiIiHSccnpMVfIgyTQLHCTtkHRFkgqJbyQ0kLySpQ4SDkgqrAoTJKZl6YWEkWolPhJcaJwk2uQxkMhDsla10lpV2rzppptG/f26172uuWZoaKhpo/ggWe3qq69u2iZPnty0HXbYYaP+rlThi4hYsWJF01at0JnngyRRqqhJ80gxQ2tFMlmOGfqnQRLaSJimmKH4q5wf1f1OUhcJftSPLLvSfNO9aK1I8KP9TXGaX0sxSuIbzRGdO7SmdDbns5jWmGQ4mo9qtWo6sypQLFRlYNob+Uxcvnx56XXURn2jNorJJUuWjPqb1mD27NlNG8UfScNTpkxp2ui9O8cuXUPvhdXKr9UfkcgxSfFH5xNVD6WYpzMrxy6tAcUy7W2qHE39qFRBrgqx1fevLrEjpaR0Cb9pFxERERHpOH5oFxERERHpOP56jIiIiIj0BX89pnf8pl1EREREpOOUv2kniYSkFJJG8nUkdJBgRZDQRlJKpYIciTx0LxJySIwkgTL3l8TA6jNpbkkyIlEqyytUfXLNmjVNG42TRGISikhWy2twzTXXNNccf/zxTRvNW7VaJsXCl7/85VF/H3vssc01JPeQYEVQ1bosyJE4RXuDJMXh4eGmjaQokgNzTFIskOxEUlRVgKL9nQVeWmO6P1XtpPuTcJbF04oIF8F7ip5J39jQmuZ1ofmm11E/SG6kOSLpNt+PJE6K+ar8StBr81lBsvHEiRObNtrbJIbTGZuldXpvofc4uhfNG702i/gR7foNDg4219B7FZ1/dFZQzNC5kPcavY7Wjs4d2kO0LrSH8nsOSaF0vtL60bytXbu2aZs1a9aov2mNaZwUf/S+R3u0ci7QGldFa9k5MT1GRERERPqC6TG9Y3qMiIiIiEjH8UO7iIiIiEjHKafHUM4U5VtVCs1QvhjlfNEzKa+WcjWpmEEunET5rJRLTjlq1RzanANIuYSUo0b5c3R/gnKlc3EOyomm/GHKM6bcQZqPSoEhWvd77723aTv00EObNsrFpjxBysPMuYi5wElExGmnnVZ6Jq0prUH+JziKP8qDpbWiua3ml+d9RcVA6F4Uk5X7j/XavK9o79HaVfPLK+cM7T16JuXGUl4t9aNSkKY639Q3Oheov3SO5f1N+51yiil2qwWiaKx57WnstO7VIj433nhj05bfq6pzS3NE/Vi6dGnTRv8Mn8+PasEvWoPq3qD30dwPmg/yCuhzAL3/EvR5IY+BxklrQGOivtGa5pipFq+juaXr6JyhWMiOGu1jch7IIegypsf0jt+0i4iIiIh0HD+0i4iIiIh0HH89RkRERET6gukxveM37SIiIiIiHaf8TTuJbySbkDiRBRcShUjmo//6qRRpGasfWUIj0aZaMIXmg8iyGo29KjdWCwxRwaUswtCcrVu3rmmjsZOAR9dV1o8kI1q7L33pS03biSeeWHpmVdjK3H777Vu8JiLipJNOatpIZMqyEPWL5pZERoJigSTTvG+r3zKQyFgVHinuc0xWZbsslI/1zMcff7xpy/uK5pZENboXnX8kUJLIma+rFiui62gMdH7QGmQBlOKW2kgmpTmi9SP5PJ+nVQF08eLFpb7RGHLskmRJY6I1pqJJFM8Up3nf0v1pzigWSHgcGBho2r773e82bbkgFxXWo7iittWrVzdt1djK80HvjySZ07lQFVazMF0tbkj9oHWvFlfK748VaTaC+ys7J6bHiIiIiEhfMD2md0yPERERERHpOH5oFxERERHpOKbHiIiIiEhfMD2md7bpQztVbiMJKEt/JG+QAEoV9ki4IAGlIs5Stb6qCEjyDY0ht02fPr25huaRIEGJZBYS3/IYaEwkSVEbrQHNG8me+bm0BiTJ0hyRnHrCCSeUXptlXRKWqI3Eun/6p38qXTd37txRf5NQRMISzTf1ja4jKTlD8UKHGFXdo31AZwDFUV4XuhfFULUSLrXlZ1TOqwiWTun+FGvU30pFVDonad2pH3Qd3S/3g9a4Uuk5gsdJlYGz8BjRSvBUFbla2ZPOFIrnfBbRfqRz8vrrr2/a6PyjflQqGZNMSlJodZ/RDwzQuZD7RvNNVCvmUt9oPvL+q76/0xrTM2ld8vzS/qn+eAPNLZ0LtB/zvqqOk84A2TkxPUZEREREpOOYHiMiIiIifcH0mN7xm3YRERERkY7jh3YRERERkY5TTo8hMWPGjBlNG4kZWfwgSYWEH5LyquIiSTq5jSrgrVy5smmj60i+IRErV9Qj0YuqwpF8Q68lAapSdY/EFZK6aN2pCiaJerR+eT6oHyRUVsWmG264odS3448/ftTfNE6aW1p3ig+K5xtvvHHU39OmTWuuOeaYY5o2ktBoDSjmaX6z6Eb3r64LVQkkoY9iK4tuVZmZBLmKdBrR7iEaO4mANHZaYxoDVaXNElq1ai+tMdFrldtKvIzVj2ql2uuuu65py2tF/SeZj+KqItxGtJIfVVels5nWhZ5JfaM5yucTrUFVaqWYp3O4UmV5W36oga6jffvYY481bfkHKCjWqjImvZZ+0CGfC3QNnQH02YPGSfu7Uq2V1oCg+Osypsf0jt+0i4iIiIh0HD+0i4iIiIh0HD+0i4iIiIh0nB0rEUpEREREdljMae+d8od2EnJWr17dtJGMmQUOqrBHQhsJLtQPEpRIJMnCz9q1a5trSCoksWS//fZr2rJkGdGKKiQFrVmzpmkjebJa6Y/a8rxRP6oyJkFjp+qvWQyiNa5WCh0aGmraqhXwbr/99lF/n3rqqc011aqPJCjRfGTJiK5ZunRp00Zy2VFHHdW0kZhG4mzefyQ409hprShmqCIgCWc5Jule1A/aG7SX99133y3eb3h4uLmGYoiqeJLkRucYSWJ0PlWg84nijyB5OcdkRdKL4DF9+ctfbtpo75GUl59LzxwcHGza6OwnUZTeN2655ZYtvo76QfFBsi7NUaVyL8UQCY90XbWCZmWsdO7QGUDPpJiksdP7S34tPZPmmz5X0BrQa/OaUlxVqxFTzNNcUn/zHqXPHnSv6vu07PiYHiMiIiIi0nFMjxERERGRvmB6TO/4TbuIiIiISMfxQ7uIiIiISMcpp8eQuEKQOJElCaoQSILVI4880rSRvEFiE5HFWRLmqP/E9773vVI/smBFEgyJdSTQkGxCoh6JKrkfJO4NDAw0bVTBj8SjqvCT44gEU1pjklNp3mj9SGTKa0WVVEn+IgGUJDeSFPP60TzSvUjiXLJkSdNGc7lgwYKmLa9LZX4i6lX3qL9EjlOKb5K/SMCjvfzwww83bRXxkvpB0jPFKa0f/dNrltXodTROkklpvxMkUed9RXF73333NW20LtRWlTHza+l1VWH6rrvuatrobMv7gNa9so/pXmO1kdCc14BERhonxR+dAdUzMZ9HFFfUt1zBNIIl++p7Vb6Ozkk6m0kepesqQmm1qmn1Olr3yg95UP9pDaqfW7qC6TG94zftIiIiIiIdxw/tIiIiIiJbwSWXXBL7779/7L777nH44YfH3XffPea1S5YsiXHjxjX/+9a3vrVVz/TXY0RERESkL+wM6TFf+MIX4g//8A/jkksuiWOOOSY++clPxvz58+Mb3/hGzJ49e8zXffvb3x6V9kSpnc/GuJFib1/84hc3bZSjVinmQvnJlCdI+W6U10i5fZRPmKkW4tlnn32aNsqfo/zHnCtHuZo0TppHyoujfhC5YEW1WAeR84IjeAyUX5mhOaPXUd8oZij/kXKs89pXCzrR/SnWTj755KYt5y2TJ0IOAUF5wZRbSkVO8jNoHinmjzzyyKaNvAKaj0qBF/JaqPBYtTBRxQGhuKL4pnWnmKfcUspDz/FG80jQmUtn4gMPPNC00dmW44hynWkfVB2n6n7JZzidC9dff33TRvNNfgDFTJ4Puobyn+mZtF+IyntatVgWxen69eubNipKRWuQ14riivYUnZ20r6ruVo5xigWao+pniMr77bRp05prKAe9WmyK5rKy1yg/ntaO7kX93d5s2rQpJkyYEEcddVTZkeoHTz31VHzlK1+JjRs34mcj4pWvfGUcdthhcemll25uO+igg+I1r3lNXHjhhc31S5YsiRNPPDEee+wx9IuqmB4jIiIiIs9pNm3aNOp/Ywm+Tz75ZNx///0xb968Ue3z5s3DytA/y6GHHhrTpk2Lk08+GaX5LeGHdhERERHpC8+kx3TpfxERs2bNigkTJmz+H31jHvFf/6L19NNPN79KNTAwEMPDw/iaadOmxac+9alYtGhRXH311XHggQfGySefHMuWLduquevOv0+IiIiIiGwHVq5cOSo9ZkupmDmdaWRkZMy04AMPPDAOPPDAzX/PmTMnVq5cGRdddFEcf/zx5T76TbuIiIiIPKcZP378qP+N9aF98uTJ8bznPa/5Vn3t2rVYE2IsjjrqqPjOd76zVX0sf9NOxRJIkiAJI+cFkTRBcgWJoiSdkpxF0lUWd6oFjEgiIVmB8p+ysEVjJ6mQxklyKslIJE9l+YZEHhKFSDKi+aZ1J1ktS0VVa7t6f+ovrXMWpWi+ad2pv/RaEgGzAHrqqac219B6UixXBSVa5yw00zxSrH3pS19q2uhQo9fS+s2dO3fU33TG0N4gkYxinsaVY5cO2Io4GsF7mWKmUsDp2muvba4hsZiKPFEskIxZObNIIKwUwIngtSK5mO538803j/qb9ln1rKPifSRj5nWheaT5qMiTEfx+QP2ltaq8rirP03W0H/NZQWtAUj/1n9poP1Is5POU5pHGSW30GYLOmbymtM8o/qhvFDPUDzqv8xiq80jr2WV29F+P2XXXXePwww+P2267LV772tdubr/tttvirLPOKt/ngQceQOn52TA9RkRERESkyMKFC+M3f/M344gjjog5c+bEpz71qVixYkWcd955ERFxwQUXxKpVq+Kzn/1sRERcfPHFsd9++8XBBx8cTz75ZFx11VWxaNGiWLRo0VY91w/tIiIiIiJFXve618WGDRviQx/6UAwNDcXLXvay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s2fPjosvvjgOOuig+N3f/d34nd/5nbjooou26rl+aBcRERGRvrC9U2G2NT3mySefjPvvvz/mzZs3qn3evHnx5S9/GV+zfPny5vrTTjst7rvvvtJPAD+DP/koIiIiIs9pcprvbrvthiln69evj6effjoGBgZGtQ8MDGC9h4j/qgNB1z/11FOxfv36mDZtWqmPftMuIiIiIs9pZs2aFRMmTNj8vwsvvPBZr6cijeQhPNv11P5slL9pH+u/HkREREREKnS1uNLKlStHicRjVWGePHlyPO95z2s+F69du7b5Nv0ZBgcH8fpddtkFf4BiLPymXURERESe04wfP37U/8b60L7rrrvG4YcfHrfddtuo9ttuuy2OPvpofM2cOXOa62+99dY44ogj8BeBxsIP7SIiIiIiRRYuXBif/vSn44orrohvfvOb8e53vztWrFgR5513XkREXHDBBfHmN7958/XnnXdefO9734uFCxfGN7/5zbjiiivi8ssvj/e+971b9VxFVBERERHpC11Nj9kaXve618WGDRviQx/6UAwNDcXLXvayWLx4cey7774RETE0NBQrVqzYfP3+++8fixcvjne/+93xiU98IqZPnx4f+9jHtuo32iMixo10aeZEREREZKdj06ZNMWHChJgyZUq5WFo/+OlPfxrr1q2LjRs3YnGsLtGdWRMREREREcT0GBERERHpCztDesz2wm/aRUREREQ6jh/aRUREREQ6jukxIiIiItIXTI/pHb9pFxERERHpOH5oFxERERHpOKbHiIiIiEhfMD2md/ymXURERESk4/ihXURERESk45geIyIiIiJ9wfSY3vGbdhERERGRjuOHdhERERGRjmN6jIiIiIj0jR0pJaVL+E27iIiIiEjH8UO7iIiIiEjHMT1GRERERPpC11JjutafZ8Nv2kVEREREOo4f2kVEREREOo7pMSIiIiLSF7qWjtK1/jwbftMuIiIiItJx/NAuIiIiItJxTI8RERERkb7QtXSUrvXn2fCbdhERERGRjuOHdhERERGRjmN6jIiIiIj0ha6lo3StP8+G37SLiIiIiHQcP7SLiIiIiHQc02NEREREpC90LR2la/15NvymXURERESk4/ihXURERESk45geIyIiIiJ9oWvpKF3rz7PhN+0iIiIiIh3HD+0iIiIiIh3H9BgRERER6QtdS0fpWn+eDb9pFxERERHpOH5oFxERERHpOKbHiIiIiEhf6Fo6Stf682z4TbuIiIiISMfxQ7uIiIiISMcxPUZERERE+kLX0lG61p9nw2/aRUREREQ6jh/aRUREREQ6jukxIiIiItIXupaO0rX+PBt+0y4iIiIi0nH80C4iIiIi0nFMjxERERGRvtC1dJSu9efZ8Jt2EREREZGO44d2EREREZGOY3qMiIiIiPSFrqWjdK0/z4bftIuIiIiIdBw/tIuIiIiIdBzTY0RERESkL3QtHaVr/Xk2/KZdRERERKTj+KFdRERERKTjmB4jIiIiIn2ha+koXevPs+E37SIiIiIiHccP7SIiIiIiHcf0GBERERHpC11LR+laf54Nv2kXEREREek4fmgXEREREek4pseIiIiISF/oWjpK1/rzbPhNu4iIiIhIx/FDu4iIiIhIxzE9RkRERET6QtfSUbrWn2fDb9pFRERERDqOH9pFRERERDqO6TEiIiIi0jd2pJSULuE37SIiIiIiHccP7SIiIiIiHccP7SIiIiLyC2XXXXeNwcHB7d0NZHBwMHbdddft3Y0tMm7ExCIRERER+QXzox/9KJ588snt3Y2GXXfdNXbfffft3Y0t4od2EREREZGOY3qMiIiIiEjH8UO7iIiIiEjH8UO7iIiIiEjH8UO7iIiIiEjH8UO7iIiIiEjH8UO7iIiIiEjH8UO7iIiIiEjH+f8BegWdmvrqXicAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "shape = [128, 128]\n", + "radius = torch.stack(meshgrid_ij(*[torch.arange(s).float() for s in shape]), -1)\n", + "radius -= (torch.as_tensor(shape).float() - 1) / 2\n", + "radius = radius.square().sum(-1).sqrt()\n", + "\n", + "mag = torch.zeros_like(radius, dtype=torch.float32)\n", + "mag[radius < 48] = 1\n", + "mag[radius < 44] = 2\n", + "mag[radius < 24] = 3\n", + "mag = mag[None] # channels dimension\n", + "\n", + "mag += torch.randn_like(mag) * 0.1\n", + "\n", + "plt.figure(figsize=(10, 10))\n", + "plt.imshow(mag.squeeze(), cmap='gray', interpolation='nearest')\n", + "plt.axis('off')\n", + "plt.title('Signal magnitude')\n", + "plt.colorbar()\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAu0AAAMWCAYAAABFnLgJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABlGklEQVR4nO3de7BlZZkf/qehb1y6W0FpGgFpygkieEvjSINclBEEMToyMyYaRyfGChmVyKWG4EwVzCUD5SWiEWGoQQgyoGVaFAUNVAbwMq0lihojMhpRELuHgEpjK32hz++PKc7Ps897up+z1tprv/ucz6eq/zhvr7332uu237PO893PgomJiYkAAACqtduoVwAAANg5k3YAAKicSTsAAFTOpB0AACpn0g4AAJUzaQcAgMqZtAMAQOVM2gEAoHILR70CAADMfY8//nhs3bp11KsxzeLFi2Pp0qWjXo1dMmkHAGCoHn/88Vi9enVs3Lhx1Ksyzf777x/33Xdf9RN3k3YAAIZq69atsXHjxnjggQdi+fLlo16dSZs2bYqDDjootm7datIOAAAREcuXL69q0j5OTNoBAOjFxMRETExMjHo1JtW0Lrvi22MAACDh8ssvj+c973mTfzFYu3ZtfO5zn9vpY+68885Ys2ZNLF26NA499NC44oorGr22STsAACQceOCBcckll8Rdd90Vd911V7zsZS+LV7/61fF//s//KS5/3333xWmnnRbHHXdc3H333fGud70rzjrrrFi3bt2sX3vBxDj9XQAAgLGzadOmWLFiRfz85z+vqqZ906ZN8dSnPjUeffTRxuu1zz77xHve8554y1veMu3/zj///LjpppvinnvumRw788wz41vf+lasX79+Vq/jTjsAAPPapk2bpvzbsmXLLh/zxBNPxMc+9rHYvHlzrF27trjM+vXr4+STT54ydsopp8Rdd90V27Ztm9U6mrQDADCvHXTQQbFixYrJfxdffPGMy/7v//2/Y++9944lS5bEmWeeGTfeeGM85znPKS67cePGWLly5ZSxlStXxvbt2+Phhx+e1Tr69hgAAHpR67fHDH5//JIlS2Z8zGGHHRbf/OY34xe/+EWsW7cu3vSmN8Wdd94548R9wYIFxdccHN8Vk3YAAOa12Xx//OLFi+NZz3pWREQcddRR8bWvfS0+8IEPxN/8zd9MW3b//fef1gX2oYceioULF8a+++47q3VUHgMAAA1NTEzMWAO/du3auO2226aM3XrrrXHUUUfFokWLZvU67rQDANCLWstjst71rnfFqaeeGgcddFA89thj8bGPfSzuuOOO+PznPx8RERdccEE8+OCDce2110bEP39TzIc+9KE455xz4q1vfWusX78+rrrqqrjhhhtmva4m7QAAkPBP//RP8cY3vjE2bNgQK1asiOc973nx+c9/Pl7+8pdHRMSGDRvi/vvvn1x+9erVccstt8TZZ58dl112WRxwwAHxwQ9+MM4444xZv7bvaQcAYKie/J72Rx55pLrvad93331bfU97X9xpBwCgF+NeHjNKgqgAAFA5k3YAAKic8hgAAHqhPKY5d9oBAKByJu0AAFA55TEAAPRCeUxz7rQDAEDlTNoBAKByymMAAOiF8pjm3GkHAIDKmbQDAEDllMcAANAL5THNudMOAACVM2kHAIDKKY8BAKAXymOac6cdAAAqZ9IOAACVUx4DAEAvlMc05047AABUzqQdAAAqpzwGAIBeKI9pzp12AAConEk7AABUTnkMAAC9UB7TnDvtAABQOZN2AAConPIYAAB6oTymOXfaAQCgcibtAABQOeUxAAD0QnlMc+60AwBA5UzaAQCgcspjAADohfKY5txpBwCAypm0AwBA5ZTHAADQC+UxzbnTDgAAlTNpBwCAyimPAQCgF8pjmnOnHeaJr371q/G7v/u7cfDBB8eSJUti5cqVsXbt2jj33HNHvWo79d3vfjcuuuii+NGPfjTt/0488cQ48sgje1mPBQsWxEUXXbTTZX70ox/FggUL4pprrullnQCYP0zaYR64+eab45hjjolNmzbFu9/97rj11lvjAx/4QBx77LHx8Y9/fNSrt1Pf/e5348///M+Lk/barFq1KtavXx+vfOUrR70qAMwxymNgHnj3u98dq1evjv/5P/9nLFz4/5/2//pf/+t497vfPcI1m1uWLFkSRx999KhXA6Bq41SSUhN32mEeeOSRR+JpT3valAn7k3bbbepl4JBDDonTTz89PvvZz8YLX/jC2GOPPeLwww+Pz372sxERcc0118Thhx8ee+21V/z2b/923HXXXdOe86abboq1a9fGnnvuGcuWLYuXv/zlsX79+mnLfelLX4qTTjopli1bFnvuuWccc8wxcfPNN0/+/zXXXBO///u/HxERL33pS2PBggXF8pOvfe1rcdxxx8Wee+4Zhx56aFxyySWxY8eOKcts2rQpzjvvvFi9enUsXrw4nvGMZ8Q73/nO2Lx587Tl3vrWt8a+++4be++9d7ziFa+If/zHf9zJ1v3/lcpjLrrooliwYEF8+9vfjt///d+PFStWxD777BPnnHNObN++Pe699954xSteEcuWLYtDDjlk2i9Rjz/+eJx77rnxghe8YPKxa9eujU9/+tPTXv8Xv/hFvOUtb4l99tkn9t5773jlK18ZP/zhD4ulPd///vfj9a9/fey3336xZMmSOPzww+Oyyy5LvU8A+mfSDvPA2rVr46tf/WqcddZZ8dWvfjW2bdu20+W/9a1vxQUXXBDnn39+fPKTn4wVK1bEa1/72rjwwgvjb//2b+Ov//qv4+/+7u/i0UcfjdNPPz1+/etfTz72+uuvj1e/+tWxfPnyuOGGG+Kqq66Kn//853HiiSfGl770pcnl7rzzznjZy14Wjz76aFx11VVxww03xLJly+JVr3rVZMnOK1/5yvjrv/7riIi47LLLYv369dPKTzZu3BhveMMb4t/+238bN910U5x66qlxwQUXxHXXXTe5zK9+9as44YQT4r//9/8eZ511Vnzuc5+L888/P6655pr4V//qX03e9ZmYmIjXvOY18dGPfjTOPffcuPHGG+Poo4+OU089tfU++IM/+IN4/vOfH+vWrYu3vvWt8f73vz/OPvvseM1rXhOvfOUr48Ybb4yXvexlk9v8SVu2bImf/exncd5558WnPvWpuOGGG+IlL3lJvPa1r41rr712crkdO3bEq171qrj++uvj/PPPjxtvvDFe/OIXxyte8Ypp6/Ld7343XvSiF8V3vvOdeN/73hef/exn45WvfGWcddZZ8ed//uet3ysAQzABzHkPP/zwxEte8pKJiJiIiIlFixZNHHPMMRMXX3zxxGOPPTZl2Wc+85kTe+yxx8RPfvKTybFvfvObExExsWrVqonNmzdPjn/qU5+aiIiJm266aWJiYmLiiSeemDjggAMmnvvc50488cQTk8s99thjE/vtt9/EMcccMzl29NFHT+y3335TXn/79u0TRx555MSBBx44sWPHjomJiYmJT3ziExMRMXH77bdPe18nnHDCRERMfPWrX50y/pznPGfilFNOmfz54osvnthtt90mvva1r01Z7n/8j/8xERETt9xyy8TExMTE5z73uYmImPjABz4wZbn/8l/+y0RETFx44YXTN+5vuO+++yYiYuLqq6+eHLvwwgsnImLife9735RlX/CCF0xExMQnP/nJybFt27ZNPP3pT5947WtfO+NrbN++fWLbtm0Tb3nLWyZe+MIXTo7ffPPNExExcfnll09Z/uKLL5627qeccsrEgQceOPHoo49OWfbtb3/7xNKlSyd+9rOf7fR9AszWo48+OhEREz/84Q8n/t//+3/V/PvhD384ERHTroc1cqcd5oF99903vvjFL8bXvva1uOSSS+LVr351/OM//mNccMEF8dznPjcefvjhKcu/4AUviGc84xmTPx9++OER8c/f1rLnnntOG//xj38cERH33ntv/PSnP403vvGNU8pu9t577zjjjDPiK1/5SvzqV7+KzZs3x1e/+tX4vd/7vdh7770nl9t9993jjW98Y/zkJz+Je++9N/Xe9t9///jt3/7tKWPPe97zJtcpIuKzn/1sHHnkkfGCF7wgtm/fPvnvlFNOiQULFsQdd9wRERG33357RES84Q1vmPJ8r3/961PrsjOnn376lJ8PP/zwWLBgwZS7+AsXLoxnPetZU9Y9IuITn/hEHHvssbH33nvHwoULY9GiRXHVVVfFPffcM7nMnXfeGRH/fEf/N/2bf/Nvpvz8+OOPx//6X/8rfvd3fzf23HPPKdvjtNNOi8cffzy+8pWvtH6/AHTLpB3mkaOOOirOP//8+MQnPhE//elP4+yzz44f/ehH0+qo99lnnyk/L168eKfjjz/+eET8c+18xD9/i8qgAw44IHbs2BE///nP4+c//3lMTEzMuNxvPteu7LvvvtPGlixZMqVk55/+6Z/i29/+dixatGjKv2XLlsXExMTkLy2PPPJILFy4cNpz7r///ql12ZnStttzzz1j6dKl08af3J4REZ/85CfjD/7gD+IZz3hGXHfddbF+/fr42te+Fv/u3/27Kcs9ue6Dr7Ny5copPz/yyCOxffv2+G//7b9N2x6nnXZaRMS0X+IAGD3fHgPz1KJFi+LCCy+M97///fGd73ynk+d8crK7YcOGaf/305/+NHbbbbd46lOfGhMTE7HbbrvNuFxExNOe9rRO1unJ59pjjz3iIx/5yIz/H/HP6799+/Z45JFHpkzcN27c2Nm6zNZ1110Xq1evjo9//OOxYMGCyfEtW7ZMWe7Jdf/Zz342ZeI+uO5PfepTJ/+i8ba3va34mqtXr+7wHQD8/yY0V2rMnXaYB0qT44iYLK948u52W4cddlg84xnPiOuvv37KhXDz5s2xbt26yW+U2WuvveLFL35xfPKTn5xyR3zHjh1x3XXXxYEHHhj/4l/8i4j457vmETFludk6/fTT4//+3/8b++67bxx11FHT/h1yyCER8c/fUBMR8Xd/93dTHn/99dc3fu22FixYEIsXL54yYd+4ceO0b4854YQTIiKmfe/+xz72sSk/77nnnvHSl7407r777nje855X3B6lv14AMFrutMM8cMopp8SBBx4Yr3rVq+LZz3527NixI775zW/G+973vth7773jP/2n/9TJ6+y2227x7ne/O97whjfE6aefHv/hP/yH2LJlS7znPe+JX/ziF3HJJZdMLnvxxRfHy1/+8njpS18a5513XixevDg+/OEPx3e+85244YYbJiepT3Y8vfLKK2PZsmWxdOnSWL169awmlu985ztj3bp1cfzxx8fZZ58dz3ve82LHjh1x//33x6233hrnnntuvPjFL46TTz45jj/++PiTP/mT2Lx5cxx11FHx5S9/OT760Y92sn2aOP300+OTn/xk/PEf/3H83u/9XjzwwAPxl3/5l7Fq1ar4/ve/P7ncK17xijj22GPj3HPPjU2bNsWaNWti/fr1k98w85sZgw984APxkpe8JI477rj4j//xP8YhhxwSjz32WPzgBz+Iz3zmM/H3f//3vb9PAHbOpB3mgT/7sz+LT3/60/H+978/NmzYEFu2bIlVq1bF7/zO78QFF1wwGSjtwutf//rYa6+94uKLL47Xve51sfvuu8fRRx8dt99+exxzzDGTy51wwgnx93//93HhhRfGm9/85tixY0c8//nPj5tuumlKaHP16tVx6aWXxgc+8IE48cQT44knnoirr7463vzmN6fXaa+99oovfvGLcckll8SVV14Z9913X+yxxx5x8MEHx+/8zu9M3mnfbbfd4qabbopzzjkn3v3ud8fWrVvj2GOPjVtuuSWe/exnd7WJZuWP/uiP4qGHHoorrrgiPvKRj8Shhx4a//k//+f4yU9+MuXrGXfbbbf4zGc+E+eee25ccsklk+t+3XXXxdFHHx1PecpTJpd9znOeE9/4xjfiL//yL+PP/uzP4qGHHoqnPOUp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Lw08hi1bp1qquRysX4+YXnNZql9v01ypaZOkruvXS3X/JbXW1fbRcGnYjX0yr5ldpuuxprLbqGlDnTbXsDaN2Jq+5lxU2j6l60lpG2VyRBG5OvFM3ftMz5WtXy+Nbd26dcrP27ZtS71mad2yx3Mtx1uX69HlNXw+NeAjR3kMAAC9UB7TnPIYAAConEk7AABUzqQdAIBePFkeU9O/2bj44ovjRS96USxbtiz222+/eM1rXhP33nvvLh935513xpo1a2Lp0qVx6KGHxhVXXDHrbTcnmiuVNA12tXmfXYZGsqHTbABqsJlSH6HTTOOkUui09J6yodNxO06b6vI47WObZYKi2SBgdr9nQ8kZbZrKZJrs1BzSY6rScVU6PkrXrEwTptJztQmYlgKlg6HT0ljTpkwR5fdAN1wXRu/OO++Mt73tbfGiF70otm/fHn/6p38aJ598cnz3u98tznsiIu6777447bTT4q1vfWtcd9118eUvfzn++I//OJ7+9KfHGWeckX5tQVQAAEj4/Oc/P+Xnq6++Ovbbb7/4+te/Hscff3zxMVdccUUcfPDBcemll0ZExOGHHx533XVXvPe9753VpF15DAAAvRh1KUzb8phBjz76aERE7LPPPjMus379+jj55JOnjJ1yyilx1113Ff8aNhN32gEAmNc2bdo05eclS5YUS9p+08TERJxzzjnxkpe8JI488sgZl9u4cWOsXLlyytjKlStj+/bt8fDDD8eqVatS6+hOOwAA89pBBx0UK1asmPx38cUX7/Ixb3/72+Pb3/523HDDDbtcdqbGhrPJlbW60559oaYB0Gyws+vOgTXIBlEXLVo0bWwwdBqR60RaClBkx7IdVgfXrbT+pfcpdNrdY5t2RG3TiTQTKM3u9zbHR9OuoJkw6WzGBgN9bV6zTYh12J1ZawnNDXs9SsdkSelLApoGUUsB0NKf2bds2TJt7PHHH9/lWGmZUoA1u261HAslo+gSPZ/V2lzpgQceiOXLl0+O7+ou+zve8Y646aab4gtf+EIceOCBO112//33j40bN04Ze+ihh2LhwoWx7777ptdVeQwAAPPa8uXLp0zaZzIxMRHveMc74sYbb4w77rgjVq9evcvHrF27Nj7zmc9MGbv11lvjqKOOKt68nInyGAAASHjb294W1113XVx//fWxbNmy2LhxY2zcuDF+/etfTy5z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9bl4/R6V7Lbs8v03zUf1kauaq/s5Q3lMc+60AwBA5UzaAQCgcspjAADohfKY5txpBwCAylV9p72WYFDWuDf6aLP+TR87n5pwNH0P2dBpSSa0mQ2wZgOgmfBraSz7uC7Pg66Pq6YBzWwANBvQbLpc1wH7YTdJ6jLc2EcAftjm6nsY1PV7Gny+NseVcCrDVPWkHQCAuUN5THPKYwAAoHIm7QAAUDnlMQAA9EJ5THMjmbT30W0sE8iZTyHIproMjbFrXQcBM+daaZlhh19LY21Cp+MW0h4caxPY7DLs2SbIOO7XhTbHUJfbqOsgY5f7pc02aroeowhnttmOtawvc5PyGAAAmIUPf/jDsXr16li6dGmsWbMmvvjFL8647B133BELFiyY9u973/verF5TeQwAAL2YC+UxH//4x+Od73xnfPjDH45jjz02/uZv/iZOPfXU+O53vxsHH3zwjI+79957Y/ny5ZM/P/3pT5/V67rTDgAASf/1v/7XeMtb3hL//t//+zj88MPj0ksvjYMOOiguv/zynT5uv/32i/3333/y3+677z6r1zVpBwBgXtu0adOUf1u2bCkut3Xr1vj6178eJ5988pTxk08+Of7hH/5hp6/xwhe+MFatWhUnnXRS3H777bNex7Erj2kTEBlcro/Q6Si6u2XUHDDtej2GvX1rCUuWDDv4ltU0EJsdq8WwO3t2vVzT58rul5q7RDc9JmtWS1iyls+SuRiiHrdjsqTW8piDDjpoyviFF14YF1100bTlH3744XjiiSdi5cqVU8ZXrlwZGzduLL7GqlWr4sorr4w1a9bEli1b4qMf/WicdNJJcccdd8Txxx+fXtexm7QDAECXHnjggSn15kuWLNnp8qVvJJzpl6rDDjssDjvssMmf165dGw888EC8973vndWkXXkMAADz2vLly6f8m2nS/rSnPS123333aXfVH3rooWl333fm6KOPju9///uzWkeTdgAAevFkeUxN/2Zj8eLFsWbNmrjtttumjN92221xzDHHpJ/n7rvvjlWrVs3qtZXHAABA0jnnnBNvfOMb46ijjoq1a9fGlVdeGffff3+ceeaZERFxwQUXxIMPPhjXXnttRERceumlccghh8QRRxwRW7dujeuuuy7WrVsX69atm9Xrjt2kfRQBq2GvR83mwvtsup9rDpiW1Lyvmgay++hSOaiW7TiK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xRWzfk2TdgAASPr4xz8e73znO+NP//RP4+67747jjjsuTj311Lj//vuLy993331x2mmnxXHHHRd33313vOtd74qzzjor1q1bN6vXXTAxThX4AACMnU2bNsWKFSti4cKFsWDBglGvzqSJiYnYvn17PProo7F8+fLUY1784hfHv/yX/zIuv/zyybHDDz88XvOa18TFF188bfnzzz8/brrpprjnnnsmx84888z41re+FevXr0+vqzvtAAD0YtSlMG3LY7Zu3Rpf//rX4+STT54yfvLJJ8c//MM/FB+zfv36acufcsopcdddd8W2bdvSr+0rHwEAmNc2bdo05eclS5bEkiVLpi338MMPxxNPPBErV66cMr5y5crYuHFj8bk3btxYXH779u3x8MMPx6pVq1Lr6E47AADz2kEHHRQrVqyY/Fcqc/lNgyU+ExMTOy37KS1fGt8Zd9oBAOhFbVHKJ9fngQcemFLTXrrLHhHxtKc9LXbfffdpd9UfeuihaXfTn7T//vsXl1+4cGHsu+++6XV1px0AgHlt+fLlU/7NNGlfvHhxrFmzJm677bYp47fddlscc8wxxcesXbt22vK33nprHHXUUbFo0aL0Opq0AwBA0jnnnBN/+7d/Gx/5yEfinnvuibPPPjvuv//+OPPMMyMi4oILLog//MM/nFz+zDPPjB//+MdxzjnnxD333BMf+chH4qqrrorzzjtvVq+rPAYAgF7UWh4zG6973evikUceib/4i7+IDRs2xJFHHhm33HJLPPOZz4yIiA0bNkz5zvbVq1fHLbfcEmeffXZcdtllccABB8QHP/jBOOOMM2b1ur6nHQCAoXrye9oXLFhQ3fe0T0xMzOp72kdFeQwAAFROeQwAAL2orcCjtvXZGXfaAQCgcibtAABQOeUxAAD0ZpxKUmriTjsAAFTOpB0AACpn0g4AwFAtXrw49t9//1GvRtH+++8fixcvHvVq7JLmSgAADN3jjz8eW7duHfVqTLN48eJYunTpqFdjl0zaAQCgcspjAACgcibtAABQOZN2AAConEk7AABUzqQdAAAqZ9IOAACVM2kHAIDK/X8HFAIih5hMEgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "smo = ccf.smooth(mag, 5)\n", + "\n", + "plt.figure(figsize=(10, 10))\n", + "plt.imshow(smo.squeeze(), cmap='gray', interpolation='nearest')\n", + "plt.axis('off')\n", + "plt.title('Smoothed image')\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 212, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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yZMn28tf/nL72Mc+Zr/4xS/MzOxnP/uZTZo0yT70oQ+1Lffhhx+2vr4+u/nmm0eG/fu//7u9+c1vthe/+MU2MDBgr3nNa+xzn/vcPrfvrrvusr6+Pvunf/qntv93yy23WF9fnz300EMjw9asWWO/+Zu/aUcccYQNDAzYKaecYn/7t3/bNu13vvMde8Mb3mADAwN2zDHH2OLFi23Xrl37XJe9vPKY4447zs4//3z76le/aqeccooNDg7a8ccfb1/96lfN7L9Kb44//nibMmWKve51r7M1a9aMmn7NmjX2tre9zY477jgbHBy04447zt7+9rfbE0880bb8b33rWzZ37lwbGBiwl7zkJfahD33IPvOZz7ilPXfccYfNnTvXpkyZYoceeqgtWLDA1q5dK20nAKA5PLQDB7A9e/bYN77xDZszZ47NnDlTmua9732vXXPNNXbuuefaypUr7U/+5E/snnvusdNPP92eeeaZUeNu2rTJfvu3f9t+53d+x1auXGkLFy60xYsX2/Lly83M7Fd+5Vfs/PPPt8997nMjD/J7LV261CZNmmS//du/bWZmjzzyiJ1++un2wx/+0G6++Wa788477YQTTrBLLrnEPvGJT3Rc3/PPP9+OPvpoW7p0adv/W7Zsmb32ta+1k08+2czMvvnNb9ob3vAGe+6552zJkiX2la98xV7zmtfYhRdeOKpG/kc/+pHNmzfPnnvuOVu2bJktWbLE1q5da//rf/0vaR928oMf/MAWL15s11xzjd155502depUe+tb32rXX3+9feYzn7GPfvSj9oUvfME2b95s559/vm3fvn1k2scff9xmz55tN910k33961+3j3/847Zx40Y77bTTRh2Xhx56yM4991zbtm2bfe5zn7MlS5bY97//ffvTP/3TtvX56Ec/am9/+9vthBNOsL/927+1z3/+8/b888/bGWecYT/60Y9C2woAKKwF4IC1adOmlpm13va2t0njr1u3rmVmrcsvv3zU8H/5l39pmVnr2muvHRl21llntcys9S//8i+jxj3hhBNaCxYsGPnzypUrW2bWuvfee0eG7d69u3XMMce0LrjggpFhb3vb21r9/f2t9evXj5rfwoULW5MnT24999xzrVar1XrsscdaZtZaunTpyDiLFi1qDQ4OjozTarVaP/rRj1pm1vrUpz41MuxVr3pV65RTTmnt2rVr1DLOP//81owZM1p79uxptVqt1oUXXtgaHBxsbdq0adQ6v+pVr2qZWeuxxx7zd+D/7/rrr2+lt9eXvexlrcHBwdZPfvKTkWEPPvhgy8xaM2bMaG3dunVk+F133dUys9bKlSs7LmP37t2tF154oTVlypTWn//5n48M/+///b+3pkyZ0vrZz342MmzPnj2tE044YdS6r1+/vjVx4sTW+9///lHzff7551vTp09v/dZv/dY+txEAxmLz5s0tM2s9+uijrZ/97GfV/Pfoo4+2zKy1efPmXu+i/eJNO4AR3/zmN83M7JJLLhk1/HWve50df/zxbSUo06dPt9e97nWjhp188smjSjYWLlxo06dPH/Um/Otf/7o99dRT9q53vWtk2De+8Q2bN29e278IXHLJJbZt2zZbvXp1x/V+17veZdu3b7c77rhjZNjSpUutv7/f3vGOd5iZ2Y9//GN7+OGHR97s7969e+S/8847zzZu3GiPPPLIyH6YN2+eTZs2bWR+EyZMsAsvvLDjOihe85rX2Ete8pKRPx9//PFm9l+lRpMnT24b/sv78YUXXrBrrrnGXvGKV9jEiRNt4sSJduihh9rWrVtt3bp1I+Pdf//99sY3vtGOOuqokWEvetGL7Ld+67dGrcvXv/512717t1100UWj9sXAwICdddZZdt9994W2FQBQFkFU4AB21FFH2eTJk+2xxx6Txn/22WfNzGzGjBlt/++YY45pq58+8sgj28br7+8fVdYxceJEe+c732mf+tSn7LnnnrPDDz/cli1bZjNmzLAFCxaMWnan5f7yunlOPPFEO+2002zp0qX2nve8x/bs2WPLly+3N7/5zXbEEUeYmdlPf/pTMzO76qqr7KqrrnLns7fM5Nlnn7Xp06e3/X9v2FjsXZe9Jk2atM/hO3bsGBn2jne8w/7pn/7JPvShD9lpp51mQ0ND1tfXZ+edd96o/f3ss8+O+svGXumwvfvjtNNOc9f1RS/inQ4A1ISHduAANmHCBJs3b579wz/8g/3kJz/Z7xdN9j6Eb9y4sW3cp556atTb27G49NJL7ZOf/KR96UtfsgsvvNBWrlxpH/zgB23ChAmjlr1x48a2aZ966ikzs/0u+9JLL7XLL7/c1q1bZ48++qht3LjRLr300pH/v3f6xYsX21vf+lZ3HrNnzx5Zl02bNrX9f29YEzZv3mxf/epX7frrr7c//MM/HBm+c+fOtm/sH3nkkSMP5L8sXfe9++Pv/u7v7GUve1kX1hoA2rVorpSNh3bgALd48WK7++677d3vfrd95StfGXmLu9euXbvsnnvusTe96U32xje+0czMli9fPuoN7He/+11bt26dXXfddVnrcPzxx9uv/uqv2tKlS23Pnj22c+fOUQ/UZmbz5s2zL3/5y/bUU0+NvF03M7v99ttt8uTJ9vrXv36fy3j7299uixYtsmXLltmjjz5qL3nJS2z+/Pkj/3/27Nn2yle+0n7wgx/YRz/60X3O65xzzrGVK1faT3/605E31Hv27BlVftOkvr4+a7VabV//+cxnPmN79uwZNeyss86yu+++25555pmRB/Nf/OIX9r//9/8eNd6CBQts4sSJ9v/+3/+zCy64oLsbAAAI46EdOMDNnTvXbrnlFrv88sttzpw59t73vtdOPPFE27Vrl61du9ZuvfVWO+mkk+xNb3qTzZ49297znvfYpz71KXvRi15kCxcutMcff9w+9KEP2cyZM+3KK6/MXo93vetd9vu///v21FNP2emnnz7yVnuv66+/3r761a/aOeecYx/+8IftiCOOsC984Qv2ta99zT7xiU/Y1KlT9zn/ww8/3P7bf/tvtmzZMnvuuefsqquuaivx+Ku/+itbuHChLViwwC655BJ7yUteYj//+c9t3bp19v3vf3/kwfaP/uiPbOXKlfbGN77RPvzhD9vkyZPt05/+tG3dujV7+yOGhobszDPPtE9+8pN21FFH2XHHHWf333+/3XbbbXb44YePGve6666zv//7v7d58+bZddddZ4ODg7ZkyZKRdd+7T4477jj7yEc+Ytddd509+uij9hu/8Rv24he/2H7605/av/7rv9qUKVPsf/7P/9n0pgIAOqBoETgIvPvd77Y1a9bYnDlz7OMf/7jNnz/f3vKWt9jf/M3f2Dve8Q679dZbR8a95ZZb7GMf+5jdfffddv7559t1111n8+fPt29/+9tuDbvqbW97mw0ODtpPfvKTtrfsZv/1Jvzb3/62zZ4926644gp7y1veYv/+7/9uS5cutT/4gz+QlnHppZfa008/bcPDw21hWrP/eoP+r//6r3b44YfbBz/4Qfv1X/91e+9732v/+I//aL/+678+Mt5JJ51k//iP/2hDQ0N28cUX23ve8x47+eST3e/N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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ker = ccf.random_kernel([1, 11, 11], distrib=\"lognormal\")\n", + "\n", + "conv = ccf.conv(mag, ker)\n", + "\n", + "plt.figure(figsize=(10, 10))\n", + "plt.imshow(conv.squeeze(), cmap='gray', interpolation='nearest')\n", + "plt.axis('off')\n", + "plt.title('Convolved image')\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 155, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAu0AAAMWCAYAAABFnLgJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAC9vElEQVR4nO29e9SdZXmgf6cyEBATyPkLSUgCGEIQxABDsIBKhcJodcqaag8eax3F6lKG2iJj7XSm0lpWF7W1UKaU1GI7tsUDVYrSVlCXeAgEOQUIkHO+L0cOQqsc/H5/9EeW+36uL7mzo5s34brW4o/98B6e8/tmf/e173Gjo6OjISIiIiIineWnnusKiIiIiIjIzvGlXURERESk4/jSLiIiIiLScXxpFxERERHpOL60i4iIiIh0HF/aRUREREQ6ji/tIiIiIiIdx5d2EREREZGOs99zXQERERER2ff5/ve/H08++eRzXY2G/fffP8aPH/9cV2OX+NIuIiIiIj9Rvv/978e8efNiZGTkua5Kw4wZM2LVqlWdf3H3pV1EREREfqI8+eSTMTIyEuvWrYsJEyY819XZwWOPPRazZ8+OJ5980pd2EREREZGIiAkTJnTqpX1vwpd2ERERERkIo6OjMTo6+lxXYwddqsuu8NdjREREREQ6ji/tIiIiIiIdx/AYERERERkIhsf0j9+0i4iIiIh0HF/aRUREREQ6juExIiIiIjIQDI/pH79pFxERERHpOL60i4iIiIh0HMNjRERERGQgGB7TP37TLiIiIiLScXxpFxERERHpOIbHiIiIiMhAMDymf/ymXURERESk4/jSLiIiIiLScQyPEREREZGBYHhM//hNu4iIiIhIx/GlXURERESk4xgeIyIiIiIDwfCY/vGbdhERERGRjuNLu4iIiIhIxzE8RkREREQGguEx/eM37SIiIiIiHceXdhERERGRjmN4jIiIiIgMBMNj+sdv2kVEREREOo4v7SIiIiIiHcfwGBEREREZCIbH9I/ftIuIiIiIdBxf2kVEREREOo7hMSIiIiIyEAyP6R+/aRcRERER6Ti+tIuIiIiIdBzDY0RERERkIBge0z9+0y4iIiIi0nF8aRcRERER6TiGx4iIiIjIQDA8pn/8pl1EREREpOP40i4iIiIi0nEMjxERERGRgWB4TP/4TbuIiIiISMfxpV1EREREpOMYHiMiIiIiA8HwmP7xm3YRERERkY7jS7uIiIiISMcxPEZEREREBoLhMf3jN+0iIiIiIh3Hl3YRERERkY5jeIyIiIiIDATDY/rHb9pFRERERDqOL+0iIiIiIh3H8BgRERERGQiGx/SP37SLPE+444474m1ve1vMmzcvxo8fHwcffHC87GUvi4997GOxffv257p6ZVavXh3jxo2LpUuXDvzeS5cujXHjxsXq1at3etzv/M7vxLhx4wZTKREReV7gN+0izwP+7//9v3H++efHggUL4jd+4zfimGOOiaeeeiqWLVsWV1xxRdxyyy3x2c9+9rmu5j7DO97xjvjZn/3Z57oaIiKyD+FLu8g+zi233BLvfve749WvfnV87nOfiwMOOGDH/3v1q18d/+N//I+44YYbnsMa7nvMmjUrZs2a9VxXQ0Skk+xNISldwvAYkX2cj370ozFu3Li48sore17Yn2X//fePn/u5n9vx+Yc//GF87GMfi6OPPjoOOOCAmDZtWrz5zW+O9evX95z3ile8Io499tj4zne+E6eddlocdNBBMX/+/Pj93//9+OEPfxgREVu2bIn9998/PvzhDzf3vffee2PcuHHx8Y9/fEfZXXfdFa973evi0EMPjfHjx8dLX/rS+Ku/+qudtu9zn/tcjBs3Lv7lX/6l+X+XX355jBs3Lu64444dZcuWLYuf+7mfi0mTJsX48ePjhBNOiL/7u79rzv3mN78ZL3/5y2P8+PExc+bMuOiii+Kpp57aaV2ehcJj5s6dG695zWviC1/4Qpxwwglx4IEHxsKFC+MLX/hCRPxH6M3ChQvjhS98YZx88smxbNmynvOXLVsWb3zjG2Pu3Llx4IEHxty5c+MXf/EXY82aNc39v/71r8eSJUti/Pjxcdhhh8WHP/zh+Iu/+AsM7fn0pz8dS5YsiRe+8IVx8MEHx9lnnx3Lly8vtVNERAaHL+0i+zDPPPNM/Ou//mssXrw4Zs+eXTrn3e9+d/zmb/5mvPrVr47rrrsu/vf//t9xww03xKmnnhpbt27tOXZkZCR++Zd/OX7lV34lrrvuujjnnHPioosuimuuuSYiIqZOnRqvec1r4q/+6q92vMg/y9VXXx37779//PIv/3JERNx3331x6qmnxt133x0f//jH4zOf+Uwcc8wx8da3vjU+9rGPjVnf17zmNTFt2rS4+uqrm/+3dOnSeNnLXhbHHXdcRER85StfiZe//OXxyCOPxBVXXBGf//zn46UvfWm84Q1v6ImRv+eee+LMM8+MRx55JJYuXRpXXHFFLF++PP7P//k/pT4ci+9+97tx0UUXxW/+5m/GZz7zmZg4cWL8/M//fHzkIx+Jv/iLv4iPfvSj8alPfSoeffTReM1rXhP//u//vuPc1atXx4IFC+Kyyy6LL33pS/EHf/AHMTw8HCeddFLPuNxxxx3x6le/Ov7t3/4t/uqv/iquuOKKuO222+L3fu/3mvp89KMfjV/8xV+MY445Jv7u7/4u/vqv/zq+973vxWmnnRb33HPPHrVVRER+zIyKyD7LyMjIaESMvvGNbywdv2LFitGIGD3//PN7yr/1rW+NRsTohz70oR1lZ5xxxmhEjH7rW9/qOfaYY44ZPfvss3d8vu6660YjYvTLX/7yjrKnn356dObMmaPnnXfejrI3vvGNowcccMDo2rVre653zjnnjB500EGjjzzyyOjo6OjoqlWrRiNi9Oqrr95xzAUXXDB64IEH7jhmdHR09J577hmNiNE/+ZM/2VF29NFHj55wwgmjTz31VM89XvOa14wODQ2NPvPMM6Ojo6Ojb3jDG0YPPPDA0ZGRkZ46H3300aMRMbpq1SruwP+fj3zkI6N5ez388MNHDzzwwNH169fvKLv99ttHI2J0aGho9IknnthR/rnPfW40Ikavu+66Me/x9NNPjz7++OOjL3zhC0f/+I//eEf5f/tv/230hS984eiWLVt2lD3zzDOjxxxzTE/d165dO7rffvuNvve97+257ve+973RGTNmjP7CL/zCTtsoIrI7PProo6MRMfrQQw+NbtmypTP/PfTQQ6MRMfroo48+1120S/ymXUR28JWvfCUiIt761rf2lJ988smxcOHCJgRlxowZcfLJJ/eUHXfccT0hG+ecc07MmDGj55vwL33pS7Fx48Z4+9vfvqPsX//1X+PMM89s/iLw1re+Nf7t3/4tbrnlljHr/fa3vz3+/d//PT796U/vKLv66qvjgAMOiF/6pV+KiIgHHngg7r333h3f7D/99NM7/jv33HNjeHg47rvvvh39cOaZZ8b06dN3XO8FL3hBvOENbxizDhVe+tKXxmGHHbbj88KFCyPiP0KNDjrooKb8R/vx8ccfj9/8zd+MI488Mvbbb7/Yb7/94uCDD44nnngiVqxYseO4m2++OV71qlfFlClTdpT91E/9VPzCL/xCT12+9KUvxdNPPx1vfvObe/pi/PjxccYZZ8RNN920R20VEZEfL4qoIvswU6ZMiYMOOihWrVpVOn7btm0RETE0NNT8v5kzZzbx05MnT26OO+CAA3rCOvbbb79405veFH/yJ38SjzzySBxyyCGxdOnSGBoairPPPrvn3mPd90frRixatChOOumkuPrqq+Od73xnPPPMM3HNNdfE6173upg0aVJERGzatCkiIi688MK48MIL8TrPhpls27YtZsyY0fx/Ktsdnq3Ls+y///47Lf/+97+/o+yXfumX4l/+5V/iwx/+cJx00kkxYcKEGDduXJx77rk9/b1t27aef2w8Sy57tj9OOukkrOtP/ZTf6YiIdAlf2kX2YV7wghfEmWeeGf/0T/8U69ev3+Uvmjz7Ej48PNwcu3Hjxp5vb3eHt73tbfGHf/iH8f/+3/+LN7zhDXHdddfF+9///njBC17Qc+/h4eHm3I0bN0ZE7PLeb3vb2+L888+PFStWxEMPPRTDw8Pxtre9bcf/f/b8iy66KH7+538er7FgwYIddRkZGWn+P5UNgkcffTS+8IUvxEc+8pH4rd/6rR3lP/jBD5rf2J88efKOF/IfJdf92f74h3/4hzj88MN/ArUWEWkZNblS3/jSLrKPc9FFF8X1118fv/Zrvxaf//znd3yL+yxPPfVU3HDDDfHa1742XvWqV0VExDXXXNPzDex3vvOdWLFiRVx88cV91WHhwoXxn//zf46rr746nnnmmfjBD37Q80IdEXHmmWfGZz/72di4ceOOb9cjIj75yU/GQQcdFKeccspO7/GLv/iLccEFF8TSpUvjoYceisMOOyzOOuusHf9/wYIFcdRRR8V3v/vd+OhHP7rTa73yla+M6667LjZt2rTjG+pnnnmmJ/xmkIwbNy5GR0ebX//5i7/4i3jmmWd6ys4444y4/vrrY+vWrTtezH/4wx/G3//93/ccd/bZZ8d+++0XDz74YJx33nk/2QaIiMge40u7yD7OkiVL4vLLL4/zzz8/Fi9eHO9+97tj0aJF8dRTT8Xy5cvjyiuvjGOPPTZe+9rXxoIFC+Kd73xn/Mmf/En81E/9VJxzzjmxevXq+PCHPxyzZ8+OD3zgA33X4+1vf3v89//+32Pjxo1x6qmn7vhW+1k+8pGPxBe+8IV45StfGb/9278dkyZNik996lPxxS9+MT72sY/FxIkTd3r9Qw45JP7rf/2vsXTp0njkkUfiwgsvbEI8/vzP/zzOOeecOPvss+Otb31rHHbYYbF9+/ZYsWJF3HbbbTtebP/n//yfcd1118WrXvWq+O3f/u046KCD4hOf+EQ88cQTfbd/T5gwYUKcfvrp8Yd/+IcxZcqUmDt3btx8881x1VVXxSGHHNJz7MUXXxz/+I//GGeeeWZcfPHFceCBB8YVV1yxo+7P9sncuXPjd3/3d+Piiy+Ohx56KH72Z382Dj300Ni0aVN8+9vfjhe+8IXxv/7X/xp0U0VEZAwMWhR5HvBrv/ZrsWzZsli8eHH8wR/8QZx11lnx+te/Pv72b/82fumXfimuvPLKHcdefvnl8fu///tx/fXXx2te85q4+OKL46yzzopvfOMbGMNe5Y1vfGMceOCBsX79+uZb9oj/+Cb8G9/4RixYsCDe8573xOtf//q466674uqrr47f+I3fKN3jbW97W2zevDmefPLJRqaN+I9v0L/97W/HIYccEu9///vjZ37mZ+Ld7353/PM//3P8zM/8zI7jjj322Pjnf/7nmDBhQrzlLW+Jd77znXHcccfh780Pir/5m7+JV77ylfHBD34wfv7nfz6WLVsWN954Y/OPmeOPPz5uvPHGOPDAA+PNb35zvPOd74xFixbF+eefHxHRc/xFF10U//AP/xD3339/vOUtb4mzzz47PvjBD8aaNWvi9NNPH2j7ROT5wbPhMV36b29h3OjeVFsREemLs846K1avXh3333//c10VEXke8thjj8XEiRPjgQceiBe96EXPdXV28L3vfS+OPPLIePTRR2PChAnPdXV2iuExIiL7GBdccEGccMIJMXv27Ni+fXt86lOfihtvvDGuuuqq57pqIiLSJ760i4jsYzzzzDPx27/92zEyMhLjxo2LY445Jv76r/86fuVXfuW5rpqIPM/pWkhKl+qyKwyPEREREZGfKM+Gx6xcubJz4TFHHXXUXhEeo4gqIiIiItJxDI8RERERkYFgeEz/+E27iIiIiEjH8aVdRERERKTjlMNj5syZ05TRnxRymu2IiMcff7zn84EHHtgc8+STTzZlP/zhD5uy//Sf/lNTlrMeRvxH2u/MD37wg13Wldhvv7abvv/975eOy1CbqIyuRe2k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PEZEREREBoLhMf3jN+0iIiIiIh3Hl3YRERERkY5TDo8hIUdEREREpIrhMf3jN+0iIiIiIh3Hl3YRERERkY7jr8eIiIiIyEAwPKZ//KZdRERERKTj+NIuIiIiItJxDI8RERERkYFgeEz/+E27iIiIiEjH8aVdRERERKTjGB4jIiIiIgPB8Jj+8Zt2EREREZGO40u7iIiIiEjHMTxGRERERAaC4TH94zftIiIiIiIdx5d2EREREZGOY3iMiIiIiAwEw2P6x2/aRUREREQ6ji/tIiIiIiIdx/AYERERERkYe1NISpfwm3YRERERkY7jS7uIiIiISMcxPEZEREREBkLXQmO6Vp+d4TftIiIiIiIdx5d2EREREZGOY3iMiIiIiAyEroWjdK0+O8Nv2kVEREREOo4v7SIiIiIiHcfwGBEREREZCF0LR+lafXaG37SLiIiIiHQcX9pFRERERDqO4TEiIiIiMhC6Fo7StfrsDL9pFxERERHpOL60i4iIiIh0HMNjRERERGQgdC0cpWv12Rl+0y4iIiIi0nF8aRcRERER6TiGx4iIiIjIQOhaOErX6rMz/KZdRERERKTj+NIuIiIiItJxDI8RERERkYHQtXCUrtVnZ/hNu4iIiIhIx/GlXURERESk4xgeIyIiIiIDoWvhKF2rz87wm3YRERERkY7jS7uIiIiISMcxPEZEREREBkLXwlG6Vp+d4TftIiIiIiIdx5d2EREREZGOY3iMiIiIiAyEroWjdK0+O8Nv2kVEREREOo4v7SIiIiIiHcfwGBEREREZCF0LR+lafXaG37SLiIiIiHQcX9pFRERERDqO4TEiIiIiMhC6Fo7StfrsDL9pFxERERHpOL60i4iIiIh0HMNjRERERGQgdC0cpWv12Rl+0y4iIiIi0nF8aRcRERER6TiGx4iIiIjIQOhaOErX6rMz/KZdRERERKTj+NIuIiIiItJxDI8RERERkYHQtXCUrtVnZ/hNu4iIiIhIx/GlXURERESk4xgeIyIiIiIDoWvhKF2rz87wm3YRERERkY7jS7uIiIiISMcxPEZEREREBkLXwlG6Vp+d4TftIiIiIiIdx5d2EREREZGOY3iMiIiIiAyMvSkkpUv4TbuIiIiISMfxpV1EREREpOP40i4iIiIiP1H233//mDFjxnNdDWTGjBmx//77P9fV2CXjRg0sEhEREZGfMN///vfjySeffK6r0bD//vvH+PHjn+tq7BJf2kVEREREOo7hMSIiIiIiHceXdhERERGRjuNLu4iIiIhIx/GlXURERESk4/jSLiIiIiLScXxpFxERERHpOL60i4iIiIh0nP8PshuMwlaCS/UAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ker = ccf.random_kernel([1, 5, 5], zero_mean=True, distrib=\"gaussian\")\n", + "\n", + "conv = ccf.conv(mag, ker)\n", + "\n", + "plt.figure(figsize=(10, 10))\n", + "plt.imshow(conv.squeeze(), cmap='gray', interpolation='nearest')\n", + "plt.axis('off')\n", + "plt.title('Convolved image')\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 269, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAt8AAAMWCAYAAAApmuoMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAACv6UlEQVR4nO39abhlZ13n/39L6MypSs1DUlWZKkOlEhIgYWqQGWMHpbUVEGXwammBthtpFGl+CO0Aik+cuoOoV4KK4gimERlEARUhCVPIVEml5tSYSqWqEoFAOP8H/U9dnu96H+pT58DKCXm/rosH52btNdz3vdbe2XV/9nfOxMTEREmSJEn6tvuuh/oEJEmSpEcKP3xLkiRJI/HDtyRJkjQSP3xLkiRJI/HDtyRJkjQSP3xLkiRJI/HDtyRJkjQSP3xLkiRJI3n0Q30CkiRJ+s73la98pe6///6H+jQGjjnmmDruuONGO54fviVJkvRt9ZWvfKXOOOOM2rVr10N9KgPLli2rTZs2jfYB3A/fkiRJ+ra6//77a9euXbVt27aaO3fuQ306hx08eLBWrlxZ999/vx++JUmS9J1l7ty5s+rD90PBD9+SJEkaxcTERE1MTDzUp3HYQ3Eu/tqJJEmSNBI/fEuSJEkjcdmJJEmSRuGyE7/5liRJkkbjh29JkiRpJC47kSRJ0ihcduI335IkSdJo/PAtSZIkjcRlJ5IkSRqFy0785luSJEkajR++JUmSpJG47ESSJEmjcNmJ33xLkiRJo/HDtyRJkjQSl51IkiRpFC478ZtvSZIkaTR++JYkSZJG4rITSZIkjcJlJ37zLUmSJI3GD9+SJEnSSFx2IkmSpFG47MRvviVJkqTR+OFbkiRJGonLTiRJkjQKl534zbckSZI0Gj98S5IkSSNx2YkkSZJG4bITv/mWJEmSRuOHb0mSJGkkLjuRJEnSKFx24jffkiRJ0mj88C1JkiSNxGUnkiRJGoXLTvzmW5IkSRqNH74lSZKkkbjsRJIkSaNw2YnffEuSJEmj8cO3JEmSNBKXnUiSJGkULjvxm29JkiRpNH74liRJkkbishNJkiSNwmUnfvMtSZIkjcYP35IkSdJIXHYiSZKkUbjsxG++JUmSpNH44VuSJEkaictOJEmSNAqXnfjNtyRJkjQaP3xLkiRJI3HZiSRJkkbhshO/+ZYeMW644YZ6xSteUWeccUYdd9xxddJJJ9VjH/vYesc73lF33333Q316sc2bN9ecOXPq6quvHv3YV199dc2ZM6c2b978Tbd761vfWnPmzBnnpCRJDyt+8y09Avzu7/5uvfrVr65zzz23fuZnfqbWrl1bX/va1+r666+vd77znfUv//Iv9b73ve+hPs3vGP/5P//n+p7v+Z6H+jQkSbOQH76l73D/8i//Uq961avqOc95Tr3//e+vY4899vD/95znPKf+x//4H/WhD33oITzD7zynnXZanXbaaQ/1aUjSrDSblp08FFx2In2He9vb3lZz5sypd73rXZM+eD/omGOOqe/7vu87/Pc3vvGNesc73lHnnXdeHXvssbVkyZJ66UtfWtu3b5/0uqc//em1bt26uu666+qpT31qnXDCCXXmmWfWr/zKr9Q3vvGNqqrau3dvHXPMMfXmN795cNxbb7215syZU7/5m795uO3GG2+s7//+76/58+fXcccdVxdffHG9+93v/qbX9/73v7/mzJlTH/vYxwb/35VXXllz5sypG2644XDb9ddfX9/3fd9XCxYsqOOOO64uueSS+rM/+7PBaz/96U/XU57ylDruuONqxYoV9cY3vrG+9rWvfdNzeRAtOzn99NPriiuuqA984AN1ySWX1PHHH1/nn39+feADH6iq/7ek5fzzz68TTzyxLrvssrr++usnvf7666+vF73oRXX66afX8ccfX6effnq9+MUvri1btgyO/0//9E/1pCc9qY477rg69dRT681vfnP93u/9Hi6Z+dM//dN60pOeVCeeeGKddNJJ9bznPa8+//nPR9cpSTp6fviWvoM98MAD9fd///f1uMc9rlauXBm95lWvelW94Q1vqOc85zl1zTXX1C/+4i/Whz70oXryk59cd91116Rtd+3aVS95yUvqR3/0R+uaa66pyy+/vN74xjfWH/3RH1VV1eLFi+uKK66od7/73Yc/kD/oqquuqmOOOaZe8pKXVFXV+vXr68lPfnLddNNN9Zu/+Zv1V3/1V7V27dp6+ctfXu94xzumPN8rrriilixZUlddddXg/7v66qvrsY99bF100UVVVfUP//AP9ZSnPKXuueeeeuc731l//dd/XRdffHG98IUvnLSG/Oabb65nPetZdc8999TVV19d73znO+vzn/98/dIv/VLUh1P54he/WG984xvrDW94Q/3VX/1VzZs3r37gB36g3vKWt9Tv/d7v1dve9rZ6z3veUwcOHKgrrriivvzlLx9+7ebNm+vcc8+tX//1X68Pf/jD9au/+qu1c+fOuvTSSyeNyw033FDPec5z6l//9V/r3e9+d73zne+sz33uc/XLv/zLg/N529veVi9+8Ytr7dq19Wd/9mf1h3/4h3Xo0KF66lOfWjfffPOMrlWSNIUJSd+xdu3aNVFVEy960Yui7W+55ZaJqpp49atfPan9M5/5zERVTfzP//k/D7d993d/90RVTXzmM5+ZtO3atWsnnve85x3++5prrpmoqomPfOQjh9u+/vWvT6xYsWLiB3/wBw+3vehFL5o49thjJ7Zu3Tppf5dffvnECSecMHHPPfdMTExMTGzatGmiqiauuuqqw9u87nWvmzj++OMPbzMxMTFx8803T1TVxG/91m8dbjvvvPMmLrnkkomvfe1rk45xxRVXTCxfvnzigQcemJiYmJh44QtfOHH88cdP7Nq1a9I5n3feeRNVNbFp0ybuwP+/t7zlLRP98bp69eqJ448/fmL79u2H277whS9MVNXE8uXLJ+67777D7e9///snqmrimmuumfIYX//61yfuvffeiRNPPHHiN37jNw63/9AP/dDEiSeeOLF3797DbQ888MDE2rVrJ5371q1bJx796EdP/NRP/dSk/R46dGhi2bJlEz/8wz/8Ta9Rko7GgQMHJqpqYuPGjRN79+6dNf/buHHjRFVNHDhwYLS+8JtvSYf9wz/8Q1VVvfzlL5/Uftlll9X5558/WNqxbNmyuuyyyya1XXTRRZOWQlx++eW1bNmySd9Mf/jDH64dO3bUj//4jx9u+/u///t61rOeNfiG/uUvf3n967/+a/3Lv/zLlOf94z/+4/XlL3+5/vRP//Rw21VXXVXHHnts/ciP/EhVVW3YsKFuvfXWw9+0f/3rXz/8v+/93u+tnTt31vr16w/3w7Oe9axaunTp4f096lGPqhe+8IVTnkPi4osvrlNPPfXw3+eff35V/b8lPCeccMKg/d/247333ltveMMb6uyzz65HP/rR9ehHP7pOOumkuu++++qWW245vN0nPvGJeuYzn1mLFi063PZd3/Vd9cM//MOTzuXDH/5wff3rX6+XvvSlk/riuOOOq+/+7u+uj3/84zO6VkkSM3ApfQdbtGhRnXDCCbVp06Zo+3379lVV1fLlywf/34oVKwbrixcuXDjY7thjj520XOLRj350/diP/Vj91m/9Vt1zzz11yimn1NVXX13Lly+v5z3veZOOPdVx/+25kQsuuKAuvfTSuuqqq+qVr3xlPfDAA/VHf/RH9f3f//21YMGCqqravXt3VVW9/vWvr9e//vW4nweXb+zbt6+WLVs2+P+p7Wg8eC4POuaYY75p+1e+8pXDbT/yIz9SH/vYx+rNb35zXXrppTV37tyaM2dOfe/3fu+k/t63b9+k/2h4UG97sD8uvfRSPNfv+i6/m5Gkbwc/fEvfwR71qEfVs571rPrbv/3b2r59+xF/gePBD9M7d+4cbLtjx45J36YejVe84hX1a7/2a/Xe9763XvjCF9Y111xTr33ta+tRj3rUpGPv3Llz8NodO3ZUVR3x2K94xSvq1a9+dd1yyy21cePG2rlzZ73iFa84/P8/+Po3vvGN9QM/8AO4j3PPPffwuezatWvw/1PbGA4cOFAf+MAH6i1veUv93M/93OH2r371q4PfaF+4cOHhD9b/Vj/3B/vjL/7iL2r16tXfhrOWpKEJi+z44Vv6TvfGN76xPvjBD9ZP/MRP1F//9V8f/lb1QV/72tfqQx/6UD3/+c+vZz7zmVVV9Ud/9EeTvhG97rrr6pZbbqk3velN0zqH888/v57whCfUVVddVQ888EB99atfnfTBuKrqWc96Vr3vfe+rHTt2HP62u6rqD/7gD+qEE06oJz7xid/0GC9+8Yvrda97XV199dW1cePGOvXUU+u5z33u4f//3HPPrTVr1tQXv/jFetvb3vZN9/WMZzyjrrnmmtq9e/fhb4wfeOCBSctaxjRnzpyamJgY/FrN7/3e79UDDzwwqe27v/u764Mf/GDdddddhz9gf+Mb36g///M/n7Td8573vHr0ox9dd9xxR/3gD/7gt/cCJEmH+eFb+g73pCc9qa688sp69atfXY973OPqVa96VV1wwQX1ta99rT7/+c/Xu971rlq3bl09//nPr3PPPbde+cpX1m/91m/Vd33Xd9Xll19emzdvrje/+c21cuXK+umf/ulpn8eP//iP13/5L/+lduzYUU9+8pMPf8v8oLe85S31gQ98oJ7xjGfUz//8z9eCBQvqPe95T/3N3/xNveMd76h58+Z90/2fcsop9R//43+sq6++uu655556/etfP1g68Tu/8zt1+eWX1/Oe97x6+ctfXqeeemrdfffddcstt9TnPve5wx9Q/7//7/+ra665pp75zGfWz//8z9cJJ5xQ//t//++67777pn39MzF37tx62tOeVr/2a79WixYtqtNPP70+8YlP1O///u/XKaecMmnbN73pTfV//+//rWc961n1pje9qY4//vh65zvfefjcH+yT008/vX7hF36h3vSmN9XGjRvre77ne2r+/Pm1e/fuuvbaa+vEE0+s//W//tfYlypJ3/Fc1Cc9AvzET/xEXX/99fW4xz2ufvVXf7We+9zn1gte8IL6kz/5k/qRH/mRete73nV42yuvvLJ+5Vd+pT74wQ/WFVdcUW9605vquc99bn3qU5/CNd6pF73oRXX88cfX9u3bB996V/2/b6Y/9alP1bnnnluvec1r6gUveEHdeOONddVVV9XP/MzPRMd4xSteUXv27Kn7779/EBqt+n/faF977bV1yimn1Gtf+9p69rOfXa961avq7/7u7+rZz3724e3WrVtXf/d3f1dz586tl73sZfXKV76yLrroIvy98rH88R//cT3jGc+on/3Zn60f+IEfqOuvv74++tGPDv6j5DGPeUx99KMfreOPP75e+tKX1itf+cq64IIL6tWvfnVV1aTt3/jGN9Zf/MVf1G233VYve9nL6nnPe1797M/+bG3ZsqWe9rSnjXp9kh4ZHlx2Mpv+N7Y5E7Np4Y0k6dviuc99bm3evLluu+22h/pUJD0CHTx4sObNm1cbNmyok08++aE+ncMOHTpUZ599dh04cKDmzp07yjFddiJJ32Fe97rX1SWXXFIrV66su+++u97znvfURz/60fr93//9h/rUJOkRzw/fkvQd5oEHHqif//mfr127dtWcOXNq7dq19Yd/+If1oz/6ow/1qUl6hPPXTlx2IkmSpG+zB5ed3H777bNu2cmaNWtGXXZi4FKSJEkaictOJEmSNAqXnfjNtyRJkjQaP3xLkiRJI4mXnTxYpvjfmjNnzqDtG9/4xhG3++pXvzrY5rjjjhu03X///dExe7nsqqqvf/3rg7ZHPepRk/7uZZlpmyr+Jwk6D9JfS/unPqNz+3f/7t9F50b91vfXK/9V1aB09VTncfzxxw/aaEyp7dGPnjzl6NrpmmiM6bU07l/72tcGbX2+0TFprGhfNBfotR2dK6Gxorbet1VV9957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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ker = ccf.random_kernel([1, 5, 5], allow_translations=True)\n", + "eps = ccf.random_field_gamma(mag.shape)\n", + "eps = ccf.conv(eps, ker)\n", + "\n", + "conv = mag * eps\n", + "\n", + "plt.figure(figsize=(10, 10))\n", + "plt.imshow(conv.squeeze(), cmap='gray', interpolation='nearest')\n", + "plt.axis('off')\n", + "plt.title('Convolved image')\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 276, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvgAAAMYCAYAAAC+EZPVAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAADJPElEQVR4nOz9edxeVXmoj9+pHAaHRKYkbyYSlEYIpWKYAjIZCYJQp7ZaW8eqR7H2oxxKD/q1etpTsdbTD7W1IJUSLVbtMQ6AiASZVIIQCGorokhCpjcDYxSrCL6/P/yR43Ov6yU3JNm8bK7r8+GPZ7Gfvddwr7V3nndd+x43MjIyEiIiIiIi0gt+4/GugIiIiIiIbDt8wBcRERER6RE+4IuIiIiI9Agf8EVEREREeoQP+CIiIiIiPcIHfBERERGRHuEDvoiIiIhIj9jh8a6AiIiIiPSfn/3sZ/HAAw883tVo2HHHHWPnnXd+vKuxTfEBX0RERES2Kz/72c9i1qxZsW7duse7Kg2TJ0+O5cuX9+oh3wd8EREREdmuPPDAA7Fu3bpYtWpVjB8//vGuzmY2bdoU06dPjwceeMAHfBERERGRR8v48ePH1AN+X/EBX0REREQ6YWRkJEZGRh7vamxmLNVlW+JbdEREREREeoQP+CIiIiIiPcItOiIiIiLSCW7R6QZ/wRcRERER6RE+4IuIiIiI9Agf8EVERESkEx7eojOW/ns0nH322XHAAQdsft3nvHnz4itf+cqox1911VUxbty45r/vf//7W9uVj4h78EVERERECkybNi0++MEPxrOf/eyIiPjEJz4RL3nJS2LZsmUxZ86cUb936623Drz/f88999yu9fQBX0RERESkwMknnzzw+a//+q/j7LPPjuuuu+4RH/AnTpwYz3zmM7dz7f4fbtERERERkU54vLfjjLZFZ9OmTQP//fznP99iWx566KH4zGc+E/fff3/MmzfvEY898MADY2hoKObPnx9XXnnlNunLR8IHfBERERF5UjN9+vSYMGHC5v/OPPPMUY/97ne/G09/+tNjp512ire+9a3xhS98Ifbbbz88dmhoKM4999xYtGhRfP7zn4/Zs2fH/Pnz45prrtleTYmIiHEjfX0BqIiIiIiMCTZt2hQTJkyIu+66a2Av+uPNpk2bYvfdd49Vq1YN1GunnXaKnXbaCb/zwAMPxMqVK+Pee++NRYsWxcc//vG4+uqrR33Iz5x88skxbty4uPDCC7dJGwj34IuIiIhIJ4zVRFcPvxWnwo477rhZsj3ooIPihhtuiL//+7+Pj33sY6XvH3bYYXHBBRc8tgoXcYuOiIiIiMhjZGRkpLRn/2GWLVsWQ0ND27FG/oIvIiIiIlLi3e9+d5xwwgkxffr0+PGPfxyf+cxn4qqrropLL700IiLOOOOMWLNmTXzyk5+MiIizzjorZs6cGXPmzIkHHnggLrjggli0aFEsWrRou9bTB3wRERER6YSxukWnyvr16+M1r3lNDA8Px4QJE+KAAw6ISy+9NI477riIiBgeHo6VK1duPv6BBx6I0047LdasWRO77LJLzJkzJ7785S/HiSeeuE3bkVGyFREREZHtysOS7caNG8ecZLvnnnvGfffdN6bqtbW4B19EREREpEe4RUdEREREOuGJvkXniYK/4IuIiIiI9Agf8EVEREREeoRbdERERESkE9yi0w3+gi8iIiIi0iN8wBcRERER6RFu0RERERGRTnCLTjf4C76IiIiISI/wAV9EREREpEe4RUdEREREOsEtOt3gL/giIiIiIj3CB3wRERERkR7hFh0RERER6QS36HSDv+CLiIiIiPQIH/BFRERERHqEW3REREREpBPcotMN/oIvIiIiItIjfMAXEREREekRbtERERERkU5wi043+Au+iIiIiEiP8AFfRERERKRHuEVHRERERDrBLTrd4C/4IiIiIiI9wgd8EREREZEe4RYdEREREekEt+h0g7/gi4iIiIj0CB/wRURERER6hFt0RERERKQT3KLTDf6CLyIiIiLSI3zAFxERERHpEW7REREREZFOcItON/gLvoiIiIhIj/ABX0RERESkR7hFR0REREQ6wS063eAv+CIiIiIiPcIHfJEnCd/5znfiDW94Q8yaNSt23nnnePrTnx7Pe97z4kMf+lDcfffdj3f1yqxYsSLGjRsXCxcu7PzaCxcujHHjxsWKFSse8bj3v//9MW7cuG4qJSIiknCLjsiTgH/+53+OU045JWbPnh1/9md/Fvvtt1/84he/iKVLl8Y555wTS5YsiS984QuPdzV7w5ve9KZ40Yte9HhXQ0RkTNLXbTFjCR/wRXrOkiVL4m1ve1scd9xx8cUvfjF22mmnzf/vuOOOi//xP/5HXHrppY9jDfvHtGnTYtq0aY93NURE5EmKW3REes4HPvCBGDduXJx77rkDD/cPs+OOO8bv/M7vbP78y1/+Mj70oQ/Fc57znNhpp51i4sSJ8drXvjZWr1498L1jjjkm9t9//7jhhhviyCOPjKc+9amx9957xwc/+MH45S9/GRERGzdujB133DHe+973Ntf9/ve/H+PGjYuPfOQjm8v+4z/+I17ykpfErrvuGjvvvHM897nPjU984hOP2L4vfvGLMW7cuPja177W/L+zzz47xo0bF9/5znc2ly1dujR+53d+J3bbbbfYeeed48ADD4x///d/b7573XXXxRFHHBE777xzTJkyJc4444z4xS9+8Yh1eRjaojNz5sw46aST4uKLL44DDzwwdtlll9h3333j4osvjohfbf/Zd99942lPe1occsghsXTp0oHvL126NF71qlfFzJkzY5dddomZM2fGH/zBH8Qdd9zRXP8b3/hGzJs3L3beeeeYOnVqvPe9742Pf/zjuL3os5/9bMybNy+e9rSnxdOf/vQ4/vjjY9myZaV2iojI2MQHfJEe89BDD8UVV1wRc+fOjenTp5e+87a3vS3+/M//PI477ri48MIL46/+6q/i0ksvjcMPPzzuvPPOgWPXrVsXf/iHfxh/9Ed/FBdeeGGccMIJccYZZ8QFF1wQERF77rlnnHTSSfGJT3xi80P/w5x//vmx4447xh/+4R9GRMStt94ahx9+ePznf/5nfOQjH4nPf/7zsd9++8XrX//6+NCHPjRqfU866aSYOHFinH/++c3/W7hwYTzvec+LAw44ICIirrzyyjjiiCPi3nvvjXPOOSe+9KUvxXOf+9x45StfObCn/3vf+17Mnz8/7r333li4cGGcc845sWzZsvjf//t/l/pwNL797W/HGWecEX/+538en//852PChAnx8pe/PN73vvfFxz/+8fjABz4Qn/rUp+K+++6Lk046Kf7rv/5r83dXrFgRs2fPjrPOOiu++tWvxt/8zd/E8PBwHHzwwQPj8p3vfCeOO+64+OlPfxqf+MQn4pxzzombbrop/vqv/7qpzwc+8IH4gz/4g9hvv/3i3//93+Nf//Vf48c//nEceeSR8b3vfW+r2ioiQjz8Fp2x9F8vGRGR3rJu3bqRiBh51ateVTr+lltuGYmIkVNOOWWg/Fvf+tZIRIy8+93v3lx29NFHj0TEyLe+9a2BY/fbb7+R448/fvPnCy+8cCQiRi677LLNZQ8++ODIlClTRl7xildsLnvVq141stNOO42sXLly4HwnnHDCyFOf+tSRe++9d2RkZGRk+fLlIxExcv75528+5tRTTx3ZZZddNh8zMjIy8r3vfW8kIkb+4R/+YXPZc57znJEDDzxw5Be/+MXANU466aSRoaGhkYceemhkZGRk5JWvfOXILrvsMrJu3bqBOj/nOc8ZiYiR5cuXcwf+/3nf+943kpfXvfbaa2SXXXYZWb169eaym2++eSQiRoaGhkbuv//+zeVf/OIXRyJi5MILLxz1Gg8++ODIT37yk5GnPe1pI3//93+/ufz3fu/3Rp72tKeNbNy4cXPZQw89NLLffvsN1H3lypUjO+yww8g73vGOgfP++Mc/Hpk8efLI7//+7z9iG0VEHg333XffSESM3H777SMbN24cM//dfvvtIxExct999z3eXbRN8Rd8EdnMlVdeGRERr3/96wfKDznkkNh3332bbTCTJ0+OQw45ZKDsgAMOGNg2csIJJ8TkyZMHfmH/6le/GmvXro03vvGNm8uuuOKKmD9/fvOXhte//vXx05/+NJYsWTJqvd/4xjfGf/3Xf8VnP/vZzWXnn39+7LTTTvHqV786IiJuu+22+P73v7/5LwYPPvjg5v9OPPHEGB4ejltvvXVzP8yfPz8mTZq0+XxPecpT4pWvfOWodajw3Oc+N6ZOnbr587777hsRv9ru9NSnPrUp//V+/MlPfhJ//ud/Hs9+9rNjhx12iB122CGe/vSnx/333x+33HLL5uOuvvrqeMELXhB77LHH5rLf+I3fiN///d8fqMtXv/rVePDBB+O1r33tQF/svPPOcfTRR8dVV121VW0VEZHHDyVbkR6zxx57xFOf+tRYvnx56fi77rorIiKGhoaa/zdlypRmv/fuu+/eHLfTTjsNbC3ZYYcd4jWveU38wz/8Q9x7773xzGc+MxYuXBhDQ0Nx/PHHD1x7tOv+et2IOXPmxMEHHxznn39+vOUtb4mHHnooLrjggnjJS14Su+22W0RErF+/PiIiTjvttDjttNPwPA9vdbnrrrti8uTJzf+nskfDw3V5mB133PERy3/2s59tLnv1q18dX/va1+K9731vHHzwwTF+/PgYN25cnHjiiQP9fddddw38w+RhctnD/XHwwQdjXX/jN/z9R0S2PSNjbFvMWKrLtsQHfJEe85SnPCXmz58fX/nKV2L16tVbfLPLww/sw8PDzbFr164d+FX40fCGN7wh/vZv/zY+85nPxCtf+cq48MIL453vfGc85SlPGbj28PBw8921a9dGRGzx2m94wxvilFNOiVtuuSVuv/32GB4ejje84Q2b///D3z/jjDPi5S9/OZ5j9uzZm+uybt265v9TWRfcd999cfHFF8f73ve++J//839uLv/5z3/e5DDYfffdNz+8/zq57g/3x+c+97nYa6+9tkOtRUTk8cIHfJGec8YZZ8Qll1wSb37zm+NLX/rS5l+HH+YXv/hFXHrppXHyySfHC17wgoiIuOCCCwZ+2b3hhhvilltuife85z2PqQ777rtvHHrooXH++efHQw89FD//+c8HHr4jIubPnx9f+MIXYu3atZt/tY+I+OQnPxlPfepT47DDDnvEa/zBH/xBnHrqqbFw4cK4/fbbY+rUqbFgwYLN/3/27Nmxzz77xLe//e34wAc+8IjnOvbYY+PCCy+M9evXb/7l+6GHHhrYAtQl48aNi5GRkeYtSB//+MfjoYceGig7+uij45JLLok777xz80P8L3/5y/i///f/Dhx3/PHHxw477BA/+tGP4hWveMX2bYCIiHSKD/giPWfevHlx9tlnxymnnBJz586Nt73tbTFnzpz4xS9+EcuWLYtzzz039t9//zj55JNj9uzZ8Za3vCX+4R/+IX7jN34jTjjhhFixYkW8973vjenTp8e73vWux1yPN77xjfHf//t/j7Vr18bhhx+++dfyh3nf+94XF198cRx77LHxF3/xF7HbbrvFpz71qfjyl78cH/rQh2LChAmPeP5nPvOZ8bKXvSwWLlwY9957b5x22mnNNpOPfexjccIJJ8Txxx8fr3/962Pq1Klx9913xy233BI33XTT5ofg/+//+//iwgsvjBe84AXxF3/xF/HUpz41PvrRj8b999//mNu/NYwfPz6OOuqo+Nu//dvYY489YubMmXH11VfHeeedF8985jMHjn3Pe94TF110UcyfPz/e8573xC677BLnnHPO5ro/3CczZ86Mv/zLv4z3vOc9cfvtt8eLXvSi2HXXXWP9+vVx/fXXx9Oe9rT4X//rf3XdVBHpOW7R6QY3WYo8CXjzm98cS5cujblz58bf/M3fxIIFC+KlL31pfPrTn45Xv/rV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ThhrW2LGWn22Ff6CLyIiIiLSI3zAFxERERHpEW7REREREZFOGGtbYsZafbYV/oIvIiIiItIjfMAXEREREekRbtERERERkU4Ya1tixlp9thX+gi8iIiIi0iN8wBcRERER6RFu0RERERGRThhrW2LGWn22Ff6CLyIiIiLSI3zAFxERERHpEW7REREREZFOGGtbYsZafbYV/oIvIiIiItIjfMAXEREREekRbtERERERkU4Ya1tixlp9thX+gi8iIiIi0iN8wBcRERER6RFu0RERERGRThhrW2LGWn22Ff6CLyIiIiLSI3zAFxERERHpEW7REREREZFOGGtbYsZafbYV/oIvIiIiItIjfMAXEREREekRbtERERERkU4Ya1tixlp9thX+gi8iIiIi0iN8wBcRERER6RFu0RERERGRThhrW2LGWn22Ff6CLyIiIiLSI3zAFxERERHpEW7REREREZFOGGtbYsZafbYV/oIvIiIiItIjfMAXEREREekRbtERERERkU4Ya1tixlp9thX+gi8iIiIi0iN8wBcRERER6RE+4IuIiIhIJ4yMjIy5/7YX99xzT7zmNa+JCRMmxIQJE+I1r3lN3HvvvY/4nde//vUxbty4gf8OO+ywR31t9+CLiIiIiGxjXv3qV8fq1avj0ksvjYiIt7zlLfGa17wmLrrookf83ote9KI4//zzN3/ecccdH/W1fcAXEREREdmG3HLLLXHppZfGddddF4ceemhERPzzP/9zzJs3L2699daYPXv2qN/daaedYvLkyVt1fbfoiIiIiEgnPN7bcUbborNp06aB/37+859vVTuXLFkSEyZM2PxwHxFx2GGHxYQJE+Laa699xO9eddVVMXHixPjN3/zNePOb3xwbNmx41Nf3AV9EREREntRMnz598175CRMmxJlnnrlV51u3bl1MnDixKZ84cWKsW7du1O+dcMIJ8alPfSquuOKK+D//5//EDTfcEC94wQse9T843KIjIiIiIk9qVq1aFePHj9/8eaeddsLj3v/+98f/r7075jUkCsM4/ohkoqGahKlGJSSoRMIHmCiI0NCpdEpfwGcRtUqvm0FHJRqJSkQxiUI0ttqbFbsuu/fuvZn8f9XJnDPzvuVTvDkzGAwefmuxWEiSQqHQ3d71ev3t859ardbbOpvNqlAoyLZtTSYTNZvNh3V/RcAHAADAf/Mdfy4Vi8VuAv6f9Ho9tdvth2eSyaSWy6X2+/3d3uFwUDwef7ovy7Jk27Y2m83T70gEfAAAAOAppmnKNM13z5VKJfm+r/l8rmKxKEmazWbyfV/lcvnpesfjUbvdTpZlvdQnM/gAAADAB8pkMqpUKup2u/I8T57nqdvtqlqt3tygk06nNR6PJUmn00n9fl+u62q73Wo6napWq8k0TTUajZfqE/ABAADwqQzD+OerHz9LIpH4q7vm3zMajZTL5eQ4jhzHUT6f13A4vDmzXq/l+74kKRwOa7VaqV6vK5VKqdPpKJVKyXVdRaPRl2qHrt9xEAoAAACBcj6fdblcvrqNO4ZhKBKJfHUbH4qADwAAAAQIIzoAAABAgBDwAQAAgAAh4AMAAAABQsAHAAAAAoSADwAAAAQIAR8AAAAIEAI+AAAAECAEfAAAACBACPgAAABAgBDwAQAAgAD5AZgMO07TwPmLAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ker = ccf.random_kernel([1, 5, 5], allow_translations=True, distrib=\"gaussian\")\n", + "eps = ccf.random_field_gaussian(mag.shape) * 0.5 * torch.rand([])\n", + "eps = ccf.conv(eps, ker)\n", + "\n", + "conv = mag + eps\n", + "\n", + "plt.figure(figsize=(10, 10))\n", + "plt.imshow(conv.squeeze(), cmap='gray', interpolation='nearest')\n", + "plt.axis('off')\n", + "plt.title('Convolved image')\n", + "plt.colorbar()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "pytorch2", + "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.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 89c886d9a43c9924484cefd2a79f9558ae02cb53 Mon Sep 17 00:00:00 2001 From: balbasty Date: Wed, 16 Apr 2025 11:26:58 +0100 Subject: [PATCH 6/6] WIP - spline interpolation --- cornucopia/functional/geometric.py | 2 +- cornucopia/functional/intensity.py | 139 +------ cornucopia/functional/psf.py | 35 +- cornucopia/functional/spline.py | 581 +++++++++++++++++++++++++++++ setup.cfg | 1 + 5 files changed, 621 insertions(+), 137 deletions(-) create mode 100644 cornucopia/functional/spline.py diff --git a/cornucopia/functional/geometric.py b/cornucopia/functional/geometric.py index 00b8a88..a995a6e 100644 --- a/cornucopia/functional/geometric.py +++ b/cornucopia/functional/geometric.py @@ -23,7 +23,7 @@ from ..utils.py import ensure_list, make_vector from ._utils import Tensor, Output, OneOrMore, Value, _backend_float from .random import random_field_like -from .intensity import spline_upsample_like +from .spline import spline_upsample_like def exp_velocity( diff --git a/cornucopia/functional/intensity.py b/cornucopia/functional/intensity.py index 3008b1b..ed1a7db 100644 --- a/cornucopia/functional/intensity.py +++ b/cornucopia/functional/intensity.py @@ -10,7 +10,6 @@ "sub_field", "mul_field", "div_field", - "spline_upsample", "spline_upsample_like", "gamma_transform", "z_transform", @@ -21,15 +20,15 @@ "mul_smooth_random_field", ] # stdlib -from typing import Sequence, Optional, Callable +from typing import Optional, Callable # external import torch -import interpol -import torch.nn.functional as F + +from cornucopia.functional.spline import spline_upsample_like # internal -from ..baseutils import prepare_output, returns_update, return_requires +from ..baseutils import prepare_output, returns_update from ..utils.smart_math import add_, mul_, pow_, div_ from ..utils.py import ensure_list from ._utils import _unsqz_spatial, Tensor, Value, Output, OneOrMore @@ -536,136 +535,6 @@ def clip_value( )() -def spline_upsample( - input: Tensor, - shape: Sequence[int], - order: int = 3, - prefilter: bool = True, - copy: bool = True, - **kwargs -) -> Output: - """ - Upsample a field of spline coefficients. - - Parameters - ---------- - input : (C, *spatial) tensor - Input spline coefficients (or values if `prefilter=True`) - shape : list[int] - Target spatial shape - order : int - Spline order - prefilter : bool - If `False`, assume that the input contains spline coefficients, - and returns the interpolated field. - If `True`, assume that the input contains low-resolution values - and convert them first to spline coefficients (= "prefilter"), - before computing the interpolated field. - copy : bool - In cases where the output matches the input (the input and target - shapes are identical, and no prefilter is required), the input - tensor is returned when `copy=False`, and a copy is made when - `copy=True`. - - Other Parameters - ---------------- - returns : [list or dict of] {"output", "input", "coeff"} - Structure of variables to return. Default: "output". - - Returns - ------- - output : (C, *shape) tensor - Output tensor. - """ - returns = kwargs.pop("returns", "output") - - ndim = input.ndim - 1 - coeff = input - - same_shape = (tuple(shape) == input.shape[1:]) - nothing_to_do = same_shape and (prefilter or order <= 1) - need_prefilter = prefilter and (order > 1) - - # 1) Nothing to do - if nothing_to_do: - output = input.clone() if copy else input - if need_prefilter and ("coeff" in return_requires(returns)): - coeff = interpol.spline_coeff_nd(input, order, dim=ndim) - - # 2) Use torch.inteprolate (faster) - elif order == 1: - mode = ("trilinear" if len(shape) == 3 else - "bilinear" if len(shape) == 2 else - "linear") - output = F.interpolate( - input[None], shape, mode=mode, align_corners=True - )[0] - - # 3) Use interpol - else: - if prefilter: - coeff = interpol.spline_coeff_nd(input, order, dim=ndim) - output = interpol.resize( - coeff, shape=shape, interpolation=order, prefilter=False - ) - - return prepare_output( - {"input": input, "output": output, "coeff": coeff}, - returns - )() - - -def spline_upsample_like( - input: Tensor, - like: Tensor, - order: int = 3, - prefilter: bool = True, - copy: bool = True, - **kwargs -) -> Output: - """ - Upsample a field of spline coefficients. - - Parameters - ---------- - input : (C, *spatial) tensor - Input spline coefficients (or values if `prefilter=True`) - like : (C, *shape) tensor - Target tensor. - order : int - Spline order - prefilter : bool - If `False`, assume that the input contains spline coefficients, - and returns the interpolated field. - If `True`, assume that the input contains low-resolution values - and convert them first to spline coefficients (= "prefilter"), - before computing the interpolated field. - copy : bool - In cases where the output matches the input (the input and target - shapes are identical, and no prefilter is required), the input - tensor is returned when `copy=False`, and a copy is made when - `copy=True`. - - Other Parameters - ---------------- - returns : [list or dict of] {"output", "input", "coeff", "like"} - Structure of variables to return. Default: "output". - - Returns - ------- - output : (C, *shape) tensor - Output tensor. - - """ - kwargs.setdefault("returns", "output") - kwargs.setdefault("order", order) - kwargs.setdefault("prefilter", prefilter) - kwargs.setdefault("copy", copy) - output = spline_upsample(input, like.shape[1:], **kwargs) - output = returns_update(like, "like", output, kwargs["returns"]) - return output() - - def gamma_transform( input: Tensor, gamma: Value = 1, diff --git a/cornucopia/functional/psf.py b/cornucopia/functional/psf.py index 625faad..99d4299 100644 --- a/cornucopia/functional/psf.py +++ b/cornucopia/functional/psf.py @@ -11,10 +11,11 @@ from ..baseutils import prepare_output from ..utils.warps import identity from ..utils.conv import smoothnd, convnd -from ..utils.py import ensure_list +from ..utils.py import ensure_list, make_vector, ensure_list from ..utils import smart_math as math from ._utils import Tensor, Value, Output, OneOrMore, _axis_name2index from .random import random_field +from .spline import spline_upsample, spline_upsample_like def smooth( @@ -354,3 +355,35 @@ def random_kernel( output = math.div_(output, norm) return prepare_output({"output": output}, returns)() + + + +def lowres( + input: Tensor, + resolution: OneOrMore[float] = 1, + function: Optional[callable] = None, + **kwargs +) -> Output: + """ + Downsample, then upsample, an image. + + Parameters + ---------- + input : (C, *spatial) tensor + Input image + resolution : [list of] float + Lower resolution, in terms of input voxels. + function : callable, optional + A function to apply in the low-resolution domain. + For example, a function that adds noise. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "lowres"} + Tensors to return. + + Returns + ------- + output : (C, *spatial) tensor + Output image + """ diff --git a/cornucopia/functional/spline.py b/cornucopia/functional/spline.py new file mode 100644 index 0000000..c2d4259 --- /dev/null +++ b/cornucopia/functional/spline.py @@ -0,0 +1,581 @@ +__all__ = [ + "spline_upsample", + "spline_upsample_like", + "spline_sample", + "spline_sample_coord", +] + +# stdlib +from functools import partial +from typing import Sequence, Optional, Union + +# external +import bounds as torch_bounds +import interpol +import torch +import torch.nn.functional as F + +# internal +from ..baseutils import prepare_output, return_requires, returns_update +from ..utils.warps import apply_flow, sub_identity, add_identity +from ..utils.py import ensure_list, make_vector, meshgrid_ij +from ..utils.conv import smoothnd +from ._utils import Tensor, Output, OneOrMore + + +def spline_upsample( + input: Tensor, + factor: Optional[OneOrMore[float]] = None, + *, + shape: Optional[Sequence[int]] = None, + order: OneOrMore[int] = 3, + prefilter: bool = True, + copy: bool = True, + bound: OneOrMore[str] = "border", + align: OneOrMore[str] = "center", + recompute_factor: bool = True, + backend: Optional[str] = None, + **kwargs, +) -> Output: + """ + Upsample a field of spline coefficients. + + Parameters + ---------- + input : (C, *spatial) tensor + Input spline coefficients (or values if `prefilter=True`). + factor : [list of] float, optional + Upsampling factor. + + Other Parameters + ---------------- + shape : list[int], optional + Target spatial shape. Required if `factor=None`. + order : int + Spline order. + prefilter : bool + If `False`, assume that the input contains spline coefficients, + and returns the interpolated field. + If `True`, assume that the input contains low-resolution values + and convert them first to spline coefficients (= "prefilter"), + before computing the interpolated field. + copy : bool + In cases where the output matches the input (the input and target + shapes are identical, and no prefilter is required), the input + tensor is returned when `copy=False`, and a copy is made when + `copy=True`. + bound : [list of] str + Boundary condition used to interpolate/extrapolate out-of-bounds: + + * `{'zero', 'zeros'}` : All voxels outside of the FOV are zeros. + * `{'border', 'nearest', 'replicate'} : Use nearest border value. + * `{'mirror', 'dct1'}` : Reflect about the center of the border voxel. + Equivalent to + `grid_sample(..., padding_mode='reflection', align_corners=True)`. + * `{'reflect', 'dct2'}` : Reflect about the edge of the border voxel. + Equivalent to + `grid_sample(..., padding_mode='reflection', align_corners=False)`. + * `{'antimirror', 'dst1'}` : Negative reflection about the first + out-of-bound voxel. + * `{'antireflect', 'dst2'}` : Negative reflection about the edge of + the border voxel. + * `{'wrap', 'circular', 'dft'}` : Wrap the FOV. + * `{'sliding'}` : Can only be used if the input tensor is a flow field. + + For more details, see the [`torch-bounds` documentation]( + https://torch-bounds.readthedocs.io/en/latest/api/types/). + align : [list of] {"c[enter]", "e[dge]"} + Whether the centers or the edges of the corner voxels should be + aligned across resolutions. + recompute_factor : bool + Recompute the upsampling factor based on `align` and the effective + input and output shapes. If `True`, backpropagation through + `factor` is not possible. + backend : {'torch', 'interpol'}, optional + Backend to use. By default, the interpolation backend is used + automatically based on the options selected. If `order` is + in `{0, 1}`, and either `bound` is in `{'border', 'reflect'}` + or `align='center'`, the `'torch'` backend is used (faster). + Otherwise, the `'interpol'` backend is used (slower). + If `backend='interpol'`, the interpol backend is always used. + If `backend='torch'` and the chosen options are not supported + by torch, an error is raised. + returns : [list or dict of] {"output", "input", "coeff"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *shape) tensor + Output tensor. + """ + can_use_torch = True + returns = kwargs.pop("returns", "output") + bck = dict(dtype=input.dtype, device=input.device) + if not input.dtype.is_floating_point: + bck["dtype"] = torch.get_default_dtype() + + ndim = input.ndim - 1 + coeff = input + + # --- preprocess options and select backend ------------------------ + align_centers = align[:1].lower() != "c" + + bound = ensure_list(bound, ndim) + bound = list(map(torch_bounds.to_fourier, bound)) + if len(set(bound)) > 1: + can_use_torch = False + if ( + not align_centers and + any(x not in ("replicate", "reflect") for x in bound) + ): + can_use_torch = False + + order = ensure_list(order, ndim) + if any(x > 1 for x in order): + can_use_torch = False + + factor_is_one = False + if factor is not None: + factor = make_vector(factor, ndim, **bck) + factor_is_one = (factor == 1).all() + if factor.requires_grad: + can_use_torch = False + + nothing_to_do = ( + (factor is None or factor_is_one) and + (shape is None or (tuple(shape) == input.shape[1:])) and + (prefilter or order <= 1) + ) + need_prefilter = prefilter and (order > 1) + + if backend is None: + backend = 'torch' if can_use_torch else 'interpol' + if backend == 'torch' and not can_use_torch: + raise ValueError( + f'Cannot use torch interpolation backend with order={order}, ' + f'bound={bound}, align={align}.' + ) + + # --- Nothing to do ------------------------------------------------ + if nothing_to_do: + output = input.clone() if copy else input + if need_prefilter and ("coeff" in return_requires(returns)): + coeff = interpol.spline_coeff_nd(input, order, dim=ndim) + + # --- Use torch.interpolate (faster) ------------------------------- + elif backend == "torch": + mode = {3: "trilinear", 2: "bilinear", 1: "linear"}[len(shape)] + if factor is not None: + factor = factor.tolist() + output = F.interpolate( + input[None], shape, + scale_factor=factor, + mode=mode, + align_corners=align_centers, + recompute_scale_factor=recompute_factor, + )[0] + + # --- Reimplement interpol :( -------------------------------------- + elif not recompute_factor: + inshape = input.shape[1:] + if prefilter: + coeff = interpol.spline_coeff_nd(input, order, dim=ndim) + if shape is None: + if factor is None: + raise ValueError("One of factor or shape must be provided.") + shape = [int(i*f) for i, f in zip(inshape, factor)] + if factor is None: + if align_centers: + factor = [(x - 1) / (y - 1) for x, y in zip(inshape, shape)] + else: + factor = [x / y for x, y in zip(inshape, shape)] + lin = [] + for f, inshp, outshp in zip(factor, inshape, shape): + shift = ((inshp - 1) - (outshp - 1) / f) * 0.5 + lin.append(torch.arange(0., outshp[0]) / f + shift) + + grid = torch.stack(meshgrid_ij(*lin), dim=-1) + output = interpol.grid_pull( + coeff, grid, + bound=bound, + interpolation=order, + extrapolate=True, + prefilter=False, + ) + + # --- Use interpol ------------------------------------------------- + else: + if prefilter: + coeff = interpol.spline_coeff_nd(input, order, dim=ndim) + output = interpol.resize( + coeff, + factor=factor, + shape=shape, + interpolation=order, + prefilter=False, + anchor=align, + ) + + return prepare_output( + {"input": input, "output": output, "coeff": coeff}, + returns + )() + + +def spline_upsample_like( + input: Tensor, + like: Tensor, + *, + factor: Optional[OneOrMore[float]] = None, + order: int = 3, + prefilter: bool = True, + copy: bool = True, + bound: OneOrMore[str] = "border", + align: OneOrMore[str] = "center", + recompute_factor: bool = True, + backend: Optional[str] = None, + **kwargs +) -> Output: + """ + Upsample a field of spline coefficients. + + Parameters + ---------- + input : (C, *spatial) tensor + Input spline coefficients (or values if `prefilter=True`) + like : (C, *shape) tensor + Target tensor. + order : int + Spline order + prefilter : bool + If `False`, assume that the input contains spline coefficients, + and returns the interpolated field. + If `True`, assume that the input contains low-resolution values + and convert them first to spline coefficients (= "prefilter"), + before computing the interpolated field. + copy : bool + In cases where the output matches the input (the input and target + shapes are identical, and no prefilter is required), the input + tensor is returned when `copy=False`, and a copy is made when + `copy=True`. + bound : [list of] str + Boundary condition used to interpolate/extrapolate out-of-bounds: + + * `{'zero', 'zeros'}` : All voxels outside of the FOV are zeros. + * `{'border', 'nearest', 'replicate'} : Use nearest border value. + * `{'mirror', 'dct1'}` : Reflect about the center of the border voxel. + Equivalent to + `grid_sample(..., padding_mode='reflection', align_corners=True)`. + * `{'reflect', 'dct2'}` : Reflect about the edge of the border voxel. + Equivalent to + `grid_sample(..., padding_mode='reflection', align_corners=False)`. + * `{'antimirror', 'dst1'}` : Negative reflection about the first + out-of-bound voxel. + * `{'antireflect', 'dst2'}` : Negative reflection about the edge of + the border voxel. + * `{'wrap', 'circular', 'dft'}` : Wrap the FOV. + * `{'sliding'}` : Can only be used if the input tensor is a flow field. + + For more details, see the [`torch-bounds` documentation]( + https://torch-bounds.readthedocs.io/en/latest/api/types/). + align : [list of] {"c[enter]", "e[dge]"} + Whether the centers or the edges of the corner voxels should be + aligned across resolutions. + recompute_factor : bool + Recompute the upsampling factor based on `align` and the effective + input and output shapes. If `True`, backpropagation through + `factor` is not possible. + backend : {'torch', 'interpol'}, optional + Backend to use. By default, the interpolation backend is used + automatically based on the options selected. If `order` is + in `{0, 1}`, and either `bound` is in `{'border', 'reflect'}` + or `align='center'`, the `'torch'` backend is used (faster). + Otherwise, the `'interpol'` backend is used (slower). + If `backend='interpol'`, the interpol backend is always used. + If `backend='torch'` and the chosen options are not supported + by torch, an error is raised. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "coeff", "like"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *shape) tensor + Output tensor. + + """ + kwargs.setdefault("returns", "output") + kwargs.setdefault("order", order) + kwargs.setdefault("prefilter", prefilter) + kwargs.setdefault("copy", copy) + kwargs.setdefault("backend", backend) + kwargs.setdefault("bound", bound) + kwargs.setdefault("align", align) + kwargs.setdefault("recompute_factor", recompute_factor) + kwargs.setdefault("factor", factor) + output = spline_upsample(input, shape=like.shape[1:], **kwargs) + output = returns_update(like, "like", output, kwargs["returns"]) + return output() + + +def spline_downsample( + input: Tensor, + factor: OneOrMore[float] = 1, + antialiasing: Union[bool, OneOrMore[float]] = True, + bound: OneOrMore[str] = 'reflect', + order: OneOrMore[int] = 1, + shape: Optional[list[int]] = None, + align: OneOrMore[str] = 'edge', + **kwargs +) -> Output: + """ + Downsample an image by some factor. + """ + returns = kwargs.pop("returns", "output") + + ndim = input.ndim - 1 + factor = make_vector(factor, ndim) + bound = ensure_list(bound, ndim) + if not torch.is_tensor(antialiasing): + antialiasing = ensure_list(antialiasing, ndim) + antialiasing = [ + factor if x is True else + 0 if x is False else + x for x in antialiasing + ] + + if sum(antialiasing) > 0: + smoothed = smoothnd(input, fwhm=antialiasing, bound=bound) + else: + smoothed = input + + output = spline_upsample( + smoothed, + factor=factor, + shape=shape, + order=order, + prefilter=True, + bound=bound, + align=align, + recompute_factor=not factor.requires_grad, + ) + + return prepare_output( + {"input": input, "output": output, "smoothed": smoothed}, + returns + )() + + +def spline_sample( + input: Tensor, + flow: Tensor, + has_identity: bool = False, + order: OneOrMore[int] = 1, + bound: OneOrMore[str] = "border", + extrapolate: bool = True, + prefilter: bool = True, + backend: Optional[str] = None, + nearest_if_label: bool = False, + **kwargs, +) -> Output: + """ + Sample an image at locations encoded by a deformation field. + + Parameters + ---------- + input : (C, *ishape) tensor + Input tensor to sample. + flow : (D, *oshape) tensor + Displacement (or coordinate) field. + has_identity : bool + * If `True`, the `flow` field contains absolute voxel coordinates. + * If `False`, the `flow` field contains relative voxel coordinates + (_i.e._, it is a displacement field). + order : [list of] {0..7} + Spline order (per spatial dimension). + bound : [list of] str + Boundary condition used to interpolate/extrapolate out-of-bounds: + + * `{'zero', 'zeros'}` : All voxels outside of the FOV are zeros. + * `{'border', 'nearest', 'replicate'} : Use nearest border value. + * `{'mirror', 'dct1'}` : Reflect about the center of the border voxel. + Equivalent to + `grid_sample(..., padding_mode='reflection', align_corners=True)`. + * `{'reflect', 'dct2'}` : Reflect about the edge of the border voxel. + Equivalent to + `grid_sample(..., padding_mode='reflection', align_corners=False)`. + * `{'antimirror', 'dst1'}` : Negative reflection about the first + out-of-bound voxel. + * `{'antireflect', 'dst2'}` : Negative reflection about the edge of + the border voxel. + * `{'wrap', 'circular', 'dft'}` : Wrap the FOV. + * `{'sliding'}` : Can only be used if the input tensor is a flow field. + + For more details, see the [`torch-bounds` documentation]( + https://torch-bounds.readthedocs.io/en/latest/api/types/). + extrapolate : bool + Whether to use boundary condition to extrapolate out-of-bound samples. + If `False`, boundary conditions are only used to interpolate in-bound + samples. + prefilter : bool + Whether to apply a spline prefilter to convert the input values + into spline coefficients. This ensures that this function + exactly interpolates the input tensor. This is equivalent to + `scipy.ndimage.map_coordinates(..., prefilter=True)`. + This has no effect is `order` is zero or one. + backend : {'torch', 'interpol'}, optional + Backend to use. By default, the interpolation backend is used + automatically based on the options selected. If `order` is + in `{0, 1}`, `bound` is in `{'zero', 'border', 'mirror', 'reflect'}`, + and `extrapolate` is True, the `'torch'` backend is used (faster). + Otherwise, the `'interpol'` backend is used (slower). + If `backend='interpol'`, the interpol backend is always used. + If `backend='torch'` and the chosen options are not supported + by torch, an error is raised. + nearest_if_label : bool + By default, if a tensor has an integer data type, it is deformed + using label-specific resampling (each unique label is extracted + and resampled using linear interpolation, and an argmax output + label map is computed on the fly). + If `nearest_if_label=True`, the entire label map will be + resampled at once using nearest-neighbour interpolation. + + Other Parameters + ---------------- + returns : [list or dict of] {"output", "input", "coeff", "flow", "disp", "coord"} + Structure of variables to return. Default: "output". + + Returns + ------- + output : (C, *oshape) tensor + Output tensor. + """ # noqa: E501 + returns = kwargs.pop("returns", "output") + can_use_torch = True + + # --- preprocess options and select backend ------------------------ + + bound = ensure_list(bound) + bound = list(map(torch_bounds.to_fourier)) + if not len(set(bound)) != 1: + can_use_torch = False + if any(x in ('dst1', 'dst2', 'dft') for x in bound): + can_use_torch = False + + order = ensure_list(order) + if not len(set(order)) != 1: + can_use_torch = False + if any(x > 1 for x in order): + can_use_torch = False + + if not extrapolate and any(x != 'zero' for x in bound): + can_use_torch = False + + if backend is None: + backend = 'torch' if can_use_torch else 'interpol' + if backend == 'torch' and not can_use_torch: + raise ValueError( + f'Cannot use torch interpolation backend with order={order}, ' + f'bound={bound}, extrapolate={extrapolate}.' + ) + + disp = coord = None + + # --- torch backend ------------------------------------------------ + if backend == 'torch': + if has_identity: + coord = flow + disp = sub_identity(flow.movedim(0, -1)).movedim(-1, 0) + else: + disp = flow + if return_requires(returns, "coord"): + coord = add_identity(flow.movedim(0, -1)).movedim(-1, 0) + + bound, align = torch_bounds.to_torch(bound[0]) + order = 'nearest' if order == 0 else 'bilinear' + output = apply_flow( + input, disp.movedim(0, -1), + mode=order, + padding_mode=bound, + align_corners=align, + ) + + # --- interpol backend --------------------------------------------- + else: + if has_identity: + coord = flow + if return_requires(returns, "disp"): + disp = sub_identity(flow.movedim(0, -1)).movedim(-1, 0) + else: + disp = flow + coord = add_identity(flow.movedim(0, -1)).movedim(-1, 0) + + if bound == ["sliding"]: + + if len(input) != len(flow) or not input.dtype.is_floating_point: + raise ValueError( + "Sliding boundary condition is only supported for " + "flow fields." + ) + + output = input.new_zeros((len(input),) + flow.shape[1:]) + bound0 = ["dct2"] * len(flow) + for i, channel in enumerate(input): + bound = list(bound0) + bound[i] = "dst2" + output[i] = interpol.grid_pull( + channel[None], coord.movedim(0, -1), + interpolation=order, + bound=bound, + extrapolate=extrapolate, + prefilter=prefilter, + ).squeeze(0) + + if input.dtype.is_floating_point: + + output = interpol.grid_pull( + input, coord.movedim(0, -1), + interpolation=order, + bound=bound, + extrapolate=extrapolate, + prefilter=prefilter, + ) + + elif nearest_if_label: + + dtype = input.dtype + input = input.to(torch.get_default_dtype()) + output = interpol.grid_pull( + input, coord.movedim(0, -1), + interpolation='nearest', + bound=bound, + extrapolate=extrapolate, + prefilter=prefilter, + ).to(dtype) + + else: + + output = input.new_zeros((len(input),) + flow.shape[1:]) + prob = torch.zeros_like(output, dtype=flow.dtype) + for label in torch.unique(input): + prob1 = (input == label).to(torch.get_default_dtype()) + prob1 = interpol.grid_pull( + prob1, coord.movedim(0, -1), + interpolation=order, + bound=bound, + extrapolate=extrapolate, + prefilter=prefilter, + ) + output.masked_fill_(prob1 > prob, label) + prob.clamp_min_(prob1) + + return prepare_output( + {"input": input, "output": output, "flow": flow, "coord": coord, + "disp": disp}, + returns + )() + + +spline_sample_coord = partial(spline_sample, has_identity=True) diff --git a/setup.cfg b/setup.cfg index a9bae2f..e0ac6e3 100755 --- a/setup.cfg +++ b/setup.cfg @@ -25,6 +25,7 @@ install_requires = numpy nibabel torch-interpol >= 0.2.4 + torch-bounds torch-distmap [versioneer]