Skip to content

Commit 1ef916b

Browse files
committed
initial public release
0 parents  commit 1ef916b

15 files changed

Lines changed: 1728 additions & 0 deletions

README.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# SPICE-HL3: Single-Photon, Inertial, and Stereo Camera dataset for Exploration of High-Latitude Lunar Landscapes <!-- omit in toc -->
2+
This repository contains all the suplementary material used during the acquisition and proecessing of the [SPICE-HL3 dataset](https://zenodo.org/records/13970078?preview=1).
3+
4+
All the details about this dataset can be found in the associated manuscript.
5+
6+
[![arXiv](https://img.shields.io/badge/arXiv-1234.56789-b31b1b.svg)](https://arxiv.org/abs/2506.22956)
7+
[![Static Badge](https://img.shields.io/badge/YouTube-video-red?style=flat)](https://youtu.be/d7sPeO50_2I)
8+
[![Static Badge](https://img.shields.io/badge/Zenodo-dataset-blue)](https://zenodo.org/records/13970078?preview=1)
9+
10+
11+
![spice-hl3_cover ](/img/dataset_cover.png)
12+
13+
Authors:
14+
- David Rodríguez Martínez [![orcid](https://orcid.org/sites/default/files/images/orcid_16x16.png)](https://orcid.org/0000-0003-4817-9225)
15+
- Dave van der Meer [![orcid](https://orcid.org/sites/default/files/images/orcid_16x16.png)](https://orcid.org/0000-0002-7892-9704)
16+
- Junlin Song [![orcid](https://orcid.org/sites/default/files/images/orcid_16x16.png)](https://orcid.org/0000-0001-9690-7253)
17+
- Abishek Bera [![orcid](https://orcid.org/sites/default/files/images/orcid_16x16.png)](https://orcid.org/0000-0002-0196-5969)
18+
- C.J Pérez del Pulgar [![orcid](https://orcid.org/sites/default/files/images/orcid_16x16.png)](https://orcid.org/0000-0001-5819-8310)
19+
- Miguel Angel Olivares-Mendez [![orcid](https://orcid.org/sites/default/files/images/orcid_16x16.png)](https://orcid.org/0000-0001-8824-3231)
20+
21+
Cite this dataset:
22+
> Rodríguez-Martínez, D., van der Meer, D., Song, J., Abishek, B., Pérez del Pulgar, C.J., Olivares-Mendez, M.A. (2025). SPICE-HL3: Single-Photon, Inertial, and Stereo Camera dataset for Exploration of High-Latitude Lunar Landscapes. *in review*.
23+
24+
```
25+
@article{rodriguez2025spicehl3,
26+
title={{SPICE}-{HL3}: Single-Photon, Inertial, and Stereo Camera dataset for Exploration of High-Latitude Lunar Landscapes},
27+
author={Rodríguez-Martínez, David and van der Meer, Dave and Song, Junlin and Bera, Abishek and Pérez del Pulgar, C.J. and Olivares-Mendez, Miguel Angel},
28+
journal={in review},
29+
volume={},
30+
number={},
31+
pages={},
32+
year={2025}
33+
}
34+
```
35+
36+
## Updates <!-- omit in toc -->
37+
38+
- (2025-Jul-01) Leaderboard created.
39+
- (2025-Jun-16) Public release of dataset repository.
40+
- (2024-Dec-01) Dataset uploaded to Zenodo in restricted mode.
41+
- (2024-Sep-10) Lunalab testing campaign.
42+
43+
## Contents <!-- omit in toc -->
44+
- [SPAD512S Data Acquisition](#spad512s-data-acquisition)
45+
- [SPAD512S Data Reader](#spad512s-data-reader)
46+
- [Data structure](#data-structure)
47+
- [Compatibility](#compatibility)
48+
- [Fixing ROS2 and HW-sync issues](#fixing-ros2-and-hw-sync-issues)
49+
- [Data processing and evaluation](#data-processing-and-evaluation)
50+
- [Using ORB-SLAM3 on SPICE-HL3 data](#using-orb-slam3-on-spice-hl3-data)
51+
- [Leaderboard](#leaderboard)
52+
- [Submitting your results](#submitting-your-results)
53+
- [References](#references)
54+
- [Licensing](#licensing)
55+
56+
## SPAD512S Data Acquisition
57+
58+
As the SPAD512S GUI lacks native support for setting a fixed frame rate (i.e., capture X number of frames every Y ms), we developed a series of scripts to programmatically enable this functionality.
59+
60+
In particular, the scripts used were:
61+
- [*SPAD_1bit_capture.py*](/spad512-acquisition/SPAD_1bit_capture.py): capture a predefined number of frames at a given batch rate.
62+
- [*SPAD_1bit_cont.py*](/spad512-acquisition/SPAD_1bit_cont.py): capture a continuous stream of binary frames at a given batch rate.
63+
- [*multiexposure_launcher_SPAD.bat*](/spad512-acquisition/multiexposure_launcher_SPAD.bat): call the `SPAD_1bit_capture.py` script to acquire binary frames at five different exposure times (to be used on Windows).
64+
65+
## SPAD512S Data Reader
66+
This section contains a series of MATLAB scripts designed to read and export data acquired with [Pi-Imaging SPAD512S](https://piimaging.com/spad-512/) single-photon camera. It contains the following scripts:
67+
68+
- [*export_spad_frames.m*](/spad512-reader/export_spad_frames.m): This function reads all the .BIN files saved during a single acquisition and extracts and exports each 1-bit frame acquired as individual .PNG images.
69+
- [*digitize_from_1bit.m*](/spad512-reader/digitize_from_1bit.m): Once the 1-bit frames have been exported to PNG, this script can build up n-bit .PNG images out of those 1-bit frames. The required bit depth needs to be explicitly defined as an argument.
70+
- [*digitize_from_4bit.m*](/spad512-reader/digitize_from_4bit.n): Similarly, this script can integrate 4-bit .PNG images to export images of a desired bit depth (always > 4bit).
71+
- [*read_512Sbin.m*](/spad512-reader/read_512sbin.m): This is a function required by `export_spad_frames()`. This function contains the necessary code to extract and reconstruct the data from a .BIN file so that single 1-bit frames can be exported. This script is based on the `python_tcp_stream_binary_intensity1bit.py` file available in the SPAD512S system documentation[[1]](#references).
72+
- [*remap.m*](/spad512-reader/remap.m): This is a simple MATLAB function meant to remap n-bit .PNG images into an 8-bit colormap.
73+
74+
75+
>[!NOTE]
76+
> For additional information on any of these funtions, simply run `help [function_name]` in the command window.
77+
78+
### Data structure
79+
The previous scripts have been updated to work with any data structure given the appropriate updates (check function help text for more info). As is, however, the scripts are designed to work with the data structure created by default when images are recorded through Pi-Imaging's camera GUI.
80+
81+
> [!NOTE]
82+
> The scripts associated with the SPAD512S Data Reader are meant to work based on 1-bit or 4-bit native frame acquisitions.
83+
84+
From the SPAD512S, data is saved by default based on the following directory structure:
85+
```
86+
> ...
87+
> data
88+
> intensity_images
89+
> acq0000X
90+
> RAW00000.bin
91+
> RAW00001.bin
92+
> ...
93+
```
94+
95+
`RAW0000X.BIN` are binary files containing a maximum of 1000 1-bit frames (i.e., ~32MB). Multiple .BIN files will be saved during longer acquisitions.
96+
97+
The scripts are designed to work regardless of the number of .BIN files saved but always within single acquisitions (i.e., the file path to the `acq0000X` folder of choice needs to be passed as an argument to the function `export_spad_frames()`). The scripts will need to be updated for them to read data from multiple acquisitions at once (i.e., from multiple `acq0000X` folders).
98+
99+
### Compatibility
100+
101+
These scripts were tested on both MATLAB [R2023a](https://ch.mathworks.com/products/new_products/release2023a.html) and [R2024a](https://ch.mathworks.com/products/new_products/latest_features.html).
102+
103+
## Fixing ROS2 and HW-sync issues
104+
105+
Due to ROS2-related issues and potential hardware synchronization limitations, some raw captured data contained frames affected by partial delays or mismatched timestamps between the left and right cameras. To correct and clean the data, we used the following scripts, which we are sharing openly so that anyone can re-create the dataset from the raw data contained in the rosbags or to review how the data was processed prior to publication.
106+
107+
The raw data frames captured by the ZED2 stereo camera were processed following these steps:
108+
109+
1. Run the [*stereo_file_matching.m*](/data-cleaner/stereo_file_matching.m) script to remove all left-to-right mismatched frames, and viceversa. Just swap target and reference. Output `left/data` and `right/data` folders with equal number of frames.
110+
2. Run the [*clean_delayed_frames.m*](/data-cleaner/clean_delayed_frames.m) script on the left and right data folders filter out all heavily delayed frames. Output data folders with sequentially timestampped frames.
111+
3. Run the [*reID_frames.m*](/data-cleaner/reID_frames.m) script to, as the name suggest, reID all frames.
112+
4. Finally, conduct a quality check with [*finalcheck_left_right_frames.m*](/data-cleaner/finalcheck_left_right_frames.m) to confirm all timestamps associated with left camera frames have matching right camera frames.
113+
114+
## Data processing and evaluation
115+
116+
- [*disparity.py*](/data-processing/disparity.py): computes disparity maps for multiple stereo image and saves the output as PNG images.
117+
118+
### Using ORB-SLAM3 on SPICE-HL3 data
119+
120+
For details on how to adapt and run this dataset through ORB-SLAM3, check this implementation [2](#references), specifically the [Adapting my own data](https://github.com/drodriguezSRL/ORBSLAM3_implementation/blob/main/HOW.md#phase-5-adapting-my-own-data) section of the implementation log book.
121+
122+
## Leaderboard
123+
124+
This is a public leaderboard showcasing the performance of various Visual Odometry and SLAM methods evaluated on different trajectories from the SPICE-HL3 dataset. The goal is to provide a centralized, transparent comparison of state-of-the-art approaches using consistent benchmarks.
125+
126+
### Submitting your results
127+
128+
To have your results included in the leaderboard, please open a new issue titled "_Leaderboard Submission_" and include the following details:
129+
```
130+
- Method name
131+
- Sensor configuration (e.g., monocular, stereo, RGB, SPAD, stereo-inertial, etc.)
132+
- Evaluated trajectory(ies)
133+
- Absolute Trajectory Error (ATE) per trajectory:
134+
- RMSE [cm]
135+
- Max error [cm]
136+
- Reference (arXiv, conference, or journal publication describing your method )
137+
```
138+
139+
Once submitted, your entry will be reviewed and added to the leaderboard. We encourage reproducibility and transparency, feel free to include any links to code, pre-trained models, or logs. If the data provided with SPICE-HL3 has been preprocessed in any way, please include details as to the methods used.
140+
141+
>[!IMPORTANT]
142+
> Note that ATE must be computed using alignment based on the initial estimated pose (no global SE(3) alignment such as Horn's).
143+
144+
> [!NOTE]
145+
> To ensure fairness and comparability between methods evaluated on different subsets of the dataset, the ranking is based on a globally weighted average ATE, where each trajectory's contribution is weighted by its length. If a method fails to produce a valid estimated trajectory on a given sequence, a **penalty RMSE of 1 m and max ATE of 5 m** are applied for that trajectory, weighted by its length. This ensures that methods are fairly penalized for non-robust behavior.
146+
147+
### Fast Sequences SPICE-HL3 Leaderboard <!-- omit in toc -->
148+
149+
| Rank | Method | Sensor | Trj_A | Trj_B | Trj_C | Trj_D | Trj_E | Trj_F | Trj_G | avATE RMSE [cm] | avATE Max [cm] |
150+
|------|--------|--------|-------|-------|-------|-------|-------|-------|-------|-----------------|----------------|
151+
| 1 :fire: | **Wheel Odometry** | WODO | :heavy_minus_sign: | 34.66 (63.91) | 124.87 (213.86) | 85.40 (123.33) | :x: | :heavy_minus_sign: | :heavy_minus_sign: | 140.70 | 442.36 |
152+
| 2 | [**RTAB-Map**]( https://arxiv.org/abs/2403.06341) | Stereo | :heavy_minus_sign: | 64.77 (97.44)| :x: | :x: | 29.12 (35.29) | 64.50 (95.65) | 145.56 (202.12) | 253.50 | 965.61 |
153+
| 3 | [**ORB-SLAM3**](https://ieeexplore.ieee.org/document/9440682) | Stereo | :heavy_minus_sign: | 83.72 (135.86) | 83.23 (135.47) | :x: | 17.31 (31.72) | :x: | :x: | 841.15 | 4161.01 |
154+
| 4 | [**ORB-SLAM3**](https://ieeexplore.ieee.org/document/9440682) | Mono | :heavy_minus_sign: | 100.27 (168.82) | 94.04 (159.92) | :x: | :x: | :x: | :x: | 859.51 | 4247.57 |
155+
| 5 | **Inertial Odometry (naive)** | IMU | :heavy_minus_sign: | 473.97 (1183.15) | 928.11 (2341.06) | 816.84 (2384.15) | :x: | :x: | :x: | 938.21 | 4265.62 |
156+
157+
:heavy_minus_sign: : Denotes trajectories that have not been evaluated.
158+
159+
:x: : Describe trajectories in which localization is lost and unrecovered; the method fails to provide a final estimate.
160+
161+
## References
162+
1. [SPAD512S System Documentation](https://piimaging.com/doc-spad512s)
163+
2. [ORB-SLAM3 Dockerized Implementation](https://github.com/drodriguezSRL/ORBSLAM3_implementation)
164+
165+
## Licensing
166+
167+
The code is released under the [MIT License](LICENSE).
168+
169+
170+
171+
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
close all; clear all; clc;
2+
3+
img_dir = '.\trajectory_G\stereo\left';
4+
img_files = dir(fullfile(img_dir, '*.png')); % Adjust the file extension as needed
5+
6+
ids = zeros(length(img_files), 1);
7+
times = zeros(length(img_files),1);
8+
9+
% Loop through the files and extract the IDs
10+
for i = 1:length(img_files)
11+
12+
[~, name, ~] = fileparts(img_files(i).name); % Extract name without extension
13+
14+
% find the numeric ID
15+
id_str = regexp(name, '_(\d+)$', 'tokens'); % Match the pattern *_IDnumber
16+
17+
% Use regular expression to find the numeric timestamp [uncomment if
18+
% needed]
19+
pattern = 'stereo_left_([\d]+\.[\d]+)_\d+';
20+
%pattern = 'stereo_right_([\d]+\.[\d]+)_\d+';
21+
time_str = regexp(name, pattern, 'tokens');
22+
23+
if ~isempty(id_str)
24+
times(i) = str2double(time_str{1}{1}); % Convert the extracted timestamp to a number
25+
ids(i) = str2double(id_str{1}{1}); % convert extracted id to a number
26+
else
27+
warning('ID not found in file name: %s', img_files(i).name);
28+
end
29+
end
30+
31+
% Combine the files and IDs into a table for easier sorting
32+
file_data = table({img_files.name}', ids, times, 'VariableNames', {'FileName', 'ID','Timestamps'});
33+
34+
% Sort the table by the ID or timestamp column
35+
%sorted_table = sortrows(file_data, 'ID'); % by ID
36+
sorted_table = sortrows(file_data, 'Timestamps'); % by timestamp
37+
sorted_files = sorted_table.FileName;
38+
39+
%writecell(sorted_files, 'C:\Users\david\Desktop\animations\trajectory_F_sorted.csv');
40+
41+
%% Problem of delayed frames
42+
43+
% ID delayed frames
44+
[sorted_ids, sort_idx] = sort(ids);
45+
sorted_times_by_ids = times(sort_idx);
46+
47+
% check if times are increasing
48+
times_diff = diff(sorted_times_by_ids);
49+
50+
% filter only positive diferences
51+
positive_diffs = times_diff(times_diff>0);
52+
avg_time_per_frame = mean(positive_diffs); % average time per frame
53+
med_time_per_frame = median(positive_diffs); % median time per frame
54+
55+
% find delayed frames
56+
delay_flags = [times_diff < 0];
57+
problematic_ids = sorted_ids(delay_flags);
58+
59+
% correct timestamps by interpolation
60+
corrected_times = sorted_times_by_ids;
61+
to_be_deleted = 0;
62+
delete_files = zeros(size(times,1),1);
63+
64+
for i = 2:(length(sorted_times_by_ids)-1)
65+
if delay_flags(i)
66+
%fprintf('\n Delayed frame: %d', sorted_ids(i))
67+
%fprintf('\nOriginal timestamp associated with frame %d: %f', sorted_ids(i), sorted_times_by_ids(i))
68+
corrected_times(i) = corrected_times(i-1) + med_time_per_frame;
69+
%fprintf('\nNew corrected timestamp: %f', corrected_times(i))
70+
71+
next_time_diff = corrected_times(i+1)-corrected_times(i);
72+
if next_time_diff < 0
73+
%fprintf('\n \nISSUE!\nNext time difference: %f ', corrected_times(i+1)-corrected_times(i))
74+
%fprintf('\nTimestamp associated with frame %d: %f \n', sorted_ids(i+1), corrected_times(i+1))
75+
to_be_deleted = to_be_deleted + 1;
76+
delete_files(i+1) = 1;
77+
end
78+
end
79+
end
80+
81+
delete_flags = [delete_files > 0];
82+
delete_files_ids = sorted_ids(delete_flags);
83+
84+
fprintf('\nNumber of frames %d', length(times))
85+
fprintf('\nAverage time per frame %f ms', avg_time_per_frame*1e3)
86+
fprintf('\nMedian time per frame %f ms', med_time_per_frame*1e3)
87+
fprintf('\nFrame rate %f Hz', 1/med_time_per_frame)
88+
fprintf('\nNumber of problematic frames %d/%d', length(problematic_ids),length(times))
89+
fprintf('\nThat is a total of %f frames', 100*length(problematic_ids)/length(times))
90+
fprintf('\nNumber of frames still need to be deleted: %d frames (%f)\n', to_be_deleted, 100*to_be_deleted/length(times))
91+
92+
%% Correct file names
93+
94+
id_to_corrected_time = containers.Map(sorted_ids, corrected_times);
95+
counter = 0;
96+
97+
% Loop through original files to rename problematic ones
98+
for i = 1:height(file_data)
99+
file_id = file_data.ID(i);
100+
101+
% If this is a problematic ID
102+
if ismember(file_id, problematic_ids)
103+
old_name = file_data.FileName{i};
104+
old_full_path = fullfile(img_dir, old_name);
105+
106+
% Get the corrected timestamp
107+
new_time = id_to_corrected_time(file_id);
108+
109+
% Reconstruct new filename with interpolated timestamp
110+
% Assuming filename format: stereo_left_<timestamp>_<id>.png
111+
new_name = sprintf('stereo_left_%.6f_%d.png', new_time, file_id);
112+
%new_name = sprintf('stereo_right_%.6f_%d.png', new_time, file_id);
113+
new_full_path = fullfile(img_dir, new_name);
114+
115+
% Rename the file
116+
movefile(old_full_path, new_full_path);
117+
fprintf('Renamed %s --> %s\n', old_name, new_name);
118+
elseif ismember(file_id, delete_files_ids)
119+
file_name = file_data.FileName(i);
120+
file_path = fullfile(img_dir, file_name{1});
121+
122+
% Remove remaining delayed files
123+
fprintf('Deleting delayed file: %s\n', file_name{1});
124+
delete(file_path);
125+
counter = counter + 1;
126+
end
127+
end
128+
129+
fprintf('\nTotal number of frames deleted: %d frames (%f)\n', counter, 100*counter/length(times))
130+
131+
%% Quality Check
132+
new_img_files = dir(fullfile(img_dir, '*.png'));
133+
134+
new_ids = zeros(length(new_img_files), 1); % Preallocate for performance
135+
new_times = zeros(length(new_img_files),1);
136+
137+
% Loop through the files and extract the IDs
138+
for i = 1:length(new_img_files)
139+
% Get the file name
140+
[~, name, ~] = fileparts(new_img_files(i).name); % Extract name without extension
141+
142+
% Use regular expression to find the numeric ID
143+
id_str = regexp(name, '_(\d+)$', 'tokens'); % Match the pattern *_IDnumber
144+
145+
% Use regular expression to find the numeric timestamp [uncomment if
146+
% needed]
147+
pattern = 'stereo_left_([\d]+\.[\d]+)_\d+';
148+
%pattern = 'stereo_right_([\d]+\.[\d]+)_\d+';
149+
time_str = regexp(name, pattern, 'tokens');
150+
151+
if ~isempty(id_str)
152+
new_times(i) = str2double(time_str{1}{1}); % Convert the extracted timestamp to a number
153+
new_ids(i) = str2double(id_str{1}{1}); % convert extracted id to a number
154+
else
155+
warning('\nID not found in file name: %s', new_img_files(i).name);
156+
end
157+
end
158+
159+
new_file_data = table({new_img_files.name}', new_ids, new_times, 'VariableNames', {'FileName', 'ID','Timestamps'});
160+
161+
% Sort the table by the ID or timestamp column
162+
%sorted_table = sortrows(file_data, 'ID'); % by ID
163+
sorted_table = sortrows(new_file_data, 'Timestamps'); % by timestamp
164+
sorted_files = sorted_table.FileName;
165+
sorted_files = struct('name', sorted_files);
166+
frameIDs = zeros(length(sorted_files), 1);
167+
168+
% Extract frame IDs
169+
for i = 1:length(sorted_files)
170+
tokens = regexp(sorted_files(i).name, '_(\d+)\.png$', 'tokens');
171+
if ~isempty(tokens)
172+
frameIDs(i) = str2double(tokens{1}{1});
173+
else
174+
error('\nInvalid filename format at line %d: %s', i, fileNames(i));
175+
end
176+
end
177+
178+
% Check for misordered IDs
179+
misorderedIdx = find(diff(frameIDs) <= 0);
180+
if isempty(misorderedIdx)
181+
fprintf('\nSUCCESS!!! All frame IDs are in strictly increasing order.\n');
182+
%writecell(sorted_files, 'C:\Users\david\Downloads\trajectory_F\stereo\left\data.csv');
183+
else
184+
fprintf('\nMisordered frame IDs detected at line(s):\n');
185+
for idx = misorderedIdx'
186+
fprintf('\nLine %d: ID %d followed by Line %d: ID %d\n', ...
187+
idx, frameIDs(idx), idx+1, frameIDs(idx+1));
188+
189+
file_name = new_file_data.FileName(idx);
190+
file_path = fullfile(img_dir, file_name{1});
191+
192+
fprintf('Deleting file...\n')
193+
194+
% Remove remaining delayed files
195+
delete(file_path);
196+
counter = counter + 1;
197+
end
198+
new_file_data = table({new_img_files.name}', new_ids, new_times, 'VariableNames', {'FileName', 'ID','Timestamps'});
199+
% Sort the table by the ID or timestamp column
200+
%sorted_table = sortrows(file_data, 'ID'); % by ID
201+
sorted_table = sortrows(new_file_data, 'Timestamps'); % by timestamp
202+
sorted_files = sorted_table.FileName;
203+
%writecell(sorted_files, 'C:\Users\david\Downloads\trajectory_F\stereo\left\data.csv');
204+
end
205+
206+
fprintf('\nFinal number of frames deleted: %d frames (%f)\n', counter, 100*counter/length(times))

0 commit comments

Comments
 (0)