Specify workflow input or properties in standalone applications

271 views
Skip to first unread message

Xavier Faure

unread,
Nov 18, 2014, 10:30:01 AM11/18/14
to bonsai...@googlegroups.com
Hello,

First of all I have been playing around with bonsai for some few weeks now, mainly with the Bonsai.Vision packages, and it is really easy and powerful to use! 

I am now starting to try to use Bonsai inside standalone applications (Windows Forms) and have the following question :

I am wondering if it is possible to specify a workflow input when using it in a standalone application. 
The idea behind it is to be able to use a workflow as a "reusable" object and to be able to instantiate n workflows, and feed each of them a different input.

If this is not possible, an alternative could be to be able to set properties of nodes of the workflow used in the standalone applications.

Or maybe something else I had not think about...

Regards,
Xavier

Gonçalo Lopes

unread,
Nov 18, 2014, 2:09:02 PM11/18/14
to bonsai...@googlegroups.com
Hi Xavier,

Feeding external inputs from C# code into pre-specified workflow files in standalone applications is indeed not very well supported in the current version of Bonsai. The reason for this is that it requires creating partially specified workflows, since you are feeding an input externally. Bonsai then has no way of knowing which data type this input will have at the level of the editor, which makes everything rather clunky.

Anyway, it is possible to do it, although a little convoluted. The first step is to create our partially specified workflow. For example, you could have:

As you can see, the workflow is missing an input to the Mod operator. We now have to insert the correct input operator using code in our standalone application. To do this, we need to create an ExpressionBuilder class. These builders are basically the classes which do the code generation for running a Bonsai workflow. There is currently no builder to inject a pre-existing source into the workflow structure, but we can make one like so:

public class InputBuilder<TSource> : ZeroArgumentExpressionBuilder
{
    IObservable<TSource> input;

    public InputBuilder(IObservable<TSource> input)
    {
        this.input = input;
    }

    public override Expression Build(IEnumerable<Expression> arguments)
    {
        return Expression.Constant(input, typeof(IObservable<TSource>));
    }
}

Now what we need to do is load our partial workflow, create an instance of this builder, and inject it into the structure, like so:

class Program
{
    static void Main(string[] args)
    {
        using (var reader = XmlReader.Create("workflow.bonsai"))
        {
            var serializer = new XmlSerializer(typeof(WorkflowBuilder));
            var workflowBuilder = (WorkflowBuilder)serializer.Deserialize(reader);

            var sourceNode = workflowBuilder.Workflow.Sources().First();
            var inputBuilder = new InputBuilder<long>(Observable.Timer(TimeSpan.FromSeconds(1)));
            var inputNode = workflowBuilder.Workflow.Add(inputBuilder);
            workflowBuilder.Workflow.AddEdge(inputNode, sourceNode, new ExpressionBuilderArgument());

            var workflow = workflowBuilder.Workflow.Build();
            var observableFactory = Expression.Lambda<Func<IObservable<long>>>(workflow).Compile();
            var subscription = observableFactory().Subscribe(
                x => Console.WriteLine("OnNext: {0}", x),
                ex => Console.WriteLine("OnError: {0}", ex.Message),
                () => Console.WriteLine("OnCompleted"));
            Console.WriteLine("Press ENTER to unsubscribe...");
            Console.ReadLine();
            subscription.Dispose();
        }
    }
}

With this bit, you should finally be able to inject your own dynamic inputs into partially instantiated workflows. I will think of ways to make it easier in future versions. However, bear in mind that for really dynamic scenarios where you want to reconfigure everything in run-time inside some other application, then you might want to consider just using the Bonsai classes directly to create your workflow in code, rather than worry about bonsai XML files.

Hope this helps,
Gonçalo

Gonçalo Lopes

