miile7
miile7

Reputation: 2403

How to communicate between dm-script and python in Digital Micrograph

How do I comminucate between dm-script code and python code, both executed in Digital Micrograph?


In my specific case I have complicated dialogs. Sice the dialogs are not supported in python but in dm-script, I wrote my dialogs in dm-script. The problem now is to pass from the dialog to the python code.

Consider the following example:

import DigitalMicrograph as DM

script = """string field_value = "";

class ButtonDialog : UIFrame{
    void field_changed(object self, TagGroup field){
        field_value = field.DLGGetStringValue();
    }
    
    object init(object self){
        TagGroup dlg, dlg_items, field;
        
        dlg = DLGCreateDialog("Type in a number", dlg_items);
        
        dlg.DLGAddElement(DLGCreateLabel("Number:"));
        
        field = DLGCreateIntegerField(0, 10, "field_changed");
        dlg.DLGAddElement(field);
        
        self.super.init(dlg);
        return self;
    }
}

object dialog = alloc(ButtonDialog).init();
dialog.pose();
"""

DM.ExecuteScriptString(script)

# how do I get this?
field_value = ""

Upvotes: 0

Views: 1098

Answers (2)

miile7
miile7

Reputation: 2403

To synchronize data between dm-script and python while both are running in the same instance (and thread in this example, can be modified for different threads too) one can use the persistent tags.

The dm-script is setting the persistent tags. python can then read the persistent tags again.


Update: Python module

Because of the limitations mentioned below and the increadibly growing and unreadable code I decided to write my own python module for this. With execdmscript one can execute dm-script code with variable synchronization very easily from Digital Micrograph.

Check out the following example:

from execdmscript import exec_dmscript

# some script to execute
dmscript = """
number input;
number s = GetNumber("Enter a number between " + min + " and " + max + ".", init, input);"
"""

# variables that will be defined for the dm-scripts (and readable later on in python)
sv = {"min": 1, "max": 10, "init": 2}
# variables the dm-script defines and that should be readable in the python file
rv = {"input": "number", "s": "number"}

with exec_dmscript(dmscript, readvars=rv, setvars=sv) as script:
    if script["s"]:
        print(script["input"])
    else:
        print("User pressed cancel.")

This hides away all the dm-script related saving and problems (mentioned below). It allows to use list and dicts and basic types like bool, str, int and float. All types and values can be used in the pythonic way with no need to care about dm-script types. The dm-script can be moved to a separete file to clean up the code even more.

Note that for debugging there is a exec_dmscript(debug=True, debug_file="path/to/file") switch that will save the code to the given debug_file. This file can then be executed in GMS manually which shows the errors.

Disclaimer: As mentioned, I am the author of the execdmscript module. Still I think this is the best solution for this case. In fact this question was the reason I wrote the module.


Basic datatypes

For basic datatypes like string, and number one can create and add the code by hand. The following example shows the idea:

import DigitalMicrograph as DM
import time

sync_id = "sync-{}".format(int(time.time() * 100))

dmscript = """
number input;
number s = GetNumber("Enter a number between {min} and {max}.", {init}, input);

TagGroup p = GetPersistentTagGroup();
p.TagGroupSetTagAsBoolean("{id}-success", s);
p.TagGroupSetTagAsLong("{id}-input", input);
"""

DM.ExecuteScriptString(dmscript.format(min=1, max=10, init=2, id=sync_id))

# cannot save TagGroups to variables, check below
s, success = DM.GetPersistentTagGroup().GetTagAsBoolean("{}-success".format(sync_id))

if s and success:
    # cannot save TagGroups to variables, check below
    s, input = DM.GetPersistentTagGroup().GetTagAsLong("{}-input".format(sync_id))
    
    if s:
        print(input)
    else:
        print("Did not find 'input' in persistent tags.")
elif not s:
    print("Did not find 'success' in persistent tags.")
else:
    print("User clicked cancel.")

# cannot save TagGroups to variables, check below
# delete tag, otherwise the persistent tags gets filled with garbage
DM.GetPersistentTagGroup().DeleteTagWithLabel("{}-success".format(sync_id))
DM.GetPersistentTagGroup().DeleteTagWithLabel("{}-input".format(sync_id))

As one can see there some downsides of this method. There is a lot of code creating the setup and the synchronization for just entering one number. This makes the code messy. Also one may want to save the dm-script code in a separate file wich adds python file opening. Debugging the dm-script code is difficult in this code too. And lastly there are limitations on TagGroup travelling on python side (check out below).


