Plugins
Lenscraft functionality can be extended with plugins. Plugins are primarily intended to add custom nodes but there are a few more extension points and more will be added over time.
This is the basic anatomy of a plugin
from lenscraft.plugin import LenscraftPlugin
plugin = LenscraftPlugin()
plugin.register_node(...
plugin.register_image_loader(...
plugin.register_camera(...
Manual Plugin Registration
To run Lenscraft with your plugin use following command:
lenscraft --plugin example_plugin.plugin
Lenscraft will dynamically import the module and look for a Plugin instance called plugin
.
If you want to use a different name you can use
lenscraft --plugin example_plugin.plugin:my_plugin
Plugin Discovery
Lenscraft will automatically try to import plugins from any module that starts with lenscraft_
So, if you create a directory called lenscraft_plugin
in the root of your project and add a __init__.py
with the following code, Lenscraft will automatically load that plugin.
from lenscraft.node import Node, NodeInput, NodeOutput, NumberValue
from lenscraft.plugin import LenscraftPlugin
plugin = LenscraftPlugin()
Extension Points
Custom Nodes
This is how you can implement a basic node.
@plugin.node()
class AddTwoNumbers(Node):
def setup(self):
self.add_input("A", IntSlot())
self.add_input("B", IntSlot())
self.add_output("Result", Number)
def compute(self):
a = self.get_input_value("A")
b = self.get_input_value("B")
self.set_output_value("Result", a + b)
The first argument to add_input
is the input name. Used in the UI and in get_input_value
below.
The second argument to add_input
is a Slot instance. Slots are part of the type system in Lenscraft and is how we specify what type of value the node expects and can enforce constraints on those values.
More about Slots in the type system section.
Here is a list of the most used slot types:
- ImageSlot
- NumberInRange
- EnumSlot
- BoolSlot
- IntSlot
- OddIntSlot
- FloatSlot
- StringSlot
- FilePath
- FolderPath
Custom Image Loader
By default Lenscraft will use PIL.Image.open
to load images. This will cover most standard image formats.
If you need to work with more obscure file formats, it is possible to add a custom ImageLoader.
This example shows how you could load image data from a .dm4 file, commonly used for electron microscopy.
The ncempy
library is doing the heavy lifting for us. The plugin just associates the custom loader with the dm4
file extension.
Now Lenscraft will call this loader for all file paths that have the .dm4
extension.
Note that is it possible to override ImageLoaders. Lenscraft will use the latest one it
found for each file extension. If you add a .png
ImageLoader, that will replace the
default PIL implementation.
import ncempy.io as nio
from lenscraft.core.io import ImageLoader, Image
from lenscraft.plugin import LenscraftPlugin
plugin = LenscraftPlugin()
class DM4Loader(ImageLoader):
def load_image(self, path):
# Load the DM4 file
dmData = nio.read(path)
# Extract the image data
image = dmData['data']
return Image(path, image)
plugin.register_image_loader(DM4Loader(), ".dm4")
Custom Camera
Lenscraft can show you a live preview from a USB or built-in webcam, but you can also add a custom camera implementation. In this context, a camera is just a class that can produce a sequence of images.
For example, lets say we want to load a sequence of frames from a directory.
class FolderCamera(Camera):
def __init__(self, folder: str, loop: bool = True):
self.folder = Path(folder)
self.loop = loop
def start(self):
self.index = 0
self.started = True
self.images = self._load_images(self.folder)
def read(self):
if self.index >= len(self.images):
if self.loop:
self.index = 0
else:
return None
path = self.images[self.index]
frame = cv2.imread(path)
self.index += 1
return frame
def release(self):
self.started = False
@classmethod
def setup_form(cls) -> Optional[Form]:
return (
Form(
title="Folder Camera",
description="Loads a sequence of image files from a folder"
)
.add_field("folder", FolderPath(), label="Image Folder Path")
.add_field("loop", BoolSlot(True), label="Loop When Finished")
)
start
will alway be called before reading frames from the camera, thats where you can setup any resources you need.
read
will be called each frame to get the lates image data. It should return a numpy array
release
will be called at the end when the camera is closed. Thats where you should cleanup resources.
Implementing setup_form
is optional but thats what allows you customize the camera configuration through the UI at runtime.
If a form instance is returned, the user will be promoted with a dialog where parameters can be set.
The for field system is basically the same as the node input system. Slots are used to specify what type of value we expect. More about Slots in the type system section.
Note that the first argument in add_field
matches the keyword arguments in the class __init__
method.