1
2
3 """
4 Authors: Jérôme Kieffer, ESRF
5 email:jerome.kieffer@esrf.fr
6
7 Cif Binary Files images are 2D images written by the Pilatus detector and others.
8 They use a modified (simplified) byte-offset algorithm.
9
10 CIF is a library for manipulating Crystallographic information files and tries
11 to conform to the specification of the IUCR
12 """
13 __author__ = "Jérôme Kieffer"
14 __contact__ = "jerome.kieffer@esrf.eu"
15 __license__ = "GPLv3+"
16 __copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
17 __version__ = ["Generated by CIF.py: Jan 2005 - December 2010",
18 "Written by Jerome Kieffer: Jerome.Kieffer@esrf.eu",
19 "On-line data analysis / ISDD ", "ESRF Grenoble (France)"]
20
21
22 import os, logging, struct
23 import numpy as np
24 from fabio.fabioimage import fabioimage
25 import time
26
27 DATA_TYPES = { "signed 8-bit integer" : np.int8,
28 "signed 16-bit integer" : np.int16,
29 "signed 32-bit integer" : np.int32
30 }
31
32 MINIMUM_KEYS = ["X-Binary-Size-Fastest-Dimension",
33 'ByteOrder',
34 'Data type',
35 'X dimension',
36 'Y dimension',
37 'Number of readouts']
38
39 DEFAULT_VALUES = {
40 "Data type": "signed 32-bit integer",
41 "X-Binary-Size-Fastest-Dimension": 2463,
42 "X-Binary-Element-Byte-Order": "LITTLE_ENDIAN"
43
44 }
50 """
51 Read the Cif Binary File data format
52 """
54 """
55 Constructor of the class CIF Binary File reader.
56
57 @param _strFilename: the name of the file to open
58 @type _strFilename: string
59 """
60 fabioimage.__init__(self)
61 self.cif = CIF()
62 if fname is not None:
63 self.read(fname)
64
65
67 """
68 Read in a header in some CBF format from a string representing binary stuff
69
70 @param inStream: the binary image (without any CIF decorators)
71 @type inStream: python string.
72 """
73 sep = "\r\n"
74 iSepPos = inStream.find(sep)
75 if iSepPos < 0 or iSepPos > 80:
76 sep = "\n"
77
78 lines = inStream.split(sep)
79 for oneLine in lines[1:]:
80 if len(oneLine) < 10:
81 break
82 try:
83 key, val = oneLine.split(':' , 1)
84 except ValueError:
85 key, val = oneLine.split('=' , 1)
86 key = key.strip()
87 self.header_keys.append(key)
88 self.header[key] = val.strip(" \"\n\r\t")
89 missing = []
90 for item in MINIMUM_KEYS:
91 if item not in self.header_keys:
92 missing.append(item)
93 if len(missing) > 0:
94 logging.debug("CBF file misses the keys " + " ".join(missing))
95
96
97 @staticmethod
99 """
100 Analyze a stream of char with any length of exception (2,4, or 8 bytes integers)
101 @param stream: string representing the compressed data
102 @param size: the size of the output array (of longInts)
103 @return :NParrays
104 """
105
106
107
108
109
110 logging.debug("CBF decompression using Python with Cython loops")
111 dataOut = np.zeros((size), dtype=np.int64)
112 i = 0
113 j = 0
114 last = 0
115 current = 0
116 while ((i < len(stream)) and (j < size)):
117 if (stream[i] == '\x80'):
118 if (stream[i + 1:i + 3] == "\x00\x80"):
119 if (stream[i + 3:i + 7] == "\x00\x00\x00\x80"):
120 current = struct.unpack("<q", stream[i + 7:i + 15])[0]
121 i += 15
122 else:
123 current = struct.unpack("<i", stream[i + 3:i + 7])[0]
124 i += 7
125 else:
126 current = struct.unpack("<h", stream[i + 1:i + 3])[0]
127 i += 3
128 else:
129 current = struct.unpack("<b", stream[i])[0]
130 i += 1
131 last += current
132 dataOut[j] = last
133 j += 1
134 return dataOut
135
136 @staticmethod
138 """
139 Analyze a stream of char with any length of exception (2,4, or 8 bytes integers)
140
141 @return list of NParrays
142 """
143 logging.debug("CBF decompression using Weave")
144 from scipy import weave
145 from scipy.weave import converters
146 dataIn = np.fromstring(stream, dtype="uint8")
147 n = dataIn.size
148 dataOut = np.zeros(size, dtype="int64")
149 codeC = """
150 unsigned char key = 0x80;
151 long j = 0;
152 long last=0;
153 long current=0;
154 for (int i=0; i< n; i++){
155 if (j>=size){
156 //printf("i= %i<%i, j=%i < size= %i %i\\n",i,n,j,size,dataIn(i));
157 break;
158 }
159 if (dataIn(i) == key){
160 if ( (dataIn(i+1)==0) and (dataIn(i+2)==key) ){
161 if ( (dataIn(i+3)==0) and (dataIn(i+4)==0) and (dataIn(i+5)==0) and (dataIn(i+6)==key) ) {
162 // 64 bits mode
163 char tmp = dataIn(i+14) ;
164 current = (long(tmp)<<56) | (long(dataIn(i+13))<<48) | (long(dataIn(i+12))<<40) | (long(dataIn(i+11))<<32) | (long(dataIn(i+10))<<24) | (long(dataIn(i+9))<<16) | (long(dataIn(i+8))<<8) | (long(dataIn(i+7)));
165 // printf("64 bit int at pos %i, %i, value=%ld \\n",i,j,current);
166 i+=14;
167 }else{
168 // 32 bits mode
169 char tmp = dataIn(i+6) ;
170 current = (long(tmp)<<24) | (long(dataIn(i+5))<<16) | (long(dataIn(i+4))<<8) | (long(dataIn(i+3)));
171 // printf("32 bit int at pos %i, %i, value=%ld was %i %i %i %i %i %i %i\\n",i,j,current,dataIn(i),dataIn(i+1),dataIn(i+2),dataIn(i+3),dataIn(i+4),dataIn(i+5),dataIn(i+6));
172 // printf("%ld %ld %ld %ld\\n",(long(tmp)<<24) , (long(dataIn(i+5))<<16) , (long(dataIn(i+4))<<8) ,long(dataIn(i+3)));
173 i+=6;
174 }
175 }else{
176 // 16 bit mode
177 char tmp = dataIn(i+2);
178 current = (long(tmp)<<8) | (long (dataIn(i+1)));
179 // printf("16 bit int at pos %i, %i, value=%ld was %i %i %i\\n",i,j,current,dataIn(i),dataIn(i+1),dataIn(i+2));
180 i+=2;
181 }
182 }else{
183 // 8 bit mode
184 char tmp = dataIn(i) ;
185 current= long(tmp) ;
186 }
187 last+=current;
188 dataOut(j)=last;
189 j++ ;
190 }
191 return_val=0;
192 """
193 rc = weave.inline(codeC, ["dataIn", "dataOut", "n", "size" ], verbose=2, type_converters=converters.blitz)
194 return dataOut
195
196
197 @staticmethod
199 """
200 Analyze a stream of char with any length of exception:
201 2, 4, or 8 bytes integers
202
203 @return list of NParrays
204 """
205 logging.debug("CBF decompression using Numpy")
206 listnpa = []
207 key16 = "\x80"
208 key32 = "\x00\x80"
209 key64 = "\x00\x00\x00\x80"
210 shift = 1
211 while True:
212 idx = stream.find(key16)
213 if idx == -1:
214 listnpa.append(np.fromstring(stream, dtype="int8"))
215 break
216 listnpa.append(np.fromstring(stream[:idx], dtype="int8"))
217
218 if stream[idx + 1:idx + 3] == key32:
219 if stream[idx + 3:idx + 7] == key64:
220
221 listnpa.append(np.fromstring(stream[idx + 7:idx + 15],
222 dtype="int64"))
223 shift = 15
224 else:
225 listnpa.append(np.fromstring(stream[idx + 3:idx + 7],
226 dtype="int32"))
227 shift = 7
228 else:
229 listnpa.append(np.fromstring(stream[idx + 1:idx + 3],
230 dtype="int16"))
231 shift = 3
232 stream = stream[idx + shift:]
233 return (np.hstack(listnpa)).astype("int64").cumsum()
234
235
237 """
238 Read in a binary part of an x-CBF_BYTE_OFFSET compressed image
239
240 @param inStream: the binary image (without any CIF decorators)
241 @type inStream: python string.
242 @return: a linear numpy array without shape and dtype set
243 @rtype: numpy array
244 """
245
246 starter = "\x0c\x1a\x04\xd5"
247 startPos = inStream.find(starter) + 4
248 data = inStream[ startPos: startPos + int(self.header["X-Binary-Size"])]
249 try:
250 import byte_offset
251 except ImportError:
252 logging.warning("Error in byte_offset part: Falling back to Numpy implementation")
253 myData = cbfimage.analyseNumpy(data, size=self.dim1 * self.dim2)
254 else:
255 myData = byte_offset.analyseCython(data, size=self.dim1 * self.dim2)
256
257 assert len(myData) == self.dim1 * self.dim2
258 return myData
259
260
261
262 - def read(self, fname):
263 """
264 Read in header into self.header and
265 the data into self.data
266 """
267 self.filename = fname
268 self.header = {}
269 self.resetvals()
270 self.cif.loadCIF(fname, _bKeepComment=True)
271
272
273 for key in self.cif:
274 if key != "_array_data.data":
275 self.header_keys.append(key)
276 self.header[key] = self.cif[key].strip(" \"\n\r\t")
277
278
279
280 if not "_array_data.data" in self.cif:
281 raise Exception("CBF file %s is corrupt, cannot find data block with '_array_data.data' key" % fname)
282 self._readheader(self.cif["_array_data.data"])
283
284 try:
285 self.dim1 = int(self.header['X-Binary-Size-Fastest-Dimension'])
286 self.dim2 = int(self.header['X-Binary-Size-Second-Dimension'])
287 except:
288 raise Exception(IOError, "CBF file %s is corrupt, no dimensions in it" % fname)
289 try:
290 bytecode = DATA_TYPES[self.header['X-Binary-Element-Type']]
291 self.bpp = len(np.array(0, bytecode).tostring())
292 except KeyError:
293 bytecode = np.int32
294 self.bpp = 32
295 logging.warning("Defaulting type to int32")
296 if self.header["conversions"] == "x-CBF_BYTE_OFFSET":
297 self.data = self._readbinary_byte_offset(self.cif["_array_data.data"]).astype(bytecode).reshape((self.dim2, self.dim1))
298 else:
299 raise Exception(IOError, "Compression scheme not yet supported, please contact FABIO development team")
300
301
302 self.bytecode = self.data.dtype.type
303 self.resetvals()
304
305 self.pilimage = None
306 return self
307
308
309
310 -class CIF(dict):
311 """
312 This is the CIF class, it represents the CIF dictionary;
313 and as a a python dictionary thus inherits from the dict built in class.
314 """
315 EOL = ["\r", "\n", "\r\n", "\n\r"]
316 BLANK = [" ", "\t"] + EOL
317 START_COMMENT = ["\"", "\'"]
318 BINARY_MARKER = "--CIF-BINARY-FORMAT-SECTION--"
319
321 """
322 Constructor of the class.
323
324 @param _strFilename: the name of the file to open
325 @type _strFilename: string
326 """
327 dict.__init__(self)
328 if _strFilename is not None:
329 self.loadCIF(_strFilename)
330
332 """
333 Just call loadCIF:
334 Load the CIF file and sets the CIF dictionnary into the object
335
336 @param _strFilename: the name of the file to open
337 @type _strFilename: string
338 """
339 self.loadCIF(_strFilename)
340
341 - def loadCIF(self, _strFilename, _bKeepComment=False):
342 """Load the CIF file and returns the CIF dictionnary into the object
343 @param _strFilename: the name of the file to open
344 @type _strFilename: string
345 @param _strFilename: the name of the file to open
346 @type _strFilename: string
347 @return the
348 """
349 if not os.path.isfile(_strFilename):
350 print "I cannot find the file %s" % _strFilename
351 raise
352 if _bKeepComment:
353 self._parseCIF(open(_strFilename, "rb").read())
354 else:
355 self._parseCIF(CIF._readCIF(_strFilename))
356
357 @staticmethod
359 """
360 Check if all characters in a string are ascii,
361
362 @param _strIn: input string
363 @type _strIn: python string
364 @return: boolean
365 @rtype: boolean
366 """
367 bIsAcii = True
368 for i in _strIn:
369 if ord(i) > 127:
370 bIsAcii = False
371 break
372 return bIsAcii
373
374 @staticmethod
376 """
377 -Check if the filename containing the CIF data exists
378 -read the cif file
379 -removes the comments
380
381 @param _strFilename: the name of the CIF file
382 @type _strFilename: string
383 @return: a string containing the raw data
384 @rtype: string
385 """
386 if not os.path.isfile(_strFilename):
387 print "I cannot find the file %s" % _strFilename
388 raise
389 lLinesRead = open(_strFilename, "rb").readlines()
390 sText = ""
391 for sLine in lLinesRead:
392 iPos = sLine.find("#")
393 if iPos >= 0:
394 if CIF.isAscii(sLine):
395 sText += sLine[:iPos] + os.linesep
396
397 if iPos > 80 :
398 print("Warning, this line is too long and could cause problems in PreQuest", os.linesep, sLine)
399 else :
400 sText += sLine
401 if len(sLine.strip()) > 80 :
402 print("Warning, this line is too long and could cause problems in PreQues", os.linesep, sLine)
403 return sText
404
405
407 """
408 -Parses the text of a CIF file
409 -Cut it in fields
410 -Find all the loops and process
411 -Find all the keys and values
412
413 @param sText: the content of the CIF-file
414 @type sText: string
415 @return: Nothing, the data are incorporated at the CIF object dictionary
416 @rtype: dictionary
417 """
418 loopidx = []
419 looplen = []
420 loop = []
421
422 lFields = CIF._splitCIF(sText.strip())
423
424 for i in range(len(lFields)):
425 if lFields[i].lower() == "loop_":
426 loopidx.append(i)
427 if len(loopidx) > 0:
428 for i in loopidx:
429 loopone, length, keys = CIF._analyseOneLoop(lFields, i)
430 loop.append([keys, loopone])
431 looplen.append(length)
432
433
434 for i in range(len(loopidx) - 1, -1, -1):
435 f1 = lFields[:loopidx[i]] + lFields[loopidx[i] + looplen[i]:]
436 lFields = f1
437
438 self["loop_"] = loop
439
440 for i in range(len(lFields) - 1):
441
442 if len(lFields[i + 1]) == 0 : lFields[i + 1] = "?"
443 if lFields[i][0] == "_" and lFields[i + 1][0] != "_":
444 self[lFields[i]] = lFields[i + 1]
445
446 @staticmethod
448 """
449 Separate the text in fields as defined in the CIF
450
451 @param sText: the content of the CIF-file
452 @type sText: string
453 @return: list of all the fields of the CIF
454 @rtype: list
455 """
456 lFields = []
457 while True:
458 if len(sText) == 0:
459 break
460 elif sText[0] == "'":
461 idx = 0
462 bFinished = False
463 while not bFinished:
464 idx += 1 + sText[idx + 1:].find("'")
465
466 if idx >= len(sText) - 1:
467
468 lFields.append(sText[1:-1].strip())
469 sText = ""
470 bFinished = True
471 break
472
473 if sText[idx + 1] in CIF.BLANK:
474 lFields.append(sText[1:idx].strip())
475 sText1 = sText[idx + 1:]
476 sText = sText1.strip()
477 bFinished = True
478
479 elif sText[0] == '"':
480 idx = 0
481 bFinished = False
482 while not bFinished:
483 idx += 1 + sText[idx + 1:].find('"')
484
485 if idx >= len(sText) - 1:
486
487 lFields.append(sText[1:-1].strip())
488
489 sText = ""
490 bFinished = True
491 break
492
493 if sText[idx + 1] in CIF.BLANK:
494 lFields.append(sText[1:idx].strip())
495
496 sText1 = sText[idx + 1:]
497 sText = sText1.strip()
498 bFinished = True
499 elif sText[0] == ';':
500 if sText[1:].strip().find(CIF.BINARY_MARKER) == 0:
501 idx = sText[32:].find(CIF.BINARY_MARKER)
502 if idx == -1:
503 idx = 0
504 else:
505 idx += 32 + len(CIF.BINARY_MARKER)
506 else:
507 idx = 0
508 bFinished = False
509 while not bFinished:
510 idx += 1 + sText[idx + 1:].find(';')
511 if sText[idx - 1] in CIF.EOL:
512 lFields.append(sText[1:idx - 1].strip())
513 sText1 = sText[idx + 1:]
514 sText = sText1.strip()
515 bFinished = True
516 else:
517 f = sText.split(None, 1)[0]
518 lFields.append(f)
519
520 sText1 = sText[len(f):].strip()
521 sText = sText1
522 return lFields
523
524 @staticmethod
526 """Processes one loop in the data extraction of the CIF file
527 @param lFields: list of all the words contained in the cif file
528 @type lFields: list
529 @param iStart: the starting index corresponding to the "loop_" key
530 @type iStart: integer
531 @return: the list of loop dictionaries, the length of the data
532 extracted from the lFields and the list of all the keys of the loop.
533 @rtype: tuple
534 """
535
536
537
538 loop = []
539 keys = []
540 i = iStart + 1
541 bFinished = False
542 while not bFinished:
543 if lFields[i][0] == "_":
544 keys.append(lFields[i])
545 i += 1
546 else:
547 bFinished = True
548 data = []
549 while True:
550 if i >= len(lFields):
551 break
552 elif len(lFields[i]) == 0:
553 break
554 elif lFields[i][0] == "_":
555 break
556 elif lFields[i] in ["loop_", "stop_", "global_", "data_", "save_"]:
557 break
558 else:
559 data.append(lFields[i])
560 i += 1
561
562 k = 0
563
564 if len(data) < len(keys):
565 element = {}
566 for j in keys:
567 if k < len(data):
568 element[j] = data[k]
569 else :
570 element[j] = "?"
571 k += 1
572
573 loop.append(element)
574
575 else:
576
577
578 for i in range(len(data) / len(keys)):
579 element = {}
580 for j in keys:
581 element[j] = data[k]
582 k += 1
583
584 loop.append(element)
585
586 return loop, 1 + len(keys) + len(data), keys
587
588
589
590
591
592
593
594
595
596
597 - def saveCIF(self, _strFilename="test.cif"):
598 """Transforms the CIF object in string then write it into the given file
599 @param _strFilename: the of the file to be written
600 @type param: string
601 """
602
603 try:
604 fFile = open(_strFilename, "w")
605 except IOError:
606 print("Error during the opening of file for write: %s" %
607 _strFilename)
608 return
609 fFile.write(self._cif2str(_strFilename))
610 try:
611 fFile.close()
612 except IOError:
613 print("Error during the closing of file for write: %s" %
614 _strFilename)
615
616
617
618
619
621 """converts a cif dictionnary to a string according to the CIF syntax
622 @param _strFilename: the name of the filename to be appended in the
623 header of the CIF file
624 @type _strFilename: string
625 @return : a sting that corresponds to the content of the CIF-file.
626 @rtype: string
627 """
628 sCifText = ""
629 for i in __version__:
630 sCifText += "# " + i + os.linesep
631 if self.exists("_chemical_name_common"):
632 t = self["_chemical_name_common"].split()[0]
633 else:
634 t = os.path.splitext(os.path.split(_strFilename.strip())[1])[0]
635 sCifText += "data_%s%s" % (t, os.linesep)
636
637 lKeys = self.keys()
638 lKeys.sort()
639 for sKey in lKeys:
640 if sKey == "loop_":
641 continue
642 sValue = str(self[sKey])
643 if sValue.find("\n") > -1:
644 sLine = "%s %s;%s %s %s;%s" % (sKey, os.linesep, os.linesep,
645 sValue, os.linesep, os.linesep)
646 elif len(sValue.split()) > 1:
647 sLine = "%s '%s' \n" % (sKey, sValue)
648 if len(sLine) > 80:
649 sLine = "%s %s '%s' %s" % (sKey, os.linesep,
650 sValue, os.linesep)
651 else:
652 sLine = "%s %s %s" % (sKey, sValue, os.linesep)
653 if len(sLine) > 80:
654 sLine = "%s %s %s %s" % (sKey, os.linesep,
655 sValue, os.linesep)
656 sCifText += sLine
657 if self.has_key("loop_"):
658 for loop in self["loop_"]:
659 sCifText += "loop_ " + os.linesep
660 lKeys = loop[0]
661 llData = loop[1]
662 for sKey in lKeys:
663 sCifText += " %s %s" % (sKey, os.linesep)
664 for lData in llData:
665 sLine = ""
666 for key in lKeys:
667 sRawValue = lData[key]
668 if sRawValue.find("\n") > -1:
669 sLine += "%s; %s %s;%s" % (os.linesep, sRawValue,
670 os.linesep, os.linesep)
671 sCifText += sLine
672 sLine = ""
673 else:
674 if len(sRawValue.split()) > 1:
675 value = "'%s'" % (sRawValue)
676 else:
677 value = sRawValue
678 if len(sLine) + len(value) > 78:
679 sCifText += sLine + " " + os.linesep
680 sLine = " " + value
681 else:
682 sLine += " " + value
683 sCifText += sLine + " " + os.linesep
684 sCifText += os.linesep
685
686 return sCifText
687
689 """
690 Check if the key exists in the CIF and is non empty.
691 @param sKey: CIF key
692 @type sKey: string
693 @param cif: CIF dictionary
694 @return: True if the key exists in the CIF dictionary and is non empty
695 @rtype: boolean
696 """
697 bExists = False
698 if self.has_key(sKey):
699 if len(self[sKey]) >= 1:
700 if self[sKey][0] not in ["?", "."]:
701 bExists = True
702 return bExists
703
705 """
706 Check if the key exists in the CIF dictionary.
707 @param sKey: CIF key
708 @type sKey: string
709 @param cif: CIF dictionary
710 @return: True if the key exists in the CIF dictionary and is non empty
711 @rtype: boolean
712 """
713 if not self.exists("loop_"):
714 return False
715 bExists = False
716 if not bExists:
717 for i in self["loop_"]:
718 for j in i[0]:
719 if j == sKey:
720 bExists = True
721 return bExists
722
724 """
725 Load the powder diffraction CHIPLOT file and returns the
726 pd_CIF dictionary in the object
727
728 @param _strFilename: the name of the file to open
729 @type _strFilename: string
730 @return: the CIF object corresponding to the powder diffraction
731 @rtype: dictionary
732 """
733 if not os.path.isfile(_strFilename):
734 print "I cannot find the file %s" % _strFilename
735 raise
736 lInFile = open(_strFilename, "r").readlines()
737 self["_audit_creation_method"] = 'From 2-D detector using FIT2D and CIFfile'
738 self["_pd_meas_scan_method"] = "fixed"
739 self["_pd_spec_description"] = lInFile[0].strip()
740 try:
741 iLenData = int(lInFile[3])
742 except ValueError:
743 iLenData = None
744 lOneLoop = []
745 try:
746 f2ThetaMin = float(lInFile[4].split()[0])
747 last = ""
748 for sLine in lInFile[-20:]:
749 if sLine.strip() != "":
750 last = sLine.strip()
751 f2ThetaMax = float(last.split()[0])
752 limitsOK = True
753
754 except (ValueError, IndexError):
755 limitsOK = False
756 f2ThetaMin = 180.0
757 f2ThetaMax = 0
758
759 for sLine in lInFile[4:]:
760 sCleaned = sLine.split("#")[0].strip()
761 data = sCleaned.split()
762 if len(data) == 2 :
763 if not limitsOK:
764 f2Theta = float(data[0])
765 if f2Theta < f2ThetaMin :
766 f2ThetaMin = f2Theta
767 if f2Theta > f2ThetaMax :
768 f2ThetaMax = f2Theta
769 lOneLoop.append({ "_pd_meas_intensity_total": data[1] })
770 if not iLenData:
771 iLenData = len(lOneLoop)
772 assert (iLenData == len(lOneLoop))
773 self[ "_pd_meas_2theta_range_inc" ] = "%.4f" % ((f2ThetaMax - f2ThetaMin) / (iLenData - 1))
774 if self[ "_pd_meas_2theta_range_inc" ] < 0:
775 self[ "_pd_meas_2theta_range_inc" ] = abs (self[ "_pd_meas_2theta_range_inc" ])
776 tmp = f2ThetaMax
777 f2ThetaMax = f2ThetaMin
778 f2ThetaMin = tmp
779 self[ "_pd_meas_2theta_range_max" ] = "%.4f" % f2ThetaMax
780 self[ "_pd_meas_2theta_range_min" ] = "%.4f" % f2ThetaMin
781 self[ "_pd_meas_number_of_points" ] = str(iLenData)
782 self["loop_"] = [ [ ["_pd_meas_intensity_total" ], lOneLoop ] ]
783
784
785 @staticmethod
787 "Returns True if the key (string) exist in the array called loop"""
788 try:
789 loop.index(key)
790 return True
791 except ValueError:
792 return False
793