unread,
Nov 18, 2014, 2:22:34 PM11/18/14
to bonsai...@googlegroups.com
Alternately, if it's enough simply to change parameters in the workflow (like the index of a camera, a filename, etc) then you can simply load from the XML file as many workflows you need, and then for each of them you can call:

workflowBuilder.Workflow.SetWorkflowProperty("PropertyName", PropertyValue);



Where "PropertyName" is the name of an externalized property in the workflow and PropertyValue is the value you want to set that property to.

Cheers,

Xavier Faure

unread,
Nov 19, 2014, 10:44:30 AM11/19/14
to bonsai...@googlegroups.com
Hi,

The first solution worked perfectly. 
I am able to feed an Iplimage from a CameraCapture to a warpPerspective "source" node. So that's perfect.

About setting the property, I get an error when specifying for instance a Point2f[] for the WarpPerspective Node. Here is the Workflow :
 

And here is the error : 
System.NotSupportedException: ArrayConverter cannot convert from OpenCV.Net.Point2f[].

And here is the code : 
var warp = InitializeQuadrangle(new Size(640, 480));                        
workflowBuilder
.Workflow.SetWorkflowProperty("SourceWarp", warp);

static Point2f[] InitializeQuadrangle(Size size)
{
    return new[]
    {
        new Point2f(0, 0),
        new Point2f(0, size.Height),
        new Point2f(size.Width, size.Height),
        new Point2f(size.Width, 0)
    };

I also tried externalizing a simple double value from a threshold and got the same error : 

System.NotSupportedException: DoubleConverter cannot convert from System.Double.

workflowBuilder.Workflow.SetWorkflowProperty("ThresholdValue", 127.0);

Cheers,
Xavier




Gonçalo Lopes

unread,
Nov 19, 2014, 11:15:38 AM11/19/14
to bonsai...@googlegroups.com
Interesting. It looks like the converter does not handle well the case where the type is actually correct. I will fix that.

Can you try passing the value as a string and see if it works? For example, in the second case, the threshold value would be "127.0" as a string.

I'll look it up in a bit see how best to workaround this.

Cheers,

Xavier Faure

unread,
Nov 19, 2014, 11:27:37 AM11/19/14
to bonsai...@googlegroups.com
I tried passing the value as a string as you said, but unfortunately, it did not work, here is the error : 

A first chance exception of type 'System.FormatException' occurred in mscorlib.dll
A first chance exception of type 'System.Exception' occurred in System.dll
System.Exception: 127.0 is not a valid value for Double. ---> System.FormatException: Input string was not in a correct format.
   at System.Number.ParseDouble(String value, NumberStyles options, NumberFormatInfo numfmt)
   at System.Double.Parse(String s, NumberStyles style, IFormatProvider provider)
   at System.ComponentModel.DoubleConverter.FromString(String value, NumberFormatInfo formatInfo)
   at System.ComponentModel.BaseNumberConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
   --- End of inner exception stack trace ---
   at System.ComponentModel.BaseNumberConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
   at System.ComponentModel.TypeConverter.ConvertFrom(Object value)
   at Bonsai.Expressions.ExpressionBuilderGraphExtensions.SetWorkflowProperty(ExpressionBuilderGraph source, String name, Object value)

Cheers,
Xavier

Gonçalo Lopes

unread,
Nov 19, 2014, 3:03:24 PM11/19/14
to bonsai...@googlegroups.com
It works for me, but I think the reason is probably your computer's localization is set to Portuguese. This means that decimal numbers are written with comma (,) as a decimal separator rather than point (.). So probably if you try "127,0" it will work. Also just "127" should work.

Gonçalo Lopes

unread,
Nov 19, 2014, 3:03:07 PM11/19/14
to bonsai...@googlegroups.com

Xavier Faure

unread,
Nov 20, 2014, 4:37:48 AM11/20/14
to bonsai...@googlegroups.com
Indeed it works with "127,0" instead of "127.0".

Thanks
Reply all
Reply to author
Forward
0 new messages