Resizing a 3D image (and resampling) - python

I have 3D image of a brain (let's call it flash) and it's currently 263 x 256 x 185. I want to resize it to be the size of another image(call it whole_brain_bravo); 256 x 256 x 176, and (hopefully) use a lanczos interpolation to resample (Image.ANTIALIAS). My (failed) attempt:
from scipy import ndimage as nd
import nibabel as nib
import numpy as np
a = nib.load('flash.hdr') # nib is what I use to load the images
b = nib.load('whole_brain_bravo.hdr')
flash = a.get_data() # Access data as array (in this case memmap)
whole = b.get_data()
downed = nd.interpolation.zoom(flash, zoom=b.shape) # This obviously doesn't work
Have you guys ever done this sort of thing on a 3D image?

From the docstring for scipy.ndimage.interpolate.zoom:
"""
zoom : float or sequence, optional
The zoom factor along the axes. If a float, `zoom` is the same for each
axis. If a sequence, `zoom` should contain one value for each axis.
"""
What is the scale factor between the two images? Is it constant across all axes (i.e. are you scaling isometrically)? In that case zoom should be a single float value. Otherwise it should be a sequence of floats, one per axis.
For example, if the physical dimensions of whole and flash can be assumed to be equal, then you could do something like this:
dsfactor = [w/float(f) for w,f in zip(whole.shape, flash.shape)]
downed = nd.interpolation.zoom(flash, zoom=dsfactor)

According to the docs, the zoom argument is "The zoom factor along the axes". That's a little vague, but it sounds like they mean a scale factor, rather than the desired dimension.
Try this:
zoomFactors = [bi/float(ai) for ai, bi in zip(a, b)]
downed = nd.interpolation.zoom(flash, zoom=zoomFactors)
Not sure about choosing a filter - the docs only mention spline interpolations of various orders.

Related

apply filters on images when there is no data pixels

I have image that contains many no data pixels. The image is 2d numpy array and the no-data values are "None". Whenever I try to apply on it filters, seems like the none values are taken into account into the kernel and makes my pixels dissapear.
For example, I have this image:
I have tried to apply on it the lee filter with this function (taken from Speckle ( Lee Filter) in Python):
from scipy.ndimage.filters import uniform_filter
from scipy.ndimage.measurements import variance
def lee_filter(img, size):
img_mean = uniform_filter(img, (size, size))
img_sqr_mean = uniform_filter(img**2, (size, size))
img_variance = img_sqr_mean - img_mean**2
overall_variance = variance(img)
img_weights = img_variance / (img_variance + overall_variance)
img_output = img_mean + img_weights * (img - img_mean)
return img_output
but the results looks like this:
with the warnning:
UserWarning: Warning: converting a masked element to nan. dv =
np.float64(self.norm.vmax) - np.float64(self.norm.vmin)
I have also tried to use the library findpeaks.
from findpeaks import findpeaks
import findpeaks
#lee enhanced filter
image_lee_enhanced = findpeaks.lee_enhanced_filter(img, win_size=3, cu=0.25)
but I get the same blank image.
When I used median filter on the same image with ndimage is worked no problem.
My question is how can I run those filters on the image without letting the None values interrupt the results?
edit: I prefer not to set no value pixels to 0 because the pixel range value is between -50-1 (is an index values). In addition i'm afraid that if I change it to any other value e.g 9999) it will also influence the filter (am I wrong?)
Edit 2:
I have read Cris Luengo answer and I have tried to apply something similar with the scipy.ndimage median filter as I have realized that the result is disorted as well.
This is the original image:
I have tried masking the Null values:
idx = np.ma.masked_where(img,img!=None)[:,1]
median_filter_img = ndimage.median_filter(img[idx].reshape(491, 473), size=10)
zeros = np.zeros([img.shape[0],img.shape[1]])
zeros[idx] = median_filter_img
The results looks like this (color is darker to see the problem in the edges):
As it can bee seen, seems like the edges values are inflluences by the None values.
I have done this also with img!=0 but got the same problem.
(just to add: the pixels vlues are between 1 to -35)
If you want to apply a linear smoothing filter, then you can use the Normalized Convolution.
The basic recipe is:
Create a mask image that is 1 for the pixels with data, and 0 for the pixels without data.
Set the pixels without data to any number, for example 0. NaN is not valid because it spreads in the computations.
Apply the linear smoothing filter to the image multiplied by the mask.
Apply the linear smoothing filter to the mask.
Divide the two results.
Basically, we normalize the result of the linear smoothing filter (convolution) by the number of pixels with data within the filter window.
In regions where the smoothed mask is 0 (far away from data), we will divide 0 by 0, so special care needs to be taken there.
Note that normalized convolution can be used also for uncertain data, where the mask image gets values in between 0 and 1 indicating the confidence we have in each pixel. Pixels thought to be noisy can be set to a value closer to 0 than the other pixels, for example.
The recipe above is only valid for linear smoothing filters. Normalized convolution can be done with other linear filters, for example derivative filters, but the resulting recipe is different. See for example here the equation for Normalized Convolution to compute the derivative.
For non-linear filters, other approaches are necessary. Non-linear smoothing filters, for example, will often avoid affecting edges, and so will work quite well in images with missing data, if the missing pixels are set to 0, or some value far outside of the data range. The concept of keeping a mask image that indicates which pixels have data and which don't is always a good idea.
Seems like a simple solution is to set the non values to zero. I don't know how you would get around this, because most image processing kernels require some value to for you to apply.
a[numpy.argwhere(a==None)] = 0

How to rotate a 3D array without rounding, by using Python?

I have a 3D numpy array that I want to rotate with an angle that I want. I have tried using scipy.ndimage.rotate function and it does the job. However, it does a lot of rounding when rotating. This causes me a problem because my 3D array is representation of an object and numbers in each pixel represent the material that pixel is filled with (which I store in a different file). Therefore, I need a way to rotate the array without doing approximation or rounding and making the object blurry is not a problem
Here is what I got with the function I used:
The problem you are dealing with is essentially a sampling issue. Your resolution is too low for the data you are dealing with. One possibility to solve this is to increase the resolution of the image you are working with, enforce the color values as you rotate (ie no blending colors at the edges), and create a size/shape template that must be met after the rotation.
Edit: For clarity, it isn't the data that is at too low of a resolution, it's the image in which the data is stored that should be at a high enough resolution. The wikipedia page on multidimensional sampling is good for this topic: https://en.wikipedia.org/wiki/Multidimensional_sampling
I think the way I would approach it, outside of someone knowing an actual package to do this, is start with the indices and rotate them, then, given they may be floating point, round them. This may not be the best, but I think it should work.
Most of this example is loading a 3D dataset I found to use as an example.
import matplotlib.pyplot as plt
import os
import numpy as np
from scipy.ndimage import rotate
def load_example_data():
# Found data as an example
from urllib.request import urlopen
import tarfile
opener = urlopen( 'http://graphics.stanford.edu/data/voldata/MRbrain.tar.gz')
tar_file = tarfile.open('MRbrain.tar.gz')
try:
os.mkdir('mri_data')
except:
pass
tar_file.extractall('mri_data')
tar_file.close()
import numpy as np
data = np.array([np.fromfile(os.path.join('mri_data', 'MRbrain.%i' % i),
dtype='>u2') for i in range(1, 110)])
data.shape = (109, 256, 256)
return data
def rotate_nn(data, angle, axes):
"""
Rotate a `data` based on rotating coordinates.
"""
# Create grid of indices
shape = data.shape
d1, d2, d3 = np.mgrid[0:shape[0], 0:shape[1], 0:shape[2]]
# Rotate the indices
d1r = rotate(d1, angle=angle, axes=axes)
d2r = rotate(d2, angle=angle, axes=axes)
d3r = rotate(d3, angle=angle, axes=axes)
# Round to integer indices
d1r = np.round(d1r)
d2r = np.round(d2r)
d3r = np.round(d3r)
d1r = np.clip(d1r, 0, shape[0])
d2r = np.clip(d2r, 0, shape[1])
d3r = np.clip(d3r, 0, shape[2])
return data[d1r, d2r, d3r]
data = load_example_data()
# Rotate the coordinates indices
angle = 5
axes = (0, 1)
data_r = rotate_nn(data, angle, axes)
I think the general idea will work. You will have to consider what the axis is to rotate around.
For anyone with this problem stumbling upon this thread: brechmos' comment under the OP put me in the right direction for an actual solution. rotate() by default uses a third-order spline interpolation, which gives nice smooth edges. We want sharp edges though, without numbers in between. Setting order = 0 does exactly this. No need for extra functions or implementing anything yourself, just change a single argument.

Create a vector graphics (svg, eps, pdf) "heatmap" from 2d function with colorbar

I want to plot a 2d hanning window function for a picture with N=512 pixels with a colorbar as vector graphics (*.svg, *.eps, (vectorized!) *.pdf or so)...
So I need to plot a 2d function
w(x,y) = sin(x*pi/N)^2 * sin(y*pi/N)^2
My solution for this was python first:
import numpy as np
from PIL import Image
im_hanning = Image.new("F", (N, N))
pix_hanning = im_hanning.load()
for x in range(0, N):
for y in range(0, N):
pix_hanning[x,y] = np.sin(x*np.pi/N)**2 * np.sin(y*np.pi/N)**2 * 255
im_hanning = Image.fromarray(array)
The result is this picture:
But this is a raster graphics of course.
So I tried it with gnuplot. This seemed better until I saw the result:
set xrange [0:1]
set yrange [0:1]
unset xtics
unset ytics
set pm3d map
set size square
set samples 512
set isosamples 512
set palette gray
splot sin(x*pi)**2 * sin(y*pi)**2
I had to increase the samples, else it looked terrible... The result looks fine:
I especially like the colorbar on the right. But this produces (no matter what terminal I set) raster graphics again.
Is there a possibility to plot a 2d function as vector graphics?
With a PDF, SVG or EPS terminal, gnuplot does give you vector graphics. But the function has to be represented in some way, and what gnuplot does is a piecewise linear interpolation, that is, the surface is represented by small portions of plane (triangles or quadrangles), the number of which is set by the sampling rate.
If you want an infinitely scalable colour map, the way to produce it has to be a primitive of the scalable vector language you are using, e.g. SVG. So this is your real question: is there an SVG/PDF/EPS primitive to represent the gradient sin(x*pi)**2 * sin(y*pi)**2. I believe this is not the case, colour gradients are also piecewise linear AFAIK, but asked in this way you may attract answers from specialists.

How can I extract this obvious event from this image?

EDIT: I have found a solution :D thanks for the help.
I've created an image processing algorithm which extracts this image from the data. It's complex, so I won't go into detail, but this image is essentially a giant numpy array (it's visualizing angular dependence of pixel intensity of an object).
I want to write a program which automatically determines when the curves switch direction. I have the data and I also have this image, but it turns out doing something meaningful with either has been tricky. Thresholding fails because there are bands of different background color. Sobel operators and Hough Transforms also do not work well for this same reason.
This is really easy for humans to see when this switch happens, but not so easy to tell a computer. Any tips? Thanks!
Edit: Thanks all, I'm now fitting lines to this image after convolution with general gaussian and skeletonization of the result. Any pointers on doing this would be appreciated :)
You can take a weighted dot product of successive columns to get a one-dimensional signal that is much easier to work with. You might be able to extract the patterns using this signal:
import numpy as np
A = np.loadtxt("img.txt")
N = A.shape[0]
L = np.logspace(1,2,N)
X = []
for c0,c1 in zip(A.T, A.T[1:]):
x = c0.dot(c1*L) / (np.linalg.norm(c0)*np.linalg.norm(c1))
X.append(x)
X = np.array(X)
import pylab as plt
plt.matshow(A,alpha=.5)
plt.plot(X*3-X.mean(),'k',lw=2)
plt.axis('tight')
plt.show()
This is absolutely not a complete answer to the question, but a useful observation that is too long for a comment. I'll delete if a better answer comes along.
With the help of Mark McCurry, I was able to get a good result.
Step 1: Load original image. Remove background by subtracting median of each vertical column from itself.
no_background=[]
for i in range(num_frames):
no_background.append(orig[:,i]-np.median(orig,1))
no_background=np.array(no_background).T
Step 2: Change negative values to 0.
clipped_background = no_background.clip(min=0)
Step 3: Extract a 1D signal. Take weighted sum of the vertical columns, which relates the max intensity in a column to its position.
def exp_func(x):
return np.dot(np.arange(len(x)), np.power(x, 10))/(np.sum(np.power(x, 10)))
weighted_sum = np.apply_along_axis(exp_func,0, clipped_background)
Step 4: Take the derivative of 1D signal.
conv = np.convolve([-1.,1],weighted_sum, mode='same')
pl.plot(conv)
Step 5: Determine when the derivative changes sign.
signs=np.sign(conv)
pl.plot(signs)
pl.ylim(-1.2,1.2)
Step 6: Apply median filter to above signal.
filtered_signs=median_filter(signs, 5) #pick window size based on result. second arg and odd number.
pl.plot(filtered_signs)
pl.ylim(-1.2,1.2)
Step 7: Find the indices (frame locations) of when the sign switches. Plot result.
def sign_switch(oneDarray):
inds=[]
for ind in range(len(oneDarray)-1):
if (oneDarray[ind]<0 and oneDarray[ind+1]>0) or (oneDarray[ind]>0 and oneDarray[ind+1]<0):
inds.append(ind)
return np.array(inds)
switched_frames = sign_switch(filtered_signs)
For detecting tip positions or turning points, you might try using a corner detector on the original image (not the skeletonized one). As a corner detector the structure tensor could be applicable. The structure tensor is also useful for calculating the local orientation in an image.

