source: sans/Dev/trunk/NCNR_User_Procedures/Reduction/HDF5gateway_NCNR.ipf @ 956

Last change on this file since 956 was 955, checked in by srkline, 8 years ago

some reorganization of the r/w routines to generate HDF test files for SANS and VSANS (all are housed together for testing). also some reorganization of the detector binning routines to get functions grouped in more logical locations.

File size: 43.8 KB
Line 
1#pragma rtGlobals=3             // Use modern global access method.
2
3#include <HDF5 Browser>
4
5//
6// !!! search for "SRK" to see what I have modified to work with NCNR files
7// -- these changes may only be for testing, and may need to be removed in the final version!!!
8//
9//
10//
11
12
13// requires the Wavemetrics "HDF5.xop" to be installed for IgorPro
14
15// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
16// $Id: HDF5gateway.ipf 768 2012-11-26 04:48:16Z svnsmang $
17// This file is part of a project hosted at the Advanced Photon Source.
18// Access all the source files and data files by checking out the project here:
19//   svn co https://subversion.xray.aps.anl.gov/small_angle/hdf5gateway/trunk hdf5gateway
20// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
21// The documentation is written in restructured text for use by Sphinx.
22// A python program will read this file and grab all the text between lines
23// beginning with "//@+" and "//@-" that begin with "//\t" and use them for the Sphinx documentation.
24// The tables here have been adjusted for a fixed width font.  Don't "fix" them in Igor!
25
26//@+
27//      ============================================================
28//      HDF5gateway: HDF5 File I/O Support
29//      ============================================================
30//     
31//      Version: 1.0
32//     
33//      HDF5gateway makes it easy to read
34//      a HDF5 file into an IgorPro folder,
35//      including group and dataset attributes,
36//      such as a NeXus data file,
37//      modify it, and then write it back out.
38//     
39//      .. index:: goal
40//     
41//      The goal was to make it easy to read a HDF5 file into an IgorPro folder,
42//      including group and dataset attributes,
43//      such as a NeXus data file,
44//      modify it, and then write it back out.
45//      This file provides functions to do just that.
46//     
47//      .. index:: functions; public
48//     
49//      Starting with utilities provided in the *HDF5 Browser* package, this file provides
50//      these public functions:
51//     
52//              * :ref:`H5GW_ReadHDF5`
53//              * :ref:`H5GW_WriteHDF5`
54//              * :ref:`H5GW_ValidateFolder`
55//     
56//      and this function which is useful only for testing and development:
57//     
58//              * :ref:`H5GW_TestSuite`
59//     
60//      Help is provided with each of these functions to indicate their usage.
61//     
62//      .. index:: read
63//     
64//      Reading
65//      ===========
66//     
67//      An HDF5 file is read into an IgorPro data folder in these steps:
68//     
69//      #. The groups and datasets are read and stored into an IgorPro folder.
70//      #. Any attributes of these groups and datasets are read and assigned to IgorPro objects.
71//     
72//      .. index:: home
73//     
74//      The data file is expected to be in the *home* folder (the folder specified by IgorPro's *home* path),
75//      or relative to that folder, or given by an absolute path name.
76//     
77//      .. index:: write, HDF5___xref
78//     
79//      Writing
80//      =================
81//     
82//      An IgorPro data folder is written to an HDF5 file in these steps:
83//     
84//      #. The IgorPro folder is validated for correct structure.
85//      #. The objects in the *HDF5___xref* text wave are written to the HDF5 file.
86//      #. Any folder attributes or wave notes are written to the corresponding HDF5 data path.
87//     
88//      The data file is expected to be in the *home* folder (the folder specified by IgorPro's *home* path),
89//      or relative to that folder, or given by an absolute path name.
90//     
91//      .. index:: validate
92//     
93//      Validating
94//      =================
95//     
96//      Call :ref:`H5GW_ValidateFolder` to test if the
97//      *parentFolder* in the IgorPro Data Browser has the proper structure to
98//      successfully write out to an HDF5 file by :ref:`H5GW_WriteHDF5`.
99//     
100//      .. index:: ! HDF5___xref
101//     
102//      Structure of the *HDF5___xref* text wave
103//      =====================================================
104//     
105//      It is necessary to devise a method to correlate the name
106//      of the same object in the HDF5 file with its representation in
107//      the IgorPro data structure.   In IgorPro, certain names are
108//      reserved such that objects cannot be named.  Routines exist
109//      to substitute such names on data import to comply with
110//      these restrictions.  The routine *HDF5LoadGroup* performs
111//      this substitution automatically, yet no routine is provided to
112//      describe any name substitutions performed.
113//     
114//      The text wave, *HDF5___xref*, is created in the base folder of
115//      the IgorPro folder structure to describe the mapping between
116//      relative IgorPro and HDF5 path names, as shown in the next table.
117//      This name was chosen in hopes that it might remain unique
118//      and unused by others at the root level HDF5 files.
119//     
120//              HDF5___xref wave column plan
121//             
122//              =======   ==================
123//              column    description
124//              =======   ==================
125//              0         HDF5 path
126//              1         Igor relative path
127//              =======   ==================
128//     
129//              **Example**
130//             
131//              Consider the HDF5 file with datasets stored in this structure:
132//     
133//              .. code-block:: guess
134//                      :linenos:
135//                     
136//                      /
137//                        /sasentry01
138//                          /sasdata01
139//                            I
140//                            Q
141//     
142//              The next table shows the contents of *HDF5___xref* once this
143//              HDF5 is read by *H5GW_WriteHDF5()*:
144//             
145//              ===  =======================  ==========================
146//              row  ``HDF5___xref[row][0]``  ``HDF5___xref[row][1]``
147//              ===  =======================  ==========================
148//              0    /                        :
149//              1    /sasentry01              :sasentry01
150//              2    /sasentry01/sasdata01    :sasentry01:sasdata01
151//              3    /sasentry01/sasdata01/I  :sasentry01:sasdata01:I0
152//              4    /sasentry01/sasdata01/Q  :sasentry01:sasdata01:Q0
153//              ===  =======================  ==========================
154//     
155//              Remember, column 0 is for HDF5 paths, column 1 is for IgorPro paths.
156//     
157//      On reading an HDF5 file, the *file_name* and *file_path* are written to the
158//      wave note of *HDF5___xref*.  These notations are strictly informative and
159//      are not used further by this interface.  When writing back to HDF5, any
160//      wave notes of the *HDF5___xref* wave are ignored.
161//     
162//              .. rubric::  About *HDF5___xref*:
163//     
164//              * Only the folders and waves listed in the *HDF5___xref* text
165//                wave will be written to the HDF5 file.
166//              * The *HDF5___xref* text wave is **not written** to the HDF5 file.
167//     
168//      When writing an HDF5 file with these functions,
169//      based on the structure expected in an IgorPro data folder structure,
170//      the *HDF5___xref* text wave is required.  Each IgorPro object described
171//      must exist as either an IgorPro folder or wave.  A wave note is optional.
172//      For each such IgorPro object, a corresponding HDF5 file object will be created.
173//     
174//      .. note:: Important!  Any IgorPro data storage objects (folders or waves)
175//         not listed in *HDF5___xref* **will not be written** to the HDF5 file.
176//     
177//      .. index:: group
178//      .. index:: folder
179//     
180//      Groups and Folders
181//      =====================
182//     
183//      An HDF5 *group* corresponds to the IgorPro *folder*.  Both are containers
184//      for either data or containers. 
185//     
186//      .. index:: Igor___folder_attributes
187//     
188//      In HDF5, a group may have attached metadata
189//      known as *attributes*.  In IgorPro, folders have no provision to store 
190//      attributes, thus an optional *Igor___folder_attributes* wave is created.  The
191//      folder attributes are stored in the wave note of this wave.  For more information
192//      about attributes, see the discussion of :ref:`attributes` below.
193//     
194//      .. index:: datasets
195//      .. index:: waves
196//     
197//      Datasets and Waves
198//      ======================
199//     
200//      Data is stored in HDF5 datasets and IgorPro waves. 
201//      Both objects are capable of storing a variety of data types
202//      with different shapes (rank and length).  Of the two systems,
203//      IgorPro is the more restrictive, limiting the rank of stored data
204//      to four dimensions.
205//     
206//      Keep in mind that all components of a single dataset (or wave) are
207//      of the same data type (such as 64-bit float or 8-bit int).
208//     
209//      In HDF5, a dataset may have attached metadata known as
210//      *attributes*.  HDF5 attributes are data structures in their own
211//      right and may contain data structures.  In IgorPro, waves have
212//      a provision to store  attributes in a text construct called the *wave note*. 
213//      Of these two, IgorPro is the more restrictive, unless one creates
214//      a new wave to hold the data structure of the attributes.
215//      For more information
216//      about attributes, see the discussion of :ref:`attributes` below.
217//     
218//      The HDF5 library used by this package will take care of converting
219//      between HDF5 datasets and IgorPro waves and the user need
220//      not be too concerned about this.
221//     
222//     
223//      .. index:: attributes
224//      .. index:: ! Igor___folder_attributes
225//     
226//      .. _attributes:
227//     
228//      Attributes and Wave Notes
229//      ============================================
230//     
231//      Metadata about each of the objects in HDF5 files and IgorPro folders
232//      is provided by *attributes*.  In HDF5, these are attributes directly attached
233//      to the object (group or dataset).  In IgorPro, these attributes are **stored as text** in
234//      different places depending on the type of the object, as shown in this table:
235//     
236//              ========   =======================================================
237//              object     description
238//              ========   =======================================================
239//              folder     attributes are stored in the wave note of a special
240//                         wave in the folder named *Igor___folder_attributes*
241//              wave       attributes are stored in the wave note of the wave
242//              ========   =======================================================
243//     
244//      .. note:: IgorPro folders do not have a *wave note*
245//     
246//      HDF5 allows an attribute to be a data structure with the same rules for
247//      complexity as a dataset except that attributes must be attached to a dataset
248//      and cannot themselves have attributes.
249//     
250//      .. note:: In IgorPro, attributes will be stored as text.
251//     
252//      An IgorPro wave note is a text string that is used here to store a list of
253//      *key,value* pairs.  IgorPro provides helpful routines to manipulate such
254//      lists, especially when used as wave notes.  The IgorPro wave note is the most
255//      natural representation of an *attribute* except that it does not preserve
256//      the data structure of an HDF5 attribute without additional coding.  This
257//      limitation is deemed acceptable for this work. 
258//     
259//      It is most obvious to see
260//      the conversion of attributes into text by reading and HDF5 file and then
261//      writing it back out to a new file.  The data type of the HDF5 attributes will
262//      likely be changed from its original type into "string, variable length".  If this
263//      is not acceptable, more work must be done in the routines below.
264//     
265//      IgorPro key,value list for the attributes
266//      ----------------------------------------------------------------------------------------
267//     
268//      Attributes are represented in IgorPro wave notes using a
269//      list of *key,value* pairs.  For example:
270//     
271//              .. code-block:: guess
272//                      :linenos:
273//     
274//                      NX_class=SASdata
275//                      Q_indices=0,1
276//                      I_axes=Q,Q
277//                      Mask_indices=0,1
278//     
279//      It is important to know the delimiters used by this string to
280//      differentiate various attributes, some of which may have a
281//      list of values.  Please refer to this table:
282//     
283//              ===========  ====  ==========================================
284//              separator    char  description
285//              ===========  ====  ==========================================
286//              keySep       =     between *key* and *value*
287//              itemSep      ,     between multiple items in *value*
288//              listSep      \\r   between multiple *key,value* pairs
289//              ===========  ====  ==========================================
290//     
291//      .. note::  A proposition is to store these values in a text wave
292//         at the base of the folder structure and then use these value
293//         throughout the folder.  This can allow some flexibility with other
294//         code and to make obvious which terms are used.
295//     
296//      .. index:: example
297//     
298//      Examples
299//      ====================
300//     
301//      Export data from IgorPro
302//      -------------------------------------------------------
303//     
304//      To write a simple dataset *I(Q)*, one might write this IgorPro code:
305//     
306//              .. code-block:: guess
307//                      :linenos:
308//                     
309//                      // create the folder structure
310//                      NewDataFolder/O/S root:mydata
311//                      NewDataFolder/O sasentry
312//                      NewDataFolder/O :sasentry:sasdata
313//     
314//                      // create the waves
315//                      Make :sasentry:sasdata:I0
316//                      Make :sasentry:sasdata:Q0
317//     
318//                      Make/N=0 Igor___folder_attributes
319//                      Make/N=0 :sasentry:Igor___folder_attributes
320//                      Make/N=0 :sasentry:sasdata:Igor___folder_attributes
321//     
322//                      // create the attributes
323//                      Note/K Igor___folder_attributes, "producer=IgorPro\rNX_class=NXroot"
324//                      Note/K :sasentry:Igor___folder_attributes, "NX_class=NXentry"
325//                      Note/K :sasentry:sasdata:Igor___folder_attributes, "NX_class=NXdata"
326//                      Note/K :sasentry:sasdata:I0, "units=1/cm\rsignal=1\rtitle=reduced intensity"
327//                      Note/K :sasentry:sasdata:Q0, "units=1/A\rtitle=|scattering vector|"
328//     
329//                      // create the cross-reference mapping
330//                      Make/T/N=(5,2) HDF5___xref
331//                      Edit/K=0 'HDF5___xref';DelayUpdate
332//                      HDF5___xref[0][1] = ":"
333//                      HDF5___xref[1][1] = ":sasentry"
334//                      HDF5___xref[2][1] = ":sasentry:sasdata"
335//                      HDF5___xref[3][1] = ":sasentry:sasdata:I0"
336//                      HDF5___xref[4][1] = ":sasentry:sasdata:Q0"
337//                      HDF5___xref[0][0] = "/"
338//                      HDF5___xref[1][0] = "/sasentry"
339//                      HDF5___xref[2][0] = "/sasentry/sasdata"
340//                      HDF5___xref[3][0] = "/sasentry/sasdata/I"
341//                      HDF5___xref[4][0] = "/sasentry/sasdata/Q"
342//     
343//                      // Check our work so far.
344//                      // If something prints, there was an error above.
345//                      print H5GW_ValidateFolder("root:mydata")
346//     
347//                      // set I0 and Q0 to your data
348//             
349//                      print H5GW_WriteHDF5("root:mydata", "mydata.h5")
350//     
351//      .. index:: read
352//     
353//      Read data into IgorPro
354//      -------------------------------------------------------
355//     
356//      .. index:: example
357//     
358//      This is a simple operation, reading the file from the previous example into a new folder:
359//     
360//              .. code-block:: guess
361//                      :linenos:
362//                     
363//                      NewDataFolder/O/S root:newdata
364//                      H5GW_ReadHDF5("", "mydata.h5")  // reads into current folder
365//@-
366
367//  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //
368
369
370//@+
371//      .. index:: read
372//     
373//      Public Functions
374//      ======================================
375//     
376//      .. index:: ! H5GW_ReadHDF5()
377//     
378//      .. _H5GW_ReadHDF5:
379//     
380//      H5GW_ReadHDF5(parentFolder, fileName, [hdf5Path])
381//      -------------------------------------------------------------------------------------------------------------
382//     
383//      Read the HDF5 data file *fileName* (located in directory *data*,
384//      an IgorPro path variable) and store it in a subdirectory of
385//      IgorPro folder *parentFolder*.
386//     
387//      At present, the *hdf5Path* parameter is not used.  It is planned
388//      (for the future) to use this to indicate reading only part of the
389//      HDF5 file to be read.
390//     
391//      :String parentFolder: Igor folder path (default is current folder)
392//      :String fileName: name of file (with extension),
393//                      either relative to current file system directory,
394//                      or include absolute file system path
395//      :String hdf5Path: path of HDF file to load (default is "/")
396//              :return String: Status: ""=no error, otherwise, error is described in text
397//@-
398
399Function/T H5GW_ReadHDF5(parentFolder, fileName, [hdf5Path])
400        String parentFolder, fileName, hdf5Path
401        if ( ParamIsDefault(hdf5Path) )
402                hdf5Path = "/"
403        endif
404
405        String status = ""
406        String oldFolder
407        oldFolder = GetDataFolder(1)
408        parentFolder = H5GW__SetStringDefault(parentFolder, oldFolder)
409
410        // First, check that parentFolder exists
411        if ( DataFolderExists(parentFolder) )
412                SetDataFolder $parentFolder
413        else
414                return parentFolder + " (Igor folder) not found"
415        endif
416       
417        // do the work here:
418        Variable/G fileID = H5GW__OpenHDF5_RO(fileName)
419        if ( fileID == 0 )
420                return fileName + ": could not open as HDF5 file"
421        endif
422       
423        SVAR tmpStr=root:file_name
424        fileName=tmpStr         //SRK - in case the file was chosen from a dialog
425       
426        //   read the data (too bad that HDF5LoadGroup does not read the attributes)
427        String base_name = StringFromList(0,FileName,".")
428        HDF5LoadGroup/Z/L=7/O/R/T=$base_name  :, fileID, hdf5Path               //      recursive
429        if ( V_Flag != 0 )
430                SetDataFolder $oldFolder
431                return fileName + ": problem while opening HDF5 file"
432        endif
433        String/G objectPaths = S_objectPaths  // this gives a clue to renamed datasets (see below for attributes)
434        //   read the attributes
435        H5GW__HDF5ReadAttributes(fileID, hdf5Path, base_name)
436        HDF5CloseFile fileID
437
438        String/G file_path
439        String/G group_name_list
440        String/G dataset_name_list
441        String file_info
442        sprintf file_info, ":%s:HDF5___xref", base_name
443        Note/K $file_info, "file_name="+fileName
444        Note $file_info, "file_path="+file_path
445
446        KillStrings/Z file_path, file_name, objectPaths, group_name_list, dataset_name_list
447        KillVariables/Z fileID
448       
449        SetDataFolder $oldFolder
450        return status
451End
452
453
454// ======================================
455//@+
456//      .. index:: write
457//      .. index:: ! H5GW_WriteHDF5()
458//     
459//      .. _H5GW_WriteHDF5:
460//     
461//      H5GW_WriteHDF5(parentFolder, newFileName)
462//      -------------------------------------------------------------------------------------------------------------
463//     
464//      Starting with an IgorPro folder constructed such that it passes the :ref:`H5GW_ValidateFolder` test,
465//      write the components described in *HDF5___xref* to *newFileName*.
466//     
467//      :String parentFolder: Igor folder path (default is current folder)
468//      :String fileName: name of file (with extension),
469//                      either relative to current file system directory,
470//                      or include absolute file system path
471//@-
472Function/T H5GW_WriteHDF5(parentFolder, newFileName, [replace])
473        String parentFolder, newFileName
474        Variable replace
475        if ( ParamIsDefault(replace) )
476                replace = 1
477        endif
478
479        String status = ""
480        String oldFolder = GetDataFolder(1)
481
482        // First, check that parentFolder exists
483        status = H5GW_ValidateFolder(parentFolder)
484        if ( strlen(status) > 0 )
485                return status
486        endif
487        SetDataFolder $parentFolder
488       
489        // Build HDF5 group structure
490        Variable fileID = H5GW__OpenHDF5_RW(newFileName, replace)
491        if (fileID == 0)
492                SetDataFolder $oldFolder
493                return "Could not create HDF5 file " + newFileName + " for writing"
494        endif
495
496        // write datasets and attributes based on HDF5___xref table
497        status = H5GW__WriteHDF5_Data(fileID)
498        if ( strlen(status) > 0 )
499                HDF5CloseFile fileID
500                SetDataFolder $oldFolder
501                return status
502        endif
503       
504        HDF5CloseFile fileID
505
506        SetDataFolder $oldFolder
507        return status                   // report success
508End
509
510
511
512// ======================================
513//@+
514//      .. index:: validate
515//      .. index:: ! H5GW_ValidateFolder()
516//     
517//      .. _H5GW_ValidateFolder:
518//     
519//      H5GW_ValidateFolder(parentFolder)
520//      -------------------------------------------------------------------------------------------------------------
521//     
522//      Check (validate) that a given IgorPro folder has the necessary
523//      structure for the function H5GW__WriteHDF5_Data(fileID) to be
524//      successful when writing that folder to an HDF5 file.
525//     
526//              :String parentFolder: Igor folder path (default is current folder)
527//              :return String: Status: ""=no error, otherwise, error is described in text
528//@-
529
530Function/T H5GW_ValidateFolder(parentFolder)
531        String parentFolder
532        String oldFolder = GetDataFolder(1)
533        // First, check that parentFolder exists
534        if ( DataFolderExists(parentFolder) )
535                SetDataFolder $parentFolder
536        else
537                return parentFolder + " (Igor folder) not found"
538        endif
539
540        if (1 != Exists("HDF5___xref"))
541                SetDataFolder $oldFolder
542                return "required wave (HDF5___xref) is missing in folder: " + parentFolder
543        endif
544        Wave/T HDF5___xref
545        if ( DimSize(HDF5___xref, 1) != 2 )
546                SetDataFolder $oldFolder
547                return "text wave HDF5___xref must be of shape (N,2)"
548        endif
549
550        Variable length = DimSize(HDF5___xref, 0), ii
551        String item, msg
552        for (ii=0; ii < length; ii=ii+1)
553                item = HDF5___xref[ii][1]
554                if ( (1 != DataFolderExists(item)) && (1 != Exists(item)) )
555                        SetDataFolder $oldFolder
556                        return "specified IgorPro object " + item + " was not found in folder " + parentFolder
557                endif
558                // TODO: Check that each corresponding HDF5___xref[ii][0] is a valid HDF5 path name
559                if ( itemsInList(item, ":") != itemsInList(HDF5___xref[ii][0], "/") )
560                        SetDataFolder $oldFolder
561                        msg = "different lengths between HDF5 and IgorPro paths on row" + num2str(ii) + "of HDF5___xref"
562                        return msg
563                endif
564        endfor
565       
566        // TODO: more validation steps
567
568        SetDataFolder $oldFolder
569        return ""
570End
571
572
573// ======================================
574//@+
575//      .. index:: test
576//      .. index:: ! H5GW_TestSuite()
577//     
578//      .. _H5GW_TestSuite:
579//     
580//      H5GW_TestSuite()
581//      -------------------------------------------------------------------------------------------------------------
582//     
583//      Test the routines in this file using the supplied test data files.
584//      HDF5 data files are obtained from the canSAS 2012 repository of
585//      HDF5 examples
586//      (http://www.cansas.org/formats/canSAS2012/1.0/doc/_downloads/simpleexamplefile.h5).
587//@-
588
589Function H5GW_TestSuite()
590        String listSep = ";"
591        String fileExt = ".h5"
592        String parentDir = "root:worker"
593
594        String name_list = "simpleexamplefile"
595        name_list = name_list +listSep + "simple2dcase"
596        name_list = name_list +listSep + "simple2dmaskedcase"
597        name_list = name_list +listSep + "generic2dqtimeseries"
598        name_list = name_list +listSep + "generic2dtimetpseries"
599
600        Variable length = itemsInList(name_list, listSep), ii
601        String name, newName, newerName
602        for (ii = 0; ii < length; ii = ii + 1)
603                name = StringFromList(ii, name_list, listSep) + fileExt
604                // Test reading the HDF5 file and then writing the data to a new HDF5 file
605                newName = H5GW__TestFile(parentDir, name)
606                // Apply the test again on the new HDF5 file
607                newerName = H5GW__TestFile(parentDir, newName)
608        endfor
609End
610
611
612//  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //
613
614
615// ======================================
616//@+
617//      .. index:: functions; private (static)
618//     
619//      Private (static) Functions
620//      ======================================
621//     
622//      Documentation of some, but not all, private functions is provided.
623//     
624//@-
625
626
627//@+
628//      .. index:: ! H5GW__OpenHDF5_RW()
629//     
630//      H5GW__OpenHDF5_RW(newFileName, replace)
631//      -------------------------------------------------------------------------------------------------------------
632//@-
633static Function H5GW__OpenHDF5_RW(newFileName, replace)
634        String newFileName
635        Variable replace
636        Variable fileID
637        if (replace)
638                HDF5CreateFile/P=home/Z/O fileID as newFileName
639        else
640                // make sure file does not exist now, or handle better
641                HDF5CreateFile/P=home/Z fileID as newFileName
642        endif
643        if (V_Flag != 0)
644                return 0
645        endif
646        return fileID
647End
648
649
650//@+
651//      .. index:: ! H5GW__WriteHDF5_Data()
652//     
653//      H5GW__WriteHDF5_Data(fileID)
654//      -------------------------------------------------------------------------------------------------------------
655//@-
656static Function/T H5GW__WriteHDF5_Data(fileID)
657        Variable fileID
658        String status = ""
659       
660        Wave/t xref = HDF5___xref
661        Variable HDF5_col = 0
662        Variable Igor_col = 1
663        Variable rows = DimSize(xref,0), ii, groupID, length, jj
664        String igorPath, hdf5Path, dataType, folder_attr_info, notes, item, key, value
665        for (ii = 0; ii < rows; ii=ii+1)
666                igorPath = xref[ii][Igor_col]
667                hdf5Path = xref[ii][HDF5_col]
668                // print DataFolderExists(igorPath), igorPath, " --> ", hdf5Path
669                if ( DataFolderExists(igorPath) )
670                        // group
671                        if ( cmpstr("/", hdf5Path) != 0 )
672                                HDF5CreateGroup /Z fileID , hdf5Path, groupID
673                                if (V_Flag != 0)
674                                        status = H5GW__AppendString(status, "\r",  "problem creating HDF5 group: " + hdf5Path)
675                                endif
676                        else
677                                groupID = fileID
678                        endif
679                        // attributes
680                        notes = ""
681                        folder_attr_info = H5GW__appendPathDelimiter(igorPath, ":") + "Igor___folder_attributes"
682                        Wave/Z folder_attr = $folder_attr_info
683                        // SRK - folder_attr wave may not exist if HDF file not written out by HDF5Gateway. Handle the error
684                        if(WaveExists(folder_attr))
685                                notes = note(folder_attr)
686                                length = itemsInList(notes, "\r")
687                                String response
688                                if ( length > 0 )
689                                        for (jj = 0; jj < length; jj=jj+1 )
690                                                item = StringFromList(jj, notes,"\r")
691                                                key = StringFromList(0, item,"=")
692                                                value = StringFromList(1, item,"=")
693                                                response = H5GW__SetTextAttributeHDF5(fileID, key, value, hdf5Path)
694                                                status = H5GW__AppendString(status, "\r",  response)
695                                        endfor
696                                endif
697                        endif   // SRK check if folder_attr exists
698                else
699                        // dataset
700                        Wave theWave = $igorPath
701                        HDF5SaveData/IGOR=0/Z theWave, fileID, hdf5Path
702                        if (V_Flag != 0)
703                                status = H5GW__AppendString(status, "\r",  "problem saving HDF5 dataset: " + hdf5Path)
704                        endif
705
706                        // look at the wave note for any attributes
707                        notes = note(theWave)
708                        length = itemsInList(notes, "\r")
709                        if ( length > 0 )
710                                for (jj = 0; jj < length; jj=jj+1 )
711                                        item = StringFromList(jj, notes,"\r")
712                                        key = StringFromList(0, item,"=")
713                                        value = StringFromList(1, item,"=")
714                                        H5GW__SetTextAttributeHDF5(fileID, key, value, hdf5Path)
715                                endfor
716                        endif
717
718                endif
719        endfor
720       
721        return status
722End
723
724
725//@+
726//      .. index:: ! H5GW__SetHDF5ObjectAttributes()
727//     
728//      H5GW__SetHDF5ObjectAttributes(itemID, igorPath, hdf5Path)
729//      -------------------------------------------------------------------------------------------------------------
730//@-
731static Function H5GW__SetHDF5ObjectAttributes(itemID, igorPath, hdf5Path)
732        Variable itemID
733        String igorPath, hdf5Path
734        Wave theWave = $igorPath
735        String notes = note(theWave), item, key, value
736        Variable jj, length = itemsInList(notes, "\r")
737        notes = note(theWave)
738        length = itemsInList(notes, "\r")
739        if ( length > 0 )
740                for (jj = 0; jj < length; jj=jj+1 )
741                        item = StringFromList(jj, notes,"\r")
742                        key = StringFromList(0, item,"=")
743                        value = StringFromList(1, item,"=")
744                        H5GW__SetTextAttributeHDF5(itemID, key, value, hdf5Path)
745                endfor
746        endif
747End
748
749
750//@+
751//      .. index:: ! H5GW__SetTextAttributeHDF5()
752//     
753//      H5GW__SetTextAttributeHDF5(itemID, name, value, hdf5Path)
754//      -------------------------------------------------------------------------------------------------------------
755//@-
756static Function/T H5GW__SetTextAttributeHDF5(itemID, name, value, hdf5Path)
757        Variable itemID
758        String name, value, hdf5Path
759        String status = ""
760        Make/T/N=(1) H5GW____temp
761        H5GW____temp[0] = value
762        HDF5SaveData/Z/A=name   H5GW____temp, itemID, hdf5Path
763        if ( V_Flag != 0)
764                status = "problem saving HDF5 text attribute: " + hdf5Path
765        endif
766        KillWaves  H5GW____temp
767        return status
768End
769
770
771// ======================================
772//@+
773//      .. index:: ! H5GW__make_xref()
774//     
775//      H5GW__make_xref(parentFolder, objectPaths, group_name_list, dataset_name_list, base_name)
776//      ---------------------------------------------------------------------------------------------------------------------------------------------------------
777//     
778//      Analyze the mapping between HDF5 objects and Igor paths
779//      Store the discoveries of this analysis in the HDF5___xref text wave
780//     
781//              :String parentFolder: Igor folder path (default is current folder)
782//              :String objectPaths: Igor paths to data objects
783//              :String group_name_list:
784//              :String dataset_name_list:
785//              :String base_name:
786//     
787//      HDF5___xref wave column plan
788//     
789//              ======   ===============================
790//              column   description
791//              ======   ===============================
792//              0        HDF5 path
793//              1        Igor relative path
794//              ======   ===============================
795//@-
796
797static Function H5GW__make_xref(parentFolder, objectPaths, group_name_list, ds_list, base_name)
798        String parentFolder, objectPaths, group_name_list, ds_list, base_name
799       
800        String xref = ""                // key.value pair list as a string
801        String keySep = "="     // between key and value
802        String listSep = "\r"   // between key,value pairs
803       
804        String matchStr = parentFolder + base_name
805        String igorPaths = ReplaceString(matchStr, objectPaths, "")
806       
807        Variable ii, length
808        String dataset, igorPath
809        // compare items in ds_list and igorPaths
810        length = itemsInList(ds_list, ";")
811        if ( length == itemsInList(igorPaths, ";") )
812                for (ii = 0; ii < length; ii = ii + 1)
813                        // ASSUME this is the cross-reference list we need
814                        dataset = StringFromList(ii, ds_list, ";")
815                        igorPath = StringFromList(ii, igorPaths, ";")
816                        xref = H5GW__addPathXref(parentFolder, base_name, dataset, igorPath, xref, keySep, listSep)
817                endfor
818        else
819                // TODO: report an error here and return
820        endif
821       
822        // finally, write the xref contents to a wave
823        length = itemsInList(xref, listSep)
824        String file_info
825        sprintf file_info, ":%s:HDF5___xref", base_name
826        Make/O/N=(length,2)/T $file_info
827        Wave/T file_infoT = $file_info
828        String item
829        Variable HDF5_col = 0
830        Variable Igor_col = 1
831        for (ii = 0; ii < length; ii=ii+1)
832                item = StringFromList(ii, xref, listSep)
833                file_infoT[ii][HDF5_col] = StringFromList(0, item, keySep)
834                file_infoT[ii][Igor_col] = StringFromList(1, item, keySep)
835        endfor
836End
837
838
839//@+
840//      .. index:: ! H5GW__addPathXref()
841//     
842//      H5GW__addPathXref(parentFolder, base_name, hdf5Path, igorPath, xref, keySep, listSep)
843//      ----------------------------------------------------------------------------------------------------------------------------------------------------------
844//@-
845static Function/T H5GW__addPathXref(parentFolder, base_name, hdf5Path, igorPath, xref, keySep, listSep)
846        String parentFolder, base_name, hdf5Path, igorPath, xref, keySep, listSep
847        String result = xref
848        // look through xref to find each component of full hdf5Path, including the dataset
849        Variable ii, length = itemsInList(hdf5Path, "/")
850        String hdf5 = "", path = ""
851        for (ii = 0; ii < length; ii = ii + 1)
852                hdf5 = H5GW__appendPathDelimiter(hdf5, "/") + StringFromList(ii, hdf5Path, "/")
853                path = H5GW__appendPathDelimiter(path, ":") + StringFromList(ii, igorPath, ":")
854                if ( strlen(StringByKey(hdf5, result, keySep, listSep)) == 0 )
855                        result = H5GW__addXref(hdf5, path, result, keySep, listSep)
856                endif
857        endfor
858        return result
859End
860
861
862//@+
863//      .. index:: ! H5GW__addXref()
864//     
865//      H5GW__addXref(key, value, xref, keySep, listSep)
866//      -------------------------------------------------------------------------------------------------------------
867//     
868//      append a new key,value pair to the cross-reference list
869//@-
870static Function/T H5GW__addXref(key, value, xref, keySep, listSep)
871        String key, value, xref, keySep, listSep
872        return H5GW__AppendString(xref, listSep,  key + keySep + value)
873End
874
875
876//@+
877//      .. index:: ! H5GW__appendPathDelimiter()
878//     
879//      H5GW__appendPathDelimiter(str, sep)
880//      -------------------------------------------------------------------------------------------------------------
881//@-
882static Function/T H5GW__appendPathDelimiter(str, sep)
883        String str, sep
884        if ( (strlen(str) == 0) || ( cmpstr(sep, str[strlen(str)-1]) != 0) )
885                return str + sep
886        endif
887        return str
888End
889
890
891
892// ======================================
893//@+
894//      .. index:: ! H5GW__findTextWaveIndex()
895//     
896//      H5GW__findTextWaveIndex(twave, str, col)
897//      -------------------------------------------------------------------------------------------------------------
898//     
899//              :Wave/T twave: correlation between HDF5 and Igor paths
900//              :String str: text to be located in column *col*
901//              :int col: column number to search for *str*
902//              :returns int: index of found text or -1 if not found
903//@-
904
905static Function H5GW__findTextWaveIndex(twave, str, col)
906        Wave/T twave
907        String str
908        Variable col
909        Variable result = -1, ii, rows=DimSize(twave,0)
910        for (ii=0; ii < rows; ii=ii+1)
911                if (0 == cmpstr(str, twave[ii][col]) )
912                        result = ii
913                        break
914                endif
915        endfor
916        return result
917End
918
919
920// ======================================
921//@+
922//      .. index:: ! H5GW__OpenHDF5_RO()
923//     
924//      H5GW__OpenHDF5_RO(fileName)
925//      -------------------------------------------------------------------------------------------------------------
926//     
927//              :String fileName: name of file (with extension),
928//                      either relative to current file system directory
929//                      or includes absolute file system path
930//              :returns int: Status: 0 if error, non-zero (fileID) if successful
931//     
932//         Assumed Parameter:
933//     
934//              * *home* (path): Igor path name (defines a file system
935//                        directory in which to find the data files)
936//                        Note: data is not changed by this function
937//@-
938
939Static Function H5GW__OpenHDF5_RO(fileName)
940        String fileName
941       
942// SRK - now will present a dialog if the file can't be found
943//      if ( H5GW__FileExists(fileName) == 0 )
944                // avoid the open file dialog if the file is not found here
945//              return 0
946//      endif
947
948        Variable fileID = 0
949        HDF5OpenFile/R/P=home/Z fileID as fileName
950//      HDF5OpenFile/R/P=catPathName/Z fileID as fileName
951        if (V_Flag != 0)
952                return 0
953        endif
954
955        if ( 0 )
956                STRUCT HDF5DataInfo di  // Defined in HDF5 Browser.ipf.
957                InitHDF5DataInfo(di)    // Initialize structure.
958                HDF5AttributeInfo(fileID, "/", 1, "file_name", 0, di)
959                Print di
960        endif
961
962
963        String/G root:file_path = S_path
964        String/G root:file_name = S_FileName
965        return fileID
966End
967
968
969// ======================================
970//@+
971//      .. index:: ! H5GW__HDF5ReadAttributes()
972//     
973//      H5GW__HDF5ReadAttributes(fileID, hdf5Path, baseName)
974//      -------------------------------------------------------------------------------------------------------------
975//     
976//      Reads and assigns the group and dataset attributes.
977//      For groups, it creates a dummy wave *Igor___folder_attributes*
978//      to hold the group attributes.
979//     
980//      All attributes are stored in the wave note
981//     
982//      Too bad that HDF5LoadGroup does not read the attributes.
983//     
984//              :int fileID: IgorPro reference number for this HDF5 file
985//              :String hdf5Path: read the HDF5 file starting
986//                              from this level (default is the root level, "/")
987//                              Note: not implemented yet.
988//              :String baseName: IgorPro subfolder name to
989//                              store attributes.  Maps directly from HDF5 path.
990//@-
991
992Static Function H5GW__HDF5ReadAttributes(fileID, hdf5Path, baseName)
993        Variable fileID
994        String hdf5Path
995        String baseName
996       
997        Variable group_attributes_type = 1
998        Variable dataset_attributes_type = 2
999       
1000        // read and assign group attributes
1001        String S_HDF5ListGroup
1002        HDF5ListGroup/F/R/TYPE=(group_attributes_type)  fileID, hdf5Path                //      TYPE=1 reads groups
1003        String/G group_name_list = hdf5Path + ";" + S_HDF5ListGroup
1004       
1005        Variable length = ItemsInList(group_name_list)
1006        Variable index, i_attr
1007        String group_name
1008        String attr_name_list, attr_name, attribute_str
1009       
1010        String old_dir = GetDataFolder(1), subdir, group_attr_name
1011        for (index = 0; index < length; index = index+1)
1012                group_name = StringFromList(index, group_name_list)
1013                attribute_str = H5GW__HDF5AttributesToString(fileID, group_name, group_attributes_type)
1014                if ( strlen(attribute_str) > 0 )
1015                        // store these attributes in the wavenote of a unique wave in the group
1016                        subdir = ":" + baseName + ReplaceString("/", group_name, ":")
1017                        SetDataFolder $subdir
1018                        group_attr_name = StringFromList((itemsInList(group_name, "/")-1), group_name, "/")
1019                        group_attr_name = H5GW__SetStringDefault(group_attr_name, "root") + "_attributes"
1020                        group_attr_name = "Igor___folder_attributes"
1021                        Make/O/N=0 $group_attr_name
1022                        Note/K $group_attr_name, attribute_str
1023                endif
1024                SetDataFolder $old_dir
1025        endfor
1026       
1027        // read and assign dataset attributes
1028        HDF5ListGroup/F/R/TYPE=(dataset_attributes_type)  fileID, hdf5Path              //      TYPE=2 reads groups
1029        String/G dataset_name_list = S_HDF5ListGroup
1030
1031        // build a table connecting objectPaths with group_name_list and dataset_name_list
1032        // using parentFolder and baseName
1033        String parentFolder = GetDataFolder(1)
1034        String/G objectPaths
1035        H5GW__make_xref(parentFolder, objectPaths, group_name_list, dataset_name_list, baseName)
1036
1037        String file_info
1038        sprintf file_info, ":%s:HDF5___xref", baseName
1039        Wave/T xref = $file_info
1040
1041        Variable row
1042        String hdf5_path, igor_path
1043        length = ItemsInList(dataset_name_list)
1044        for (index = 0; index < length; index = index+1)
1045                hdf5_path = StringFromList(index, dataset_name_list)
1046                attribute_str = H5GW__HDF5AttributesToString(fileID, hdf5_path, dataset_attributes_type)
1047                if ( strlen(attribute_str) > 0 )
1048                        // store these attributes in the wavenote of the dataset
1049                        row = H5GW__findTextWaveIndex(xref, hdf5_path, 0)
1050                        if (row > -1)
1051                                igor_path = ":" + baseName + xref[row][1]
1052                                wave targetWave=$igor_path
1053                                Note/K targetWave, attribute_str
1054                        endif
1055                endif
1056        endfor
1057
1058End
1059
1060// ======================================
1061//@+
1062//      .. index:: ! H5GW__HDF5AttributesToString()
1063//     
1064//      H5GW__HDF5AttributesToString(fileID, hdf5_Object, hdf5_Type, [keyDelimiter, keyValueSep, itemDelimiter])
1065//      ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
1066//     
1067//      Reads the attributes assigned to this object and returns
1068//      a string of key=value pairs, delimited by ;
1069//      Multiple values for a key are delimited by ,
1070//     
1071//      All attributes are stored in the wave note
1072//     
1073//      Too bad that HDF5LoadGroup does not read the attributes.
1074//     
1075//              :int fileID: IgorPro reference number for this HDF5 file
1076//              :String hdf5_Object: full HDF5 path of object
1077//              :int hdf5_Type: 1=group, 2=dataset
1078//              :String keyDelimiter: between key=value pairs, default = "\r"
1079//              :String keyValueSep: key and value, default = "="
1080//              :String itemDelimiter: between multiple values, default = ","
1081//              :returns str: key1=value;key2=value,value;key3=value, empty string if no attributes
1082//@-
1083
1084Static Function/T H5GW__HDF5AttributesToString(fileID, hdf5_Object, hdf5_Type, [keyDelimiter, keyValueSep, itemDelimiter])
1085        Variable fileID
1086        String hdf5_Object
1087        Variable hdf5_Type
1088        String keyDelimiter
1089        String keyValueSep
1090        String itemDelimiter
1091       
1092        if ( ParamIsDefault(keyDelimiter) )
1093                keyDelimiter = "\r"
1094        endif
1095        if ( ParamIsDefault(keyValueSep) )
1096                keyValueSep = "="
1097        endif
1098        if ( ParamIsDefault(itemDelimiter) )
1099                itemDelimiter = ","
1100        endif
1101       
1102        String result = ""
1103        String attr_name_list, attr_name, attr_str, temp_str
1104        Variable num_attr, i_attr
1105        HDF5ListAttributes/TYPE=(hdf5_Type)/Z  fileID, hdf5_Object
1106        if ( V_Flag != 0 )
1107                return result
1108        endif
1109        attr_name_list = S_HDF5ListAttributes
1110        num_attr = ItemsInList(attr_name_list)
1111        for (i_attr = 0; i_attr < num_attr; i_attr = i_attr+1)
1112                attr_name = StringFromList(i_attr, attr_name_list)
1113                attr_str = H5GW__HDF5AttributeDataToString(fileID, hdf5_Object, hdf5_Type, attr_name, itemDelimiter)
1114                if (strlen(result) > 0)
1115                        result = result + keyDelimiter
1116                endif
1117                result = result + attr_name + keyValueSep + attr_str
1118        endfor
1119        KillWaves/Z attr_wave
1120
1121        return result
1122End
1123
1124
1125// ======================================
1126//@+
1127//      .. index:: ! H5GW__HDF5AttributeDataToString()
1128//     
1129//      H5GW__HDF5AttributeDataToString(fileID, hdf5_Object, hdf5_Type, attr_name, itemDelimiter)
1130//      ---------------------------------------------------------------------------------------------------------------------------------------------------
1131//     
1132//      Reads the value of a specific attribute assigned to this object
1133//      and returns its value.
1134//     
1135//              :int fileID: IgorPro reference number for this HDF5 file
1136//              :String hdf5_Object: full HDF5 path of object
1137//              :int hdf5_Type: 1=group, 2=dataset
1138//              :String attr_name: name of the attribute
1139//              :String itemDelimiter: if the attribute data is an array,
1140//                              this will delimit the representation of its members in a string
1141//              :returns String: value, empty string if no value
1142//@-
1143
1144Static Function/T H5GW__HDF5AttributeDataToString(fileID, hdf5_Object, hdf5_Type, attr_name, itemDelimiter)
1145        Variable fileID
1146        String hdf5_Object
1147        Variable hdf5_Type
1148        String attr_name
1149        String itemDelimiter
1150
1151        String attr_str = "", temp_str
1152        Variable index
1153        HDF5LoadData/A=attr_name/TYPE=(hdf5_Type)/N=attr_wave/Z/O/Q   fileID, hdf5_Object
1154        if ( V_Flag == 0 )
1155                if ( 0 == cmpstr( "attr_wave,", WaveList("attr_wave", ",", "TEXT:1")) )
1156                        Wave/T attr_waveT=attr_wave
1157                        attr_str = attr_waveT[0]
1158                else
1159                        Wave attr_waveN=attr_wave
1160                        attr_str = ""
1161                        sprintf attr_str, "%g", attr_waveN[0]           // assume at least one point
1162                        for ( index=1; index < numpnts(attr_waveN); index=index+1)
1163                                sprintf temp_str, "%g", attr_waveN[index]
1164                                attr_str = attr_str + itemDelimiter + temp_str
1165                        endfor
1166                endif
1167        endif
1168        KillWaves/Z attr_wave
1169        return attr_str
1170End
1171
1172
1173// ======================================
1174//@+
1175//      .. index:: ! H5GW__SetStringDefault()
1176//     
1177//      H5GW__SetStringDefault(str, string_default)
1178//      -------------------------------------------------------------------------------------------------------------
1179//     
1180//         :String str: supplied value
1181//         :String string_default: default value
1182//         :returns String: default if supplied value is empty
1183//@-
1184
1185Static Function/T H5GW__SetStringDefault(str, string_default)
1186        String str
1187        String string_default
1188        String result = string_default
1189        if ( strlen(str)>0 )
1190                result = str
1191        endif
1192        return result
1193End
1194
1195
1196// ======================================
1197//@+
1198//      .. index:: ! H5GW__AppendString()
1199//     
1200//      H5GW__AppendString(str, sep, newtext)
1201//      -------------------------------------------------------------------------------------------------------------
1202//     
1203//         :String str: starting string
1204//         :String sep: separator
1205//         :String newtext: text to be appended
1206//         :returns String: result
1207//@-
1208
1209Static Function/T H5GW__AppendString(str, sep, newtext)
1210        String str, sep, newtext
1211        if ( strlen(newtext) == 0 )
1212                return str
1213        endif
1214        if ( strlen(str) > 0 )
1215                str = str + sep
1216        endif
1217        return str +newtext
1218End
1219
1220
1221
1222
1223// ======================================
1224//@+
1225//      .. index:: ! H5GW__FileExists()
1226//     
1227//      H5GW__FileExists(file_name)
1228//      -------------------------------------------------------------------------------------------------------------
1229//     
1230//              :String file_name: name of file to be found
1231//              :returns int: 1 if exists, 0 if does not exist
1232//@-
1233
1234Static Function H5GW__FileExists(file_name)
1235        String file_name
1236        Variable fileID
1237//      Open/R/P=home/Z fileID as file_name     // test if it will open as a regular file
1238        Open/R/P=catPathName/Z fileID as file_name      // test if it will open as a regular file
1239        if ( fileID > 0 )
1240                Close fileID
1241                return 1
1242        endif
1243        return 0
1244End
1245
1246
1247//  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //  //
1248
1249
1250//@+
1251//      Testing and Development
1252//      ======================================
1253//     
1254//      Examples to test read and write:
1255//     
1256//              .. code-block:: guess
1257//                      :linenos:
1258//                     
1259//                      print H5GW_ReadHDF5("root:worker", "simpleexamplefile.h5")
1260//                      print H5GW_ReadHDF5("root:worker", "simple2dcase.h5")
1261//                      print H5GW_ReadHDF5("root:worker", "simple2dmaskedcase.h5")
1262//                      print H5GW_ReadHDF5("root:worker", "generic2dqtimeseries.h5")
1263//                      print H5GW_ReadHDF5("root:worker", "generic2dtimetpseries.h5")
1264//                      print H5GW_WriteHDF5("root:worker:simpleexamplefile", "test_output.h5")
1265//     
1266//      .. index:: test
1267//      .. index:: ! H5GW__TestFile()
1268//     
1269//      H5GW__TestFile(parentDir, sourceFile)
1270//      -------------------------------------------------------------------------------------------------------------
1271//     
1272//      Reads HDF5 file sourceFile into a subfolder of IgorPro folder parentDir.
1273//      Then writes the structure from that subfolder to a new HDF5 file: ``"test_"+sourceFile``
1274//      Assumes that sourceFile is only a file name, with no path components, in the present working directory.
1275//      Returns the name (string) of the new HDF5 file written.
1276//     
1277//              :String parentDir: folder within IgorPro memory to contain the HDF5 test data
1278//              :String sourceFile: HDF5 test data file (assumes no file path information prepends the file name)
1279//@-
1280
1281static Function/T H5GW__TestFile(parentDir, sourceFile)
1282        String parentDir, sourceFile
1283        String prefix = "test_"
1284        String newFile = prefix + sourceFile
1285        String name = StringFromList(0, sourceFile, ".")
1286        print H5GW_ReadHDF5(parentDir, sourceFile)
1287        print H5GW_WriteHDF5(parentDir+":"+name, newFile)
1288        return newFile
1289End
Note: See TracBrowser for help on using the repository browser.