Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Feat process fit #331

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
93fe01a
add fitparse to requirements.txt
zlavergne Mar 1, 2019
c6b49b3
initial commit for fit parsing
zlavergne Mar 6, 2019
3055e6e
Hardcoded 27 second offset to fit file start time.
zlavergne Mar 8, 2019
1bcc10c
Merge pull request #1 from KaartGroup/feat-process-fit
zlavergne Mar 8, 2019
1b0044a
Update setup.py
zlavergne Mar 9, 2019
1255687
changed the gps parsers to be able to take lists rather than a single…
zlavergne Mar 11, 2019
727224b
Merge pull request #2 from KaartGroup/feat-process-fit
zlavergne Mar 11, 2019
045dbf5
changed get_video_start_time to be a subprocess rather than ffprobe m…
zlavergne Mar 11, 2019
924b14d
added missing comma
zlavergne Mar 11, 2019
e2eb7b4
debug print for server
zlavergne Mar 11, 2019
fb075df
debug
zlavergne Mar 11, 2019
9a05003
mas debug
zlavergne Mar 11, 2019
aafd37e
added missing part of flag to ffmpeg
zlavergne Mar 11, 2019
9b4952a
fix try...catch block
zlavergne Mar 11, 2019
1e4fff2
added missing declaration for extract_geotag_properties
zlavergne Mar 14, 2019
003a153
updated fit_parser to use camera_events to sync the gps points with v…
zlavergne Mar 15, 2019
f6ae95d
removed unused library
zlavergne Mar 15, 2019
5d0ab47
updated flags
zlavergne Mar 15, 2019
ec4e384
Merge pull request #3 from KaartGroup/feat-process-fit
zlavergne Mar 15, 2019
5b6927e
remove comment from setup.py
zlavergne Mar 15, 2019
2b917f6
Merge branch 'master' of github.com:KaartGroup/mapillary_tools
zlavergne Mar 15, 2019
2d3c8f8
updated fit processing to correct the video time based on the correla…
zlavergne Mar 18, 2019
5f06be6
remove comment for testing
zlavergne Mar 18, 2019
48b1d67
fit parser offset is determined by the proper start time of the video…
zlavergne Mar 18, 2019
69af655
Merge pull request #4 from KaartGroup/feat-process-fit
zlavergne Mar 18, 2019
5b02958
comment Pillow for Synology NAS
zlavergne Mar 18, 2019
ea0e6fd
fixed `offset_time` implementation for fit parsing
zlavergne Mar 25, 2019
55cf48e
added int casting to make sure video id matches
zlavergne Apr 2, 2019
68511d9
remove unnecessary declaration
zlavergne Apr 3, 2019
5a81cc3
more robust video matching
zlavergne Apr 3, 2019
7417a21
fix offset time
zlavergne Apr 4, 2019
353ae17
removed unnecessary exit
zlavergne Apr 29, 2019
2406650
Merge tag 'v0.5.0'
tsmock Jul 22, 2019
f39d9e4
Dynamically get the correct dict entry for garmin fit files
tsmock Jul 22, 2019
848cd68
Add catch for StopIteration in parsing fit files
tsmock Jul 30, 2019
ea92efe
change camera_event_type to video_second_stream_*
zlavergne Jul 31, 2019
deebba7
FIXUP: Account for videos that start before the fit files start
tsmock Jul 31, 2019
d9a33cb
Merge pull request #5 from KaartGroup/feat-process-fit-bugfix1
Jul 31, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mapillary_tools/commands/extract_geotag_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def add_basic_arguments(self, parser):

def add_advanced_arguments(self, parser):
parser.add_argument('--geotag_source', help='Provide the source of date/time and gps information needed for geotagging.', action='store',
choices=['exif', 'gpx', 'gopro_videos', 'nmea', "blackvue_videos"], default="exif", required=False)
choices=['exif', 'gpx', 'gopro_videos', 'nmea', "blackvue_videos", 'garmin_fit'], default="exif", required=False)
parser.add_argument(
'--geotag_source_path', help='Provide the path to the file source of date/time and gps information needed for geotagging.', action='store',
default=None, required=False)
Expand Down
2 changes: 1 addition & 1 deletion mapillary_tools/commands/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def add_advanced_arguments(self, parser):

