#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ ####################################### #### LSPyIV --TRANSFORMATION MODULE ####################################### Python LSPIV tools Contains all definitions/functions that deals with transformations between coordinate systems (image/world) """ import numpy as np import os from tkinter import Tk from tkinter.filedialog import askopenfilename class GRP: """Groud Reference Points class""" def __init__(self): # World coordinates self.X = -99.99 self.Y = -99.99 self.Z = -99.99 # Image coordinates self.i = -99 self.j = -99 class imgRef: """Image References for transformations""" def __init__(self): self.xyMin = (-99.99,-99.99) self.xyMax = (-99.99,-99.99) self.res = -99.99 self.ninj = (-99,-99) # Size of ORIGINAL image class camera: """Camera class. contains many informations that could also be used for other purposes""" def __init__(self): self.position = [] # usually 3-D vector (numpy array) self.angles = [] # usually 3D vector (numpy array) self.intrinsic = {} # dict containing intrinsic parameters self.scale = -99 # used for orthophotographic camera (equals to zoom factor) self.fps = -99 self.resolution = -99 # resolution of the camera (usually in m/pix or pix/m) self.resolutionInfo = "" # enter here the unit of resolution to avoid confusions self.imagesize = () # size of image (tuple) self.a = [] # a matrix from DLT, used to convert world to image space self.a_inv = [] # a_inv matrix, used to convert from image to world coordinates self.xy0 = () # Position of lowest image corner in world unit def read_a(self,filename = ""): """ Reads the file containing the a matrix for transformation between space and image coodinate s Parameters ---------- filename : [str], optional Path to the file 'coeff.dat'. If "" then a GUI will be opened to select file The default is "". Returns ------- - errno [int] Error number. Possible values : - 0 - no errors - 1 - wrong format contains file coeff.dat """ # ---- Initialisation isExist = os.path.exists(filename) # Check if file exists if (filename == "") or (isExist == False): # Open GUI to find file - tkinker lib is used Tk().withdraw() filename = askopenfilename(title = "Select coeff.dat", filetypes = (("coeff file","coeff.dat"),("all files","*.*"))) # Get file path if (filename[-4:] != ".dat" ): print("ERROR : file format doesn't match\n") return 1 # ---- Read coeff.dat fCoeff = open(filename,"r") lstExtract = fCoeff.read() lstExtract = lstExtract.strip() lstExtract = lstExtract.split("\n") fCoeff.close() if(int(lstExtract[0]) == 11): #------------------- self.a = np.zeros(11,dtype = float) # from verif_ortho.f90 self.a[0] = np.float(lstExtract[1]) self.a[1] = np.float(lstExtract[2]) self.a[2] = np.float(lstExtract[3]) self.a[3] = np.float(lstExtract[4]) self.a[4] = np.float(lstExtract[8]) self.a[5] = np.float(lstExtract[9]) self.a[6] = np.float(lstExtract[10]) self.a[7] = np.float(lstExtract[11]) self.a[8] = np.float(lstExtract[5]) self.a[9] = np.float(lstExtract[6]) self.a[10] = np.float(lstExtract[7]) else: print("ERROR: wrong format from file 'coeff.dat'\n") return 1 return 0 def read_a_inv(self,filename = ""): """ Reads the file containing the a_inv matrix for transformation between space and image coodinate s Parameters ---------- filename : [str], optional Path to the file 'coeff.dat'. If "" then a GUI will be opened to select file The default is "". Returns ------- - errno [int] Error number. Possible values : - 0 - no errors - 1 - wrong format contains file coeff.dat """ # ---- Initialisation isExist = os.path.exists(filename) # Check if file exists if (filename == "") or (isExist == False): # Open GUI to find file - tkinker lib is used Tk().withdraw() filename = askopenfilename(title = "Select coeff_inv.dat", filetypes = (("coeff_inv file","coeff_inv.dat"),("all files","*.*"))) # Get file path if (filename[-4:] != ".dat" ): print("ERROR : file format doesn't match\n") return 1 # ---- Read coeff.dat fVerif = open(filename,"r") # Burn first line (header) ligne = fVerif.readline() # Initialize arrays ligne = fVerif.readline() extract = ligne[:-1].split(' ') fVerif.close() # Arrange extracted data extract = [nb for nb in extract if nb!=''] self.a_inv = np.array(extract,dtype = np.float64) return 0 def image2space(self,imgIndx,xy0 = True,ni = True): """ Converts image indexes to world units with information of the camera and xy0. Assume that the input image is already ortho-rectified (i.e. image can be assumed to be a plan XY) Parameters ---------- - imgIndx : [npArrayInt] Indexes of the images to be transformed. --> shape : [(ind),(i,j)] WARNING : image coordinate are expected. Values from Fudaa-LSPIV are usually given in a mathematical coordinate space with (0,0) located down left So the i index need to be "invert" --> correctiIndex = imageSize(i) - iIndexFudaa - camera : [lspiv.transform.camera()] Contains the information of the camera - xyz0 : [npArrayFloat] Offset (in meters) apply on the values X, Y for transformation Returns ------- - SpaceCoord [npArrayFloat] X,Y values in meters - errno [int] In case of an error this function returns an integer - 1 - Information missing on the camera : no resolution and no scaling - 2 - Image isn't squarred, problem with scaling... - 3 - ResolutionInfo of the camera isn't recognize """ if self.resolutionInfo == "": # Case no resolution is entered, use simple scaling (typical blender case with othographic camera) if self.scale == -99: print("ERROR : Information missing on the camera : no resolution and no scaling") return 1 # Image size is assumed to be squared if self.imagesize[0]!=self.imagesize[1]: print("ERROR : Image isn't squarred, problem with scaling...") return 1 res = self.imagesize[0] / self.scale elif self.resolutionInfo == "m/pix": # Case resolution is given in meter/pixels (typical units from Fudaa-LSPIV project) res = 1/self.resolution elif self.resolutionInfo == "pix/m": # Case resolution is given in pixels/meter res = self.resolution else: print("ERROR : resolution information of the camera isn't recognize. Expected : 'm/pix' or 'pix/m'") return 3 # If no xy0 are passed to the function they're calculated from the camera data if isinstance(xy0,bool) and self.scale != -99: xy0 = (self.position - (0.5*self.scale))[:2] if len(imgIndx)==0: print("WARNING : imgIndx is empty...") # Swap column to get (j,i) as j is x and i is y if ni == True: ni = np.max(imgIndx[:,0]) JIimgIndx = np.array([imgIndx[:,1],ni-imgIndx[:,0]]).T out = (JIimgIndx/res) + xy0 return out def velSpace2dispImage(self,spaceVel,xy0 = np.nan): """ Converts image indexes to world units with information of the camera and xy0. Assume that the input image is already ortho-rectified (i.e. image can be assumed to be a plan XY) Parameters ---------- - imgIndx : [npArrayInt] Indexes of the images to be transformed. --> shape : [(ind),(i,j)] WARNING : image coordinate are expected. Values from Fudaa-LSPIV are usually given in a mathematical coordinate space with (0,0) located down left So the i index need to be "invert" --> correctiIndex = imageSize(i) - iIndexFudaa - camera : [lspiv.transform.camera()] Contains the information of the camera - xyz0 : [npArrayFloat] Offset (in meters) apply on the values X, Y for transformation Returns ------- - SpaceCoord [npArrayFloat] X,Y values in meters - errno [int] In case of an error this function returns an integer - 1 - Information missing on the camera : no resolution and no scaling - 2 - Image isn't squarred, problem with scaling... - 3 - ResolutionInfo of the camera isn't recognize """ # if isinstance(camera,transform.camera): # print("\nERROR : wrong format of camera passed to the function. Expected : lspiv.transform.camera ") # return 1 if self.resolutionInfo == "": # Case no resolution is entered, use simple scaling (typical blender case with othographic camera) if self.scale == -99: print("\nERROR : Information missing on the camera : no resolution and no scaling") return 1 # Image size is assumed to be squared if self.imagesize[0]!=self.imagesize[1]: print("\nERROR : Image isn't squarred, problem with scaling...") return 1 res = self.imagesize[0] / self.scale elif self.resolutionInfo == "m/pix": # Case resolution is given in meter/pixels (typical units from Fudaa-LSPIV project) res = 1/self.resolution elif self.resolutionInfo == "pix/m": # Case resolution is given in pixels/meter res = self.resolution else: print("\nERROR : resolution information of the camera isn't recognize. Expected : 'm/pix' or 'pix/m'") return 3 # If no xy0 are passed to the function they're calculated from the camera data if np.isnan(xy0): xy0 = (self.position - (0.5*self.scale))[:2] if len(spaceVel)==0: print("\nWARNING : Values are empty...") # Compute displacement based on camera info XYdisp = spaceVel / self.fps JIspaceV = (XYdisp)*res # Swap column to get (i,j) as x is j and y is i IJindex = np.array([JIspaceV[:,1],JIspaceV[:,0]]).T # Invert i ax IJindex[:,0] = -IJindex[:,0] return IJindex ################### #### FUNCTIONS ################### def read_a(filename = ""): """ Reads the file containing the a matrix for transformation between space and image coodinate s Parameters ---------- filename : [str], optional Path to the file 'coeff.dat'. If "" then a GUI will be opened to select file The default is "". Returns ------- - errno [int] Error number. Possible values : - 0 - no errors - 1 - wrong format contains file coeff.dat """ # ---- Initialisation isExist = os.path.exists(filename) # Check if file exists if (filename == "") or (isExist == False): # Open GUI to find file - tkinker lib is used Tk().withdraw() filename = askopenfilename(title = "Select coeff.dat", filetypes = (("coeff file","coeff.dat"),("all files","*.*"))) # Get file path if (filename[-4:] != ".dat" ): print("ERROR : file format doesn't match\n") return 1 # ---- Read coeff.dat fCoeff = open(filename,"r") lstExtract = fCoeff.read() lstExtract = lstExtract.strip() lstExtract = lstExtract.split("\n") fCoeff.close() if(int(lstExtract[0]) == 11): #------------------- a = np.zeros(11,dtype = float) # from verif_ortho.f90 a[0] = np.float(lstExtract[1]) a[1] = np.float(lstExtract[2]) a[2] = np.float(lstExtract[3]) a[3] = np.float(lstExtract[4]) a[4] = np.float(lstExtract[8]) a[5] = np.float(lstExtract[9]) a[6] = np.float(lstExtract[10]) a[7] = np.float(lstExtract[11]) a[8] = np.float(lstExtract[5]) a[9] = np.float(lstExtract[6]) a[10] = np.float(lstExtract[7]) else: print("ERROR: wrong format from file 'coeff.dat'\n") return 1 return a def read_a_inv(filename = ""): """ Reads the file containing the a_inv matrix for transformation between space and image coodinate s Parameters ---------- filename : [str], optional Path to the file 'coeff.dat'. If "" then a GUI will be opened to select file The default is "". Returns ------- - errno [int] Error number. Possible values : - 0 - no errors - 1 - wrong format contains file coeff.dat """ # ---- Initialisation isExist = os.path.exists(filename) # Check if file exists if (filename == "") or (isExist == False): # Open GUI to find file - tkinker lib is used Tk().withdraw() filename = askopenfilename(title = "Select coeff_inv.dat", filetypes = (("coeff_inv file","coeff_inv.dat"),("all files","*.*"))) # Get file path if (filename[-4:] != ".dat" ): print("ERROR : file format doesn't match\n") return 1 # ---- Read coeff.dat fVerif = open(filename,"r") # Burn first line (header) ligne = fVerif.readline() # Initialize arrays ligne = fVerif.readline() extract = ligne[:-1].split(' ') fVerif.close() # Arrange extracted data extract = [nb for nb in extract if nb!=''] a_inv = np.array(extract,dtype = np.float64) return a_inv # Space coordinates to image coordinate (from Fudaa-LSPIV source) def space2CRT(a,x,y,h): xp=((a[0]*x)+(a[1]*y)+(a[2]*h+a[3])) / ((a[8]*x)+(a[9]*y)+(a[10]*h+1)) yp=((a[4]*x)+(a[5]*y)+((a[6]*h)+a[7])) / ((a[8]*x)+(a[9]*y)+(a[10]*h+1)) return (yp,xp) # Image to space coordinate def CRT2space(a_inv,i,j): x = (a_inv[0]*j+a_inv[1]*i+a_inv[2]) / (a_inv[3]*j+a_inv[4]*i+a_inv[5]) y = (a_inv[6]*j+a_inv[7]*i+a_inv[8]) / (a_inv[3]*j+a_inv[4]*i+a_inv[5]) return (x,y) def CRT2space2D(a_inv,i,j): x = (a_inv[0]*j+a_inv[1]*i+a_inv[2]) / (a_inv[6]*j+a_inv[7]*i+1) y = (a_inv[3]*j+a_inv[4]*i+a_inv[5]) / (a_inv[6]*j+a_inv[7]*i+1) return (x,y) def read_grid(filename = ""): """ Reads grid.dat file and arrange it following the indexing order. Parameters ---------- filename : [str], optional Path to the file 'coeff.dat'. If "" then a GUI will be opened to select file The default is "". indexing : [str], optional Defines the order used to store data. - "ij" : traditionnal ij indexing (used for images) - "raw": Fudaa-LSPIV indexing (raw values in grid.dat) Returns ------- - errno [int] Error number. Possible values : - 0 - no errors - 1 - wrong format contains file coeff.dat """ # ---- Initialisation isExist = os.path.exists(filename) # Check if file exists if (filename == "") or (isExist == False): # Open GUI to find file - tkinker lib is used Tk().withdraw() filename = askopenfilename(title = "Select grid.dat", filetypes = (("grid file","grid.dat"),("all files","*.*"))) # Get file path if (filename[-4:] != ".dat" ): print("ERROR : file format doesn't match\n") return 1 # ---- Read coeff.dat fGrid = open(filename,"r") lstExtract = fGrid.read() lstExtract = lstExtract.strip() lstExtract = lstExtract.split("\n") fGrid.close() i,j = [],[] # Extract fi,fj (fudaa i and fudaa j) for el in lstExtract: fj,fi = el.split() i.append(int(fi)) j.append(int(fj)) return i,j def bicubic_at(ip,jp,im): i = int(ip) j = int(jp) sum_fx = 0 for k in range(4): for l in range(4): sx = abs(j+k-2-jp) sy = abs(i+l-2-ip) if ((sx >= 0) and (sx < 1)): #Cx = un - deux*(sx*sx) + (sx*sx*sx) Cx = 1. + (sx-2.)*sx*sx elif ((sx >= 1) and (sx < 2)): #Cx = four - huit*sx + cinq*(sx*sx) - (sx*sx*sx) Cx = 4. + ((5. - sx)*sx - 8.)*sx elif (sx > 2): Cx = 0. if ((sy >= 0) and (sy < 1)): #Cy = un - deux*(sy*sy) + (sy*sy*sy) Cy = 1 + (sy-2)*sy*sy elif ((sy >= 1) and (sy < 2)): #!Cy = four - huit*sy + cinq*(sy*sy) - (sy*sy*sy) Cy = 4 + ((5. -sy)*sy - 8.)*sy elif (sy > 2): Cy = 0. fx = im[i+l-1,j+k-1]*Cx*Cy sum_fx = sum_fx+fx if (int(sum_fx) > 255): sum_fx = 255 if (int(sum_fx) < 0): sum_fx = 0 return int(sum_fx) def readGRPtable(filename): # Open GRP file try: f = open(filename,"r") buff = f.read() f.close() except Exception as e: print("ERROR reading file coeff.dat : " + str(e)) return 1 # Get number of GRP i0 = buff.find("\n") i1 = i0 + buff[i0+1:].find("\n") nbGRP = int(buff[i0+1:i1+1]) # Read file if0 = buff.find("j") tmp = np.array(buff[if0+2:].split(),float) # Re-arrange data lstGRP = tmp.reshape((nbGRP,5)) return lstGRP def writeGRPtable(GRP,filename,ni,nj): buff = "GRP V2.0 {} {}\n".format(int(ni),int(nj)) buff += "{}\nX\tY\tZ\ti\tj\n".format(int(np.shape(GRP)[0])) for el in GRP: buff += "%.3f\t%.3f\t%.3f\t%d\t%d\n"%(el[0],el[1],el[2],int(el[3]),int(el[4])) # Open GRP file try: f = open(filename,"w") f.write(buff) f.close() except Exception as e: print("ERROR reading file coeff.dat : " + str(e)) return 1 return 0