source: trunk/GSASIIfiles.py @ 4656

Last change on this file since 4656 was 4656, checked in by vondreele, 12 months ago

double up escape '\AA' in G2files.
Adjust slice transparency

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 45.1 KB
Line 
1# -*- coding: utf-8 -*-
2########### SVN repository information ###################
3# $Date: 2020-11-10 21:21:46 +0000 (Tue, 10 Nov 2020) $
4# $Author: vondreele $
5# $Revision: 4656 $
6# $URL: trunk/GSASIIfiles.py $
7# $Id: GSASIIfiles.py 4656 2020-11-10 21:21:46Z vondreele $
8########### SVN repository information ###################
9'''
10*GSASIIfiles: 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 platform
25import os
26import sys
27import glob
28import inspect
29import numpy as np
30
31import GSASIIpath
32GSASIIpath.SetVersionNumber("$Revision: 4656 $")
33
34# N.B. This is duplicated in G2IO
35def sfloat(S):
36    'Convert a string to float. An empty field or a unconvertable value is treated as zero'
37    if S.strip():
38        try:
39            return float(S)
40        except ValueError:
41            pass
42    return 0.0
43
44G2printLevel = 'all'
45'''This defines the level of output from calls to :func:`G2Print`,
46which should  be used in place of print() within this module.
47Settings for this are 'all', 'warn', 'error' or 'none'. Also see:
48:func:`G2Print` and :func:`G2SetPrintLevel`.
49'''
50
51def G2SetPrintLevel(level):
52    '''Set the level of output from calls to :func:`G2Print`, which should
53    be used in place of print() within GSASII. Settings for the mode are
54    'all', 'warn', 'error' or 'none'
55   
56    :param str level: a string used to set the print level, which may be
57      'all', 'warn', 'error' or 'none'.
58      Note that capitalization and extra letters in level are ignored, so
59      'Warn', 'warnings', etc. will all set the mode to 'warn'
60    '''
61    global G2printLevel
62    for mode in  'all', 'warn', 'error', 'none':
63        if mode in level.lower():
64            G2printLevel = mode
65            return
66    else:
67        G2Print('G2SetPrintLevel Error: level={} cannot be interpreted.',
68                    'Use all, warn, error or none.')
69       
70def find(name, path):
71    '''find 1st occurance of file in path
72    '''
73    for root, dirs, files in os.walk(path):
74        if name in files:
75            return os.path.join(root, name)
76       
77def G2Print(*args,**kwargs):
78    '''Print with filtering based level of output (see :func:`G2SetPrintLevel`).
79    Use G2Print() as replacement for print().
80
81    :param str mode: if specified, this should contain the mode for printing
82      ('error', 'warn' or anything else). If not specified, the first argument
83      of the print command (args[0]) should contain the string 'error' for
84      error messages and 'warn' for warning messages
85      (capitalization and additional letters ignored.)
86    '''
87    if G2printLevel is 'none': return
88    if kwargs.get('mode') is None:
89        testStr = str(args[0]).lower()
90    else:
91        testStr = kwargs['mode'][:].lower()
92        del kwargs['mode'] 
93    level = 2
94    for i,mode in enumerate(('error', 'warn')):
95        if mode in testStr:
96            level = i
97            break
98    if G2printLevel == 'error' and level > 0: return
99    if G2printLevel == 'warn' and level > 1: return
100    print(*args,**kwargs)
101
102def get_python_versions(packagelist):
103    versions = [['Python', sys.version.split()[0]]]
104    for pack in packagelist:
105        try:
106            versions.append([pack.__name__, pack.__version__])
107        except:
108            pass
109    versions.append(['Platform',
110                     sys.platform + ' ' + platform.architecture()[0] +
111                     ' ' + platform.machine()])
112    return versions
113
114def makeInstDict(names,data,codes):
115    inst = dict(zip(names,zip(data,data,codes)))
116    for item in inst:
117        inst[item] = list(inst[item])
118    return inst
119
120def SetPowderInstParms(Iparm, rd):
121    '''extracts values from instrument parameters in rd.instdict
122    or in array Iparm.
123    Create and return the contents of the instrument parameter tree entry.
124    '''
125    Irads = {0:' ',1:'CrKa',2:'FeKa',3:'CuKa',4:'MoKa',5:'AgKa',6:'TiKa',7:'CoKa'}
126    DataType = Iparm['INS   HTYPE '].strip()[:3]  # take 1st 3 chars
127    # override inst values with values read from data file
128    Bank = rd.powderentry[2]    #should be used in multibank iparm files
129    if rd.instdict.get('type'):
130        DataType = rd.instdict.get('type')
131    data = [DataType,]
132    instname = Iparm.get('INS  1INAME ')
133    irad = int(Iparm.get('INS  1 IRAD ','0'))
134    if instname:
135        rd.Sample['InstrName'] = instname.strip()
136    if 'C' in DataType:
137        wave1 = None
138        wave2 = 0.0
139        if rd.instdict.get('wave'):
140            wl = rd.instdict.get('wave')
141            wave1 = wl[0]
142            if len(wl) > 1: wave2 = wl[1]
143        s = Iparm['INS  1 ICONS']
144        if not wave1:
145            wave1 = sfloat(s[:10])
146            wave2 = sfloat(s[10:20])
147        v = (wave1,wave2,
148             sfloat(s[20:30])/100.,sfloat(s[55:65]),sfloat(s[40:50])) #get lam1, lam2, zero, pola & ratio
149        if not v[1]:
150            names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth']
151            v = (v[0],v[2],v[4])
152            codes = [0,0,0,0,0]
153            rd.Sample.update({'Type':'Debye-Scherrer','Absorption':[0.,False],'DisplaceX':[0.,False],'DisplaceY':[0.,False]})
154        else:
155            names = ['Type','Lam1','Lam2','Zero','I(L2)/I(L1)','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth']
156            codes = [0,0,0,0,0,0,0]
157            rd.Sample.update({'Type':'Bragg-Brentano','Shift':[0.,False],'Transparency':[0.,False],
158                'SurfRoughA':[0.,False],'SurfRoughB':[0.,False]})
159        data.extend(v)
160        if 'INS  1PRCF  ' in Iparm:
161            v1 = Iparm['INS  1PRCF  '].split()
162            v = Iparm['INS  1PRCF 1'].split()
163            data.extend([float(v[0]),float(v[1]),float(v[2])])                  #get GU, GV & GW - always here
164            azm = float(Iparm.get('INS  1DETAZM','0.0'))
165            v = Iparm['INS  1PRCF 2'].split()
166            if v1[0] == 3:
167                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
168            else:
169                data.extend([0.0,0.0,0.0,0.002,azm])                                      #OK defaults if fxn #3 not 1st in iprm file
170        else:
171            v1 = Iparm['INS  1PRCF1 '].split()
172            v = Iparm['INS  1PRCF11'].split()
173            data.extend([float(v[0]),float(v[1]),float(v[2])])                  #get GU, GV & GW - always here
174            azm = float(Iparm.get('INS  1DETAZM','0.0'))
175            v = Iparm['INS  1PRCF12'].split()
176            if v1[0] == 3:
177                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
178            else:
179                data.extend([0.0,0.0,0.0,0.002,azm])                                      #OK defaults if fxn #3 not 1st in iprm file
180        codes.extend([0,0,0,0,0,0,0])
181        Iparm1 = makeInstDict(names,data,codes)
182        Iparm1['Source'] = [Irads[irad],Irads[irad]]
183        Iparm1['Bank'] = [Bank,Bank,0]
184        return [Iparm1,{}]
185    elif 'T' in DataType:
186        names = ['Type','fltPath','2-theta','difC','difA', 'difB','Zero','alpha','beta-0','beta-1',
187            'beta-q','sig-0','sig-1','sig-2','sig-q', 'X','Y','Z','Azimuth',]
188        codes = [0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,]
189        azm = 0.
190        if 'INS  1DETAZM' in Iparm:
191            azm = float(Iparm['INS  1DETAZM'])
192        rd.Sample['Azimuth'] = azm
193        fltPath0 = 20.                      #arbitrary
194        if 'INS   FPATH1' in Iparm:
195            s = Iparm['INS   FPATH1'].split()
196            fltPath0 = sfloat(s[0])
197        if 'INS  1BNKPAR' not in Iparm:     #bank missing from Iparm file
198            return []
199        s = Iparm['INS  1BNKPAR'].split()
200        fltPath1 = sfloat(s[0])
201        data.extend([fltPath0+fltPath1,])               #Flight path source-sample-detector
202        data.extend([sfloat(s[1]),])               #2-theta for bank
203        s = Iparm['INS  1 ICONS'].split()
204        data.extend([sfloat(s[0]),sfloat(s[1]),0.0,sfloat(s[2])])    #difC,difA,difB,Zero
205        if 'INS  1PRCF  ' in Iparm:
206            s = Iparm['INS  1PRCF  '].split()
207            pfType = int(s[0])
208            s = Iparm['INS  1PRCF 1'].split()
209            if abs(pfType) == 1:
210                data.extend([sfloat(s[1]),sfloat(s[2]),sfloat(s[3])]) #alpha, beta-0, beta-1
211                s = Iparm['INS  1PRCF 2'].split()
212                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
213            elif abs(pfType) in [3,4,5]:
214                data.extend([sfloat(s[0]),sfloat(s[1]),sfloat(s[2])]) #alpha, beta-0, beta-1
215                if abs(pfType) == 4:
216                    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
217                else:
218                    s = Iparm['INS  1PRCF 2'].split()
219                    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
220            elif abs(pfType) == 2:
221                print('''***WARNING gsas profile function #2 does not give valid GSAS-II diffractometer/profile coefficients ***
222                you should request GSAS-II instparm file from Instrument responsible''')
223                data.extend([sfloat(s[1]),0.0,1./sfloat(s[3])]) #alpha, beta-0, beta-1
224                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
225        else:
226            s = Iparm['INS  1PRCF1 '].split()
227            pfType = int(s[0])
228            s = Iparm['INS  1PRCF11'].split()
229            if abs(pfType) == 1:
230                data.extend([sfloat(s[1]),sfloat(s[2]),sfloat(s[3])]) #alpha, beta-0, beta-1
231                s = Iparm['INS  1PRCF12'].split()
232                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
233            elif abs(pfType) in [3,4,5]:
234                data.extend([sfloat(s[0]),sfloat(s[1]),sfloat(s[2])]) #alpha, beta-0, beta-1
235                if abs(pfType) == 4:
236                    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
237                else:
238                    s = Iparm['INS  1PRCF12'].split()
239                    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
240        Inst1 = makeInstDict(names,data,codes)
241        Inst1['Bank'] = [Bank,Bank,0]
242        Inst2 = {}
243        if pfType < 0:
244            Ipab = 'INS  1PAB'+str(-pfType)
245            Npab = int(Iparm[Ipab+'  '].strip())
246            Inst2['Pdabc'] = []
247            for i in range(Npab):
248                k = Ipab+str(i+1).rjust(2)
249                s = Iparm[k].split()
250                Inst2['Pdabc'].append([float(t) for t in s])
251            Inst2['Pdabc'] = np.array(Inst2['Pdabc'])
252            Inst2['Pdabc'].T[3] += Inst2['Pdabc'].T[0]*Inst1['difC'][0] #turn 3rd col into TOF
253        if 'INS  1I ITYP' in Iparm:
254            s = Iparm['INS  1I ITYP'].split()
255            Ityp = int(s[0])
256            Tminmax = [float(s[1])*1000.,float(s[2])*1000.]
257            Itypes = ['Exponential','Maxwell/Exponential','','Maxwell/Chebyschev','']
258            if Ityp in [1,2,4]:
259                Inst2['Itype'] = Itypes[Ityp-1]
260                Inst2['Tminmax'] = Tminmax
261                Icoeff = []
262                Iesd = []
263                Icovar = []
264                for i in range(3):
265                    s = Iparm['INS  1ICOFF'+str(i+1)].split()
266                    Icoeff += [float(S) for S in s]
267                    s = Iparm['INS  1IECOF'+str(i+1)].split()
268                    Iesd += [float(S) for S in s]
269                NT = 10
270                for i in range(8):
271                    s = Iparm['INS  1IECOR'+str(i+1)]
272                    if i == 7:
273                        NT = 8
274                    Icovar += [float(s[6*j:6*j+6]) for j in range(NT)]
275                Inst2['Icoeff'] = Icoeff
276                Inst2['Iesd'] = Iesd
277                Inst2['Icovar'] = Icovar
278        return [Inst1,Inst2]
279
280def ReadPowderInstprm(instLines, bank, databanks, rd):
281    '''Read lines from a GSAS-II (new) instrument parameter file
282    similar to G2pwdGUI.OnLoad
283    If instprm file has multiple banks each with header #Bank n: ..., this
284    finds matching bank no. to load - problem with nonmatches?
285   
286    Note that this routine performs a similar role to :meth:`GSASIIdataGUI.GSASII.ReadPowderInstprm`,
287    but that will call a GUI routine for selection when needed. This routine will raise exceptions
288    on errors and will select the first bank when a choice might be appropriate.
289    TODO: refactor to combine the two routines.
290   
291    :param list instLines: strings from GSAS-II parameter file; can be concatenated with ';'
292    :param int  bank: bank number to check when instprm file has '#BANK n:...' strings
293         when bank = n then use parameters; otherwise skip that set. Ignored if BANK n:
294         not present. NB: this kind of instprm file made by a Save all profile command in Instrument Par     ameters
295    :return dict: Inst  instrument parameter dict if OK, or
296             str: Error message if failed
297   
298    (transliterated from GSASIIdataGUI.py:1235 (rev 3008), function of the same name)
299     ''' 
300    if 'GSAS-II' not in instLines[0]:
301        raise ValueError("Not a valid GSAS-II instprm file")
302
303    newItems = []
304    newVals = []
305    Found = False
306    il = 0
307    if bank is None:
308        banklist = set()
309        for S in instLines:
310            if S[0] == '#' and 'Bank' in S:
311                banklist.add(int(S.split(':')[0].split()[1]))
312        # Picks the first bank by default
313        if len(banklist) > 1:
314            bank = sorted(banklist)[0]
315        else:
316            bank = 1
317        rd.powderentry[2] = bank
318    while il < len(instLines):
319        S = instLines[il]
320        if S[0] == '#':
321            if Found:
322                break
323            if 'Bank' in S:
324                if bank == int(S.split(':')[0].split()[1]):
325                    il += 1
326                    S = instLines[il]
327                else:
328                    il += 1
329                    S = instLines[il]
330                    while il < len(instLines) and '#Bank' not in S:
331                        il += 1
332                        if il == len(instLines):
333                            raise ValueError("Bank {} not found in instprm file".format(bank))
334                        S = instLines[il]
335                    continue
336            else:
337                il += 1
338                S = instLines[il]
339        Found = True
340        if '"""' in S:
341            delim = '"""'
342        elif "'''" in S:
343            delim = "'''"
344        else:
345            S = S.replace(' ', '')
346            SS = S.strip().split(';')
347            for s in SS:
348                item, val = s.split(':', 1)
349                newItems.append(item)
350                try:
351                    newVals.append(float(val))
352                except ValueError:
353                    newVals.append(val)
354            il += 1
355            continue
356        # read multiline values, delimited by ''' or """
357        item, val = S.strip().split(':', 1)
358        val = val.replace(delim, '').rstrip()
359        val += '\n'
360        while True:
361            il += 1
362            if il >= len(instLines):
363                break
364            S = instLines[il]
365            if delim in S:
366                val += S.replace(delim, '').rstrip()
367                val += '\n'
368                break
369            else:
370                val += S.rstrip()
371                val += '\n'
372        newItems.append(item)
373        newVals.append(val)
374        il += 1
375    if 'Lam1' in newItems:
376        rd.Sample.update({'Type':'Bragg-Brentano','Shift':[0.,False],'Transparency':[0.,False],
377            'SurfRoughA':[0.,False],'SurfRoughB':[0.,False]})
378    else:
379        rd.Sample.update({'Type':'Debye-Scherrer','Absorption':[0.,False],'DisplaceX':[0.,False],'DisplaceY':[0.,False]})
380    return [makeInstDict(newItems, newVals, len(newVals)*[False]), {}]
381
382def LoadImportRoutines(prefix, errprefix=None, traceback=False):
383    '''Routine to locate GSASII importers matching a prefix string
384    '''
385    if errprefix is None:
386        errprefix = prefix
387
388    readerlist = []
389    import_files = {}
390    if '.' not in sys.path: sys.path.append('.')
391    for path in sys.path:
392        for filename in glob.iglob(os.path.join(path, 'G2'+prefix+'*.py')):
393            pkg = os.path.splitext(os.path.split(filename)[1])[0]
394            if pkg in import_files:
395                G2Print('Warning: file {} hidden by {}'
396                                  .format(os.path.abspath(filename),import_files[pkg]))
397            else:
398                import_files[pkg] = filename           
399
400    for pkg in sorted(import_files.keys()):
401        try:
402            exec('import '+pkg)
403            for name, value in inspect.getmembers(eval(pkg)):
404                if name.startswith('_'):
405                    continue
406                if inspect.isclass(value):
407                    for method in 'Reader', 'ExtensionValidator', 'ContentsValidator':
408                        if not hasattr(value, method):
409                            break
410                        if not callable(getattr(value, method)):
411                            break
412                    else:
413                        reader = value()
414                        if reader.UseReader:
415                            readerlist.append(reader)
416        except AttributeError:
417            G2Print ('Import_' + errprefix + ': Attribute Error ' + import_files[pkg])
418            if traceback:
419                traceback.print_exc(file=sys.stdout)
420        except Exception as exc:
421            G2Print ('\nImport_' + errprefix + ': Error importing file ' + import_files[pkg])
422            G2Print (u'Error message: {}\n'.format(exc))
423            if traceback:
424                traceback.print_exc(file=sys.stdout)
425    return readerlist
426
427def LoadExportRoutines(parent, traceback=False):
428    '''Routine to locate GSASII exporters
429    '''
430    exporterlist = []
431    import_files = {}
432    if '.' not in sys.path: sys.path.append('.')
433    for path in sys.path:
434        for filename in glob.iglob(os.path.join(path,"G2export*.py")):
435            pkg = os.path.splitext(os.path.split(filename)[1])[0]
436            if pkg in import_files:
437                G2Print('Warning: file {} hidden by {}'
438                                  .format(os.path.abspath(filename),import_files[pkg]))
439            else:
440                import_files[pkg] = filename           
441    # go through the routines and import them, saving objects that
442    # have export routines (method Exporter)
443    for pkg in sorted(import_files.keys()):
444        try:
445            exec('import '+pkg)
446            for clss in inspect.getmembers(eval(pkg)): # find classes defined in package
447                if clss[0].startswith('_'): continue
448                if not inspect.isclass(clss[1]): continue
449                # check if we have the required methods
450                if not hasattr(clss[1],'Exporter'): continue
451                if not callable(getattr(clss[1],'Exporter')): continue
452                if parent is None:
453                    if not hasattr(clss[1],'Writer'): continue
454                else:
455                    if not hasattr(clss[1],'loadParmDict'): continue
456                    if not callable(getattr(clss[1],'loadParmDict')): continue
457                try:
458                    exporter = clss[1](parent) # create an export instance
459                except AttributeError:
460                    pass
461                except Exception as exc:
462                    G2Print ('\nExport init: Error substantiating class ' + clss[0])
463                    G2Print (u'Error message: {}\n'.format(exc))
464                    if traceback:
465                        traceback.print_exc(file=sys.stdout)
466                    continue
467                exporterlist.append(exporter)
468        except AttributeError:
469            G2Print ('Export Attribute Error ' + filename)
470            if traceback:
471                traceback.print_exc(file=sys.stdout)
472        except Exception as exc:
473            G2Print ('\nExport init: Error importing file ' + filename)
474            G2Print (u'Error message: {}\n'.format(exc))
475            if traceback:
476                traceback.print_exc(file=sys.stdout)
477    return exporterlist
478
479def readColMetadata(imagefile):
480    '''Reads image metadata from a column-oriented metadata table
481    (1-ID style .par file). Called by :func:`GetColumnMetadata`
482   
483    The .par file has any number of columns separated by spaces.
484    The directory for the file must be specified in
485    Config variable :data:`config_example.Column_Metadata_directory`.
486    As an index to the .par file a second "label file" must be specified with the
487    same file root name as the .par file but the extension must be .XXX_lbls (where
488    .XXX is the extension of the image) or if that is not present extension
489    .lbls.
490
491    :param str imagefile: the full name of the image file (with extension, directory optional)
492
493    :returns: a dict with parameter values. Named parameters will have the type based on
494       the specified Python function, named columns will be character strings
495   
496    The contents of the label file will look like this::
497   
498        # define keywords
499        filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34
500        distance: float | 75
501        wavelength:lambda keV: 12.398425/float(keV)|9
502        pixelSize:lambda x: [74.8, 74.8]|0
503        ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4
504        Temperature: float|53
505        FreePrm2: int | 34 | Free Parm2 Label
506        # define other variables
507        0:day
508        1:month
509        2:date
510        3:time
511        4:year
512        7:I_ring
513
514    This file contains three types of lines in any order.
515     * Named parameters are evaluated with user-supplied Python code (see
516       subsequent information). Specific named parameters are used
517       to determine values that are used for image interpretation (see table,
518       below). Any others are copied to the Comments subsection of the Image
519       tree item.
520     * Column labels are defined with a column number (integer) followed by
521       a colon (:) and a label to be assigned to that column. All labeled
522       columns are copied to the Image's Comments subsection.
523     * Comments are any line that does not contain a colon.
524
525    Note that columns are numbered starting at zero.
526
527    Any named parameter may be defined provided it is not a valid integer,
528    but the named parameters in the table have special meanings, as descibed.
529    The parameter name is followed by a colon. After the colon, specify
530    Python code that defines or specifies a function that will be called to
531    generate a value for that parameter.
532
533    Note that several keywords, if defined in the Comments, will be found and
534    placed in the appropriate section of the powder histogram(s)'s Sample
535    Parameters after an integration: ``Temperature``, ``Pressure``, ``Time``,
536    ``FreePrm1``, ``FreePrm2``, ``FreePrm3``, ``Omega``, ``Chi``, and ``Phi``.
537
538    After the Python code, supply a vertical bar (|) and then a list of one
539    more more columns that will be supplied as arguments to that function.
540
541    Note that the labels for the three FreePrm items can be changed by
542    including that label as a third item with an additional vertical bar. Labels
543    will be ignored for any other named parameters.
544   
545    The examples above are discussed here:
546
547    ``filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34``
548        Here the function to be used is defined with a lambda statement::
549       
550          lambda x,y: "{}_{:0>6}".format(x,y)
551
552        This function will use the format function to create a file name from the
553        contents of columns 33 and 34. The first parameter (x, col. 33) is inserted directly into
554        the file name, followed by a underscore (_), followed by the second parameter (y, col. 34),
555        which will be left-padded with zeros to six characters (format directive ``:0>6``).
556
557        When there will be more than one image generated per line in the .par file, an alternate way to
558        generate list of file names takes into account the number of images generated::
559
560          lambda x,y,z: ["{}_{:0>6}".format(x,int(y)+i) for i in range(int(z))]
561
562        Here a third parameter is used to specify the number of images generated, where
563        the image number is incremented for each image.
564         
565    ``distance: float | 75``
566        Here the contents of column 75 will be converted to a floating point number
567        by calling float on it. Note that the spaces here are ignored.
568       
569    ``wavelength:lambda keV: 12.398425/float(keV)|9``
570        Here we define an algebraic expression to convert an energy in keV to a
571        wavelength and pass the contents of column 9 as that input energy
572       
573    ``pixelSize:lambda x: [74.8, 74.8]|0``
574        In this case the pixel size is a constant (a list of two numbers). The first
575        column is passed as an argument as at least one argument is required, but that
576        value is not used in the expression.
577
578    ``ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4``
579        This example defines a parameter that takes items in the first five columns
580        and formats them in a different way. This parameter is not one of the pre-defined
581        parameter names below. Some external code could be used to change the month string
582        (argument ``m``) to a integer from 1 to 12.
583       
584    ``FreePrm2: int | 34 | Free Parm2 Label``
585        In this example, the contents of column 34 will be converted to an integer and
586        placed as the second free-named parameter in the Sample Parameters after an
587        integration. The label for this parameter will be changed to "Free Parm2 Label".
588           
589    **Pre-defined parameter names**
590   
591    =============  =========  ========  =====================================================
592     keyword       required    type      Description
593    =============  =========  ========  =====================================================
594       filename    yes         str or   generates the file name prefix for the matching image
595                               list     file (MyImage001 for file /tmp/MyImage001.tif) or
596                                        a list of file names.
597     polarization  no         float     generates the polarization expected based on the
598                                        monochromator angle, defaults to 0.99.
599       center      no         list of   generates the approximate beam center on the detector
600                              2 floats  in mm, such as [204.8, 204.8].
601       distance    yes        float     generates the distance from the sample to the detector
602                                        in mm
603       pixelSize   no         list of   generates the size of the pixels in microns such as
604                              2 floats  [200.0, 200.0].
605       wavelength  yes        float     generates the wavelength in Angstroms
606    =============  =========  ========  =====================================================
607   
608    '''
609    dir,fil = os.path.split(os.path.abspath(imagefile))
610    imageName,ext = os.path.splitext(fil)
611    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
612    parfiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
613    if len(parfiles) == 0:
614        G2Print('Sorry, No Column metadata (.par) file found in '+
615              GSASIIpath.GetConfigValue('Column_Metadata_directory'))
616        return {}
617    for parFil in parfiles: # loop over all .par files (hope just 1) in image dir until image is found
618        parRoot = os.path.splitext(parFil)[0]
619        for e in (ext+'_lbls','.lbls'):
620            if os.path.exists(parRoot+e):
621                lblFil = parRoot+e
622                break
623        else:
624            G2Print('Warning: No labels definitions found for '+parFil)
625            continue
626        labels,lbldict,keyCols,keyExp,errors = readColMetadataLabels(lblFil)
627        if errors:
628            print('Errors in labels file '+lblFil)
629            for i in errors: print('  '+i)
630            continue
631        else:
632            G2Print('Read '+lblFil)
633        # scan through each line in this .par file, looking for the matching image rootname
634        fp = open(parFil,'r')
635        for iline,line in enumerate(fp):
636            items = line.strip().split(' ')
637            nameList = keyExp['filename'](*[items[j] for j in keyCols['filename']])
638            if type(nameList) is str:
639                if nameList != imageName: continue
640                name = nameList
641            else:
642                for name in nameList:
643                    if name == imageName: break # got a match
644                else:
645                    continue
646            # parse the line and finish
647            metadata = evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp)
648            metadata['par file'] = parFil
649            metadata['lbls file'] = lblFil
650            G2Print("Metadata read from {} line {}".format(parFil,iline+1))
651            fp.close()
652            return metadata
653        else:
654            G2Print("Image {} not found in {}".format(imageName,parFil))
655            fp.close()
656            continue
657        fp.close()
658    else:
659        G2Print("Warning: No .par metadata for image {}".format(imageName))
660        return {}
661
662def readColMetadataLabels(lblFil):
663    '''Read the .*lbls file and setup for metadata assignments
664    '''
665    lbldict = {}
666    keyExp = {}
667    keyCols = {}
668    labels = {}
669    errors = []
670    fp = open(lblFil,'r')         # read column labels
671    for iline,line in enumerate(fp): # read label definitions
672        line = line.strip()
673        if not line or line[0] == '#': continue # comments
674        items = line.split(':')
675        if len(items) < 2: continue # lines with no colon are also comments
676        # does this line a definition for a named parameter?
677        key = items[0]
678        try: 
679            int(key)
680        except ValueError: # try as named parameter since not a valid number
681            items = line.split(':',1)[1].split('|')
682            try:
683                f = eval(items[0]) # compile the expression
684                if not callable(f):
685                    errors += ['Expression "{}" for key {} is not a function (line {})'.
686                           format(items[0],key,iline)]
687                    continue
688                keyExp[key] = f
689            except Exception as msg:
690                errors += ['Expression "{}" for key {} is not valid (line {})'.
691                           format(items[0],key,iline)]
692                errors += [str(msg)]
693                continue
694            keyCols[key] = [int(i) for i in items[1].strip().split(',')]
695            if key.lower().startswith('freeprm') and len(items) > 2:
696                labels[key] = items[2]
697            continue
698        if len(items) == 2: # simple column definition
699            lbldict[int(items[0])] = items[1]
700    fp.close()
701    if 'filename' not in keyExp:
702        errors += ["File {} is invalid. No valid filename expression.".format(lblFil)]
703    return labels,lbldict,keyCols,keyExp,errors
704
705def evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,ShowError=False):
706    '''Evaluate the metadata for a line in the .par file
707    '''
708    metadata = {lbldict[j]:items[j] for j in lbldict}
709    named = {}
710    for key in keyExp:
711        try:
712            res = keyExp[key](*[items[j] for j in keyCols[key]])
713        except:
714            if ShowError:
715                res = "*** error ***"
716            else:
717                continue
718        named[key] = res
719    metadata.update(named)
720    for lbl in labels: # add labels for FreePrm's
721        metadata['label_'+lbl[4:].lower()] = labels[lbl]
722    return metadata
723
724def GetColumnMetadata(reader):
725    '''Add metadata to an image from a column-type metadata file
726    using :func:`readColMetadata`
727   
728    :param reader: a reader object from reading an image
729   
730    '''
731    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
732    parParms = readColMetadata(reader.readfilename)
733    if not parParms: return # check for read failure
734    specialKeys = ('filename',"polarization", "center", "distance", "pixelSize", "wavelength",)
735    reader.Comments = ['Metadata from {} assigned by {}'.format(parParms['par file'],parParms['lbls file'])]
736    for key in parParms:
737        if key in specialKeys+('par file','lbls file'): continue
738        reader.Comments += ["{} = {}".format(key,parParms[key])]
739    if "polarization" in parParms:
740        reader.Data['PolaVal'][0] = parParms["polarization"]
741    else:
742        reader.Data['PolaVal'][0] = 0.99
743    if "center" in parParms:
744        reader.Data['center'] = parParms["center"]
745    if "pixelSize" in parParms:
746        reader.Data['pixelSize'] = parParms["pixelSize"]
747    if "wavelength" in parParms:
748        reader.Data['wavelength'] = parParms['wavelength']
749    else:
750        G2Print('Error: wavelength not defined in {}'.format(parParms['lbls file']))
751    if "distance" in parParms:
752        reader.Data['distance'] = parParms['distance']
753        reader.Data['setdist'] = parParms['distance']
754    else:
755        G2Print('Error: distance not defined in {}'.format(parParms['lbls file']))
756
757def LoadControls(Slines,data):
758    'Read values from a .imctrl (Image Controls) file'
759    cntlList = ['color','wavelength','distance','tilt','invert_x','invert_y','type','Oblique',
760        'fullIntegrate','outChannels','outAzimuths','LRazimuth','IOtth','azmthOff','DetDepth',
761        'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg','varyList','setdist',
762        'PolaVal','SampleAbs','dark image','background image','twoth']
763    save = {}
764    for S in Slines:
765        if S[0] == '#':
766            continue
767        [key,val] = S.strip().split(':',1)
768        if key in ['type','calibrant','binType','SampleShape','color',]:    #strings
769            save[key] = val
770        elif key in ['varyList',]:
771            save[key] = eval(val)   #dictionary
772        elif key in ['rotation']:
773            save[key] = float(val)
774        elif key in ['center',]:
775            if ',' in val:
776                save[key] = eval(val)
777            else:
778                vals = val.strip('[] ').split()
779                save[key] = [float(vals[0]),float(vals[1])] 
780        elif key in cntlList:
781            save[key] = eval(val)
782    data.update(save)
783
784def WriteControls(filename,data):
785    'Write current values to a .imctrl (Image Controls) file'
786    File = open(filename,'w')
787    keys = ['type','color','wavelength','calibrant','distance','center','Oblique',
788            'tilt','rotation','azmthOff','fullIntegrate','LRazimuth','setdist',
789            'IOtth','outChannels','outAzimuths','invert_x','invert_y','DetDepth',
790            'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg','varyList',
791            'binType','SampleShape','PolaVal','SampleAbs','dark image','background image',
792            'twoth']
793    for key in keys:
794        if key not in data:     #uncalibrated!
795            continue
796        File.write(key+':'+str(data[key])+'\n')
797    File.close()
798
799def RereadImageData(ImageReaderlist,imagefile,ImageTag=None,FormatName=''):
800    '''Read a single image with an image importer. This is called to
801    reread an image after it has already been imported, so it is not
802    necessary to reload metadata.
803
804    Based on :func:`GetImageData.GetImageData` which this can replace
805    where imageOnly=True
806
807    :param list ImageReaderlist: list of Reader objects for images
808    :param str imagefile: name of image file
809    :param int/str ImageTag: specifies a particular image to be read from a file.
810      First image is read if None (default).
811    :param str formatName: the image reader formatName
812
813    :returns: an image as a numpy array
814    '''
815    # determine which formats are compatible with this file
816    primaryReaders = []
817    secondaryReaders = []
818    for rd in ImageReaderlist:
819        flag = rd.ExtensionValidator(imagefile)
820        if flag is None: 
821            secondaryReaders.append(rd)
822        elif flag:
823            if not FormatName:
824                primaryReaders.append(rd)
825            elif FormatName == rd.formatName:
826                primaryReaders.append(rd)
827    if len(secondaryReaders) + len(primaryReaders) == 0:
828        G2Print('Error: No matching format for file '+imagefile)
829        raise Exception('No image read')
830    errorReport = ''
831    if not imagefile:
832        return
833    for rd in primaryReaders+secondaryReaders:
834        rd.ReInitialize() # purge anything from a previous read
835        rd.errors = "" # clear out any old errors
836        if not rd.ContentsValidator(imagefile): # rejected on cursory check
837            errorReport += "\n  "+rd.formatName + ' validator error'
838            if rd.errors: 
839                errorReport += ': '+rd.errors
840                continue
841        flag = rd.Reader(imagefile,None,blocknum=ImageTag)
842        if flag: # this read succeeded
843            if rd.Image is None:
844                raise Exception('No image read. Strange!')
845            if GSASIIpath.GetConfigValue('Transpose'):
846                G2Print ('Warning: Transposing Image!')
847                rd.Image = rd.Image.T
848            #rd.readfilename = imagefile
849            return rd.Image
850    else:
851        G2Print('Error reading file '+imagefile)
852        G2Print('Error messages(s)\n'+errorReport)
853        raise Exception('No image read')
854
855def readMasks(filename,masks,ignoreThreshold):
856    '''Read a GSAS-II masks file'''
857    File = open(filename,'r')
858    save = {}
859    oldThreshold = masks['Thresholds'][0]
860    S = File.readline()
861    while S:
862        if S[0] == '#':
863            S = File.readline()
864            continue
865        [key,val] = S.strip().split(':',1)
866        if key in ['Points','Rings','Arcs','Polygons','Frames','Thresholds']:
867            if ignoreThreshold and key == 'Thresholds':
868                S = File.readline() 
869                continue
870            save[key] = eval(val)
871            if key == 'Thresholds':
872                save[key][0] = oldThreshold
873                save[key][1][1] = min(oldThreshold[1],save[key][1][1])
874        S = File.readline()
875    File.close()
876    masks.update(save)
877    # CleanupMasks
878    for key in ['Points','Rings','Arcs','Polygons']:
879        masks[key] = masks.get(key,[])
880        masks[key] = [i for i in masks[key] if len(i)]
881
882def PDFWrite(PDFentry,fileroot,PDFsaves,PDFControls,Inst={},Limits=[]):
883    '''Write PDF-related data (G(r), S(Q),...) into files, as
884    selected.
885
886    :param str PDFentry: name of the PDF entry in the tree. This is
887      used for comments in the file specifying where it came from;
888      it can be arbitrary
889    :param str fileroot: name of file(s) to be written. The extension
890      will be ignored.
891    :param list PDFsaves: flags that determine what type of file will be
892      written:
893      PDFsaves[0], if True writes a I(Q) file with a .iq extension
894      PDFsaves[1], if True writes a S(Q) file with a .sq extension
895      PDFsaves[2], if True writes a F(Q) file with a .fq extension
896      PDFsaves[3], if True writes a G(r) file with a .gr extension
897      PDFsaves[4], if True writes G(r) in a pdfGUI input file with
898      a .gr extension. Note that if PDFsaves[3] and PDFsaves[4] are
899      both True, the pdfGUI overwrites the G(r) file.
900      PDFsaves[5], if True writes F(Q) & g(R) with .fq & .gr extensions
901      overwrites these if selected by option 2, 3 or 4
902    :param dict PDFControls: The PDF parameters and computed results
903    :param dict Inst: Instrument parameters from the PDWR entry used
904      to compute the PDF. Needed only when PDFsaves[4] is True.
905    :param list Limits: Computation limits from the PDWR entry used
906      to compute the PDF. Needed only when PDFsaves[4] is True.
907    '''
908    import scipy.interpolate as scintp
909    fileroot = os.path.splitext(fileroot)[0]
910    if PDFsaves[0]:     #I(Q)
911        iqfilename = fileroot+'.iq'
912        iqdata = PDFControls['I(Q)'][1]
913        iqfxn = scintp.interp1d(iqdata[0],iqdata[1],kind='linear')
914        iqfile = open(iqfilename,'w')
915        iqfile.write('#T I(Q) %s\n'%(PDFentry))
916        iqfile.write('#L Q     I(Q)\n')
917        qnew = np.arange(iqdata[0][0],iqdata[0][-1],0.005)
918        iqnew = zip(qnew,iqfxn(qnew))
919        for q,iq in iqnew:
920            iqfile.write("%15.6g %15.6g\n" % (q,iq))
921        iqfile.close()
922        G2Print (' I(Q) saved to: '+iqfilename)
923
924    if PDFsaves[1]:     #S(Q)
925        sqfilename = fileroot+'.sq'
926        sqdata = PDFControls['S(Q)'][1]
927        sqfxn = scintp.interp1d(sqdata[0],sqdata[1],kind='linear')
928        sqfile = open(sqfilename,'w')
929        sqfile.write('#T S(Q) %s\n'%(PDFentry))
930        sqfile.write('#L Q     S(Q)\n')
931        qnew = np.arange(sqdata[0][0],sqdata[0][-1],0.005)
932        sqnew = zip(qnew,sqfxn(qnew))
933        for q,sq in sqnew:
934            sqfile.write("%15.6g %15.6g\n" % (q,sq))
935        sqfile.close()
936        G2Print (' S(Q) saved to: '+sqfilename)
937
938    if PDFsaves[2]:     #F(Q)
939        fqfilename = fileroot+'.fq'
940        fqdata = PDFControls['F(Q)'][1]
941        fqfxn = scintp.interp1d(fqdata[0],fqdata[1],kind='linear')
942        fqfile = open(fqfilename,'w')
943        fqfile.write('#T F(Q) %s\n'%(PDFentry))
944        fqfile.write('#L Q     F(Q)\n')
945        qnew = np.arange(fqdata[0][0],fqdata[0][-1],0.005)
946        fqnew = zip(qnew,fqfxn(qnew))
947        for q,fq in fqnew:
948            fqfile.write("%15.6g %15.6g\n" % (q,fq))
949        fqfile.close()
950        G2Print (' F(Q) saved to: '+fqfilename)
951
952    if PDFsaves[3]:     #G(R)
953        grfilename = fileroot+'.gr'
954        grdata = PDFControls['G(R)'][1]
955        grfxn = scintp.interp1d(grdata[0],grdata[1],kind='linear')
956        grfile = open(grfilename,'w')
957        grfile.write('#T G(R) %s\n'%(PDFentry))
958        grfile.write('#L R     G(R)\n')
959        rnew = np.arange(grdata[0][0],grdata[0][-1],0.010)
960        grnew = zip(rnew,grfxn(rnew))
961        for r,gr in grnew:
962            grfile.write("%15.6g %15.6g\n" % (r,gr))
963        grfile.close()
964        G2Print (' G(R) saved to: '+grfilename)
965
966    if PDFsaves[4]: #pdfGUI file for G(R)
967        import GSASIImath as G2mth
968        import GSASIIlattice as G2lat       
969        grfilename = fileroot+'.gr'
970        grdata = PDFControls['G(R)'][1]
971        qdata = PDFControls['I(Q)'][1][0]
972        grfxn = scintp.interp1d(grdata[0],grdata[1],kind='linear')
973        grfile = open(grfilename,'w')
974        rnew = np.arange(grdata[0][0],grdata[0][-1],0.010)
975        grnew = zip(rnew,grfxn(rnew))
976
977        grfile.write('[DEFAULT]\n')
978        grfile.write('\n')
979        grfile.write('version = GSAS-II-v'+str(GSASIIpath.GetVersionNumber())+'\n')
980        grfile.write('\n')
981        grfile.write('# input and output specifications\n')
982        grfile.write('dataformat = Qnm\n')
983        grfile.write('inputfile = %s\n'%(PDFControls['Sample']['Name']))
984        grfile.write('backgroundfile = %s\n'%(PDFControls['Sample Bkg.']['Name']))
985        grfile.write('outputtype = gr\n')
986        grfile.write('\n')
987        grfile.write('# PDF calculation setup\n')
988        if 'x' in Inst['Type']:
989            grfile.write('mode = %s\n'%('xray'))
990        elif 'N' in Inst['Type']:
991            grfile.write('mode = %s\n'%('neutron'))
992        wave = G2mth.getMeanWave(Inst)
993        grfile.write('wavelength = %.5f\n'%(wave))
994        formula = ''
995        for el in PDFControls['ElList']:
996            formula += el
997            num = PDFControls['ElList'][el]['FormulaNo']
998            if num == round(num):
999                formula += '%d'%(int(num))
1000            else:
1001                formula += '%.2f'%(num)
1002        grfile.write('composition = %s\n'%(formula))
1003        grfile.write('bgscale = %.3f\n'%(-PDFControls['Sample Bkg.']['Mult']))
1004        highQ = 2.*np.pi/G2lat.Pos2dsp(Inst,Limits[1][1])
1005        grfile.write('qmaxinst = %.2f\n'%(highQ))
1006        grfile.write('qmin = %.5f\n'%(qdata[0]))
1007        grfile.write('qmax = %.4f\n'%(qdata[-1]))
1008        grfile.write('rmin = %.2f\n'%(PDFControls['Rmin']))
1009        grfile.write('rmax = %.2f\n'%(PDFControls['Rmax']))
1010        grfile.write('rstep = 0.01\n')
1011        grfile.write('\n')
1012        grfile.write('# End of config '+63*'-')
1013        grfile.write('\n')
1014        grfile.write('#### start data\n')
1015        grfile.write('#S 1\n')
1016        grfile.write('#L r($\\AA$)  G($\\AA^{-2}$)\n')           
1017        for r,gr in grnew:
1018            grfile.write("%15.2F %15.6F\n" % (r,gr))
1019        grfile.close()
1020        G2Print (' G(R) saved to: '+grfilename)
1021       
1022    if PDFsaves[5]: #RMCProfile files for F(Q) & g(r) overwrites any above
1023       
1024        fqfilename = fileroot+'.fq'
1025        fqdata = PDFControls['F(Q)'][1]
1026        fqfxn = scintp.interp1d(fqdata[0],fqdata[1],kind='linear')
1027        fqfile = open(fqfilename,'w')
1028        qnew = np.arange(fqdata[0][0],fqdata[0][-1],0.005)
1029        nq = qnew.shape[0]
1030        fqfile.write('%20d\n'%nq-1)
1031        fqfile.write(fqfilename+'\n')
1032        fqnew = zip(qnew,fqfxn(qnew))
1033        for q,fq in fqnew[1:]:
1034            fqfile.write("%15.6g %15.6g\n" % (q,fq))
1035        fqfile.close()
1036        G2Print (' F(Q) saved to: '+fqfilename)
1037       
1038        grfilename = fileroot+'.gr'
1039        grdata = PDFControls['g(r)'][1]
1040        grfxn = scintp.interp1d(grdata[0],grdata[1],kind='linear')
1041        grfile = open(grfilename,'w')
1042        rnew = np.arange(grdata[0][0],grdata[0][-1],0.010)
1043        nr = rnew.shape[0]
1044        grfile.write('%20d\n'%nr-1)
1045        grfile.write(grfilename+'\n')
1046        grnew = zip(rnew,grfxn(rnew))
1047        for r,gr in grnew[1:]:
1048            grfile.write("%15.6g %15.6g\n" % (r,gr))
1049        grfile.close()
1050        G2Print (' G(R) saved to: '+grfilename)
1051
Note: See TracBrowser for help on using the repository browser.