Click or drag to resize

Classifier Example

Below, an example implementation of a RelaxISPlugin_Classifier is shown.

The function of the plugin is chosen for illustrative purposes.

Requirements
  • Reference to the RelaxIS_SDK.dll

  • .NET Framework 4.7.2 target (class library)

  • Recommended: Development environment with compiler e.g. RelaxIS SDK Code Editor, Microsoft Visual Studio

Demonstrates

This examples illustrates the implementation of a Classifier plugin.

The plugin has no real functionality. As an example it determines a score randomly for a number of different first-names (used as classes) and optionally includes the name length in the score.

Example
C#
// <copyright file="MyClassifier.cs" company="rhd instruments GmbH and Co. KG">
// Copyright (c) rhd instruments GmbH and Co. KG. All rights reserved.
// Licensed under the MIT No Attribution (MIT-0) license. See section 'License' in the 'SDK Examples / Tutorials' topic for full license information.
// </copyright>

namespace RelaxIS_SDK_Examples.Plugins
{
    /***
    * Classifier plugins define a set of classes and provide methods to provide a score for each class for a given spectrum.
    * One example application is the legacy Model Screening, where - effectively - Auto Fits are performed for a number of different
    * models and the score is determined from the sum of squared residuals and the number of parameters.
    * However, in the Classifier plugins all this is generic - the classes can be freely defined and the way the score is determined
    * is arbitrary.
    * The plugins can define all relevant interface details, such as configuration and display controls for integration into RelaxIS.
    * Note, that the plugin defines an execution mode, to define if the plugin is called from the user-interface or not. It is important
    * to not require user-interaction if the classifier runs in ExecutionMode.StandaloneRun.
    */

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using RelaxIS_SDK.Common;
    using RelaxIS_SDK.Plugins;
    using RelaxIS_SDK.Plugins.Classifier;
    using RelaxIS_SDK.Plugins.Classifier.Controls;
    using RelaxIS_SDK.Plugins.Classifier.Data;

