Play through the slice of an Image stack - dm-script

In GMS3 (or GMS2), is there any command available to access the SlicePlayer or Slice palette? Like pressing the 'play' button to play through the slice of 3D data via script. OR is there any other way to do this? Thanks.

The commands you're seeking are :
void ImageDisplayGetDisplayedLayers( ImageDisplay imgDisp, NumberVariable start, NumberVariable end )
void ImageDisplaySetDisplayedLayers( ImageDisplay imgDisp, Number start, Number end )
void ImageDisplayGetDisplayedLayers( ImageDisplay imgDisp, NumberVariable start1, NumberVariable end1, NumberVariable start2, NumberVariable end2 )
void ImageDisplaySetDisplayedLayers( ImageDisplay imgDisp, Number start1, Number end1, Number start2, Number end2 )
See example:
number nz = 25
image stack := RealImage( "Fake Stack",4,100,100,nz)
stack = iradius<(1+iplane)*2?irow:icol
stack.ShowImage()
imageDisplay disp = stack.ImageGetImageDisplay(0)
for( number i=0;i<nz;i++ )
{
disp.ImageDisplaySetDisplayedLayers(i,i)
stack.UpdateImage()
sleep(0.1)
}
All ImageDisplay commands can be found in the F1 documentation here:

Related

Adjusting image contrast in FIB/SEM image - without affecting the text bar at the bottom of the image

FIB/SEM images have a text bar at the bottom of the image.
When imported into GMS, any contrast, gamma, .. adjustment also affects the text bar.
Is it possible to break up the image and have the data processing affect only the actual image - not the text bar?
The best you can do here is to break the actual image array into 2 separate images and then have the text-bar section displayed as a separate imageDisplay which you can add onto the imageDisplay of the data. You can shift/scale them with respect to each other, and you can also lock the added display so that it can not be shifted by mouse anymore. The following example should do what you need:
void CropAndMerge(Image img, number h){
number sx = img.ImageGetDimensionSize(0)
number sy = img.ImageGetDimensionSize(1)
image data := img.slice2(0,0,0,0,sx,1,1,sy-h,1).ImageClone() // note ":=", we sub-slice keeping tags and all
image anno = img.slice2(0,sy-h,0,0,sx,1,1,h,1) // note "=", we just want the data copy
imageDisplay disp
// Simple way to get imageDisplay. First show, then grab
// data.ShowImage()
// disp = data.ImageGetImageDisplay(0)
// Better alternative: No need to show
imageDocument doc = NewImageDocument( img.ImageGetName() )
doc.ImageDocumentAddImage( data )
// doc.ImageDocumentAddImage( anno ) // Use this to add 'side ordered' in case of page-view type. However, I'd rather not use page-mode.
disp = data.ImageGetImageDisplay(0)
disp.ImageDisplaySetColorTableByName( "Black Body Extended" ) // Just to show you can act on the display before actually showing it that way
// Add Annotation area as annotation on imageDisplay (all are components)
imageDisplay annoDisp = NewImageDisplay( anno, "best" )
disp.ComponentAddChildAtEnd( annoDisp )
// move out of the way
// ComponentPositionAroundPoint: Moves the annotation so the 'rel_x' horizontal point in the bounding rect is at 'new_x' (if bool horz is true), and for y accordinglye
number doVert = 1
number rel_y = 1.0 // bottom (relative coordinate!)
number new_y = sy // becomes bottom (absolute position)
annoDisp.ComponentPositionAroundPoint( 0,new_y,0,rel_y,0,doVert)
// make sure nobody messes with the annotation area
annoDisp.ComponentSetSelectable(0)
doc.ImageDocumentShow()
}
number sx = 1024
number sy = 1024
number h = 300
image in := realimage("Imported",4,sx,sy)
in = (icol%100 + iradius*sin(irow/iheight*5*Pi() + itheta )**2)
in.slice2(0,sy-h,0,0,sx,1,1,h,1) = (icol+irow)%50>45?max(in)+100:0
//in.showimage()
CropAndMerge(in.imageClone(),h)

How to add annotation information in each frame of a stack image?

