├── LICENSE ├── Makefile ├── README.md ├── generating.py ├── generating.sh └── utilities.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SunLab-UGA 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GPU= 2 | ifdef gpus 3 | GPU=--gpus=$(gpus) 4 | endif 5 | export GPU 6 | 7 | doc: FORCE 8 | cd doc && ./build_docs.sh 9 | 10 | docker: 11 | docker build -t sionna -f DOCKERFILE . 12 | 13 | install: FORCE 14 | pip install . 15 | 16 | lint: 17 | pylint sionna/ 18 | 19 | run-docker: 20 | docker run -itd -v {write_your_path_here}:/tf/WiSegRT -u $(id -u):$(id -g) -p 8887:8888 --privileged=true $(GPU) --env NVIDIA_DRIVER_CAPABILITIES=graphics,compute,utility sionna 21 | 22 | test: FORCE 23 | cd test && pytest 24 | 25 | FORCE: 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WiSegRT 2 | 3 | WiSegRT (Wireless Segmented Ray Tracing) is a precise indoor radio dataset for channel modeling, as detailed in our paper [WiSegRT: Dataset for Site-Specific Indoor Radio Propagation Modeling with 3D Segmentation and Differentiable Ray-Tracing](https://arxiv.org/abs/2312.11245). 4 | 5 | ### Download WiSegRT and Install Sionna 6 | * To begin, download the WiSegRT code and extract it to your target path. Then download the [scene.zip](https://outlookuga-my.sharepoint.com/:u:/g/personal/lz90799_uga_edu/ERQgp55EGwhLj4eKJOSQUvoBs5AVTWb7I4KPt_gaYaUNag?e=e2KRdh) and extract it to the WiSegRT directory. 7 | 8 | * Then you need to install Sionna, visit the official Sionna repository at [https://github.com/nvlabs/sionna](https://github.com/nvlabs/sionna) and follow the installation guide. For data generation with either our models or your own, we strongly recommend using the Docker installation. This document's instructions are based on the Docker method, which we have tested on Ubuntu 20.04 and 22.04. 9 | * The Docker-based sionna installation includes the NVIDIA Container Toolkit for GPU support. Ensure that Docker GPU support is functioning correctly. Based on our experience, you can use `sudo make docker` when creating the Sionna Docker Image and `sudo make run-docker gpus=all` when creating the corresponding container later. 10 | 11 | * Enter the WiSegRT directory and edit the Makefile, find this line 12 | ``` 13 | run-docker: 14 | docker run -itd -v {write_your_path_here}:/tf/WiSegRT -u $(id -u):$(id -g) -p 8887:8888 --privileged=true $(GPU) --env NVIDIA_DRIVER_CAPABILITIES=graphics,compute,utility sionna 15 | ``` 16 | * Write the path of WiSegRT directory as `/your/path/WiSegRT:/tf/WiSegRT`. This will mount the WiSegRT directory to the container as `/tf/WiSegRT`. 17 | 18 | * Then, launch a terminal in the WiSegRT directory and execute `sudo make run-docker gpus=all` to initiate a Docker container which will run in the background. This will also start a JupyterLab server in that container , accessible at [`http://127.0.0.1:8887/lab/`](http://127.0.0.1:8887/lab/) in your browser. You can explore the Sionna RT tutorial Jupyter notebooks here to ensure the NVIDIA Container Toolkit operates as expected. 19 | ``` 20 | sudo make run-docker gpus=all 21 | ``` 22 | 23 | ### Generating Paths 24 | * After initiating a container, execute `sudo docker ps -a` to find your container's name and `sudo docker exec -it {container_name} /bin/bash` to open a virtual terminal in that container. (If there is only one container, you can use `tab` key to fill the name.) 25 | ``` 26 | sudo docker ps -a 27 | ``` 28 | ``` 29 | sudo docker exec -it {container_name} /bin/bash 30 | ``` 31 | 32 | * Now, you are in the virtual terminal of the container and you can enter the WiSegRT directory in the virtual terminal by: 33 | ``` 34 | cd WiSegRT/ 35 | ``` 36 | * and run the provided script by `bash generating.sh `. Here are the default settings to run the simulations for our models. (GPU_index controls which GPU the simulations will run on): 37 | ``` 38 | bash generating.sh 30 0 scene_01 8 6 2.4 0.3 39 | bash generating.sh 16 0 scene_02 4 3 2.4 0.2 40 | bash generating.sh 18 0 scene_03 12 8 2.4 0.3 41 | bash generating.sh 9 0 scene_04 12 8 2.4 0.3 42 | bash generating.sh 18 0 scene_05 6 4 2.4 0.2 43 | bash generating.sh 16 0 scene_06 16 8 2.4 0.3 44 | ``` 45 | * Or you can run small-scale simulations by setting the transmitter_number to 1. 46 | 47 | * Upon completion, the generated path data and `.obj` models are stored within the `path` directory. For details on the data structure, refer to the `save_path` function in `generating.py`. Use the `load_path` function in `utilities.py` to access the stored paths. Note that outputs may include invalid receiver positions, which can be easily filterd out by their lack of valid path. 48 | 49 | * Notably, the attenuation (a) and delay (tau) of each path are from the `path.cir()` function, representing the baseband equivalent channel impulse response. Besides, the latest Sionna version (0.16.2) has introduced `path.to_dict()` and `path.from_dict()` methods, facilitating the export and import of path data for Sionna communication system simulations. We plan to adopt this feature in future releases. 50 | 51 | ### Conducting Multiple Simulations at One Time 52 | * To run multiple simulations at one time, you have to open multiple terminals in the WiSegRT directory and open virtual container terminals by running `sudo docker exec -it {container_name} /bin/bash` in each of them. Then you can run the shell script in each of the virtual terminals. 53 | 54 | * The feasible number of concurrent simulations depends on the hardware capabilities and the complexity of the scene model. For example, it's possible to run three simulations on GPU 0 and another three on GPU 1. Monitor system load with `nvidia-smi` and allocate resources with some redundancy to accommodate the variations in computational demand as receiver locations change. 55 | 56 | ### Creating Custom Scene Models with Blender 57 | Follow the [Sionna RT tutorial](https://www.youtube.com/watch?v=7xHLDxUaQ7c) to learn how to create your custom scene for Sionna. Generally, you need to install [Blender 3.6 LTS](https://www.blender.org/download/releases/3-6/), Mitsuba `pip install mitsuba`, and then [Mitsuba-Blender add-on](https://github.com/mitsuba-renderer/mitsuba-blender)( we tested [v0.3.0](https://github.com/mitsuba-renderer/mitsuba-blender/releases/download/v0.3.0/mitsuba-blender.zip) with Blender 3.6 LTS). When creating your scene, ensure that materials are appropriately named (e.g., `itu_wood.001`, `itu_wood.002`... for diffrent Blender materials that are actually "wood", the `generating.py` will handle it). When exporting scenes to XML files, adhere to the specified export settings (export IDs, Y forward, Z up) and export them directly to the `./scene` directory. Execute `generating.sh` with your scene parameters. 58 | -------------------------------------------------------------------------------- /generating.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2024, SunLab (Lihao Zhang) 3 | # SunLab Wireless Research Group, https://sunlab.uga.edu 4 | # All rights reserved. 5 | # 6 | # This software is free for non-commercial, research and evaluation use 7 | # under the terms of the LICENSE.md file. 8 | # 9 | # For inquiries contact hsun@uga.edu 10 | # 11 | 12 | import argparse 13 | parser = argparse.ArgumentParser(description = 'gpu_ind & scene_file') 14 | parser.add_argument('gpu_ind', type=str) 15 | parser.add_argument('scene_file',type =str) 16 | parser.add_argument('x_lim',type = float) 17 | parser.add_argument('y_lim',type = float) 18 | parser.add_argument('z_lim',type = float) 19 | parser.add_argument('interval',type = float) 20 | parser.add_argument('tx_ind',type = int) 21 | 22 | args = parser.parse_args() 23 | import os 24 | 25 | gpu_ind = args.gpu_ind 26 | os.environ["CUDA_VISIBLE_DEVICES"] = gpu_ind 27 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' 28 | import tensorflow as tf 29 | gpus = tf.config.list_physical_devices('GPU') 30 | print(gpus) 31 | if gpus: 32 | try: 33 | tf.config.experimental.set_memory_growth(gpus[0], True) 34 | except RuntimeError as e: 35 | print(e) 36 | tf.get_logger().setLevel('ERROR') 37 | 38 | tf.random.set_seed(1) # Set global random seed for reproducibility 39 | 40 | 41 | import matplotlib.pyplot as plt 42 | import numpy as np 43 | import time 44 | import sionna 45 | 46 | '''import Sionna RT components''' 47 | from sionna.rt import load_scene, Transmitter, Receiver, PlanarArray, Camera,RadioMaterial 48 | 49 | 50 | ################################################################################ 51 | x_lim = args.x_lim 52 | y_lim = args.y_lim 53 | z_lim = args.z_lim 54 | interval = args.interval 55 | 56 | def generate_h_pos(x_lim,interval): 57 | x_pos_num = int(x_lim/interval)-1 58 | x_pos = np.zeros(x_pos_num) 59 | for i in range(0,x_pos_num): 60 | x_pos[i] = np.around((-x_lim/2 + interval*(i+1)),1) 61 | return x_pos 62 | 63 | def generate_v_pos(x_lim,interval): 64 | x_pos_num = int(x_lim/interval)-1 65 | x_pos = np.zeros(x_pos_num) 66 | for i in range(0,x_pos_num): 67 | x_pos[i] = np.around((0 + interval*(i+1)),1) 68 | return x_pos 69 | 70 | x_pos = generate_h_pos(x_lim,interval) 71 | y_pos = generate_h_pos(y_lim,interval) 72 | z_pos = generate_v_pos(z_lim,interval) 73 | 74 | #Load scene 75 | 76 | scene_name = args.scene_file 77 | scene_path = './scene/'+scene_name+'.xml' 78 | scene = load_scene(scene_path) 79 | 80 | custom_plastic = RadioMaterial("custom_plastic", 81 | relative_permittivity=2.3, 82 | conductivity=0) 83 | 84 | custom_leather = RadioMaterial("custom_leather", 85 | relative_permittivity=1.8, 86 | conductivity=0) 87 | 88 | custom_cloth = RadioMaterial("custom_cloth", 89 | relative_permittivity=1.8, 90 | conductivity=0) 91 | 92 | scene._radio_materials[custom_plastic.name] = custom_plastic 93 | scene._radio_materials[custom_leather.name] = custom_leather 94 | scene._radio_materials[custom_cloth.name] = custom_cloth 95 | 96 | for i,obj in enumerate(scene.objects.values()): 97 | if f"{obj.radio_material.name}"[-4:-3] == ".": 98 | obj.radio_material = scene.get(f"{obj.radio_material.name}"[:-4]) 99 | else: 100 | obj.radio_material = scene.get(f"{obj.radio_material.name}") 101 | 102 | 103 | 104 | # Configure antenna array for all transmitters 105 | scene.tx_array = PlanarArray(num_rows=1, 106 | num_cols=1, 107 | vertical_spacing=0.5, 108 | horizontal_spacing=0.5, 109 | pattern="dipole", 110 | polarization="V") 111 | 112 | # Configure antenna array for all receivers 113 | scene.rx_array = PlanarArray(num_rows=1, 114 | num_cols=1, 115 | vertical_spacing=0.5, 116 | horizontal_spacing=0.5, 117 | pattern="iso", 118 | polarization="cross") 119 | 120 | scene.frequency = 2.4e9 # in Hz; implicitly updates RadioMaterials 121 | 122 | scene.synthetic_array = True # If set to False, ray tracing will be done per antenna element (slower for large arrays) 123 | 124 | ####################################################################################### 125 | 126 | def save_path(path,tx_loc,rx_loc,output_path): 127 | a,tau = path.cir() 128 | path_dict = np.array([ 129 | tx_loc, 130 | rx_loc, 131 | a.numpy(), #Channel coefficients//Attenuation 132 | tau.numpy(), #delay 133 | path.theta_t.numpy(), #Zenith angles of departure 134 | path.phi_t.numpy(), #Azimuth angles of departure 135 | path.theta_r.numpy(), #Zenith angles of arrival 136 | path.phi_r.numpy(), #Azimuth angles of arrival 137 | path.types.numpy() 138 | ],dtype=object) 139 | 140 | file_path = output_path+ '/tx_'+str(int(tx_loc[0]*10))+'_'+str(int(tx_loc[1]*10))+'_'+str(int(tx_loc[2]*10)) \ 141 | +'/path/path_'+str(int(rx_loc[0]*10))+'_'+str(int(rx_loc[1]*10))+'_'+str(int(rx_loc[2]*10)) 142 | 143 | np.save(file_path,path_dict) 144 | path.export(output_path+'/tx_'+str(int(tx_loc[0]*10))+'_'+str(int(tx_loc[1]*10))+'_'+str(int(tx_loc[2]*10))\ 145 | +'/obj/path_'+str(int(rx_loc[0]*10))+'_'+str(int(rx_loc[1]*10))+'_'+str(int(rx_loc[2]*10))+'.obj') 146 | 147 | def generate_paths(scene,tx_loc,output_path): 148 | start_time = time.time() 149 | count = 0 150 | 151 | tx = Transmitter(name="tx", 152 | position=tx_loc) 153 | scene.add(tx) 154 | 155 | dir_tx = output_path+ '/tx_'+str(int(tx_loc[0]*10))+'_'+str(int(tx_loc[1]*10))+'_'+str(int(tx_loc[2]*10))+'/path' 156 | if not os.path.exists(dir_tx): 157 | os.makedirs(dir_tx) 158 | dir_tx_obj = output_path+ '/tx_'+str(int(tx_loc[0]*10))+'_'+str(int(tx_loc[1]*10))+'_'+str(int(tx_loc[2]*10))+'/obj' 159 | if not os.path.exists(dir_tx_obj): 160 | os.makedirs(dir_tx_obj) 161 | 162 | 163 | for x in x_pos: 164 | curr_time = time.time() 165 | print(count,': ',curr_time-start_time,'seconds') 166 | for y in y_pos: 167 | 168 | for z in z_pos: 169 | 170 | rx_loc = [x,y,z] 171 | rx = Receiver(name='rx', 172 | position=[x,y,z], 173 | orientation=[0,0,0]) 174 | scene.add(rx) 175 | paths = scene.compute_paths(max_depth=4, 176 | diffraction = True, 177 | num_samples=1e6) 178 | 179 | 180 | save_path(paths,tx_loc,rx_loc,output_path) 181 | 182 | scene.remove('rx') 183 | count+=1 184 | 185 | scene.remove('tx') 186 | 187 | ################################################################################################ 188 | 189 | output_path = './paths/'+f"{scene_name}" 190 | tx_ind = args.tx_ind 191 | tx_locs=np.load('./scene/'+ f"{scene_name}"+'_tx_locs.npy').tolist() 192 | 193 | generate_paths(scene,tx_locs[tx_ind],output_path) 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /generating.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | iteration=$1 3 | for ((i=0;i<$1;i++)); do 4 | echo "$3 tx $i" 5 | python generating.py $2 $3 $4 $5 $6 $7 $i 6 | done 7 | -------------------------------------------------------------------------------- /utilities.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2024, SunLab (Lihao Zhang) 3 | # SunLab Wireless Research Group, https://sunlab.uga.edu 4 | # All rights reserved. 5 | # 6 | # This software is free for non-commercial, research and evaluation use 7 | # under the terms of the LICENSE.md file. 8 | # 9 | # For inquiries contact hsun@uga.edu 10 | # 11 | 12 | 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | 16 | #scene parameters 17 | scene_name = 'scene_01' 18 | x_lim = 8 19 | y_lim = 6 20 | z_lim = 2.4 21 | interval = 0.3 22 | 23 | tx_locs=np.load('./scene/'+ scene_name+'_tx_locs.npy').tolist() 24 | 25 | # Generate the receiver grid 26 | def generate_h_pos(x_lim,interval): 27 | x_pos_num = int(x_lim/interval)-1 28 | x_pos = np.zeros(x_pos_num) 29 | for i in range(0,x_pos_num): 30 | x_pos[i] = np.around((-x_lim/2 + interval*(i+1)),1) 31 | return x_pos 32 | 33 | def generate_v_pos(x_lim,interval): 34 | x_pos_num = int(x_lim/interval)-1 35 | x_pos = np.zeros(x_pos_num) 36 | for i in range(0,x_pos_num): 37 | x_pos[i] = np.around((0 + interval*(i+1)),1) 38 | return x_pos 39 | 40 | # Iterate all the combination of the following ios, y_pos and z_pos 41 | x_pos = generate_h_pos(x_lim,interval) 42 | y_pos = generate_h_pos(y_lim,interval) 43 | z_pos = generate_v_pos(z_lim,interval) 44 | 45 | 46 | ### Load the stored path_dict 47 | # path_dict = np.array([ 48 | # tx_loc, 49 | # rx_loc, 50 | # a.numpy(), #Channel coefficients//Attenuation 51 | # tau.numpy(), #delay 52 | # path.theta_t.numpy(), #Zenith angles of departure 53 | # path.phi_t.numpy(), #Azimuth angles of departure 54 | # path.theta_r.numpy(), #Zenith angles of arrival 55 | # path.phi_r.numpy(), #Azimuth angles of arrival 56 | # path.types.numpy() 57 | # ],dtype=object) 58 | 59 | def load_path(scene_name,tx_loc,rx_loc): 60 | tx_name = 'tx_'+str(int(tx_loc[0]*10))+'_'+str(int(tx_loc[1]*10))+'_'+str(int(tx_loc[2]*10)) 61 | rx_name = str(int(rx_loc[0]*10))+'_'+str(int(rx_loc[1]*10))+'_'+str(int(rx_loc[2]*10)) 62 | file_path = './paths/'+scene_name+'/'+ tx_name +'/path/path_'+rx_name+'.npy' 63 | path = np.load(file_path,allow_pickle=True) 64 | return path 65 | 66 | 67 | ### Plot the CIR of the path 68 | def print_cir(a,tau): 69 | t = tau[0,0,0,:]/1e-9 # Scale to ns 70 | a_abs = np.abs(a)[0,0,0,0,0,:,0] 71 | a_max = np.max(a_abs) 72 | # Add dummy entry at start/end for nicer figure 73 | t = np.concatenate([(0.,), t, (np.max(t)*1.1,)]) 74 | a_abs = np.concatenate([(np.nan,), a_abs, (np.nan,)]) 75 | 76 | # And plot the CIR 77 | plt.figure() 78 | plt.title("Channel impulse response realization") 79 | 80 | plt.stem(t, a_abs) 81 | plt.xlim([0, np.max(t)]) 82 | plt.ylim([-2e-6, a_max*1.1]) 83 | plt.xlabel(r"$\tau$ [ns]") 84 | plt.ylabel(r"$|a|$"); 85 | 86 | ### Plot the distribution of the generated paths 87 | # !pip install seaborn #in jupyter notebook 88 | import seaborn as sns 89 | 90 | def get_a_samples(scene_name,sample_number=1e6): 91 | non_list = [] 92 | all_a= np.array(non_list) 93 | count =0 94 | for tx_loc in tx_locs: 95 | for x in x_pos: 96 | print('Sampled data:'all_a.shape[0],end = "\r") 97 | for y in y_pos: 98 | for z in z_pos: 99 | count+=1 100 | if count%10 != 0: 101 | continue 102 | rx_loc=[x,y,z] 103 | path = load_path(scene_name,tx_loc,rx_loc) 104 | if not path[2].all(): #filiter invalid rx positions 105 | continue 106 | a_abs = np.abs(path[2])[0,0,0,0,0,:,0] 107 | 108 | for temp_a in a_abs: 109 | all_a = np.append(all_a,temp_a) 110 | if all_a.shape[0] >= sample_number: 111 | return all_a 112 | 113 | fig, ax = plt.subplots() 114 | all_a = get_a_samples(scene_name) 115 | sns.ecdfplot(data=all_a,log_scale=True) 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | --------------------------------------------------------------------------------