Convert EELS map to line scan data but DM 3.0 still recognize it as a map - dm-script

I wrote a script to convert the EELS map to EELS line scan data, and it works well with DM 2.0. I can deal with it as directly collected EELS line scan data with DM2.0. But it does not work with DM 3.0 and the above version. It looks DM 3.0 still recognizes it as an EELS map file. DM3.0 still tried to generate elemental maps with multiple windows from it not generate line scan profiles with one single window and said the display type is incorrect. Not sure what code/command I need to add to fit the DM 3.0 and above versions. Appreciate any suggestions/comments.
image source
source := getFrontImage()
number sizeX,sizeY,sizeZ
source.Get3Dsize(sizeX,sizeY,sizeZ)
Result( "Original size:"+ sizeX +"; "+ sizeY+"; "+sizeZ+""+"\n" )
image sum
number regionsizeX = 1
number regionsizeY = sizeY
number row,col
Result( "new size:"+ regionsizeX +"; "+ regionsizeY+"; "+row+""+row+" "+"\n" )
sum := RealImage("Line Scan of [0,0,"+regionSizeY+","+regionSizeX+"]",4,sizeX/regionSizeX,sizey/regionsizeY,sizeZ)
//sum := ImageClone(source)
sum = 0
for (row=0;row<regionsizeY;row++) for (col=0;col<regionSizeX;col++)
{
OpenAndSetProgressWindow("Doing sub-block","x = "+col," y = "+row)
sum += Slice3(source,col,row,0,0,sizeX/regionSizeX,regionsizeX,1,sizeY/regionSizeY,regionSizeY,2,sizez,1)
}
OpenAndSetProgressWindow("","","")
ImageCopyCalibrationFrom(sum, source)
sum.setdisplaytype (1)
sum.SetStringNote( "Meta Data:Format", "Spectrum image" )
sum.SetStringNote( "Meta Data:Signal", "EELS" )
showimage(sum)