I know how to add the arrows in a single image displayed front, but now I need to add the arrow annotations on each frame of a image stack to indicate the contrast change position and show them using the GSM "slice player". How to do it?
There is no difference between a 2D image and a 3D stack in DigitalMicrograph. Both are just dimensional data. As such "slices" in a Stack do neither have their individual tags nor annotations - there is just a single imageTagGroup and a single imageDisplays.
So to achieve what you want you need a different approach. You need to move your annotation whenever the display updates to shows a different slice.
In order to do this, you need to add a display-listener to your image display and act on the slice_property_changed event.
A basic example script for this:
Class CStackAnno
{
ImageDisplay disp
Component arrow
Number ListenerID
// This method is called whenever the imagedisplay fires the slice update event
void OnSlicePropChanged( object self, Number disp_flags, ImageDisplay disp, Number flags1, Number flags2, object slice_id_beg, object slice_id_end )
{
image img := disp.ImageDisplayGetImage()
if ( 3 != img.ImageGetNumDimensions() ) return
if ( !arrow.ComponentIsValid() ) return
number sx = img.imageGetDimensionsize(0)
number sy = img.imageGetDimensionsize(1)
number sz = img.imageGetDimensionsize(2)
number start, end
disp.ImageDisplayGetDisplayedLayers( start, end )
number kLineEndPoint = 2
arrow.ComponentSetControlPoint( kLineEndPoint, sx/sz * start, sx/sz * start, 0 )
}
Object Launch( object self, image Img )
{
if ( !img.ImageIsValid() ) Throw( "Invalid input image." )
if ( 3 != img.ImageGetNumDimensions() ) Throw( "This script only supports 3D images." )
disp = img.ImageGetImageDisplay(0)
// Register DisplayListener to catch when it updates
ListenerID = disp.ImageDisplayAddEventListener( self, "slice_property_changed:OnSlicePropChanged" )
// Add the annotation
arrow = NewArrowAnnotation( img.ImageGetDimensionSize(1)/5, img.ImageGetDimensionSize(0)*4/5, 0, 0 )
arrow.ComponentSetForegroundColor( 0, 0.5 , 1 )
arrow.ComponentSetBackgroundColor( 0, 0.8 , 1 )
arrow.ComponentSetDrawingMode( 1 )
disp.ComponentAddChildAtEnd( arrow )
return self
}
}
//Main call
image fImg
GetFrontImage(fImg)
Alloc(CStackAnno).Launch(fImg)

How to apply virtual apperture with 4D-STEM dataset in EFFICIENT way?

