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

Last change on this file since 954 was 946, checked in by srkline, 9 years ago

Adding procedures that will allow read/write of raw data in HDF5 format. No Nexus file definition has been settled upon yet, so I made up my own for testing, simply converting what was present in the VAX file header. Simple read/write operations are functional, but no full scale testing.

Transmission calculation is partially broken as there are no file suffixes anymore.

Otherwise, a good starting point for the HDF5/Nexus of the future.

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