How to arbitrarily extract a specific subset of images from a dataset? - indexing

Recently I'm planning to manipulate a stack of images and the goal is to extract a specific subset of slices from there, for example only even or odd or arbitrary indexes, and then save them into another dataset.
In DM, there are a number of helpful functions in the Volume menu but unfortunately, they cannot really fullfill what I want to do.
I am just wondering whether this idea can be realized via scripting.
Many thanks for your help in advance.

There are two ways you can go about it, one of them only suitable for data up to 3D and generally slower than the other, but more flexible.
As you have been asking for arbitrary subsampling, I'm starting with that option, but it is more likely that the second option gives you what you want: orthogonal, regular subsampling.
If you are in a hurry, the short answer is: Use the SliceN command.
1) Using expressions (arbitrary subsampling)
Individual pixel positions in an Image data (img) can be addressed
using the notations
img[ X, 0 ] ... for 1D data at position X
img[ X, Y ] ... for 2D data at position X/Y
img[ X, Y, Z ] ... for 3D data at position X/Y/Z
Note that even if this addresses a single number, the result is an expression of size 1x1 or 1x1x1 and not a scalar number, therefore you can not do: number num = img[10,4]
However, you can use a little trick to use any of the functions that convert an expression to a single number like f.e. summation. So you can do: number num = sum(img[10,4])
So how does this relate to your question? Well, in the expressions above, we used scalar values as X, Y and Z, and the resulting expressions were expressions of size 1x1 and 1x1x1, but
You can use expressions of any size as X, Y, Z in this notations, as long as all of them are expressions of same size. The resulting addressed data is of this size with values references by the according coordinates.
This will become clearer with the examples below. Starting out with a simple 1D example:
image img1D := RealImage( "TestData", 4, 100 )
image coord := RealImage( "Coordinates", 4, 10 )
img1D = 1000 + icol // Just sum test data
coord = trunc(100*Random()) // random integer 0-99
image subImg := img1D[coord,0]
img1D.ShowImage()
coord.ShowImage()
subImg.ShowImage()
Our testdata (img1D) here is just a linear graph from 1000 to 1099 using the icol expression which, at each pixel, represents that pixels X coordinate.
The coordinate image (coord) is containing random integer values between 0 and 99.
The 'magic' happens in the subImg. We use an expression with the coord image as X coordinates. That images is of size 10(x1), so the outcoming expression is of size 10(x1) which we assign to the image subImg before showing it.
Note, that the expression we have built is really just pointing to that data of the image. Instead of showing it as a new image, we could have use that expression to change these points in the data instead, using:
img1D[coord,0] = 0
Taking it from here, it is straight forward to extend the example to 2D:
image img2D := RealImage( "TestData", 4, 30, 30 )
image coordX := RealImage( "Coordinates X", 4, 10 )
image coordY := RealImage( "Coordinates Y", 4, 10 )
img2D = 10000 + icol + irow * 100
coordX = trunc(30*Random())
coordY = trunc(30*Random())
img2D[coordX,coordY] = 0
coordX.ShowImage()
coordY.ShowImage()
img2D.ShowImage()
...and 3D:
image img3D := RealImage( "TestData", 4, 30, 30, 30 )
image coordX := RealImage( "Coordinates X", 4, 10 )
image coordY := RealImage( "Coordinates Y", 4, 10 )
image coordZ := RealImage( "Coordinates Y", 4, 10 )
img3D = 10000 + icol + irow * 100 + iplane * 1000
coordX = trunc(30*Random())
coordY = trunc(30*Random())
coordZ = trunc(30*Random())
img3D[coordX,coordY,coordZ] = 0
coordX.ShowImage()
coordY.ShowImage()
coordZ.ShowImage()
img3D.ShowImage()
Unfortunately, it ends here.
You can no longer do this type of addressing in 4D or 5D data, because expression with 4 parameters are already defined to address a rectangle region in 2D data as img[T,L,B,R]
2) Using SliceN (orthogonal subsampling)
Data subsets along the dimension directions of data can be addressed using the command SliceN and its simplified variants Slice1, Slice2 and Slice3.
The SliceN command is maybe one of my favourite commands in the language when dealing with data. It looks intimidating at first, but it is straight forward.
Lets start with its simplified version for 1D extraction, Slice1.
To extract 1D data from any data up to 3D with the Slice1 command, you need the following (-and these are exactly the 7 parameters used by the command-):
data source
start point in the source
sampling direction
sampling length
sampling step-size
The only thing you need to know on top of that is:
The start point is always defined as a X,Y,Z triplet, even if the data source is only 2D or 1D. 0 is used for the not needed
dimensions.
Directions are given as dimension index: 0 = X, 1 = Y, 2 = Z
Step-size can be negative to indicate opposite directions
The specified sampling must be contained within the source data.(You can not 'extrapolate')
So a very simple example of extracting a 1D data of a 3D dataset would be:
number sx = 20
number sy = 20
number sz = 20
image img3D := RealImage( "Spectrum Image", 4, sx, sy, sz )
img3D = 5000000 + icol + irow * 100 + iplane * 10000
number px = 5
number py = 7
image spec1D := Slice1( img3D, px,py,0, 2,sz,1 )
ShowImage( img3D )
ShowImage( spec1D )
This example showed a quite typical situation in analytical microscopy when dealing with "3D Spectrum Image" data: Extracting a "1D Spectrum" at a specific spatial position.
The example did that for the spatial point px,py. Starting at the point at that position (px,py,0), it samples along the Z direction (2) for all pixels of the data (sz) with a step-size of 1.
Note, that the command again returns an expression within the source data, and that you can use this to set values as well, just using f.e.:
Slice1( img3D, px,py,0, 2,sz,1 ) = 0
The extension for 2D and 3D data using the commands Slice2 and Slice3 is straight forward. Instead of defining one output direction, you define two or three, respectively. Each with a triplet of numbers: direction, length, step-size.
The following example extracts an "image plane" of a "3D Spectrum image":
number sx = 20
number sy = 20
number sz = 20
image img3D := RealImage( "Spectrum Image", 4, sx, sy, sz )
img3D = 5000000 + icol + irow * 100 + iplane * 10000
number pz = 3
image plane2D := Slice2( img3D, 0,0,pz, 0,sx,1, 1,sy,1 )
ShowImage( img3D )
ShowImage( plane2D )
And the following example "rotates" a 3D image:
number sx = 6
number sy = 4
number sz = 3
image img3D := RealImage( "Spectrum Image", 4, sx, sy, sz )
img3D = 1000 + icol + irow * 10 + iplane * 100
image rotated := Slice3( img3D, 0,0,0, 0,sx,1, 2,sz,1, 1,sy,1 )
ShowImage( img3D )
ShowImage( rotated )
You can get all sorts of rotations, mirroring, binning with these
commands. If you want the full flexibility to get any expression up to
5D from any source data up to 5D, then you need the most versatile
SliceN command.
It works exactly the same, but you need to specify both the dimensionality of the source data, and the dimensionality of the output expression. Then, the 'starting' point needs to be defined with as many coordinates as the source data dimension suggests, and you need one triplet of specification for each output dimension.
For a source data of N dimensions and want an output of M dimensions you need: 2 + N + 3*M parameters.
As an example, lets extract the "plane" at specific spatial position from a "4D Diffraction image" data, which stores a 2D image at each spatial location of a 2D scan:
number sx = 9
number sy = 9
number kx = 9
number ky = 9
image img4D := RealImage( "Diffraction Image", 4, sx, sy, kx, ky )
img4D = 50000 + icol + irow * 10 + idimindex(2)*100 + idimindex(3)*1000
number px = 3
number py = 4
image img2D := SliceN( img4D, 4, 2, px,py,0,0, 2,kx,1, 3,ky,1 )
ShowImage( img4D )
ShowImage( img2D )

