Created Radiation, Photo classes and slimmed down main program.

- Radiation creates a list with timezone_aware datetime and radiation in µS/h.
- Photo reads DateTimeOriginal from photo.
- Photo.write_exif compiles new metadata object and writes exif tags to photo.
- Documentd changes in CHANGELOG.md.
- Changed Readme.me to match changes in code.
- Removed obsolete line from .gitignore
This commit is contained in:
Marcus Scholz 2020-03-10 21:09:25 +01:00
parent d347bbdf55
commit c19a94374e
5 changed files with 75 additions and 29 deletions

3
.gitignore vendored
View File

@ -1,5 +1,4 @@
testdata
testsource testsource
testdest testdest
testdata
usercomment.sh
__pycache__ __pycache__

View File

@ -8,12 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added GPX parser - Added GPX parser
- Added optional dry-run modifier - Added optional dry-run modifier
- Made timezone-aware for timesources without timezone - Made timezone-aware for timesources without timezone
- Added optional parameter to override timezone to local timezone, defaults to localtime - Added optional parameter to override timezone to local timezone, defaults to utc
- Swith to oop style programming. Made code easier to read. - 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 ## [0.2] - 2020-02-05
### Added ### 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 copy function to be able to place files to outdir before modification
- added verbose output about what it going to happen - added verbose output about what it going to happen
- added table header for output - added table header for output

View File

@ -1,12 +1,14 @@
# radiation tagger # 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. 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 ## Dependencies
Right now it depends on the following non-core Python 3 libraries: 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
``` ```
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] [-o OUTDIR]
CSV Photo [Photo ...] 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. 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) Modifying photos in place (overwrite)
filename date / time Exif UserComment filename date / time Exif UserComment
DSC_0196.JPG 2020-03-03 18:33:33 NOT FOUND! 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'. 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) Modifying photos in testdest/ (copy)
filename date / time Exif UserComment filename date / time Exif UserComment
DSC_0196.JPG 2020-03-03 18:33:33 NOT FOUND! 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` `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 ### Using GeigerLog to download history
Now the program can be started by double-clicking `geigerlog` or by executing `./geigerlog` on the command prompt. 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" [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 ## 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. * 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. * 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.

View File

@ -1,14 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Classes used by main program. Handles CSV and GPX processing.""" ''' Classes used by main program. '''
from datetime import datetime from datetime import datetime
import pyexiv2
class Radiation: class Radiation:
''' Handles CSV processing.'''
def __init__(self, timestamp, radiation, local_timezone, si_factor): def __init__(self, timestamp, radiation, local_timezone, si_factor):
self.timestamp = self._time_conversion(timestamp, local_timezone) 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): def __repr__(self):
return '%s %f µS/h' % (str(self.timestamp), self.radiation) 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) csv_aware_time = csv_naive_time.astimezone(local_timezone)
return csv_aware_time 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 # Convert CP/M to µS/h using si_factor
radiation = round(float(radiation) * si_factor, 2) radiation = round(float(radiation) * si_factor, 2)
return radiation 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

View File

@ -1,20 +1,18 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- 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 DateTimeOriginal from Exif tag to DateTime in a csv log
of a GeigerMuellerCounter and writes its value to the UserComment 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 os
import shutil import shutil
import csv import csv
import argparse import argparse
import pytz import pytz
import pyexiv2
import gpxpy import gpxpy
from functions import Radiation from functions import Radiation, Photo
# SIFACTOR for GQ Geiger counters # SIFACTOR for GQ Geiger counters
@ -110,14 +108,14 @@ for src_photo in args.photos:
else: else:
photo = src_photo photo = src_photo
# Load Exif data from image pic_aware_time = Photo(photo, local_timezone)
metadata = pyexiv2.ImageMetadata(photo) print(photo_basename, pic_aware_time)
metadata.read()
tag = metadata['Exif.Photo.DateTimeOriginal'] # Here the matching magic has to happen
# tag.value creates datetime object in pictime
pic_naive_time = tag.value # Write exif data
# Set timezone # exif_tags = Photo.write_exif(radiation, latitude, longitude, args.dry)
pic_time = pic_naive_time.astimezone(local_timezone) # print(exif_tags)
# Print table header # Print table header
print('{:<15} {:<25} {:<22}'.format('filename', 'date / time', 'Exif UserComment')) print('{:<15} {:<25} {:<22}'.format('filename', 'date / time', 'Exif UserComment'))