diff options
Diffstat (limited to 'src/silx/third_party/EdfFile.py')
-rw-r--r-- | src/silx/third_party/EdfFile.py | 1302 |
1 files changed, 1302 insertions, 0 deletions
diff --git a/src/silx/third_party/EdfFile.py b/src/silx/third_party/EdfFile.py new file mode 100644 index 0000000..a9e2e1b --- /dev/null +++ b/src/silx/third_party/EdfFile.py @@ -0,0 +1,1302 @@ +# /*########################################################################## +# +# Copyright (c) 2004-2023 European Synchrotron Radiation Facility +# +# This file is part of the PyMca X-ray Fluorescence Toolkit developed at +# the ESRF by the Software group. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ############################################################################*/ +__author__ = "Alexandre Gobbo, V.A. Sole - ESRF Data Analysis" +__contact__ = "sole@esrf.fr" +__license__ = "MIT" +__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France" +""" + EdfFile.py + Generic class for Edf files manipulation. + + Interface: + =========================== + class EdfFile: + __init__(self,FileName) + GetNumImages(self) + def GetData(self,Index, DataType="",Pos=None,Size=None): + GetPixel(self,Index,Position) + GetHeader(self,Index) + GetStaticHeader(self,Index) + WriteImage (self,Header,Data,Append=1,DataType="",WriteAsUnsigened=0,ByteOrder="") + + + Edf format assumptions: + =========================== + The following details were assumed for this implementation: + - Each Edf file contains a certain number of data blocks. + - Each data block represents data stored in an one, two or three-dimensional array. + - Each data block contains a header section, written in ASCII, and a data section of + binary information. + - The size of the header section in bytes is a multiple of 1024. The header is + padded with spaces (0x20). If the header is not padded to a multiple of 1024, + the file is recognized, but the output is always made in this format. + - The header section starts by '{' and finishes by '}'. It is composed by several + pairs 'keyword = value;'. The keywords are case insensitive, but the values are case + sensitive. Each pair is put in a new line (they are separeted by 0x0A). In the + end of each line, a semicolon (;) separes the pair of a comment, not interpreted. + Exemple: + { + ; Exemple Header + HeaderID = EH:000001:000000:000000 ; automatically generated + ByteOrder = LowByteFirst ; + DataType = FloatValue ; 4 bytes per pixel + Size = 4000000 ; size of data section + Dim_1= 1000 ; x coordinates + Dim_2 = 1000 ; y coordinates + + (padded with spaces to complete 1024 bytes) + } + - There are some fields in the header that are required for this implementation. If any of + these is missing, or inconsistent, it will be generated an error: + Size: Represents size of data block + Dim_1: size of x coordinates (Dim_2 for 2-dimentional images, and also Dim_3 for 3d) + DataType + ByteOrder + - For the written images, these fields are automatically genereted: + Size,Dim_1 (Dim_2 and Dim_3, if necessary), Byte Order, DataType, HeaderID and Image + These fields are called here "static header", and can be retrieved by the method + GetStaticHeader. Other header components are taken by GetHeader. Both methods returns + a dictionary in which the key is the keyword of the pair. When writting an image through + WriteImage method, the Header parameter should not contain the static header information, + which is automatically generated. + - The indexing of images through these functions is based just on the 0-based position in + the file, the header items HeaderID and Image are not considered for referencing the + images. + - The data section contais a number of bytes equal to the value of Size keyword. Data + section is going to be translated into an 1D, 2D or 3D Numpy Array, and accessed + through GetData method call. +""" +DEBUG = 0 +################################################################################ +import sys +import numpy +import os.path + +try: + import gzip + + GZIP = True +except: + GZIP = False +try: + import bz2 + + BZ2 = True +except: + BZ2 = False + +MARCCD_SUPPORT = False +PILATUS_CBF_SUPPORT = False +CAN_USE_FASTEDF = False + +from fabio import TiffIO + +TIFF_SUPPORT = True + +from silx.utils.deprecation import deprecated_warning + +deprecated_warning( + "Module", + "silx.third_party.EdfFile", + since_version="2.0.0", + replacement="fabio.open and fabio.edfimage.EdfImage", +) + +# Constants + +HEADER_BLOCK_SIZE = 1024 +STATIC_HEADER_ELEMENTS = ( + "HeaderID", + "Image", + "ByteOrder", + "DataType", + "Dim_1", + "Dim_2", + "Dim_3", + "Offset_1", + "Offset_2", + "Offset_3", + "Size", +) + +STATIC_HEADER_ELEMENTS_CAPS = ( + "HEADERID", + "IMAGE", + "BYTEORDER", + "DATATYPE", + "DIM_1", + "DIM_2", + "DIM_3", + "OFFSET_1", + "OFFSET_2", + "OFFSET_3", + "SIZE", +) + +LOWER_CASE = 0 +UPPER_CASE = 1 + +KEYS = 1 +VALUES = 2 + + +class Image(object): + """ """ + + def __init__(self): + """Constructor""" + self.Header = {} + self.StaticHeader = {} + self.HeaderPosition = 0 + self.DataPosition = 0 + self.Size = 0 + self.NumDim = 1 + self.Dim1 = 0 + self.Dim2 = 0 + self.Dim3 = 0 + self.DataType = "" + + +class EdfFile(object): + """ """ + + def __init__(self, FileName, access=None, fastedf=None): + """Constructor + + :param FileName: Name of the file (either existing or to be created) + :type FileName: string + :param access: access mode "r" for reading (the file should exist) or + "w" for writing (if the file does not exist, it does not matter). + :type access: string + :type fastedf: True to use the fastedf module + :param fastedf: bool + """ + self.Images = [] + self.NumImages = 0 + self.FileName = FileName + self.File = 0 + if fastedf is None: + fastedf = 0 + self.fastedf = fastedf + self.ADSC = False + self.MARCCD = False + self.TIFF = False + self.PILATUS_CBF = False + self.SPE = False + if sys.byteorder == "big": + self.SysByteOrder = "HighByteFirst" + else: + self.SysByteOrder = "LowByteFirst" + + if hasattr(FileName, "seek") and hasattr(FileName, "read"): + # this looks like a file descriptor ... + self.__ownedOpen = False + self.File = FileName + try: + self.FileName = self.File.name + except AttributeError: + self.FileName = self.File.filename + elif FileName.lower().endswith(".gz"): + if GZIP: + self.__ownedOpen = False + self.File = gzip.GzipFile(FileName) + else: + raise IOError("No gzip module support in this system") + elif FileName.lower().endswith(".bz2"): + if BZ2: + self.__ownedOpen = False + self.File = bz2.BZ2File(FileName) + else: + raise IOError("No bz2 module support in this system") + else: + self.__ownedOpen = True + + if self.File in [0, None]: + if access is not None: + if access[0].upper() == "R": + if not os.path.isfile(self.FileName): + raise IOError("File %s not found" % FileName) + if "b" not in access: + access += "b" + if 1: + if not os.path.isfile(self.FileName): + # write access + if access is None: + # allow writing and reading + access = "ab+" + self.File = open(self.FileName, access) + self.File.seek(0, 0) + return + if "b" not in access: + access += "b" + self.File = open(self.FileName, access) + return + else: + if access is None: + if os.access(self.FileName, os.W_OK): + access = "r+b" + else: + access = "rb" + self.File = open(self.FileName, access) + self.File.seek(0, 0) + twoChars = self.File.read(2) + tiff = False + if sys.version < "3.0": + if twoChars in ["II", "MM"]: + tiff = True + elif twoChars in [eval('b"II"'), eval('b"MM"')]: + tiff = True + if tiff: + fileExtension = os.path.splitext(self.FileName)[-1] + if ( + fileExtension.lower() in [".tif", ".tiff"] + or sys.version > "2.9" + ): + if not TIFF_SUPPORT: + raise IOError("TIFF support not implemented") + else: + self.TIFF = True + elif not MARCCD_SUPPORT: + if not TIFF_SUPPORT: + raise IOError("MarCCD support not implemented") + else: + self.TIFF = True + else: + self.MARCCD = True + basename = os.path.basename(FileName).upper() + if basename.endswith(".CBF"): + if not PILATUS_CBF_SUPPORT: + raise IOError("CBF support not implemented") + if twoChars[0] != "{": + self.PILATUS_CBF = True + elif basename.endswith(".SPE"): + if twoChars[0] != "$": + self.SPE = True + elif basename.endswith("EDF.GZ") or basename.endswith("CCD.GZ"): + self.GZIP = True + else: + try: + self.File.close() + except: + pass + raise IOError("EdfFile: Error opening file") + + self.File.seek(0, 0) + if self.TIFF: + self._wrapTIFF() + self.File.close() + return + if self.MARCCD: + self._wrapMarCCD() + self.File.close() + return + if self.PILATUS_CBF: + self._wrapPilatusCBF() + self.File.close() + return + if self.SPE: + self._wrapSPE() + self.File.close() + return + + Index = 0 + line = self.File.readline() + selectedLines = [""] + if sys.version > "2.6": + selectedLines.append(eval('b""')) + parsingHeader = False + while line not in selectedLines: + # decode to make sure I have character string + # str to make sure python 2.x sees it as string and not unicode + if sys.version < "3.0": + if type(line) != type(str("")): + line = "%s" % line + else: + try: + line = str(line.decode()) + except UnicodeDecodeError: + try: + line = str(line.decode("utf-8")) + except UnicodeDecodeError: + try: + line = str(line.decode("latin-1")) + except UnicodeDecodeError: + line = "%s" % line + if (line.count("{\n") >= 1) or (line.count("{\r\n") >= 1): + parsingHeader = True + Index = self.NumImages + self.NumImages = self.NumImages + 1 + self.Images.append(Image()) + + if line.count("=") >= 1: + listItems = line.split("=", 1) + typeItem = listItems[0].strip() + listItems = listItems[1].split(";", 1) + valueItem = listItems[0].strip() + if (typeItem == "HEADER_BYTES") and (Index == 0): + self.ADSC = True + break + + # if typeItem in self.Images[Index].StaticHeader.keys(): + if typeItem.upper() in STATIC_HEADER_ELEMENTS_CAPS: + self.Images[Index].StaticHeader[typeItem] = valueItem + else: + self.Images[Index].Header[typeItem] = valueItem + if ((line.count("}\n") >= 1) or (line.count("}\r") >= 1)) and ( + parsingHeader + ): + parsingHeader = False + # for i in STATIC_HEADER_ELEMENTS_CAPS: + # if self.Images[Index].StaticHeader[i]=="": + # raise "Bad File Format" + self.Images[Index].DataPosition = self.File.tell() + # self.File.seek(int(self.Images[Index].StaticHeader["Size"]), 1) + StaticPar = SetDictCase( + self.Images[Index].StaticHeader, UPPER_CASE, KEYS + ) + if "SIZE" in StaticPar.keys(): + self.Images[Index].Size = int(StaticPar["SIZE"]) + if self.Images[Index].Size <= 0: + self.NumImages = Index + line = self.File.readline() + continue + else: + raise TypeError("EdfFile: Image doesn't have size information") + if "DIM_1" in StaticPar.keys(): + self.Images[Index].Dim1 = int(StaticPar["DIM_1"]) + self.Images[Index].Offset1 = int(StaticPar.get("Offset_1", "0")) + else: + raise TypeError("EdfFile: Image doesn't have dimension information") + if "DIM_2" in StaticPar.keys(): + self.Images[Index].NumDim = 2 + self.Images[Index].Dim2 = int(StaticPar["DIM_2"]) + self.Images[Index].Offset2 = int(StaticPar.get("Offset_2", "0")) + if "DIM_3" in StaticPar.keys(): + self.Images[Index].NumDim = 3 + self.Images[Index].Dim3 = int(StaticPar["DIM_3"]) + self.Images[Index].Offset3 = int(StaticPar.get("Offset_3", "0")) + if "DATATYPE" in StaticPar.keys(): + self.Images[Index].DataType = StaticPar["DATATYPE"] + else: + raise TypeError("EdfFile: Image doesn't have datatype information") + if "BYTEORDER" in StaticPar.keys(): + self.Images[Index].ByteOrder = StaticPar["BYTEORDER"] + else: + raise TypeError("EdfFile: Image doesn't have byteorder information") + + self.File.seek(self.Images[Index].Size, 1) + + line = self.File.readline() + + if self.ADSC: + self.File.seek(0, 0) + self.NumImages = 1 + # this is a bad implementation of fabio adscimage + # please take a look at the fabio module of fable at sourceforge + infile = self.File + header_keys = [] + header = {} + try: + """read an adsc header""" + line = infile.readline() + bytesread = len(line) + while "}" not in line: + if "=" in line: + (key, val) = line.split("=") + header_keys.append(key.strip()) + header[key.strip()] = val.strip(" ;\n") + line = infile.readline() + bytesread = bytesread + len(line) + except: + raise Exception("Error processing adsc header") + # banned by bzip/gzip??? + try: + infile.seek(int(header["HEADER_BYTES"]), 0) + except TypeError: + # Gzipped does not allow a seek and read header is not + # promising to stop in the right place + infile.close() + infile = self._open(fname, "rb") + infile.read(int(header["HEADER_BYTES"])) + binary = infile.read() + infile.close() + + # now read the data into the array + self.Images[Index].Dim1 = int(header["SIZE1"]) + self.Images[Index].Dim2 = int(header["SIZE2"]) + self.Images[Index].NumDim = 2 + self.Images[Index].DataType = "UnsignedShort" + try: + self.__data = numpy.reshape( + numpy.copy(numpy.frombuffer(binary, numpy.uint16)), + (self.Images[Index].Dim2, self.Images[Index].Dim1), + ) + except ValueError: + msg = "Size spec in ADSC-header does not match size of image data field" + raise IOError(msg) + if "little" in header["BYTE_ORDER"]: + self.Images[Index].ByteOrder = "LowByteFirst" + else: + self.Images[Index].ByteOrder = "HighByteFirst" + if self.SysByteOrder.upper() != self.Images[Index].ByteOrder.upper(): + self.__data = self.__data.byteswap() + self.Images[Index].ByteOrder = self.SysByteOrder + + self.Images[Index].StaticHeader["Dim_1"] = self.Images[Index].Dim1 + self.Images[Index].StaticHeader["Dim_2"] = self.Images[Index].Dim2 + self.Images[Index].StaticHeader["Offset_1"] = 0 + self.Images[Index].StaticHeader["Offset_2"] = 0 + self.Images[Index].StaticHeader["DataType"] = self.Images[Index].DataType + + self.__makeSureFileIsClosed() + + def _wrapTIFF(self): + self._wrappedInstance = TiffIO.TiffIO( + self.File, cache_length=0, mono_output=True + ) + self.NumImages = self._wrappedInstance.getNumberOfImages() + if self.NumImages < 1: + return + + # wrapped image objects have to provide getInfo and getData + # info = self._wrappedInstance.getInfo( index) + # data = self._wrappedInstance.getData( index) + # for the time being I am going to assume all the images + # in the file have the same data type type + data = None + + for Index in range(self.NumImages): + info = self._wrappedInstance.getInfo(Index) + self.Images.append(Image()) + self.Images[Index].Dim1 = info["nRows"] + self.Images[Index].Dim2 = info["nColumns"] + self.Images[Index].NumDim = 2 + if data is None: + data = self._wrappedInstance.getData(0) + self.Images[Index].DataType = self.__GetDefaultEdfType__(data.dtype) + self.Images[Index].StaticHeader["Dim_1"] = self.Images[Index].Dim1 + self.Images[Index].StaticHeader["Dim_2"] = self.Images[Index].Dim2 + self.Images[Index].StaticHeader["Offset_1"] = 0 + self.Images[Index].StaticHeader["Offset_2"] = 0 + self.Images[Index].StaticHeader["DataType"] = self.Images[Index].DataType + self.Images[Index].Header.update(info) + + def _wrapMarCCD(self): + raise NotImplementedError("Look at the module EdfFile from PyMca") + + def _wrapPilatusCBF(self): + raise NotImplementedError("Look at the module EdfFile from PyMca") + + def _wrapSPE(self): + if 0 and sys.version < "3.0": + self.File.seek(42) + xdim = numpy.int64(numpy.fromfile(self.File, numpy.int16, 1)[0]) + self.File.seek(656) + ydim = numpy.int64(numpy.fromfile(self.File, numpy.int16, 1)) + self.File.seek(4100) + self.__data = numpy.fromfile(self.File, numpy.uint16, int(xdim * ydim)) + else: + import struct + + self.File.seek(0) + a = self.File.read() + xdim = numpy.int64(struct.unpack("<h", a[42:44])[0]) + ydim = numpy.int64(struct.unpack("<h", a[656:658])[0]) + fmt = "<%dH" % int(xdim * ydim) + self.__data = numpy.array( + struct.unpack(fmt, a[4100 : int(4100 + int(2 * xdim * ydim))]) + ).astype(numpy.uint16) + self.__data.shape = ydim, xdim + Index = 0 + self.Images.append(Image()) + self.NumImages = 1 + self.Images[Index].Dim1 = ydim + self.Images[Index].Dim2 = xdim + self.Images[Index].NumDim = 2 + self.Images[Index].DataType = "UnsignedShort" + self.Images[Index].ByteOrder = "LowByteFirst" + if self.SysByteOrder.upper() != self.Images[Index].ByteOrder.upper(): + self.__data = self.__data.byteswap() + self.Images[Index].StaticHeader["Dim_1"] = self.Images[Index].Dim1 + self.Images[Index].StaticHeader["Dim_2"] = self.Images[Index].Dim2 + self.Images[Index].StaticHeader["Offset_1"] = 0 + self.Images[Index].StaticHeader["Offset_2"] = 0 + self.Images[Index].StaticHeader["DataType"] = self.Images[Index].DataType + + def GetNumImages(self): + """Returns number of images of the object (and associated file)""" + return self.NumImages + + def GetData(self, *var, **kw): + try: + self.__makeSureFileIsOpen() + return self._GetData(*var, **kw) + finally: + self.__makeSureFileIsClosed() + + def _GetData(self, Index, DataType="", Pos=None, Size=None): + """Returns numpy array with image data + Index: The zero-based index of the image in the file + DataType: The edf type of the array to be returnd + If ommited, it is used the default one for the type + indicated in the image header + Attention to the absence of UnsignedShort, + UnsignedInteger and UnsignedLong types in + Numpy Python + Default relation between Edf types and NumPy's typecodes: + SignedByte int8 b + UnsignedByte uint8 B + SignedShort int16 h + UnsignedShort uint16 H + SignedInteger int32 i + UnsignedInteger uint32 I + SignedLong int32 i + UnsignedLong uint32 I + Signed64 int64 (l in 64bit, q in 32 bit) + Unsigned64 uint64 (L in 64bit, Q in 32 bit) + FloatValue float32 f + DoubleValue float64 d + Pos: Tuple (x) or (x,y) or (x,y,z) that indicates the begining + of data to be read. If ommited, set to the origin (0), + (0,0) or (0,0,0) + Size: Tuple, size of the data to be returned as x) or (x,y) or + (x,y,z) if ommited, is the distance from Pos to the end. + + If Pos and Size not mentioned, returns the whole data. + """ + fastedf = self.fastedf + if Index < 0 or Index >= self.NumImages: + raise ValueError("EdfFile: Index out of limit") + if fastedf is None: + fastedf = 0 + if Pos is None and Size is None: + if self.ADSC or self.MARCCD or self.PILATUS_CBF or self.SPE: + return self.__data + elif self.TIFF: + data = self._wrappedInstance.getData(Index) + return data + else: + self.File.seek(self.Images[Index].DataPosition, 0) + datatype = self.__GetDefaultNumpyType__( + self.Images[Index].DataType, index=Index + ) + try: + datasize = self.__GetSizeNumpyType__(datatype) + except TypeError: + print("What is the meaning of this error?") + datasize = 8 + if self.Images[Index].NumDim == 3: + image = self.Images[Index] + sizeToRead = image.Dim1 * image.Dim2 * image.Dim3 * datasize + Data = numpy.copy( + numpy.frombuffer(self.File.read(sizeToRead), datatype) + ) + Data = numpy.reshape( + Data, + ( + self.Images[Index].Dim3, + self.Images[Index].Dim2, + self.Images[Index].Dim1, + ), + ) + elif self.Images[Index].NumDim == 2: + image = self.Images[Index] + sizeToRead = image.Dim1 * image.Dim2 * datasize + Data = numpy.copy( + numpy.frombuffer(self.File.read(sizeToRead), datatype) + ) + # print "datatype = ",datatype + # print "Data.type = ", Data.dtype.char + # print "self.Images[Index].DataType ", self.Images[Index].DataType + # print "Data.shape",Data.shape + # print "datasize = ",datasize + # print "sizeToRead ",sizeToRead + # print "lenData = ", len(Data) + Data = numpy.reshape( + Data, (self.Images[Index].Dim2, self.Images[Index].Dim1) + ) + elif self.Images[Index].NumDim == 1: + sizeToRead = self.Images[Index].Dim1 * datasize + Data = numpy.copy( + numpy.frombuffer(self.File.read(sizeToRead), datatype) + ) + elif self.ADSC or self.MARCCD or self.PILATUS_CBF or self.SPE: + return self.__data[Pos[1] : (Pos[1] + Size[1]), Pos[0] : (Pos[0] + Size[0])] + elif self.TIFF: + data = self._wrappedInstance.getData(Index) + return data[Pos[1] : (Pos[1] + Size[1]), Pos[0] : (Pos[0] + Size[0])] + elif fastedf and CAN_USE_FASTEDF: + raise NotImplementedError("Look at the module EdfFile from PyMCA") + else: + if fastedf: + print("It could not use fast routines") + type_ = self.__GetDefaultNumpyType__( + self.Images[Index].DataType, index=Index + ) + size_pixel = self.__GetSizeNumpyType__(type_) + Data = numpy.array([], type_) + if self.Images[Index].NumDim == 1: + if Pos is None: + Pos = (0,) + if Size is None: + Size = (0,) + sizex = self.Images[Index].Dim1 + Size = list(Size) + if Size[0] == 0: + Size[0] = sizex - Pos[0] + self.File.seek( + (Pos[0] * size_pixel) + self.Images[Index].DataPosition, 0 + ) + Data = numpy.copy( + numpy.frombuffer(self.File.read(Size[0] * size_pixel), type_) + ) + elif self.Images[Index].NumDim == 2: + if Pos is None: + Pos = (0, 0) + if Size is None: + Size = (0, 0) + Size = list(Size) + sizex, sizey = self.Images[Index].Dim1, self.Images[Index].Dim2 + if Size[0] == 0: + Size[0] = sizex - Pos[0] + if Size[1] == 0: + Size[1] = sizey - Pos[1] + # print len(range(Pos[1],Pos[1]+Size[1])), "LECTURES OF ", Size[0], "POINTS" + # print "sizex = ", sizex, "sizey = ", sizey + Data = numpy.zeros((Size[1], Size[0]), type_) + dataindex = 0 + for y in range(Pos[1], Pos[1] + Size[1]): + self.File.seek( + (((y * sizex) + Pos[0]) * size_pixel) + + self.Images[Index].DataPosition, + 0, + ) + line = numpy.copy( + numpy.frombuffer(self.File.read(Size[0] * size_pixel), type_) + ) + Data[dataindex, :] = line[:] + # Data=numpy.concatenate((Data,line)) + dataindex += 1 + # print "DataSize = ",Data.shape + # print "Requested reshape = ",Size[1],'x',Size[0] + # Data = numpy.reshape(Data, (Size[1],Size[0])) + elif self.Images[Index].NumDim == 3: + if Pos is None: + Pos = (0, 0, 0) + if Size is None: + Size = (0, 0, 0) + Size = list(Size) + sizex, sizey, sizez = ( + self.Images[Index].Dim1, + self.Images[Index].Dim2, + self.Images[Index].Dim3, + ) + if Size[0] == 0: + Size[0] = sizex - Pos[0] + if Size[1] == 0: + Size[1] = sizey - Pos[1] + if Size[2] == 0: + Size[2] = sizez - Pos[2] + for z in range(Pos[2], Pos[2] + Size[2]): + for y in range(Pos[1], Pos[1] + Size[1]): + self.File.seek( + ((((z * sizey + y) * sizex) + Pos[0]) * size_pixel) + + self.Images[Index].DataPosition, + 0, + ) + line = numpy.copy( + numpy.frombuffer( + self.File.read(Size[0] * size_pixel), type_ + ) + ) + Data = numpy.concatenate((Data, line)) + Data = numpy.reshape(Data, (Size[2], Size[1], Size[0])) + + if self.SysByteOrder.upper() != self.Images[Index].ByteOrder.upper(): + Data = Data.byteswap() + if DataType != "": + Data = self.__SetDataType__(Data, DataType) + return Data + + def GetPixel(self, Index, Position): + """Returns double value of the pixel, regardless the format of the array + Index: The zero-based index of the image in the file + Position: Tuple with the coordinete (x), (x,y) or (x,y,z) + """ + if Index < 0 or Index >= self.NumImages: + raise ValueError("EdfFile: Index out of limit") + if len(Position) != self.Images[Index].NumDim: + raise ValueError("EdfFile: coordinate with wrong dimension ") + + size_pixel = self.__GetSizeNumpyType__( + self.__GetDefaultNumpyType__(self.Images[Index].DataType, index=Index) + ) + offset = Position[0] * size_pixel + if self.Images[Index].NumDim > 1: + size_row = size_pixel * self.Images[Index].Dim1 + offset = offset + (Position[1] * size_row) + if self.Images[Index].NumDim == 3: + size_img = size_row * self.Images[Index].Dim2 + offset = offset + (Position[2] * size_img) + self.File.seek(self.Images[Index].DataPosition + offset, 0) + Data = numpy.copy( + numpy.frombuffer( + self.File.read(size_pixel), + self.__GetDefaultNumpyType__(self.Images[Index].DataType, index=Index), + ) + ) + if self.SysByteOrder.upper() != self.Images[Index].ByteOrder.upper(): + Data = Data.byteswap() + Data = self.__SetDataType__(Data, "DoubleValue") + return Data[0] + + def GetHeader(self, Index): + """Returns dictionary with image header fields. + Does not include the basic fields (static) defined by data shape, + type and file position. These are get with GetStaticHeader + method. + Index: The zero-based index of the image in the file + """ + if Index < 0 or Index >= self.NumImages: + raise ValueError("Index out of limit") + # return self.Images[Index].Header + ret = {} + for i in self.Images[Index].Header.keys(): + ret[i] = self.Images[Index].Header[i] + return ret + + def GetStaticHeader(self, Index): + """Returns dictionary with static parameters + Data format and file position dependent information + (dim1,dim2,size,datatype,byteorder,headerId,Image) + Index: The zero-based index of the image in the file + """ + if Index < 0 or Index >= self.NumImages: + raise ValueError("Index out of limit") + # return self.Images[Index].StaticHeader + ret = {} + for i in self.Images[Index].StaticHeader.keys(): + ret[i] = self.Images[Index].StaticHeader[i] + return ret + + def WriteImage(self, *var, **kw): + try: + self.__makeSureFileIsOpen() + return self._WriteImage(*var, **kw) + finally: + self.__makeSureFileIsClosed() + + def _WriteImage(self, Header, Data, Append=1, DataType="", ByteOrder=""): + """Writes image to the file. + Header: Dictionary containing the non-static header + information (static information is generated + according to position of image and data format + Append: If equals to 0, overwrites the file. Otherwise, appends + to the end of the file + DataType: The data type to be saved to the file: + SignedByte + UnsignedByte + SignedShort + UnsignedShort + SignedInteger + UnsignedInteger + SignedLong + UnsignedLong + FloatValue + DoubleValue + Default: according to Data array typecode: + 1: SignedByte + b: UnsignedByte + s: SignedShort + w: UnsignedShort + i: SignedInteger + l: SignedLong + u: UnsignedLong + f: FloatValue + d: DoubleValue + ByteOrder: Byte order of the data in file: + HighByteFirst + LowByteFirst + Default: system's byte order + """ + if Append == 0: + self.File.truncate(0) + self.Images = [] + self.NumImages = 0 + Index = self.NumImages + self.NumImages = self.NumImages + 1 + self.Images.append(Image()) + + # self.Images[Index].StaticHeader["Dim_1"] = "%d" % Data.shape[1] + # self.Images[Index].StaticHeader["Dim_2"] = "%d" % Data.shape[0] + scalarSize = self.__GetSizeNumpyType__(Data.dtype) + if len(Data.shape) == 1: + self.Images[Index].Dim1 = Data.shape[0] + self.Images[Index].StaticHeader["Dim_1"] = "%d" % self.Images[Index].Dim1 + self.Images[Index].Size = Data.shape[0] * scalarSize + elif len(Data.shape) == 2: + self.Images[Index].Dim1 = Data.shape[1] + self.Images[Index].Dim2 = Data.shape[0] + self.Images[Index].StaticHeader["Dim_1"] = "%d" % self.Images[Index].Dim1 + self.Images[Index].StaticHeader["Dim_2"] = "%d" % self.Images[Index].Dim2 + self.Images[Index].Size = Data.shape[0] * Data.shape[1] * scalarSize + self.Images[Index].NumDim = 2 + elif len(Data.shape) == 3: + self.Images[Index].Dim1 = Data.shape[2] + self.Images[Index].Dim2 = Data.shape[1] + self.Images[Index].Dim3 = Data.shape[0] + self.Images[Index].StaticHeader["Dim_1"] = "%d" % self.Images[Index].Dim1 + self.Images[Index].StaticHeader["Dim_2"] = "%d" % self.Images[Index].Dim2 + self.Images[Index].StaticHeader["Dim_3"] = "%d" % self.Images[Index].Dim3 + self.Images[Index].Size = ( + Data.shape[0] * Data.shape[1] * Data.shape[2] * scalarSize + ) + self.Images[Index].NumDim = 3 + elif len(Data.shape) > 3: + raise TypeError("EdfFile: Data dimension not suported") + + if DataType == "": + self.Images[Index].DataType = self.__GetDefaultEdfType__(Data.dtype) + else: + self.Images[Index].DataType = DataType + Data = self.__SetDataType__(Data, DataType) + + if ByteOrder == "": + self.Images[Index].ByteOrder = self.SysByteOrder + else: + self.Images[Index].ByteOrder = ByteOrder + + self.Images[Index].StaticHeader["Size"] = "%d" % self.Images[Index].Size + self.Images[Index].StaticHeader["Image"] = Index + 1 + self.Images[Index].StaticHeader["HeaderID"] = ( + "EH:%06d:000000:000000" % self.Images[Index].StaticHeader["Image"] + ) + self.Images[Index].StaticHeader["ByteOrder"] = self.Images[Index].ByteOrder + self.Images[Index].StaticHeader["DataType"] = self.Images[Index].DataType + + self.Images[Index].Header = {} + self.File.seek(0, 2) + StrHeader = "{\n" + for i in STATIC_HEADER_ELEMENTS: + if i in self.Images[Index].StaticHeader.keys(): + StrHeader = StrHeader + ( + "%s = %s ;\n" % (i, self.Images[Index].StaticHeader[i]) + ) + for i in Header.keys(): + StrHeader = StrHeader + ("%s = %s ;\n" % (i, Header[i])) + self.Images[Index].Header[i] = Header[i] + newsize = ( + ((len(StrHeader) + 1) // HEADER_BLOCK_SIZE) + 1 + ) * HEADER_BLOCK_SIZE - 2 + newsize = int(newsize) + StrHeader = StrHeader.ljust(newsize) + StrHeader = StrHeader + "}\n" + + self.Images[Index].HeaderPosition = self.File.tell() + self.File.write(StrHeader.encode()) + self.Images[Index].DataPosition = self.File.tell() + + # if self.Images[Index].StaticHeader["ByteOrder"] != self.SysByteOrder: + if self.Images[Index].ByteOrder.upper() != self.SysByteOrder.upper(): + self.File.write((Data.byteswap()).tobytes()) + else: + self.File.write(Data.tobytes()) + + def __makeSureFileIsOpen(self): + if DEBUG: + print("Making sure file is open") + if not self.__ownedOpen: + return + if self.ADSC or self.MARCCD or self.PILATUS_CBF or self.SPE: + if DEBUG: + print("Special case. Image is buffered") + return + if self.File in [0, None]: + if DEBUG: + print("File is None") + elif self.File.closed: + if DEBUG: + print("Reopening closed file") + accessMode = self.File.mode + fileName = self.File.name + newFile = open(fileName, accessMode) + self.File = newFile + return + + def __makeSureFileIsClosed(self): + if DEBUG: + print("Making sure file is closed") + if not self.__ownedOpen: + return + if self.ADSC or self.MARCCD or self.PILATUS_CBF or self.SPE: + if DEBUG: + print("Special case. Image is buffered") + return + if self.File in [0, None]: + if DEBUG: + print("File is None") + elif not self.File.closed: + if DEBUG: + print("Closing file") + self.File.close() + return + + def __GetDefaultNumpyType__(self, EdfType, index=None): + """Internal method: returns NumPy type according to Edf type""" + return self.GetDefaultNumpyType(EdfType, index) + + def __GetDefaultEdfType__(self, NumpyType): + """Internal method: returns Edf type according Numpy type""" + if NumpyType in ["b", numpy.int8]: + return "SignedByte" + elif NumpyType in ["B", numpy.uint8]: + return "UnsignedByte" + elif NumpyType in ["h", numpy.int16]: + return "SignedShort" + elif NumpyType in ["H", numpy.uint16]: + return "UnsignedShort" + elif NumpyType in ["i", numpy.int32]: + return "SignedInteger" + elif NumpyType in ["I", numpy.uint32]: + return "UnsignedInteger" + elif NumpyType == "l": + if sys.platform == "linux2": + return "Signed64" + else: + return "SignedLong" + elif NumpyType == "L": + if sys.platform == "linux2": + return "Unsigned64" + else: + return "UnsignedLong" + elif NumpyType == numpy.int64: + return "Signed64" + elif NumpyType == numpy.uint64: + return "Unsigned64" + elif NumpyType in ["f", numpy.float32]: + return "FloatValue" + elif NumpyType in ["d", numpy.float64]: + return "DoubleValue" + else: + raise TypeError("unknown NumpyType %s" % NumpyType) + + def __GetSizeNumpyType__(self, NumpyType): + """Internal method: returns size of NumPy's Array Types""" + if NumpyType in ["b", numpy.int8]: + return 1 + elif NumpyType in ["B", numpy.uint8]: + return 1 + elif NumpyType in ["h", numpy.int16]: + return 2 + elif NumpyType in ["H", numpy.uint16]: + return 2 + elif NumpyType in ["i", numpy.int32]: + return 4 + elif NumpyType in ["I", numpy.uint32]: + return 4 + elif NumpyType == "l": + if sys.platform == "linux2": + return 8 # 64 bit + else: + return 4 # 32 bit + elif NumpyType == "L": + if sys.platform == "linux2": + return 8 # 64 bit + else: + return 4 # 32 bit + elif NumpyType in ["f", numpy.float32]: + return 4 + elif NumpyType in ["d", numpy.float64]: + return 8 + elif NumpyType == "Q": + return 8 # unsigned 64 in 32 bit + elif NumpyType == "q": + return 8 # signed 64 in 32 bit + elif NumpyType == numpy.uint64: + return 8 + elif NumpyType == numpy.int64: + return 8 + else: + raise TypeError("unknown NumpyType %s" % NumpyType) + + def __SetDataType__(self, Array, DataType): + """Internal method: array type convertion""" + # AVOID problems not using FromEdfType= Array.dtype.char + FromEdfType = Array.dtype + ToEdfType = self.__GetDefaultNumpyType__(DataType) + if ToEdfType != FromEdfType: + aux = Array.astype(self.__GetDefaultNumpyType__(DataType)) + return aux + return Array + + def __del__(self): + try: + self.__makeSureFileIsClosed() + except: + pass + + def GetDefaultNumpyType(self, EdfType, index=None): + """Returns NumPy type according Edf type""" + if index is None: + return GetDefaultNumpyType(EdfType) + EdfType = EdfType.upper() + if EdfType in ["SIGNED64"]: + return numpy.int64 + if EdfType in ["UNSIGNED64"]: + return numpy.uint64 + if EdfType in ["SIGNEDLONG", "UNSIGNEDLONG"]: + dim1 = 1 + dim2 = 1 + dim3 = 1 + if hasattr(self.Images[index], "Dim1"): + dim1 = self.Images[index].Dim1 + if hasattr(self.Images[index], "Dim2"): + dim2 = self.Images[index].Dim2 + if dim2 <= 0: + dim2 = 1 + if hasattr(self.Images[index], "Dim3"): + dim3 = self.Images[index].Dim3 + if dim3 <= 0: + dim3 = 1 + if hasattr(self.Images[index], "Size"): + size = self.Images[index].Size + if size / (dim1 * dim2 * dim3) == 8: + if EdfType == "UNSIGNEDLONG": + return numpy.uint64 + else: + return numpy.int64 + if EdfType == "UNSIGNEDLONG": + return numpy.uint32 + else: + return numpy.int32 + + return GetDefaultNumpyType(EdfType) + + +def GetDefaultNumpyType(EdfType): + """Returns NumPy type according Edf type""" + EdfType = EdfType.upper() + if EdfType == "SIGNEDBYTE": + return numpy.int8 # "b" + elif EdfType == "UNSIGNEDBYTE": + return numpy.uint8 # "B" + elif EdfType == "SIGNEDSHORT": + return numpy.int16 # "h" + elif EdfType == "UNSIGNEDSHORT": + return numpy.uint16 # "H" + elif EdfType == "SIGNEDINTEGER": + return numpy.int32 # "i" + elif EdfType == "UNSIGNEDINTEGER": + return numpy.uint32 # "I" + elif EdfType == "SIGNEDLONG": + return numpy.int32 # "i" #ESRF acquisition is made in 32bit + elif EdfType == "UNSIGNEDLONG": + return numpy.uint32 # "I" #ESRF acquisition is made in 32bit + elif EdfType == "SIGNED64": + return numpy.int64 # "l" + elif EdfType == "UNSIGNED64": + return numpy.uint64 # "L" + elif EdfType == "FLOATVALUE": + return numpy.float32 # "f" + elif EdfType == "FLOAT": + return numpy.float32 # "f" + elif EdfType == "DOUBLEVALUE": + return numpy.float64 # "d" + else: + raise TypeError("unknown EdfType %s" % EdfType) + + +def SetDictCase(Dict, Case, Flag): + """Returns dictionary with keys and/or values converted into upper or lowercase + Dict: input dictionary + Case: LOWER_CASE, UPPER_CASE + Flag: KEYS, VALUES or KEYS | VALUES + """ + newdict = {} + for i in Dict.keys(): + newkey = i + newvalue = Dict[i] + if Flag & KEYS: + if Case == LOWER_CASE: + newkey = newkey.lower() + else: + newkey = newkey.upper() + if Flag & VALUES: + if Case == LOWER_CASE: + newvalue = newvalue.lower() + else: + newvalue = newvalue.upper() + newdict[newkey] = newvalue + return newdict + + +def GetRegion(Arr, Pos, Size): + """Returns array with refion of Arr. + Arr must be 1d, 2d or 3d + Pos and Size are tuples in the format (x) or (x,y) or (x,y,z) + Both parameters must have the same size as the dimention of Arr + """ + Dim = len(Arr.shape) + if len(Pos) != Dim: + return None + if len(Size) != Dim: + return None + + if Dim == 1: + SizeX = Size[0] + if SizeX == 0: + SizeX = Arr.shape[0] - Pos[0] + ArrRet = numpy.take(Arr, range(Pos[0], Pos[0] + SizeX)) + elif Dim == 2: + SizeX = Size[0] + SizeY = Size[1] + if SizeX == 0: + SizeX = Arr.shape[1] - Pos[0] + if SizeY == 0: + SizeY = Arr.shape[0] - Pos[1] + ArrRet = numpy.take(Arr, range(Pos[1], Pos[1] + SizeY)) + ArrRet = numpy.take(ArrRet, range(Pos[0], Pos[0] + SizeX), 1) + elif Dim == 3: + SizeX = Size[0] + SizeY = Size[1] + SizeZ = Size[2] + if SizeX == 0: + SizeX = Arr.shape[2] - Pos[0] + if SizeY == 0: + SizeX = Arr.shape[1] - Pos[1] + if SizeZ == 0: + SizeZ = Arr.shape[0] - Pos[2] + ArrRet = numpy.take(Arr, range(Pos[2], Pos[2] + SizeZ)) + ArrRet = numpy.take(ArrRet, range(Pos[1], Pos[1] + SizeY), 1) + ArrRet = numpy.take(ArrRet, range(Pos[0], Pos[0] + SizeX), 2) + else: + ArrRet = None + return ArrRet + + +if __name__ == "__main__": + if 1: + a = numpy.zeros((5, 10)) + for i in range(5): + for j in range(10): + a[i, j] = 10 * i + j + edf = EdfFile("armando.edf", access="ab+") + edf.WriteImage({}, a) + del edf # force to close the file + inp = EdfFile("armando.edf") + b = inp.GetData(0) + out = EdfFile("armando2.edf") + out.WriteImage({}, b) + del out # force to close the file + inp2 = EdfFile("armando2.edf") + c = inp2.GetData(0) + print("A SHAPE = ", a.shape) + print("B SHAPE = ", b.shape) + print("C SHAPE = ", c.shape) + for i in range(5): + print("A", a[i, :]) + print("B", b[i, :]) + print("C", c[i, :]) + + x = numpy.arange(100) + x.shape = 5, 20 + for item in [ + "SignedByte", + "UnsignedByte", + "SignedShort", + "UnsignedShort", + "SignedLong", + "UnsignedLong", + "Signed64", + "Unsigned64", + "FloatValue", + "DoubleValue", + ]: + fname = item + ".edf" + if os.path.exists(fname): + os.remove(fname) + towrite = EdfFile(fname) + towrite.WriteImage({}, x, DataType=item, Append=0) + sys.exit(0) + + # Creates object based on file exe.edf + exe = EdfFile("images/test_image.edf") + x = EdfFile("images/test_getdata.edf") + # Gets unsigned short data, storing in an signed long + arr = exe.GetData(0, Pos=(100, 200), Size=(200, 400)) + x.WriteImage({}, arr, 0) + + arr = exe.GetData(0, Pos=(100, 200)) + x.WriteImage({}, arr) + + arr = exe.GetData(0, Size=(200, 400)) + x.WriteImage({}, arr) + + arr = exe.GetData(0) + x.WriteImage({}, arr) + + sys.exit() + + # Creates object based on file exe.edf + exe = EdfFile("images/.edf") + + # Creates long array , filled with 0xFFFFFFFF(-1) + la = numpy.zeros((100, 100)) + la = la - 1 + + # Creates a short array, filled with 0xFFFF + sa = numpy.zeros((100, 100)) + sa = sa + 0xFFFF + sa = sa.astype("s") + + # Writes long array, initializing file (append=0) + exe.WriteImage({}, la, 0, "") + + # Appends short array with new header items + exe.WriteImage({"Name": "Alexandre", "Date": "16/07/2001"}, sa) + + # Appends short array, in Edf type unsigned + exe.WriteImage({}, sa, DataType="UnsignedShort") + + # Appends short array, in Edf type unsigned + exe.WriteImage({}, sa, DataType="UnsignedLong") + + # Appends long array as a double, considering unsigned + exe.WriteImage({}, la, DataType="DoubleValue", WriteAsUnsigened=1) + + # Gets unsigned short data, storing in an signed long + ushort = exe.GetData(2, "SignedLong") + + # Makes an operation + ushort = ushort - 0x10 + + # Saves Result as signed long + exe.WriteImage({}, ushort) + + # Saves in the original format (unsigned short) + OldHeader = exe.GetStaticHeader(2) + exe.WriteImage({}, ushort, 1, OldHeader["DataType"]) |