Related

How to read the calibration in a line profile

I would like to read the calibration factor of a line profile. It is stored in "Image Display Info - Calibration". I use the function GetUnitsH (image, num), but I only obtain the channel number, not the calibrated position (in nanometers).
Thank you in advance.
The command you are seeking are:
Number ImageGetDimensionScale( BasicImage, Number dimension )
Number ImageGetDimensionOrigin( BasicImage, Number dimension )
String ImageGetDimensionUnitString( BasicImage, Number dimension )
Number ImageGetIntensityScale( BasicImage )
Number ImageGetIntensityOrigin( BasicImage )
String ImageGetIntensityUnitString( Number dimension )
These will give you the calibrations as shown in the image-display.
In order to convert calibrated and uncalibrated units, you have to do the accordign maths yourself.
And yes, each of the "Get" commands has an according "Set" command as well, if you need it.
One thing to watch out for is:
Do you really look at your image, or at a copy of it?
In particular, makes sure that you use := and not = when assigning image variables to images.
Example:
This will work:
Image img := GetFrontImage()
number scale_x = img.ImageGetDimensionScale(0)
Result("\n Scale X:" + scale_x )
This will not work:
Image img = GetFrontImage()
number scale_x = img.ImageGetDimensionScale(0)
Result("\n Scale X:" + scale_x )
In the second case, one gets the refernece to the front-most image, but the = will just copy the values (and not the calibrations or other meta data) into a new image.