Limitations

Update: The python module mentioned below can deal with TagGrous and TagLists because it saves them linearized and with separate keys and types.

Note that TagGroups are very difficult to synchronize. TagGroups must not be assigned to variables. As soon as they are, they are not usable anymore. This can be illustrated by the following, very easy code:

import DigitalMicrograph as DM

# create group for the example
group1 = DM.NewTagGroup()
group1.SetTagAsString("test-string", "test content")
print("Group 1 valid:", group1.IsValid(), "(type:", type(group1), ")")

# save to persistent tags
DM.GetPersistentTagGroup().SetTagAsTagGroup("test", group1)

# get the group without assigning to a variable, works
s, group2 = DM.GetPersistentTagGroup().GetTagAsTagGroup("test")
print("Group 2 success:", s, ", valid:", group2.IsValid(), "(type:", type(group2), ")")

# assign one parent group to a variable, doesn't work
tags = DM.GetPersistentTagGroup()
s, group3 = tags.GetTagAsTagGroup("test")
print("Group 3 success:", s, ", valid:", group3.IsValid(), "(type:", type(group3), ")")

This code will produce the output

Group 1 valid: True (type: <class 'DigitalMicrograph.Py_TagGroup'> )
Group 2 success: True , valid: True (type: <class 'DigitalMicrograph.Py_TagGroup'> )
Group 3 success: True , valid: False (type: <class 'DigitalMicrograph.Py_TagGroup'> )

This shows that you cannot assign TagGroups to python variables.


Asynchronous ideas

This can be extended for asynchronous applications. The dm-script and the python implementation can set threads that observe the persistent tags. (Note that you can mark TagGroups as modified by using TagGroupMarkAsChanged()), perferrably different tags. Then both can add commands and/or data to the tag. The other "instance" can read and process them.

This has to be coded manually. This is (currently) not included in the execdmscript module.

Upvotes: 0

BmyGuest
BmyGuest

Reputation: 2949

DM-scripts calling Python scripts

This answer is showing some communication between Python and DM-script but form the opposite side. It also uses the persistent tagGroup as transfer-proxy.

Passing simple values in and out of Python from a DM script

// //////////////////////////////////////////////////
// Helper methods 
// Build the DM-String representing the PythonScript
// which is then called
// //////////////////////////////////////////////////
string AddPythonValueIN( number val, string valName, string valType )
{
    string py
    py += "\n#Get value DM->Python via global tags" + "\n"
    py += "success, " + valName + " = DM.GetPersistentTagGroup().GetTagAs" + valType + "('Python_temp:in:" + valName + "')" + "\n"
    py += "if False == success:" + "\n"
    py += "    print( 'Error. No value passed in by DM-script sucessfully.' )" + "\n"
    py += "    exit(0)" + "\n"
    return py
}

string AddPythonValueIN( string val, string valName, string valType )
{
    string py
    py += "\n#Get value DM->Python via global tags" + "\n"
    py += "success, " + valName + " = DM.GetPersistentTagGroup().GetTagAs" + valType + "('Python_temp:in:" + valName + "')" + "\n"
    py += "if False == success:" + "\n"
    py += "    print( 'Error. No value passed in by DM-script sucessfully.' )" + "\n"
    py += "    exit(0)" + "\n"
    return py
}

string AddPythonValueOut( string valName, string valType )
{
    string py
    py += "\n#Get value Python->DM via global tags" + "\n"
    py += "DM.GetPersistentTagGroup().SetTagAs" + valType + "('Python_temp:out:" + valName + "'," + valName + ")" + "\n"
    return py
}

string AddPythonPrintModifyPrint()
{
    string py
    py += "\n#Print InValue, modify, print OutValue" + "\n"
    py += "print('InValue',InValue)"+ "\n"
    py += "OutValue = InValue * 2" + "\n"
    py += "print('OutValue',OutValue)"+ "\n"
    return py
}

