Calculate pixelwise distance of 3D tensor in tensorflow? - python

I am trying to create a 3d distance map (size: W * H * D) in tensorflow to be used in a loss function for training. I have a ground truth (binary volume of size W * H * D) that I will use to create the distance map, i.e. the value of each pixel of my distance map will be the minimum distance of that pixel to the positive valued (i.e pixel=1) shape in the ground truth.
Having issues with the 3d shape problem as L2.NORM reduce the axis to a 2D shape and making this problem fully differentiable. Any advice or pointers would be much appreciated.

If I understand correctly, you want to compute the distance from each position in the volume to the closest position of a given class. For simplicity, I will assume that the interesting class is labelled with 1, but hopefully you can adapt it to your case if it is different. The code is for TensorFlow 2.0, but should work the same for 1.x.
The simplest way to do this is to compute the distance between all the coordinates in the volume against every coordinate with a 1, and then pick the smallest distance from there. You can do that like this:
import tensorflow as tf
# Make input data
w, h, d = 10, 20, 30
w, h, d = 2, 3, 4
t = tf.random.stateless_uniform([w, h, d], (0, 0), 0, 2, tf.int32)
print(t.numpy())
# [[[0 1 0 0]
# [0 0 0 0]
# [1 1 0 1]]
#
# [[1 0 0 0]
# [0 0 0 0]
# [1 1 0 0]]]
# Make coordinates
coords = tf.meshgrid(tf.range(w), tf.range(h), tf.range(d), indexing='ij')
coords = tf.stack(coords, axis=-1)
# Find coordinates that are positive
m = t > 0
coords_pos = tf.boolean_mask(coords, m)
# Find every pairwise distance
vec_d = tf.reshape(coords, [-1, 1, 3]) - coords_pos
# You may choose a difference precision type here
dists = tf.linalg.norm(tf.dtypes.cast(vec_d, tf.float32), axis=-1)
# Find minimum distances
min_dists = tf.reduce_min(dists, axis=-1)
# Reshape
out = tf.reshape(min_dists, [w, h, d])
print(out.numpy().round(3))
# [[[1. 0. 1. 2. ]
# [1. 1. 1.414 1. ]
# [0. 0. 1. 0. ]]
#
# [[0. 1. 1.414 2.236]
# [1. 1. 1.414 1.414]
# [0. 0. 1. 1. ]]]
This may work well enough for you, although it may not be the most efficient solution. The smartest thing would be to search for the closest positive position in the neighboring area of each position, but that is complicated to do effectively, both in general and more so in a vectorized way in TensorFlow. There are however a couple of ways we can improve on the code above. On the one hand, we know that positions with a 1 will always have zero distance, so computing for those is unnecessary. On the other hand, if the 1 class in the 3D volume represents some kind of dense shape, then we could save some time if we only computed the distances against the surface of that shape. All other positive positions will have necessarily a greater distance to positions outside the shape. So we can do the same thing we were doing, but computing only distances from non-positive positions to positive surface positions. You can do that like this:
import tensorflow as tf
# Make input data
w, h, d = 10, 20, 30
w, h, d = 2, 3, 4
t = tf.dtypes.cast(tf.random.stateless_uniform([w, h, d], (0, 0)) > .15, tf.int32)
print(t.numpy())
# [[[1 1 1 1]
# [1 1 1 1]
# [1 1 0 0]]
#
# [[1 1 1 1]
# [1 1 1 1]
# [1 1 1 1]]]
# Find coordinates that are positive and on the surface
# (surrounded but at least one 0)
t_pad_z = tf.pad(t, [(1, 1), (1, 1), (1, 1)]) <= 0
m_pos = t > 0
m_surround_z = tf.zeros_like(m_pos)
# Go through the 6 surrounding positions
for i in range(3):
for s in [slice(None, -2), slice(2, None)]:
slices = tuple(slice(1, -1) if i != j else s for j in range(3))
m_surround_z |= t_pad_z.__getitem__(slices)
# Surface points are positive points surrounded by some zero
m_surf = m_pos & m_surround_z
coords_surf = tf.where(m_surf)
# Find coordinates that are zero
coords_z = tf.where(~m_pos)
# Find every pairwise distance
vec_d = tf.reshape(coords_z, [-1, 1, 3]) - coords_surf
dists = tf.linalg.norm(tf.dtypes.cast(vec_d, tf.float32), axis=-1)
# Find minimum distances
min_dists = tf.reduce_min(dists, axis=-1)
# Put minimum distances in output array
out = tf.scatter_nd(coords_z, min_dists, [w, h, d])
print(out.numpy().round(3))
# [[[0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 0. 1. 1.]]
#
# [[0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 0. 0. 0.]]]
EDIT: Here is one way in which you can divide the distance computations in chunks with a TensorFlow loop:
# Following from before
coords_surf = ...
coords_z = ...
CHUNK_SIZE = 1_000 # Choose chunk size
dtype = tf.float32
# If using TF 2.x you can know in advance the size of the tensor array
# (although the element shape will not be constant due to the last chunk)
num_z = tf.shape(coords_z)[0]
arr = tf.TensorArray(dtype, size=(num_z - 1) // CHUNK_SIZE + 1, element_shape=[None], infer_shape=False)
_, arr = tf.while_loop(lambda i, arr: i < num_z,
lambda i, arr: (i + CHUNK_SIZE, arr.write(i // CHUNK_SIZE,
tf.reduce_min(tf.linalg.norm(tf.dtypes.cast(
tf.reshape(coords_z[i:i + CHUNK_SIZE], [-1, 1, 3]) - coords_surf,
dtype), axis=-1), axis=-1))),
[tf.constant(0, tf.int32), arr])
min_dists = arr.concat()
out = tf.scatter_nd(coords_z, min_dists, [w, h, d])

Related

Calculate means of array with specific elements

I'm implementing the Nearest Centroid Classification algorithm and I'm kind of blocked on how to use numpy.mean in my case.
So suppose I have some spherical datasets X:
[[ 0.39151059 3.48203037]
[-0.68677876 1.45377717]
[ 2.30803493 4.19341503]
[ 0.50395297 2.87076658]
[ 0.06677012 3.23265678]
[-0.24135103 3.78044279]
[-0.05660036 2.37695381]
[ 0.74210998 -3.2654815 ]
[ 0.05815341 -2.41905942]
[ 0.72126958 -1.71081388]
[ 1.03581142 -4.09666955]
[ 0.23209714 -1.86675298]
[-0.49136284 -1.55736028]
[ 0.00654881 -2.22505305]]]
and the labeled vector Y:
[0. 0. 0. 0. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1.]
An example with 100 2D data points gives the following result:
The NCC algorithm consists of first calculating the class mean of each class (0 and 1: that's blue and red) and then calculating the nearest class centroid for the next data point.
This is my current function:
def mean_ncc(X,Y):
# find unique classes
m_cids = np.unique(Y) #[0. 1.]
# compute class means
mu = np.zeros((len(cids), X.shape[1])) #[[0. 0.] [0. 0.]] (in the case where Y has 2 unique points (0 and 1)
for class_idx, class_label in enumerate(cids):
mu[class_idx, :] = #problem here
return mu
So here I want an array containing the class means of '0' (blue) points and '1' (red) points:
How can I specify the number of elements of X whose mean I want to calculate?
I would like to do something like this:
for class_idx, class_label in enumerate(m_cids):
mu[class_idx, :] = np.mean(X[only the elements,that contains the same class_label], axis=0)
Is it possible or is there another way to implement this?
You could use something like this:
import numpy as np
tags = [0, 0, 1, 1, 0, 1]
values = [5, 4, 2, 5, 9, 8]
tags_np = np.array(tags)
values_np = np.array(values)
print(values_np[tags_np == 1].mean())
EDIT: You will surely need to look more into the axis parameter for the mean function:
import numpy as np
values = [[5, 4],
[5, 4],
[4, 3],
[4, 3]]
values_np = np.array(values)
tags_np = np.array([0, 0, 1, 1])
print(values_np[tags_np == 0].mean(axis=0))

How to generate 2d gaussian kernel using 2d convolution in python?

From my workout instruction:
A 2D Gaussian can be formed by convolution of a 1D Gaussian with its transpose.
Here is my 1d gaussian function:
def gauss1d(sigma, filter_length=11):
# INPUTS
# # sigma : sigma of gaussian distribution
# # filter_length : integer denoting the filter length
# OUTPUTS
# # gauss_filter : 1D gaussian filter without normalization
rng = range(-int(filter_length/2),int(filter_length/2)+1)
gauss_filter = [np.exp((-x**2) / (2*sigma**2)) for x in rng]
# The formula used above has been given in the instruction.
return np.array(gauss_filter)
And 2d convolution function which performs a 2D convolution between image and filt, image being a 2D image.
def myconv2(image, filt):
# INPUTS
# # image : 2D image, as numpy array of size mxn
# # filt : 1D or 2D filter of size kxl
# OUTPUTS
# img_filtered : 2D filtered image, of size (m+k-1)x(n+l-1)
m, n = image.shape
k, l = filt.shape
offsety = k // 2
offsetx = l // 2
img_filtered = np.zeros((m+k-1, n+l-1), "double")
image = np.pad(image, ((offsety,offsety),(offsetx, offsetx)), mode='constant')
for i in range(offsety, m+offsety):
for j in range(offsetx, n+offsetx):
box_vals = image[ i - offsety : i + offsety+1, j-offsetx: j+offsetx+1]
new_val = np.sum( filt * box_vals)
img_filtered[i][j] = np.sum(new_val)
return img_filtered
A simple presentation of how function works for 5x5 input image and 3x3 filter kernel:
With having following 1d gaussian and its transpose, I call myconv2 function :
sigma = 3
filter_length = 5
gauss = gauss1d(sigma, filter_length).reshape(1,filter_length)
guass
array([[0.18073067, 0.20897821, 0.22058223, 0.20897821, 0.18073067]])
gauss_t = np.transpose(gauss)
gauss_t
array([[0.18073067],
[0.20897821],
[0.22058223],
[0.20897821],
[0.18073067]])
myconv2(gauss, guass_t)
array([[0. , 0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. ],
[0.03986597, 0.04609688, 0.04865652, 0.04609688, 0.03986597],
[0. , 0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. ]])
As you can see its not actually a 2d gaussian kernel and some values are missing.
I don't know what I am missing and what should I consider in my code to reach the goal.
Thanks.
You could just do a matrix multiplication. The convolution should also work, just beware of the padding.
gaus2d = gauss.T # gauss
Your conv2d implementation does not seem to be right. I suggest you to implement a 'valid' convolution (or cross correlation):
simple_valid_cross_correlation(img, filt):
ih, iw = img.shape
fh, fw = filt.shape
result = np.zeros((ih - fh + 1, iw - fw + 1))
for i in range(result.shape[0]):
for j in range(result.shape[1]):
result[i, j] = np.sum(filt * img[i:i+fh, j:j+fw])
return result
gauss_pad = np.pad(gauss.T, ((0, 0), (gauss.shape[1]-1, gauss.shape[1]-1)))
gauss2d = simple_valid_cross_correlation(gauss_pad, gauss)
There is also scipy.signal.convolve2d if you don't want to implement your own conv. I think it may be faster

tensorflow find minimum distance to real points

I have a Bx3 tensor, foo of B= batch size 3D points. Through some fanciness or another, I get another tensor, bar that is of shape Bx6x3, and where each of the B 6x3 matrices corresponds to a point in foo. That 6x3 matrix consists of 6 complex-valued 3D points. What I would like to do is, for each of my B points, find the closest real-valued point out of the 6 in bar to the corresponding point in foo, ending up with a new Bx3 min_bar consisting of the closest points in bar to points in foo.
In numpy, I can accomplish this feat with masked arrays:
foo = np.array([
[1,2,3],
[4,5,6],
[7,8,9]])
# here bar is only Bx2x3 for simplicity, but the solution generalizes
bar = np.array([
[[2,3,4],[1+0.1j,2+0.1j,3+0.1j]],
[[6,5,4],[4,5,7]],
[[1j,1j,1j],[0,0,0]],
])
#mask complex elements of bar
bar_with_masked_imag = np.ma.array(bar)
candidates = bar_with_masked_imag.imag == 0
bar_with_masked_imag.mask = ~candidates
dists = np.sum(bar_with_masked_imag**2, axis=1)
mindists = np.argmin(dists, axis=1)
foo_indices = np.arange(foo.shape[0])
min_bar = np.array(
bar_with_masked_imag[foo_indices,mindists,:],
dtype=float
)
print(min_bar)
#[[2. 3. 4.]
# [4. 5. 7.]
# [0. 0. 0.]]
However, tensorflow doesn't have masked arrays and such. How do I translate this into tensorflow?
Here is a way to do that:
import tensorflow as tf
import math
def solution_tf(foo, bar):
foo = tf.convert_to_tensor(foo)
bar = tf.convert_to_tensor(bar)
# Get real and imaginary parts
bar_r = tf.cast(tf.real(bar), foo.dtype)
bar_i = tf.imag(bar)
# Mask of all real-valued points
m = tf.reduce_all(tf.equal(bar_i, 0), axis=-1)
# Distance to every corresponding point
d = tf.reduce_sum(tf.squared_difference(tf.expand_dims(foo, 1), bar_r), axis=-1)
# Replace distances of complex points with infinity
d2 = tf.where(m, d, tf.fill(tf.shape(d), tf.constant(math.inf, d.dtype)))
# Find smallest distances
idx = tf.argmin(d2, axis=1)
# Get points with smallest distances
b = tf.range(tf.shape(foo, out_type=idx.dtype)[0])
return tf.gather_nd(bar_r, tf.stack([b, idx], axis=1))
# Test
with tf.Graph().as_default(), tf.Session() as sess:
foo = tf.constant([
[1,2,3],
[4,5,6],
[7,8,9]], dtype=tf.float32)
bar = tf.constant([
[[2,3,4],[1+0.1j,2+0.1j,3+0.1j]],
[[6,5,4],[4,5,7]],
[[1j,1j,1j],[0,0,0]]], dtype=tf.complex64)
sol_tf = solution_tf(foo, bar)
print(sess.run(sol_tf))
# [[2. 3. 4.]
# [4. 5. 7.]
# [0. 0. 0.]]

Neural network in Theano with sparse weight matrix diverging

I am working on a neural network with very large sparse weight matrices. The zero values in the weights are to remain zero and not be changed. Gradients should only be calculated and propagated for non zero values since it will otherwise be too expensive to run. This means I am using Theano and its sparse.structured_dot matrix multiplication. This is my code:
#!/usr/bin/env python3
import math
import numpy as np
import theano as th
import theano.tensor as T
from theano import sparse as sp
import scipy.sparse as spsp
def sparse_matrix(matrix, name):
"""Convert an array to a shared sparse theano matrix."""
matrix = np.asarray(matrix)
if matrix.shape[0] > matrix.shape[1]: matrix = spsp.csc_matrix(matrix)
else: matrix = spsp.csr_matrix(matrix)
return th.shared(matrix, name)
def mul(weight, matrix):
"""Sparse matrix multiplication.
:param weight: sparse weight m x n matrix, where m is number of features of output and n is number of features for
input.
:param matrix: input n x p matrix, where n is number of features for input and p is number of input vectors.
"""
return T.transpose(sp.structured_dot(weight, T.transpose(matrix)))
x = T.fmatrix('x')
target = T.fmatrix('target')
W = sparse_matrix([[0, 0.5], [0.5, 0]], 'W')
y = mul(W, x)
cost = T.mean((y - target) ** 2) / 2
gradient = T.grad(cost=cost, wrt=W)
W_updated = W - (gradient * 0.01)
updates = [(W, W_updated)]
f = th.function(inputs=[x, target], outputs=[y, cost, gradient], updates=updates, allow_input_downcast=True)
print("start weight:\n", W.get_value().todense())
for i in range(10):
pred, c, g = f([[4.0, 2.0]], [[2.0, 4.0]])
print("pred:", pred)
print("cost:", c)
print("grad:\n", g)
print("end weight:\n", W.get_value().todense())
The example I test is simple, the matrix values are supposed to both go from 0.5 to 1.0 where the cost will become zero. It however diverges, both values decrease and the gradients are always about 1.0 and 2.0. It seems the gradients are being calculated wrong. If I run the code I get the following output:
start weight:
[[ 0. 0.5]
[ 0.5 0. ]]
pred: [[ 1. 2.]]
cost: 1.25
grad:
(0, 1) 1.00000071339
(1, 0) 2.00000143424
pred: [[ 0.97999999 1.91999994]]
cost: 1.3417000669408599
grad:
(0, 1) 1.00000071343
(1, 0) 2.00000143439
pred: [[ 0.95999997 1.83999989]]
cost: 1.4368001387634612
grad:
(0, 1) 1.00000071347
(1, 0) 2.00000143453
pred: [[ 0.93999996 1.75999983]]
cost: 1.5353002154685411
grad:
(0, 1) 1.0000007135
(1, 0) 2.00000143468
pred: [[ 0.91999994 1.67999977]]
cost: 1.637200297056838
grad:
(0, 1) 1.00000071354
(1, 0) 2.00000143483
pred: [[ 0.89999993 1.59999971]]
cost: 1.7425003835290889
grad:
(0, 1) 1.00000071358
(1, 0) 2.00000143498
pred: [[ 0.87999991 1.51999966]]
cost: 1.8512004748860316
grad:
(0, 1) 1.00000071362
(1, 0) 2.00000143513
pred: [[ 0.8599999 1.4399996]]
cost: 1.9633005711284035
grad:
(0, 1) 1.00000071365
(1, 0) 2.00000143528
pred: [[ 0.83999989 1.35999954]]
cost: 2.0788006722569428
grad:
(0, 1) 1.00000071369
(1, 0) 2.00000143543
pred: [[ 0.81999987 1.27999948]]
cost: 2.197700778272387
grad:
(0, 1) 1.00000071373
(1, 0) 2.00000143558
end weight:
[[ 0. 0.39999993]
[ 0.29999986 0. ]]
I thought CSR and CSC sparse matrices were supposed to act the same in this context, hence the sparse_matrix function picks one based on the matrix dimensions. However it turns out that my problem is fixed if I explicitly use CSC and avoid CSR. Then, the values in the sparse matrix converges to 1.0 and the cost disappears.

Tridiagonal Matrix using Python

I looked on the past questions on tridiagonals but none seem to be experiencing the problem i'm having. I'm trying to form a tridiagonal stiffness matrix for the non uniform Poisson equation using scipy.sparse.spdiags but do not seem to be receiving a matrix as a result.
def Poisson_Stiffness(x0):
N = len(x0) - 1 #THE AMOUNT OF ELEMENTS (NOT THE AMOUNT OF POINTS) x0, x1, ... , x_N
h = np.zeros(N)
a = np.zeros(N+1)
b = np.zeros(N)
for i in range(N):
h[i] = x0[i+1] - x0[i] #Length of each nonuniform element
a[0] = 1/h[0]
for i in range(1,N):
a[i] = 1/h[i] + 1/h[i-1] #Main Diagonal of stiffness matrix
a[N] = 1/h[N-1]
for i in range(N):
b[i] = -1/h[i] #Upper and lower diagonal of stiffness matrix.
Tridiagonal_Data = np.array([[a],[b],[b]])
Positions = [0, 1, -1]
Stiffness_Matrix = scipy.sparse.spdiags(Tridiagonal_Data,Positions,N+1,N+1)
print Stiffness_Matrix
As a result from this, with x0 = [0,0.3,0.4,0.7,1]; I am getting the stiffness matrix as :
Jamess-MBP:Poisson jamesmalone$ python Poisson1d.py
(0, 0) [ 3.33333333 13.33333333 13.33333333 6.66666667 3.33333333]
(1, 0) [-3.33333333 -10. -3.33333333 -3.33333333]
My question is why does it come out like this and not in matrix form?
I have tried changing the data types to see if that was the problem but i would receive errors such as (for .toarray()):
Jamess-MBP:Poisson jamesmalone$ python Poisson1d.py
Traceback (most recent call last):
File "Poisson1d.py", line 33, in <module>
Poisson_Stiffness([0,0.3,0.4,0.7,1])
File "Poisson1d.py", line 29, in Poisson_Stiffness
Stiffness_Matrix = scipy.sparse.spdiags(Tridiagonal_Data,Positions,N+1,N+1).toarray()
File "/Users/jamesmalone/anaconda/lib/python2.7/site-packages/scipy/sparse/base.py", line 637, in toarray
return self.tocoo().toarray(order=order, out=out)
File "/Users/jamesmalone/anaconda/lib/python2.7/site-packages/scipy/sparse/coo.py", line 275, in toarray
B.ravel('A'), fortran)
RuntimeError: internal error: failed to resolve data types
Thanks in advance.
Try using scipy.sparse.diags. I also cleaned up your code because you are not taking advantage of numpy's strengths (broadcasting) with those for loops. Also cleaned up some formatting according to PEP8:
from scipy.sparse import diags
x0 = np.array(x0)
N = len(x0) - 1
h = x0[1:] - x0[:-1]
a = np.zeros(N+1)
a[0] = 1/h[0]
a[1:-1] = 1/h[1:] + 1/h[:-1]
a[-1] = 1/h[-1]
b = -1/h
data = [a.tolist(), b.tolist(), b.tolist()]
positions = [0, 1, -1]
stiffness_matrix = diags(data, positions, (N+1, N+1))
print stiffness_matrix.toarray()
With x0 = [0, 0.3, 0.4, 0.7, 1], this yields
[[ 3.33333333 -3.33333333 0. 0. 0. ]
[ -3.33333333 13.33333333 -10. 0. 0. ]
[ 0. -10. 13.33333333 -3.33333333 0. ]
[ 0. 0. -3.33333333 6.66666667 -3.33333333]
[ 0. 0. 0. -3.33333333 3.33333333]]

Categories