I would like to apply arbitrarily defined bit mask as virtual aperture and apply it to 4D-STEM data set in an EFFICIENT way.
I did it using the SliceN function and apply the mask pixel-by-pixel, which is very slow for large datasets. How to optimize it to so to run faster?
Image 4DSTEM := GetFrontImage() // dimention [ScanX, ScanY, Dx, Dy]
Image mask: = iradius // just an arbitrary mask (aperture)
Image out // dimention [ScanX, ScanY]
for (number i=0; i<ScanX; i++)
{ for (number j=0; j<ScanY; j++)
{
Diff2D = 4DSTEM.SliceN(4,2,i,j,0,0,2,Dx,1,3,Dy,1)
out.setpixel(i,j, sum(diff2D*mask))
}
}
out.showimage()
for an [100,100,512,512] dataset, that took few minutes to finish. When I have to repeat the operation several times, that is way to slow compare to matrix operation. but I dont know how to make it in an efficient way.
Thanks!
you're hitting the limitations of scripting languages here. Using sliceN is already pretty much the optimum you can get to, unfortunately. Everything else in speed optimization requires parallelized, compiled code. (i.e. you could code C++ code and use the SDK to compile your own plugin.)
However, there is a bit of room for improvement over your example.
First of all, your example above doesn't run :c) But that is quickly fixed.
Point #1:
Try to avoid number type casting. DM script only knows number but internally there is a difference between the proper number types (integer, floating point, signed/unsigned, byte-size). The script languages uses real-4-byte as the default unless told differently explicitly. And some methods will return real-4-byte by default. For this reason, the processing will be fastest, if both data and mask use real-4-byte data as well.
In my testing, the time-difference between running with uint16 data plus uint8 mask and *real4 data plus real4 mask) was significant! Nearly 30% time difference.
Point #2:
Don't copy you sliced image! Use := not = for your Dif2D.
The SliceN command returns an expression directly addressing the required memory. You can use it directly in any other expression (like I do below) or you can assign an image variable to it using := to give it a name.
The speed increase is not huge, but it's one copy-operation less per loop iteration.
Point #3:
You additional knowledge: Now for arbitrary masks there is not much you can do, but most often masks are zero-valued over large stretches and it is possible to define a smaller ROI containing all non-zero points. If this is the case, you can limit your math operations to that region.
i.e. instead of multiplying the whole DP with the same sized mask, just use a smaller mask and use the according sub-section of the DP.
This can actually make a big difference, but it will depend on your mask.
Of course you need to "find" this ROI first. In my script below I'm having a helper method to do that, utilizing the comparatively fast max() command and image rotation as trick for speed-up.
Point #4:
...would be to get rid of the double-for loop and replace it with image-expressions. Unfortunately, DigitalMicrograph does currently (GMS 3.3) not support this for 4D or 5D data.
The script below executed on a [53 x 52 x 512 x 512] STEM DI (of real-4 byte data) gave me the following timings:
Original: 12.80910 sec
Test 1 : 10.77700 sec
Test 2 : 1.83017 sec
// Helper class for timing
class CTimer{
number s
string n
~CTimer(object self){result("\n"+n+": "+ (GetHighResTickCount()-s)/GetHighResTicksPerSecond()+" sec");}
object Start(object self, string n_) { n=n_; s=GetHighResTickCount(); return self;}
}
// Helper method to find best non-zero containing ROI
void GetNonZeroArea( image src, number &t, number &l, number &b, number &r )
{
image work = !!src // Make a binary image which is 0 only where src==0
number d
max(work,d,t) // get "first" non-zero pixel coordinate, this is y = dist from TOP
rotateRight(work) // rotate image right
max(work,d,l) // get "first" non-zero pixel coordinate, this is y = dist from LEFT
rotateRight(work) // rotate image right
max(work,d,b) // get "first" non-zero pixel coordinate, this is y = dist from BOTTOM
b = work.ImageGetDimensionSize(1) - b // Opposite side!
rotateRight(work) // rotate image right
max(work,d,r) // get "first" non-zero pixel coordinate
r = work.ImageGetDimensionSize(1) - r // Opposite side!
}
// The original proposed script (plus fixes to make it actually run)
image Original(image STEM4D, image mask)
{
Number ScanX = STEM4D.ImageGetDimensionSize(0)
Number ScanY = STEM4D.ImageGetDimensionSize(1)
Number Dx = STEM4D.ImageGetDimensionSize(2)
Number Dy = STEM4D.ImageGetDimensionSize(3)
Image out := RealImage("Test1",4,ScanX,ScanY)
for (number i=0; i<ScanX; i++)
{ for (number j=0; j<ScanY; j++)
{
image Diff2D = STEM4D.SliceN(4,2,i,j,0,0,2,Dx,1,3,Dy,1)
out.setpixel(i,j, sum(Diff2D*mask))
}
}
return out
}
// Remove copying the slice, just reference it
image Test1(image STEM4D, image mask)
{
Number ScanX = STEM4D.ImageGetDimensionSize(0)
Number ScanY = STEM4D.ImageGetDimensionSize(1)
Number Dx = STEM4D.ImageGetDimensionSize(2)
Number Dy = STEM4D.ImageGetDimensionSize(3)
Image out := RealImage("Test1",4,ScanX,ScanY)
for (number i=0; i<ScanX; i++)
{ for (number j=0; j<ScanY; j++)
{
image Diff2D := STEM4D.SliceN(4,2,i,j,0,0,2,Dx,1,3,Dy,1)
out.setpixel(i,j, sum(Diff2D*mask))
}
}
return out
}
// Limit mask size to what is needed!
image Test2(image STEM4D, image mask )
{
Number ScanX = STEM4D.ImageGetDimensionSize(0)
Number ScanY = STEM4D.ImageGetDimensionSize(1)
Number Dx = STEM4D.ImageGetDimensionSize(2)
Number Dy = STEM4D.ImageGetDimensionSize(3)
Image out := RealImage("Test1",4,ScanX,ScanY)
Number t,l,b,r
GetNonZeroArea(mask,t,l,b,r)
Number w = r - l
Number h = b - t
image subMask := mask.slice2(l,t,0, 0,w,1, 1,h,1 )
for (number i=0; i<ScanX; i++)
for (number j=0; j<ScanY; j++)
out.setpixel(i,j, sum(STEM4D.SliceN(4,2,i,j,l,t,2,w,1,3,h,1)*subMask))
return out
}
Image src := GetFrontImage() // dimention [ScanX, ScanY, Dx, Dy]
Number ScanX = src.ImageGetDimensionSize(0)
Number ScanY = src.ImageGetDimensionSize(1)
Number Dx = src.ImageGetDimensionSize(2)
Number Dy = src.ImageGetDimensionSize(3)
Number r = 50 // mask radius
Image maskImg := RealImage("Mask",4,Dx,Dy)
maskImg = iradius < r ? 1 : 0 // just an aperture mask
image resultImg
{
object timer = Alloc(CTimer).Start("Original")
resultImg := Original(src,maskImg)
}
resultImg.SetName("Oringal")
resultImg.ShowImage()
{
object timer = Alloc(CTimer).Start("Test 1")
Test1(src,maskImg).ShowImage()
}
resultImg.SetName("Test 1")
resultImg.ShowImage()
{
object timer = Alloc(CTimer).Start("Test 2")
Test2(src,maskImg).ShowImage()
}
resultImg.SetName("Test 2")
resultImg.ShowImage()
Compiled code comparison:
Now, it should be added that the above script still is rather slow. Because it is iterating and using script language. The fully compiled c++ code of DigitalMicrograph is much faster. So if you have the licensed packages giving you the SI menu, then you want to use the SI/Map/Signal command. This is near-instantaneous for the example STEM DI I've mentioned above. My other answer shows how one could utilize this functionality by script.
As mentioned in my other answer, a real speed-win comes when compiled, parallelized code is used. DigitalMicrograph does this, after all, in the available SI "signal" map functionality. This feature is not available in the free version, but if you have Spectrum-Imaging acquisition, you most likely have the appropriated license as well.
The answer below utilizes this functionality by accessing the UI with the command ChooseMenuItem() and applying a few more tricks. The script is a bit lengthy, but its parts also show some other nice tricks worthwhile knowing:
TestSignalIntegrationInSI is the main script demoing how things can work.
CreatePickerByScript shows how one can create picker-spectra on SIs. This is used to open a 'Picker Diffraction Pattern' image from the STEM DI.
AddTestMasksToDP_ROIs programmatically adds ROIs to the diffraction pattern to be used as mask
AddTestMasksToDP_Threshold programmatically adds an intensity-threshold mask to be used as mask.
AddTestMasksToDP_DPMasks programmatically adds the various types of diffraction-masks to be used as mask
GetIntegratedSignalViaSIMenu is the central step of the script. With a picker-DP and required 'masks' on it front-most, the menu command is called to perform the signal-extraction (as fast as possible.) Then the displayed result-image is returned.
GetNewestImage is just a utility method showing how on can access the latest memory-created image.
Here is the script:
image GetNewestImage()
{
// New images get the next higher imageID.
// This can be used to identify the "latest" created image.
if ( 0 == CountImages() ) Throw( "No image in memory!" )
// We create a temp. image to get the uppder limit
number lastID = RealImage("Dummy",4,1).ImageGetID()
// Then we search for the next lower existing one
image lastImg
for( number ID = lastID - 1; ID>0; ID-- )
{
lastImg := FindImageByID(ID)
if ( lastImg.ImageIsValid() ) break
}
return lastImg
}
image CreatePickerByScript( image SI, number t, number l, number b, number r )
{
if ( SI.ImageGetNumDimensions()<3 ) Throw( "Sorry, LineScans are not supprorted here." )
// Adding a non-volatile ROI of specific RoiNAME acts as if using
// the picker-tool. The ID string must be unique!
ROI pickerROI = NewROI()
pickerROI.RoiSetVolatile( 0 )
string uniqueID = GetDate(0)+"#"+GetTime(1)+";"+round(random()*1000)
pickerROI.RoiSetName( "SICursor(##"+uniqueID+"##)" )
SI.ImageGetImageDisplay(0).ImageDisplayAddROI( pickerROI )
// This creates the picker image.
// So the child is now the "newest" image in memory
image child := GetNewestImage()
return child
}
void AddTestMasksToDP_ROIs( image DP )
{
// Add ROIs to the DP which are your masks (any numebr and type of ROI works)
imageDisplay DPdisp = DP.ImageGetImageDisplay(0)
number dpX = DP.ImageGetDimensionSize(0)
number dpY = DP.ImageGetDimensionSize(1)
// Only simple RECT ROIs are supported
ROI maskRoi1 = NewROI()
maskRoi1.ROISetRectangle( dpY*0.1, dpX*0.1, dpY*0.8, dpX*0.3 )
DPdisp.ImageDisplayAddROI(maskRoi1)
// Arbitrary multi-vertex (use for ovals etc.)
ROI maskRoi2 = NewROI()
maskRoi2.ROISetRectangle( dpY*0.7, dpX*0.1, dpY*0.9, dpX*0.9 )
DPdisp.ImageDisplayAddROI(maskRoi2)
}
void AddTestMasksToDP_Threshold( image DP )
{
// Add intensity treshhold mask (highest 95% intensity range)
imageDisplay DPdisp = DP.ImageGetImageDisplay(0)
DPdisp.RasterImageDisplaySetThresholdOn( 1 )
number low = max(DP) * 0.05
number high = max(DP)
DPdisp.RasterImageDisplaySetThresholdLimits( low, high )
}
void AddTestMasksToDP_DPMasks( image DP )
{
// Add Diffraction masks to the DP
imageDisplay DPdisp = DP.ImageGetImageDisplay(0)
// Spot masks (always symmetric pair)
Component spotMask = NewComponent(8,0,0,0,0) // 8 = Spotmask
spotMask.ComponentSetControlPoint(4, 0, 0,0) // 4 = TopLeft of one spot [Size only]
spotMask.ComponentSetControlPoint(7,10,10,0) // 7 = BottomRight of one spot [Size only]
spotMask.ComponentSetControlPoint(8,150,0,0) // 8 = Spot position [center]
DPdisp.ComponentAddChildAtEnd(spotMask)
// Bandpass mask (Only circles are correctly supported)
Component bandpassMask = NewComponent(15,0,0,0,0) // 15 = Bandpass (ring)
number r1 = 100
number r2 = 120
bandpassMask.ComponentSetControlPoint(7,r1,r1,0) // 7 = BottomRight of one ring [Size only]
bandpassMask.ComponentSetControlPoint(14,r2,r2,0) // 14 = BottomRight of one ring [Size only]
DPdisp.ComponentAddChildAtEnd(bandpassMask)
// Wege mask (symmetric)
Component wedgeMask = NewComponent(19,0,0,0,0) // 19 = wedgemask (ringsegment)
wedgeMask.ComponentSetControlPoint(9,10,20,0) // 9 = One wedge vector
wedgeMask.ComponentSetControlPoint(10,-20,40,0) // 10 = Other wedge vector
DPdisp.ComponentAddChildAtEnd(wedgeMask)
// Array mask (symmetric)
Component arrayMask = NewComponent(9,0,0,0,0) // 9 = arrayMask (ringsegment)
arrayMask.ComponentSetControlPoint(9,-70,-60,0) // 9 = One array vector
arrayMask.ComponentSetControlPoint(10,99,-99,0) // 10 = Other array vector
arrayMask.ComponentSetControlPoint(4, 0, 0,0) // 4 = TopLeft of one spot [Size only]
arrayMask.ComponentSetControlPoint(7,20,20,0) // 7 = BottomRight of one spot [Size only]
DPdisp.ComponentAddChildAtEnd(arrayMask)
}
image GetIntegratedSignalViaSIMenu( image pickerChild )
{
// Call the Menu to do the work
// The picker-spectrum or DP needs to be front-most
pickerChild.SelectImage()
ChooseMenuItem("SI","Map","Signal")
// The created signal map is NOT the newest image
// (some internal iamges are created for the mask)
// but it is the front-most displayed one.
image signalMap := GetFrontImage()
return signalMap
}
image GetMaskFromSignalMap( image signalMap, number DPx, number DPy )
{
// The actual mask is stored in the tags
string tagPath = "Processing:[0]:Parameters:Mask"
tagGroup tg = signalMap.ImageGetTagGroup()
if ( !tg.TagGroupDoesTagExist(tagPath) )
Throw( "Sorry, no mask tag found." )
image mask := RealImage("Mask",4,DPx, DPy )
if ( !tg.TagGroupGetTagAsArray(tagPath,mask) )
Throw( "Sorry, could not retrieve mask. Maybe wrong size?" )
return mask
}
void TestSignalIntegrationInSI()
{
image STEMDI := GetFrontImage()
image DP := STEMDI.CreatePickerByScript(0,0,1,1)
if ( TwoButtonDialog( "Add ROIs as mask?", "Yes", "No" ) )
AddTestMasksToDP_ROIs( DP )
else if ( TwoButtonDialog( "Add intensity treshold as mask?", "Yes", "No" ) )
AddTestMasksToDP_Threshold( DP )
else if ( TwoButtonDialog( "Add diffraction masks as mask?", "Yes", "No" ) )
AddTestMasksToDP_DPMasks( DP )
image signalMap := GetIntegratedSignalViaSIMenu( DP )
number dpX = DP.ImageGetDimensionSize(0)
number dpY = DP.ImageGetDimensionSize(1)
// We may want to close the DP again. No longer needed
//DP.DeleteImage()
// Verification: Get Mask image form SignalMap
image usedMask := GetMaskFromSignalMap( signalMap, dpX, dpY )
usedMask.SetName( "This mask was used." )
usedMask.ShowImage()
}
TestSignalIntegrationInSI()
The solution below utilizes the intrinsic expression loops by performing in-place multiplication and then projection.
Disappointingly, it turns out the solution is actually a bit slower then the for-loop with the SliceN command.
For the same test-data of size [53 x 52 x 512 x 512] the resulting timing is:
Data copy: 1.28073 sec
Inplace multiply: 30.1978 sec
Project 1/2: 1.1208 sec
Project 2/2: 0.0019557 sec
InPlace multiplication with projections (total): 32.9045 sec
InPlace multiplication with projections (total): 34.9853 sec
// Helper class for timing
class CTimer{
number s
string n
~CTimer(object self){result("\n"+n+": "+ (GetHighResTickCount()-s)/GetHighResTicksPerSecond()+" sec");}
object Start(object self, string n_) { n=n_; s=GetHighResTickCount(); return self;}
}
image MaskMultipliedSum( image STEM4D, image MASK2D, number copyFirst )
{
// Boring feasability checks...
if ( 4 != STEM4D.ImageGetNumDimensions() )
Throw( "Input data is not 4D." )
if ( 2 != MASK2D.ImageGetNumDimensions() )
Throw( "Input mask is not 2D." )
Number ScanX = STEM4D.ImageGetDimensionSize(0)
Number ScanY = STEM4D.ImageGetDimensionSize(1)
Number Dx = STEM4D.ImageGetDimensionSize(2)
Number Dy = STEM4D.ImageGetDimensionSize(3)
if ( Dx != MASK2D.ImageGetDimensionSize(0) )
Throw ("X dimension of mask does not match input data." )
if ( Dy != MASK2D.ImageGetDimensionSize(1) )
Throw ("Y dimension of mask does not match input data." )
// Do the maths!
image workCopy4D
if ( copyFirst )
{
object timer = Alloc(CTimer).Start("Data copy")
workCopy4D = STEM4D
}
else
workCopy4D := STEM4D
{
object timer = Alloc(CTimer).Start("Inplace multiply")
workCopy4D *= MASK2D[idimindex(2),idimindex(3)]
}
// Now we want to "sum up" over Dx and Dy
image p1,p2
{
object timer = Alloc(CTimer).Start("Project 1/2")
p1 := project( workCopy4D, 3 )
}
{
object timer = Alloc(CTimer).Start("Project 2/2")
p2 := project( p1, 2 )
}
return p2
}
image stack4D, mask2D
If ( GetTwoLabeledImagesWithPrompt("Please select 4D data and 2D mask", "Select input", "4D data", stack4D, "2D mask", mask2D ) )
{
number doCopy = TwoButtonDialog("Create workcopy?","Yes (takes time)","No (overwrites input data!)")
object timer = Alloc(CTimer).Start("InPlace multiplication with projections (total)")
MaskMultipliedSum(stack4D,mask2D,doCopy).ShowImage()
}

