source: trunk/GSASIIfiles.py @ 3814

Last change on this file since 3814 was 3814, checked in by toby, 3 years ago

refactor to move some IO-only routines; add initial image support to scriptable

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 32.1 KB
Line 
1# -*- coding: utf-8 -*-
2########### SVN repository information ###################
3# $Date: 2019-02-10 22:40:06 +0000 (Sun, 10 Feb 2019) $
4# $Author: toby $
5# $Revision: 3814 $
6# $URL: trunk/GSASIIfiles.py $
7# $Id: GSASIIfiles.py 3814 2019-02-10 22:40:06Z toby $
8########### SVN repository information ###################
9'''
10*GSASIIfile: data (non-GUI) I/O routines*
11=========================================
12
13Module with miscellaneous routines for input and output from files.
14
15This module should not contain any references to wxPython so that it
16can be imported for scriptable use or potentially on clients where
17wx is not installed.
18
19Future refactoring: This module and GSASIIIO.py needs some work to
20move non-wx routines here. It may will likely make sense to rename the module(s)
21at that point.
22'''
23from __future__ import division, print_function
24import os
25import sys
26import glob
27import imp
28import inspect
29import platform
30import numpy as np
31
32import GSASIIpath
33GSASIIpath.SetVersionNumber("$Revision: 3814 $")
34
35# N.B. This is duplicated in G2IO
36def sfloat(S):
37    'Convert a string to float. An empty field or a unconvertable value is treated as zero'
38    if S.strip():
39        try:
40            return float(S)
41        except ValueError:
42            pass
43    return 0.0
44
45def get_python_versions(packagelist):
46    versions = [['Python', sys.version.split()[0]]]
47    for pack in packagelist:
48        try:
49            versions.append([pack.__name__, pack.__version__])
50        except:
51            pass
52    versions.append(['Platform',
53                     sys.platform + ' ' + platform.architecture()[0] +
54                     ' ' + platform.machine()])
55    return versions
56
57def makeInstDict(names,data,codes):
58    inst = dict(zip(names,zip(data,data,codes)))
59    for item in inst:
60        inst[item] = list(inst[item])
61    return inst
62
63def SetPowderInstParms(Iparm, rd):
64    '''extracts values from instrument parameters in rd.instdict
65    or in array Iparm.
66    Create and return the contents of the instrument parameter tree entry.
67    '''
68    Irads = {0:' ',1:'CrKa',2:'FeKa',3:'CuKa',4:'MoKa',5:'AgKa',6:'TiKa',7:'CoKa'}
69    DataType = Iparm['INS   HTYPE '].strip()[:3]  # take 1st 3 chars
70    # override inst values with values read from data file
71    Bank = rd.powderentry[2]    #should be used in multibank iparm files
72    if rd.instdict.get('type'):
73        DataType = rd.instdict.get('type')
74    data = [DataType,]
75    instname = Iparm.get('INS  1INAME ')
76    irad = int(Iparm.get('INS  1 IRAD ','0'))
77    if instname:
78        rd.Sample['InstrName'] = instname.strip()
79    if 'C' in DataType:
80        wave1 = None
81        wave2 = 0.0
82        if rd.instdict.get('wave'):
83            wl = rd.instdict.get('wave')
84            wave1 = wl[0]
85            if len(wl) > 1: wave2 = wl[1]
86        s = Iparm['INS  1 ICONS']
87        if not wave1:
88            wave1 = sfloat(s[:10])
89            wave2 = sfloat(s[10:20])
90        v = (wave1,wave2,
91             sfloat(s[20:30])/100.,sfloat(s[55:65]),sfloat(s[40:50])) #get lam1, lam2, zero, pola & ratio
92        if not v[1]:
93            names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth']
94            v = (v[0],v[2],v[4])
95            codes = [0,0,0,0,0]
96            rd.Sample.update({'Type':'Debye-Scherrer','Absorption':[0.,False],'DisplaceX':[0.,False],'DisplaceY':[0.,False]})
97        else:
98            names = ['Type','Lam1','Lam2','Zero','I(L2)/I(L1)','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth']
99            codes = [0,0,0,0,0,0,0]
100            rd.Sample.update({'Type':'Bragg-Brentano','Shift':[0.,False],'Transparency':[0.,False],
101                'SurfRoughA':[0.,False],'SurfRoughB':[0.,False]})
102        data.extend(v)
103        if 'INS  1PRCF  ' in Iparm:
104            v1 = Iparm['INS  1PRCF  '].split()
105            v = Iparm['INS  1PRCF 1'].split()
106            data.extend([float(v[0]),float(v[1]),float(v[2])])                  #get GU, GV & GW - always here
107            azm = float(Iparm.get('INS  1DETAZM','0.0'))
108            v = Iparm['INS  1PRCF 2'].split()
109            if v1[0] == 3:
110                data.extend([float(v[0]),float(v[1]),0.0,float(v[2])+float(v[3],azm)])  #get LX, LY, Z, S+H/L & azimuth
111            else:
112                data.extend([0.0,0.0,0.0,0.002,azm])                                      #OK defaults if fxn #3 not 1st in iprm file
113        else:
114            v1 = Iparm['INS  1PRCF1 '].split()
115            v = Iparm['INS  1PRCF11'].split()
116            data.extend([float(v[0]),float(v[1]),float(v[2])])                  #get GU, GV & GW - always here
117            azm = float(Iparm.get('INS  1DETAZM','0.0'))
118            v = Iparm['INS  1PRCF12'].split()
119            if v1[0] == 3:
120                data.extend([float(v[0]),float(v[1]),0.0,float(v[2])+float(v[3],azm)])  #get LX, LY, Z, S+H/L & azimuth
121            else:
122                data.extend([0.0,0.0,0.0,0.002,azm])                                      #OK defaults if fxn #3 not 1st in iprm file
123        codes.extend([0,0,0,0,0,0,0])
124        Iparm1 = makeInstDict(names,data,codes)
125        Iparm1['Source'] = [Irads[irad],Irads[irad]]
126        Iparm1['Bank'] = [Bank,Bank,0]
127        return [Iparm1,{}]
128    elif 'T' in DataType:
129        names = ['Type','fltPath','2-theta','difC','difA', 'difB','Zero','alpha','beta-0','beta-1',
130            'beta-q','sig-0','sig-1','sig-2','sig-q', 'X','Y','Z','Azimuth',]
131        codes = [0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,]
132        azm = 0.
133        if 'INS  1DETAZM' in Iparm:
134            azm = float(Iparm['INS  1DETAZM'])
135        rd.Sample['Azimuth'] = azm
136        fltPath0 = 20.                      #arbitrary
137        if 'INS   FPATH1' in Iparm:
138            s = Iparm['INS   FPATH1'].split()
139            fltPath0 = sfloat(s[0])
140        if 'INS  1BNKPAR' not in Iparm:     #bank missing from Iparm file
141            return []
142        s = Iparm['INS  1BNKPAR'].split()
143        fltPath1 = sfloat(s[0])
144        data.extend([fltPath0+fltPath1,])               #Flight path source-sample-detector
145        data.extend([sfloat(s[1]),])               #2-theta for bank
146        s = Iparm['INS  1 ICONS'].split()
147        data.extend([sfloat(s[0]),sfloat(s[1]),0.0,sfloat(s[2])])    #difC,difA,difB,Zero
148        if 'INS  1PRCF  ' in Iparm:
149            s = Iparm['INS  1PRCF  '].split()
150            pfType = int(s[0])
151            s = Iparm['INS  1PRCF 1'].split()
152            if abs(pfType) == 1:
153                data.extend([sfloat(s[1]),sfloat(s[2]),sfloat(s[3])]) #alpha, beta-0, beta-1
154                s = Iparm['INS  1PRCF 2'].split()
155                data.extend([0.0,0.0,sfloat(s[1]),sfloat(s[2]),0.0,0.0,0.0,0.0,azm])    #beta-q, sig-0, sig-1, sig-2, sig-q, X, Y, Z
156            elif abs(pfType) in [3,4,5]:
157                data.extend([sfloat(s[0]),sfloat(s[1]),sfloat(s[2])]) #alpha, beta-0, beta-1
158                if abs(pfType) == 4:
159                    data.extend([0.0,0.0,sfloat(s[3]),0.0,0.0,0.0,0.0,0.0,azm])    #beta-q, sig-0, sig-1, sig-2, sig-q, X, Y, Z
160                else:
161                    s = Iparm['INS  1PRCF 2'].split()
162                    data.extend([0.0,0.0,sfloat(s[0]),sfloat(s[1]),0.0,0.0,0.0,0.0,azm])    #beta-q, sig-0, sig-1, sig-2, sig-q, X, Y, Z
163            elif abs(pfType) == 2:
164                data.extend([sfloat(s[1]),0.0,1./sfloat(s[3])]) #alpha, beta-0, beta-1
165                data.extend([0.0,0.0,sfloat(s[1]),0.0,0.0,0.0,0.0,0.0,azm])    #beta-q, sig-0, sig-1, sig-2, sig-q, X, Y, Z
166        else:
167            s = Iparm['INS  1PRCF1 '].split()
168            pfType = int(s[0])
169            s = Iparm['INS  1PRCF11'].split()
170            if abs(pfType) == 1:
171                data.extend([sfloat(s[1]),sfloat(s[2]),sfloat(s[3])]) #alpha, beta-0, beta-1
172                s = Iparm['INS  1PRCF12'].split()
173                data.extend([0.0,0.0,sfloat(s[1]),sfloat(s[2]),0.0,0.0,0.0,0.0,0.0,azm])    #beta-q, sig-0, sig-1, sig-2, sig-q, X, Y, Z
174            elif abs(pfType) in [3,4,5]:
175                data.extend([sfloat(s[0]),sfloat(s[1]),sfloat(s[2])]) #alpha, beta-0, beta-1
176                if abs(pfType) == 4:
177                    data.extend([0.0,0.0,sfloat(s[3]),0.0,0.0,0.0,0.0,0.0,azm])    #beta-q, sig-0, sig-1, sig-2, sig-q, X, Y, Z
178                else:
179                    s = Iparm['INS  1PRCF12'].split()
180                    data.extend([0.0,0.0,sfloat(s[0]),sfloat(s[1]),0.0,0.0,0.0,0.0,azm])    #beta-q, sig-0, sig-1, sig-2, sig-q, X, Y, Z
181        Inst1 = makeInstDict(names,data,codes)
182        Inst1['Bank'] = [Bank,Bank,0]
183        Inst2 = {}
184        if pfType < 0:
185            Ipab = 'INS  1PAB'+str(-pfType)
186            Npab = int(Iparm[Ipab+'  '].strip())
187            Inst2['Pdabc'] = []
188            for i in range(Npab):
189                k = Ipab+str(i+1).rjust(2)
190                s = Iparm[k].split()
191                Inst2['Pdabc'].append([float(t) for t in s])
192            Inst2['Pdabc'] = np.array(Inst2['Pdabc'])
193            Inst2['Pdabc'].T[3] += Inst2['Pdabc'].T[0]*Inst1['difC'][0] #turn 3rd col into TOF
194        if 'INS  1I ITYP' in Iparm:
195            s = Iparm['INS  1I ITYP'].split()
196            Ityp = int(s[0])
197            Tminmax = [float(s[1])*1000.,float(s[2])*1000.]
198            Itypes = ['Exponential','Maxwell/Exponential','','Maxwell/Chebyschev','']
199            if Ityp in [1,2,4]:
200                Inst2['Itype'] = Itypes[Ityp-1]
201                Inst2['Tminmax'] = Tminmax
202                Icoeff = []
203                Iesd = []
204                Icovar = []
205                for i in range(3):
206                    s = Iparm['INS  1ICOFF'+str(i+1)].split()
207                    Icoeff += [float(S) for S in s]
208                    s = Iparm['INS  1IECOF'+str(i+1)].split()
209                    Iesd += [float(S) for S in s]
210                NT = 10
211                for i in range(8):
212                    s = Iparm['INS  1IECOR'+str(i+1)]
213                    if i == 7:
214                        NT = 8
215                    Icovar += [float(s[6*j:6*j+6]) for j in range(NT)]
216                Inst2['Icoeff'] = Icoeff
217                Inst2['Iesd'] = Iesd
218                Inst2['Icovar'] = Icovar
219        return [Inst1,Inst2]
220
221def ReadPowderInstprm(instLines, bank, databanks, rd):
222    '''Read lines from a GSAS-II (new) instrument parameter file
223    similar to G2pwdGUI.OnLoad
224    If instprm file has multiple banks each with header #Bank n: ..., this
225    finds matching bank no. to load - problem with nonmatches?
226   
227    Note that this routine performs a similar role to :meth:`GSASIIdataGUI.GSASII.ReadPowderInstprm`,
228    but that will call a GUI routine for selection when needed. This routine will raise exceptions
229    on errors and will select the first bank when a choice might be appropriate.
230    TODO: refactor to combine the two routines.
231   
232    :param list instLines: strings from GSAS-II parameter file; can be concatenated with ';'
233    :param int  bank: bank number to check when instprm file has '#BANK n:...' strings
234         when bank = n then use parameters; otherwise skip that set. Ignored if BANK n:
235         not present. NB: this kind of instprm file made by a Save all profile command in Instrument Par     ameters
236    :return dict: Inst  instrument parameter dict if OK, or
237             str: Error message if failed
238   
239    (transliterated from GSASIIdataGUI.py:1235 (rev 3008), function of the same name)
240     ''' 
241    if 'GSAS-II' not in instLines[0]:
242        raise ValueError("Not a valid GSAS-II instprm file")
243
244    newItems = []
245    newVals = []
246    Found = False
247    il = 0
248    if bank is None:
249        banklist = set()
250        for S in instLines:
251            if S[0] == '#' and 'Bank' in S:
252                banklist.add(int(S.split(':')[0].split()[1]))
253        # Picks the first bank by default
254        if len(banklist) > 1:
255            bank = sorted(banklist)[0]
256        else:
257            bank = 1
258        rd.powderentry[2] = bank
259    while il < len(instLines):
260        S = instLines[il]
261        if S[0] == '#':
262            if Found:
263                break
264            if 'Bank' in S:
265                if bank == int(S.split(':')[0].split()[1]):
266                    il += 1
267                    S = instLines[il]
268                else:
269                    il += 1
270                    S = instLines[il]
271                    while il < len(instLines) and '#Bank' not in S:
272                        il += 1
273                        if il == len(instLines):
274                            raise ValueError("Bank {} not found in instprm file".format(bank))
275                        S = instLines[il]
276                    continue
277            else:
278                il += 1
279                S = instLines[il]
280        Found = True
281        if '"""' in S:
282            delim = '"""'
283        elif "'''" in S:
284            delim = "'''"
285        else:
286            S = S.replace(' ', '')
287            SS = S.strip().split(';')
288            for s in SS:
289                item, val = s.split(':', 1)
290                newItems.append(item)
291                try:
292                    newVals.append(float(val))
293                except ValueError:
294                    newVals.append(val)
295            il += 1
296            continue
297        # read multiline values, delimited by ''' or """
298        item, val = S.strip().split(':', 1)
299        val = val.replace(delim, '').rstrip()
300        val += '\n'
301        while True:
302            il += 1
303            if il >= len(instLines):
304                break
305            S = instLines[il]
306            if delim in S:
307                val += S.replace(delim, '').rstrip()
308                val += '\n'
309                break
310            else:
311                val += S.rstrip()
312                val += '\n'
313        newItems.append(item)
314        newVals.append(val)
315        il += 1
316    return [makeInstDict(newItems, newVals, len(newVals)*[False]), {}]
317
318def LoadImportRoutines(prefix, errprefix=None, traceback=False):
319    '''Routine to locate GSASII importers matching a prefix string
320    '''
321    if errprefix is None:
322        errprefix = prefix
323
324    readerlist = []
325    pathlist = sys.path[:]
326    if '.' not in pathlist:
327        pathlist.append('.')
328
329    potential_files = []
330    for path in pathlist:
331        for filename in glob.iglob(os.path.join(path, 'G2'+prefix+'*.py')):
332            potential_files.append(filename)
333
334    potential_files = sorted(list(set(potential_files)))
335    for filename in potential_files:
336        path, rootname = os.path.split(filename)
337        pkg = os.path.splitext(rootname)[0]
338
339        try:
340            fp = None
341            fp, fppath, desc = imp.find_module(pkg, [path])
342            pkg = imp.load_module(pkg, fp, fppath, desc)
343            for name, value in inspect.getmembers(pkg):
344                if name.startswith('_'):
345                    continue
346                if inspect.isclass(value):
347                    for method in 'Reader', 'ExtensionValidator', 'ContentsValidator':
348                        if not hasattr(value, method):
349                            break
350                        if not callable(getattr(value, method)):
351                            break
352                    else:
353                        reader = value()
354                        if reader.UseReader:
355                            readerlist.append(reader)
356        except AttributeError:
357            print ('Import_' + errprefix + ': Attribute Error ' + filename)
358            if traceback:
359                traceback.print_exc(file=sys.stdout)
360        except Exception as exc:
361            print ('\nImport_' + errprefix + ': Error importing file ' + filename)
362            print (u'Error message: {}\n'.format(exc))
363            if traceback:
364                traceback.print_exc(file=sys.stdout)
365        finally:
366            if fp:
367                fp.close()
368
369    return readerlist
370
371def LoadExportRoutines(parent, traceback=False):
372    '''Routine to locate GSASII exporters
373    '''
374    exporterlist = []
375    pathlist = sys.path
376    filelist = []
377    for path in pathlist:
378        for filename in glob.iglob(os.path.join(path,"G2export*.py")):
379                    filelist.append(filename)   
380    filelist = sorted(list(set(filelist))) # remove duplicates
381    # go through the routines and import them, saving objects that
382    # have export routines (method Exporter)
383    for filename in filelist:
384        path,rootname = os.path.split(filename)
385        pkg = os.path.splitext(rootname)[0]
386        try:
387            fp = None
388            fp, fppath,desc = imp.find_module(pkg,[path,])
389            pkg = imp.load_module(pkg,fp,fppath,desc)
390            for clss in inspect.getmembers(pkg): # find classes defined in package
391                if clss[0].startswith('_'): continue
392                if not inspect.isclass(clss[1]): continue
393                # check if we have the required methods
394                if not hasattr(clss[1],'Exporter'): continue
395                if not callable(getattr(clss[1],'Exporter')): continue
396                if parent is None:
397                    if not hasattr(clss[1],'Writer'): continue
398                else:
399                    if not hasattr(clss[1],'loadParmDict'): continue
400                    if not callable(getattr(clss[1],'loadParmDict')): continue
401                try:
402                    exporter = clss[1](parent) # create an export instance
403                except AttributeError:
404                    pass
405                except Exception as exc:
406                    print ('\nExport init: Error substantiating class ' + clss[0])
407                    print (u'Error message: {}\n'.format(exc))
408                    if traceback:
409                        traceback.print_exc(file=sys.stdout)
410                    continue
411                exporterlist.append(exporter)
412        except AttributeError:
413            print ('Export Attribute Error ' + filename)
414            if traceback:
415                traceback.print_exc(file=sys.stdout)
416        except Exception as exc:
417            print ('\nExport init: Error importing file ' + filename)
418            print (u'Error message: {}\n'.format(exc))
419            if traceback:
420                traceback.print_exc(file=sys.stdout)
421        finally:
422            if fp:
423                fp.close()
424    return exporterlist
425
426def readColMetadata(imagefile):
427    '''Reads image metadata from a column-oriented metadata table
428    (1-ID style .par file). Called by :func:`GetColumnMetadata`
429   
430    The .par file has any number of columns separated by spaces.
431    The directory for the file must be specified in
432    Config variable ``Column_Metadata_directory``.
433    As an index to the .par file a second "label file" must be specified with the
434    same file root name as the .par file but the extension must be .XXX_lbls (where
435    .XXX is the extension of the image) or if that is not present extension
436    .lbls.
437
438    :param str imagefile: the full name of the image file (with extension, directory optional)
439
440    :returns: a dict with parameter values. Named parameters will have the type based on
441       the specified Python function, named columns will be character strings
442   
443    The contents of the label file will look like this::
444   
445        # define keywords
446        filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34
447        distance: float | 75
448        wavelength:lambda keV: 12.398425/float(keV)|9
449        pixelSize:lambda x: [74.8, 74.8]|0
450        ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4
451        Temperature: float|53
452        FreePrm2: int | 34 | Free Parm2 Label
453        # define other variables
454        0:day
455        1:month
456        2:date
457        3:time
458        4:year
459        7:I_ring
460
461    This file contains three types of lines in any order.
462     * Named parameters are evaluated with user-supplied Python code (see
463       subsequent information). Specific named parameters are used
464       to determine values that are used for image interpretation (see table,
465       below). Any others are copied to the Comments subsection of the Image
466       tree item.
467     * Column labels are defined with a column number (integer) followed by
468       a colon (:) and a label to be assigned to that column. All labeled
469       columns are copied to the Image's Comments subsection.
470     * Comments are any line that does not contain a colon.
471
472    Note that columns are numbered starting at zero.
473
474    Any named parameter may be defined provided it is not a valid integer,
475    but the named parameters in the table have special meanings, as descibed.
476    The parameter name is followed by a colon. After the colon, specify
477    Python code that defines or specifies a function that will be called to
478    generate a value for that parameter.
479
480    Note that several keywords, if defined in the Comments, will be found and
481    placed in the appropriate section of the powder histogram(s)'s Sample
482    Parameters after an integration: ``Temperature``,``Pressure``,``Time``,
483    ``FreePrm1``,``FreePrm2``,``FreePrm3``,``Omega``,``Chi``, and ``Phi``.
484
485    After the Python code, supply a vertical bar (|) and then a list of one
486    more more columns that will be supplied as arguments to that function.
487
488    Note that the labels for the three FreePrm items can be changed by
489    including that label as a third item with an additional vertical bar. Labels
490    will be ignored for any other named parameters.
491   
492    The examples above are discussed here:
493
494    ``filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34``
495        Here the function to be used is defined with a lambda statement::
496       
497          lambda x,y: "{}_{:0>6}".format(x,y)
498
499        This function will use the format function to create a file name from the
500        contents of columns 33 and 34. The first parameter (x, col. 33) is inserted directly into
501        the file name, followed by a underscore (_), followed by the second parameter (y, col. 34),
502        which will be left-padded with zeros to six characters (format directive ``:0>6``).
503
504        When there will be more than one image generated per line in the .par file, an alternate way to
505        generate list of file names takes into account the number of images generated::
506
507          lambda x,y,z: ["{}_{:0>6}".format(x,int(y)+i) for i in range(int(z))]
508
509        Here a third parameter is used to specify the number of images generated, where
510        the image number is incremented for each image.
511         
512    ``distance: float | 75``
513        Here the contents of column 75 will be converted to a floating point number
514        by calling float on it. Note that the spaces here are ignored.
515       
516    ``wavelength:lambda keV: 12.398425/float(keV)|9``
517        Here we define an algebraic expression to convert an energy in keV to a
518        wavelength and pass the contents of column 9 as that input energy
519       
520    ``pixelSize:lambda x: [74.8, 74.8]|0``
521        In this case the pixel size is a constant (a list of two numbers). The first
522        column is passed as an argument as at least one argument is required, but that
523        value is not used in the expression.
524
525    ``ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4``
526        This example defines a parameter that takes items in the first five columns
527        and formats them in a different way. This parameter is not one of the pre-defined
528        parameter names below. Some external code could be used to change the month string
529        (argument ``m``) to a integer from 1 to 12.
530       
531    ``FreePrm2: int | 34 | Free Parm2 Label``
532        In this example, the contents of column 34 will be converted to an integer and
533        placed as the second free-named parameter in the Sample Parameters after an
534        integration. The label for this parameter will be changed to "Free Parm2 Label".
535           
536    **Pre-defined parameter names**
537   
538    =============  =========  ========  =====================================================
539     keyword       required    type      Description
540    =============  =========  ========  =====================================================
541       filename    yes         str or   generates the file name prefix for the matching image
542                               list     file (MyImage001 for file /tmp/MyImage001.tif) or
543                                        a list of file names.
544     polarization  no         float     generates the polarization expected based on the
545                                        monochromator angle, defaults to 0.99.
546       center      no         list of   generates the approximate beam center on the detector
547                              2 floats  in mm, such as [204.8, 204.8].
548       distance    yes        float     generates the distance from the sample to the detector
549                                        in mm
550       pixelSize   no         list of   generates the size of the pixels in microns such as
551                              2 floats  [200.0, 200.0].
552       wavelength  yes        float     generates the wavelength in Angstroms
553    =============  =========  ========  =====================================================
554   
555    '''
556    dir,fil = os.path.split(os.path.abspath(imagefile))
557    imageName,ext = os.path.splitext(fil)
558    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
559    parfiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
560    if len(parfiles) == 0:
561        print('Sorry, No Column metadata (.par) file found in '+
562              GSASIIpath.GetConfigValue('Column_Metadata_directory'))
563        return {}
564    for parFil in parfiles: # loop over all .par files (hope just 1) in image dir until image is found
565        parRoot = os.path.splitext(parFil)[0]
566        for e in (ext+'_lbls','.lbls'):
567            if os.path.exists(parRoot+e):
568                lblFil = parRoot+e
569                break
570        else:
571            print('Warning: No labels definitions found for '+parFil)
572            continue
573        labels,lbldict,keyCols,keyExp,errors = readColMetadataLabels(lblFil)
574        if errors:
575            print('Errors in labels file '+lblFil)
576            for i in errors: print('  '+i)
577            continue
578        else:
579            print('Read '+lblFil)
580        # scan through each line in this .par file, looking for the matching image rootname
581        fp = open(parFil,'Ur')
582        for iline,line in enumerate(fp):
583            items = line.strip().split(' ')
584            nameList = keyExp['filename'](*[items[j] for j in keyCols['filename']])
585            if type(nameList) is str:
586                if nameList != imageName: continue
587                name = nameList
588            else:
589                for name in nameList:
590                    if name == imageName: break # got a match
591                else:
592                    continue
593            # parse the line and finish
594            metadata = evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp)
595            metadata['par file'] = parFil
596            metadata['lbls file'] = lblFil
597            print("Metadata read from {} line {}".format(parFil,iline+1))
598            fp.close()
599            return metadata
600        else:
601            print("Image {} not found in {}".format(imageName,parFil))
602            fp.close()
603            continue
604        fp.close()
605    else:
606        print("Warning: No .par metadata for image {}".format(imageName))
607        return {}
608
609def readColMetadataLabels(lblFil):
610    '''Read the .*lbls file and setup for metadata assignments
611    '''
612    lbldict = {}
613    keyExp = {}
614    keyCols = {}
615    labels = {}
616    errors = []
617    fp = open(lblFil,'Ur')         # read column labels
618    for iline,line in enumerate(fp): # read label definitions
619        line = line.strip()
620        if not line or line[0] == '#': continue # comments
621        items = line.split(':')
622        if len(items) < 2: continue # lines with no colon are also comments
623        # does this line a definition for a named parameter?
624        key = items[0]
625        try: 
626            int(key)
627        except ValueError: # try as named parameter since not a valid number
628            items = line.split(':',1)[1].split('|')
629            try:
630                f = eval(items[0]) # compile the expression
631                if not callable(f):
632                    errors += ['Expression "{}" for key {} is not a function (line {})'.
633                           format(items[0],key,iline)]
634                    continue
635                keyExp[key] = f
636            except Exception as msg:
637                errors += ['Expression "{}" for key {} is not valid (line {})'.
638                           format(items[0],key,iline)]
639                errors += [str(msg)]
640                continue
641            keyCols[key] = [int(i) for i in items[1].strip().split(',')]
642            if key.lower().startswith('freeprm') and len(items) > 2:
643                labels[key] = items[2]
644            continue
645        if len(items) == 2: # simple column definition
646            lbldict[int(items[0])] = items[1]
647    fp.close()
648    if 'filename' not in keyExp:
649        errors += ["File {} is invalid. No valid filename expression.".format(lblFil)]
650    return labels,lbldict,keyCols,keyExp,errors
651
652def evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,ShowError=False):
653    '''Evaluate the metadata for a line in the .par file
654    '''
655    metadata = {lbldict[j]:items[j] for j in lbldict}
656    named = {}
657    for key in keyExp:
658        try:
659            res = keyExp[key](*[items[j] for j in keyCols[key]])
660        except:
661            if ShowError:
662                res = "*** error ***"
663            else:
664                continue
665        named[key] = res
666    metadata.update(named)
667    for lbl in labels: # add labels for FreePrm's
668        metadata['label_'+lbl[4:].lower()] = labels[lbl]
669    return metadata
670
671def GetColumnMetadata(reader):
672    '''Add metadata to an image from a column-type metadata file
673    using :func:`readColMetadata`
674   
675    :param reader: a reader object from reading an image
676   
677    '''
678    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
679    parParms = readColMetadata(reader.readfilename)
680    if not parParms: return # check for read failure
681    specialKeys = ('filename',"polarization", "center", "distance", "pixelSize", "wavelength",)
682    reader.Comments = ['Metadata from {} assigned by {}'.format(parParms['par file'],parParms['lbls file'])]
683    for key in parParms:
684        if key in specialKeys+('par file','lbls file'): continue
685        reader.Comments += ["{} = {}".format(key,parParms[key])]
686    if "polarization" in parParms:
687        reader.Data['PolaVal'][0] = parParms["polarization"]
688    else:
689        reader.Data['PolaVal'][0] = 0.99
690    if "center" in parParms:
691        reader.Data['center'] = parParms["center"]
692    if "pixelSize" in parParms:
693        reader.Data['pixelSize'] = parParms["pixelSize"]
694    if "wavelength" in parParms:
695        reader.Data['wavelength'] = parParms['wavelength']
696    else:
697        print('Error: wavelength not defined in {}'.format(parParms['lbls file']))
698    if "distance" in parParms:
699        reader.Data['distance'] = parParms['distance']
700        reader.Data['setdist'] = parParms['distance']
701    else:
702        print('Error: distance not defined in {}'.format(parParms['lbls file']))
703
704def LoadControls(Slines,data):
705    'Read values from a .imctrl (Image Controls) file'
706    cntlList = ['color','wavelength','distance','tilt','invert_x','invert_y','type','Oblique',
707        'fullIntegrate','outChannels','outAzimuths','LRazimuth','IOtth','azmthOff','DetDepth',
708        'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg','varyList','setdist',
709        'PolaVal','SampleAbs','dark image','background image','twoth']
710    save = {}
711    for S in Slines:
712        if S[0] == '#':
713            continue
714        [key,val] = S.strip().split(':',1)
715        if key in ['type','calibrant','binType','SampleShape','color',]:    #strings
716            save[key] = val
717        elif key in ['varyList',]:
718            save[key] = eval(val)   #dictionary
719        elif key in ['rotation']:
720            save[key] = float(val)
721        elif key in ['center',]:
722            if ',' in val:
723                save[key] = eval(val)
724            else:
725                vals = val.strip('[] ').split()
726                save[key] = [float(vals[0]),float(vals[1])] 
727        elif key in cntlList:
728            save[key] = eval(val)
729    data.update(save)
730
731def WriteControls(filename,data):
732    'Write current values to a .imctrl (Image Controls) file'
733    File = open(filename,'w')
734    keys = ['type','color','wavelength','calibrant','distance','center','Oblique',
735            'tilt','rotation','azmthOff','fullIntegrate','LRazimuth','setdist',
736            'IOtth','outChannels','outAzimuths','invert_x','invert_y','DetDepth',
737            'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg','varyList',
738            'binType','SampleShape','PolaVal','SampleAbs','dark image','background image',
739            'twoth']
740    for key in keys:
741        if key not in data:     #uncalibrated!
742            continue
743        File.write(key+':'+str(data[key])+'\n')
744    File.close()
745   
Note: See TracBrowser for help on using the repository browser.