Blackboard (design pattern)

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

This pattern has been identified by the members of the HEARSAY-II project and first applied for speech recognition.[1]

Structure

the blackboard model defines three main components:

Implementation

First step is to design the solution space (i.e. various solutions) which leads to the definition of blackboard structure. Then, knowledge source are to be identified. These two activities are very related.[1]

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

[1]

Known Uses

Some usage-domains are:

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 properties such as:

Blackboard pattern allows multiple processes to work closer together on separate threads, polling, and reacting if it is needed.[2]

Example

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

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>

[2] 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>

[2]

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>

[2]

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);
 
}

[2]

Code behind the Controller:

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

[2]

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();
}

[2]

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),
};

[2]

Code to hanlde 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);
}

[2]

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();
}

[2]

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;

[2]

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);

[2]

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

[2]

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);
}

[2]

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

[2]

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;
}

[2]

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;
}

[2]

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;
    }
}

[2]

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;
        }
    }
}

[2]

See also

References

  1. 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
  2. 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.
This article is issued from Wikipedia - version of the Thursday, May 05, 2016. The text is available under the Creative Commons Attribution/Share Alike but additional terms may apply for the media files.