├── .gitignore ├── sample_data ├── cbox_rgb_nr-240_nc-320.exr ├── cbox_rgb_nr-240_nc-320.npy └── cbox_depthmap_nr-240_nc-320.npy ├── Timer.py ├── VisualizeCodingScheme.py ├── LICENSE ├── Decoding.py ├── tofenv.yml ├── README.md ├── ToFSinglePixel.py ├── UtilsPlot.py ├── simple_tofsim.py ├── Utils.py ├── CalculateCodingSchemeMeanExpectedDepthError.py └── CodingFunctions.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | __pycache__/ -------------------------------------------------------------------------------- /sample_data/cbox_rgb_nr-240_nc-320.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipegb94/ToFSim/HEAD/sample_data/cbox_rgb_nr-240_nc-320.exr -------------------------------------------------------------------------------- /sample_data/cbox_rgb_nr-240_nc-320.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipegb94/ToFSim/HEAD/sample_data/cbox_rgb_nr-240_nc-320.npy -------------------------------------------------------------------------------- /sample_data/cbox_depthmap_nr-240_nc-320.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipegb94/ToFSim/HEAD/sample_data/cbox_depthmap_nr-240_nc-320.npy -------------------------------------------------------------------------------- /Timer.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class Timer(object): 4 | def __init__(self, name=None): 5 | self.name = name 6 | def __enter__(self): 7 | self.tstart = time.time() 8 | def __exit__(self, type, value, traceback): 9 | if self.name: 10 | print('[{}] - Elapsed: {} seconds.'.format(self.name, time.time() - self.tstart)) 11 | else: 12 | print('Elapsed: {}'.format(time.time() - self.tstart)) 13 | 14 | 15 | -------------------------------------------------------------------------------- /VisualizeCodingScheme.py: -------------------------------------------------------------------------------- 1 | 2 | import CodingFunctions 3 | import UtilsPlot 4 | 5 | N = 1000 6 | K = 4 7 | # (ModFs,DemodFs) = CodingFunctions.GetCosCos(N = N, K = K) 8 | # (ModFs,DemodFs) = CodingFunctions.GetHamK3(N = N) 9 | (ModFs,DemodFs) = CodingFunctions.GetHamK4(N = N) 10 | # (ModFs,DemodFs) = CodingFunctions.GetHamK5(N = N) 11 | # (ModFs,DemodFs) = CodingFunctions.GetMultiFreqCosK5(N = N) 12 | 13 | 14 | UtilsPlot.PlotCodingScheme(ModFs,DemodFs) 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Felipe Gutierrez-Barragan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Decoding.py: -------------------------------------------------------------------------------- 1 | """Decoding 2 | Decoding functions for time of flight coding schemes. 3 | """ 4 | #### Python imports 5 | 6 | #### Library imports 7 | import numpy as np 8 | from IPython.core import debugger 9 | breakpoint = debugger.set_trace 10 | 11 | #### Local imports 12 | import Utils 13 | 14 | def DecodeXCorr(b_measurements, norm_corrfs): 15 | """DecodeXCorr: Generic decoding algorithm that performs a 1D search on the normalized 16 | correlation functions. 17 | 18 | Args: 19 | b_measurements (np.ndarray): B x K matrix. B sets of K brightness measurements 20 | norm_corrfs (np.ndarray): N x K matrix. Normalized Correlation functions. Zero mean 21 | unit variance. 22 | Returns: 23 | np.array: decoded_depths 24 | """ 25 | (n_measurements, k) = b_measurements.shape 26 | ## Normalize Brightness Measurements functions 27 | norm_b_measurements = Utils.NormalizeBrightnessVals(b_measurements) 28 | ## Calculate the cross correlation for every measurement and the maximum one will be the depth 29 | decoded_depths = np.argmax(np.dot(norm_corrfs, norm_b_measurements.transpose()), axis=0) 30 | 31 | return decoded_depths -------------------------------------------------------------------------------- /tofenv.yml: -------------------------------------------------------------------------------- 1 | name: tofenv 2 | channels: 3 | - defaults 4 | dependencies: 5 | - backcall=0.1.0=py35_0 6 | - blas=1.0=mkl 7 | - ca-certificates=2019.1.23=0 8 | - certifi=2018.8.24=py35_1 9 | - cycler=0.10.0=py35hc4d5149_0 10 | - dbus=1.13.6=h746ee38_0 11 | - decorator=4.3.0=py35_0 12 | - expat=2.2.6=he6710b0_0 13 | - fontconfig=2.13.0=h9420a91_0 14 | - freetype=2.9.1=h8a8886c_1 15 | - glib=2.56.2=hd408876_0 16 | - gst-plugins-base=1.14.0=hbbd80ab_1 17 | - gstreamer=1.14.0=hb453b48_1 18 | - icu=58.2=h9c2bf20_1 19 | - intel-openmp=2019.1=144 20 | - ipython=6.5.0=py35_0 21 | - ipython_genutils=0.2.0=py35hc9e07d0_0 22 | - jedi=0.12.1=py35_0 23 | - jpeg=9b=h024ee3a_2 24 | - kiwisolver=1.0.1=py35hf484d3e_0 25 | - libedit=3.1.20181209=hc058e9b_0 26 | - libffi=3.2.1=hd88cf55_4 27 | - libgcc-ng=8.2.0=hdf63c60_1 28 | - libgfortran-ng=7.3.0=hdf63c60_0 29 | - libpng=1.6.36=hbc83047_0 30 | - libstdcxx-ng=8.2.0=hdf63c60_1 31 | - libuuid=1.0.3=h1bed415_2 32 | - libxcb=1.13=h1bed415_1 33 | - libxml2=2.9.9=he19cac6_0 34 | - matplotlib=3.0.0=py35h5429711_0 35 | - mkl=2018.0.3=1 36 | - mkl_fft=1.0.6=py35h7dd41cf_0 37 | - mkl_random=1.0.1=py35h4414c95_1 38 | - ncurses=6.1=he6710b0_1 39 | - numpy=1.15.2=py35h1d66e8a_0 40 | - numpy-base=1.15.2=py35h81de0dd_0 41 | - openssl=1.0.2p=h14c3975_0 42 | - pandas=0.23.4=py35h04863e7_0 43 | - parso=0.3.1=py35_0 44 | - pcre=8.42=h439df22_0 45 | - pexpect=4.6.0=py35_0 46 | - pickleshare=0.7.4=py35hd57304d_0 47 | - pip=10.0.1=py35_0 48 | - prompt_toolkit=1.0.15=py35hc09de7a_0 49 | - ptyprocess=0.6.0=py35_0 50 | - pygments=2.2.0=py35h0f41973_0 51 | - pyparsing=2.2.1=py35_0 52 | - pyqt=5.9.2=py35h05f1152_2 53 | - python=3.5.6=hc3d631a_0 54 | - python-dateutil=2.7.3=py35_0 55 | - pytz=2018.5=py35_0 56 | - qt=5.9.6=h8703b6f_2 57 | - readline=7.0=h7b6447c_5 58 | - scikit-learn=0.20.0=py35h4989274_1 59 | - scipy=1.1.0=py35hfa4b5c9_1 60 | - setuptools=40.2.0=py35_0 61 | - simplegeneric=0.8.1=py35_2 62 | - sip=4.19.8=py35hf484d3e_0 63 | - six=1.11.0=py35_1 64 | - sqlite=3.26.0=h7b6447c_0 65 | - tk=8.6.8=hbc83047_0 66 | - tornado=5.1.1=py35h7b6447c_0 67 | - traitlets=4.3.2=py35ha522a97_0 68 | - wcwidth=0.1.7=py35hcd08066_0 69 | - wheel=0.31.1=py35_0 70 | - xz=5.2.4=h14c3975_4 71 | - zlib=1.2.11=h7b6447c_3 72 | prefix: /mnt/c/Users/felipegb94/miniconda3/envs/dsenv 73 | 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ToFSim 2 | 3 | Simulation Engine for Time-of-Flight Imaging. 4 | 5 | The simulation code assumes that there is not multi-path interference. 6 | 7 | The script `ToFSinglePixel.py` does the brightness measurements calculations, adds noise, and computes depths. If you want to simulate an entire scene you can modify the variable `depth` and change it to a list of depths associated to each point in the scene. Make sure that the depths of the scene are within the unambigous depth range of your functions (see `dMax` variable in the script). You can control the signal-to-noise-ratio (SNR) of the scene by varying: source average power (`pAveSourcePerPixel`), ambient average power (`pAveAmbientPerPixel`), mean albedo/reflectivity (`meanBeta`), integration/exposure time (`T`). 8 | 9 | The script `CalculateCodingSchemeMeanExpectedDepthError.py` shows how to calculate the mean expected depth error for a given coding scheme, under a set of SNR parameters. Figure 4 in [Practical Coding Function Design for Time-of-Flight Imaging](http://openaccess.thecvf.com/content_CVPR_2019/papers/Gutierrez-Barragan_Practical_Coding_Function_Design_for_Time-Of-Flight_Imaging_CVPR_2019_paper.pdf) was generated by running this type of simulation for a large number of SNR parameter combinations. 10 | 11 | The script `VisualizeCodingScheme.py` plots the modulation, demodulation, and correlation functions for a given coding scheme. 12 | 13 | ### References 14 | 15 | If you use this code please make sure that you cite the following papers: 16 | 17 | * *Practical Coding Function Design for Time-of-Flight Imaging*. Felipe Gutierrez-Barragan, Syed Azer Reza, Andreas Velten, Mohit Gupta. CVPR 2019. 18 | 19 | * *What are the Optimal Coding Functions for Time-of-Flight Imaging?*. Mohit Gupta, S Nayar, A Velten, Eric Breitbach. ACM TOG, presented at SIGGRAPH 2018. 20 | 21 | ### Python Environment Setup 22 | 23 | Follow the steps in this section to setup an anaconda virtual environment that contains all the required dependencies/libraries to run the code in this repository. 24 | 25 | 1. **Install miniconda** 26 | 2. **Create anaconda environment:** The following command creates an anaconda environment called `tofenv` with python 3.5. 27 | ```conda create -n tofenv python=3.5 ``` 28 | 29 | 3. **Activate environment:** 30 | ```source activate tofenv``` 31 | 32 | 4. **Install libraries:** The code in this repository uses numpy, scipy, pandas, matplotlib, scikit-learn, and ipython (for development). To install all these dependencies run the following command: 33 | ```conda install numpy scipy matplotlib ipython pandas scikit-learn``` 34 | 35 | **Note:** If directly installing the packages with the above commands does not work it is probably because different versions of the libraries were installed. If this happened remove the environment and start over with the following steps. 36 | 37 | 1. Install miniconda and clone this repository. 38 | 2. Navigate to the folder containing this repos. 39 | 3. Use the `tofenv.yml` file to create the conda environment by running the following command: 40 | ```conda env create -f tofenv.yml```. 41 | This command will setup the exact same conda environment we are currently using with all library versions being the same. 42 | 43 | For more details on how to manage a conda environment take a look at this webpage: [Managing Conda Environment](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#sharing-an-environment). 44 | 45 | 46 | -------------------------------------------------------------------------------- /ToFSinglePixel.py: -------------------------------------------------------------------------------- 1 | # Python imports 2 | import math 3 | # Library imports 4 | import numpy as np 5 | from scipy import signal 6 | from scipy import special 7 | import matplotlib as mpl 8 | import matplotlib.pyplot as plt 9 | from IPython.core import debugger 10 | breakpoint = debugger.set_trace 11 | 12 | # Local imports 13 | import CodingFunctions 14 | import Utils 15 | import Decoding 16 | 17 | #################### Set Function Parameters 18 | N = 10000 19 | #################### Get coding functions with total energy = 1 20 | # K = 4 21 | # (ModFs,DemodFs) = CodingFunctions.GetCosCos(N = N, K = K) 22 | (ModFs,DemodFs) = CodingFunctions.GetHamK3(N = N) 23 | # (ModFs,DemodFs) = CodingFunctions.GetHamK4(N = N) 24 | # (ModFs,DemodFs) = CodingFunctions.GetHamK5(N = N) 25 | # (ModFs,DemodFs) = CodingFunctions.GetMultiFreqCosK5(N = N) 26 | 27 | #################### Coding Function and Scene Parameters 28 | sourceExponent = 9 29 | ambientExponent = 6 30 | #### Global parameters 31 | speedOfLight = 299792458. * 1000. # mm / sec 32 | #### Sensor parameters 33 | T = 0.1 # Integration time. Exposure time in seconds 34 | readNoise = 20 # Standard deviation in photo-electrons 35 | #### Coding function parameters 36 | dMax = 10000 # maximum depth 37 | fMax = speedOfLight/(2*float(dMax)) # Maximum unambiguous repetition frequency (in Hz) 38 | tauMin = 1./fMax 39 | fSampling = float(dMax)*fMax # Sampling frequency of mod and demod functuion 40 | dt = tauMin/float(N) 41 | pAveSourcePerPixel = np.power(10, sourceExponent) # Source power. Avg number of photons emitted by the light source per second. 42 | # pAveSourcePerPixel = pAveSource/nPixels # Avg number of photons arriving to each pixel per second. If all light is reflected back. 43 | freq = fMax # Fundamental frequency of modulation and demodulation functions 44 | tau = 1/freq 45 | #### Scene parameters 46 | pAveAmbientPerPixel = np.power(10, ambientExponent) # Ambient light power. Avg number of photons per second due to ambient light sources 47 | # pAveAmbientPerPixel = pAveAmbient/nPixels # Avg # of photons per second arriving to each pixel 48 | meanBeta = 1e-4 # Avg fraction of photons reflected from a scene points back to the detector 49 | #### Camera gain parameter 50 | ## The following bound is found by assuming the max brightness value is obtained when demod is 1. 51 | gamma = 1./(meanBeta*T*(pAveAmbientPerPixel+pAveSourcePerPixel)) # Camera gain. Ensures all values are between 0-1. 52 | 53 | #### Set list of depths/depth map 54 | depths = np.round(np.random.rand(5)*dMax).astype(int) 55 | # depths = np.arange(0,10000) 56 | print("True Depths: {}".format(depths)) 57 | 58 | #################### Simulation 59 | ## Set area under the curve of outgoing ModF to the totalEnergy 60 | ModFs = Utils.ScaleMod(ModFs, tau=tauMin, pAveSource=pAveSourcePerPixel) 61 | CorrFs = Utils.GetCorrelationFunctions(ModFs,DemodFs,dt=dt) 62 | ## Normalized correlation functions, zero mean, unit variance. We have to transpose so that broadcasting works. 63 | NormCorrFs = (CorrFs.transpose() - np.mean(CorrFs, axis=1)) / np.std(CorrFs, axis=1) 64 | # Transpose it again so that it has dims NxK 65 | NormCorrFs = NormCorrFs.transpose() 66 | 67 | BVals = Utils.ComputeBrightnessVals(ModFs=ModFs, DemodFs=DemodFs, depths=depths, 68 | pAmbient=pAveAmbientPerPixel, beta=meanBeta, T=T, tau=tau, dt=dt, gamma=gamma) 69 | #### Add noise 70 | # caluclate variance 71 | noiseVar = BVals*gamma + math.pow(readNoise*gamma, 2) 72 | ##### Add noise to all brightness values 73 | for i in range(depths.size): 74 | BVals[i,:] = Utils.GetClippedBSamples(nSamples=1,BMean=BVals[i,:],BVar=noiseVar[i,:]) 75 | 76 | decodedDepths = Decoding.DecodeXCorr(BVals,NormCorrFs) 77 | 78 | print("Decoded depths: {},".format(decodedDepths)) 79 | -------------------------------------------------------------------------------- /UtilsPlot.py: -------------------------------------------------------------------------------- 1 | """UtilsPlot 2 | 3 | Attributes: 4 | colors (TYPE): Colors for plotting 5 | plotParams (TYPE): Default plotting parameters 6 | """ 7 | #### Python imports 8 | 9 | #### Library imports 10 | import numpy as np 11 | import matplotlib as mpl 12 | import matplotlib.pyplot as plt 13 | # from IPython.core import debugger 14 | # breakpoint = debugger.set_trace 15 | 16 | #### Local imports 17 | import Utils 18 | 19 | #### Default matplotlib preferences 20 | plt.style.use('ggplot') 21 | colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] 22 | plotParams = { 23 | 'font.size': 16, 24 | 'figure.dpi': 80, 25 | 'figure.autolayout': True, 26 | 'figure.titleweight': 'bold', 27 | 'savefig.dpi': 200, 28 | 'axes.titlesize': 18, # main title 29 | 'axes.labelsize': 16, # x and y titles 30 | 'axes.titleweight': 'bold', # x and y titles 31 | 'axes.labelweight': 'bold', # x and y titles 32 | 'grid.linestyle': '--', 33 | 'grid.linewidth': 2, 34 | 'text.usetex': False, 35 | 'xtick.labelsize': 14, 36 | 'xtick.minor.visible': True, 37 | 'ytick.labelsize': 14, 38 | 'ytick.minor.visible': True, 39 | 'lines.linewidth': 2, 40 | 'lines.markersize': 8.0, 41 | 'legend.fontsize': 14, 42 | 'legend.shadow': True, 43 | } 44 | 45 | # mpl.use('Qt4Agg', warn=False) ## Needed to allow drawing with matplotlib during debug mode 46 | plt._INSTALL_FIG_OBSERVER = True 47 | 48 | mpl.rcParams.update(plotParams) 49 | plt.ion() 50 | 51 | 52 | def PlotCodingScheme(ModFs, DemodFs): 53 | """PlotCodingScheme: Create a 1x3 figure with modulation, demodulation, and the correlation. 54 | 55 | Args: 56 | modF (numpy.ndarray): Modulation functions. N x K matrix. 57 | demodF (numpy.ndarray): Demodulation functions. N x K matrix 58 | 59 | Returns: 60 | plt.figure: Figure handle 61 | plt.axis: Axis handle 62 | """ 63 | #### Assume the following constants 64 | totalEnergy = 1. 65 | tau = 1. 66 | averagePower = totalEnergy / tau 67 | #### Reshape to ensure needed dimensions 68 | ## Assume that the number of elements is larger than the number of coding pairs, i.e. rows>cols 69 | if(ModFs.shape[0] < ModFs.shape[1]): ModFs = ModFs.transpose() 70 | if(DemodFs.shape[0] < DemodFs.shape[1]): DemodFs = DemodFs.transpose() 71 | #### Verify Inputs 72 | assert(ModFs.shape == DemodFs.shape), "Input Error - PlotCodingScheme: ModFs and \ 73 | DemodFs should be the same dimensions." 74 | #### Set some parameters 75 | (N,K) = ModFs.shape 76 | avgPower = np.sum(ModFs[:,0])/N 77 | #### Set default values 78 | t = np.linspace(0, tau, N) 79 | phase = np.linspace(0, 2*np.pi,N) 80 | #### Reshape to ensure same dimensions 81 | t = t.reshape((N,)) 82 | #### Get Correlation functions 83 | CorrFs = Utils.GetCorrelationFunctions(ModFs=ModFs,DemodFs=DemodFs) 84 | #### Plot Decomposition 85 | ## Clear current plot 86 | plt.clf() 87 | ## Get current figure 88 | fig = plt.gcf() 89 | ## Add subplots and get axis array 90 | for i in range(K): 91 | # breakpoint() 92 | fig.add_subplot(K,3,3*i + 1) 93 | fig.add_subplot(K,3,3*i + 2) 94 | fig.add_subplot(K,3,3*i + 3) 95 | axarr = fig.get_axes() 96 | ## Make all plots 97 | ## Calculate Avg power. 98 | avgPower = np.sum(ModFs[:,0]) / N 99 | avgPower = [avgPower for i in range(0, N)] 100 | 101 | ## Plot ObjCorrF first so that stars don't cover the corrFs. 102 | for i in range(0, K): 103 | labelInfo = str(i) 104 | axarr[3*i + 0].plot(t, ModFs[:,i], label='Md-'+labelInfo,linewidth=2, color=colors[i]) 105 | axarr[3*i + 1].plot(t, DemodFs[:,i], label='Dmd-'+labelInfo,linewidth=2, color=colors[i]) 106 | axarr[3*i + 2].plot(phase, CorrFs[:,i], label='Crr-'+labelInfo,linewidth=2, color=colors[i]) 107 | axarr[3*i + 0].plot(t, avgPower, '--', label='AvgPower', linewidth=3, color=colors[i]) 108 | ## Set axis labels 109 | axarr[3*i + 0].set_xlabel('Time') 110 | axarr[3*i + 1].set_xlabel('Time') 111 | axarr[3*i + 2].set_xlabel('Phase') 112 | axarr[3*i + 0].set_ylabel('Instant Power') 113 | axarr[3*i + 1].set_ylabel('Exposure') 114 | axarr[3*i + 2].set_ylabel('Magnitude') 115 | 116 | ## Set Titles 117 | axarr[0].set_title('Modulation') 118 | axarr[1].set_title('Demodulation') 119 | axarr[2].set_title('Correlation') 120 | # ## Set ylimit so that we can see the legend 121 | # axarr[0].set_ylim([0,1.2*np.max(ModFs)]) 122 | # axarr[1].set_ylim([0,1.2*np.max(DemodFs)]) 123 | # axarr[2].set_ylim([0,1.2*np.max(CorrFs)]) 124 | 125 | return (fig, axarr) 126 | 127 | 128 | -------------------------------------------------------------------------------- /simple_tofsim.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This script simulates clean ToF data from a depth image. It is very simple and ignores a lot of the scaling factors that each tof measurements 3 | usually undergoes (e.g., reflectivity, exposure time, etc) 4 | ''' 5 | 6 | #### Python imports 7 | 8 | #### Library imports 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from IPython.core import debugger 12 | breakpoint = debugger.set_trace 13 | 14 | #### Local imports 15 | 16 | 17 | def generate_m_phase_sinusoid(n, m_phase=3, freqs=[1]): 18 | n_freqs = len(freqs) 19 | k = m_phase*n_freqs 20 | corrfs = np.zeros((n, k)) 21 | phase_step = 2*np.pi / m_phase 22 | domain = np.arange(0, n) / n 23 | for i in range(n_freqs): 24 | curr_f = freqs[i] 25 | for j in range(m_phase): 26 | curr_k = (i*m_phase) + j 27 | curr_phi = (j*phase_step) / curr_f 28 | # print("Curr F = {}, Curr K = {}, Curr Phi = {}".format(curr_f, curr_k, curr_phi)) 29 | curr_sinusoid = 0.5*np.cos((2*np.pi*curr_f*domain) + curr_phi) + 0.5 30 | corrfs[:, curr_k] = curr_sinusoid 31 | return corrfs 32 | 33 | def circular_conv( v1, v2, axis=-1 ): 34 | """1D Circular convolution: Calculate the circular convolution for vectors v1 and v2. v1 and v2 are the same size 35 | 36 | Args: 37 | v1 (numpy.ndarray): ...xN vector 38 | v2 (numpy.ndarray): ...xN vector 39 | Returns: 40 | v1convv2 (numpy.ndarray): convolution result. N x 1 vector. 41 | """ 42 | v1convv2 = np.fft.irfft( np.fft.rfft( v1, axis=axis ) * np.fft.rfft( v2, axis=axis ), axis=axis, n=v1.shape[axis] ) 43 | return v1convv2 44 | 45 | def circular_corr( v1, v2, axis=-1 ): 46 | """1D Circular correlation: Calculate the circular correlation for vectors v1 and v2. v1 and v2 are the same size 47 | 48 | Args: 49 | v1 (numpy.ndarray): Nx1 vector 50 | v2 (numpy.ndarray): Nx1 vector 51 | Returns: 52 | v1corrv2 (numpy.ndarray): correlation result. N x 1 vector. 53 | """ 54 | v1corrv2 = np.fft.ifft( np.fft.fft( v1, axis=axis ).conj() * np.fft.fft( v2, axis=axis ), axis=axis ).real 55 | return v1corrv2 56 | 57 | def generate_hamk4(n): 58 | from CodingFunctions import GetHamK4 59 | (modfs, demodfs) = GetHamK4(n) 60 | corrfs = circular_corr(modfs, demodfs, axis=0) 61 | return corrfs 62 | 63 | def norm(v, axis=-1): 64 | # Add 1e-7 to make sure no division by 0 65 | return v / (np.linalg.norm(v, ord=2, axis=axis, keepdims=True) + 1e-7) 66 | 67 | def zero_norm(v, axis=-1): 68 | return norm(v - v.mean(axis=axis,keepdims=True)) 69 | 70 | def zncc_depth_est(b_img, corrfs): 71 | ''' 72 | Assumes that the last dimension of b_img and corrfs match 73 | ''' 74 | EPSILON = 1e-6 75 | zn_corrfs = zero_norm(corrfs, axis=-1) 76 | zn_b_img = zero_norm(b_img) 77 | zncc_img = np.matmul(zn_corrfs, zn_b_img[..., np.newaxis] ).squeeze(-1) 78 | depth_bin_img = np.argmax(zncc_img,axis=-1) 79 | return depth_bin_img 80 | 81 | if __name__=='__main__': 82 | 83 | # 84 | n = 1000 85 | k = 4 86 | max_depth = 10 # 10 meters 87 | depth_res = max_depth / n 88 | 89 | # generate corrfs (can sinusoid or hamiltonian) 90 | # corrfs = generate_hamk4(n) 91 | corrfs = generate_m_phase_sinusoid(n, m_phase=k, freqs=[1]) 92 | # plot corrfs 93 | plt.clf() 94 | plt.plot(corrfs) 95 | 96 | # load depth image (units are in meters) 97 | depth_img = np.load('./sample_data/cbox_depthmap_nr-240_nc-320.npy') 98 | (nr, nc) = depth_img.shape 99 | 100 | # simulate ToF measurements 101 | # NOTE: The code below can be easily vectorized, but I left it as for loops to make it easier to undersnat 102 | b_img = np.zeros((nr, nc, k)) 103 | # For each pixel, get current depth, and find the K correlation function values associated with that depth 104 | for i in range(nr): 105 | for j in range(nc): 106 | curr_depth = depth_img[i,j] 107 | curr_depth_bin = np.floor(curr_depth / depth_res) 108 | b_img[i,j,:] = corrfs[int(curr_depth_bin), :] 109 | 110 | 111 | # Estimate depths 112 | est_depth_img = zncc_depth_est(b_img, corrfs)*depth_res 113 | 114 | 115 | plt.clf() 116 | plt.subplot(4,1,1) 117 | plt.plot(corrfs) 118 | plt.title("Correlation Functions") 119 | plt.subplot(4,4,5) 120 | plt.imshow(b_img[:,:,0]) 121 | plt.title("Brighness Image [0]") 122 | plt.subplot(4,4,6) 123 | plt.imshow(b_img[:,:,1]) 124 | plt.title("Brighness Image [1]") 125 | plt.subplot(4,4,7) 126 | plt.imshow(b_img[:,:,2]) 127 | plt.title("Brighness Image [2]") 128 | plt.subplot(4,4,8) 129 | plt.imshow(b_img[:,:,3]) 130 | plt.title("Brighness Image [3]") 131 | 132 | plt.subplot(2,3,4) 133 | plt.imshow(depth_img, vmin=depth_img.min(), vmax=depth_img.max()) 134 | plt.title("True Depths in m") 135 | plt.colorbar() 136 | 137 | plt.subplot(2,3,5) 138 | plt.imshow(est_depth_img, vmin=depth_img.min(), vmax=depth_img.max()) 139 | plt.title("Estimated Depths in m") 140 | plt.colorbar() 141 | 142 | plt.subplot(2,3,6) 143 | plt.imshow(np.abs(est_depth_img-depth_img)*1000, vmin=0, vmax=2*depth_res*1000) 144 | plt.title("Depth Errors in mm") 145 | plt.colorbar() 146 | 147 | print("Note that the maximum depth error is bounded by the depth resolution ({})".format(depth_res)) 148 | 149 | 150 | -------------------------------------------------------------------------------- /Utils.py: -------------------------------------------------------------------------------- 1 | #### Python imports 2 | #### Library imports 3 | import numpy as np 4 | from scipy import stats 5 | from IPython.core import debugger 6 | breakpoint = debugger.set_trace 7 | 8 | 9 | 10 | def ScaleAreaUnderCurve(x, dx=0., desiredArea=1.): 11 | """ScaleAreaUnderCurve: Scale the area under the curve x to some desired area. 12 | 13 | Args: 14 | x (TYPE): Discrete set of points that lie on the curve. Numpy vector 15 | dx (float): delta x. Set to 1/length of x by default. 16 | desiredArea (float): Desired area under the curve. 17 | 18 | Returns: 19 | numpy.ndarray: Scaled vector x with new area. 20 | """ 21 | #### Validate Input 22 | # assert(UtilsTesting.IsVector(x)),'Input Error - ScaleAreaUnderCurve: x should be a vector.' 23 | #### Calculate some parameters 24 | N = x.size 25 | #### Set default value for dc 26 | if(dx == 0): dx = 1./float(N) 27 | #### Calculate new area 28 | oldArea = np.sum(x)*dx 29 | y = x*desiredArea/oldArea 30 | #### Return scaled vector 31 | return y 32 | 33 | 34 | def ScaleMod(ModFs, tau=1., pAveSource=1.): 35 | """ScaleMod: Scale modulation appropriately given the beta of the scene point, the average 36 | source power and the repetition frequency. 37 | 38 | Args: 39 | ModFs (np.ndarray): N x K matrix. N samples, K modulation functions 40 | tau (float): Repetition frequency of ModFs 41 | pAveSource (float): Average power emitted by the source 42 | beta (float): Average reflectivity of scene point 43 | 44 | Returns: 45 | np.array: ModFs 46 | """ 47 | (N,K) = ModFs.shape 48 | dt = tau / float(N) 49 | eTotal = tau*pAveSource # Total Energy 50 | for i in range(0,K): 51 | ModFs[:,i] = ScaleAreaUnderCurve(x=ModFs[:,i], dx=dt, desiredArea=eTotal) 52 | 53 | return ModFs 54 | 55 | 56 | def ApplyKPhaseShifts(x, shifts): 57 | """ApplyPhaseShifts: Apply phase shift to each vector in x. 58 | 59 | Args: 60 | x (np.array): NxK matrix 61 | shifts (np.array): Array of dimension K. 62 | 63 | Returns: 64 | np.array: Return matrix x where each column has been phase shifted according to shifts. 65 | """ 66 | K = 0 67 | if(type(shifts) == np.ndarray): K = shifts.size 68 | elif(type(shifts) == list): K = len(shifts) 69 | else: K = 1 70 | for i in range(0,K): 71 | x[:,i] = np.roll(x[:,i], int(round(shifts[i]))) 72 | 73 | return x 74 | 75 | def GetCorrelationFunctions(ModFs, DemodFs, dt=None): 76 | """GetCorrelationFunctions: Calculate the circular correlation of all modF and demodF. 77 | 78 | Args: 79 | corrFAll (numpy.ndarray): Correlation functions. N x K matrix. 80 | 81 | Returns: 82 | np.array: N x K matrix. Each column is the correlation function for the respective pair. 83 | """ 84 | #### Reshape to ensure needed dimensions 85 | if(ModFs.ndim == 1): ModFs = ModFs.reshape((ModFs.shape[0], 1)) 86 | if(DemodFs.ndim == 1): DemodFs = DemodFs.reshape((DemodFs.shape[0], 1)) 87 | ## Assume that the number of elements is larger than the number of coding pairs, i.e. rows>cols 88 | if(ModFs.shape[0] < ModFs.shape[1]): ModFs = ModFs.transpose() 89 | if(DemodFs.shape[0] < DemodFs.shape[1]): DemodFs = DemodFs.transpose() 90 | #### Verify Inputs 91 | assert(ModFs.shape == DemodFs.shape), "Input Error - PlotCodingScheme: ModFs and \ 92 | DemodFs should be the same dimensions." 93 | #### Declare some parameters 94 | (N,K) = ModFs.shape 95 | #### Get dt 96 | if(dt == None): dt = 1./N 97 | #### Allocate the correlation function matrix 98 | CorrFs = np.zeros(ModFs.shape) 99 | #### Get correlation functions 100 | for i in range(0,K): 101 | CorrFs[:,i] = np.fft.ifft(np.fft.fft(ModFs[:,i]).conj() * np.fft.fft(DemodFs[:,i])).real 102 | #### Scale by dt 103 | CorrFs = CorrFs*dt 104 | return CorrFs 105 | 106 | 107 | def NormalizeBrightnessVals(b_vals, axis=-1): 108 | """ 109 | b_vals = n x k numpy matrix where each row corresponds to a set of k brightness measurements 110 | """ 111 | ## Normalized correlation functions, zero mean, unit variance. We have to transpose so that broadcasting works. 112 | norm_bvals = (b_vals - np.mean(b_vals, axis=axis, keepdims=True)) / np.std(b_vals, axis=axis, keepdims=True) 113 | 114 | return norm_bvals 115 | 116 | 117 | def ComputeBrightnessVals(ModFs, DemodFs, depths=None, pAmbient=0, beta=1, T=1, tau=1, dt=1, gamma=1): 118 | """ComputeBrightnessVals: Computes the brightness values for each possible depth. 119 | 120 | Args: 121 | ModFs (np.ndarray): N x K matrix. N samples, K modulation functions 122 | DemodFs (np.ndarray): N x K matrix. N samples, K demodulation functions 123 | tau (float): Repetitiion period of ModFs and DemodFs 124 | pAmbient (float): Average power of the ambient illumination component 125 | beta (float): Reflectivity to be used 126 | T (float): 127 | Returns: 128 | np.array: ModFs 129 | """ 130 | (N,K) = ModFs.shape 131 | if(depths is None): depths = np.arange(0, N, 1) 132 | depths = np.round(depths) 133 | ## Calculate correlation functions (integral over 1 period of m(t-phi)*d(t)) for all phi 134 | CorrFs = GetCorrelationFunctions(ModFs,DemodFs,dt=dt) 135 | ## Calculate the integral of the demodulation function over 1 period 136 | kappas = np.sum(DemodFs,0)*dt 137 | ## Calculate brightness values 138 | BVals = (gamma*beta)*(T/tau)*(CorrFs + pAmbient*kappas) 139 | ## Return only the brightness vals for the specified depths 140 | BVals = BVals[depths.astype(int),:] 141 | 142 | return (BVals) 143 | 144 | def GetClippedBSamples(nSamples, BMean, BVar): 145 | """GetClippedBSamples: Draw N brightness samples from the truncated multivariate gaussian dist 146 | with mean BVal and Covariance Sigma=diag(NoiseVar) 147 | Args: 148 | nSamples (int): Number of samples to draw. 149 | BMean (np.ndarray): 1 x K array. 150 | BVar (np.ndarray): 1 x K array. 151 | Returns: 152 | BSampels (np.ndarray): nSamples x K array. 153 | """ 154 | K = BMean.size 155 | lower, upper = 0, 1 156 | MultNormDist = stats.multivariate_normal(mean=BMean,cov=np.diag(BVar)) 157 | 158 | BSamples = MultNormDist.rvs(nSamples) 159 | BSamples[BSamples<0]=lower 160 | BSamples[BSamples>1]=upper 161 | 162 | 163 | return (BSamples) -------------------------------------------------------------------------------- /CalculateCodingSchemeMeanExpectedDepthError.py: -------------------------------------------------------------------------------- 1 | ''' 2 | For a given set of SNR parameters and a coding scheme calculate the mean depth error 3 | 4 | Parameters that control SNR: 5 | - sourceExponent: Controls source average power per pixel, pAveSourcePerPixel = 10^sourceExponent 6 | - ambientExponent: Controls ambient average power per pixel, pAveAmbientPerPixel = 10^ambientExponent 7 | - exposureTimeBudget: Total exposure time divided across all K coding functions in the coding scheme 8 | - meanBeta: Average effective albedo set for a pixel 9 | - dMax: Maximum depth that can be imaged by the coding scheme. Decreasing this value is the equivalent of increasing the repetition frequency and hence increases SNR. 10 | 11 | To calculate the mean depth errors of a coding scheme we evenly discretize the depth range of the coding scheme and 12 | calculate the depth error for each depth. The depth error for each depth is calculated by simulating that depth many times 13 | and adding noise each time and calculating the absolute error. We find that simulating the given depth 5000 times is enough to give 14 | a stable depth error. 15 | 16 | Parameters that affect runtime of calculation: 17 | 18 | - dSample: Spacing between discretized depths across the depth range. These are the depths we are calculating the depth error for. If you increase dSample then we are sampling depths 19 | farther appart and hence we are sampling less depths across the depth range. That means that we will have to calculate less depth errors. The fewer depth 20 | errors that are calculated the less accurate the Mean Depth Error Calculated will be. 21 | - nMonteCarloSamples: Number of depth errors calculated per depth 22 | 23 | Note: Usually expected depth errors are a bit high at the edges of the depth range (e.g. between 0 and 100 mm and 9900 and 10000 mm for a 10000mm depth range) 24 | 25 | To reproduce Figure 4 of 26 | http://openaccess.thecvf.com/content_CVPR_2019/papers/Gutierrez-Barragan_Practical_Coding_Function_Design_for_Time-Of-Flight_Imaging_CVPR_2019_paper.pdf 27 | you need to run this script with various compinations of sourceExponent and ambientExponent. The range that we used for those paramters in those figures was 28 | around sourceExponent = [7 to 9], ambientExponent = [6 to 9] 29 | ''' 30 | 31 | # Python imports 32 | import math 33 | # Library imports 34 | import numpy as np 35 | from scipy import signal 36 | from scipy import special 37 | import matplotlib as mpl 38 | import matplotlib.pyplot as plt 39 | from IPython.core import debugger 40 | breakpoint = debugger.set_trace 41 | 42 | # Local imports 43 | import CodingFunctions 44 | import Utils 45 | import Decoding 46 | 47 | #################### Set Function Parameters 48 | N = 10000 49 | dSample = 200 50 | nMonteCarloSamples = 5000 51 | 52 | #################### Get coding functions with total energy = 1 53 | # (ModFs,DemodFs) = CodingFunctions.GetCosCos(N = N, K = 4) 54 | # (ModFs,DemodFs) = CodingFunctions.GetSqSq(N = N, K = 4) 55 | (ModFs,DemodFs) = CodingFunctions.GetHamK3(N = N) 56 | # (ModFs,DemodFs) = CodingFunctions.GetHamK4(N = N) 57 | (ModFs,DemodFs) = CodingFunctions.GetHamK5(N = N) 58 | # (ModFs,DemodFs) = CodingFunctions.GetMultiFreqCosK5(N = N) 59 | (_, K) = ModFs.shape 60 | 61 | #################### Coding Function and Scene Parameters 62 | sourceExponent = 8 63 | ambientExponent = 8 64 | #### Global parameters 65 | speedOfLight = 299792458. * 1000. # mm / sec 66 | #### Sensor parameters 67 | exposureTimeBudget = 0.1 # Total exposure time for the whole coding scheme 68 | T = exposureTimeBudget/float(K) # Exposure time per coding function pair 69 | readNoise = 20 # Standard deviation in photo-electrons 70 | #### Coding function parameters 71 | dMax = 10000. # maximum depth (in mm) 72 | deltaDepth = dMax / N 73 | fMax = speedOfLight/(2*dMax) # Maximum unambiguous repetition frequency (in Hz) 74 | tauMin = 1./fMax 75 | fSampling = dMax*fMax # Sampling frequency of mod and demod functuion 76 | dt = tauMin/float(N) 77 | pAveSourcePerPixel = np.power(10, sourceExponent) # Source power. Avg number of photons emitted by the light source per second. 78 | # pAveSourcePerPixel = pAveSource/nPixels # Avg number of photons arriving to each pixel per second. If all light is reflected back. 79 | freq = fMax # Fundamental frequency of modulation and demodulation functions 80 | tau = 1/freq 81 | #### Scene parameters 82 | pAveAmbientPerPixel = np.power(10, ambientExponent) # Ambient light power. Avg number of photons per second due to ambient light sources 83 | # pAveAmbientPerPixel = pAveAmbient/nPixels # Avg # of photons per second arriving to each pixel 84 | meanBeta = 1e-4 # Avg fraction of photons reflected from a scene points back to the detector 85 | #### Camera gain parameter 86 | ## The following bound is found by assuming the max brightness value is obtained when demod is 1. 87 | gamma = 1./(meanBeta*T*(pAveAmbientPerPixel+pAveSourcePerPixel)) # Camera gain. Ensures all values are between 0-1. 88 | 89 | #### Set list of depths 90 | depths = np.round(np.arange(0, dMax, dSample)) 91 | nDepths = len(depths) 92 | print("True Depths: {}".format(depths)) 93 | 94 | #################### ToF Simulation 95 | ## Set area under the curve of outgoing ModF to the totalEnergy 96 | ModFs = Utils.ScaleMod(ModFs, tau=tauMin, pAveSource=pAveSourcePerPixel) 97 | CorrFs = Utils.GetCorrelationFunctions(ModFs,DemodFs,dt=dt) 98 | ## Normalized correlation functions, zero mean, unit variance. We have to transpose so that broadcasting works. 99 | NormCorrFs = (CorrFs.transpose() - np.mean(CorrFs, axis=1)) / np.std(CorrFs, axis=1) 100 | # Transpose it again so that it has dims NxK 101 | NormCorrFs = NormCorrFs.transpose() 102 | 103 | # Calculate brightness values without any noise 104 | BValsNoNoise = Utils.ComputeBrightnessVals(ModFs=ModFs, DemodFs=DemodFs, depths=depths, 105 | pAmbient=pAveAmbientPerPixel, beta=meanBeta, T=T, tau=tau, dt=dt, gamma=gamma) 106 | #### Add noise 107 | # Calculate gaussian noise variance 108 | # The first term corresponds to the photon noise whose variance are proportional to the amount of photons arriving at the pixel 109 | # The second term is the read noise (scaled by the gain factor) 110 | noiseVar = BValsNoNoise*gamma + math.pow(readNoise*gamma, 2) 111 | 112 | # Create array to store the expected depth errors for each depth 113 | ExpectedDepthErrors = np.zeros((nDepths,)) 114 | 115 | ##### Add noise to all brightness values 116 | print("Expected Depth Errors for Current Coding Scheme") 117 | for i in range(nDepths): 118 | ## Sample from a clipped multivariate gaussian dist of Mean: BValsNoNoise, Covariance: diag(noiseVa) 119 | # We use a clipped distribution because of saturation at 0 and at 1.0 120 | CurrBSamples = Utils.GetClippedBSamples(nMonteCarloSamples, BMean=BValsNoNoise[i,:], BVar=noiseVar[i,:]) 121 | # Calculate depths for all BSamples 122 | CurrDecodedDepths = Decoding.DecodeXCorr(CurrBSamples,NormCorrFs) 123 | CurrTrueDepth = depths[i] 124 | # Calcualte expeted depth error for current depth 125 | ExpectedDepthErrors[i] = np.mean( np.abs( CurrDecodedDepths - CurrTrueDepth ) ) 126 | 127 | print(" Expected Depth Error for depth {} = {}".format(CurrTrueDepth, ExpectedDepthErrors[i])) 128 | 129 | MeanExpectedDepthError = np.mean(ExpectedDepthErrors) 130 | print("Mean Expected Depth Error = {}".format(MeanExpectedDepthError)) 131 | -------------------------------------------------------------------------------- /CodingFunctions.py: -------------------------------------------------------------------------------- 1 | #### Python imports 2 | import math 3 | 4 | #### Library imports 5 | import numpy as np 6 | from scipy import signal 7 | # from IPython.core import debugger 8 | # breakpoint = debugger.set_trace 9 | 10 | #### Local imports 11 | import Utils 12 | 13 | TotalEnergyDefault = 1. 14 | TauDefault = 1. 15 | AveragePowerDefault = TotalEnergyDefault / TauDefault 16 | 17 | def GetCosCos(N=1000, K=4): 18 | """GetCosCos: Get modulation and demodulation functions for sinusoid coding scheme. The shift 19 | between each demod function is 2*pi/k where k can be [3,4,5...] 20 | 21 | Args: 22 | N (int): N - Number of Samples 23 | k (int): k - Number of coding function 24 | freqFactor (float): Multiplicative factor to the fundamental frequency we want to use. 25 | 26 | Returns: 27 | np.array: modFs 28 | np.array: demodFs 29 | """ 30 | #### Allocate modulation and demodulation vectors 31 | modFs = np.zeros((N,K)) 32 | demodFs = np.zeros((N,K)) 33 | t = np.linspace(0, 2*np.pi, N) 34 | dt = float(TauDefault) / float(N) 35 | #### Declare base sin function 36 | cosF = (0.5*np.cos(t)) + 0.5 37 | #### Set each mod/demod pair to its base function and scale modulations 38 | for i in range(0,K): 39 | ## No need to apply phase shift to modF 40 | modFs[:,i] = cosF 41 | ## Scale modF so that area matches the total energy 42 | modFs[:,i] = Utils.ScaleAreaUnderCurve(modFs[:,i], dx=dt, desiredArea=TotalEnergyDefault) 43 | ## Apply phase shift to demodF 44 | demodFs[:,i] = cosF 45 | #### Apply phase shifts to demodF 46 | shifts = np.arange(0, K)*(float(N)/float(K)) 47 | demodFs = Utils.ApplyKPhaseShifts(demodFs,shifts) 48 | #### Return coding scheme 49 | return (modFs, demodFs) 50 | 51 | def GetSqSq(N=1000, K=4): 52 | """GetSqSq: Get modulation and demodulation functions for square coding scheme. The shift 53 | between each demod function is 2*pi/k where k can be [3,4,5...]. 54 | 55 | Args: 56 | N (int): Number of discrete points in the scheme 57 | k (int): Number of mod/demod function pairs 58 | 0.5 59 | 60 | Returns: 61 | np.array: modFs 62 | np.array: demodFs 63 | """ 64 | #### Allocate modulation and demodulation vectors 65 | modFs = np.zeros((N,K)) 66 | demodFs = np.zeros((N,K)) 67 | t = np.linspace(0, 2*np.pi, N) 68 | dt = float(TauDefault) / float(N) 69 | #### Declare base sin function 70 | sqF = (0.5*signal.square(t, duty=0.5)) + 0.5 71 | #### Set each mod/demod pair to its base function and scale modulations 72 | for i in range(0,K): 73 | ## No need to apply phase shift to modF 74 | modFs[:,i] = sqF 75 | ## Scale modF so that area matches the total energy 76 | modFs[:,i] = Utils.ScaleAreaUnderCurve(modFs[:,i], dx=dt, desiredArea=TotalEnergyDefault) 77 | ## Apply phase shift to demodF 78 | demodFs[:,i] = sqF 79 | #### Apply phase shifts to demodF 80 | shifts = np.arange(0, K)*(float(N)/float(K)) 81 | demodFs = Utils.ApplyKPhaseShifts(demodFs,shifts) 82 | #### Return coding scheme 83 | return (modFs, demodFs) 84 | 85 | def GetHamK3(N=1000): 86 | """GetHamK3: Get modulation and demodulation functions for the coding scheme 87 | HamK3 - Sq16Sq50. 88 | Args: 89 | N (int): N 90 | Returns: 91 | modFs: NxK matrix 92 | demodFs: NxK matrix 93 | """ 94 | #### Set some parameters 95 | K = 3 96 | maxInstantPowerFactor = 6. 97 | dt = float(TauDefault) / float(N) 98 | #### Allocate modulation and demodulation vectors 99 | modFs = np.zeros((N,K)) 100 | demodFs = np.zeros((N,K)) 101 | #### Prepare modulation functions 102 | modDuty = 1./maxInstantPowerFactor 103 | for i in range(0,K): 104 | modFs[0:math.floor(modDuty*N),i] = maxInstantPowerFactor*AveragePowerDefault 105 | #### Prepare demodulation functions 106 | ## Make shape of function 107 | demodDuty = 1./2. 108 | for i in range(0,K): 109 | demodFs[0:math.floor(demodDuty*N),i] = 1. 110 | ## Apply necessary phase shift 111 | shifts = [0, (1./3.)*N, (2./3.)*N] 112 | demodFs = Utils.ApplyKPhaseShifts(demodFs,shifts) 113 | 114 | return (modFs, demodFs) 115 | 116 | 117 | def GetHamK4(N=1000): 118 | """GetHamK4: Get modulation and demodulation functions for the coding scheme HamK4 119 | Args: 120 | N (int): N 121 | Returns: 122 | modFs: NxK matrix 123 | demodFs: NxK matrix 124 | """ 125 | #### Set some parameters 126 | K = 4 127 | maxInstantPowerFactor=12. 128 | dt = float(TauDefault) / float(N) 129 | #### Allocate modulation and demodulation vectors 130 | modFs = np.zeros((N,K)) 131 | demodFs = np.zeros((N,K)) 132 | #### Prepare modulation functions 133 | modDuty = 1./maxInstantPowerFactor 134 | for i in range(0,K): 135 | modFs[0:math.floor(modDuty*N),i] = maxInstantPowerFactor*AveragePowerDefault 136 | #### Prepare demodulation functions 137 | ## Make shape of function 138 | demodDuty1 = np.array([6./12.,6./12.]) 139 | shift1 = 5./12. 140 | demodDuty2 = np.array([6./12.,6./12.]) 141 | shift2 = 2./12. 142 | demodDuty3 = np.array([3./12.,4./12.,3./12.,2./12.]) 143 | shift3 = 0./12. 144 | demodDuty4 = np.array([2./12.,3./12,4./12.,3./12.]) 145 | shift4 = 4./12. 146 | shifts = [shift1*N, shift2*N, shift3*N, shift4*N] 147 | demodDutys = [demodDuty1, demodDuty2, demodDuty3, demodDuty4] 148 | for i in range(0,K): 149 | demodDuty = demodDutys[i] 150 | startIndeces = np.floor((np.cumsum(demodDuty) - demodDuty)*N) 151 | endIndeces = startIndeces + np.floor(demodDuty*N) - 1 152 | for j in range(len(demodDuty)): 153 | if((j%2) == 0): 154 | demodFs[int(startIndeces[j]):int(endIndeces[j]),i] = 1. 155 | ## Apply necessary phase shift 156 | demodFs = Utils.ApplyKPhaseShifts(demodFs,shifts) 157 | 158 | return (modFs, demodFs) 159 | 160 | 161 | def GetHamK5(N=1000): 162 | """GetHamK5: Get modulation and demodulation functions for the coding scheme HamK5. 163 | Args: 164 | N (int): N 165 | Returns: 166 | modFs: NxK matrix 167 | demodFs: NxK matrix 168 | """ 169 | #### Set some parameters 170 | K = 5 171 | maxInstantPowerFactor=30. 172 | dt = float(TauDefault) / float(N) 173 | #### Allocate modulation and demodulation vectors 174 | modFs = np.zeros((N,K)) 175 | demodFs = np.zeros((N,K)) 176 | #### Prepare modulation functions 177 | modDuty = 1./maxInstantPowerFactor 178 | for i in range(0,K): 179 | modFs[0:math.floor(modDuty*N),i] = maxInstantPowerFactor*AveragePowerDefault 180 | #### Prepare demodulation functions 181 | ## Make shape of function 182 | demodDuty1 = np.array([15./30.,15./30.]) 183 | shift1 = 15./30. 184 | demodDuty2 = np.array([15./30.,15./30.]) 185 | shift2 = 7./30. 186 | demodDuty3 = np.array([8./30.,8./30.,7./30.,7./30.]) 187 | shift3 = 3./30. 188 | demodDuty4 = np.array([4./30.,4./30.,4./30.,4./30.,3./30.,4./30.,4./30.,3./30.]) 189 | shift4 = 1./30. 190 | demodDuty5 = np.array([2./30.,2./30.,2./30.,2./30.,2./30.,2./30.,2./30., 191 | 3./30.,2./30.,2./30.,2./30.,2./30.,3./30.,2./30]) 192 | shift5 = 4./30. 193 | shifts = [shift1*N, shift2*N, shift3*N, shift4*N, shift5*N] 194 | demodDutys = [demodDuty1, demodDuty2, demodDuty3, demodDuty4, demodDuty5] 195 | for i in range(0,K): 196 | demodDuty = demodDutys[i] 197 | startIndeces = np.floor((np.cumsum(demodDuty) - demodDuty)*N) 198 | endIndeces = startIndeces + np.floor(demodDuty*N) - 1 199 | for j in range(len(demodDuty)): 200 | if((j%2) == 0): 201 | demodFs[int(startIndeces[j]):int(endIndeces[j]),i] = 1. 202 | 203 | ## Apply necessary phase shift 204 | demodFs = Utils.ApplyKPhaseShifts(demodFs,shifts) 205 | 206 | return (modFs, demodFs) 207 | 208 | def GetMultiFreqCosK5(N=1000, highFreqFactor=7.): 209 | """GetMultiFreqCos: Returns a coding scheme based on square waves that goes the following way. 210 | Let w be the repetition frequency. The first code is a SqCode with rep freq w. The second 211 | code is a Sq Code with rep freq 2*w. The Kth code is a SqCode with a rep freq K*w 212 | 213 | Args: 214 | N (int): Number of discrete points in the scheme 215 | k (int): Number of mod/demod function pairs 216 | Returns: 217 | np.array: modFs 218 | np.array: demodFs 219 | """ 220 | K=5 221 | ModFs = np.zeros((N,K)) 222 | DemodFs = np.zeros((N,K)) 223 | t = np.linspace(0, 2*np.pi, N) 224 | HighFreqCosF = (0.5*np.cos(highFreqFactor*t)) + 0.5 # Phase = 0 225 | HighFreqCosF90 = np.roll(HighFreqCosF, int(round((N/4) / highFreqFactor))) # Phase 90 degrees 226 | #### Generate the fundamental frequency sinusoids 227 | (CosModFs,CosDemodFs) = GetCosCos(N=N,K=3) 228 | ModFs[:,0:3] = CosModFs 229 | DemodFs[:,0:3] = CosDemodFs 230 | #### Generate the high frequency sinusoid functions 231 | ModFs[:,3] = HighFreqCosF*2. 232 | DemodFs[:,3] = HighFreqCosF 233 | ModFs[:,4] = ModFs[:,3] 234 | DemodFs[:,4] = HighFreqCosF90 235 | 236 | #### Smooth the High Freq terms 237 | return (ModFs,DemodFs) --------------------------------------------------------------------------------