diff --git a/.gitignore b/.gitignore index a62b1e5..f8f9504 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ +testdata testsource testdest -testdata -usercomment.sh __pycache__ diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d4ebc..ecf64b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added GPX parser - Added optional dry-run modifier - Made timezone-aware for timesources without timezone -- Added optional parameter to override timezone to local timezone, defaults to localtime -- Swith to oop style programming. Made code easier to read. +- Added optional parameter to override timezone to local timezone, defaults to utc +- Refactored variable name_scheme. +- Switch to oop style programming. Made code easier to read. +- Put CSV processing and Exif reading / writing into a class. ## [0.2] - 2020-02-05 ### Added -- uses pyexiv2 instead of piexif which is also able to tag various camera raw formats, e. g. CR2 +- uses pyexiv2 instead of piexif which is also able to tag various camera raw formats, e. g. CR2 and is capable of writing properly formed UTF-8 strings. - added copy function to be able to place files to outdir before modification - added verbose output about what it going to happen - added table header for output diff --git a/Readme.md b/Readme.md index 604f1e2..c891f9f 100644 --- a/Readme.md +++ b/Readme.md @@ -1,12 +1,14 @@ # radiation tagger -exif_rad.py is a simple unix-style cross-platform Python 3 tool which can write certain tags to an image file. +rad_tag.py is a simple unix-style cross-platform Python 3 tool which can write certain tags to an image file. It can scan a couple of images, extract their Exif-tags, and compare the `DateTimeOriginal` with other sources. -By now it can parse a .his (CSV) file from a [GeigerLog](https://sourceforge.net/projects/Geigerlog/) file export and calculate the radiation in µS/h using the factor in `SIFACTOR`. +It can parse a .his (CSV) file from a [GeigerLog](https://sourceforge.net/projects/Geigerlog/) file export and calculate the radiation in µS/h using the factor in `sifactor`. -It then creates a `UserComment` Exif tag with the actual measured radiation at the time the photo has been taken. +Furthermore it can optionally read a gpx-file, compare the timestamps to 'DateTimeOriginal' and determine closest-matching latitude / longitude. If your gpx-file has times stored including the timezone, you can set --timezone to the local timezone, your camera / geiger counter ran at. + +It then creates a `UserComment` with the actual measured radiation at the time the photo has been taken and writes the geocoordinates into the appropiate Exif tags. ## Dependencies Right now it depends on the following non-core Python 3 libraries: @@ -32,7 +34,7 @@ These exported .his files look like this: ## Usage ``` -usage: exif_rad.py [-h] [-si SIFACTOR] [-tz Timezone] [-d] [-g GPX] +usage: rad_tag.py [-h] [-si SIFACTOR] [-tz Timezone] [-d] [-g GPX] [-o OUTDIR] CSV Photo [Photo ...] @@ -63,7 +65,7 @@ optional arguments: Use test.hisdb.his from current working dir and modify (overwrite) all .CR2 files in place. ``` -./exif_rad.py test.hisdb.his *.CR2 +./rat_tag.py test.hisdb.his *.CR2 Modifying photos in place (overwrite) filename date / time Exif UserComment DSC_0196.JPG 2020-03-03 18:33:33 NOT FOUND! @@ -74,7 +76,7 @@ DSC_0198.JPG 2020-03-03 22:18:13 Radiation ☢ 0.07 µS/h Use test.hisdb.his in folder 'testdata', read all .JPG files from 'testsource' and write them to 'testdest'. ``` -./exif_rad.py testdata/test.hisdb.his -o testdest/ testsource/*.JPG +./rad_tag.py testdata/test.hisdb.his -o testdest/ testsource/*.JPG Modifying photos in testdest/ (copy) filename date / time Exif UserComment DSC_0196.JPG 2020-03-03 18:33:33 NOT FOUND! @@ -99,6 +101,8 @@ The GMC* defaults are quite sane, but you might want to set the correct serial p `usbport = /dev/ttyUSB0` +GeigerLog can also use a bunch of other devices while still outputting a csv-file in compatible format. + ### Using GeigerLog to download history Now the program can be started by double-clicking `geigerlog` or by executing `./geigerlog` on the command prompt. @@ -110,11 +114,10 @@ GeigerLog now presents you a rendering of the radiation over time in its main wi [main_window]: images/geigerlog_main_window.png "GeigerLog Main Window with graph" -Once imported, you can export the history into a hisdb.his-file, which is basically the CSV-file `exif_rad.py` can process. Choose 'History' -> Save History Data into .his file (CSV)'. +Once imported, you can export the history into a hisdb.his-file, which is basically the CSV-file `rad_tag.py` can process. Choose 'History' -> Save History Data into .his file (CSV)'. ## future possibilities - * In the future it should also be able to do the same with a gpx-file to extract geolocations and to write them into the appropiate Exif-fields. * It might get a setup.py if I want to waste my time on it. * I might want to get rid of the requirement to use a bloated GUI application to download the history data off the Geigercounter. There must be a neat working command line tool. Maybe I'll write it myself. diff --git a/functions.py b/functions.py index ca347df..7f41ba0 100644 --- a/functions.py +++ b/functions.py @@ -1,14 +1,16 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""Classes used by main program. Handles CSV and GPX processing.""" +''' Classes used by main program. ''' from datetime import datetime +import pyexiv2 class Radiation: + ''' Handles CSV processing.''' def __init__(self, timestamp, radiation, local_timezone, si_factor): self.timestamp = self._time_conversion(timestamp, local_timezone) - self.radiation = self._radiation(radiation, si_factor) + self.radiation = self._radiation_conversion(radiation, si_factor) def __repr__(self): return '%s %f µS/h' % (str(self.timestamp), self.radiation) @@ -19,7 +21,49 @@ class Radiation: csv_aware_time = csv_naive_time.astimezone(local_timezone) return csv_aware_time - def _radiation(self, radiation, si_factor): + def _radiation_conversion(self, radiation, si_factor): # Convert CP/M to µS/h using si_factor radiation = round(float(radiation) * si_factor, 2) return radiation + +class Photo: + ''' Reads and writes Exif metadata''' + def __init__(self, photo, local_timezone): + self.get_date = self._get_creation_date(photo, local_timezone) + self.photo = photo + + def __repr__(self): + return 'Photo Creation Date: %s' % str(self.get_date) + + def _get_creation_date(self, photo, local_timezone): + # Load Exif data from photo + metadata = pyexiv2.ImageMetadata(photo) + metadata.read() + date = metadata['Exif.Photo.DateTimeOriginal'] + # date.value creates datetime object in pic_naive_time + pic_naive_time = date.value + # Set timezone + pic_aware_time = pic_naive_time.astimezone(local_timezone) + return pic_aware_time + + def write_exif(self, radiation, latitude, longitude, dry_run): + + ''' UNTESTED ! ''' + + metadata = pyexiv2.ImageMetadata(self.photo) + + # Set new UserComment + new_comment = 'Radiation ☢ ' + str(radiation) + ' µS/h' + # Exif tags to write + keys = ['Exif.Photo.UserComment', 'Exif.Photo.latitude', 'Exif.Photo.longitude'] + # Values to write + values = [new_comment, latitude, longitude] + + # Create metadata object with all data to write + for key, value in zip(keys, values): + metadata[key] = pyexiv2.ExifTag(key, value) + + # Write Exif tags to file, if not in dry-run mode + if dry_run == 'True': + metadata.write() + return new_comment diff --git a/rad_tag.py b/rad_tag.py index a532732..6b14f8e 100755 --- a/rad_tag.py +++ b/rad_tag.py @@ -1,20 +1,18 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""Iterates over a bunch of .jpg or .cr2 files and matches +''' 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""" +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 +from functions import Radiation, Photo # SIFACTOR for GQ Geiger counters @@ -110,14 +108,14 @@ for src_photo in args.photos: else: photo = src_photo - # Load Exif data from image - metadata = pyexiv2.ImageMetadata(photo) - metadata.read() - tag = metadata['Exif.Photo.DateTimeOriginal'] - # tag.value creates datetime object in pictime - pic_naive_time = tag.value - # Set timezone - pic_time = pic_naive_time.astimezone(local_timezone) + pic_aware_time = Photo(photo, local_timezone) + print(photo_basename, pic_aware_time) + + # Here the matching magic has to happen + + # Write exif data + # exif_tags = Photo.write_exif(radiation, latitude, longitude, args.dry) + # print(exif_tags) # Print table header print('{:<15} {:<25} {:<22}'.format('filename', 'date / time', 'Exif UserComment'))