22. Taxon names photos in 1 folder uploadin to iNaturalist


Put a bunch of photos in a folder whose title starts with the taxon id. Each observation must be a single photo.

Run this script. It uploads every photo as an observation with the time and location from the photo.
Not sure it fits well in many people's workflow, but it might help others who want to make a script which matches what they do. If you happen to have a photo of a few hundred individuals of the same species this can very quickly post everything.

https://www.inaturalist.org/journal/glmory/21331-python-upload-script
https://www.inaturalist.org/journal/glmory/21331-python-upload-script

Python upload script

Since pyinaturalist recently came out I thought it would be a good time to try and write a script to automatically upload files to iNaturalist. Few if any example scripts are out there and this should make it easier for other people to write one which matches their workflow.

This script assumes a large number of photos of the same species. This might happen for example if you were trying to map every tree on a property. The workflow would consist of taking a single geotagged photo of each individual then separating out the photos so each one is in a folder which starts with its taxon ID. For example aphids would go in a folder named '52381' or '52381 Aphids' or '52381-Aphididae'

If you don't have python, I suggest installing Anaconda then pyinaturalist. You will then need to get an app ID.

Copy this script, paste it to a text file renamed to end in .py, add your user name, password, app id, secret, and the time zone of the photos. Then run the script. It should upload everything jpg file in the folder as the file you select.

https://www.inaturalist.org/observations/import#photo_import

https://www.inaturalist.org/journal/glmory/21331-python-upload-script

https://groups.google.com/forum/#!searchin/inaturalist/pyinaturalist%7Csort:date/inaturalist/iF-Nw1oKsBo/4yqfflaDAwAJ

https://pyinaturalist.readthedocs.io/en/latest/index.html

https://www.inaturalist.org/pages/api+reference

https://groups.google.com/forum/#!searchin/inaturalist/pyinaturalist%7Csort:date/inaturalist/PNfHggqoIYs/duIgqvopDgAJ

Input your user name here:

user = ''

Input your password here:

passw = ''

Input your app ID and secret here:

app = ''

secret = ''

Input the time zone for the photos here, options can be found at the

website below

https://gist.github.com/mjrulesamrat/0c1f7de951d3c508fb3a20b4b0b33a98

time_zone = 'America/Los_Angeles'

tkinter used to choose a file

from tkinter import filedialog

from tkinter import Tk

os used to get a folder name

import os

pillow used to get exif data from the photos

import PIL

This is used to upload the photos.

import pyinaturalist

from pyinaturalist.rest_api import create_observations

from pyinaturalist.rest_api import get_access_token

print("Running")

This code lets you choose a photo, can delete and replace with folder_name=''

root = Tk()

filename = filedialog.askopenfilename(initialdir = "/",

title = "Select one of the .jpg files in "

"the folder to be uploaded. All files in "

"the folder will be uploaded. The folder "

"name should start with the taxon number",

filetypes = (("jpeg files","*.jpg"),

("all files",".")))

root.withdraw()

folder_name = os.path.dirname(filename) +'/'

print('Uploading all photos in ' + folder_name + 'as a unique observation')

Makes a list of all files in the folder inside element 2 of a tuple

for file in os.walk(folder_name):

if file[0] == folder_name:

files = file

Creates list of all the file paths for every file in the folder.

file_paths = []

for file in files[2]: # All files are in files[2]

file_path = files[0] + file # files[0] has the path to the folder

file_paths.append(file_path) # Makes a big list of paths

This function returns the latitude and longitude of a .jpg image

def get_lat_long(image):

# Gets all the exif data from the photo

exif = {

PIL.ExifTags.TAGS[k]: v

for k, v in image._getexif().items()

if k in PIL.ExifTags.TAGS

}

# From all the exif data, pulls the GPS data

gps_info = exif.get('GPSInfo')

# The GPS data is in a odd format, so have to dig for it a bit. This was

# only tested on files lightroom tagged.

latitude_direction = str(gps_info.get(1)[0])

latitude_degrees = float(gps_info.get(2)[0][0])

minutes = float(gps_info.get(2)[1][0])

multiplier = float(gps_info.get(2)[1][1])

latitude_minutes = minutes/multiplier

# The sign is changed depending on if this is N or S

if latitude_direction == 'N' or latitude_direction == 'n':

latitude = latitude_degrees+latitude_minutes/60

elif latitude_direction == 'S' or latitude_direction == 's':

latitude = -(latitude_degrees+latitude_minutes/60)

longitude_direction = gps_info.get(3)[0]

longitude_degrees = gps_info.get(4)[0][0]

minutes = float(gps_info.get(4)[1][0])

multiplier = float(gps_info.get(4)[1][1])

