I would like to ask if it is possible to locate the position of every maxima and minima of an intensity profile on DM.
How do I come up with a simple script that identifies the positions of the peaks in the example below?
Here's a screenshot of line intensity profile of a STEM image along the Y-direction:
If you want to filter for "strict" local maxima, then you can easily do this with image expressions and the conditional "tert" operator. The following example illustrates this:
image CreateTestSpec( number nChannels, number nPeaks, number amp, number back )
{
image testImg := RealImage( "TestSpec", 4, nChannels )
testImg = amp * cos( PI() + 2*PI() * nPeaks * icol/(iwidth-1) )
testImg += back
testImg = PoissonRandom( testImg )
return testImg
}
image FilterLocalMaxima1D( image spectrumIn, number range )
{
image spectrumOut := spectrumIn.ImageClone()
for( number dx = -range; dx<=range; dx++ )
spectrumout *= ( spectrumIn >= offset(spectrumIn,dx,0) ? 1 : 0 )
spectrumout.SetName("Local maxima ("+range+") filtered")
return spectrumOut
}
image test1 := CreateTestSpec(256,10,1000,5000)
image test2 := FilterLocalMaxima1D(test1,3)
test1.ShowImage()
test2.ShowImage()
However, considering noise (also in your example image), you might want to perform fits around these "local maxima" to ensure you're really getting what you want. The data from above is then only the starting point for that.
Also: Sometimes you can get away with first averaging your data and then finding the local maxima, instead of doing real data fitting in each peak. This works in particular, if you "know" the width of your real peaks rather well.
This would be the example:
image CreateTestSpec( number nChannels, number nPeaks, number amp, number back )
{
image testImg := RealImage( "TestSpec", 4, nChannels )
testImg = amp * cos( PI() + 2*PI() * nPeaks * icol/(iwidth-1) )
testImg += back
testImg = PoissonRandom( testImg )
return testImg
}
image FilterLocalMaxima1D( image spectrumIn, number range )
{
image spectrumOut := spectrumIn.ImageClone()
for( number dx = -range; dx<=range; dx++ )
spectrumout *= ( spectrumIn >= offset(spectrumIn,dx,0) ? 1 : 0 )
spectrumout.SetName("Local maxima ("+range+") filtered")
return spectrumOut
}
image FilterLocalMaxima1DAveraged( image spectrumIn, number range )
{
image avSpectrum := spectrumIn.ImageClone()
avSpectrum = 0
for( number dx = -range; dx<=range; dx++ )
avSpectrum += offset(spectrumIn,dx,0)
avSpectrum *= 1/(2*range+1)
image spectrumOut := spectrumIn.ImageClone()
for( number dx = -range; dx<=range; dx++ )
spectrumout *= ( avSpectrum >= offset(avSpectrum,dx,0) ? 1 : 0 )
spectrumout.SetName("Local maxima ("+range+") filtered, average")
return spectrumOut
}
image test1 := CreateTestSpec(256,10,1000,5000)
image maxPeaks := FilterLocalMaxima1D(test1,3)
image maxPeaksAv := FilterLocalMaxima1DAveraged(test1,3)
test1.ShowImage()
test1.ImageGetImageDisplay(0).ImageDisplayAddImage( maxPeaks, "local max" )
test1.ImageGetImageDisplay(0).ImageDisplayAddImage( maxPeaksAv, "local max from Average" )
test1.ImageGetImageDisplay(0).LinePlotImageDisplaySetSliceComponentColor( 0, 1, 0.7, 0.7, 0.7 )
test1.ImageGetImageDisplay(0).LinePlotImageDisplaySetSliceDrawingStyle( 1, 2)
test1.ImageGetImageDisplay(0).LinePlotImageDisplaySetSliceComponentColor( 1, 1, 1, 0, 0 )
test1.ImageGetImageDisplay(0).LinePlotImageDisplaySetSliceTransparency( 1, 1, 0.7 )
test1.ImageGetImageDisplay(0).LinePlotImageDisplaySetSliceDrawingStyle( 2, 2)
test1.ImageGetImageDisplay(0).LinePlotImageDisplaySetSliceComponentColor( 2, 1, 0, 1, 0 )
test1.ImageGetImageDisplay(0).LinePlotImageDisplaySetSliceTransparency( 2, 1, 0.7 )
The easiest way is to use 1-point (or 2-point) neighborhood to decide, whether center is minimum (maximum). See pseudo code below:
// assume 0 <= x <= maxX, y(x) is value at point x, radius is 1
x = 1;
while (x + 1 <= maxX)
{
if (y(x) > y(x-1) and y(x) > y(x+1))
// we found a maximum at x
if (y(x) < y(x-1) and y(x) < y(x+1))
// we found a minimum at x
x = x + 1
}
For 2-point neighborhood maximum condition could be
if (y(x) > y(x-1) and y(x-1) >= y(x-2) and y(x) > y(x+1) and y(x+1) >= y(x+2))
Note >= here. You may use > instead.
Note that above approach won't find maximum in case two consecutive x have the same value y e.g. for y(0) = 0, y(1) = 1, y(2) = 1, y(3) = 0 it won't find maximum neither for x = 1, nor for x = 2.
Related
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();
search it in the help document, could not find it.
is something like: LinePlotImageDisplayDeleteSlices(LinePlotImageDisplay lpid)
Delete.LinePlotImageDisplayGetSlice ?
Thanks,
you are seeking
Boolean ImageDisplayDeleteSliceWithId( ImageDisplay id, ScriptObject slice_id )
used in combination with
ScriptObject ImageDisplayGetSliceIdByIndex( ImageDisplay id, Number slice_index )
Example copied from the F1 help documentation of GMS 3.3:
image CreateRandomSpectrum( number sx )
{
image img := RealImage( "Spectrum", 4, sx )
number fac = Random() * 3
number off = Random() * 10
number mag = Random() * 3
img = mag * sin( ( off + fac * icol / iwidth ) * Pi() )
return img
}
imageDisplay CreateAndShowMultiSlice( number nSlice, number sx )
{
image firstImg := CreateRandomSpectrum(100)
firstImg.ShowImage()
imageDisplay disp = firstImg.ImageGetImageDisplay(0)
for( number i = 1; i<nSlice; i++ )
disp.ImageDisplayAddImage( CreateRandomSpectrum(100), "" )
disp.LinePlotImageDisplaySetLegendShown( 1 )
return disp
}
void DemoRemoveSlice()
{
imageDisplay disp = CreateAndShowMultiSlice(5,100)
OKDialog( "Now remove 2nd slice" )
object id = disp.ImageDisplayGetSliceIDByIndex( 2 )
disp.ImageDisplayDeleteSliceWithId( id )
}
DemoRemoveSlice()
I have a 4D dataset with dimensions, e.g., 100(width) x 100(height) x 2048(energy channels) x 20 (frames).
Is there any function or method to append a 3D datacube (frame) of the same width, height and energy channels to the end of the 4D data set?
Finally the 4D data set should be 100*100*2048*21.
There is no command which expands an image object in this way.
You have to perform this as a manual copy. (Which requires the additional memory, unfortunately.)
number sx = 100
number sy = 100
number sz = 100
number sf = 10
image before := RealImage( "Before", 4, sx, sy, sz, sf )
before = icol
image addFrame := RealImage( "Frame", 4, sx, sy, sz )
addFrame = irow
image after := RealImage( "After", 4, sx, sy, sz, sf + 1)
after.SliceN( 4,4, 0,0,0,0, 0,sx,1, 1,sy,1, 2,sz,1, 3,sf,1 ) = before
after.SliceN( 4,4, 0,0,0,sf, 0,sx,1, 1,sy,1, 2,sz,1, 3,1,1 ) = addFrame
before.showimage()
after.ShowImage()
If this is an issue, you can alternatively use the streaming commands to directly stream data (values only, no meta data) into either a buffer memory or onto the hard disc. Only at the end, you'd stream that data into an apropriately sized image variable.
Finally, depending on your application, you might consider adding new data as a taggroup to a taglist (which can be appended). You can store "images" as tags ( TagGroupSetTagAsArray() ). Again, you could create a 'complete' image form this information at a later time point.
number sx = 100
number sy = 100
number sz = 100
number sf = 10
tagGroup FrameTags = NewTagGroup()
tagGroup FrameList = FrameTags.TagGroupGetOrCreateTagList( "frames" )
FrameTags.TagGroupSetTagAsLong( "X Size", sx )
FrameTags.TagGroupSetTagAsLong( "Y Size", sy )
FrameTags.TagGroupSetTagAsLong( "Z Size", sz )
// Add frames whenever you need .....
for( number f = 0; f < sf; f++ )
{
image addFrame := RealImage( "Frame", 4, sx, sy, sz )
addFrame = random()
FrameList.TagGroupInsertTagAsArray( f, addFrame )
}
// Handle taggroup as you want. (Add to global tags, add to image, store to disc... )
FrameTags.TagGroupOpenBrowserWindow( "frames", 0 )
// Whenever needed, reconstruct image from frames
number nsx, nsy, nsz
FrameTags.TagGroupGetTagAsLong( "X Size", nsx )
FrameTags.TagGroupGetTagAsLong( "Y Size", nsy )
FrameTags.TagGroupGetTagAsLong( "Z Size", nsz )
TagGroup nFrameList
FrameTags.TagGroupGetTagAsTagGroup( "frames", nFrameList )
number nsf = nFrameList.TagGroupCountTags()
image after := RealImage( "After", 4, nsx, nsy, nsz, nsf )
for( number f = 0; f < nsf; f++ )
{
image addFrame := RealImage( "Frame", 4, sx, sy, sz )
FrameList.TagGroupGetIndexedTagAsArray( f, addFrame )
after.SliceN( 4,4, 0,0,0,f, 0,sx,1, 1,sy,1, 2,sz,1, 3,1,1) = addFrame
}
after.ShowImage()
The scripting help documentation of DigitalMicrograph offers an example for setting LinePlot styles with respect of colour and fill (see example script below).
However, the ImageDisplay menu for LinePlots also allows setting line styles (dotted, dashed,...) line thickness and transparency. Can somebody give an example on how to set these values, please?
// create image and image document
ImageDocument imageDoc = CreateImageDocument( "New ImageDocument" )
number width = 256
number height = 5
image img := RealImage("Line Plot Test", 4, width, height )
img = sin( irow + icol/100 )
// add LinePlotImageDisplay to ImageDocument
ImageDisplay imgdsp = imageDoc.ImageDocumentAddImageDisplay( img, 3 )
imgdsp.LinePlotImageDisplaySetContrastLimits( -1.1, 1.1 )
imgdsp.LinePlotImageDisplaySetDoAutoSurvey( 0, 0 )
// draw fill and line for slice 0
imgdsp.LinePlotImageDisplaySetSliceDrawingStyle(0, 3)
// set line color to red
imgdsp.LinePlotImageDisplaySetSliceComponentColor(0, 0, 1, 0, 0)
// set fill color to yellow
imgdsp.LinePlotImageDisplaySetSliceComponentColor(0, 1, 0.9, 0.9, 0)
// draw fill for slice 1 and 2
imgdsp.LinePlotImageDisplaySetSliceDrawingStyle(1, 2)
imgdsp.LinePlotImageDisplaySetSliceDrawingStyle(2, 2)
// draw line for slice 3 and 4
imgdsp.LinePlotImageDisplaySetSliceDrawingStyle(3, 1)
imgdsp.LinePlotImageDisplaySetSliceDrawingStyle(4, 1)
imageDoc.ImageDocumentShow()
The commands you are looking for are:
void LinePlotImageDisplaySetSliceLineThickness( LinePlotImageDisplay lpid, Number slice_id, Number lineThickness )
void LinePlotImageDisplaySetSliceLineStyle( LinePlotImageDisplay lpid, Number slice_id, Number lineStyle )
void LinePlotImageDisplaySetSliceTransparency( LinePlotImageDisplay lpid, Number sliceIndex, Boolean doTransparent, Number transparency )
They are demonstrated in the example below. Note that the visibility of line styles depend on the number of points in a LinePlot. If the LinePlot has more data points than displayed pixels, you may not notice the line style as it is defined 'in between' data points:
// create image and image document
ImageDocument imageDoc = CreateImageDocument( "New ImageDocument" )
number width = 64
number height = 10
image img := RealImage("Line Plot Test", 4, width, height )
img = sin( irow + icol / iwidth * 2 * Pi() ) + ( irow < ( height / 2 ) ? 1.5 : -1.5 )
// add LinePlotImageDisplay to ImageDocument
ImageDisplay imgdsp = imageDoc.ImageDocumentAddImageDisplay( img, 3 )
imgdsp.LinePlotImageDisplaySetContrastLimits( -2.6, 2.6 )
imgdsp.LinePlotImageDisplaySetDoAutoSurvey( 0, 0 )
// Line style demo
for ( number i = 0 ; i < height / 2 ; i++ )
{
number index = i + height / 2
// Set Line - drawing (no fill)
imgdsp.LinePlotImageDisplaySetSliceDrawingStyle( index , 1 )
// Set black line
imgdsp.LinePlotImageDisplaySetSliceComponentColor( index , 0 , 0, 0, 0 )
// Set LineThickness
imgdsp.LinePlotImageDisplaySetSliceLineThickness( index , height / 2 - i + 1 )
// Set LineStyle
imgdsp.LinePlotImageDisplaySetSliceLineStyle( index , i )
}
// Transparecny demo
for ( number i = 0 ; i < height / 2 ; i++ )
{
number index = i
// Set Fill & Line - drawing
imgdsp.LinePlotImageDisplaySetSliceDrawingStyle( index , 1 + 2 )
// Set black fill & red line
imgdsp.LinePlotImageDisplaySetSliceComponentColor( index , 1 , 0 , 0 , 0 )
imgdsp.LinePlotImageDisplaySetSliceComponentColor( index , 0 , 255 , 0 , 0 )
// Set transparency ( 70% transparency = 30% opacity )
imgdsp.LinePlotImageDisplaySetSliceTransparency( index , 1 , 0.7 )
}
imageDoc.ImageDocumentShow()