source: trunk/GSASIIfiles.py @ 4211

Last change on this file since 4211 was 4195, checked in by vondreele, 6 years ago

add a find utility to GSASIIfiles for finding a file within a directory tree
faster version of ExpandCell? & define SwapItems? in GSASIIlattice
changes to RMCProfile interface

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