I'm also a bit confused by your terminology. When you write "Convert a Map into a LineScan" do you mean:
a) Convert a 3D Spectrum-Image (xy scan, one spectral dimension) into a 2D Line-Scan Spectrum-Image (one spatial dimension, one spectral dimension)
or
b) Convert a 2D Map (xy scan, one value) in a 1D Line-Trace (one spatial dimension, one value per point) ?
I suppose you mean a) and answer to that.
I'm surprised if/that your script would work without issues in GMS 2.
Your final (supposedly line-scan SI) data is still a 3D dataset with the dispersion running in Z-direction. This is not the typical LineScan SI data format (which is dispersion in X, spatial dimension in Y, no Z dimension).
Am I right in thinking that you want to "collapse" your 3D data along the y-dimension (by summing) ?
If so, what you want to do is:
// Get Input
image src3D := GetFrontImage()
number sizeX,sizeY,sizeZ
if ( 3 != src3D.ImageGetNumDimensions() ) Throw( "Input not 3D")
src3D.Get3Dsize(sizeX,sizeY,sizeZ)
// Optional: Use Rect-ROI on image to specify area
// If no selection, will return full FOV
number t,l,b,r
src3D.GetSelection(t,l,b,r)
// Prepare output (for summing 3D of rect-selection along y)
// NB: 2D container has:
// X dimension (spatial) along Y
// Z dimension (energy) along X
number nSpatial = r - l
number nSpectral = sizeZ
number eOrig, eScale, sOrig, sScale
string eUnit, sUnit
src3D.ImageGetDimensionCalibration(0, sOrig, sScale, sUnit, 0)
src3D.ImageGetDimensionCalibration(2, eOrig, eScale, eUnit, 0)
string name
if ( nSpatial != sizeX )
name = "Y-projection of [" + t + "," + l + "," + b + "," + r + "] over " + (b-t) + " rows"
else
name = "Y-projection over " + sizeY + " rows"
image dst2D := RealImage( name, 4, nSpectral, nSpatial )
dst2D.ImageSetDimensionCalibration(0, eOrig, eScale, eUnit, 0)
dst2D.ImageSetDimensionCalibration(1, sOrig, sScale, sUnit, 0)
// Copy Tags (contains necessary meta tags! Meta Data Format & Signal)
dst2D.ImageGetTagGroup().TagGroupCopyTagsFrom( src3D.ImageGetTagGroup() )
// Display (with captions)
dst2D.ShowImage()
dst2D.ImageGetImageDisplay(0).ImageDisplaySetCaptionOn(1)
number doFAST = 0
if ( !doFAST )
{
// Perform actuall summing (projection) by summing "line by line"
// into the LinePlot SI. Note the flipping of input and output dimensions!
for( number y = t; y<b; y++ )
{
number lineNumber = y - t
dst2D.slice2( 0,0,0, 0,nSpectral,1, 1,nSpatial,1 ) += src3D.slice2( l,y,0, 2,nSpectral,1, 0,nSpatial,1)
}
}
else
{
// Alternative (faster) projection. Use dedicated projection command.
image proj := src3D[l,t,0,r,b,nSpectral].Project(1) // Outcome of projectsion is having x=x and y=z, so need flip axis
dst2D = proj.slice2(0,0,0, 1,nSpectral,1, 0,nSpatial,1 ) // Flip axis
}
// Display (with captions)
dst2D.ShowImage()
dst2D.ImageGetImageDisplay(0).ImageDisplaySetCaptionOn(1)
Note that iterating using slice blocks is fast, but not as fast as the dedicated 'Project' command available in latest GMS versions. The example uses either, but lines #51-56 might not be available in older GMS.
Edit to address comment below:
Other relevant meta data for spectra is also found in the tags. For EELS, in particular the collection & convergence angle as well as the HT is of importance. You can find out about the tag-path by checking the tags of a properly acquired EELS spectrum.
Or, you can find out about their tag-paths by "converting" an empty 1D line-plot into an EELS spectrum and then attempting a quantification. You will get the prompt to fill in the data. After doing so, check the tags of the image:

Related

How to reshape the dimension of data cube in DM with script