what is the behavior of SAME padding when stride is greater than 1?

My understanding of SAME padding in Tensorflow is that padding is added such that the output dimensions (for width and height) will be the same as the input dimensions. However, this understanding only really makes sense when stride=1, because if stride is >1 then output dimensions will almost certainly be lower.
So I'm wondering what the algorithm is for calculating padding in this case. Is it simply that padding is added so that the filter is applied to every input value, rather than leaving some off on the right?
There is a formula for that:
n' = floor((n+2*p-f)/s + 1)
where n' is the output size, n is the input size, p is the padding and f is the filter size, s will be the stride.
If you are using SAME padding with stride > 1, p will be the minimum number to make (n+2*p-f) divisible by s. Note: p could be decimal as it will be averaged over two sides of the image.
Peter's answer is true but might lack a few details. Let me add on top of it.
Autopadding = SAME means that: o = ceil(i/s), where o = output size, i = input size, s = stride.
In addition, the generic output size formula is:
o = floor( (i + p - k) / s) + 1
Where the new terms are p (pading) and k, i.e., the effective kernel size
(including dilation, or just kernel size if dilation is disabled).
If you develop that formula to solve for p, you get:
p_min = (o-1) s - i + k # i.e., when the floor is removed from the previous equation
p_max = o s - i + k - 1 # i.e., when the numerator of the floor % s is s-1
Any padding value p in the range [p_min, p_max] will satisfy the condition o = ceil(i/s), meaning that for a stride s there are s total solution satisfying the formula.
It is the norm to use p_min as padding, so you can ignore all other s-1 solutions.
PS: This would be for 1D, but for nD, simply repeat these formulas independently for each dimension, i.e.,
p_min[dimension_index] = (o[dimension_index]-1)s[dimension_index] - i[dimension_index] + k[dimension_index]
For references, these 2 links are really useful:
https://arxiv.org/abs/1603.07285
https://towardsdatascience.com/a-comprehensive-introduction-to-different-types-of-convolutions-in-deep-learning-669281e58215
https://mmuratarat.github.io/2019-01-17/implementing-padding-schemes-of-tensorflow-in-python

orientation of normal surface/vertex vectors

