Structure to wave note?

Is there a way to pass a dynamic structure definition to a function?

I'm new to using structures in Igor, and would like to have a function that will write a given structure's fields to the note of a wave as a means to carry the data between various routines.

For example, if I have two structures defined in Igor as "A_struct" and "B_struct," then I'd like to have a function such as the following:

Function struct2note(wavenm, s, s_def)
     WAVE wavenm
     string s_def
     STRUCT s_def &s

     ...remaining code

end


...where the "s_def" string can be used to pass the string "A_struct" or "B_struct" to the function so the function can parse it out and append info from it to a wave as a note.

Is this possible to do in Igor?
Quote:
I'm new to using structures in Igor, and would like to have a function that will write a given structure's fields to the note of a wave as a means to carry the data between various routines.


You can pass a structure from one routine to a subroutine without any tricks. If you want to store the structure's information for later user, after the original functions have all returned, then you need tricks.

You can convert the structure to text and then convert back. Here is an example. Note the caveats in the comments:

Structure TestStruct
    Variable num
    String str
    Wave w
EndStructure

Function/S TestStructToText(s)
    STRUCT TestStruct& s
   
    String temp
    String text = ""

    sprintf temp, "num=%.15g;", s.num
    text += temp
   
    /// s.str must not contain = or ; characters
    sprintf temp, "str=%s;", s.str
    text += temp
   
    // Path to wave must not contain = character
    String fullPath = ""
    if (WaveExists(s.w))
        fullPath = GetWavesDataFolder(s.w, 2)
    endif
    sprintf temp, "wave=%s;", fullPath
    text += temp
   
    return text
End

Function SetStructFromText(text, s)
    String text         // keyword-value pair created by TestStructToText
    STRUCT TestStruct& s

    String temp
   
    temp = StringByKey("num", text, "=")
    s.num = str2num(temp)
   
    temp = StringByKey("str", text, "=")
    s.str = temp
   
    temp = StringByKey("wave", text, "=")
    Wave/Z s.w = $temp          // NULL if wave does not exist
End

Function Demo()
    Make /O root:DemoWave
    STRUCT TestStruct s
    s.num = pi
    s.str = "pie"
    Wave s.w = root:DemoWave
    // Print s
   
    String text = TestStructToText(s)
    Print text
   
    STRUCT TestStruct s2
    SetStructFromText(text, s2)
    Print s2
End

Thanks, but that doesnt quite do what I'm wondering. I know you can pass a structure to another function, but in your example there is only one defined structure "TestStruct" and you have a specific routine that converts its values to text.

What I am looking to do instead is write a function that can take any structure definition as an input and then be able to parse it. So if I have one structure definition "A" that has two strings, and structure "B" that has three strings, I'd like to be able to pass the function either of these structures ("A" or "B") and have it identify their properties and convert them to a single text string.

In essence, I'm curious to know if there is a way to use a dynamic structure name in a "STRUCT" declaration in a function. I'd like to be able to use a string variable as the structure name, so the single STRUCT declared in a function can be any structure I've defined. With this approach, I'd like to write a function similar to the one in my first posting, where the string "s_def" can be the name of structure "A" or "B" depending on what I define "s_def" as.
Quote:
What I am looking to do instead is write a function that can take any structure definition as an input and then be able to parse it.


No, this is not possible.

You can freeze-dry any structure using PutStruct and reconstitute it to a structure using GetStruct but you need to know the specific type of the structure to use it once reconstituted.

In its freeze-dried state it is binary and not human-readable. Also you can not freeze-dry any structure containing a String, Wave or DFREF field because the objects they refer to may not exist when they are reconstituted.

To store free-form data you can use a keyword-value string (see StringByKey).

I'm not sure why you want to freeze-dry any structure. You might be approaching the problem in the wrong way for an Igor program

Quote:
In essence, I'm curious to know if there is a way to use a dynamic structure name in a "STRUCT" declaration in a function. I'd like to be able to use a string variable as the structure name, so the single STRUCT declared in a function can be any structure I've defined.


No, this is not possible.


Thanks. That's a bummer. My hope was to define multiple structures in functions and be able to pass their contents globally somehow, without cluttering the data browser with too many variables.

As for why this would be useful, one example would be to call multiple "wavestats" routines and store the resulting variables in several different local structures, such as the following:

//the structure definition to hold "wavestats" output
Structure w_stat
    variable npnts
    variable numNANs
    variable numINFs
    variable avg
    variable sum
    variable sdev
    variable sem
    variable rms
    variable adev
    variable skew
    variable kurt
    variable minloc
    variable maxloc
    variable min
    variable max
    variable minRowLoc
    variable maxRowLoc
    variable startRow
    variable endRow
endstructure