I have an image with dimensions 1024*1024 stored in a HDF5 file, which is treated as a data cube of slice thickness 1, (so that stored dimension is 1024*1024*1) . I used the Niermann HDF5 plug-in (https://github.com/niermann/gms_plugin_hdf5) to import the data. After importing, the data cube became 1*1024*1024, and displayed as 1 pixel wide, 1024 pixels hight and 1024 slices images.
Before considering re-implement the plug-in, I'd like to ask, is there any way to "reshape" the data (like in "Numpy.reshape"), so that the dimensions can be properly treated?
Thanks!
If you don't like icol, irow and these expressions, another elegant solution is to just use the streaming object.
image ReShape3D( image input, number sx, number sy, number sz )
{
// Perform testing
number nPix=1
for ( number d=0; d<input.ImageGetNumDimensions(); d++ )
nPix *= input.ImageGetDimensionsize(d)
if ( sx*sy*sz < nPix ) Throw( "Input image larger than provided shape." )
if ( sx*sy*sz > nPix ) Throw( "Input image smaller than provided shape." )
image reShaped := input.Imageclone()
reShaped.ImageResize(3,sx,sy,sz)
object dStream = NewStreamFromBuffer(0)
ImageWriteImageDataToStream(input,dStream,0)
dStream.StreamSetPos(0,0)
ImageReadImageDataFromStream(reShaped,dStream,0)
return reshaped
}
Image before := RealImage("Before",4,10,20,30)
before = random()
Image after := ReShape3D( before,20,10,30 )
before.ShowImage()
after.ShowImage()
If your input/output array sizes do not match in dimension (such that slice would not work), then you can also 'stream' the data into and out of 1D using the following:
number sx = 4
number sy = 5
number sz = 2
image oneLine := RealImage( "1D",4, sx*sy*sz )
oneLine = icol
oneLine.ShowImage()
image reShape1Dto3D := RealImage( "1D->3D", 4, sx, sy, sz )
reShape1Dto3D = oneLine[icol + iwidth*irow + iwidth*iheight*iplane, 0 ]
reShape1Dto3D.ShowImage()
image reShape3Dto1D := RealImage( "3D->1D", 4, sx*sy*sz )
reShape3Dto1D[icol + iwidth*irow + iwidth*iheight*iplane, 0 ] = reShape1Dto3D
reShape3Dto1D.ShowImage()
The trick here is, that you can address a single value in an image expression using square-brackets. In a 3D image by [X,Y,Z], in a 2D image by [X,Y], and in a 1D images as [X,0]. [*]
The internal variables icol, irow, iplane are replaced by X,Y,Z coordinate of the evaluated expression, whereas iwidth, iheight and idepth are replaced by the dimension sizes of the evaluated expression.
What is the evaluated expression's size? It becomes defined by the only image of "known size" in the line - either left or right side, so that
reShape1Dto3D = oneLine[ icol + iwidth*irow + iwidth*iheight*iplane, 0 ]
becomes a loop over X/Y/Z of all pixels of reShape1Dto3D an the lefthand side of the expression. For each triplet (X/Y/Z) the value is taken from the computed position of oneLine.
Exactly the same is used in
reShape3Dto1D[ icol + iwidth*irow + iwidth*iheight*iplane, 0 ] = reShape1Dto3D
but here the loop is again over the size of reShape1Dto3D, because that is the image of "known size" in the line, even if it is on the righthand side.
* Higher dimensionality is not supported in this way, as [T,L,B,R] is already used for sub-areas.
After few more trial with the examples in "DM scripting handbook", a method is figured out:
image out = in.slice2(0,0,0, 1,1024,1, 2,1024,1)
that is, the output 2d image in the x-y take the projection of y-z plane of the input image using Slice2() command.

determinate: is point on line segment

I am trying to code a java methods which returns a Boolean true if a point(x,y) is on a line segment and false if not.
I tried this:
public static boolean OnDistance(MyLocation a, MyLocation b, MyLocation queryPoint) {
double value = java.lang.Math.signum((a.mLongitude - b.mLongitude) * (queryPoint.mLatitude - a.mLatitude)
- (b.mLatitude - a.mLatitude) * (queryPoint.mLongitude - a.mLongitude));
double compare = 1;
if (value == compare) {
return true;
}
return false;
}
but it doesn't work.
I am not JAVA coder so I stick to math behind ... For starters let assume you are on plane (not sphere surface)
I would use Vector math so let:
a,b - be the line endpoints
q - queried point
c=q-a - queried line direction vector
d=b-a - line direction vector
use dot product for parameter extraction
t=dot(c,d)/(|c|*|d|)
t is line parameter <0,1> if out of range q is not inside line
|c|=sqrt(c.x*c.x+c.y*c.y) size of vector
dot(c,d)=c.x*d.x+c.y*d.y scalar vector multiply
now compute corresponding point on line
e=a+(t*d)
e is the closest point to q on the line ab
compute perpendicular distance of q and ab
l=|q-e|;
if (l>treshold) then q is not on line ab else it is on the line ab. The threshold is the max distance from line you are still accepting as inside line. No need to have l sqrt-ed the threshold constant can be powered by 2 instead for speed.
if you add all this to single equation
then some things will simplify itself (hope did not make some silly math mistake)
l=|(q-a)-(b-a)*(dot(q-a,b-a)/|b-a|^2)|;
return (l<=treshold);
or
l=|c-(d*dot(c,d)/|d|^2)|;
return (l<=treshold);
As you can see we do not even need sqrt for this :)
[Notes]
If you need spherical or ellipsoidal surface instead then you need to specify it closer which it is what are the semi axises. The line become arc/curve and need some corrections which depends on the shape of surface see
Projecting a point onto a path
but can be done also by approximation and may be also by binary search of point e see:
mine approx class in C++
The vector math used can be found here at the end:
Understanding 4x4 homogenous transform matrices
Here 3D C++ implementation (with different names):
double distance_point_axis(double *p,double *p0,double *dp)
{
int i;
double l,d,q[3];
for (i=0;i<3;i++) q[i]=p[i]-p0[i]; // q = p-p0
for (l=0.0,i=0;i<3;i++) l+=dp[i]*dp[i]; // l = |dp|^2
for (d=0.0,i=0;i<3;i++) d+=q[i]*dp[i]; // d = dot(q,dp)
if (l<1e-10) d=0.0; else d/=l; // d = dot(q,dp)/|dp|^2
for (i=0;i<3;i++) q[i]-=dp[i]*d; // q=q-dp*dot(q,dp)/|dp|^2
for (l=0.0,i=0;i<3;i++) l+=q[i]*q[i]; l=sqrt(l); // l = |q|
return l;
}
Where p0[3] is any point on axis and dp[3] is direction vector of axis. The p[3] is the queried point you want the distance to axis for.

