Completed
Push — main ( bd3202...72b597 )
by Yunguan
27s queued 13s
created

deepreg.loss.util.gaussian_kernel1d_sigma()   A

Complexity

Conditions 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 13
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
"""Provide helper functions or classes for defining loss or metrics."""
2
import math
3
4
import tensorflow as tf
5
6
7
class NegativeLossMixin(tf.keras.losses.Loss):
8
    """Mixin class to revert the sign of the loss value."""
9
10
    def __init__(self, **kwargs):
11
        """
12
        Init without required arguments.
13
14
        :param kwargs: additional arguments.
15
        """
16
        super().__init__(**kwargs)
17
        self.name = self.name + "Loss"
18
19
    def call(self, y_true: tf.Tensor, y_pred: tf.Tensor) -> tf.Tensor:
20
        """
21
        Revert the sign of loss.
22
23
        :param y_true: ground-truth tensor.
24
        :param y_pred: predicted tensor.
25
        :return: negated loss.
26
        """
27
        return -super().call(y_true=y_true, y_pred=y_pred)
28
29
30
EPS = tf.keras.backend.epsilon()
31
32
33
def rectangular_kernel1d(kernel_size: int) -> (tf.Tensor, tf.Tensor):
34
    """
35
    Return a the 1D filter for separable convolution equivalent to a 3-D rectangular
36
    kernel for LocalNormalizedCrossCorrelation.
37
38
    :param kernel_size: scalar, size of the 1-D kernel
39
    :return: kernel_weights, of shape (kernel_size, )
40
    """
41
42
    kernel = tf.ones(shape=(kernel_size,), dtype=tf.float32)
43
    return kernel
44
45
46
def triangular_kernel1d(kernel_size: int) -> (tf.Tensor, tf.Tensor):
47
    """
48
    1D triangular kernel.
49
50
    Assume kernel_size is odd, it will be a smoothed from
51
    a kernel which center part is zero.
52
    Then length of the ones will be around half kernel_size.
53
    The weight scale of the kernel does not matter as LNCC will normalize it.
54
55
    :param kernel_size: scalar, size of the 1-D kernel
56
    :return: kernel_weights, of shape (kernel_size, )
57
    """
58
    assert kernel_size >= 3
59
    assert kernel_size % 2 != 0
60
61
    padding = kernel_size // 2
62
63
    kernel = (
64
        [0] * math.ceil(padding / 2)
65
        + [1] * (kernel_size - padding)
66
        + [0] * math.floor(padding / 2)
67
    )
68
    kernel = tf.constant(kernel, dtype=tf.float32)
69
70
    # (padding*2, )
71
    filters = tf.ones(shape=(kernel_size - padding, 1, 1), dtype=tf.float32)
72
73
    # (kernel_size, 1, 1)
74
    kernel = tf.nn.conv1d(
75
        kernel[None, :, None], filters=filters, stride=[1, 1, 1], padding="SAME"
76
    )
77
    return kernel[0, :, 0]
78
79
80
def gaussian_kernel1d_size(kernel_size: int) -> (tf.Tensor, tf.Tensor):
81
    """
82
    Return a the 1D filter for separable convolution equivalent to a 3-D Gaussian
83
    kernel for LocalNormalizedCrossCorrelation.
84
    :param kernel_size: scalar, size of the 1-D kernel
85
    :return: filters, of shape (kernel_size, )
86
    """
87
    mean = (kernel_size - 1) / 2.0
88
    sigma = kernel_size / 3
89
90
    grid = tf.range(0, kernel_size, dtype=tf.float32)
91
    filters = tf.exp(-tf.square(grid - mean) / (2 * sigma ** 2))
0 ignored issues
show
introduced by
bad operand type for unary -: object
Loading history...
92
93
    return filters
94
95
96
def gaussian_kernel1d_sigma(sigma: int) -> tf.Tensor:
97
    """
98
    Calculate a gaussian kernel.
99
100
    :param sigma: number defining standard deviation for
101
                  gaussian kernel.
102
    :return: shape = (dim, )
103
    """
104
    assert sigma > 0
105
    tail = int(sigma * 3)
106
    kernel = tf.exp([-0.5 * x ** 2 / sigma ** 2 for x in range(-tail, tail + 1)])
107
    kernel = kernel / tf.reduce_sum(kernel)
108
    return kernel
109
110
111
def cauchy_kernel1d(sigma: int) -> tf.Tensor:
112
    """
113
    Approximating cauchy kernel in 1d.
114
115
    :param sigma: int, defining standard deviation of kernel.
116
    :return: shape = (dim, )
117
    """
118
    assert sigma > 0
119
    tail = int(sigma * 5)
120
    k = tf.math.reciprocal([((x / sigma) ** 2 + 1) for x in range(-tail, tail + 1)])
121
    k = k / tf.reduce_sum(k)
122
    return k
123
124
125
def separable_filter(tensor: tf.Tensor, kernel: tf.Tensor) -> tf.Tensor:
126
    """
127
    Create a 3d separable filter.
128
129
    Here `tf.nn.conv3d` accepts the `filters` argument of shape
130
    (filter_depth, filter_height, filter_width, in_channels, out_channels),
131
    where the first axis of `filters` is the depth not batch,
132
    and the input to `tf.nn.conv3d` is of shape
133
    (batch, in_depth, in_height, in_width, in_channels).
134
135
    :param tensor: shape = (batch, dim1, dim2, dim3, 1)
136
    :param kernel: shape = (dim4,)
137
    :return: shape = (batch, dim1, dim2, dim3, 1)
138
    """
139
    strides = [1, 1, 1, 1, 1]
140
    kernel = tf.cast(kernel, dtype=tensor.dtype)
141
142
    tensor = tf.nn.conv3d(
143
        tf.nn.conv3d(
144
            tf.nn.conv3d(
145
                tensor,
146
                filters=tf.reshape(kernel, [-1, 1, 1, 1, 1]),
147
                strides=strides,
148
                padding="SAME",
149
            ),
150
            filters=tf.reshape(kernel, [1, -1, 1, 1, 1]),
151
            strides=strides,
152
            padding="SAME",
153
        ),
154
        filters=tf.reshape(kernel, [1, 1, -1, 1, 1]),
155
        strides=strides,
156
        padding="SAME",
157
    )
158
    return tensor
159