Bullseye Migration Guide

Bullseye Migration Guide

In Actcast OS 3, the base OS is Raspberry Pi Bullseye, so modifications are required to the application configuration file and code.

Here, we will explain how to make an existing application running on Raspberry Pi Buster compatible with Raspberry Pi Bullseye, using ImageNet Classification as an example.

See here for the entire code.

Advance preparation and prior knowledge

As a prerequisite, we will assume that you have an application being maintained on Actcast.

Please install the latest ActDK.

You can use the API Token as it is. There is also no need to obtain a new application ID.

Overview of the corrections

First, we will list the items that need to be corrected.

  1. Indicate target_types in .actdk/setting.json and include raspberrypi-bullseye.

  2. Include the raspberrypi-bullseye section added in 1 in the .actdk/dependencies.json file.

  3. Add a manifest file for Bullseye to manifesto/.

  4. Make corrections to use different APIs depending on whether you use the official Raspberry Pi CSI camera or a USB camera as the camera device.

The specific correction methods for each are explained below.

Modify .actdk/setting.json

Actcast supports raspberrypi-buster (or raspberrypi) as well as raspberrypi-bullseye as a firmware type.

From now on, the app needs to specify which firmware type it is targeting.

Specify a list of target_types as follows:

  {
    "app_id": "imagenet-classification-for-raspi",
    "app_server_id": 1,
    "short_description": "1000 class ImageNet classification demo for raspi",
    "short_descriptions": {
      "ja": "ImageNet1000γ‚―γƒ©γ‚Ήεˆ†ι‘žγƒ‡γƒ’(for raspi)"
-    }
+    },
+   "target_types": ["raspberrypi-buster", "raspberrypi-bullseye"]
  }

Modifying .actdk/dependencies.json

Add a section for each firmware type added to .actdk/setting.json.

If the existing settings use the name raspberrypi, change it to raspberrypi-buster to match .actdk/setting.json.

raspberrypi may be deprecated in the future.

In addition to this, add a raspberrypi-bullseye section.

Add apt and pip dependencies as necessary.

  {
    "apt": [
      "libv4l-0",
      "libv4lconvert0",
      "python3-pil",
      "python3-numpy",
      "fonts-dejavu-core"
    ],
    "pip": [],
-   "raspberrypi": {
+   "raspberrypi-buster": {
      "apt": [
        "libraspberrypi0"
      ],
      "pip": [
        "actfw-raspberrypi"
      ]
-   }
+   },
+   "raspberrypi-bullseye": {
+     "apt": [
+       "libraspberrypi0"
+     ],
+     "pip": [
+       "actfw-raspberrypi"
+     ]
+   }
  }

Modifying and adding manifesto files

Modify the existing manifest file.

First, set version to 2.

Specify raspberrypi-buster as target_type.

  {
+   "version": 2,
+   "target_type": "raspberrypi-buster",
    "boards": [
      "RSPi3B",
      "RSPi3BPlus",
      "RSPi3APlus",
      "RSPi4B"
    ],
    "devices": [
      { "type": "camera" },
      { "type": "videocore" },
      {
        "type": "framebuffer",
        "device": [
          "/dev/fb0"
        ],
        "required": false
      }
    ]
  }

Next, add a new manifesto/bullseye.json file. The file name does not matter as long as it is under the manifesto directory. However, be careful not to duplicate target_type.

The device files used for some device types differ between raspberrypi-buster and raspberrypi-bullseye. If you omit the device file specification, as in camera and videocore below, the device file normally used for each device type will be automatically mounted. However, please note that there are some device types for which you cannot omit the device file specification. For details, see Application Schemas.

+ {
+   "version": 2,
+   "target_type": "raspberrypi-bullseye",
+   "boards": [
+     "RSPi3B",
+     "RSPi3BPlus",
+     "RSPi3APlus",
+     "RSPi4B"
+   ],
+   "devices": [
+     { "type": "camera" },
+     { "type": "videocore" },
+     {
+       "type": "framebuffer",
+       "device": [
+         "/dev/fb0"
+       ],
+       "required": false
+     }
+   ]
+ }