How to detect when a live DigiScan image has finished acquiring a complete frame with an event handler?

I am looking to take snapshots of a live DigiScan image.
However, I want to do so only when a complete (or nearly so) frame has been acquired!
Attaching an event handler to a live DigiScan image does not work, as the image 'changes' with each line acquired serially.
I am hoping to monitor just the last few pixels of the live image and only capture an image when those change. Is this possible?
Your suggestion is exactly what I did: Using the data_changed event as a trigger, and in the event handling method check the value of the last image pixel for if it has changed. (The initial value of the first pass is guaranteed to be zero. From then on, keep the value of a check.)
So, essentially you’ve answered your own question - what remains unclear?
You can check a single pixel using the GetPixel() command, or the img[x,y] notation.
Example
class CFrameCompleteListen
{
number lastPixelValue
number listenID
number frameCount, sx, sy
void OnDataChanged( object self, number flags, image img )
{
number value = img.GetPixel( sx-1,sy-1)
if ( value == lastPixelValue )
return
frameCount++
lastPixelValue = value
Result( "\n Image [" + img.GetLabel() + "] frame #"+ frameCount + " complete." )
if ( 3 <= frameCount )
{
ImageRemoveEventListener( img, listenID )
Result("\n Listening stopped." )
}
}
object Launch( object self, image img )
{
lastPixelValue = 0
frameCount = 0
sx = img.ImageGetDimensionSize(0)
sy = img.ImageGetDimensionSize(1)
listenID = ImageAddEventListener( img, self, "data_changed:OnDataChanged" )
Result("\n Listening started." )
return self
}
}
Alloc( CFrameCompleteListen ).Launch( GetFrontImage() )
This answer is merely to post the alternate strategy mentioned in the comments to the answer with the original example script. Please note that the following altered version DOES NOT WORK since the attempt to register an image change listener seems to fail (the listener object is immediately released).
class CFrameCompleteListen2
{
number lastPixelValue
number listenID
number frameCount
void OnDataChanged( object self, number flags, image img )
{
Result("\n Change event." )
number value = img.GetPixel( 0, 0 )
if ( value == lastPixelValue )
return
frameCount++
lastPixelValue = value
Result( "\n Image [" + img.GetLabel() + "] frame #"+ frameCount + " complete." )
if ( 3 <= frameCount )
{
ImageRemoveEventListener( img, listenID )
Result("\n Listening stopped." )
}
}
object Launch( object self, image img )
{
lastPixelValue = img.GetPixel( 0, 0 )
frameCount = 0
listenID = ImageAddEventListener( img, self, "data_changed:OnDataChanged" )
Result("\n Listening started." )
return self
}
~CFrameCompleteListen2( object self )
{
Result( "\n Listener released.\n" )
}
}
void main()
{
image targetImage := GetFrontImage()
number targetW = targetImage.ImageGetDimensionSize(0)
number targetH = targetImage.ImageGetDimensionSize(1)
image lastPixelImage := targetImage[targetH - 1, targetW - 1, targetH, targetW]
lastPixelImage.ShowImage()
object listener = Alloc( CFrameCompleteListen2 ).Launch( lastPixelImage )
}
main()
Edit: by BmyGuest (see comments):
The following script shows, that displaying a "sliced" memory creates a new image with a new reference:
ClearResults()
image newFull := RealImage("Full",4,100,100)
Result("\n Image label 'full':" + newFull.ImageGetLabel() + " | ID:" + newFull.ImageGetID())
newFull.ShowImage()
Result("\n Image label 'full':" + newFull.ImageGetLabel() + " | ID:" + newFull.ImageGetID())
image getFull := GetFrontImage()
Result("\n Image label 'full' (front):" + getFull.ImageGetLabel() + " | ID:" + getFull.ImageGetID())
image slice := newFull[0,0,2,2]
Result("\n Image label 'slice':" + slice.ImageGetLabel() + " | ID:" + newFull.ImageGetID())
slice.ShowImage()
Result("\n Image label 'slice':" + slice.ImageGetLabel() + " | ID:" + newFull.ImageGetID())
image getSlice := GetFrontImage()
Result("\n Image label 'slice' (front):" + getSlice.ImageGetLabel() + " | ID:" + getSlice.ImageGetID())
The output of that script is something like:
Consequently, the listener script above fails and unregisters the listener because the 'lastPixelImage' does not reference the shown 1-pixel image and hence goes out of scope immediately (removing the listener in the process). The script does work, if one grabs the displayed image after showing it and using that one as reference. Accordingly, the script would also work if the sliced image variable would be held in scope.

