Skip to content

Commit

Permalink
First release of M3ED build system
Browse files Browse the repository at this point in the history
  • Loading branch information
fcladera committed Aug 4, 2023
0 parents commit 2b39788
Show file tree
Hide file tree
Showing 61 changed files with 9,101 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
utils/
__pycache__
Log/
49 changes: 49 additions & 0 deletions build_system/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
![alt text](https://github.com/daniilidis-group/m3ed/blob/master/M3ED_banner.webp)

# M3ED Processing Scripts

This repo contains the scripts used to process the M3ED data. These scripts are
run automatically in Jenkins.

## Dockerfile

We provide a `Dockerfile` to run the data conversions. We used this docker
environment to process the bags automatically and generate the bag reports.

### Jenkins build architecture

Jenkins was used as our deployment platform. There are three Jenkins pipelines:

- Dispatcher: this pipeline gathers all the input files, and orchestrates the
execution of the work among the different nodes. The `Jenkinsfile_dispatcher` is used for this
pipeline.

- Docker-build: this pipeline builds the docker image **in each node**. This
ensures that the processing pipelines have the most up-to-date docker image.
The `Jenkinsfile_docker_build` is used for this pipeline.

- Processing: Runs the processing scripts. The `Jenkinsfile_processing` is used
for this pipeline.

### Jenkins Master Setup

1. Install Jenkins
2. Create three jobs: `M3ED-dispatcher`, `M3ED-docker-build`, `M3ED-processing`
3. Ensure that `M3ED-docker-build` and `M3ED-dispatcher` are executed for all
branches (Branch Specifier).
4. Choose the Branch Specifier that you want for `M3ED-dispatcher`.

## Scripts

- `preliminary.sh`: this script is executed in each build node and it verifies that
the docker image is properly built, we have access to the processing folder and
files.

- `bag_processing/rosbag2verify.py`: verifies bag timestamps and integrity.

- `bag_processing/rosbag2hdf5.py`: converts the bag data into HDF5.

- `bag_processing/hdf52media.py`: generates media output from the HDF5 (videos, plots, images).

## License
M3ED is released under the (Creative Commons Attribution-ShareAlike 4.0 International License)[https://creativecommons.org/licenses/by-sa/4.0/].
120 changes: 120 additions & 0 deletions build_system/bag_processing/hdf52imu_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import os
import pdb
import argparse

import numpy as np
import h5py
import matplotlib.pyplot as plt

from scipy.interpolate import splev, splrep, barycentric_interpolate
from scipy.spatial.transform import Rotation
from scipy import signal

def resample_imu(imu, imu_ts, sample_times):
spl = splrep(imu_ts, imu)
return splev( sample_times, spl)

def filter_imu_sample(imu, numtaps=7, f=0.10):
coeffs = signal.firwin(numtaps, f)
filtered = np.zeros(imu.shape)
filtered[:,0] = signal.lfilter(coeffs, 1.0, imu[:,0])
filtered[:,1] = signal.lfilter(coeffs, 1.0, imu[:,1])
filtered[:,2] = signal.lfilter(coeffs, 1.0, imu[:,2])
return filtered

def align_imus(ovc_omega, ovc_accel, ouster_omega, ouster_accel):
"""
Solving ouster_R_ovc * ovc_omega = ouster_omega
"""
ovc_measurements = np.concatenate( [ovc_omega] ).T
ouster_measurements = np.concatenate( [ouster_omega] ).T

ouster_R_ovc = (ouster_measurements @ np.linalg.pinv(ovc_measurements))
U,S,Vh = np.linalg.svd(ouster_R_ovc)
ouster_R_ovc = U@Vh
ouster_R_ovc[:,0] = -ouster_R_ovc[:,0]
ouster_R_ovc[:,2] = np.cross(ouster_R_ovc[:,0],ouster_R_ovc[:,1])

assert np.all(np.isclose( np.linalg.det(ouster_R_ovc), 1.0 ))

return ouster_R_ovc


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Creates vids and imgs from h5.")
# input h5 file
parser.add_argument("--h5fn", help="The h5 file to convert.",
required=True)
args = parser.parse_args()

if not os.path.isfile(args.h5fn):
sys.exit("The input h5 file %s does not exist." % args.h5fn)

h5f = h5py.File(args.h5fn, 'r')

ovc_mask = np.isfinite(h5f['/ovc/imu/accel'][:,0])
ovc_ts = h5f['/ovc/imu/ts'][...][ovc_mask]
ovc_accel = h5f['/ovc/imu/accel'][...][ovc_mask,:]
ovc_omega = h5f['/ovc/imu/omega'][...][ovc_mask,:]

ouster_mask = np.isfinite(h5f['/ouster/imu/accel'][1000:-1000,0])
ouster_ts = h5f['/ouster/imu/ts'][1000:-1000][ouster_mask]
ouster_accel = h5f['/ouster/imu/accel'][1000:-1000][ouster_mask,:]
ouster_omega = h5f['/ouster/imu/omega'][1000:-1000][ouster_mask,:]

ovc_resampled_omega = np.stack( [resample_imu(ovc_omega[:,i], ovc_ts, ouster_ts) for i in range(3)], axis=-1 )
ovc_resampled_accel = np.stack( [resample_imu(ovc_accel[:,i], ovc_ts, ouster_ts) for i in range(3)], axis=-1 )

ouster_accel = filter_imu_sample(ouster_accel)
ouster_omega = filter_imu_sample(ouster_omega)

ovc_omega = filter_imu_sample(ovc_resampled_omega)
ovc_accel = filter_imu_sample(ovc_resampled_accel)
ovc_ts = ouster_ts

ouster_R_ovc = align_imus(ovc_omega, ovc_accel, ouster_omega, ouster_accel)

print(ouster_R_ovc)
print(np.rad2deg(Rotation.from_matrix(ouster_R_ovc).as_euler('xyz')))
print(np.linalg.det(ouster_R_ovc))

transformed_ovc_omega = (ouster_R_ovc @ ovc_omega.T).T
transformed_ovc_accel = (ouster_R_ovc @ ovc_accel.T).T

fig, axes = plt.subplots(3,2,sharex=True,sharey=True)

axes[0,0].set_title('OVC gyro')
axes[0,0].plot( ovc_ts, transformed_ovc_omega[:,0] )
axes[1,0].plot( ovc_ts, transformed_ovc_omega[:,1] )
axes[2,0].plot( ovc_ts, transformed_ovc_omega[:,2] )

axes[0,1].set_title('Ouster gyro')
axes[0,1].plot( ouster_ts[:-1], ouster_omega[:-1,0] )
axes[1,1].plot( ouster_ts[:-1], ouster_omega[:-1,1] )
axes[2,1].plot( ouster_ts[:-1], ouster_omega[:-1,2] )

fig, axes = plt.subplots(3,2,sharex=True,sharey=True)

axes[0,0].set_title('OVC accel')
axes[0,0].plot( ovc_ts, transformed_ovc_accel[:,0] )
axes[1,0].plot( ovc_ts, transformed_ovc_accel[:,1] )
axes[2,0].plot( ovc_ts, transformed_ovc_accel[:,2] )

axes[0,1].set_title('Ouster accel')
axes[0,1].plot( ouster_ts[:-1], ouster_accel[:-1,0] )
axes[1,1].plot( ouster_ts[:-1], ouster_accel[:-1,1] )
axes[2,1].plot( ouster_ts[:-1], ouster_accel[:-1,2] )

plt.figure()
plt.plot(ovc_ts, transformed_ovc_omega[:,0] )
plt.plot( ouster_ts, ouster_omega[:,0])

plt.figure()
plt.plot(ovc_ts, transformed_ovc_omega[:,1] )
plt.plot( ouster_ts, ouster_omega[:,1])

plt.figure()
plt.plot(ovc_ts, transformed_ovc_omega[:,2] )
plt.plot( ouster_ts, ouster_omega[:,2])

plt.show()
32 changes: 32 additions & 0 deletions build_system/bag_processing/hdf52media.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash

source ./build_system/preamble.bash

output_files=("$EVENTS_VIDEO_RAW" "$RGB_VIDEO_RAW")
check_files output_files
check_free_space fixed

# create tmp variables for videos
EVENTS_VIDEO_RAW_TMP="${EVENTS_VIDEO_RAW%.*}_tmp.${EVENTS_VIDEO_RAW##*.}"
RGB_VIDEO_RAW_TMP="${RGB_VIDEO_RAW%.*}_tmp.${RGB_VIDEO_RAW##*.}"

# Run bag processing
echo -e "${BLUE}Generating raw videos${NC}"
python3 build_system/bag_processing/hdf52media.py \
--h5fn "$H5_PATH" \
--out_events_gray "$EVENTS_VIDEO_RAW_TMP" \
--out_rgb "$RGB_VIDEO_RAW_TMP"
if [ $? -ne 0 ]; then
echo -e "${RED}Error creating media files for $H5_PATH${NC}"
rm "$EVENTS_VIDEO_RAW_TMP" "$RGB_VIDEO_RAW_TMP"
exit 1
fi
echo -e "${GREEN}Raw videos finished${NC}"
mv "$EVENTS_VIDEO_RAW_TMP" "$EVENTS_VIDEO_RAW"
mv "$RGB_VIDEO_RAW_TMP" "$RGB_VIDEO_RAW"

# Compress videos
echo -e "${BLUE}Generating compressed videos${NC}"
compress_with_ffmpeg "$EVENTS_VIDEO_RAW"
compress_with_ffmpeg "$RGB_VIDEO_RAW"
echo -e "${GREEN}Compressed videos finished${NC}"
Loading

0 comments on commit 2b39788

Please sign in to comment.