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:
parent
d347bbdf55
commit
c19a94374e
|
@ -1,5 +1,4 @@
|
||||||
|
testdata
|
||||||
testsource
|
testsource
|
||||||
testdest
|
testdest
|
||||||
testdata
|
|
||||||
usercomment.sh
|
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
|
@ -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
|
||||||
|
|
19
Readme.md
19
Readme.md
|
@ -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.
|
||||||
|
|
50
functions.py
50
functions.py
|
@ -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
|
||||||
|
|
24
rad_tag.py
24
rad_tag.py
|
@ -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'))
|
||||||
|
|
Loading…
Reference in New Issue