NS-3: How to change node position during simulation?

I am trying to model in NS-3 some nodes moving with constant vertical velocity inside a rectangle.
Problem: I have to add the feature that, when a node goes beyond y=2500, its x-position abruptly changes to its x-position + 257. This is what I have tried so far:
NodeContainer nodes;
nodes.Create (25);
MobilityHelper mobility;
mobility.SetPositionAllocator ("ns3::GridPositionAllocator",
"MinX", DoubleValue (0.0),
"MinY", DoubleValue (0.0),
"DeltaX", DoubleValue (500),
"DeltaY", DoubleValue (500),
"GridWidth", UintegerValue (5),
"LayoutType", StringValue ("RowFirst"));
mobility.SetMobilityModel ("ns3::ConstantVelocityMobilityModel");
mobility.Install (nodes);
for (uint n=0 ; n < nodes.GetN() ; n++)
{
Ptr<ConstantVelocityMobilityModel> mob = nodes.Get(n)->GetObject<ConstantVelocityMobilityModel>();
mob->SetVelocity(Vector(0, 10, 0));
}
for (uint n=0 ; n < satellites.GetN() ; n++)
{
Ptr<ConstantVelocityMobilityModel> cvMob = satellites.Get(n)->GetObject<ConstantVelocityMobilityModel>();
Ptr<MobilityModel> mob = satellites.Get(n)->GetObject<MobilityModel>();
Vector m_position = mob->GetPosition();
Vector m_velocity = mob->GetVelocity();
if (m_position.y > 2500)
{
m_position.x += 257;
m_velocity.y *= -1;
cvMob->SetVelocity(m_velocity);
mob->SetPosition(m_position);
}
}
This last for loop is not working at all! How should I implement the feature into the current script?
PS: Since I am new to NS-3, I do not want to risk modifying any NS-3 source file.
what you need to do is to move the content of your second loop in a 'scheduled' method (e.g. CheckBound).
void CheckBound(Ptr< ConstantVelocityMobilityModel > mob ){
Vector m_position = mob->GetPosition();
Vector m_velocity = mob->GetVelocity();
if (m_position.y > 2500)
{
m_position.x += 257;
m_velocity.y *= -1;
mob->SetVelocity(m_velocity);
mob->SetPosition(m_position);
}
}
Then in your main you need to estimate when this has to be called. Doing simple math calculations, we estimate the Dy which is the distance to your 2500 limit and the time it is required Dt = Dy/speed.
e.g.
for (uint n=0 ; n < satellites.GetN() ; n++)
{
Ptr<ConstantVelocityMobilityModel> cvMob = satellites.Get(n)->GetObject<ConstantVelocityMobilityModel>();
Vector m_position = cvMob->GetPosition();
Vector m_speed = cvMob->GetVelocity();
double Dy = 2500 - m_position.y;
double Dt = Dy/m_speed.y;
Simulator::Schedule(Seconds(Dt), MakeCallback(&CheckBound, cvMob));
}