Switch on argument type

Using Open SCAD, I have a module that, like cube(), has a size parameter that can be a single value or a vector of three values. Ultimately, I want a vector of three values.
If the caller passes a single value, I'd like all three values of the vector to be the same. I don't see anything in the language documentation about detecting the type of an argument. So I came up with this hack:
module my_cubelike_thing(size=1) {
dimensions = concat(size, size, size);
width = dimensions[0];
length = dimensions[1];
height = dimensions[2];
// ... use width, length, and height ...
}
When size is a single value, the result of the concat is exactly what I want: three copies of the value.
When size is a three-value vector, the result of the concat is nine-value vector, and my code just ignores the last six values.
It works but only because what I want in the single value case is to replicate the value. Is there a general way to switch on the argument type and do different things depending on that type?
If type of size only can be single value or a vector with 3 values, the type can helpwise be found by the special value undef:
a = [3,5,8];
// a = 5;
if (a[0] == undef) {
dimensions = concat(a, a, a);
// do something
cube(size=dimensions,center=false);
}
else {
dimensions = a;
// do something
cube(size=dimensions,center=false);
}
But assignments are only valid in the scope in which they are defined , documnetation of openscad.
So in each subtree much code is needed and i would prefere to validate the type of size in an external script (e.g. python3) and write the openscad-code with the assignment of variables to a file, which can be included in the openscad-file, here my short test-code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
# size = 20
size = [20,15,10]
if type(size) == int:
dimensions = [size, size, size]
elif type(size) == list:
dimensions = size
else:
# if other types possible
pass
with open('variablen.scad', 'w') as wObj:
for i, v in enumerate(['l', 'w', 'h']):
wObj.write('{} = {};\n'.format(v, dimensions[i]))
os.system('openscad ./typeDef.scad')
content of variablen.scad:
l = 20;
w = 15;
h = 10;
and typeDef.scad can look like this
include <./variablen.scad>;
module my_cubelike_thing() {
linear_extrude(height=h, center=false) square(l, w);
}
my_cubelike_thing();

Description of parameters of GDAL SetGeoTransform

