Try to calculate the total counts/intensity of different ROIs by DM, but, weirdly, it keeps giving the counts of the left 1st ROI., and I tracked the second ROI by the result. It looks like it gave the whole image. I believe something is wrong, or there is a better method to do this. Any suggestion appreciated
ROI roi_2 = NewROI()
ROISetRange( roi_2, 10, 30 )
cropdisp.ImageDisplayAddROI( roi_2)
imagedisplaysetroiselected(cropdisp, roi_2,1)
number count_l, count_r
image img1 := GetFrontImage()
number integral = sum( img1[] )
number scale = img1.ImageGetIntensityScale()
number origin = img1.ImageGetIntensityOrigin()
number t,l,b,r
img1.GetSelection(t,l,b,r)
result("\n select1 is"+t+","+l+","+b+","+r)
number nChannels = r - l
number integral_cal = (integral - origin * nChannels) * scale
count_l= integral_cal
result("\n count_l= "+count_l +", ")
ROI roi_3 = NewROI()
ROISetRange( roi_3, 35, 55 )
cropdisp.ImageDisplayAddROI( roi_3)
imagedisplaysetroiselected(cropdisp, roi_3,1)
showimage(cropped)
image img2 := GetFrontImage()
number integral2 = sum( img2[] )
number scale2 = img2.ImageGetIntensityScale()
number origin2 = img2.ImageGetIntensityOrigin()
t2=0;l2=x0+1;b2=1;r2=xmax+1
img1.GetSelection(t2,l2,b2,r2)
result("\n select is"+t2+","+l2+","+b2+","+r2)
number nChannels2 = r2-l2
number integral_cal2 = (integral2 - origin2 * nChannels2) * scale
count_r= integral_cal2
result("\n count_r= "+count_r +", ")
The code snipped as shown above is incomplete and does not run.
I think you wanted to show the following:
image img:=RealImage("Test",4,400)
img=icol
img.ShowImage()
imagedisplay disp = img.ImageGetImageDisplay(0)
ROI r1 = newROI()
r1.RoiSetRange(10,20)
disp.ImageDisplayAddROI(r1)
disp.ImageDisplaySetRoiSelected(r1,1)
Result("\n R1: "+sum(img[]))
ROI r2 = newROI()
r2.RoiSetRange(100,120)
disp.ImageDisplayAddROI(r2)
disp.ImageDisplaySetRoiSelected(r2,1)
Result("\n R2: "+sum(img[]))
disp.ImageDisplaySetRoiSelected(r1,1)
Result("\n R1?: "+sum(img[]))
This code outputs:
R1: 145
R2: 79800
R1?: 79800
And the reason is: The script img[] addresses the first found selected ROI region. If you select more than one ROI, it will always return the sum of the first ROI area only. As adding a ROI to the display adds it "on top" and you are adding R1 then R2 you have the following situation:
nothing selected: img[] refers to the full iamge
Only r1 selected: img[] refers to the region R1
Only r2 selected: img[] refers to the region R2
Both r1&r2 selected: img[] refers to the region R2 (because it is "on top")
You could fix this, be ensuring you don't have the first ROI selected anymore:
image img:=RealImage("Test",4,400)
img=icol
img.ShowImage()
imagedisplay disp = img.ImageGetImageDisplay(0)
ROI r1 = newROI()
r1.RoiSetRange(10,20)
disp.ImageDisplayAddROI(r1)
disp.ImageDisplaySetRoiSelected(r1,1)
Result("\n R1: "+sum(img[]))
ROI r2 = newROI()
r2.RoiSetRange(100,120)
disp.ImageDisplayAddROI(r2)
disp.ImageDisplaySetRoiSelected(r1,0)
disp.ImageDisplaySetRoiSelected(r2,1)
Result("\n R2: "+sum(img[]))
disp.ImageDisplaySetRoiSelected(r2,0)
disp.ImageDisplaySetRoiSelected(r1,1)
Result("\n R1?: "+sum(img[]))
However, this is all rather unnecessary complicated code!
Why rely on the "selected" state and a visible ROI in the first place? You can always address an area by the coordinates directly:
image img:=RealImage("Test",4,400)
img=icol
img.ShowImage()
imagedisplay disp = img.ImageGetImageDisplay(0)
ROI r1 = newROI()
r1.RoiSetRange(10,20)
disp.ImageDisplayAddROI(r1)
ROI r2 = newROI()
r2.RoiSetRange(100,120)
disp.ImageDisplayAddROI(r2)
number start,end
r1.ROIGetRange(start,end)
Result("\n R1: "+sum(img[0,start,1,end]))
r2.ROIGetRange(start,end)
Result("\n R2: "+sum(img[0,start,1,end]))
Related
I want to calculate the area under a curve with giving ROI, like the attached picture, I want to get the area under the green curve from 13 to 30, could we reach this function by DM scripting?
Never done similar things before, any suggestions thanks!
If the image is of label "A" then, you can simply do:
sum( A[] )
or, a bit more elaborated:
image img := GetFrontImage()
number integral = sum( img[] )
result("\n The intensity of the selected region in image [")
result( img.imageGetLabel() + "]:" + img.ImageGetName() + " is: ")
result( integral)
Note that this will give you the integral (or just the sum of channel-values) in raw numbers. If you have calibrated the intensity, you will have to take the calibration into account.
If - as in most cases - you just have an intensity scale but no offset, it is straight forward:
image img := GetFrontImage()
number integral = sum( img[] )
number scale = img.ImageGetIntensityScale()
number integral_cal = integral * scale
result("\n The intensity of the selected region in image [")
result( img.imageGetLabel() + "]:" + img.ImageGetName() + " is: ")
result( integral_cal )
If you also have an origin, it becomes:
image img := GetFrontImage()
number integral = sum( img[] )
number scale = img.ImageGetIntensityScale()
number origin = img.ImageGetIntensityOrigin()
number t,l,b,r
img.GetSelection(t,l,b,r)
number nChannels = r - l
number integral_cal = (integral - origin * nChannels) * scale
result("\n The intensity of the selected region in image [")
result( img.imageGetLabel() + "]:" + img.ImageGetName() + " is: ")
result( integral_cal )
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();
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()
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.