# geotagging
parser.add_argument('--geotag_source', help='Provide the source of date/time and gps information needed for geotagging.', action='store',
choices=['exif', 'gpx', 'gopro_videos', 'nmea', 'blackvue_videos'], default="exif", required=False)
choices=['exif', 'gpx', 'gopro_videos', 'nmea', 'blackvue_videos', 'garmin_fit'], default="exif", required=False)
parser.add_argument(
'--geotag_source_path', help='Provide the path to the file source of date/time and gps information needed for geotagging.', action='store',
default=None, required=False)
Expand Down
2 changes: 1 addition & 1 deletion mapillary_tools/commands/process_and_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def add_advanced_arguments(self, parser):

# geotagging
parser.add_argument('--geotag_source', help='Provide the source of date/time and gps information needed for geotagging.', action='store',
choices=['exif', 'gpx', 'gopro_videos', 'nmea', 'blackvue_videos'], default="exif", required=False)
choices=['exif', 'gpx', 'gopro_videos', 'nmea', 'blackvue_videos', 'garmin_fit'], default="exif", required=False)
parser.add_argument(
'--geotag_source_path', help='Provide the path to the file source of date/time and gps information needed for geotagging.', action='store',
default=None, required=False)
Expand Down
2 changes: 1 addition & 1 deletion mapillary_tools/commands/video_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def add_advanced_arguments(self, parser):

# geotagging
parser.add_argument('--geotag_source', help='Provide the source of date/time and gps information needed for geotagging.', action='store',
choices=['exif', 'gpx', 'gopro_videos', 'nmea', 'blackvue_videos'], default="exif", required=False)
choices=['exif', 'gpx', 'gopro_videos', 'nmea', 'blackvue_videos', 'garmin_fit'], default="exif", required=False)
parser.add_argument(
'--geotag_source_path', help='Provide the path to the file source of date/time and gps information needed for geotagging.', action='store',
default=None, required=False)
Expand Down
2 changes: 1 addition & 1 deletion mapillary_tools/commands/video_process_and_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def add_advanced_arguments(self, parser):

# geotagging
parser.add_argument('--geotag_source', help='Provide the source of date/time and gps information needed for geotagging.', action='store',
choices=['exif', 'gpx', 'gopro_videos', 'nmea', 'blackvue_videos'], default="exif", required=False)
choices=['exif', 'gpx', 'gopro_videos', 'nmea', 'blackvue_videos', 'garmin_fit'], default="exif", required=False)
parser.add_argument(
'--geotag_source_path', help='Provide the path to the file source of date/time and gps information needed for geotagging.', action='store',
default=None, required=False)
Expand Down
83 changes: 83 additions & 0 deletions mapillary_tools/fit_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python

from fitparse import FitFile
import datetime
from tqdm import tqdm

'''
Methods for parsing gps data from Garmin FIT files
'''


def parse_uuid_string(uuid_string):
'''
Parses a video uuid string from fit similar to: VIRBactioncameraULTRA30_Timelapse_3840_2160_1.0000_3936073999_36a2eff0_1_111_2019-01-17-09-01-03.fit
Returns a tuple (camera_type, video_type, width, height, frame_rate, serial, unknown_1, unkown_2, video_id, fit_filename)
'''
return tuple(uuid_string.split("_"))


def get_lat_lon_time_from_fit(geotag_file_list, local_time=True, verbose=False):
'''
Read location and time stamps from a track in a FIT file.

Returns a tuple (video_start_time, points) where points is a list of tuples (time, lat, lon, altitude)
'''
vids = {}
for geotag_file in geotag_file_list:

alt = None
lat = None
lon = None
time_delta = None
try:
fit = FitFile(geotag_file)

vid_times = {}

timestamp_correlations = fit.get_messages(162)
timestamp_correlation = next(timestamp_correlations).get_values()
timestamp = timestamp_correlation['local_timestamp']
offset = datetime.timedelta(seconds=timestamp_correlation['system_timestamp'],
milliseconds=timestamp_correlation['system_timestamp_ms'])
start_time = timestamp - offset
camera_events = (c for c in fit.get_messages(161) if c.get('camera_event_type').value in ['video_second_stream_start', 'video_second_stream_end'])
for start in tqdm(camera_events, desc='Extracting Video data from .FIT file'):
vid_id = parse_uuid_string(start.get('camera_file_uuid').value)[-2]
end = next(camera_events)
start_timedelta = datetime.timedelta(seconds=start.get('timestamp').value, milliseconds=start.get('timestamp_ms').value)
start_timestamp = start_time + start_timedelta
end_timedelta = datetime.timedelta(seconds=end.get('timestamp').value, milliseconds=end.get('timestamp_ms').value)
end_timestamp = start_time + end_timedelta
vid_times[vid_id] = (start_timestamp, end_timestamp)

