Blackboard (design pattern)

In software engineering, the blackboard pattern is a behavioral design pattern[1] that provides a computational framework for the design and implementation of systems that integrate large and diverse specialized modules, and implement complex, non-deterministic control strategies.[2][1]

This pattern was identified by the members of the HEARSAY-II project and first applied to speech recognition.[2]

Structure

The blackboard model defines three main components:

  • blackboard - a structured global memory containing objects from the solution space
  • knowledge sources - specialized modules with their own representation
  • control component - selects, configures and executes modules.[2]

Implementation

The first step is to design the solution space (i.e. potential solutions) that leads to the blackboard structure. Then, knowledge sources are identified. These two activities are closely related.[2]

The next step is to specify the control component; it generally takes the form of a complex scheduler that makes use of a set of domain-specific heuristics to rate the relevance of executable knowledge sources.[2]

System Structure[2]

Applications

Usage-domains include:

Consequences

The blackboard pattern provides effective solutions for designing and implementing complex systems where heterogeneous modules have to be dynamically combined to solve a problem. This provides non-functional properties such as:

  • reusability
  • changeability
  • robustness.[2]

The blackboard pattern allows multiple processes to work closer together on separate threads, polling and reacting when necessary.[1]

Example

Sample radar defense system is provided as an example (in C#).

Code for MainWindow.xaml:

<ListBox ItemsSource="{Binding blackboard.CurrentObjects}" ItemsPanel="{DynamicResource ItemsPanelTemplate1}" ItemContainerStyle="{DynamicResource ItemContainerStyle}" ItemTemplate="{DynamicResource ItemTemplate}" Margin="20,20,20,10" Foreground="#FFDE6C6C" >
    <ListBox.Resources>
        <ItemsPanelTemplate x:Key="ItemsPanelTemplate1">
            <Canvas IsItemsHost="True"/>
        </ItemsPanelTemplate>

[1] Code for item container for positioning

<Style x:Key="ItemContainerStyle" TargetType="{x:Type ListBoxItem}">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
    <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
    <Setter Property="Canvas.Left" Value="{Binding X}"/>
    <Setter Property="Canvas.Top" Value="{Binding Y}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

[1]

Code for Item (ItemTemplate defines the object, an Image and TextBoxes):

<DataTemplate x:Key="ItemTemplate">
    <Border>
        <Border.Style>
            <Style TargetType="{x:Type Border}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsThreat}" Value="true">
                        <Setter Property="Background" Value="Red"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding IsThreat}" Value="false">
                        <Setter Property="Background" Value="Green"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Border.Style>
        <Grid Margin="3">
            <Image Height="48" Source="{Binding Image}" />
            <StackPanel Margin="0,0,0,-30" VerticalAlignment="Bottom" >
                <TextBlock Text="{Binding Type}"/>
                <TextBlock Text="{Binding Name}"/>
            </StackPanel>
            <TextBlock HorizontalAlignment="Right" TextWrapping="Wrap" Text="{Binding DistanceFromDestruction}" VerticalAlignment="Bottom" Width="Auto" Visibility="{Binding IsThreat, Converter={StaticResource BooleanToVisibilityConverter}}"/>
        </Grid>
    </Border>
</DataTemplate>

[1]

Code behind the Blackboard component in MVVM ViewModel implementation:

public Blackboard blackboard { get; set; }
Controller controller;

public MainWindow()
{
    InitializeComponent();
    DataContext = this;

    blackboard = new Blackboard();
    controller = new Controller(blackboard);
}

[1]

Code behind the Controller:

private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
    controller.AddSignalProcessor();
}

[1]

Code for the base class IObject:

public interface IObject
{
    ObjectType Type { get; set; }
    string Name { get; set; }
    WriteableBitmap Image { get; set; }
    bool? IsThreat { get; set; }
    ProcessingStage Stage { get; set; }
    int X { get; set; }
    int Y { get; set; }

    IObject Clone();
}

[1]

Code in the radar module:

AllObjects = new List<IObject>
{
    new BirdObject(ObjectType.Bird, "", new WriteableBitmap(new System.Windows.Media.Imaging.BitmapImage(new Uri(@"pack://application:,,,/Media/Bird.bmp", UriKind.Absolute))), false, false),
    new PlaneObject(ObjectType.Plane, "", new WriteableBitmap(new System.Windows.Media.Imaging.BitmapImage(new Uri(@"pack://application:,,,/Media/Plane.bmp", UriKind.Absolute))), false, false),
    new RocketObject(ObjectType.Rocket, "", new WriteableBitmap(new System.Windows.Media.Imaging.BitmapImage(new Uri(@"pack://application:,,,/Media/Rocket.bmp", UriKind.Absolute))), false, false),
};

[1]

Code to handle incoming object:

public IncomingObject(IObject obj)
    : base(ObjectType.Unknown, null, null, true, null)
{
    actualObject = obj;
    ProcessedPixels = new  bool[16, 16];

    //Paint the image as all red to start with
    Image = new  WriteableBitmap(48, 48, 72, 72, PixelFormats.Bgr32, null);
    int[] ary = new  int[(48*48)];
    for (var x = 0; x < 48; x++)
        for (var y = 0; y < 48; y++)
            ary[48*y + x] = 255*256*256;
    Image.WritePixels(new Int32Rect(0, 0, 48, 48), ary, 4*48, 0);
}

[1]

Code for knowledge source interface:

public interface IKnowledgeSource
{
    bool IsEnabled { get; }
    void Configure(Blackboard board);
    void ExecuteAction();

