source: trunk/GSASIIfiles.py @ 4408

Last change on this file since 4408 was 4388, checked in by vondreele, 5 years ago

Add warning for using type=2 TOF instrument profile function in old gsas iparm files

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