points = []
for vid_id, times in tqdm(vid_times.items(), desc='Extracting GPS data from .FIT file'):
gps_metadata = (g for g in fit.get_messages(160) if times[0] <= (start_time + datetime.timedelta(seconds=g.get('timestamp').value, milliseconds=g.get('timestamp_ms').value)) <= times[-1])
for gps in gps_metadata:
try:
alt = gps.get('enhanced_altitude').value
lat_in_semicircles = gps.get('position_lat').value
lat = float(lat_in_semicircles) * 180 / 2**31
lon_in_semicircles = gps.get('position_long').value
lon = float(lon_in_semicircles) * 180 / 2**31
time_delta = datetime.timedelta(seconds=gps.get('timestamp').value, milliseconds=gps.get('timestamp_ms').value)
wp_datetime = start_time + time_delta
except:
continue
if alt is not None and lat is not None and lon is not None and wp_datetime is not None and times[0] <= wp_datetime <= times[-1]:
points.append((wp_datetime, lat, lon, alt))
try:
vids[int(vid_id)] = (times[0], sorted(points))
except:
vids[vid_id] = (times[0], sorted(points))

except ValueError:
if verbose:
print("Warning: {} is not formatted properly".format(geotag_file))
pass
except StopIteration:
if verbose:
print("Warning: {} does not have enough iterations".format(geotag_file))
pass
return vids
4 changes: 4 additions & 0 deletions mapillary_tools/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ def write_gpx(filename, gps_trace):
fout.write(gpx)


def semicircle_to_degrees(semicircle):
decimal = semicircle * (180.0/(2**31))
return decimal

def get_timezone_and_utc_offset(lat, lon):
tz = tzwhere.tzwhere(forceTZ=True) #TODO: This library takes 2 seconds to initialize. Should be done only once if used for many videos
timezone_str = tz.tzNameAt(lat, lon)
Expand Down
76 changes: 39 additions & 37 deletions mapillary_tools/gps_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
'''


def get_lat_lon_time_from_gpx(gpx_file, local_time=True):
def get_lat_lon_time_from_gpx(gpx_file_list, local_time=True):
'''
Read location and time stamps from a track in a GPX file.

Expand All @@ -26,28 +26,29 @@ def get_lat_lon_time_from_gpx(gpx_file, local_time=True):
GPX stores time in UTC, by default we assume your camera used the local time
and convert accordingly.
'''
with open(gpx_file, 'r') as f:
gpx = gpxpy.parse(f)

points = []
if len(gpx.tracks) > 0:
for track in gpx.tracks:
for segment in track.segments:
for point in segment.points:
t = utc_to_localtime(point.time) if local_time else point.time
points.append((t, point.latitude, point.longitude, point.elevation))
if len(gpx.waypoints) > 0:
for point in gpx.waypoints:
t = utc_to_localtime(point.time) if local_time else point.time
points.append((t, point.latitude, point.longitude, point.elevation))
for gpx_file in gpx_file_list:
with open(gpx_file, 'r') as f:
gpx = gpxpy.parse(f)

if len(gpx.tracks) > 0:
for track in gpx.tracks:
for segment in track.segments:
for point in segment.points:
t = utc_to_localtime(point.time) if local_time else point.time
points.append((t, point.latitude, point.longitude, point.elevation))
if len(gpx.waypoints) > 0:
for point in gpx.waypoints:
t = utc_to_localtime(point.time) if local_time else point.time
points.append((t, point.latitude, point.longitude, point.elevation))

# sort by time just in case
points.sort()

return points


def get_lat_lon_time_from_nmea(nmea_file, local_time=True):
def get_lat_lon_time_from_nmea(nmea_file_list, local_time=True):
'''
Read location and time stamps from a track in a NMEA file.