    KnowledgeSourceType KSType { get; }
    KnowledgeSourcePriority Priority { get; }
    void Stop();
}

[1]

implementation for signal processor:

public override bool IsEnabled
{
    get
    {
        for (var ix = 0; ix < blackboard.CurrentObjects.Count(); ix++)
            if (blackboard.CurrentObjects[ix].Stage < ProcessingStage.Analysed)
                return true;

        return false;
    }
}

public override void ExecuteAction()
{
    for (var ix = 0; ix < blackboard.CurrentObjects.Count(); ix++)
        if (blackboard.CurrentObjects[ix].Stage < ProcessingStage.Analysed)
            ProcessAnotherBit(blackboard.CurrentObjects[ix]);
}

void ProcessAnotherBit(IObject obj)
{
    int GRANULARITY = 16;
    int blockWidth = obj.Image.PixelWidth/GRANULARITY;

[1]

code segments for copying between writablebitmaps:

int stride = obj.Image.PixelWidth*obj.Image.Format.BitsPerPixel/8;
int byteSize = stride*obj.Image.PixelHeight*obj.Image.Format.BitsPerPixel/8;
var ary = new byte[byteSize];
obj.Image.CopyPixels(ary, stride, 0);

[1]

var unk = obj as IncomingObject;
unk.GetActualObject().Image.CopyPixels(aryOrig, stride, 0);

[1]

for (var iy = 0; iy < blockWidth; iy++)
{
    for (var ix = 0; ix < blockWidth; ix++)
        for (var b = 0; b < 4; b++)
        {
            ary[curix] = aryOrig[curix];
            curix++;
        }
    curix = curix + stride - (blockWidth*4);
}

[1]

obj.Image.WritePixels(new Int32Rect(0, 0, obj.Image.PixelWidth, obj.Image.PixelHeight), ary, stride, 0);

[1]

Code for comparing pixel in image recognition:

for (var ix = 0; ix < blockWidth; ix++)
{
    var argb1 = (ary[curix + 1]*256*256) + (ary[curix + 2]*256) + ary[curix + 3];
    var argb2 = (aryKnown[curix + 1]*256*256) + (aryKnown[curix + 2]*256) + aryKnown[curix + 3];
    if (argb1 != 255*256*256 && argb1 != argb2)
    {
        nomatch = true;
        break;
    }
    curix += 4;
}

[1]

if (matches.Count() == 1)
{
    obj.Type = matches[0].Type;
    obj.Name = matches[0].Name;
    obj.IsThreat = matches[0].IsThreat;

    obj.Image = new  WriteableBitmap(matches[0].Image); //Create new image instance

    if (obj.Type != ObjectType.Plane)
        obj.Stage = ProcessingStage.Identified;
    else
        obj.Stage = ProcessingStage.Analysed;
}

[1]

Code for plane identification:

for (var ix = 0; ix < blackboard.CurrentObjects.Count(); ix++)
{
    var obj = blackboard.CurrentObjects[ix];
    if (obj.Stage == ProcessingStage.Analysed && obj.Type == ObjectType.Plane)
    {
        var unk = obj as IncomingObject;
        var actual = unk.GetActualObject();
        obj.Name = actual.Name;
        obj.IsThreat = actual.IsThreat;
        obj.Stage = ProcessingStage.Identified;
    }
}

[1]

Code for the war machine:

public override void ExecuteAction()
{
    for (var ix = 0; ix < blackboard.CurrentObjects.Count(); ix++)
    {
        var obj = blackboard.CurrentObjects[ix] as IncomingObject;
        if (obj.IsThreat != null && obj.IsThreat.Value && (obj.Stage != ProcessingStage.Actioned))
        {
            if (obj.MoveHitsTarget())
                DestroyTarget(obj);
        }
    }
}

private void DestroyTarget(IncomingObject obj)
{
    int stride = obj.Image.PixelWidth*obj.Image.Format.BitsPerPixel/8;
    int byteSize = stride*obj.Image.PixelHeight*obj.Image.Format.BitsPerPixel/8;
    var ary = new byte[byteSize];
    obj.Image.CopyPixels(ary, stride, 0);

    DrawCross(stride, ary);

    obj.Image.WritePixels(new Int32Rect(0, 0, obj.Image.PixelWidth, obj.Image.PixelHeight), ary, stride, 0);
    obj.Stage = ProcessingStage.Actioned;
}

private static void DrawCross(int stride, byte[] ary)
{
    for (var y = 1; y < 47; y++)
    {
        var line1Pos = (y*stride) + (y*4);
        var line2Pos = (y*stride) + (stride - 4) - (y*4);
        for (var a = -1; a < 2; a++)
        {
            ary[line1Pos + 4 + (a*4)] = ary[line2Pos + 4 + (a*4)] = 255;
            ary[line1Pos + 5 + (a*4)] = ary[line2Pos + 5 + (a*4)] = 0;
            ary[line1Pos + 6 + (a*4)] = ary[line2Pos + 6 + (a*4)] = 0;
            ary[line1Pos + 7 + (a*4)] = ary[line2Pos + 7 + (a*4)] = 0;
        }
    }
}

[1]

See also

References

  1. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 "Blackboard Design Pattern". Microsoft TechNet. Microsoft. Retrieved 5 February 2016.
  2. 1 2 3 4 5 6 7 8 Lalanda, P., Two complementary patterns to build multi-expert systems, Orsay, France: Thomson CSF Corporate Research Laboratory
This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.