import sys import cv2 import numpy as np from PIL import Image import objc from AppKit import ( NSApplication, NSApp, NSWindow, NSView, NSImageView, NSButton, NSStackView, NSImage, NSBitmapImageRep, NSBackingStoreBuffered, NSWindowStyleMaskTitled, NSWindowStyleMaskClosable, NSWindowStyleMaskResizable, NSWindowStyleMaskMiniaturizable, NSTimer, NSMakeSize, NSMakeRect, NSObject, NSLog, NSUserInterfaceLayoutOrientationVertical, NSLayoutAttributeCenterX, NSLayoutAttributeCenterY, NSLayoutAttributeWidth, NSLayoutAttributeHeight, NSLayoutAttributeTop, NSLayoutAttributeBottom, NSLayoutAttributeLeading, NSLayoutAttributeTrailing ) from Foundation import NSObject, NSTimer, NSDate class ItemSenseApp(NSObject): def applicationDidFinishLaunching_(self, notification): self.window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_( NSMakeRect(0, 0, 800, 600), NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable, NSBackingStoreBuffered, False ) self.window.setTitle_("ItemSense") self.window.center() # Main content view (StackView for layout) self.stack_view = NSStackView.alloc().init() self.stack_view.setOrientation_(NSUserInterfaceLayoutOrientationVertical) self.stack_view.setSpacing_(10) self.stack_view.setEdgeInsets_((10, 10, 10, 10)) self.window.setContentView_(self.stack_view) # Image View for Camera Feed self.image_view = NSImageView.alloc().init() self.image_view.setImageScaling_(0) # NSImageScaleProportionallyDown self.stack_view.addView_inGravity_(self.image_view, 1) # Top gravity # Capture Button self.capture_button = NSButton.buttonWithTitle_target_action_("Capture", self, "captureClicked:") self.stack_view.addView_inGravity_(self.capture_button, 3) # Bottom gravity self.window.makeKeyAndOrderFront_(None) # Initialize Camera self.cap = cv2.VideoCapture(0) if not self.cap.isOpened(): NSLog("Error: Could not open camera") # Start Timer for 30 FPS self.timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( 1.0/30.0, self, "updateFrame:", None, True ) def applicationShouldTerminateAfterLastWindowClosed_(self, sender): return True def applicationWillTerminate_(self, notification): if hasattr(self, 'cap') and self.cap.isOpened(): self.cap.release() def updateFrame_(self, timer): if hasattr(self, 'cap') and self.cap.isOpened(): ret, frame = self.cap.read() if ret: # Convert BGR to RGB rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # Convert to NSImage height, width, channels = rgb_frame.shape bytes_per_line = channels * width # Create BitmapRep bitmap_rep = NSBitmapImageRep.alloc().initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_( None, width, height, 8, 3, False, False, "NSDeviceRGBColorSpace", bytes_per_line, 24 ) # Copy data bitmap_data = bitmap_rep.bitmapData() # We need to copy the bytes. This is the PyObjC way to write to the buffer requires a bit of care. # A safer/easier way with PIL: image = Image.fromarray(rgb_frame) img_data = image.tobytes() # Low-level memory copy might be tricky in pure python/objc without unsafe pointers. # Alternative: Use PIL to save to memory buffer (TIFF/PNG) and load NSImage from data. # This is slightly slower but safer and easier in Python. import io # Using PPM format is fast (uncompressed) header = f"P6 {width} {height} 255 ".encode() data = header + rgb_frame.tobytes() ns_data = objc.lookUpClass("NSData").dataWithBytes_length_(data, len(data)) ns_image = NSImage.alloc().initWithData_(ns_data) self.image_view.setImage_(ns_image) def captureClicked_(self, sender): print("Capture clicked") if __name__ == "__main__": app = NSApplication.sharedApplication() delegate = ItemSenseApp.alloc().init() app.setDelegate_(delegate) NSApp.activateIgnoringOtherApps_(True) app.run()