Given a convex 3d polygon (convex hull) How can I determine the correct direction for normal surface/vertex vectors? As the polygon is convex, by correct I mean outward facing (away from the centroid).
def surface_normal(centroid, p1, p2, p3):
a = p2-p1
b = p3-p1
n = np.cross(a,b)
if **test including centroid?** :
return n
else:
return -n # change direction
I actually need the normal vertex vectors as I am exporting as a .obj file, but I am assuming that I would need to calculate the surface vectors before hand and combine them.
This solution should work under the assumption of a convex hull in 3d. You calculate the normal as shown in the question. You can normalize the normal vector with
n /= np.linalg.norm(n) # which should be sqrt(n[0]**2 + n[1]**2 + n[2]**2)
You can then calculate the center point of your input triangle:
pmid = (p1 + p2 + p3) / 3
After that you calculate the distance of the triangle-center to your surface centroid. This is
dist_centroid = np.linalg.norm(pmid - centroid)
The you can calculate the distance of your triangle_center + your normal with the length of the distance to the centroid.
dist_with_normal = np.linalg.norm(pmid + n * dist_centroid - centroid)
If this distance is larger than dist_centroid, then your normal is facing outwards. If it is smaller, it is pointing inwards. If you have a perfect sphere and point towards the centroid, it should almost be zero. This may not be the case for your general surface, but the convexity of the surface should make sure, that this is enough to check for its direction.
if(dist_centroid < dist_with_normal):
n *= -1
Another, nicer option is to use a scalar product.
pmid = (p1 + p2 + p3) / 3
if(np.dot(pmid - centroid, n) < 0):
n *= -1
This checks if your normal and the vector from the mid of your triangle to the centroid have the same direction. If that is not so, change the direction.

Equation to find average of multiple velocities?

I need to find the average Edit: total 2D velocity given multiple 2D velocities (speed and direction). A few examples:
Example 1
Velocity 1 is 90° at a speed of 10 pixels or units per second.
Velocity 2 is 270° at a speed of 5 pixels or units per second.
The average velocity is 90° at 5 pixels or units per second.
Example 2
Velocity 1 is 0° at a speed of 10 pixels or units per second
Velocity 2 is 180° at a speed of 10 pixels or units per second
Velocity 3 is 90° at a speed of 8 pixels or units per second
The average velocity is 90° at 8 pixels or units per second
Example 3
Velocity 1 is 0° at 10 pixels or units per second
Velocity 2 is 90° at 10 pixels or units per second
The average velocity is 45° at 14.142 pixels or units per second
I am using JavaScript but it's mostly a language-independent question and I can convert it to JavaScript if necessary.
If you're going to be using a bunch of angles, I would just calculate each speed,
vx = v * cos(theta),
vy = v * sin(theta)
then sum the x velocities and the y velocities separately as vector components and divide by the total number of velocities,
sum(vx) / total v, sum(vy) / total v
and then finally calculate the final speed and direction with your final vx and vy. The magnitude of the speed can be found by a simple application of pythagorean theorem, and then final angle should just be tan-1(y/x).
Per example #3
vx = 10 * cos(90) + 10 * cos(0) = 10,
vy = 10 * sin(90) + 10 * sin(0) = 10
so, tan-1(10/10) = tan-1(1) = 45
then a final magnitude of sqrt(10^2 + 10^2) = 14.142
These are vectors, and you should use vector addition to add them. So right and up are positive, while left and down are negative.
Add your left-to-right vectors (x axis).
Example 1 = -10+5 = -5
Example 2 = -8 = -8
Example 3 = 10 = 10. (90 degrees is generally 90 degrees to the right)
Add you ups and downs similarly and you get these velocities, your left-to-right on the left in the brackets, and your up-to-down on the right.
(-5, 0)
(-8,0)
(10, 10)
These vectors contain all the information you need to plot the motion of an object, you do not need to calculate angles to plot the motion of the object. If for some reason you would rather use speeds (similar to velocity, but different) and angles, then you must first calculate the vectors as above and then use the Pythagorean theorem to find the speed and simple trigonometry to get the angle. Something like this:
var speed = Math.sqrt(x * x + y * y);
var tangeant = y / x;
var angleRadians = Math.atan(tangeant);
var angleDegrees = angleRadians * (180 / Math.PI);
I'll warn you that you should probably talk to someone who know trigonometry and test this well. There is potential for misleading bugs in work like this.
From your examples it sounds like you want addition of 2-dimensional vectors, not averages.
E.g. example 2 can be represented as
(0,10) + (0,-10) + (-8, 0) = (-8,0)
The speed is then equal to the length of the vector:
sqrt(x^2+y^2)
To get average:
add each speed, and then divide by the number of speeds.
10mph + 20mph / 2 = 15
12mph + 14mph + 13mph + 16mph / 4 = 14 (13,75)
This is not so much average as it is just basic vector addition. You're finding multiple "pixel vectors" and adding them together. If you have a velocity vector of 2 pixels to the right, and 1 up, and you add it to a velocity vector of 3 pixels to the left and 2 down, you will get a velocity vector of 1 pixel left, and 1 down.
So the speed is defined by
pij = pixels going up or (-)down
pii = pixels going right or (-)left
speedi = pii1 + pii2 = 2-3 = -1 (1 pixel left)
speedj = pij1 + pij2 = 1-2 = -1 (1 pixel down)
From there, you need to decide which directions are positive, and which are negative. I recommend that left is negative, and down is negative (like a mathematical graph).
The angle of the vector, would be the arctan(speedj/speedi)
arctan(-1/-1) = 45 degrees