Modifying the program

Now we will modify the program. There are three changes to make:

  1. To detect the camera device, use find_csi_camera_device from the actfw_core.system module for CSI cameras, and find_usb_camera_device from the same module for USB camera devices.

  2. To use a CSI camera with a Raspberry Pi Bullseye, use UnicamIspCapture from the actfw_core.unicam_isp_capture module. To use a USB camera with a Raspberry Pi Buster or Bullseye, use V4LCameraCapture from the actfw_core.capture module.

  3. The layer passed to the argument of the open_window function of the actfw_raspberrypi.vc4 module must be a positive number less than or equal to 16.

For ImageNet Classification, we added a setting value called use_usb_camera to tell the app whether the camera is a USB camera or not in order to determine condition 2. Below are the changes made to setting_schema.json and act_settings.json.

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "display": {
            "title": "display",
            "description": "output video to HDMI display",
            "type": "boolean",
            "default": false
        },
        "threshold": {
            "title": "probability threshold",
            "description": "notify when over this threshold",
            "type": "number",
            "default": 0.9,
            "minimum": 0,
            "maximum": 1
-       }
+       },
+       "use_usb_camera": {
+           "title": "use USB camera",
+           "description": "use USB camera instead of Raspberry Pi Camera",
+           "type": "boolean",
+           "default": false
+       }
    },
    "required": [
        "display",
        "threshold"
    ],
    "propertyOrder": [
        "display",
-       "threshold"
+       "threshold",
+       "use_usb_camera"
    ]
}
  {
    "display": true,
-   "threshold": 0.1
+   "threshold": 0.1,
+   "use_usb_camera": false
  }

Finally, here is the difference when the above changes are made to app/main.

  #!/usr/bin/env python3
  import argparse
  from PIL import Image, ImageDraw, ImageFont
  import actfw_core
  from actfw_core.task import Pipe, Consumer
+ from actfw_core.system import get_actcast_firmware_type, find_csi_camera_device, find_usb_camera_device
  from actfw_core.capture import V4LCameraCapture
+ from actfw_core.unicam_isp_capture import UnicamIspCapture
  import actfw_raspberrypi
  from actfw_raspberrypi.vc4 import Display
  import numpy as np
  from model import Model

  (CAPTURE_WIDTH, CAPTURE_HEIGHT) = (224, 224)  # capture image size
  (DISPLAY_WIDTH, DISPLAY_HEIGHT) = (640, 480)  # display area size


  class Classifier(Pipe):

      def __init__(self, capture_size):
          super(Classifier, self).__init__()
          self.model = Model()
          self.capture_size = capture_size

      def proc(self, frame):
          rgb_image = Image.frombuffer('RGB', self.capture_size, frame.getvalue(), 'raw', 'RGB')
          rgb_image = rgb_image.resize((CAPTURE_WIDTH, CAPTURE_HEIGHT))
          input_image = np.asarray(rgb_image).reshape(1, CAPTURE_WIDTH, CAPTURE_HEIGHT, 3).astype(np.float32)
          probs, = self.model.MobileNet_v2(input_image)
          return (rgb_image, probs[0][1:])


  class Presenter(Consumer):

      def __init__(self, settings, preview_window, cmd):
          super(Presenter, self).__init__()
          self.settings = settings
          self.preview_window = preview_window
          self.cmd = cmd
          self.font = ImageFont.truetype(font='/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', size=18)
          with open('labels.txt') as f:
              self.labels = f.read().splitlines()

      def proc(self, images):
          rgb_image, probs = images
          top1 = probs.argsort()[-1]
          if probs[top1] > self.settings['threshold']:
              actfw_core.notify([{'prob': float(probs[top1]), 'label': self.labels[top1]}])
          self.cmd.update_image(rgb_image)  # update `Take Photo` image
          actfw_core.heartbeat()
          if self.preview_window is not None:
              draw = ImageDraw.Draw(rgb_image)
              draw.text((0, 0), "{:>6.2f}% {}".format(100 * probs[top1], self.labels[top1]), font=self.font, fill=(0, 255, 0))
              self.preview_window.blit(rgb_image.tobytes())
              self.preview_window.update()