Can anyone help me with parameters for SetGeoTransform? I'm creating raster layers with GDAL, but I can't find description of 3rd and 5th parameter for SetGeoTransform. It should be definition of x and y axis for cells. I try to find something about it here and here, but nothing.
I need to find description of these two parameters... It's a value in degrees, radians, meters? Or something else?
The geotransform is used to convert from map to pixel coordinates and back using an affine transformation. The 3rd and 5th parameter are used (together with the 2nd and 4th) to define the rotation if your image doesn't have 'north up'.
But most images are north up, and then both the 3rd and 5th parameter are zero.
The affine transform consists of six coefficients returned by
GDALDataset::GetGeoTransform() which map pixel/line coordinates into
georeferenced space using the following relationship:
Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2)
Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5)
See the section on affine geotransform at:
https://gdal.org/tutorials/geotransforms_tut.html
I did do like below code.
As a result I was able to do same with SetGeoTransform.
# new file
dst = gdal.GetDriverByName('GTiff').Create(OUT_PATH, xsize, ysize, band_num, dtype)
# old file
ds = gdal.Open(fpath)
wkt = ds.GetProjection()
gcps = ds.GetGCPs()
dst.SetGCPs(gcps, wkt)
...
dst.FlushCache()
dst = Nonet
Given information from the aforementioned gdal datamodel docs, the 3rd & 5th parameters of SatGeoTransform (x_skew and y_skew respectively) can be calculated from two control points (p1, p2) with known x and y in both "geo" and "pixel" coordinate spaces. p1 should be above-left of p2 in pixelspace.
x_skew = sqrt((p1.geox-p2.geox)**2 + (p1.geoy-p2.geoy)**2) / (p1.pixely - p2.pixely)`
y_skew = sqrt((p1.geox-p2.geox)**2 + (p1.geoy-p2.geoy)**2) / (p1.pixelx - p2.pixelx)`
In short this is the ratio of Euclidean distance between the points in geospace to the height (or width) of the image in pixelspace.
The units of the parameters are "geo"length/"pixel"length.
Here is a demonstration using the corners of the image stored as control points (gcps):
import gdal
from math import sqrt
ds = gdal.Open(fpath)
gcps = ds.GetGCPs()
assert gcps[0].Id == 'UpperLeft'
p1 = gcps[0]
assert gcps[2].Id == 'LowerRight'
p2 = gcps[2]
y_skew = (
sqrt((p1.GCPX-p2.GCPX)**2 + (p1.GCPY-p2.GCPY)**2) /
(p1.GCPPixel - p2.GCPPixel)
)
x_skew = (
sqrt((p1.GCPX-p2.GCPX)**2 + (p1.GCPY-p2.GCPY)**2) /
(p1.GCPLine - p2.GCPLine)
)
x_res = (p2.GCPX - p1.GCPX) / ds.RasterXSize
y_res = (p2.GCPY - p1.GCPY) / ds.RasterYSize
ds.SetGeoTransform([
p1.GCPX,
x_res,
x_skew,
p1.GCPY,
y_skew,
y_res,
])

Storing plot objects in a list