    /// <summary>
    /// Defines an example <see cref="RelaxISPlugin_Classifier"/> class.
    /// </summary>
    public class MyClassifier
        : RelaxISPlugin_Classifier
    {
        /***
         * First, implement the default plugin properties Name and Description that describe the plugin.
         * This is mainly used for display purposes such as the entries in the classifier list in the user interface.
         */

        /// <inheritdoc/>
        public override string Name
        {
            get { return "MyClassifier"; }
        }

        /// <inheritdoc/>
        public override string Description
        {
            get { return "Decides on a random basis what the most appropriate name for a given spectrum is."; }
        }

        /***
         * This property determines if the user may select a subset of the classes provided in the GetClasses function.
         * If false, all classes will be handed over to the RunClassificationAsync function.
         */

        /// <inheritdoc/>
        public override bool SupportsSelectableClasses
        {
            get
            {
                return true;
            }
        }

        /***
         * This property determines if the user can select one of the result classes and apply it to the source spectrum.
         * If not, then this option is not available in the user interface and the ApplyClassResultAsync function is never called.
         */

        /// <inheritdoc/>
        public override bool SupportApplyResult
        {
            get
            {
                return true;
            }
        }

        /***
         * This property defines if the classification algorithm treats higher scores as "better" than lower scores
         * or vice versa. This is mainly a visual option that e.g. determines the default ordering of the result list.
         */

        /// <inheritdoc/>
        public override OptimizerType OptimizerType
        {
            get
            {
                return OptimizerType.Maximizer;
            }
        }

        /***
         * The classifier needs a couple of custom classes for operation and display.
         * These objects have base classes that need to be overridden by custom classes.
         * The factory object can create these overridden classes for use by RelaxIS.
         * The factory is defined below.
         */

        /// <inheritdoc/>
        public override IClassifierObjectFactory GetFactory()
        {
            return new MyClassifierFactory();
        }

        /***
         * This function returns all available classes that the classifier can understand. Depending on the SupportsSelectableClasses
         * property the user may or may not select a subset of these. Note, that in this example the display string and class data of
         * the classes are the same. Only the display string is shown to the user, the class data may contain more relevant data for
         * the classifier that is invisible.
         */

        /// <inheritdoc/>
        public override IEnumerable<ClassifierClass> GetClasses(ExecutionMode mode, ClassifierClassSettingsBase settings)
        {
            var english = new string[] { "Noah", "Oliver", "George", "Arthur", "Leo", "Harry", "Oscar", "Archie", "Henry", "Olivia", "Amelia", "Isla", "Ava", "Ivy", "Freya", "Lily", "Florence", "Mia", "Willow", };
            var german = new string[] { "Anna", "Frida", "Martha", "Marie", "Erna", "Emma", "Elisabeth", "Getrud", "Berta", "Clara", "Karl", "Wilhelm", "Hans", "Heinrich", "Hermann", "Paul", "Otto", "Ernst", "Walter", "Max", };

            var res = new List<ClassifierClass>();

            // Use our own ClassifierClass objects here as defined below.
            foreach (var n in english)
            {
                res.Add(new MyClassifierClass("English names", n, n.Length));
            }

            foreach (var n in german)
            {
                res.Add(new MyClassifierClass("German names", n, n.Length));
            }

            return res;
        }

        /***
         * This is the main classification function. The function should determine a score for each of the classes and return this result list.
         * The function typically runs asynchronously and can provide status updates via the ClassifierProgress event.
         * This shows a progress bar, status message and intermediate result list.
         * Also, for longer running tasks it is advised to monitor the CancellationToken and stop the progress. This happens when the user cancels
         * by clicking the Stop button.
         */

        /// <inheritdoc/>
        public override async Task<IEnumerable<ClassResult>> RunClassificationAsync(
            ImpedanceSpectrum spectrum,
            ExecutionMode mode,
            ClassifierSettingsBase settings,
            ClassifierClassCollection classes,
            CancellationToken cancellation)
        {
            var mySettings = settings as ClassifierSettings;
            var useNameLengthScore = false;
            if (mySettings != null)
            {
                useNameLengthScore = mySettings.ScoreByNameLength;
            }

            var r = new Random();
            var results = new List<ClassResult>();
            await Task.Run(
                () =>
                {
                    var max = classes.Count;
                    foreach (var c in classes)
                    {
                        var score = r.NextDouble() * 100;
                        if (useNameLengthScore)
                        {
                            score *= c.DisplayString.Length;
                        }

                        // Use our custom ClassResult class here as defined below.
                        var result = new MyClassifierClassResult(c, score);

                        results.Add(result);
                        this.UpdateProgress(results, settings, classes, "Tested: " + c.DisplayString);
                        Thread.Sleep(50);
                        cancellation.ThrowIfCancellationRequested();
                    }
                },
                cancellation);

            return results;
        }

        /***
         * This function is called if SupportApplyResult and the user decides to apply one of the result classes to the input spectrum.
         * What happens in this case is up to the classifier plugin. One example is applying a new model, changing metadata.
         *  Longer running tasks should run asyncronously.
         */

        /// <inheritdoc/>
        public override Task ApplyClassResultAsync(ImpedanceSpectrum spectrum, ExecutionMode mode, ClassResult result, ClassificationResult fullResult, CancellationToken cancel)
        {
            spectrum.Datasource = result.SourceClass.DisplayString + " " + spectrum.Datasource;
            return Task.CompletedTask;
        }

        /***
         * A number of custom classes need to be defined. First a factory class is defined that creates custom types
         * specific for this classifier.
         */

        private class MyClassifierFactory
            : IClassifierObjectFactory
        {
            /***
             * Class Settings is an object that can define settings for the initial retrieval of classifier classes.
             * It can be edited in an additional step in the user interface if CreateClassSettingsControl() returns
             * not null.
             * The function can return null as well, in this case the GetClasses() function also receives a null argument
             * and the setup step in RelaxIS is skipped as well.
             */

            public ClassifierClassSettingsBase CreateClassSettings(ExecutionMode mode)
            {
                return null;
            }

            /***
             * Return a settings object used by this classifier. This is typically an object of
             * a type that inherits ClassifierSettingsBase and contains custom settings for this classifier.
             */

            public ClassifierSettingsBase CreateSettings(ExecutionMode mode)
            {
                return new ClassifierSettings();
            }

            /***
             * These three functions are used by RelaxIS to create custom settings and display controls.
             * The class settings control is populated with a ClassifierClassSettingsBase object that is created via the
             * CreateClassSettings() function. The Class Settings are optional and can return null. In this case the
             * setup script is skipped by RelaxIS.
             * The settings control is populated with a ClassifierSettingsBase object that is created via the
             * CreateSettings() function.
             * The display control is initialized with one of the ClassResult objects returned by the classification function.
             */

            public ClassifierClassSettingsControlBase CreateClassSettingsControl()
            {
                return null;
            }

            public ClassifierSettingsControlBase CreateSettingsControl()
            {
                return new SettingsControl();
            }

            public ClassResultDisplayControlBase CreateDisplayControl()
            {
                return new DisplayControl();
            }
        }

        /***
         * Custom class objects can be used in order to transport more information along.
         * This is not part of the factory class, as it is created exclusively by the GetClasses() function.
         * This class for example transports an additional length parameter.
         */

        private class MyClassifierClass
            : ClassifierClass
        {
            private readonly int length;

            public MyClassifierClass(string category, string displayString, int length)
                : base(category, displayString)
            {
                this.length = length;
            }

            public int Length
            {
                get
                {
                    return this.length;
                }
            }
        }

        /***
         * After overriding the ClassifierClass type, we also replace the ClassResult type to allow us
         * to persist the Length parameter in the Result Library.
         * This especially makes sense if the classifier produces a spectrum fit as a result. This can
         * be persisted in the respective properties of the ClassResultPersistanceData object, allowing
         * RelaxIS to reproduce the fit curve from the Result Library dialog.
         */

        private class MyClassifierClassResult
            : ClassResult
        {
            public MyClassifierClassResult(ClassifierClass sourceClass, double score)
                : base(sourceClass, score)
            {
            }

            public override ClassResultPersistanceData GetPersistenceValues()
            {
                var sc = (MyClassifierClass)this.SourceClass;
                var res = new ClassResultPersistanceData();
                res.PersistenceValues.Add(new PersistenceKeyValuePair("Length", sc.Length));
                return res;
            }
        }

        /***
         * The Settings class contains setting that are editable by the Settings Control defined below and are
         * passed on to the Classification and ApplyResult functions.
         */

        private class ClassifierSettings
            : ClassifierSettingsBase
        {
            /// <summary>
            /// Gets or sets a value indicating whether the score includes a factor for the name length.
            /// </summary>
            public bool ScoreByNameLength
            {
                get;
                set;
            }
        }

        /***
         * The DisplayControl displays one of the ClassResult objects for the input spectrum.
         * The way this is done is up to the plugin. It can just be a text output, but it may also
         * be a plot or something similar.
         * When a result should be displayed the overridable function Display is called. In case the
         * control should be cleared, the overriable ClearResult function is called by RelaxIS.
         */

        private class DisplayControl
            : ClassResultDisplayControlBase
        {
            private readonly Font defaultFont;
            private readonly Label lbl;

            public DisplayControl()
                : base()
            {
                var lbl = new Label() { Text = "No Name", TextAlign = System.Drawing.ContentAlignment.MiddleCenter, AutoSize = false, Dock = DockStyle.Fill };
                this.Controls.Add(lbl);
                this.defaultFont = new Font("Comic Sans MS", 16, System.Drawing.FontStyle.Bold);
                this.lbl.Font = this.defaultFont;
                this.lbl = lbl;
            }

            public override void Display(ImpedanceSpectrum spectrum, ClassifierSettingsBase settings, ClassResult result)
            {
                this.lbl.Text = result.SourceClass.DisplayString;
            }

            public override void ClearDisplay()
            {
                this.lbl.Text = "---";
            }

            protected override void Dispose(bool disposing)
            {
                base.Dispose(disposing);
                if (disposing)
                {
                    this.defaultFont.Dispose();
                    this.lbl.Dispose();
                }
            }
        }

        /***
         * The SettingsControl is initialized with a settings object via the InitializeFrom function.
         * Here, the control should update its controls to show the values in the settings object.
         * If the ApplyTo function is called, the control should write back the settings defined by its controls
         * into the settings object.
         * Note, the actual type of the ClassifierSettingsBase is the same as the one returned by the GetDefaultSettings class.
         */

        private class SettingsControl
            : ClassifierSettingsControlBase
        {
            private readonly CheckBox cb;

            public SettingsControl()
            {
                this.cb = new CheckBox
                {
                    Text = "Include name-length score factor",
                    AutoSize = false,
                    Dock = DockStyle.Fill,
                };

                this.Controls.Add(this.cb);
            }

            public override void InitializeFrom(ClassifierSettingsBase settings)
            {
                var mySettings = settings as ClassifierSettings;
                if (mySettings != null)
                {
                    this.cb.Checked = mySettings.ScoreByNameLength;
                }
            }

            public override void ApplyTo(ClassifierSettingsBase settings)
            {
                var mySettings = settings as ClassifierSettings;
                if (mySettings != null)
                {
                    mySettings.ScoreByNameLength = this.cb.Checked;
                }
            }

            protected override void Dispose(bool disposing)
            {
                base.Dispose(disposing);
                if (disposing)
                {
                    this.cb.Dispose();
                }
            }
        }
    }
}
See Also