// //////////////////////////////////////////////////
// Example calls 
// Demonstrating value passing from DM script to a 
// called Python snippet. TagTypes need to be 
// explicit in Python.
// //////////////////////////////////////////////////
void PythonValueInAndOut_Float()
{
    number value = 13.2
    GetPersistentTagGroup().TagGroupSetTagAsFloat("Python_temp:in:InValue",value)
    
    Result( " DM-Script passed value in: " + value + "\n")
    
    string py = "#Script to demonstrate value passing in Hybrid scripts. DM calling Python." + "\n"
    py += AddPythonValueIN( value, "InValue", "Float" )
    py += AddPythonPrintModifyPrint() 
    py += AddPythonValueOut( "OutValue", "Float" )
    
    Result( "\n PYTHON CODE EXECUTES\n --------------------- \n\n" )
    ExecutePythonScriptString( py, 1 )
    Result( "\n --------------------- \n PYTHON CODE FINISHED \n\n" )
    
    if ( !GetPersistentTagGroup().TagGroupGetTagAsFloat("Python_temp:out:OutValue",value))
        Throw("Python value passed out not found.")
        
    Result( "DM-Script received value: " + value + "\n")
    
    GetPersistentTagGroup().TagGroupDeleteTagWithLabel("Python_temp")
}

void PythonValueInAndOut_Double()
{
    number value = 13.2
    GetPersistentTagGroup().TagGroupSetTagAsDouble("Python_temp:in:InValue",value)
    
    Result( " DM-Script passed value in: " + value + "\n")
    
    string py = "#Script to demonstrate value passing in Hybrid scripts. DM calling Python." + "\n"
    py += AddPythonValueIN( value, "InValue", "Double" )
    py += AddPythonPrintModifyPrint() 
    py += AddPythonValueOut( "OutValue", "Double" )
    
    Result( "\n PYTHON CODE EXECUTES\n --------------------- \n\n" )
    ExecutePythonScriptString( py, 1 )
    Result( "\n --------------------- \n PYTHON CODE FINISHED \n\n" )
    
    if ( !GetPersistentTagGroup().TagGroupGetTagAsDouble("Python_temp:out:OutValue",value))
        Throw("Python value passed out not found.")
        
    Result( "DM-Script received value: " + value + "\n")
    
    GetPersistentTagGroup().TagGroupDeleteTagWithLabel("Python_temp")
}

void PythonValueInAndOut_String()
{
    string value = "hallo"
    GetPersistentTagGroup().TagGroupSetTagAsString("Python_temp:in:InValue",value)
    
    Result( " DM-Script passed value in: " + value + "\n")
    
    string py = "#Script to demonstrate value passing in Hybrid scripts. DM calling Python." + "\n"
    py += AddPythonValueIN( value, "InValue", "String" )
    py += AddPythonPrintModifyPrint() 
    py += AddPythonValueOut( "OutValue", "String" )
    
    Result( "\n PYTHON CODE EXECUTES\n --------------------- \n\n" )
    ExecutePythonScriptString( py, 1 )
    Result( "\n --------------------- \n PYTHON CODE FINISHED \n\n" )
    
    if ( !GetPersistentTagGroup().TagGroupGetTagAsString("Python_temp:out:OutValue",value))
        Throw("Python value passed out not found.")
        
    Result( "DM-Script received value: " + value + "\n")
    
    GetPersistentTagGroup().TagGroupDeleteTagWithLabel("Python_temp")
}

ClearResults()
PythonValueInAndOut_float()
PythonValueInAndOut_double()
PythonValueInAndOut_string()

Passing image data in and out of Python from a DM script

// Images and other objects which are in memory can be most easily passed
// by passing their ID value and then finding them by ID.
// However, this does not work if the object would go out of scope at the 
// end of a script (like an image which is not displayed)
//
// To pass a NumpyArray out of Python without showing an image, one can
// instead write the array to the global tags directly, together with the
// information needed to read the tags out into a DM-image
void PythonImageInOut( )
{
    image img := RealImage("test",4,5,5)
    img = icol
    
    Result( " DM-Script passed image ["+img.ImageGetLabel()+"] in.\n")
        
    string py
    py += "\n#Get image DM->Python via imageLabel" + "\n"
    py += "img = DM.FindImageByID("+ img.ImageGetID()+")" + "\n"
    py += "if None == img:" + "\n"
    py += "    print( 'Error.No image passed in by DM-script sucessfully.' )" + "\n"
    py += "    exit(0)" + "\n"
    py += "\n"
    py += "print('Image from DM: Label = ', img.GetLabel())" + "\n"
    py += "print('Image from DM: Name  = ', img.GetName())" + "\n"
    py += "print('Image from DM: Value = \\n', img.GetNumArray())" + "\n"
    py += "del img #ALWAYS delete DM objects in Python scripts" + "\n"
    py += "\n"
    py += "\n#Create DM image in Python and pass out as ID via global tags" + "\n"
    py += "import numpy as np" + "\n"
    py += "data = np.arange(48).reshape(6,8)" + "\n"
    py += "outImg = DM.CreateImage(data.copy(order='C'))" + "\n"
    py += "DM.GetPersistentTagGroup().SetTagAsLong('Python_temp:out:ID',outImg.GetID())" + "\n"
    py += "print('Create new image with ID:',outImg.GetID())"+"\n"
    py += "outImg.ShowImage()    #Image needs to be shown or it will not stay in memory" + "\n"
    py += "del outImg #ALWAYS delete DM objects in Python scripts" + "\n"
    
    Result( "\n PYTHON CODE EXECUTES\n --------------------- \n\n" )
    ExecutePythonScriptString( py, 1 )
    Result( "\n --------------------- \n PYTHON CODE FINISHED \n\n" )
    
    number imgID
    if ( !GetPersistentTagGroup().TagGroupGetTagAsLong("Python_temp:out:ID",imgID))
        Throw("Python imageID value passed out not found.")
        
    image outImg := FindImageByID(imgID)
    if ( !outImg.ImageIsValid()  )
        Throw("Python imageID value passed out but no matching image found.")
        
        
    Result( "DM-Script received image: " + outImg.ImageGetLabel()+ "\n")
    
    GetPersistentTagGroup().TagGroupDeleteTagWithLabel("Python_temp")
    
}