+ def setup_camera_capture(use_usb_camera):
+     framerate = 15
+     device = find_usb_camera_device() if use_usb_camera else find_csi_camera_device()
+
+     if get_actcast_firmware_type() == "raspberrypi-bullseye" and not use_usb_camera:
+         # Use UnicamIspCapture on Raspberry Pi OS Bullseye
+         cap = UnicamIspCapture(unicam=device, size=(CAPTURE_WIDTH, CAPTURE_HEIGHT), framerate=framerate)
+     else:
+         # Use V4LCameraCapture on Raspberry Pi OS Buster or use USB camera
+         cap = V4LCameraCapture(device, (CAPTURE_WIDTH, CAPTURE_HEIGHT), framerate, format_selector=V4LCameraCapture.FormatSelector.PROPER)
+
+     return cap

  def main(args):

      # Actcast application
      app = actfw_core.Application()

      # Load act setting
-     settings = app.get_settings({'display': True, 'threshold': 0.1})
+     settings = app.get_settings({'display': True, 'threshold': 0.1, 'use_usb_camera': False})

      # CommandServer (for `Take Photo` command)
      cmd = actfw_core.CommandServer()
      app.register_task(cmd)

-     # Capture task
-     cap = V4LCameraCapture('/dev/video0', (CAPTURE_WIDTH, CAPTURE_HEIGHT), 15, format_selector=V4LCameraCapture.FormatSelector.PROPER)
+     cap = setup_camera_capture(settings['use_usb_camera'])
      capture_size = cap.capture_size()
      app.register_task(cap)

      # Classifier task
      conv = Classifier(capture_size)
      app.register_task(conv)

      def run(preview_window=None):

          # Presentation task
          pres = Presenter(settings, preview_window, cmd)
          app.register_task(pres)

          # Make task connection
          cap.connect(conv)  # from `cap` to `conv`
          conv.connect(pres)  # from `conv` to `pres`

          # Start application
          app.run()

      if settings['display']:
          with Display() as display:
              display_width, display_height = display.size()
              scale = min(float(display_width / CAPTURE_WIDTH), float(display_height / CAPTURE_WIDTH))
              width = int(scale * CAPTURE_WIDTH)
              height = int(scale * CAPTURE_HEIGHT)
              left = (display_width - width) // 2
              upper = (display_height - height) // 2
-             with display.open_window((left, upper, width, height), (CAPTURE_WIDTH, CAPTURE_HEIGHT), 1000) as preview_window:
+             # layer must be between 1 and 16
+             layer = 16
+             with display.open_window((left, upper, width, height), (CAPTURE_WIDTH, CAPTURE_HEIGHT), layer) as preview_window:
                  run(preview_window)
      else:
          run()


  if __name__ == '__main__':
      parser = argparse.ArgumentParser(description='example: 1000 class classification')
      main(parser.parse_args())

About Debugging

In the latest ActDK, there are two types of Actsim images: one for Buster and one for Bullseye. If you want to use both, please use the respective Actsim images for debugging and checking the operation.

Uploading your application

Uploading to Actcast remains unchanged. Uploading apps for both Buster and Bullseye will generate build images for both.

Other points to note

When making your Actcast application compatible with Bullseye, please also note the following points.

  • Make sure that the layer passed to the argument of the open_window method of the actfw_raspberrypi.vc4.Display class is unique within the application.

  • Do not use the actfw_raspberrypi.vc4.Display class when a display is not connected.

Last updated on