Fast formula for a "high contrast" curve

My inner loop contains a calculation that profiling shows to be problematic.
The idea is to take a greyscale pixel x (0 <= x <= 1), and "increase its contrast". My requirements are fairly loose, just the following:
for x < .5, 0 <= f(x) < x
for x > .5, x < f(x) <= 1
f(0) = 0
f(x) = 1 - f(1 - x), i.e. it should be "symmetric"
Preferably, the function should be smooth.
So the graph must look something like this:
.
I have two implementations (their results differ but both are conformant):
float cosContrastize(float i) {
return .5 - cos(x * pi) / 2;
}
float mulContrastize(float i) {
if (i < .5) return i * i * 2;
i = 1 - i;
return 1 - i * i * 2;
}
So I request either a microoptimization for one of these implementations, or an original, faster formula of your own.
Maybe one of you can even twiddle the bits ;)
Consider the following sigmoid-shaped functions (properly translated to the desired range):
error function
normal CDF
tanh
logit
I generated the above figure using MATLAB. If interested here's the code:
x = -3:.01:3;
plot( x, 2*(x>=0)-1, ...
x, erf(x), ...
x, tanh(x), ...
x, 2*normcdf(x)-1, ...
x, 2*(1 ./ (1 + exp(-x)))-1, ...
x, 2*((x-min(x))./range(x))-1 )
legend({'hard' 'erf' 'tanh' 'normcdf' 'logit' 'linear'})
Trivially you could simply threshold, but I imagine this is too dumb:
return i < 0.5 ? 0.0 : 1.0;
Since you mention 'increasing contrast' I assume the input values are luminance values. If so, and they are discrete (perhaps it's an 8-bit value), you could use a lookup table to do this quite quickly.
Your 'mulContrastize' looks reasonably quick. One optimization would be to use integer math. Let's say, again, your input values could actually be passed as an 8-bit unsigned value in [0..255]. (Again, possibly a fine assumption?) You could do something roughly like...
int mulContrastize(int i) {
if (i < 128) return (i * i) >> 7;
// The shift is really: * 2 / 256
i = 255 - i;
return 255 - ((i * i) >> 7);
A piecewise interpolation can be fast and flexible. It requires only a few decisions followed by a multiplication and addition, and can approximate any curve. It also avoids the courseness that can be introduced by lookup tables (or the additional cost in two lookups followed by an interpolation to smooth this out), though the lut might work perfectly fine for your case.
With just a few segments, you can get a pretty good match. Here there will be courseness in the color gradients, which will be much harder to detect than courseness in the absolute colors.
As Eamon Nerbonne points out in the comments, segmentation can be optimized by "choos[ing] your segmentation points based on something like the second derivative to maximize detail", that is, where the slope is changing the most. Clearly, in my posted example, having three segments in the middle of the five segment case doesn't add much more detail.