diff --git a/imgs/20220612/161620.jpg b/imgs/20220612/161620.jpg new file mode 100644 index 0000000000000000000000000000000000000000..10a66a2cc19d180608f0692835a07d49fb1086aa Binary files /dev/null and b/imgs/20220612/161620.jpg differ diff --git a/imgs/image.db b/imgs/image.db new file mode 100644 index 0000000000000000000000000000000000000000..9e82a62e022d8472ff2ef6b8310e62eb7987dc70 Binary files /dev/null and b/imgs/image.db differ diff --git a/src/gripper.py b/src/gripper.py index aff5b9c44be8359d8ab606b02d396094f67e33ce..27b72215533b7f2414874aca6fb8068055dd86e7 100755 --- a/src/gripper.py +++ b/src/gripper.py @@ -2,9 +2,9 @@ TODO: Integrate with FarmbotClient or FarmbotYoloClient """ -from client import FarmbotClient -from creds import device_id -from creds import token +from utils.client import FarmbotClient +from utils.creds import device_id +from utils.creds import token GRIPPER_PIN = 12 GRIPPER_OPEN_STATE = 0 diff --git a/src/imgs/20220612/161646.jpg b/src/imgs/20220612/161646.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9960fdf894c0a8643f94b6145d02ff200d002830 Binary files /dev/null and b/src/imgs/20220612/161646.jpg differ diff --git a/src/imgs/20220612/161937.jpg b/src/imgs/20220612/161937.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3ea63ebdd449234c49ee086df68481156476d001 Binary files /dev/null and b/src/imgs/20220612/161937.jpg differ diff --git a/src/main.py b/src/main.py index c68b26834577376f28ee6adbdeb8bf66f9ee69db..d719b2a8023d4f8d9747b4c0ec0a6f81bd68707e 100644 --- a/src/main.py +++ b/src/main.py @@ -48,7 +48,6 @@ def remove_overlap(table_coordinate:DataFrame, tolerance=50.00)->DataFrame: return table_coordinate - def remove_temp(path: Path)-> None: ''' Clean temporary files, i.e., photos, location.txt, annotations diff --git a/src/move.py b/src/move.py index 3768b918c06a894d58bd0311faac64012f0eed35..afa038ffd45ee74d472ff4473201a08e631858fc 100644 --- a/src/move.py +++ b/src/move.py @@ -8,6 +8,7 @@ Note: it is for remote server, can ben replaced by a local script from argparse import ArgumentParser from logging import getLogger from os import path, makedirs, system +import sys from time import sleep, strftime, time #from serial import Serial, PARITY_NONE, STOPBITS_ONE, EIGHTBITS from requests.api import delete @@ -20,8 +21,8 @@ from datetime import timezone, datetime from dateutil.parser import parse from requests import get, delete -import creds -from client import FarmbotClient +import utils.creds as creds +from utils.client import FarmbotClient _SWEEEP_HEIGHT = 0 @@ -40,7 +41,7 @@ class Opts: def scan(img_path: Path, location_path: Path, # smaller delta - min_x=0, max_x=1300, min_y=0, max_y=1000, delta=1000, offset=0, flag=True) -> List: #里面的数字需要重新测量 + min_x=0, max_x=1175, min_y=0, max_y=974, delta=300, offset=0, flag=True) -> List: #里面的数字需要重新测量 ''' scan the bed at a certain height, first move along x axis, then y, like a zig zag; Taking pictures and record the location of the camera that corresponds to the picture @@ -76,7 +77,8 @@ def scan(img_path: Path, location_path: Path, # smaller delta client.move(0, 0, _SWEEEP_HEIGHT) # ensure moving from original for x, y in pts: client.move(x, y, _SWEEEP_HEIGHT) # move camera - take_photo(img_path) + #take_photo(img_path) + client.take_photo() client.shutdown() # write to img/location with open(path.join(location_path, "location.txt"), 'w') as f: @@ -85,14 +87,21 @@ def scan(img_path: Path, location_path: Path, # smaller delta return None -def take_photo(img_path: Path): - HERE = path.dirname(__file__) - IMG_DIR = path.join(HERE, img_path) +def take_photo(): + client = FarmbotClient(creds.device_id, creds.token) + client.take_photo() + # download image + system('python ./utils/download.py') + + +# def take_photo(img_path: Path): +# HERE = path.dirname(__file__) +# IMG_DIR = path.join(HERE, img_path) - with request.urlopen('http://localhost:8080/?action=snapshot') as photo: - filename = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + ".jpg" - with open(path.join(IMG_DIR, filename), mode="wb") as save_file: - save_file.write(photo.read()) +# with request.urlopen('http://localhost:8080/?action=snapshot') as photo: +# filename = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + ".jpg" +# with open(path.join(IMG_DIR, filename), mode="wb") as save_file: +# save_file.write(photo.read()) def simple_move(x: int, y: int, z: int) -> None: @@ -145,12 +154,14 @@ if __name__ == '__main__': destination_x = int(input('X:')) destination_y = int(input('Y:')) destination_z = int(input('Z:')) - photo = True if input('Take a photo or not?[Y/N]:') == 'Y' else False simple_move_start = time() - simple_move(destination_x, destination_y, destination_z, photo) + simple_move(destination_x, destination_y, destination_z) Logger.info(f'time cost {time()-simple_move_start}') elif arguments.mode == 2: scan(arguments.photo, arguments.locations, flag=False) + #take_photo(arguments.photo) + elif arguments.mode == 3: + take_photo() else: Logger.error('Wrong mode number {arguments.mode}') diff --git a/src/client.py b/src/utils/client.py similarity index 96% rename from src/client.py rename to src/utils/client.py index 22f34607d44c4b73a66ebc2fff10fcbf865dc4d7..8768294e2024a1470054068fbbb991d74c1cef33 100755 --- a/src/client.py +++ b/src/utils/client.py @@ -33,12 +33,12 @@ def read_pin_request(pin_number, pin_mode="digital"): return {"kind": "rpc_request", "args": {"label": ""}, - "body": [{"kind": "read_pin" + "body": [{"kind": "read_pin", "args": { "label": "pin" + str(pin_number), "pin_mode": modes[pin_mode] or (modes["digital"]), "pin_number": pin_number - } + }}]} def write_pin_request(pin_number, pin_value, pin_mode="digital"): modes = {"digital": 0, "analog": 1} @@ -58,7 +58,7 @@ def write_pin_request(pin_number, pin_value, pin_mode="digital"): ] } -def toggle_pin_request(pin_number) +def toggle_pin_request(pin_number): return {"kind": "rpc_request", "args": {"label": ""}, "body": [{"kind": "toggle_pin", diff --git a/src/utils/creds.py b/src/utils/creds.py new file mode 100644 index 0000000000000000000000000000000000000000..46be62284eae0f0b7881a135b128d16fc5663ef8 --- /dev/null +++ b/src/utils/creds.py @@ -0,0 +1,2 @@ +device_id="***REMOVED***" +token="***REMOVED***" diff --git a/src/utils/download.py b/src/utils/download.py new file mode 100644 index 0000000000000000000000000000000000000000..f47961cde83b142e87ed77492d22e2e148a09930 --- /dev/null +++ b/src/utils/download.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +from datetime import timezone +from dateutil.parser import parse +from image_db import ImageDB +import utils.creds as creds +import json +import os +import requests +import sys +import time + +# note: download only returns 100 at a time! +# note: we are currently ignoreing placeholders + +REQUEST_HEADERS = {'Authorization': 'Bearer ' + creds.token, 'content-type': "application/json"} + +image_db = ImageDB() +image_db.create_if_required() + +while True: + response = requests.get('https://my.farmbot.io/api/images', headers=REQUEST_HEADERS) + images = response.json() + print("#images", len(images)) + if len(images) == 0: + exit() + + at_least_one_dup = False + for image_info in images: + + if image_db.has_record_for_farmbot_id(image_info['id']): + print("IGNORE! dup", image_info['id']) + requests.delete("https://my.farmbot.io/api/images/%d" % image_info['id'], + headers=REQUEST_HEADERS) + at_least_one_dup = True + continue + + if 'placehold.it' in image_info['attachment_url']: + print("IGNORE! placeholder", image_info['id']) + continue + + # convert date time of capture from UTC To AEDT and extract + # a simple string version for local image filename + dts = parse(image_info['attachment_processed_at']) + dts = dts.replace(tzinfo=timezone.utc).astimezone(tz=None) + local_img_dir = "imgs/%s" % dts.strftime("%Y%m%d") + if not os.path.exists(local_img_dir): + os.makedirs(local_img_dir) + local_img_name = "%s/%s.jpg" % (local_img_dir, dts.strftime("%H%M%S")) + print(">", local_img_name) + + # download image from google storage and save locally + captured_img_name = image_info['meta']['name'] + if captured_img_name.startswith("/tmp/images"): + req = requests.get(image_info['attachment_url'], allow_redirects=True) + open(local_img_name, 'wb').write(req.content) + + # add entry to db + image_db.insert(image_info, dts, local_img_name) + + # post delete from cloud storage + requests.delete("https://my.farmbot.io/api/images/%d" % image_info['id'], + headers=REQUEST_HEADERS) + + if at_least_one_dup: + print("only at least one dup; give DELETEs a chance to work") + time.sleep(2) \ No newline at end of file diff --git a/src/utils/image_db.py b/src/utils/image_db.py new file mode 100644 index 0000000000000000000000000000000000000000..6153ba57d06a8ad250e27949ac8e0236819cade4 --- /dev/null +++ b/src/utils/image_db.py @@ -0,0 +1,100 @@ +# image db helper + +#from calculate_detections import Detection +import sqlite3 +import json + +class ImageDB(object): + def __init__(self, image_db_file='/home/bot/farmbot/farmbot_yolo/imgs/image.db', check_same_thread=True): + self.conn = sqlite3.connect(image_db_file, check_same_thread=check_same_thread) + + def create_if_required(self): + # called once to create db + c = self.conn.cursor() + try: + c.execute('''create table imgs ( + id integer primary key autoincrement, + farmbot_id integer, + capture_time text, + x integer, + y integer, + z integer, + api_response text, + filename text, + detections_run integer + )''') + except sqlite3.OperationalError: + # assume table already exists? clumsy... + pass + try: + c.execute('''create table detections ( + id integer primary key autoincrement, + img_id integer, + theta integer, + entity text, + score real, + x0 integer, + y0 integer, + x1 integer, + y1 integer + )''') + except sqlite3.OperationalError: + # assume table already exists? clumsy... + pass + + + def has_record_for_farmbot_id(self, farmbot_id): + c = self.conn.cursor() + c.execute("select farmbot_id from imgs where farmbot_id=?", (farmbot_id,)) + return c.fetchone() is not None + + def imgs_for_coords(self, x, y, z): + c = self.conn.cursor() + if z is None: + c.execute("select id, x, y, z, filename from imgs where x=? and y=? order by capture_time", (x, y, )) + else: + c.execute("select id, x, y, z, filename from imgs where x=? and y=? and z=? order by capture_time", (x, y, z, )) + return c.fetchall() + + def img_id_for_filename(self, filename): + c = self.conn.cursor() + c.execute("select id from imgs where filename=?", (filename,)) + f = c.fetchone() + if f is None: return f + return f[0] + + def insert(self, api_response, dts, filename): + farmbot_id = api_response['id'] + capture_time = dts.strftime("%Y-%m-%d %H:%M:%S") + x, y, z = map(int, [api_response['meta'][c] for c in ['x', 'y', 'z']]) + c = self.conn.cursor() + insert_values = (farmbot_id, capture_time, x, y, z, json.dumps(api_response), filename) + c.execute("insert into imgs (farmbot_id, capture_time, x, y, z, api_response, filename) values (?,?,?,?,?,?,?)", insert_values) + self.conn.commit() + + def x_y_counts(self, min_c=1): + c = self.conn.cursor() + c.execute("select x, y, count(*) as c from imgs group by x, y having c >= ?", (min_c,)) + records = c.fetchall() + return sorted(records, key=lambda r: -r[2]) # return sorted by freq + + def img_ids_without_detections(self): + c = self.conn.cursor() + c.execute("select id, filename from imgs where detections_run is null") + return c.fetchall() + + def insert_detections(self, img_id, detections): + c = self.conn.cursor() + if len(detections) > 0: + values = [(img_id, d.theta, d.entity, d.score, d.x0, d.y0, d.x1, d.y1) for d in detections] + c.executemany("insert into detections (img_id, theta, entity, score, x0, y0, x1, y1) values (?,?,?,?,?,?,?,?)", values) + c.execute("update imgs set detections_run=1 where id=?", (img_id,)) + self.conn.commit() + + def detections_for_img(self, filename): + c = self.conn.cursor() + c.execute("""select d.theta, d.entity, d.score, d.x0, d.y0, d.x1, d.y1 + from imgs i join detections d on i.id=d.img_id + where i.filename=? + order by score desc""", (filename,)) + #return list(map(Detection._make, c.fetchall())) \ No newline at end of file diff --git a/src/request_token.py b/src/utils/request_token.py similarity index 100% rename from src/request_token.py rename to src/utils/request_token.py