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 used to integrate large and diverse specialized modules, and implement complex, non deterministic control strategies.[2][1]
This pattern has been identified by the members of the HEARSAY-II project and first applied for speech recognition.[2]
Structure
The blackboard model defines three main components:
- blackboard - a structured global memory containing objects from the solution space
- knowledge sources - highly specialized modules with their own representation
- control component - selects, configures and execute knowledge sources.[2]
Implementation
First step is to design the solution space (i.e. various solutions) that leads to the definition of blackboard structure. Then, knowledge source are to be identified. These two activities are very related.[2]
The next step is to specify the control component that 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.[2]
Known Uses
Some usage-domains are:
- speech recognition
- vehicle identification and tracking
- identification of the structure of protein molecules
- sonar signals interpretation.[2]
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:
- reusability
- changeability
- robustness.[2]
Blackboard pattern allows multiple processes to work closer together on separate threads, polling, and reacting, if it is needed.[1]
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>
[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>
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>
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);
}
Code behind the Controller:
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
controller.AddSignalProcessor();
}
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();
}
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),
};
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);
}
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();
}
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;
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);
var unk = obj as IncomingObject;
unk.GetActualObject().Image.CopyPixels(aryOrig, stride, 0);
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);
}
obj.Image.WritePixels(new Int32Rect(0, 0, obj.Image.PixelWidth, obj.Image.PixelHeight), ary, stride, 0);
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;
}
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;
}
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;
}
}
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;
}
}
}