void PythonArrayOut( )
{
    string py
    py += "#Passing Numpy array values via global tags" + "\n"
    py += "import numpy as np" + "\n"
    py += "data = np.arange(6*8*3).reshape(6,8,3)" + "\n"
    py += "outImg = DM.CreateImage(data.copy(order='C'))" + "\n"
    py += "DM.GetPersistentTagGroup().SetTagAsArray('Python_temp:out:array',outImg)" + "\n"
    py += "DM.GetPersistentTagGroup().SetTagAsLong('Python_temp:out:type',outImg.GetDataType())" + "\n"
    py += "for i in range(data.ndim,0,-1):" + "\n"
    py += "    DM.GetPersistentTagGroup().SetTagAsLong('Python_temp:out:dim:' + str(i-1),data.shape[i-1])" + "\n"   
    py += "\n"
    py += "del outImg #ALWAYS delete DM objects in Python scripts" + "\n"
    
    Result( "\n PYTHON CODE EXECUTES\n --------------------- \n\n" )
    ExecutePythonScriptString( py, 1 )
    Result( "\n --------------------- \n PYTHON CODE FINISHED \n\n" )

    number dataType
    if ( !GetPersistentTagGroup().TagGroupGetTagAsLong("Python_temp:out:type",dataType))
        Throw("Python dataType value passed out not found.")
    
    TagGroup dimTG
    if ( !GetPersistentTagGroup().TagGroupGetTagAsTagGroup("Python_temp:out:dim",dimTG))
        Throw("Python dimensions passed out not found.")
    
    number nDim = dimTG.TagGroupCountTags()
    number n0, n1, n2, n3, n4
    dimTG.TagGroupGetIndexedTagAsLong(0,n0) 
    dimTG.TagGroupGetIndexedTagAsLong(1,n1)
    dimTG.TagGroupGetIndexedTagAsLong(2,n2)
    dimTG.TagGroupGetIndexedTagAsLong(3,n3)
    dimTG.TagGroupGetIndexedTagAsLong(4,n4)
    
    image outImg 
    if ( 1 == nDim  )
        outImg := NewImage("Test", dataType, n0 )
    else if ( 2 == nDim  )
        outImg := NewImage("Test", dataType, n0, n1 )
    else if ( 3 == nDim  )
        outImg := NewImage("Test", dataType, n0, n1, n2 )
    else if ( 4 == nDim  )
        outImg := NewImage("Test", dataType, n0, n1, n2, n3 )
    else if ( 5 == nDim  )
        outImg := NewImage("Test", dataType, n0, n1, n2, n3, n4 )
        
    if ( !GetPersistentTagGroup().TagGroupGetTagAsArray("Python_temp:out:array",outImg))
        Throw("Python array data passed out not found.")
    
    Result("\n DM recreated image from array values: " + outImg.ImageGetLabel()+ "\n")
        
    GetPersistentTagGroup().TagGroupDeleteTagWithLabel("Python_temp")
}

clearresults()
Result("\n\nEXAMPLE Python<->DM image passing\n")
PythonImageInOut()
Result("\n\nEXAMPLE Python-->DM array passing\n")
PythonArrayOut( )

Upvotes: 0

Related Questions