Related
I want to get length scale, length unit, energy scale, and energy unit from an EELS map data by DM script
source.ImageGetDimensionCalibration(0, sOrig, sScale, sUnit, 0)
source.ImageGetDimensionCalibration(2, eOrig, eScale, eUnit, 0)
The above codes did not work, it always make the energy unit with 1eV
For example, an EELS data with dispersion 0.9 ev/pixel, or 0.5 ev/pixel, and the energy range from 0 to 400, and the EELS map is 10nm x8nm, I need to get all this information with DM script.
Now the bug of my code is it always count the disepersion is 1 ev/pixel
Any suggestions, thanks
The following script will hopefully clear up any questions:
void PrintCalibrations(image source)
{
number nDim = source.ImageGetNumDimensions()
result("\n '[" + source.ImageGetLabel() +"]:"+ source.ImageGetName() +"' calibrations:")
result("\n Data has " + nDim + " dimensions.")
for( number d=0; d<nDim; d++ )
{
string unit_dim
number scale_dim, origin_dim
source.ImageGetDimensionCalibration(d, origin_dim, scale_dim, unit_dim, 0)
result("\n\t Dimension #"+d+": scale = " + scale_dim + " \t origin = " + origin_dim + " \t [" + unit_dim + "]" )
}
}
// Pick your data by image letter!
Image SIdata_3D := B
Image PickerSpectrum_1D := A
Image elementalMap_2D := D
ClearResults()
PickerSpectrum_1D.PrintCalibrations()
elementalMap_2D.PrintCalibrations()
SIdata_3D.PrintCalibrations()
Applied to a test data set:
In order to perform drift correction in a SI image as shown in the following figure:
I write the code :
number max_shift=5
image src := GetFrontImage()
number sx, sy, sz
src.Get3DSize(sx, sy, sz)
result("sx: "+sx+"\n")
result("sy: "+sy+"\n")
result("sz: "+sz+"\n")
// assume a random shift in x
image shift := IntegerImage("xcorrd",4,0, 1, sy)
shift = max_shift*Random()
// make a coordinate table
image col := IntegerImage("col",4,0, sx, sy)
image row := IntegerImage("row",4,0, sx, sy)
image plane := IntegerImage("plane",4,0, sx, sy)
col = icol
row = irow
plane = iplane
// to expand the shift as the same size with source image
image ones := IntegerImage("ones",4,0, sx, sy)
ones = 1
// create a random column shift of the source SI image
for(number i=0; i<sy; i++) {
col[i,0,i+1,sx] = col[i,0,i+1,sx]+shift.GetPixel(0,i)*ones[i,0,i+1,sx]
};
// drift corrected
image im := RealImage("test si", 4, sx+max_shift, sy, sz)
im=0
im[col, row, plane] = src[icol,irow,iplane]
im.ImageGetTagGroup().TagGroupCopyTagsFrom(src.ImageGetTagGroup())
im.ImageCopyCalibrationFrom(src)
im.SetName(src.GetName()+"-drift corrected")
im.showimage()
The image can be corrected, however the spectrum cannot be transferred to the corrected SI as shown :
I am just wondering what's wrong with my script.
Thank you in advance.
im[col, row, plane] = src[icol,irow,iplane]
The intrinsic variables icol, irow, iplane will be evaluated by the only fixed size image expression in the line. In your case col, row and plane (all of same size)
However, they are all 2D so what is internally happening is that you iterate over X & Y and then write the values:
im[ col(x,y), row(x,y), plane(x,y) ] = src[x,y,0] // iterated over all x/y
As Don I was mentioning in the comments, you would want to iterate over the z dimension.
Alternatively, you could make all of your images of size (sx,sy,sz) in your script.
This would work for the expression, but is horrifically inefficient.
In general, the best solution here is to no t use icol,irow,iplane at all, but make use of the Slice commands. see this answer:
I would possibly code a line-wise x-shift for an SI like below:
The script utilizes the fact that one can shift whole "blocks" (X x 1 x Z) in x-direction, iterating over y.
number sx = 256
number sy = 256
number sz = 100
image testSI := realImage("SI",4,sx,sy,sz)
testSI = sin(itheta/(idepth-iplane)*idepth) + (iplane % (icol+1))/idepth
testSI.ShowImage()
image xdrift := RealImage("XDrift per line",4,sy)
xdrift = trunc(random()*5 + 20*sin(icol/iwidth*3*PI()))
xdrift.ShowImage()
// Apply linewise Drift to SI, assuming xDrift holds this data
xDrift -= min(xDrift) // ensure only positive shifts
image outSI := realImage("SI shifted",4,sx+max(xDrift),sy,sz)
outSI.ShowImage()
for( number y=0; y<sy; y++ ){
number yShift = sum(xDrift[y,0])
outSI.slice2( yShift,y,0, 0,sx,1, 2,sz,1 ) = testSI.slice2(0,y,0,0,sx,1,2,sz,1)
}
The script below performs the iteration "plane by plane", but does not have a restriction on the plane-wise shift.
In fact, here each pixel gets an assigned XY shift.
Note that you can use warp(source, xexpr, yexpr ) instead of 2D addressing source[ xexpr, yexpr ] if you want to use bilinear interploation of values (and 0 truncation outside the valid range).
number sx = 256
number sy = 256
number sz = 100
image testSI := realImage("SI",4,sx,sy,sz)
testSI = sin(itheta/(idepth-iplane)*idepth) + (iplane % (icol+1))/idepth
testSI.ShowImage()
image xdrift := RealImage("pixelwise XDrift",4,sx,sy)
xdrift = irow%10*random() + 20*cos(irow/iheight*5*PI())
xdrift.ShowImage()
image ydrift := RealImage("pixelwise yDrift",4,sx,sy)
ydrift = 10*abs(cos(icol/iwidth* (irow/iheight) * 10 * PI())) + irow/iheight * 10
ydrift.ShowImage()
// Apply pixelwise Drift to SI
xDrift -= min(xDrift) // ensure only positive shifts
yDrift -= min(yDrift) // ensure only positive shifts
number mx = max(xDrift)
number my = max(yDrift)
image outSI := realImage("SI shifted",4,sx+mx,sy+my,sz)
outSI.ShowImage()
for( number z=0;z<sz;z++){
image outPlane := outSI.Slice2( 0,0,z, 0,sx+mx,1,1,sy+my,1)
image srcPlane := testSI.Slice2( 0,0,z, 0,sx,1,1,sy,1)
outPlane = srcPlane[ icol + xdrift[icol,irow] - mx, irow + ydrift[icol,irow] - my ]
// outPlane = srcPlane.warp( icol + xdrift[icol,irow] - mx, irow + ydrift[icol,irow] - my)
}
I am trying to draw line projection for an image . The line 4 in the code below sy/2 represents the length of projection (here is the half image range). But how to set the starting point or ending point with scripting? For example, I want to draw the line projection, from 1/4 image range to 3/4 image range. Any suggestions?
image src := getfrontimage()
number sx,sy
src.GetSize(sx,sy)
image line_projection := RealImage( "Vertical", 4, sy/2 )
line_projection[irow,0] += src
line_projection *= 1/sx
While using intrinsic variables (icol,irow,...) for iterative summing was the fasted method in GMS 1, this is no longer true for newer versions that utilize multi-threaded code, as demonstrated by the following example:
// various ways to sum a subsection of an image
number sx = 4096, sy = 4096
number startx = 0.2, starty = 0.2
number endx = 0.8, endy = 0.4
// Coordinates of cut
number t = trunc(starty*sy), l = trunc(startx*sx), b = trunc(endy*sy), r = trunc(endx*sx)
image test := realImage( "Test", 4, sx, sy )
test = sin( icol/iwidth * 20*Pi()) + cos( itheta * iradius/iwidth * 50)
test= sin( icol/iwidth * 20*Pi())
test.ShowImage()
ROI marker = NewROI()
marker.ROISetLabel( "Section" )
marker.ROISetRectangle( t, l, b, r )
marker.ROISetVolatile( 0 )
test.ImageGetImageDisplay(0).ImageDisplayAddRoi( marker )
//OKDialog( "Performing vertical sum with various methods now." )
number h = b - t
number w = r - l
ClearResults()
number ts, te, tps = GetHighResTicksPerSecond()
// using intrinsic variables
image sumImg1 := RealImage( "Sum intrinsic", 4, w )
ts = GetHighResTickcount()
sumImg1[icol, 0] += test[t,l,b,r];
te = GetHighResTickcount()
sumImg1.ShowImage()
result("\n Summing using intrinisic variables: " + (te-ts)/tps + " sec")
// using for-loop of slice
image sumImg2 := RealImage( "Sum with slice", 4, w )
ts = GetHighResTickcount()
for( number i=0; i<h; i++)
sumImg2 += test.slice1(0,i,0, 0,w,1)
te = GetHighResTickcount()
sumImg2.ShowImage()
result("\n Summing using for-loop with slice : " + (te-ts)/tps + " sec")
// using project of slice
image sumImg3 := RealImage( "Sum with project", 4, w )
ts = GetHighResTickcount()
sumImg3 = test[t,l,b,r].project( 1 )
te = GetHighResTickcount()
sumImg3.ShowImage()
result("\n Summing using project on section : " + (te-ts)/tps + " sec")
You can use slicing to only look at the image area you are interested in. For "clipping" the source to the interesting part use img[y1, x1, y2, x2].
image src := getFrontImage();
number width, height;
src.GetSize(width, height);
number start_y = 1/4 * height;
number end_y = 3/4 * height;
image line_projection := RealImage("Vertical", 4, width);
line_projection[icol, 0] += src[start_y, 0, end_y, width];
line_projection *= 1/(height/2);
line_projection.ShowImage();
For a line profile (curve), I want to reach that list all X coordinates that corresponding a Y coordinate by given this Y coordinate. And I could get the minimum and maximum values of these x coordinates. Here supposed I want to list all the X coordinates corresponding y=8, is this correct or any other better way? Thx
Number minx, maxx
Image front=:getfrontimage()
GetSize( front, xsize, ysize )
for (i=0; i<xsize; i++)
{
x= getpixel(front, i, 8)
minx=min(x)
maxx=max(x)
}
You script becomes wrong when you use the min and max, because you can not get a minimum/maximum of a single value (or rather, it is always that value). What you want to do is likely:
image spec := RealImage("Test",4,100)
spec = trunc(Random()*10)
number v = 8
ClearResults()
number nCh = spec.ImageGetDimensionSize(0)
for( number i=0; i<nCh; i++)
{
if( v == sum(spec[i,0]) )
Result("\n Value "+ v +" # " + i )
}
(The sum() is needed here a a trick to convert an image-expression to a single value.)
However, going pixel-by-pixel in script can be slow. Whenever possible, try to code with image-expressions, because they are much faster (for big images).
I therefore often utilize a trick: I threshold an image for the value I search for, and then iterate over that mask as long as it is not all-zero. The max(img,x,y) command will return the first maximum if there are multiple, so I get an ordered list.
image spec := RealImage("Test",4,100)
spec = trunc(Random()*10)
spec.ShowImage()
number v = 8
image mask = (spec==v)?1:0
ClearResults()
while( 0<sum(mask) )
{
number x,y
max(mask,x,y)
Result("\n Value " + v +" # " + x )
mask[x,0]=0
}
Edit: Answering the question of the comment below.
This is how one gets the ZLP maximum (position and value) from a line-profile in calibrated values.
Precursor: DM contains all data as simple arrays and values (real or integer). These are the real data and unrelated to any calibrations. You see these values if you toggle the "calibration" checkbox off in the Image Status palette:
These are the values all script commands etc. will use, i.e. positions are always indices (starting from 0) and values are the raw numeric values stored.
These images or spectra are calibrated by defining an origin and scale (and unit) for each dimensional axis as well as the intensity (=value). These triplets of values can be found in the image display info of data:
Only when the "Show calibrated values" checkbox is checked, is the data displayed in calibrated values. However, the real values remain unchanged. Just the scale/origin values are used to convert the numbers.
If you want to use a script to use calibrated values, then you have to perform the same conversions in you script yourself.
Here is the example:
image spectrum := GetFrontImage()
number xScale = spectrum.ImageGetDimensionScale(0) // 0 for X dimension
number xOrigin = spectrum.ImageGetDimensionOrigin(0)
string xUnit = spectrum.ImageGetDimensionUnitString(0)
number iScale = spectrum.ImageGetIntensityScale()
number iOrigin = spectrum.ImageGetIntensityOrigin()
string iUnit = spectrum.ImageGetIntensityUnitString()
string info = "\n"
info += "Image ["+spectrum.ImageGetLabel()+"]:"
info += "\n\t Dimension calibration: nCh * " + xScale + " + " + xOrigin + " [" + xUnit + "]"
info += "\n\t Intensity calibration: (value - " + iOrigin + ") * " + iScale +" [" + iUnit + "]"
Result(info)
// Find ZLP maximum (uncalibrated values)
number maxP_ch, dummy, maxV_raw
maxV_raw = max(spectrum,maxP_ch,dummy)
info = "\n"
info += "\n\t The maximum position is at channel index: " + maxP_ch
info += "\n\t The maximum Value at maximum position is: " + maxV_raw
Result(info)
number maxP_cal = xOrigin + xScale * maxP_ch
number maxV_cal = (maxV_raw - iOrigin) * iScale
info = "\n"
info += "\n\t The maximum position is at : " + maxP_cal
info += "\n\t The maximum Value is : " + maxV_cal
Result(info)
Note the different calibration formulas between dimensional calibration and intensity calibration!
I want to allow a user to change the diameter of an oval ROI or annotation with a fixed axes ratio (for circles, the ratio is 1:1). Also, the Oval should open up around the center only but not be allowed to shift sideways.
Currently, I have a script that reads the ROI and corrects when the user strays from the shape or the location of its center. However, it looks rather annoying and confusing, for example when the circle is changed to an oval and then pops back to a circle. I was hoping for commands that allow e.g., resize (with the option of fixed axes ratio) but restrict (lateral) movement.
Any suggestion is very welcome.
This question is a specific application of the more general question here.
The trick is to act whenever the size of a ROI is changed and then replace it with the restrictions in place. This is done by attaching a "listener" method
which is invoked when the ROI is changed.
There are two ways one can do this for ROIs:
1) Attaching the listener to the imageDisplay on which the ROI sits
// EXAMPLE using ImageDisplay listener
// This will handle ALL ROIs on the display, so one would like to
// "filter" specific ROIs. f.e. by using the ROI property "Name".
class CCircleRestrict
{
number x0,y0
object Init( object self, number cx, number cy ){ x0 = cx; y0 = cy; return self; }
void OnRestrict( object self, number e_fl, ImageDisplay idisp, number r_fl, number r_fl2, ROI theROI )
{
if ( theROI.ROIGetName() != "special" ) return; // Skip, if it isn't "our" ROI
if ( !theROI.ROIIsOval() ) return; // Skip, if it isn't an oval ROI
// get size of ROI ( as currently dragged by user )
number t, l, b, r
theROI.ROIGetOval( t, l, b, r )
number radius = max( b - t, r - l ) / 2
// Re-Set the ROI centered on x0/y0 with the new radius
theROI.ROISetOval( y0 - radius, x0 - radius, y0 + radius, x0 + radius )
}
}
// Main script "attaches" the display listener to
void main()
{
// Create and show test image
number size = 512
number r1 = 20
number r2 = 20
number off = 100
image test := realImage( "Test", 4, size, size )
test.ShowImage()
imageDisplay disp = test.ImageGetImageDisplay(0)
// Add two oval ROIs, name one of them "special" for identification
ROI specialROI = NewROI()
specialROI.ROISetName( "special" )
specialROI.ROISetOval( size/2 - r1, size/2 - r1, size/2 + r1, size/2 + r1 )
specialROI.ROISetVolatile(0)
specialROI.ROISetColor(0.1,0.9,0.1)
disp.ImageDisplayAddROI(specialROI)
ROI otherROI = NewROI()
otherROI.ROISetOval( off + size/2 - r2, off + size/2 - r2, off + size/2 + r2, off + size/2 + r2 )
otherROI.ROISetVolatile(0)
disp.ImageDisplayAddROI(otherROI)
// Create listener object and attach listener to display
object dispListener = Alloc(CCircleRestrict).Init( size/2, size/2 )
disp.ImageDisplayAddEventListener( dispListener, "roi_property_changed:OnRestrict" )
}
EGUPerformActionWithAllShownImages( "Delete" )
main()
EGUPerformActionWithAllShownImages( "Arrange" )
2) Attaching the listener to the ROI object itself
// EXAMPLE using ROI listener
// This will handle changes a specific ROI, regardless of the display(s) it is on
class CCircleRestrict
{
number x0,y0
object Init( object self, number cx, number cy ){ x0 = cx; y0 = cy; return self; }
void OnRestrict( object self, ROI theROI )
{
if ( !theROI.ROIIsOval() ) return; // Skip, if it isn't an oval ROI
// get size of ROI ( as currently dragged by user )
number t, l, b, r
theROI.ROIGetOval( t, l, b, r )
number radius = max( b - t, r - l ) / 2
// Re-Set the ROI centered on x0/y0 with the new radius
theROI.ROISetOval( y0 - radius, x0 - radius, y0 + radius, x0 + radius )
}
}
// Main script "attaches" the listener to the ROI
void main()
{
// Create and show test image
number size = 512
number r1 = 20
number r2 = 20
number off = 100
image test := realImage( "Test", 4, size, size )
test.ShowImage()
imageDisplay disp = test.ImageGetImageDisplay(0)
// Add two oval ROIs
ROI specialROI = NewROI()
specialROI.ROISetOval( size/2 - r1, size/2 - r1, size/2 + r1, size/2 + r1 )
specialROI.ROISetVolatile(0)
specialROI.ROISetColor(0.1,0.9,0.1)
disp.ImageDisplayAddROI(specialROI)
ROI otherROI = NewROI()
otherROI.ROISetOval( off + size/2 - r2, off + size/2 - r2, off + size/2 + r2, off + size/2 + r2 )
otherROI.ROISetVolatile(0)
disp.ImageDisplayAddROI(otherROI)
// Create listener object and attach listener to specific ROI
object roiListener = Alloc(CCircleRestrict).Init( size/2, size/2 )
ConnectObject( specialROI.ROIGetID(), "changed", "EventID_Name", roiListener, "OnRestrict" )
}
EGUPerformActionWithAllShownImages( "Delete" )
main()
EGUPerformActionWithAllShownImages( "Arrange" )
Both examples above restrict the oval ROI to a circle, but it is straight forward to change it such that a specific aspect ratio of the oval is achieved.
However, it is important to consider that the "newly set" and adjusted
ROI will itself again trigger the listener. It has to be ensured that
no infinite loop is created in this way, i.e. the triggering of the
method for the second time must not result in new restrictions.
A simple example for an oval ROI with a 1 : 2 aspect ratio would use a restriction method as in:
void OnRestrict( object self, ROI theROI )
{
ar = 2
if ( !theROI.ROIIsOval() ) return; // Skip, if it isn't an oval ROI
// get size of ROI ( as currently dragged by user )
number t, l, b, r
theROI.ROIGetOval( t, l, b, r )
number w = r - l
number h = b - t
number newW = max( W, AR*H )
number newH = newW/AR
// Re-Set the ROI centered on x0/y0 with the new radius
theROI.ROISetOval( y0 - newH/2, x0 - newW/2, y0 + newH/2, x0 + newW/2 )
}
The answer above specifies the type of restricitions asked for, but for completeness sake, it should also be mentioned that there are some ROI properties which can be useful in this context.
From the F1 help documentation:
However, the moveable property supersedes the resizable property, i.e if you can't move, you can't resize.