跳转至

How to retrieve output keywords from Call_Procedure

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/how-to-retrieve-output-keywords-from-call-procedure

14158 Rate this article:

No rating

How to retrieve output keywords from Call_Procedure

Anonym Thursday, February 26, 2015

When working in IDL, often it will seem like doing something is just not possible. That is, until you talk to the right person who knows language tricks you never would have dreamed of. In my case that happened while I was working on our new ENVITask subclass for wrapping any IDL procedure. I wanted to use Call_Procedure to invoke it, but could not figure out how to get the values of output keywords from the procedure, so I had to resort to using the much more flexible but slower Execute() approach. Then I had a conversation with Adam Lefkoff, creator of the original version of ENVI and all around IDL guru, and he showed me a trick to take advantage of that works with Call_Procedure.

Let me step back a bit and describe the problem better before I demonstrate the solution. Let's say there is an IDL procedure that you want to call, but you have the names of the keywords as string literals instead of having a priori knowledge of them at compile time. Perhaps they come from a database or external source. You have two options for invoking this procedure: Execute and Call_Procedure:

pro testProcedure, FOO=foo, BAR=bar, _REF_EXTRA=refExtra

compile_opt idl2

help, foo, OUTPUT=fooOut

help, bar, OUTPUT=barOut

print, fooOut, barOut

if (ISA(refExtra)) then begin

foreach keyword, refExtra do begin

value = Scope_VarFetch(keyword, /REF_EXTRA)

help, value, OUTPUT=valueOut

print, keyword, valueOut

endforeach

endif

end

pro Execute_Wrapper, procName, _REF_EXTRA=refExtra

compile_opt idl2

params = Hash()

foreach keyword, refExtra do begin

params[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)

endforeach

print, 'in Execute_Wrapper'

command = procName + ', _EXTRA=params.ToStruct()'

ret = Execute(command)

end

pro Call_Procedure_Wrapper, procName, _REF_EXTRA=refExtra

compile_opt idl2

params = Hash()

foreach keyword, refExtra do begin

params[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)

endforeach

print, 'in Call_Procedure_Wrapper'

Call_Procedure, procName, _EXTRA=params.ToStruct()

end

pro test_procedure_wrapping

compile_opt idl2

Execute_Wrapper, 'testProcedure', FOO='foo', BAR=!pi, BAZ=List(1,2), QUX=Hash(1,2)

Call_Procedure_Wrapper, 'testProcedure', FOO='foo', BAR=!pi, BAZ=List(1,2), QUX=Hash(1,2)

end

The example might be a bit contrived, but it demonstrates how you Scope_VarFetch() each of the values in _REF_EXTRA and put them into a Hash object. The Hash is then converted to a struct when invoking the procedure, and by assigning that struct to the _EXTRA keyword it will connect all the keywords appropriately. We can see from the output that both Execute and Call_Procedure work:

in Execute_Wrapper

FOO             STRING    = 'foo'

BAR             FLOAT     =       3.14159

BAZ VALUE           LIST 

QUX VALUE           HASH 

in Call_Procedure_Wrapper

FOO             STRING    = 'foo'

BAR             FLOAT     =       3.14159

BAZ VALUE           LIST 

QUX VALUE           HASH 

The problem is that the procedure only has input keywords, no output keywords. If we want to be able to dynamically invoke a procedure that has output keywords, a little more effort is required. Here is the updated version of the Execute wrapper in action:

pro testProcedure, FOO=foo, BAR=bar, OUT_HASH=outHash, _REF_EXTRA=refExtra

compile_opt idl2

outHash = Hash('FOO', foo, 'BAR', bar)

if (ISA(refExtra)) then begin

foreach keyword, refExtra do begin

outHash[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)

endforeach

endif

end

pro Execute_Wrapper, procName, OUT_NAMES=outNames, _REF_EXTRA=refExtra

compile_opt idl2

params = Hash()

foreach keyword, refExtra do begin

if (outNames.HasValue(keyword)) then continue

params[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)

endforeach

print, 'in Execute_Wrapper'

command = procName + ', _EXTRA=params.ToStruct()'

foreach name, outNames do begin

command += ', ' + name.ToUpper() + '=' + name.ToLower()

endforeach