longitude_minutes = minutes/multiplier

# The sign is changed depending on if this is E or W

if longitude_direction == 'E' or longitude_direction == 'e':

longitude = longitude_degrees+longitude_minutes/60

elif longitude_direction == 'W' or longitude_direction == 'w':

longitude = -(longitude_degrees+longitude_minutes/60)

latitude_longitude = [latitude, longitude]

# Returns a list with both latitude and longiude in decimal format.

return latitude_longitude

Pulls the date information from

def get_date(image):

# Gets all the exif data from the photo

exif = {

PIL.ExifTags.TAGS[k]: v

for k, v in img._getexif().items()

if k in PIL.ExifTags.TAGS

}

# Pulls the date and time from the exif format

date = exif.get('DateTime').split()[0]

time = exif.get('DateTime').split()[1]

# Reformats the date to use - instead of :

for character in date:

if character == ':':

date = date.replace(character, '-')

# Combines the date and time to match the format pyinaturalist wants,

date_time = str(date) + 'T' + str(time)

# returns a date and time formatted to submit to iNaturalist with

# pyinaturalist

return date_time

This presumes the name of the folder starts with the taxon number.It finds

the taxon number by looking at the folder name and taking all the digits it

sees. This allows you to name the folder "##### species name" to quickly

tell where photos go. For example anything in '52381-Aphididae' is uploaded

as an aphid.

def get_taxon(folder):

taxon = ''

folder =os.path.split(os.path.dirname(folder_name))[-1]

for character in folder:

if character.isdigit():

taxon = taxon + character

return taxon

This is getting a token to allow photos to be uploaded.

token = get_access_token(username=user, password=passw,

app_id=app,

app_secret=secret)

This goes to every file, checks if it is a jpg, gets the gps coordinates,

get the time, and uploads it to iNaturalist.

for file in file_paths:

if file[-3:] == 'jpg' or file[-3:] == 'JPG' or file[-3:] == 'Jpg':

print('Uploading ' + file)

try:

img = PIL.Image.open(file)

coordinates = get_lat_long(img)

except:

coordinates = 'No Coordinates'

try:

img = PIL.Image.open(file)

date_time = get_date(img)

except:

date_time = 'No Date or Time'

taxon = get_taxon(folder_name)

params = {'observation':

{'taxon_id': taxon, # Vespa Crabro

'observed_on_string': date_time,

'time_zone': time_zone,

'description': 'This is a test upload',

'tag_list': '',

'latitude': coordinates[0],

'longitude': coordinates[1],

'positional_accuracy': 50, # meters,

'observation_field_values_attributes':

[{'observation_field_id': '','value': ''}],

},}

r = create_observations(params=params, access_token=token)

new_observation_id = r[0]['id']

from pyinaturalist.rest_api import add_photo_to_observation

r = add_photo_to_observation(observation_id=new_observation_id,

file_object=open(file, 'rb'),

access_token=token)

print("Program complete")
Posted by glmory glmory, February 04, 2019 03:14
https://www.inaturalist.org/journal/glmory/21331-python-upload-script

https://www.inaturalist.org/taxa/flickr_tagger

הועלה ב-פברואר 4, 2019 01:53 אחה"צ על ידי ahospers ahospers

תגובות

But you're welcome to write your own script that posts through our API
https://www.inaturalist.org/pages/developers

Here's a snippet of posting users and obs over the API written in Ruby

require 'rubygems'
require 'rest_client'
require 'json'

First, enter your app_id, app_secret, and redirect_uri from

http://gorilla.inaturalist.org/oauth/applications/206

site = "http://gorilla.inaturalist.org"
app_id = '308714d38eaf78ed57c11c0790f639d7d05e86cb7564f641629116e5b3bea024'
app_secret = '8be0ee61e1b7858a14c050b982eb3e6447b2d674075f3d8c58c8759ed2ee02a6'
redirect_uri = 'http://www.bd.dix/utils/migratelanding.cfm'

Next, visit this link on your browser while logged in as 'tegenligger'

http://gorilla.inaturalist.org/oauth/authorize?client_id=308714d38eaf78ed57c11c0790f639d7d05e86cb7564f641629116e5b3bea024&redirect_uri=http%3A%2F%2Fwww.bd.dix%2Futils%2Fmigratelanding.cfm&response_type=code

and get your auth code

auth_code = "d9c5335b17c0ec05f7444b1673c675b16a9a4d77f2d499b852778888503760a3"

Next, get a token for tegenligger

payload = {
:client_id => app_id,
:client_secret => app_secret,
:code => auth_code,
:redirect_uri => redirect_uri,
:grant_type => "authorization_code"
}
response = RestClient.post("#{site}/oauth/token", payload)
token = JSON.parse(response)["access_token"]
headers = {"Authorization" => "Bearer #{token}"}

