diff --git a/.gitignore b/.gitignore index 41ded9e..a62b1e5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ testsource testdest testdata usercomment.sh +__pycache__ diff --git a/functions.py b/functions.py new file mode 100644 index 0000000..25824be --- /dev/null +++ b/functions.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Classes used by main program. Handles CSV and GPX processing.""" + +from datetime import datetime + +class Radiation: + def __init__(self, timestamp, radiation, local_timezone, si_factor): + self.timestamp = self._time_conversion(timestamp, local_timezone) + self.radiation = self._radiation(radiation, si_factor) + + def __repr__(self): + return '%s %f µS/h' % (str(self.timestamp), self.radiation) + + def _time_conversion(self, timestamp, local_timezone): + csv_naive_time = datetime.fromisoformat(timestamp) + # Set timezone + csv_aware_time = csv_naive_time.astimezone(local_timezone) + return csv_aware_time + + def _radiation(self, radiation, si_factor): + # Convert CP/M to µS/h using si_factor + radiation = round(float(radiation) * si_factor, 2) + return radiation + +class Position: + def __init__(self, timestamp, latitude, longitude): + self.timestamp = timestamp + self.latitude = latitude + self.longitude = longitude diff --git a/rad_tag.py b/rad_tag.py new file mode 100644 index 0000000..23aa67f --- /dev/null +++ b/rad_tag.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Iterates over a bunch of .jpg or .cr2 files and matches +DateTimeOriginal from Exif tag to DateTime in a csv log +of a GeigerMuellerCounter and writes its value to the UserComment +Exif tag in µS/h""" + +from datetime import datetime, timedelta +import os +import shutil +import csv +import argparse +import pytz +import pyexiv2 +import gpxpy +from functions import Radiation, Position + +# SIFACTOR for GQ Geiger counters + +# 300 series: 0.0065 µSv/h / CPM +# 320 series: 0.0065 µSv/h / CPM +# 500 series: 0.0065 µSv/h / CPM +# 500+ series: 0.0065 µSv/h / CPM for the first tube +# 600 series: 0.0065 µSv/h / CPM +# 600+ series: 0.002637 µSv/h / CPM + +# Configure argument parser for cli options +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description='''A unix-tyle tool that + extracts GPS and/or radiation data from GPX/CSV files and writes + them into the Exif tags of given photos.''') +parser.add_argument('-si', '--sifactor', type=float, default=0.0065, + help='Factor to multiply recorded CPM with.') +parser.add_argument('-tz', '--timezone', type=str, metavar='Timezone', default='utc', + help='''Manually set timezone of CSV / and Photo timestamp, + defaults to UTC if omitted. This is useful, if the GPS-Logger + saves the time incl. timezone''') +parser.add_argument('-d', '--dry', action='store_true', + help='Dry-run, do not actually write anything.') +parser.add_argument('csv', metavar='CSV', type=str, + help='Geiger counter history file in CSV format.') +parser.add_argument('-g', '--gpx', metavar='GPX', type=str, + help='GPS track in GPX format') +parser.add_argument('photos', metavar='Photo', type=str, nargs='+', + help='One or multiple photo image files to process.') +parser.add_argument('-o', '--outdir', type=str, default='.', + help='Directory to output processed photos.') + +args = parser.parse_args() + +# Create timezone datetime object +local_timezone = pytz.timezone(args.timezone) + +# Inform the user about what is going to happen +if args.dry is not None: + if args.outdir == ".": + print('Modifying photos in place (overwrite)') + else: + print('Modifying photos in', str(args.outdir), '(copy)') +else: + print('Not modifying anything. Just print what would happen without --dry') + +# Print table header +print('{:<15} {:<25} {:<22}'.format('filename', 'date / time', 'Exif UserComment')) + +for srcphoto in args.photos: + # Get image file name out of path + photo_basename = os.path.basename(srcphoto) + + # Decide whether to modify photo in place or to copy it to outdir first + # Then set the destination file as 'photo' to work on + if args.outdir == ".": + photo = srcphoto + else: + # be os aware and use the correct directory delimiter for destfile + dstphoto = os.path.join(args.outdir, photo_basename) + # Don't copy image if in dry-run mode + if args.dry == 'True': + shutil.copy(srcphoto, dstphoto) + photo = dstphoto + else: + photo = srcphoto + + # Load Exif data from image + metadata = pyexiv2.ImageMetadata(photo) + metadata.read() + tag = metadata['Exif.Photo.DateTimeOriginal'] + # tag.value creates datetime object in pictime + picnaivetime = tag.value + # Set timezone + pictime = picnaivetime.astimezone(local_timezone) + + radiation_list = [] + position_list = [] + + # Import GeigerCounter log + with open(args.csv, "r") as f: + csvreader = csv.reader(filter(lambda row: row[0] != '#', f), + delimiter=',', skipinitialspace=True) + + for _, csvrawtime, csvrawcpm, _ in csvreader: + radiation = Radiation(csvrawtime, csvrawcpm, local_timezone, args.sifactor) + radiation_list.append(radiation) + print(radiation_list) + + # close CSV file + f.close() + + # Import GPX track(s) + if args.gpx is not None: + gpx_file = open(args.gpx, 'r') + gpxreader = gpxpy.parse(gpx_file) + + for waypoint in gpxreader.waypoints: + for track in gpxreader.tracks: + for segment in track.segments: + for point in segment.points: + # datetimes match with 1 minute precision + delta = timedelta(seconds=60) + if abs(point.time - pictime) < delta: + valuelist = [] + row = [point.time - pictime, point.latitude, point.longitude] + valuelist.append(row) + print(valuelist) + min(valuelist[0][0]) + #print(pictime, 'vs.', point.time, 'Delta:', pictime - point.time) +# print('Point at ({0},{1}) -> {2}'.format(point.latitude, point.longitude, point.time))