//w_stats structure creation handler
Function wstats(wavenm,w,[M,R])
    WAVE wavenm
    STRUCT w_stats &w
    variable M //mode option
    string R //range option
   
    if(M)
    else
        M=2
    endif
   
    if(!stringmatch(R,""))
        variable points=0
        if(stringmatch(R[0],"["))
            points=1
        elseif(stringmatch(R[0],"("))
            points=0
        else
            Abort "Improper range specification. Use \"[start, stop]\" for points or \"(start, stop)\" for x values"
        endif
        R=R[1,strlen(R)-2]
        variable start=str2num(stringfromlist(0,R,","))
        variable fin=str2num(stringfromlist(1,R,","))
        if(points)
            Wavestats/Q/M=(M)/R=[start,fin] wavenm
        else
            Wavestats/Q/M=(M)/R=(start,fin) wavenm
        endif
    else
        Wavestats/Q/M=(M) wavenm
    endif
   
    w.npnts=V_npnts
    w.numNANs=V_numNANs
    w.numINFs=V_numINFs
    w.avg=V_avg
    w.sum=V_sum
    w.sdev=V_sdev
    w.sem=V_sem
    w.rms=V_rms
    w.adev=V_adev
    w.skew=V_skew
    w.kurt=V_kurt
    w.minloc=V_minloc
    w.maxloc=V_maxloc
    w.min=V_min
    w.max=V_max
    w.minRowLoc=V_minRowLoc
    w.maxRowLoc=V_maxRowLoc
    w.startRow=V_startRow
    w.endRow=V_endRow
end

//All put to use...
Function Demo()
     make test
     make test2

     test=x
     test=x^2

     STRUCT w_stat tt1
     wstats(test, tt1, R="[0-100]") //runs wavestats between pt 0-100 on wave "test" and stores results in struct tt1

     STRUCT w_stat tt2
     wstats(test2, tt2, R="[0-100]") //runs wavestats between pt 0-100 on wave "test2" and stores results in struct tt2

     //calculations can now be done with the structures
     Print tt1.avg-tt2.avg
end


In this case we have two structures of definition "w_stat" that exist locally in the "Demo" function, and arguably provide a more concise approach to pulling the wavestats variables than having to call "wavestats" each time and ensure the output variables (V_avg, V_sum, V_npnts, etc.) are properly stored each time.

The true benefit of this approach would be to pass the data in the structures globally as objects for other uses; however, the structs are restricted to this function and those that are called by it, which is a big limitation for this approach.

One workaround to this limitation is as you mentioned to write a function specific to one structure that will save its contents (with some exceptions) as a global string variable, though this requires a special handling routine for each structure definition when it would be nice to have a single function that can handle "any" structure in this manner, regardless of its definition.

The second option would be to append such a string to a current object that can handle it, and the wave "notes" feature seems like a good option to exploit for this purpose. With a basic pair of "SetNote" and "GetNote" routines that could save variables to the notes by key, one could quickly put and pull the structure information on a wave in whatever function the wave is currently being handled, and without the function needing to be called from a prior one, which is a requirement for direct uses of structures.

I guess one approach for this would be to forego using structs altogether and instead use a routine that will develop and manage the structured information in the notes of a wave directly (setting and getting the data from the wave notes instead of managing a separate struct and then writing it to the wave notes or a string); however, this might slow things down as the data would have to be converted to and from strings all the time instead of being pulled from the notes into a struct at the beginning of the function, and managed as structure data in the function, and then converted back to a note for storage at the end of the function's routines.
It's generally a good idea to store as little data as possible. It reduces clutter, makes what is important more clear, and reduces the possibility of using stale data.

For example, don't store the min and max of a wave unless you can save a significant amount of time by storing it. (If you do store it you will need to store the modification date of the wave to determine if the stored min and max may be stale.)

If you want to store data describing a wave then you should use the wave note to store keyword-value pairs. You compute and store only the data you need. For example, if you need the min and max, compute only what you need to compute (either using WaveMin and WaveMax or WaveStats/M=1) and store the minimum in the wave note.

Where structures work is for passing a collection of data from one function to another to another when the information needed consists of too many items to use regular parameters. You would collect all of the information in the structure in the top-level function and pass it to lower-level functions. You might use the keyword-value pair of the wave note as the source for some of the information in the structure.

The HDF5 Browser.ipf file uses structures this way. The structure HDF5BrowserData stores all of the data needed for an HDF5 browser window. A single function, SetHDF5BrowserData, is called by the high-level routines. It collects all of the information into the structure:
STRUCT HDF5BrowserData bd
SetHDF5BrowserData(browserName, bd)


The structure is then passed from one routine to another.

tkessler wrote:

As for why this would be useful, one example would be to call multiple "wavestats" routines and store the resulting variables in several different local structures, such as the following:


I must be completely missing your point because you could use:

