I'm working with dm-script to process a raw profile. I first calculate its first derivative to detect edges, and then let users select a region between these edges in the derivative plot. However, I'd like the same selection box to also appear simultaneously in the original raw profile plot to guide the user intuitively.
Currently, my implementation uses compound functions to vertically stack two plots (raw profile and derivative profile). Although it works, the synchronization between interactive regions feels cumbersome and not very elegant.
Is there a better or more efficient way in DM scripting to link interactive selection regions between the original profile and its derivative plot?
Any suggestions or code examples are appreciated!
Class CMyHandleLegend
{
TagGroup CreateLegendTags(object self, CompoundDocument doc, number selectedCMDid)
{
TagGroup Legend = NewTagList();
for(number i = 0; i < doc.CompoundDocumentCountImageDisplays() - 1; i++)
{
ImageDisplay ithDisp = doc.CompoundDocumentGetImageDisplay(i);
string buttonName = ithDisp.ImageDisplayGetImage().ImageGetName();
number buttonCMDid = 10000 + ithDisp.ComponentGetCompoundID();
TagGroup button = NewTagGroup();
button.TagGroupSetTagAsUInt32("ID", buttonCMDid);
button.TagGroupSetTagAsString("Name", buttonName);
button.TagGroupSetTagAsBoolean("Selected", buttonCMDid == selectedCMDid);
Legend.TagGroupAddTagGroupAtEnd(button);
}
// Legend.TagGroupOpenBrowserWindow(0)
return Legend;
}
void GetIthDisplayInfo(object self, CompoundDocument doc, number i, number &compID, number &AR, string &name)
{
ImageDisplay ithDisp = doc.CompoundDocumentGetImageDisplay(i);
Image ithImg = ithDisp.ImageDisplayGetImage();
compID = ithDisp.ComponentGetCompoundID();
Result("\n" + compID)
number sx = ithImg.ImageGetDimensionSize(0);
number sy = ithImg.ImageGetDimensionSize(1);
AR = (0 < sy) ? sx / sy : 1;
AR = clip(AR, 0.5, 2);
name = ithImg.ImageGetName();
}
TagGroup CreateRowLayout(object self, CompoundDocument doc, number cmdID)
{
number rowH = 300;
TagGroup Layout = CreateCompoundDocumentLayoutTagGroup("CompoundDocument Example 2");
TagGroup row = Layout.CompoundDocumentLayoutAddRow(rowH);
TagGroup row2 = Layout.CompoundDocumentLayoutAddRow(rowH);
number iCompID, iAR;
string iName;
number nImgD = doc.CompoundDocumentCountImageDisplays();
for(number i = 0; i < nImgD; i++)
{
self.GetIthDisplayInfo(doc, i, iCompID, iAR, iName);
if ((iCompID + 10000) == cmdID)
row.CompoundDocumentLayoutAddColumnAsImage(rowH * iAR, iName, iCompID);
if ((iCompID + 10000) == (cmdID + 1))
row2.CompoundDocumentLayoutAddColumnAsImage(rowH * iAR, iName, iCompID);
}
Layout.TagGroupSetTagAsBoolean("Legend", 1);
Layout.TagGroupSetTagAsTagGroup("Buttons", self.CreateLegendTags(doc, cmdID));
return Layout;
}
void ChooseLayout(object self, CompoundDocument doc, number cmdID)
{
TagGroup Layout = self.CreateRowLayout(doc, cmdID);
//Layout.TagGroupOpenBrowserWindow(0);
doc.CompoundDocumentShow(Layout);
}
void HandleLegendButtonPressed(object self, number flags, CompoundDocument doc, number cmdID)
{
Result("\n Received button message from: " + doc.ImageDocumentGetName());
Result("\n Button ID: " + cmdID);
self.ChooseLayout(doc, cmdID);
}
}
Image absolute_first_derivative(Image in)
{
// Edge detected at the peak (maximum absolute value) of the first derivative curve
Number size_x, size_y, origin, scale
String units
Image out
in.ImageGetDimensionCalibration(0, origin, scale, units, 0)
in.GetSize(size_x, size_y)
if (size_y != 1) Result("Only compatible with 1D image")
out := RealImage("first_derivative", 4, size_x, size_y)
out = abs(in[icol + 1, irow] - in[icol, irow])
//out[0, 0] = 0
//out[size_x - 1, 0] = 0
out.ImageSetDimensionCalibration(0, origin, scale, units, 0)
return out
}
void ShowLegendCompoundDocument()
{
CompoundDocument doc = NewCompoundDocument("CompoundDoc Example");
image img1 := RealImage("test", 4, 200);
img1 = 100
img1[0,0,1,50] = 50
img1[0,150,1,200] = 50
Image img2 := absolute_first_derivative(img1)
Image img3 = img1
Image img4 := absolute_first_derivative(img3)
img1.SetName("test 1")
img2.SetName("test 2")
img3.SetName("test 3")
img4.SetName("test 4")
doc.ImageDocumentAddImageDisplay(img1.ImageCreateImageDisplay(-2));
doc.ImageDocumentAddImageDisplay(img2.ImageCreateImageDisplay(-2));
doc.ImageDocumentAddImageDisplay(img3.ImageCreateImageDisplay(-2));
doc.ImageDocumentAddImageDisplay(img4.ImageCreateImageDisplay(-2));
object obj = Alloc(CMyHandleLegend);
number listenID = doc.CompoundDocumentAddListener(obj, "compounddocument_buttonpressed:HandleLegendButtonPressed");
obj.ChooseLayout(doc, 10008);
}
ShowLegendCompoundDocument();
In DM scripting, what you are calling a "selection box" is an ROI object on a LinePlotImageDisplay object. There are several ways to be notified that an ROI has changed and to use that to keep multiple ROIs synchronized. This is generally done via a construct called a Listener. Please do a search on "[dm-script] listener" to see several previous posts that cover this topic.
In your case, I believe the following one might be particularly helpful: