{"repo": "bengoldfeder17/highfive", "files": {"api.py": {"ac4e116dac69c334bec8d193a370ccb4b8aebd93": {"t": 1785674483.1426873, "n": 8333, "h": "e9e40f2db918ab4b", "c": "#!/home/highfive/Downloads/highfive/Downloads/bin/python3.11\nimport os\nimport sys\nimport time\nimport threading\nimport base64\nimport io\n\nimport numpy as np\nimport RPi.GPIO as GPIO\nimport pygame\nfrom picamera2 import Picamera2\nfrom PIL import Image\nimport openai\n\n# ─── 0) OpenAI QuickStart setup ──────────────────────────────────────────\nOPENAI_KEY = input(\"Enter your OpenAI Project API key (must start with sk-proj-): \").strip()\nif not OPENAI_KEY.startswith(\"sk-proj-\"):\n print(\"[ERROR] This script only accepts project-scoped API keys (sk-proj-...).\")\n sys.exit(1)\n\nOPENAI_PROJECT = input(\"Enter your OpenAI project ID: \").strip()\n\n# Set API key and project ID as custom header\nopenai.api_key = OPENAI_KEY\nopenai._custom_headers = {\n \"OpenAI-Project\": OPENAI_PROJECT\n}\n\n# ─── 1) Categories & bin mapping ───────────────────────────────────────────\nCLASS_NAMES = [\"biodegradable\", \"cardboard\", \"glass\", \"metal\", \"paper\", \"plastic\", \"trash\"]\nRECYCLE_SET = {\"cardboard\", \"glass\", \"metal\", \"paper\",\"recycling\"}\nTRASH_SET = {\"biodegradable\", \"plastic\", \"trash\"}\n\n# ─── 2) Constants ──────────────────────────────────────────────────────────\nITEM_WEIGHT = 0.125 # lbs per detected item\nDIFF_THRESH = 5 # pixel‐difference threshold\nPAUSE_BEFORE = 2 # seconds to wait once motion detected\nBIN_CAP = 10.0 # default capacity (gallons)\n\n# ─── 3) GPIO & motor setup ────────────────────────────────────────────────\nIN1, IN2, ENA = 23, 24, 5\nGPIO.setmode(GPIO.BCM)\nGPIO.setup(IN1, GPIO.OUT)\nGPIO.setup(IN2, GPIO.OUT)\nGPIO.setup(ENA, GPIO.OUT)\npwm = GPIO.PWM(ENA, 1000)\npwm.start(0)\n\ndef rotate_left(speed=100):\n GPIO.output(IN1, GPIO.HIGH)\n GPIO.output(IN2, GPIO.LOW)\n pwm.ChangeDutyCycle(speed)\n\ndef rotate_right(speed=100):\n GPIO.output(IN1, GPIO.LOW)\n GPIO.output(IN2, GPIO.HIGH)\n pwm.ChangeDutyCycle(speed)\n\ndef stop_motor():\n pwm.ChangeDutyCycle(0)\n GPIO.output(IN1, GPIO.LOW)\n GPIO.output(IN2, GPIO.LOW)\n\n# ─── 4) Camera init & background capture ────────────────────────────────────\nos.environ[\"BCM2835_PERI_BASE\"] = \"0xFE000000\"\nos.environ[\"BCM2708_PERI_BASE\"] = \"0xFE000000\"\n\npicam2 = Picamera2()\ncfg = picam2.create_preview_configuration(main={\"size\": (640,480)})\npicam2.configure(cfg)\npicam2.start()\ntime.sleep(2)\nbackground = picam2.capture_array().astype(np.int16)\n\n# ─── 5) OpenAI‐based classify function (resized, JPEG, sanitized + logging) ─\ndef classify_with_openai(frame: np.ndarray) -> (str, float):\n global CLASS_NAMES\n\n # 1) shrink to 224×224 & convert to RGB\n pil = Image.fromarray(frame).resize((224,224), Image.LANCZOS)\n if pil.mode != \"RGB\":\n pil = pil.convert(\"RGB\")\n\n # 2) JPEG-compress\n buf = io.BytesIO()\n pil.save(buf, format=\"JPEG\", quality=20)\n b64 = base64.b64encode(buf.getvalue()).decode()\n\n # 3) prompt model to reply with exactly one category word\n prompt = (\n \"Classify the object on the platform image into exactly one of these words:\\n\"\n \"biodegradable, cardboard, glass, metal, paper, plastic\\n\"\n \"Reply with exactly one word (no punctuation).\\n\\n\"\n \"Here is the image (base64 JPEG):\\n\" + b64\n )\n resp = openai.chat.completions.create(\n model=\"gpt-4.1\",\n messages=[\n {\"role\": \"system\", \"content\": \"You are an expert recycling classifier.\"},\n {\"role\": \"user\", \"content\": prompt}\n ],\n max_completion_tokens=3\n )\n\n raw = resp.choices[0].message.content.strip().lower()\n print(f\"[OpenAI] raw response → {raw}\") # log to terminal\n\n # 4) sanitize: match against CLASS_NAMES\n label = next((c for c in CLASS_NAMES if raw == c or c in raw), None)\n if label is None:\n print(f\"[OpenAI] unexpected label “{raw}”, defaulting to “trash”\")\n label = \"trash\"\n return label, 1.0\n\n# ─── 6) Shared state & background thread ───────────────────────────────────\nrunning = True\ndisplay_label = \"Waiting for Object\"\nconfidence = 0.0\ntrash_weight = 0.0\nrecycle_weight = 0.0\nbin_capacity = BIN_CAP\n\ndef classify_and_act():\n global display_label, confidence, trash_weight, recycle_weight\n while running:\n frame = picam2.capture_array()\n diff_val = np.mean(np.abs(frame.astype(np.int16) - background))\n if diff_val < DIFF_THRESH:\n display_label = \"Waiting for Object\"\n confidence = 0.0\n else:\n time.sleep(PAUSE_BEFORE)\n raw_label, conf = classify_with_openai(frame)\n confidence = conf\n\n if raw_label in RECYCLE_SET:\n display_label = f\"Recycle ({raw_label.capitalize()})\"\n recycle_weight += ITEM_WEIGHT\n rotate_left(); time.sleep(4); stop_motor()\n time.sleep(1)\n rotate_right(); time.sleep(4); stop_motor()\n elif raw_label in TRASH_SET:\n display_label = f\"Trash ({raw_label.capitalize()})\"\n trash_weight += ITEM_WEIGHT\n rotate_right(); time.sleep(4); stop_motor()\n time.sleep(1)\n rotate_left(); time.sleep(4); stop_motor()\n else:\n display_label = f\"Unknown ({raw_label.capitalize()})\"\n stop_motor()\n\n time.sleep(5)\n\nthreading.Thread(target=classify_and_act, daemon=True).start()\n\n# ─── 7) Pygame UI loop ───────────────────────────────────────────────────────\npygame.init()\nSCREEN_W, SCREEN_H = 720, 1280\nscreen = pygame.display.set_mode((SCREEN_W, SCREEN_H))\npygame.display.set_caption(\"AI Trash\")\n\nWHITE = (255,255,255)\nBLACK = (0,0,0)\nLIGHT_BLUE = (173,216,230)\nLIGHT_CORAL = (240,128,128)\n\nfont_title = pygame.font.Font(None, 100)\nfont_text = pygame.font.Font(None, 60)\nfont_button = pygame.font.Font(None, 50)\n\nbtn_h, btn_w = 80, 220\nbtn_y = SCREEN_H - btn_h - 100\nreset_rect = pygame.Rect(60, btn_y, btn_w, btn_h)\ncap_rect = pygame.Rect(SCREEN_W - btn_w - 60, btn_y, btn_w, btn_h)\n\ntry:\n while running:\n for ev in pygame.event.get():\n if ev.type == pygame.QUIT:\n running = False\n elif ev.type == pygame.MOUSEBUTTONDOWN:\n x,y = ev.pos\n if reset_rect.collidepoint(x,y):\n trash_weight = recycle_weight = 0.0\n display_label = \"Waiting for Object\"\n elif cap_rect.collidepoint(x,y):\n bin_capacity += -0.5 if x < cap_rect.centerx else 0.5\n bin_capacity = max(1.0, bin_capacity)\n\n screen.fill(WHITE)\n title = font_title.render(\"AI Trash\", True, BLACK)\n screen.blit(title, ((SCREEN_W - title.get_width())//2, 20))\n\n cur = font_text.render(display_label, True, BLACK)\n screen.blit(cur, ((SCREEN_W - cur.get_width())//2, 140))\n\n pct_rec = min(recycle_weight / bin_capacity, 1.0)\n pct_tr = min(trash_weight / bin_capacity, 1.0)\n rec = font_text.render(f\"Recycling: {pct_rec:.1%}\", True, LIGHT_BLUE)\n tr = font_text.render(f\"Trash: {pct_tr:.1%}\", True, LIGHT_CORAL)\n screen.blit(rec, ((SCREEN_W - rec.get_width())//2, 220))\n screen.blit(tr, ((SCREEN_W - tr.get_width())//2, 300))\n\n frame = picam2.capture_array()\n if frame.ndim == 2:\n frame = np.stack((frame,)*3, axis=-1)\n elif frame.shape[2] == 4:\n frame = frame[:,:,:3]\n h,w = frame.shape[:2]\n surf = pygame.image.frombuffer(frame.tobytes(), (w,h), 'RGB')\n fw = SCREEN_W - 120\n fh = int(fw * h / w)\n feed = pygame.transform.scale(surf, (fw, fh))\n screen.blit(feed, ((SCREEN_W - fw)//2, 380))\n\n pygame.draw.rect(screen, LIGHT_BLUE, reset_rect)\n rs = font_button.render(\"Reset\", True, BLACK)\n screen.blit(rs, (reset_rect.x + (btn_w - rs.get_width())//2,\n reset_rect.y + (btn_h - rs.get_height())//2))\n\n pygame.draw.rect(screen, LIGHT_BLUE, cap_rect)\n ct = font_button.render(f\"< {bin_capacity:.1f} gal >\", True, BLACK)\n screen.blit(ct, (cap_rect.x + (btn_w - ct.get_width())//2,\n cap_rect.y + (btn_h - ct.get_height())//2))\n\n pygame.display.flip()\n\nfinally:\n picam2.stop()\n pwm.stop()\n GPIO.cleanup()\n pygame.quit()\n"}}}}