Function wstats(wavenm,name,[M,R])
    WAVE wavenm
    String name
    variable M //mode option
    string R //range option
 
    if(M)
    else
        M=2
    endif
 
    if(!stringmatch(R,""))
        variable points=0
        if(stringmatch(R[0],"["))
            points=1
        elseif(stringmatch(R[0],"("))
            points=0
        else
            Abort "Improper range specification. Use \"[start, stop]\" for points or \"(start, stop)\" for x values"
        endif
        R=R[1,strlen(R)-2]
        variable start=str2num(stringfromlist(0,R,","))
        variable fin=str2num(stringfromlist(1,R,","))
        if(points)
            Wavestats/Q/M=(M)/R=[start,fin]/W wavenm
        else
            Wavestats/Q/M=(M)/R=(start,fin)/W wavenm
        endif
    else
        Wavestats/Q/M=(M)/W wavenm
    endif
 
    Wave M_WaveStats
    Duplicate /O M_WaveStats,$name
end

... and the various waves saved have nice dimension labels for the various variables which you can access with the [%...] method.

A.G.
WaveMetrics, Inc.

Igor wrote:
tkessler wrote:

As for why this would be useful, one example would be to call multiple "wavestats" routines and store the resulting variables in several different local structures, such as the following:


I must be completely missing your point because you could use:

Function wstats(wavenm,name,[M,R])

        ...
 
    Wave M_WaveStats
    Duplicate /O M_WaveStats,$name
end

... and the various waves saved have nice dimension labels for the various variables which you can access with the [%...] method.

A.G.
WaveMetrics, Inc.


I see the mention of the M_WaveStats wave in the help documentation for wavestats, but when I try it out as you mentioned here it gives me an error that it expected a wave name, and upon debugging the "Wave M_WaveStats" declaration results in a pointer to a null wave. Is this perhaps a bug?

While this approach may work for WaveStats, my original goal was to have a paradigm that would globally save the contents of any user-defined structure and then be able to call it back as is needed. I did use a wavestats example here, but that is just one example and there could be other uses for structures to store data.

However, as hrodstein mentioned in his last post the issue here may be the storage of data and that a better approach may be to limit it as much as possible. While I agree reducing this is best, I can still see situations where storing structured data globally could be very useful. I think I will try to rework my approach here to use a keyword-value pairing of data to save directly in the notes of a wave, and create a set of handling functions for this purpose. That's ultimately what I wanted to do from the beginning with a function that could dynamically access a structure by its definition name, but I could not figure out how to do that (and it doesn't seem to be possible).
Quote:
I see the mention of the M_WaveStats wave in the help documentation for wavestats, but when I try it out as you mentioned here it gives me an error that it expected a wave name, and upon debugging the "Wave M_WaveStats" declaration results in a pointer to a null wave.


I see from the help that M_WaveStats is created only if you use /W or /C=<non-zero value>. So if you want to produce M_WaveStats use the /W flag. Here is an example:

Function Test(w)
    Wave w
    WaveStats /W /M=1 /Q w
    Wave M_WaveStats
    Print M_WaveStats
End


It is important that a wave declaration such as "Wave M_WaveStats" be placed after the operation that creates the wave. For an explanation of this, execute:
DisplayHelpTopic "Put WAVE Declaration After Wave Is Created"


hrodstein wrote:
Quote:
I see the mention of the M_WaveStats wave in the help documentation for wavestats, but when I try it out as you mentioned here it gives me an error that it expected a wave name, and upon debugging the "Wave M_WaveStats" declaration results in a pointer to a null wave.


I see from the help that M_WaveStats is created only if you use /W or /C=<non-zero value>. So if you want to produce M_WaveStats use the /W flag. Here is an example:

Function Test(w)
    Wave w
    WaveStats /W /M=1 /Q w
    Wave M_WaveStats
    Print M_WaveStats
End


It is important that a wave declaration such as "Wave M_WaveStats" be placed after the operation that creates the wave. For an explanation of this, execute:
DisplayHelpTopic "Put WAVE Declaration After Wave Is Created"



Ah missed that flag when checking out the code above. Its working now. I'll see what I can do with this as one option at least for handling wavestats.
Hello Everyone,
I am trying to follow all the instructions given here to solve for my problems. Let give you an overview of what I want to achieve. I have a wave acquired over a period of 12hours. I change the concentration every 10 mins (it can vary). I have defined another wave (wave2) which have definite values anytime i change the concentration. Example, when wave1 concentration is changed, wave2 has a value of 2 (manually defined) or else a value of 0. I want to used wave2[i]=2 to average the range using wavestats and copy V_avg, V_npts, V_startRow and V_endRow to columns in a new table. Please help. I am new to Igor.
It sounds like you don't have a continguous block of data, but rather random places within the wave where you need to get the data.

How about something like
Extract wave1, extractedwave, wave2==2
WaveStats extractedwave

In this case, the start and end rows don't have much meaning.

John Weeks
WaveMetrics, Inc.
support@wavemetrics.com