Smoothing an array along an axis

I've looked into smoothing of a signal and found the Cookbook page http://wiki.scipy.org/Cookbook/SignalSmooth which works fine for an array with only one dimension. Now I would really need a function that does the same thing but on an (TxNx3) array along the N axis.
The context is, that I have a trajectory of N particles and T frames each with x,y,z and now I would like to implement some sort of low pass filter on their movements in time to make the wiggle less in a movie.
So to illustarte the problem:
import numpy as np
from MY_Cookbook_copy import smooth
T = np.random.random((30,30,3)) # this is the trajectory
imshow( T[:,:,0], interpolation='nearest' )
# this works but is too slow and only for the x data
for i in range( T.shape[1] ):
T[:,i,0] = smooth( T[:,i,0], 5 )
imshow( T[:,:,1], interpolation='nearest' )
this gives
so dimensions of my problem are on the order of something like 1000 particles over 5000 time frames
You could try a multi-dimensional Gaussian filter - http://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.filters.gaussian_filter.html
Applying a Gaussian filter would smooth the signal and you can apply it to in multiple dimensions through setting the sigma as a tuple so you can have different amount of smoothing in each axis. I guess you only want to smooth the x, y, z values over time, so you can try using a sigma of (2, 0, 0) perhaps, although I'm not entirely clear on your data setup. The sigma controls the width of the Gaussian filter, so if you want to smooth very locally, then use a small sigma. You may need to experiment a little to find the level of smoothing that you want.

Categories

Resources