Expand All @@ -56,29 +57,30 @@ def get_lat_lon_time_from_nmea(nmea_file, local_time=True):
GPX stores time in UTC, by default we assume your camera used the local time
and convert accordingly.
'''
with open(nmea_file, "r") as f:
lines = f.readlines()
lines = [l.rstrip("\n\r") for l in lines]

# Get initial date
for l in lines:
if "GPRMC" in l:
data = pynmea2.parse(l)
date = data.datetime.date()
break

# Parse GPS trace
points = []
for l in lines:
if "GPRMC" in l:
data = pynmea2.parse(l)
date = data.datetime.date()

if "$GPGGA" in l:
data = pynmea2.parse(l)
timestamp = datetime.datetime.combine(date, data.timestamp)
lat, lon, alt = data.latitude, data.longitude, data.altitude
points.append((timestamp, lat, lon, alt))
for nmea_file in nmea_file_list:
with open(nmea_file, "r") as f:
lines = f.readlines()
lines = [l.rstrip("\n\r") for l in lines]

# Get initial date
for l in lines:
if "GPRMC" in l:
data = pynmea2.parse(l)
date = data.datetime.date()
break

# Parse GPS trace
for l in lines:
if "GPRMC" in l:
data = pynmea2.parse(l)
date = data.datetime.date()

if "$GPGGA" in l:
data = pynmea2.parse(l)
timestamp = datetime.datetime.combine(date, data.timestamp)
lat, lon, alt = data.latitude, data.longitude, data.altitude
points.append((timestamp, lat, lon, alt))

points.sort()
return points
40 changes: 40 additions & 0 deletions mapillary_tools/gpx_from_fit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from fitparse import FitFile
from geo import semicircle_to_degrees, utc_to_localtime, write_gpx
from tqdm import tqdm

def get_points_from_fit(file_list, local_time=False, verbose=False):
'''
Read location and time stamps from a track in a FIT file.

Returns a list of tuples (time, lat, lon, altitude)
'''
data = []
for file in file_list:
point = ()
try:
fit = FitFile(file)

messages = fit.get_messages('gps_metadata')
for record in tqdm(messages, desc='Extracting GPS data from .FIT file'):
timestamp = record.get('utc_timestamp').value
timestamp = utc_to_localtime(timestamp) if local_time else timestamp
lat = semicircle_to_degrees(record.get('position_lat').value)
lon = semicircle_to_degrees(record.get('position_long').value)
altitude = record.get('altitude').value
point = point + (timestamp, lat, lon, altitude)

except ValueError:
if verbose:
print("File {} not formatted properly".format(file))
pass
if point:
data.append(point)
return data


def gpx_from_fit(file_list, import_path, verbose=False):
data = get_points_from_fit(file_list)
data = sorted(data, key=lambda x: x[0])
gpx_path = import_path + '.gpx'
write_gpx(gpx_path, data)
return gpx_path
9 changes: 9 additions & 0 deletions mapillary_tools/process_geotag_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,13 @@ def process_geotag_properties(import_path,
sub_second_interval,
use_gps_start_time,
verbose)
elif geotag_source == "garmin_fit":
geotag_properties = processing.geotag_from_garmin_fit(process_file_list,
geotag_source_path,
offset_time,
offset_angle,
local_time,
sub_second_interval,
use_gps_start_time,
verbose)
print("Sub process ended")
8 changes: 6 additions & 2 deletions mapillary_tools/process_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def timestamp_from_filename(video_filename,
seconds = (int(filename.rstrip(".jpg").replace("{}_".format(
video_filename), "").lstrip("0")) - 1) * interval * adjustment


return start_time + datetime.timedelta(seconds=seconds)


Expand Down Expand Up @@ -116,7 +117,8 @@ def extract_frames(video_file,
'-i', video_file,
'-loglevel', 'quiet',
'-vf', 'fps=1/{}'.format(video_sample_interval),
'-qscale', '1', '-nostdin'
'-qscale', '1', '-nostdin',
'-qmin', '1', '-qmax', '1'
]

command.append('{}_%0{}d.jpg'.format(os.path.join(
Expand Down Expand Up @@ -193,7 +195,9 @@ def get_video_end_time(video_file):
print("Error, video file {} does not exist".format(video_file))
return None
try:
time_string = FFProbe(video_file).video[0].creation_time
# time_string = FFProbe(video_file).video[0].creation_time
result = subprocess.Popen(["ffprobe", video_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
time_string = next(iter(x.split(': ')[-1] for x in result.stdout.readlines() if "creation_time" in x)).strip("\n")
try:
creation_time = datetime.datetime.strptime(
time_string, TIME_FORMAT)
Expand Down
Loading