ret = Execute(command)

foreach name, outNames do begin

(Scope_VarFetch(name, /REF_EXTRA)) = Scope_VarFetch(name)

endforeach

end

pro test_procedure_wrapping

compile_opt idl2

execOutHash = 0

Execute_Wrapper, 'testProcedure', FOO='foo', BAR=!pi, BAZ=List(1,2), QUX=Hash(1,2), OUT_NAMES='OUT_HASH', OUT_HASH=execOutHash

print, execOutHash, /IMPLIED

end

The Execute_Wrapper routine now requires knowledge of which keywords in _REF_EXTRA are input and which are output. So when we construct the Hash we omit the output keywords, but then append them to the command string  as new variables in the local scope. After invoking the procedure, these local variables will have the correct output values, but we need the very odd looking line with two calls to Scope_VarFetch() to copy them into the _REF_EXTRA bag to get them back out to the routine that called Execute_Wrapper. The Scope_VarFetch() call on the left uses the /REF_EXTRA keyword to connect with the callstack, but we need to encapsulate it in parentheses to make it an assignment operation. The Scope_VarFetch() call on the right is used to get the value of the named local variable for this assignment. We also need to make sure that the OUT_HASH keyword is present in the call to Execute_Wrapper so that this _REF_EXTRA trick will work.

While this is functional, it looks a little complicated and redundant with the OUT_HASH keyword repeated as a string literal. The other problem is that it is still using Execute(), which isn't as efficient as Call_Procedure. But we can't modify the Call_Procedure_Wrapper routine in the same manner and have success. The reason for this is that when the struct is built to pass into Call_Procedure it contains copies of the values of each keywords, not references to the variables used to build it. So we need to introduce a new function to get the output keyword values:

pro testProcedure, FOO=foo, BAR=bar, OUT_HASH=outHash, _REF_EXTRA=refExtra

compile_opt idl2

outHash = Hash('FOO', foo, 'BAR', bar)

if (ISA(refExtra)) then begin

foreach keyword, refExtra do begin

outHash[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)

endforeach

endif

end

function Procedure_Wrapper, procName, _REF_EXTRA=refExtra

compile_opt idl2

Call_Procedure, procName, _EXTRA=refExtra

results = Hash()

foreach keyword, refExtra do begin

results[keyword] = Scope_Varfetch(keyword, /REF_EXTRA)

endforeach

return, results

end

pro Call_Procedure_Wrapper, procName, OUT_NAMES=outNames, _REF_EXTRA=refExtra

compile_opt idl2

params = Hash()

foreach keyword, refExtra do begin

params[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)

endforeach

print, 'in Call_Procedure_Wrapper'

results = Procedure_Wrapper(procName, _EXTRA=params.ToStruct())

foreach name, outNames do begin

(Scope_VarFetch(name, /REF_EXTRA)) = results[name]

endforeach

end

pro test_procedure_wrapping

compile_opt idl2

callOutHash = 0

Call_Procedure_Wrapper, 'testProcedure', FOO='foo', BAR=!pi, BAZ=List(1,2), QUX=Hash(1,2), OUT_NAMES='OUT_HASH', OUT_HASH=callOutHash

print, callOutHash, /IMPLIED

end

This new function behaves similarly to the modified Execute_Wrapper routine, but since it passes all of its keywords through to Call_Procedure it uses references not values. It can then use Scope_VarFetch(/REF_EXTRA) to copy the values from those keywords into a Hash that it returns to its caller. In there we again use Scope_VarFetch(/REF_EXTRA) to copy the output values from that Hash into the variables from its calling routine. The use of the OUT_NAMES keyword is not completely necessary, but it avoids copying input variables back to the caller, which may or may not be expensive.

In the ENVITask context, we know which keywords are inputs and which are outputs, so instead of passing in the OUT_NAMES keyword and returning a Hash of value we can make Procedure_Wrapper a member function and set the output parameter values directly. But as you'll see in ENVI 5.2 SP1 we can wrap any IDL procedure that uses keywords in an ENVITaskFromProcedure object that knows how to map input and output keywords to ENVITaskParameters for you automatically.

File Storage: Using the Regions of Interest (ROI) Tool Image Analysis: Now Part of Hollywood’s Story