I asked this question yesterday about storing a plot within an object. I tried implementing the first approach (aware that I did not specify that I was using qplot() in my original question) and noticed that it did not work as expected.
library(ggplot2) # add ggplot2
string = "C:/example.pdf" # Setup pdf
pdf(string,height=6,width=9)
x_range <- range(1,50) # Specify Range
# Create a list to hold the plot objects.
pltList <- list()
pltList[]
for(i in 1 : 16){
# Organise data
y = (1:50) * i * 1000 # Get y col
x = (1:50) # get x col
y = log(y) # Use natural log
# Regression
lm.0 = lm(formula = y ~ x) # make linear model
inter = summary(lm.0)$coefficients[1,1] # Get intercept
slop = summary(lm.0)$coefficients[2,1] # Get slope
# Make plot name
pltName <- paste( 'a', i, sep = '' )
# make plot object
p <- qplot(
x, y,
xlab = "Radius [km]",
ylab = "Services [log]",
xlim = x_range,
main = paste("Sample",i)
) + geom_abline(intercept = inter, slope = slop, colour = "red", size = 1)
print(p)
pltList[[pltName]] = p
}
# close the PDF file
dev.off()
I have used sample numbers in this case so the code runs if it is just copied. I did spend a few hours puzzling over this but I cannot figure out what is going wrong. It writes the first set of pdfs without problem, so I have 16 pdfs with the correct plots.
Then when I use this piece of code:
string = "C:/test_tabloid.pdf"
pdf(string, height = 11, width = 17)
grid.newpage()
pushViewport( viewport( layout = grid.layout(3, 3) ) )
vplayout <- function(x, y){viewport(layout.pos.row = x, layout.pos.col = y)}
counter = 1
# Page 1
for (i in 1:3){
for (j in 1:3){
pltName <- paste( 'a', counter, sep = '' )
print( pltList[[pltName]], vp = vplayout(i,j) )
counter = counter + 1
}
}
dev.off()
the result I get is the last linear model line (abline) on every graph, but the data does not change. When I check my list of plots, it seems that all of them become overwritten by the most recent plot (with the exception of the abline object).
A less important secondary question was how to generate a muli-page pdf with several plots on each page, but the main goal of my code was to store the plots in a list that I could access at a later date.
Ok, so if your plot command is changed to
p <- qplot(data = data.frame(x = x, y = y),
x, y,
xlab = "Radius [km]",
ylab = "Services [log]",
xlim = x_range,
ylim = c(0,10),
main = paste("Sample",i)
) + geom_abline(intercept = inter, slope = slop, colour = "red", size = 1)
then everything works as expected. Here's what I suspect is happening (although Hadley could probably clarify things). When ggplot2 "saves" the data, what it actually does is save a data frame, and the names of the parameters. So for the command as I have given it, you get
> summary(pltList[["a1"]])
data: x, y [50x2]
mapping: x = x, y = y
scales: x, y
faceting: facet_grid(. ~ ., FALSE)
-----------------------------------
geom_point:
stat_identity:
position_identity: (width = NULL, height = NULL)
mapping: group = 1
geom_abline: colour = red, size = 1
stat_abline: intercept = 2.55595281266726, slope = 0.05543539319091
position_identity: (width = NULL, height = NULL)
However, if you don't specify a data parameter in qplot, all the variables get evaluated in the current scope, because there is no attached (read: saved) data frame.
data: [0x0]
mapping: x = x, y = y
scales: x, y
faceting: facet_grid(. ~ ., FALSE)
-----------------------------------
geom_point:
stat_identity:
position_identity: (width = NULL, height = NULL)
mapping: group = 1
geom_abline: colour = red, size = 1
stat_abline: intercept = 2.55595281266726, slope = 0.05543539319091
position_identity: (width = NULL, height = NULL)
So when the plot is generated the second time around, rather than using the original values, it uses the current values of x and y.
I think you should use the data argument in qplot, i.e., store your vectors in a data frame.
See Hadley's book, Section 4.4:
The restriction on the data is simple: it must be a data frame. This is restrictive, and unlike other graphics packages in R. Lattice functions can take an optional data frame or use vectors directly from the global environment. ...
The data is stored in the plot object as a copy, not a reference. This has two
important consequences: if your data changes, the plot will not; and ggplot2 objects are entirely self-contained so that they can be save()d to disk and later load()ed and plotted without needing anything else from that session.
There is a bug in your code concerning list subscripting. It should be
pltList[[pltName]]
not
pltList[pltName]
Note:
class(pltList[1])
[1] "list"
pltList[1] is a list containing the first element of pltList.
class(pltList[[1]])
[1] "ggplot"
pltList[[1]] is the first element of pltList.
For your second question: Multi-page pdfs are easy -- see help(pdf):
onefile: logical: if true (the default) allow multiple figures in one
file. If false, generate a file with name containing the
page number for each page. Defaults to ‘TRUE’.
For your main question, I don't understand if you want to store the plot inputs in a list for later processing, or the plot outputs. If it is the latter, I am not sure that plot() returns an object you can store and retrieve.
Another suggestion regarding your second question would be to use either Sweave or Brew as they will give you complete control over how you display your multi-page pdf.
Have a look at this related question.