Now make a user using tegenligger's token

username = 'testuser1'
email = 'testuser1@bar.net'
password = 'testuser1password'

results = RestClient.post("#{site}/users.json", {"user[login]" =>
username, "user[email]" => email, "user[password]" => password,
"user[password_confirmation]" => password}, headers)
puts "created http://gorilla.inaturalist.org/users/#{JSON.parse(results)["id"]}"

Now get a token for testuser1

payload = {
:client_id => app_id,
:client_secret => app_secret,
:grant_type => "password",
:username => username,
:password => password
}
puts "POST #{site}/oauth/token, payload: #{payload.inspect}"
response_for_user1 = RestClient.post("#{site}/oauth/token", payload)
token_for_user1 = JSON.parse(response)["access_token"]
headers_for_user1 = {"Authorization" => "Bearer #{token}"}

Now make a observation on behalf of testuser1

results = RestClient.post("#{site}/observations.json",{
"observation[species_guess]" => "Northern Cardinal",
"observation[taxon_id]" => 9083,
"observation[observed_on_string]" => "2013-01-03",
"observation[time_zone]" => "Eastern Time (US %26 Canada)",
"observation[description]" => "what a cardinal",
"observation[tag_list]" => "foo,bar",
"observation[place_guess]" => "clinton, ct",
"observation[latitude]" => 41.27872259999999,
"observation[longitude]" => -72.5276073,
"observation[map_scale]" => 11,
"observation[location_is_exact]" => false,
"observation[positional_accuracy]" => 7798,
"observation[geoprivacy]" => "obscured"
}, headers_for_user1)

puts "created http://gorilla.inaturalist.org/observations/#{JSON.parse(results)[0]["id"]}"

Now make a another user using tegenligger's token

username = 'testuser2'
email = 'testuser2@bar.net'
password = 'testuser2password'

results = RestClient.post("#{site}/users.json", {"user[login]" =>
username, "user[email]" => email, "user[password]" => password,
"user[password_confirmation]" => password}, headers)
puts "created http://gorilla.inaturalist.org/users/#{JSON.parse(results)["id"]}"

Now get a token for testuser2

payload = {
:client_id => app_id,
:client_secret => app_secret,
:grant_type => "password",
:username => username,
:password => password
}
puts "POST #{site}/oauth/token, payload: #{payload.inspect}"
response_for_user2 = RestClient.post("#{site}/oauth/token", payload)
token_for_user2 = JSON.parse(response)["access_token"]
headers_for_user2 = {"Authorization" => "Bearer #{token}"}

Now make a observation on behalf of testuser2

results = RestClient.post("#{site}/observations.json",{
"observation[species_guess]" => "Northern Cardinal",
"observation[taxon_id]" => 9083,
"observation[observed_on_string]" => "2013-01-03",
"observation[time_zone]" => "Eastern Time (US %26 Canada)",
"observation[description]" => "what a cardinal",
"observation[tag_list]" => "foo,bar",
"observation[place_guess]" => "clinton, ct",
"observation[latitude]" => 41.27872259999999,
"observation[longitude]" => -72.5276073,
"observation[map_scale]" => 11,
"observation[location_is_exact]" => false,
"observation[positional_accuracy]" => 7798,
"observation[geoprivacy]" => "obscured"
}, headers_for_user2)

puts "created http://gorilla.inaturalist.org/observations/#{JSON.parse(results)[0]["id"]}"

פורסם על-ידי ahospers לפני יותר מ 5 שנים

e I’m not querying the API directly is the rate limit any higher? Here is the script I’m currently using to download 1 photo/sec for 10k/day:

import os
import pickle
import requests
import shutil
import sys
import time

def download(url, name):
print(name)
with requests.get(url, stream=True) as r:
if r.status_code != 200:
print('{} error: {}'.format(r.status_code, url), file=sys.stderr)
return
r.raw.decode_content = True
with open('./ducks/{}.jpg'.format(name), 'wb') as f:
shutil.copyfileobj(r.raw, f)

def main():
os.makedirs('ducks', exist_ok=True)
present = set([x.replace('.jpg', '') for x in os.listdir('ducks')])

with open('duck_urls', 'rb') as f:
urls = pickle.load(f)

ctr = 0
for url in urls:
if ctr > 10000:
return
name = os.path.normpath(url).split(os.path.sep)[-2]
if name in present:
continue
download(url, name)
ctr += 1
time.sleep(1)

if name == 'main':
main()

פורסם על-ידי ahospers לפני כמעט 4 שנים

הוספת תגובה

כניסה או הרשמה להוספת הערות