From a5e7e6f616fe301210fea33ec7ff88b7eca60bdb Mon Sep 17 00:00:00 2001 From: jonas Date: Mon, 12 Oct 2020 08:58:53 +0200 Subject: Simple python application that uses the O-3000 driver and Color Image Pipeline using a Python C-Extension. --- image-record-annotate.py | 332 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 image-record-annotate.py (limited to 'image-record-annotate.py') diff --git a/image-record-annotate.py b/image-record-annotate.py new file mode 100644 index 0000000..d206bca --- /dev/null +++ b/image-record-annotate.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import sys +#from tkinter import * +import tkinter as tk +from tkinter import ttk +from PIL import ImageTk, Image +import numpy as np +import o3000 +import cv2 +from datetime import datetime + + +import code # entering the interactive mode at a certain point: code.interact(local=dict(globals(), **locals())), Ctrl+D and the script goes on + + +IMAGE_WIDTH = 1280 +IMAGE_HEIGHT = 960 + + +class ImageLabel(tk.Label): + def __init__(self, *args, **kwargs): + super(ImageLabel,self).__init__(*args, **kwargs) + self.image_update(255*np.ones([IMAGE_HEIGHT,IMAGE_WIDTH,3], dtype=np.uint8)) + + def image_update(self, image): + ''' + :param image: A numpy array or a PIL image object, refere to https://pillow.readthedocs.io/en/stable/reference/Image.html + ''' + if isinstance(image, Image.Image): + pass + elif isinstance(image, np.ndarray): + #code.interact(local=dict(globals(), **locals())) + image = Image.fromarray(image) + else: + raise Exception("illegal image format, must be eighter a numpy array or a PIL image object") + self._image = ImageTk.PhotoImage(image) + self.configure(image=self._image) + + +class StringEntry(tk.Entry): + def __init__(self, master, *args, **kwargs): + self._variable = tk.StringVar() + self.name = '' + super(StringEntry,self).__init__(master=master,text=self._variable, *args, **kwargs) + + @property + def name(self): + return self._variable.get() + + @name.setter + def name(self, name): + self._variable.set(name) + +class FloatEntry(tk.Entry): + def __init__(self, master, scaling = 1.0, *args, **kwargs): + self._scaling = scaling + self._variable = tk.StringVar() + self.value = '' + vcmd = (master.register(self.validate_digit)) + super(FloatEntry,self).__init__(master=master,text=self._variable, *args, **kwargs, + validate='all', validatecommand=(vcmd, '%P')) + @property + def scaling(self): + return self._scaling + @property + def value(self): + try: + return float(self._variable.get())/self.scaling + except ValueError: + return None + @value.setter + def value(self, value): + if self.validate_digit(value): + try: self._variable.set(float(value)*self.scaling) + except ValueError as e: print('FloatEntry ignore value {}, it is not a float'.format(value)) + else: + print('FloatEntry ignore value {}'.format(value)) + def validate_digit(self, P): + if P == '': return True + try: + _val = float(P) + if _val < 0.0: return False + else: return True + except ValueError: return False + + +class ImageControlPanel(tk.Frame): + def __init__(self, save_fcn, *args, **kwargs): + super(ImageControlPanel,self).__init__(*args, **kwargs) + + tk.Label(self,text='Filename: ').grid(column=0, row=0) + self.filename_entry = StringEntry(self, bg='white') + self.filename_entry.grid(column=1, row=0) + tk.Label(self,text='.tiff').grid(column=2, row=0) + + def btn_save_fcn(): + save_fcn(self.filename_entry.name) + + self.save_btn = tk.Button(self, text="save", command=btn_save_fcn) + self.save_btn.grid(column=1, row=1) + + +class CamControlPanel(tk.Frame): + def __init__(self, *args, **kwargs): + super(CamControlPanel,self).__init__(*args, **kwargs) + + tk.Label(self, text="Sensitivity:").grid(column=0, row=0) + self.sensitivity = FloatEntry(self, width=7, bg='white') + self.sensitivity.grid(column=1, row=0) + tk.Label(self, text="%").grid(column=2, row=0) + + + tk.Label(self, text="Exposure Time:").grid(column=0, row=1) + self.exposure_time = FloatEntry(self, scaling=1000.0, width=7, bg='white') + self.exposure_time.grid(column=1, row=1) + tk.Label(self, text="ms").grid(column=2, row=1) + + def configure(): + o3000.video_xml_send("time".format(self.exposure_time.value)) + o3000.video_xml_send("sensitivity{:f}".format(self.sensitivity.value)) + + self.btn_configure = tk.Button(self, text="send configuration", command=configure) + self.btn_configure.grid(column=0, row=3, columnspan=3) + + # TODO TODO these are hardcoded default values + self.exposure_time.value = 0.02 + self.sensitivity.value = 1.5 + configure() + + + + + +class AnnotationPanel(tk.Frame): + _PERSONS = ["nobody", "kris", "stefan", "sophia", "jonas", "juerg", "petar"] + _PERSONS = ["nobody", "kris", "stefan", "sophia", "jonas", "juerg", "petar"] + _POSES = ["no pose", "sleeping", "typing", "writting", "tinkering"] + + def __init__(self, *args, **kwargs): + super(AnnotationPanel,self).__init__(*args, **kwargs) + + + tk.Label(self,text='Date:').grid(column=0, row=0) + self.date_var = tk.StringVar() + tk.Label(self,textvariable=self.date_var).grid(column=1, row=0) + + tk.Label(self,text='Time:').grid(column=0, row=1) + self.time_var = tk.StringVar() + tk.Label(self,textvariable=self.time_var).grid(column=1, row=1) + + + tk.Label(self,text='Person:').grid(column=0, row=2, sticky='w') + self.person_list = tk.Listbox(self, exportselection=False) + self.person_list.grid(column=0, row=3) + + [self.person_list.insert(tk.END, item) for item in self._PERSONS] + + tk.Label(self,text='Pose:').grid(column=1, row=2, sticky='w') + self.pose_list = tk.Listbox(self, selectmode=tk.SINGLE, exportselection=False) + self.pose_list.grid(column=1, row=3) + [self.pose_list.insert(tk.END, item) for item in self._POSES] + + tk.Label(self,text='Comment: ').grid(column=0, row=4, columnspan=2) + self.comment_text = tk.Text(self, bg='white', width=50, height=5) + self.comment_text.grid(column=0, row=5, columnspan=2) + + + + def _list_selection_get(self, listbox): + ''' + :param: a tkinter Listbox widget + :return: text of selected line in the given Listbox widget + ''' + sel = listbox.curselection() + if len(sel) == 0: + return 'unkown' + elif len(sel) == 1: + return listbox.get(sel[0],sel[0])[0] + else: + raise Exception('only single selection allowed') + + def update_date_time(self): + ''' + Updates annotation information to the current date and time + ''' + now = datetime.now() + self.date_var.set(now.strftime("%Y-%m-%d")) + self.time_var.set(now.strftime("%H:%M:%S")) + + @property + def pose(self): + ''' + :return: the selected pose name + ''' + return self._list_selection_get(self.pose_list) + + @property + def person(self): + ''' + :return: the selected pose name + ''' + return self._list_selection_get(self.person_list) + + @property + def date(self): + return self.date_var.get() + + @property + def time(self): + return self.time_var.get() + + @property + def comment(self): + return self.comment_text.get(1.0,tk.END) + + + +def annotation_text(filenames, annotation_panel, cam_control_panel): + ''' + build the annotation text for the annotation file + ''' + + ap = annotation_panel + + _str = 'raw camera data (not debayered): {:s}\n'.format(filenames[0]) + _str += 'color image (without lense calibration): {:s}\n'.format(filenames[1]) + _str += 'calibrated color image (lense calibrated): {:s}\n'.format(filenames[2]) + + _str += '\n' + + _str += 'camera: O-3020 (color, rolling shutter, serial no.: 10030)\n' + _str += 'lense: C-Mount 6mm 1/2.7" IR MP, with IR cutoff filter\n' + _str += 'exposure time: {:f}s\n'.format(cam_control_panel.exposure_time.value) + _str += 'sensitivity: {:f}%'.format(cam_control_panel.sensitivity.value) + + _str += '\n' + + _str += 'date: {:s}\n'.format(ap.date) + _str += 'time: {:s}\n'.format(ap.time) + + _str += '\n' + + _str += 'person: {:s}\n'.format(ap.person) + _str += 'pose: {:s}\n'.format(ap.pose) + _str += 'comment:\n' + _str += ap.comment[:-1] + + return _str + + +imgs = [None, None, None] + +def main(): + o3000.video_init() + o3000.video_xml_send(""); + + + root = tk.Tk() + root.title("0-3000 Image Recorder") + + # the geometry of the box which will be displayed on the screen + root.geometry("1700x1000") + + image_label = ImageLabel() + image_label.pack(side=tk.LEFT) + + + control_panel = tk.Frame(root) + control_panel.pack(side=tk.LEFT) + + tk.Label(control_panel, text='').pack(pady=20) # separater, vertical space + + + tk.Label(control_panel, text='').pack(pady=20) # separater, vertical space + + tk.Label(control_panel, text='Camera Configuration').pack(anchor='w') + cam_control_panel = CamControlPanel(master=control_panel) + cam_control_panel.pack() + + tk.Label(control_panel, text='').pack(pady=20) # separater, vertical space + + tk.Label(control_panel, text='Annotation').pack(anchor='w') + annotation_panel = AnnotationPanel(master=control_panel) + annotation_panel.pack() + + + def record_image(): + global imgs + imgs = o3000.video_images_get() + image = imgs[2] + image_label.image_update(image) + annotation_panel.update_date_time() + + + btn_record_image = tk.Button(control_panel, text="record/update image", command=record_image) + btn_record_image.pack() + + + def save_image(): + ap = annotation_panel + filenamebase = '{:s}_{:s}'.format(ap.date.strip('-'), ap.time.strip(':')) + + filenames = [ + '{:s}_raw.tiff'.format(filenamebase), + '{:s}_rgb.tiff'.format(filenamebase), + '{:s}.tiff'.format(filenamebase), + ] + annotation_filename = '{:s}.txt'.format(filenamebase) + + _annotation_text = annotation_text(filenames, annotation_panel, cam_control_panel) + + print('save images and annotation file {:s}, annotation:'.format(annotation_filename)) + print(_annotation_text) + + for filename,image, in zip(filenames,imgs): + im = Image.fromarray(image) + im.save(filename) + + with open(annotation_filename,'w') as f: + f.write(_annotation_text) + + + btn_save_image = tk.Button(control_panel, text="save image", command=save_image) + btn_save_image.pack() + + + root.mainloop() + +if __name__ == '__main__': + main() -- cgit v1.2.1