From f14d8882e6f8eb1b393220907e11879d5c4e9da5 Mon Sep 17 00:00:00 2001 From: Daniel Toyra <dtoyra@star.sr.bham.ac.uk> Date: Wed, 12 Aug 2015 19:00:33 +0100 Subject: [PATCH] Added function that reads the binary metroPro files --- pykat/optics/maps.py | 224 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 191 insertions(+), 33 deletions(-) diff --git a/pykat/optics/maps.py b/pykat/optics/maps.py index 4e7de57..1c427e6 100644 --- a/pykat/optics/maps.py +++ b/pykat/optics/maps.py @@ -399,6 +399,24 @@ class surfacemap(object): return fig + def rescaleData(self,scaling): + ''' + Rescaling data. Assumes that the current map is scaled correctly, i.e., + surfacemap.scaling*surfacemap.data would give the data in meters. + + Input: scaling + scaling - float number that representing the new scaling. E.g., + scaling = 1.0e-9 means that surfacemap.data is converted to + being given in nano meters. + ''' + if isinstance(scaling, float): + self.data[self.notNan] = self.data[self.notNan]*self.scaling/scaling + self.scaling = scaling + else: + print('Error: Scaling factor must be of type float, {:s} found'. + format(type(scaling))) + + def rms(self, w=None): if w is None: return math.sqrt((self.data[self.notNan]**2).sum())/self.notNan.sum() @@ -1821,44 +1839,186 @@ def read_map(filename, mapFormat='finesse', scaling=1.0e-9): # map with surfacemap.plot(). data[isNan] = 0 - return surfacemap(name, maptype, size, center, step, scaling, data, notNan) + return surfacemap(name, maptype, (0,0), center, step, scaling, data, notNan) + + if mapFormat == 'metroPro': + # Remove '.dat' for output name + name = filename.split('.')[0] + # Reading file + data,hData,x1,y1 = readMetroProData(filename) + # xy-stepsize in meters + stepSize = (hData['cameraRes'], hData['cameraRes']) + # Temporary center + center = ((len(data[0,:])-1)/2.0,(len(data[:,0])-1)/2.0) + # Keeping track of nan's + isNan = np.isnan(data) + notNan = isNan==False + data[isNan] = 0 + # Change this: + mType = 'phase' + fType = 'both' + maptype = ' '.join([mType, fType]) + # Unnecessary size, remove from class later + size = data.shape[::-1] + smap = surfacemap(name, maptype, size, center, stepSize, 1.0, data, notNan) + smap.rescaleData(scaling) + smap.recenter() - # TODO: Add options for reading virgo maps, and .xyz zygo - # maps (need .xys file for this). Binary ligo-maps? - # The intensity data is not used to anything here. Remove - # or add to pykat? - + return smap +def readMetroProData(filename): + ''' + Reading the metroPro binary data files. + + Translated from 'LoadMetroProData.m' by Hiro Yamamoto. + ''' + f = BinaryReader(filename) + # Read header + hData = readHeaderMP(f) + if hData['format'] < 0: + print('Error: Format unknown to readMetroProData()\nfilename: {:s}'.format(filename)) + return 0 + # Read phase map data + # Skipping header and intensity data + f.seek(hData['size']+hData['intNBytes']) + # Reading data + dat = f.read('int32',size=hData['Nx']*hData['Ny']) + # Marking unmeasured data as NaN + dat[dat >= hData['invalid']] = np.nan + # Scale data to meters + dat = dat*hData['convFactor'] + # Reshaping into Nx * Ny matrix + dat = dat.reshape(hData['Ny'], hData['Nx']) + # Flipping up/down, i.e., change direction of y-axis. + dat = dat[::-1,:] + # Auxiliary data to return + dxy = hData['cameraRes'] + + print(dxy) + x1 = dxy * np.arange( -(len(dat[0,:])-1)/2, (len(dat[0,:])-1)/2 + 1) + y1 = dxy * np.arange( -(len(dat[:,0])-1)/2, (len(dat[:,1])-1)/2 + 1) -## def rnm(n,m,rho): -## ''' -## Based on 'FT_Rnm.m'. -## Calculates radial part of the Zernike polynomial for given n and m, and rho. -## n - Radial coordinate -## m - Azimuthal coordinate -## rho - Matrix with normalised radial coordinates, i.e., rho[i,j] <= 1. -## ''' + return dat, hData, x1, y1 + + +def readHeaderMP(f): + ''' + Reads header of the metroPro format files. -## m = abs(m) -## Rnm = 0 -## # If n<=25 we use original formula, otherwise recurrence formula as factorials lead -## # to very large numbers. -## if n<=25: -## # Radial term -## S = int((n-m)/2) -## for k in range(S+1): -## a = ((-1)**k)*math.factorial(n-k)/\ -## (math.factorial(k)*math.factorial((n+m)/2.0-k)*math.factorial((n-m)/2.0-k)) -## p = a*rho**(n-2*k) -## Rnm = Rnm+p -## else: -## # Use recurrence formula -## pass + Translated from the readerHeader() function within the + 'LoadMetroProData.m' function written by Hiro Yamamoto. + ''' + hData = {} + hData['magicNum'] = f.read('uint32') + hData['format'] = f.read('int16') + hData['size'] = f.read('int32') + # Check if the magic string and format are known ones. + if not (hData['format'] >= 1 and hData['format']<=3 and + hData['magicNum']-hData['format'] == int('881B036E',16)): + hData['format'] = -1 + # Read necessary data + hData['invalid'] = int('7FFFFFF8',16) + f.seek(60) + # Intensity data, which we will skip over. + hData['intNBytes'] = f.read('int32') + # Top-left coordinates, which are useless. + hData['X0'] = f.read('int16') + hData['Y0'] = f.read('int16') + # Number of data points alng x and y + hData['Nx'] = f.read('int16') + hData['Ny'] = f.read('int16') + # Total data, 4*Nx*Ny + hData['phaNBytes'] = f.read('int32') + f.seek(218) + # Scale factor determined by phase resolution tag + phaseResTag = f.read('int16') + if phaseResTag == 0: + phaseResVal = 4096 + elif phaseResTag == 1: + phaseResVal = 32768 + elif phaseResTag == 2: + phaseResVal = 131072 + else: + phaseResVal = 0 + f.seek(164) + intf_scale_factor = f.read('float') + hData['waveLength'] = f.read('float') + f.seek(176) + obliquity_factor = f.read('float') + # Eq. in p12-6 in MetroPro Reference Guide + hData['convFactor'] = intf_scale_factor*obliquity_factor*\ + hData['waveLength']/phaseResVal + # Bin size of each measurement + f.seek(184) + hData['cameraRes'] = f.read('float') -## return Rnm - + return hData + + +import struct + +class BinaryReaderEOFException(Exception): + def __init__(self): + pass + def __str__(self): + return 'Not enough bytes in file to satisfy read request' + +class BinaryReader: + # Map well-known type names into struct format characters. + typeNames = { + 'int8' :'b', + 'uint8' :'B', + 'int16' :'h', + 'uint16' :'H', + 'int32' :'i', + 'uint32' :'I', + 'int64' :'q', + 'uint64' :'Q', + 'float' :'f', + 'double' :'d', + 'char' :'s'} + + def __init__(self, fileName): + self.file = open(fileName, 'rb') + + def read(self, typeName, size=None): + typeFormat = BinaryReader.typeNames[typeName.lower()] + typeFormat = '>'+typeFormat + typeSize = struct.calcsize(typeFormat) + if size is None: + value = self.file.read(typeSize) + if typeSize != len(value): + raise BinaryReaderEOFException + unpacked = struct.unpack(typeFormat, value)[0] + else: + value = self.file.read(size*typeSize) + if size*typeSize != len(value): + raise BinaryReaderEOFException + unpacked = np.zeros(size) + for k in range(size): + i = k*typeSize + unpacked[k] = struct.unpack(typeFormat,value[i:i+typeSize])[0] + return unpacked + + def seek(self, offset, refPos=0): + ''' + offset in bytes and refPos gives reference position, where 0 + means origin of the file, 1 uses current position and 2 uses + the end of the file. + ''' + self.file.seek(offset, refPos) + + + def __del__(self): + self.file.close() + +# TODO: Add options for reading virgo maps, and .xyz zygo + # maps (need .xys file for this). Binary ligo-maps? + # The intensity data is not used to anything here. Remove + # or add to pykat? + @@ -1870,5 +2030,3 @@ def read_map(filename, mapFormat='finesse', scaling=1.0e-9): # [map3,x_tilt,y_tilt,offset2]=FT_remove_piston_from_mirror_map(map2,w, display_style); # map3=FT_invert_mirror_map(map3, invert); -# Understand the internal coordinate system of the -# maps/matrices. -- GitLab