Classifier Example |
Below, an example implementation of a RelaxISPlugin_Classifier is shown.
The function of the plugin is chosen for illustrative purposes.
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
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.
// <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(); } } } } }