diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1dd248f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.pb filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text diff --git a/ColorSubdivision/App.config b/ColorSubdivision/App.config new file mode 100644 index 0000000..8fc0551 --- /dev/null +++ b/ColorSubdivision/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/ColorSubdivision/ColorSubdivision.csproj b/ColorSubdivision/ColorSubdivision.csproj new file mode 100644 index 0000000..9f1ebea --- /dev/null +++ b/ColorSubdivision/ColorSubdivision.csproj @@ -0,0 +1,151 @@ + + + + + Debug + AnyCPU + {AD42A573-7AC3-4714-9D53-DB9921815CBB} + WinExe + ColorSubdivision + ColorSubdivision + v4.7.1 + 512 + true + true + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + x64 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x64 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + true + + + + ..\..\..\..\..\..\system\sdk\OpenCVsharp\net461\OpenCvSharp.dll + + + ..\..\..\..\..\..\system\sdk\OpenCVsharp\net461\OpenCvSharp.Blob.dll + + + ..\..\..\..\..\..\system\sdk\OpenCVsharp\net461\OpenCvSharp.Extensions.dll + + + ..\..\..\..\..\..\system\sdk\OpenCVsharp\net461\OpenCvSharp.UserInterface.dll + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + Always + + + + + False + Microsoft .NET Framework 4.7.1 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + \ No newline at end of file diff --git a/ColorSubdivision/ColorSubdivision.csproj.user b/ColorSubdivision/ColorSubdivision.csproj.user new file mode 100644 index 0000000..51f6223 --- /dev/null +++ b/ColorSubdivision/ColorSubdivision.csproj.user @@ -0,0 +1,13 @@ + + + + publish\ + + + + + + en-US + false + + \ No newline at end of file diff --git a/ColorSubdivision/Form1.Designer.cs b/ColorSubdivision/Form1.Designer.cs new file mode 100644 index 0000000..1b0e6fd --- /dev/null +++ b/ColorSubdivision/Form1.Designer.cs @@ -0,0 +1,84 @@ +namespace ColorSubdivision +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.RichTextBox1 = new System.Windows.Forms.RichTextBox(); + this.SuspendLayout(); + // + // button1 + // + this.button1.Location = new System.Drawing.Point(115, 47); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 0; + this.button1.Text = "Button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.Button1_Click); + // + // button2 + // + this.button2.Location = new System.Drawing.Point(115, 105); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(75, 23); + this.button2.TabIndex = 1; + this.button2.Text = "Button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.Button2_Click); + // + // RichTextBox1 + // + this.RichTextBox1.Location = new System.Drawing.Point(370, 138); + this.RichTextBox1.Name = "RichTextBox1"; + this.RichTextBox1.Size = new System.Drawing.Size(100, 96); + this.RichTextBox1.TabIndex = 2; + this.RichTextBox1.Text = ""; + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.RichTextBox1); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Name = "Form1"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.RichTextBox RichTextBox1; + } +} + diff --git a/ColorSubdivision/Form1.cs b/ColorSubdivision/Form1.cs new file mode 100644 index 0000000..9d64c49 --- /dev/null +++ b/ColorSubdivision/Form1.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using OpenCvSharp; + +namespace ColorSubdivision +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + + private void Button1_Click(object sender, EventArgs e) + { + var path = @"D:\kei2\Solutions\DeepTongue\LocalRepository\Tongue extraction_cropresizemethod\Tongue extraction\bin\x64\Debug\mask_final\20180315093610.jpg"; + using (Mat mat_input = Cv2.ImRead(path, ImreadModes.Grayscale)) + { + var mat_dst = new Mat(mat_input.Size(), mat_input.Type()); + bool isEdge = false; + for (int i = 0; i < mat_input.Height; i++) + { + if(!isEdge) + { + for (int j = 0; j < mat_input.Width; j++) + { + if (mat_input.At(i, j) > 200) + { + mat_dst.Set(i, j, 100); + isEdge = true; + } + } + } + } + Cv2.ImShow("input", mat_input); + Cv2.ImShow("dst", mat_dst); + mat_dst.Dispose(); + } + GC.Collect(); + + } + + private void Button2_Click(object sender, EventArgs e) + { + + } + } +} diff --git a/ColorSubdivision/Form1.resx b/ColorSubdivision/Form1.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/ColorSubdivision/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ColorSubdivision/Program.cs b/ColorSubdivision/Program.cs new file mode 100644 index 0000000..b7233a7 --- /dev/null +++ b/ColorSubdivision/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ColorSubdivision +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/ColorSubdivision/Properties/AssemblyInfo.cs b/ColorSubdivision/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..87b468f --- /dev/null +++ b/ColorSubdivision/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ColorSubdivision")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ColorSubdivision")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ad42a573-7ac3-4714-9d53-db9921815cbb")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ColorSubdivision/Properties/Resources.Designer.cs b/ColorSubdivision/Properties/Resources.Designer.cs new file mode 100644 index 0000000..6ca7260 --- /dev/null +++ b/ColorSubdivision/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ColorSubdivision.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ColorSubdivision.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/ColorSubdivision/Properties/Resources.resx b/ColorSubdivision/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/ColorSubdivision/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ColorSubdivision/Properties/Settings.Designer.cs b/ColorSubdivision/Properties/Settings.Designer.cs new file mode 100644 index 0000000..41f48bd --- /dev/null +++ b/ColorSubdivision/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ColorSubdivision.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/ColorSubdivision/Properties/Settings.settings b/ColorSubdivision/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/ColorSubdivision/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tongue extraction.sln b/Tongue extraction.sln new file mode 100644 index 0000000..2b3f7b3 --- /dev/null +++ b/Tongue extraction.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30011.22 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepTIAS2.7", "Tongue extraction\DeepTIAS2.7.csproj", "{AFD610B1-8D23-423A-AA0F-B09BA769BDD7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Debug|x64.ActiveCfg = Debug|x64 + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Debug|x64.Build.0 = Debug|x64 + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Debug|x86.ActiveCfg = Debug|x64 + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Debug|x86.Build.0 = Debug|x64 + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Release|Any CPU.Build.0 = Release|Any CPU + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Release|x64.ActiveCfg = Release|x64 + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Release|x64.Build.0 = Release|x64 + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Release|x86.ActiveCfg = Release|Any CPU + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {33F6C697-859B-4D55-9D28-998267FD09AE} + EndGlobalSection +EndGlobal diff --git a/Tongue extraction/App.config b/Tongue extraction/App.config new file mode 100644 index 0000000..96ac1f2 --- /dev/null +++ b/Tongue extraction/App.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tongue extraction/ColorExtractor_fiveClick.cs b/Tongue extraction/ColorExtractor_fiveClick.cs new file mode 100644 index 0000000..bdeb460 --- /dev/null +++ b/Tongue extraction/ColorExtractor_fiveClick.cs @@ -0,0 +1,1164 @@ +using OpenCvSharp; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Windows.Forms; + +namespace Tongue_extraction +{ + /* + * クラスのメソッド実装としてまずい部分が多く, + * 引数も返り値もとりあえずMatやListが多いために, + * 大変扱いづらいクラスです. + */ + + + // 5点クリック法に基づく色抽出 + public partial class ColorExtractor_fiveClick + { + // Config + const int RADIUS_COLORAREA = 10; + public static Bitmap bitmap; + float[] a = new float[17]; + float[] b = new float[17]; + float[] c = new float[17]; + int k; + public static bool m_getColor = false; + public static Mat m_CalibFrame; //キャリブレーション用画像 + public static OpenCvSharp.Point[] getRGBpoint = new OpenCvSharp.Point[24];//RGB取得用 + double[] m_BforLab = new double[24]; + double[] m_GforLab = new double[24]; + double[] m_RforLab = new double[24]; + public static bool m_bCalib; + public static OpenCvSharp.Point pt = new OpenCvSharp.Point();//キャリブレーション用のポイント入れ + + public enum FivePointMethod {Method1, Method2, Method3}; + + // 新しく作成 + public Mat Get4areaMask(Mat mat_mask) + { + // 表示用 + var mat_dst = mat_mask.Clone(); + Cv2.CvtColor(mat_dst, mat_dst, ColorConversionCodes.GRAY2BGR); + var list_8area = Get8area(Get5points(mat_mask, FivePointMethod.Method3)); + + Cv2.Circle(mat_dst, list_8area[0], 10, new Scalar(0, 0, 255), -1); + Cv2.Circle(mat_dst, list_8area[1], 10, new Scalar(0, 0, 255), -1); + Cv2.Circle(mat_dst, list_8area[2], 10, new Scalar(0, 255, 0), -1); + Cv2.Circle(mat_dst, list_8area[3], 10, new Scalar(0, 255, 0), -1); + Cv2.Circle(mat_dst, list_8area[4], 10, new Scalar(255, 0, 0), -1); + Cv2.Circle(mat_dst, list_8area[5], 10, new Scalar(255, 0, 0), -1); + Cv2.Circle(mat_dst, list_8area[6], 10, new Scalar(0, 128, 255), -1); + Cv2.Circle(mat_dst, list_8area[7], 10, new Scalar(0, 128, 255), -1); + return mat_dst; + } + + public List Get5points(Mat mat_finalMask, FivePointMethod method) + { + // 表示用 + var mat_dst = mat_finalMask.Clone(); + Cv2.CvtColor(mat_dst, mat_dst, ColorConversionCodes.GRAY2BGR); + + // マスクの舌領域画素座標 + var mat_nonZeroCoordinates = new Mat(); + Cv2.FindNonZero(mat_finalMask, mat_nonZeroCoordinates); + + // 舌領域上の点をすべてlistに詰める + var list_X = new List(); + var list_Y = new List(); + for (int i = 0; i < mat_nonZeroCoordinates.Total(); i++) + { + var x = mat_nonZeroCoordinates.At(i).X; + var y = mat_nonZeroCoordinates.At(i).Y; + list_X.Add(x); + list_Y.Add(y); + } + + if(method == FivePointMethod.Method1) + { + /// method1 + // 端っこを探索(ラスタ左上から) + var p_top = mat_nonZeroCoordinates.At(list_Y.IndexOf(list_Y.Min())); + var p_bottom = mat_nonZeroCoordinates.At(list_Y.IndexOf(list_Y.Max())); + var p_left = mat_nonZeroCoordinates.At(list_X.IndexOf(list_X.Min())); + var p_right = mat_nonZeroCoordinates.At(list_X.IndexOf(list_X.Max())); + + // 舌尖領域を示すy座標を取得(割合を今回は決め打ち) + var y_apex = (int)(p_top.Y + ((p_bottom.Y - p_top.Y) * 0.8)); + var p_apex_left = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_apex).Min()); + var p_apex_right = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_apex).Max()); + + // 表示してみる + //Cv2.Circle(mat_dst, p_top, 20, new Scalar(255, 255, 0), -1); + //Cv2.Circle(mat_dst, p_bottom, 20, new Scalar(255, 255, 0), -1); + //Cv2.Circle(mat_dst, p_left, 20, new Scalar(255, 255, 0), -1); + //Cv2.Circle(mat_dst, p_right, 20, new Scalar(255, 255, 0), -1); + //Cv2.Circle(mat_dst, p_apex_left, 20, new Scalar(255, 255, 0), -1); + //Cv2.Circle(mat_dst, p_apex_right, 20, new Scalar(255, 255, 0), -1); + + mat_dst.Dispose(); + mat_nonZeroCoordinates.Dispose(); + GC.Collect(); + + var li_dst = new List { p_left, p_apex_left, p_bottom, p_apex_right, p_right }; + return li_dst; + } + else if (method == FivePointMethod.Method2) + { + /// method2 + // 重心(CoG)計算 + var moments = Cv2.Moments(mat_finalMask, true); + var moment_x = moments.M10 / moments.M00; + var moment_y = moments.M01 / moments.M00; + + // 輪郭座標 + OpenCvSharp.Point[][] contours; + HierarchyIndex[] hierarchy; + Cv2.FindContours(mat_finalMask, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); + var sortedContour = contours.OrderByDescending(n => Cv2.ContourArea(n)).ToList(); + var maxContour = sortedContour[0]; + + // 重心-輪郭の距離 + double maxDistance_lefttop = 0.0; + double maxDistance_righttop = 0.0; + var p_left_2 = new OpenCvSharp.Point(); + var p_right_2 = new OpenCvSharp.Point(); + for (int i = 0; i < maxContour.Length; i++) + { + // 重心より上側 + if (maxContour[i].Y < moment_y) + { + // 重心より上側左側 + if (maxContour[i].X < moment_x) + { + var distance = maxContour[i].DistanceTo(new OpenCvSharp.Point(moment_x, moment_y)); + if (distance > maxDistance_lefttop) + { + maxDistance_lefttop = distance; + p_left_2 = maxContour[i]; + } + + } + // 重心より上側右側 + if (maxContour[i].X >= moment_x) + { + var distance = maxContour[i].DistanceTo(new OpenCvSharp.Point(moment_x, moment_y)); + if (distance > maxDistance_righttop) + { + maxDistance_righttop = distance; + p_right_2 = maxContour[i]; + } + } + } + } + // 舌尖領域を示すy座標を取得(割合を今回は決め打ち) + var p_bottom = mat_nonZeroCoordinates.At(list_Y.IndexOf(list_Y.Max())); + var y_top_avg_ = (p_left_2.Y + p_right_2.Y) / 2.0; + var y_apex_2 = (int)(y_top_avg_ + ((p_bottom.Y - y_top_avg_) * 0.57)); + var p_apex_left_2 = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_apex_2).Min()); + var p_apex_right_2 = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_apex_2).Max()); + + // 表示 + //Cv2.Circle(mat_dst, p_left_2, 20, new Scalar(255, 0, 0), -1); + //Cv2.Circle(mat_dst, p_right_2, 20, new Scalar(255, 0, 0), -1); + //Cv2.Circle(mat_dst, p_bottom_2, 20, new Scalar(255, 0, 0), -1); + //Cv2.Circle(mat_dst, p_apex_left_2, 20, new Scalar(255, 0, 0), -1); + //Cv2.Circle(mat_dst, p_apex_right_2, 20, new Scalar(255, 0, 0), -1); + //Cv2.Circle(mat_dst, new OpenCvSharp.Point(moment_x, moment_y), 10, new Scalar(200, 60, 200), -1); //重心 + //Cv2.DrawContours(mat_dst, contours, 0, new Scalar(0, 255, 255), 4); // 輪郭 + mat_dst.Dispose(); + mat_nonZeroCoordinates.Dispose(); + GC.Collect(); + + var li_dst = new List { p_left_2, p_apex_left_2, p_bottom, p_apex_right_2, p_right_2 }; + return li_dst; + } + else if (method == FivePointMethod.Method3) + { + /// method3 + // 重心(CoG)計算 + var moments = Cv2.Moments(mat_finalMask, true); + var moment_x = moments.M10 / moments.M00; + var moment_y = moments.M01 / moments.M00; + + // 輪郭座標 + OpenCvSharp.Point[][] contours; + HierarchyIndex[] hierarchy; + Cv2.FindContours(mat_finalMask, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); + var sortedContour = contours.OrderByDescending(n => Cv2.ContourArea(n)).ToList(); + var maxContour = sortedContour[0]; + + // 重心-輪郭の距離 + double maxDistance_lefttop = 0.0; + double maxDistance_righttop = 0.0; + var p_left_3 = new OpenCvSharp.Point(); + var p_right_3 = new OpenCvSharp.Point(); + // 距離が最も遠いものを採択 + for (int i = 0; i < maxContour.Length; i++) + { + // 重心より上側 + if (maxContour[i].Y < moment_y) + { + // 重心より上側左側 + if (maxContour[i].X < moment_x) + { + var distance = maxContour[i].DistanceTo(new OpenCvSharp.Point(moment_x, moment_y)); + if (distance > maxDistance_lefttop) + { + maxDistance_lefttop = distance; + p_left_3 = maxContour[i]; + } + + } + // 重心より上側右側 + if (maxContour[i].X >= moment_x) + { + var distance = maxContour[i].DistanceTo(new OpenCvSharp.Point(moment_x, moment_y)); + if (distance > maxDistance_righttop) + { + maxDistance_righttop = distance; + p_right_3 = maxContour[i]; + } + } + } + } + + // 重心とtopの中点を算出する + var p_top = mat_nonZeroCoordinates.At(list_Y.IndexOf(list_Y.Min())); + var y_topToCoG = (int)(moment_y + p_top.Y) / 2; + var p_topToCoG_left = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_topToCoG).Min()); + var p_topToCoG_right = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_topToCoG).Max()); + var p_topToCoG_center = new OpenCvSharp.Point((int)((p_topToCoG_left.X + p_topToCoG_right.X) / 2), y_topToCoG); + // 重心とbottomの中点を算出する + var p_bottom = mat_nonZeroCoordinates.At(list_Y.IndexOf(list_Y.Max())); + var y_bottomToCoG = (int)(moment_y + p_bottom.Y) / 2; + var p_bottomToCoG_left = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_bottomToCoG).Min()); + var p_bottomToCoG_right = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_bottomToCoG).Max()); + var p_bottomToCoG_center = new OpenCvSharp.Point((int)((p_bottomToCoG_left.X + p_bottomToCoG_right.X) / 2), y_bottomToCoG); + + // 直線フィッティング + var lefty = new OpenCvSharp.Point(); + var righty = new OpenCvSharp.Point(); + if (p_topToCoG_center.X == p_bottomToCoG_center.X) + { + // 傾き0の場合の例外処理 + lefty.X = p_topToCoG_center.X; + lefty.Y = 0; + righty.X = p_topToCoG_center.X; + righty.Y = mat_finalMask.Rows - 1; + } + else + { + var line = Cv2.FitLine(new OpenCvSharp.Point[2] { p_topToCoG_center, p_bottomToCoG_center }, DistanceTypes.L2, 0, 0.01, 0.01); + lefty.X = 0; + lefty.Y = (int)((-line.X1 * line.Vy / line.Vx) + line.Y1); + righty.X = mat_finalMask.Cols - 1; + righty.Y = (int)(((mat_finalMask.Cols - line.X1) * line.Vy / line.Vx) + line.Y1); + + } + // 直線上の輪郭点 + var mat_centerline = new Mat(mat_finalMask.Size(), MatType.CV_8UC1, 0); + var mat_contour = new Mat(mat_finalMask.Size(), MatType.CV_8UC1, 0); + var mat_and = new Mat(mat_finalMask.Size(), MatType.CV_8UC1, 0); + Cv2.Line(mat_centerline, lefty, righty, 3); + Cv2.DrawContours(mat_contour, sortedContour, 0, 3); + Cv2.BitwiseAnd(mat_centerline, mat_contour, mat_and); + Cv2.FindNonZero(mat_and, mat_and); + var bottom_y_3 = 0; + var bottom_x_3 = 0; + for (int i = 0; i < mat_and.Total(); i++) + { + var x = mat_and.At(i).X; + var y = mat_and.At(i).Y; + if (bottom_y_3 < y) + { + bottom_y_3 = y; + bottom_x_3 = x; + } + } + var p_bottom_3 = new OpenCvSharp.Point(bottom_x_3, bottom_y_3); + + // 舌尖領域を示すy座標を取得(割合を今回は決め打ち) + //p_left_3.Y = p_left_3.Y + 40; + //p_right_3.Y = p_right_3.Y + 40; + var y_top_avg_ = (p_left_3.Y + p_right_3.Y) / 2.0; + var y_apex_3 = (int)(y_top_avg_ + ((p_bottom_3.Y - y_top_avg_) * 0.57)); + var p_apex_left_3 = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_apex_3).Min()); + var p_apex_right_3 = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_apex_3).Max()); + + //Cv2.Circle(mat_dst, p_topToCoG_left, 20, new Scalar(0, 100, 255), -1); + //Cv2.Circle(mat_dst, p_topToCoG_right, 20, new Scalar(0, 100, 255), -1); + //Cv2.Circle(mat_dst, p_topToCoG_center, 20, new Scalar(0, 0, 255), -1); + //Cv2.Circle(mat_dst, p_bottomToCoG_left, 20, new Scalar(0, 100, 255), -1); + //Cv2.Circle(mat_dst, p_bottomToCoG_right, 20, new Scalar(0, 100, 255), -1); + //Cv2.Circle(mat_dst, p_bottomToCoG_center, 20, new Scalar(0, 0, 255), -1); + //Cv2.Circle(mat_dst, p_bottom_3, 20, new Scalar(0, 0, 255), -1); + + // 破棄 + mat_dst.Dispose(); + mat_nonZeroCoordinates.Dispose(); + GC.Collect(); + + var li_dst = new List { p_left_3, p_apex_left_3, p_bottom_3, p_apex_right_3, p_right_3 }; + return li_dst; + } + else + { + return new List(); + } + // DEBUG + //Cv2.NamedWindow("dst", WindowMode.KeepRatio ^ WindowMode.AutoSize); + //Cv2.ImShow("dst", mat_dst.Resize(new OpenCvSharp.Size((int)mat_dst.Width * 0.5, (int)mat_dst.Height * 0.5))); + + } + + public List Get8area(List list_5points) + { + var li_dst = new List(); + // ROIマスク画像1 + // 0____________ 4 + // | | + // | | + // | | + // 1|● ●|3 + // 舌    / + // 舌_______/ + // 2 + + var points1 = new int[2, 3] { { 0, 2, 1 }, { 4, 2, 3 } }; + var area1 = new OpenCvSharp.Point(); + for (int i = 0; i < 2; i++) + { + var cx1 = (list_5points[points1[i, 0]].X + list_5points[points1[i, 1]].X) / 2; + var cy1 = (list_5points[points1[i, 0]].Y + list_5points[points1[i, 1]].Y) / 2; + area1.X = (list_5points[points1[i, 2]].X + cx1) / 2; + area1.Y = (list_5points[points1[i, 2]].Y + cy1) / 2; + li_dst.Add(area1); + } + + // ROIマスク画像2 + // 0____________ 4 + // | ● ● | + // | | + // | | + // 1| |3 + // 舌    / + // 舌_______/ + // 2 + + var points2 = new int[2, 4] { { 0, 3, 0, 4 }, { 4, 1, 4, 0 } }; + var area2 = new OpenCvSharp.Point(); + for (int i = 0; i < 2; i++) + { + float cx1 = list_5points[points2[i, 0]].X + (list_5points[points2[i, 1]].X - list_5points[points2[i, 0]].X) / 4; + float cy1 = list_5points[points2[i, 0]].Y + (list_5points[points2[i, 1]].Y - list_5points[points2[i, 0]].Y) / 4; + float cx2 = (list_5points[points2[i, 2]].X + list_5points[points2[i, 3]].X) / 2; + float cy2 = (list_5points[points2[i, 2]].Y + list_5points[points2[i, 3]].Y) / 2; + area2.X = (int)(cx1 + cx2) / 2; + area2.Y = (int)(cy1 + cy2) / 2; + li_dst.Add(area2); + } + + // ROIマスク画像3 + // 0____________ 4 + // | | + // | | + // | | + // 1| ● ● |3 + // 舌    / + // 舌_______/ + // 2 + var points3 = new int[2, 3] { { 0, 3, 2 }, { 4, 1, 2 } }; + var area3 = new OpenCvSharp.Point(); + for (int i = 0; i < 2; i++) + { + float cx1 = list_5points[points3[i, 0]].X + (list_5points[points3[i, 1]].X - list_5points[points3[i, 0]].X) / 4; + float cy1 = list_5points[points3[i, 0]].Y + (list_5points[points3[i, 1]].Y - list_5points[points3[i, 0]].Y) / 4; + area3.X = (int)(list_5points[points3[i, 2]].X + cx1) / 2; + area3.Y = (int)(list_5points[points3[i, 2]].Y + cy1) / 2; + li_dst.Add(area3); + } + + // ROIマスク画像3 + // 0____________ 4 + // | | + // | | + // | | + // 1| |3 + // 舌    / + // 舌_●_●_/ + // 2 + var points4 = new int[2, 2] { { 0, 2 }, { 4, 2 } }; + var area4 = new OpenCvSharp.Point(); + for (int i = 0; i < 2; i++) + { + area4.X = list_5points[points4[i, 0]].X + (list_5points[points4[i, 1]].X - list_5points[points4[i, 0]].X) * 7 / 8; + area4.Y = list_5points[points4[i, 0]].Y + (list_5points[points4[i, 1]].Y - list_5points[points4[i, 0]].Y) * 7 / 8; + li_dst.Add(area4); + } + + // To do : もし8areaが舌領域に載っていなかったら修正 + //bool isOnTongueArea = DiscriminateOnTongueArea(li_dst); + //if (isOnTongueArea) + //{ + + //} + + return li_dst; + } + + public List Get4colors(Mat mat_maskedImg, List list_8area) + { + List li_dst = new List(); + for (int i = 0; i < list_8area.Count(); i = i + 2) + { + using (Mat mat_colorRoi = Mat.Zeros(mat_maskedImg.Size(), MatType.CV_8UC1)) + { + // 色抽出領域を示すマスク画像を作成 + Cv2.Circle(mat_colorRoi, list_8area[i], RADIUS_COLORAREA, 255, -1); + Cv2.Circle(mat_colorRoi, list_8area[i + 1], RADIUS_COLORAREA, 255, -1); + + // 領域で色を抽出 + var color = Cv2.Mean(mat_maskedImg, mat_colorRoi); + li_dst.Add(color); + } + } + return li_dst; + } + + public void Show8area(Mat oriImg, List list_8area) + { + + Cv2.Circle(oriImg, list_8area[0], 10, new Scalar(0, 255, 0), -1); + Cv2.Circle(oriImg, list_8area[1], 10, new Scalar(0, 255, 0), -1); + Cv2.Circle(oriImg, list_8area[2], 10, new Scalar(255, 255, 255), -1); + Cv2.Circle(oriImg, list_8area[3], 10, new Scalar(255, 255, 255), -1); + Cv2.Circle(oriImg, list_8area[4], 10, new Scalar(255, 255, 0), -1); + Cv2.Circle(oriImg, list_8area[5], 10, new Scalar(255, 255, 0), -1); + Cv2.Circle(oriImg, list_8area[6], 10, new Scalar(255, 0, 0), -1); + Cv2.Circle(oriImg, list_8area[7], 10, new Scalar(255, 0, 0), -1); + Cv2.NamedWindow("dst_", WindowFlags.KeepRatio ^ WindowFlags.Normal); + Cv2.ImShow("dst_", oriImg.Resize(new OpenCvSharp.Size((int)oriImg.Width * 0.5, (int)oriImg.Height * 0.5))); + } + + public Mat ShowResult(Mat oriImg, List list_5point, List list_8area) + { + Cv2.Circle(oriImg, list_5point[0], 3, new Scalar(255, 0, 0), -1); + Cv2.Circle(oriImg, list_5point[1], 3, new Scalar(255, 0, 0), -1); + Cv2.Circle(oriImg, list_5point[2], 3, new Scalar(255, 0, 0), -1); + Cv2.Circle(oriImg, list_5point[3], 3, new Scalar(255, 0, 0), -1); + Cv2.Circle(oriImg, list_5point[4], 3, new Scalar(255, 0, 0), -1); + + Cv2.Circle(oriImg, list_8area[0], 10, new Scalar(0, 255, 0), -1); + Cv2.Circle(oriImg, list_8area[1], 10, new Scalar(0, 255, 0), -1); + Cv2.Circle(oriImg, list_8area[2], 10, new Scalar(0, 255, 255), -1); + Cv2.Circle(oriImg, list_8area[3], 10, new Scalar(0, 255, 255), -1); + Cv2.Circle(oriImg, list_8area[4], 10, new Scalar(255, 255, 0), -1); + Cv2.Circle(oriImg, list_8area[5], 10, new Scalar(255, 255, 0), -1); + Cv2.Circle(oriImg, list_8area[6], 10, new Scalar(255, 0, 255), -1); + Cv2.Circle(oriImg, list_8area[7], 10, new Scalar(255, 0, 255), -1); + + Cv2.Line(oriImg, list_5point[0], list_5point[1], new Scalar(255, 100, 100)); + Cv2.Line(oriImg, list_5point[1], list_5point[2], new Scalar(255, 100, 100)); + Cv2.Line(oriImg, list_5point[2], list_5point[3], new Scalar(255, 100, 100)); + Cv2.Line(oriImg, list_5point[3], list_5point[4], new Scalar(255, 100, 100)); + Cv2.Line(oriImg, list_5point[4], list_5point[0], new Scalar(255, 100, 100)); + + return oriImg; + } + + + + + + public List Calc4Lab(List list_4colors, string path_calibCsv, string path_colorMatrixXYZ) + { + GetColorMatrixRGB(path_calibCsv); + CalcTransMat(path_colorMatrixXYZ); + Read_TranslationMatrix(); + var li_dst = new List(); + for (int i = 0; i < list_4colors.Count(); i++) + { + var LabValue = CalcLab(list_4colors[i]); + li_dst.Add(LabValue); + } + return li_dst; + } + + public void GetColorMatrixRGB(string path_calibCsv) + { + System.Text.Encoding encoding = GetType(path_calibCsv); + System.IO.FileStream fs3 = new System.IO.FileStream(path_calibCsv, System.IO.FileMode.Open, System.IO.FileAccess.Read); + System.IO.StreamReader sr3 = new System.IO.StreamReader(fs3, encoding); + string strLine = ""; + string[] aryLine; + sr3.ReadLine(); // headerをスルー + int i = 0; + while ((strLine = sr3.ReadLine()) != null) + { + aryLine = strLine.Split(','); + var no = Convert.ToSingle(aryLine[0]); + float r = Convert.ToSingle(aryLine[1]); + float g = Convert.ToSingle(aryLine[2]); + float b = Convert.ToSingle(aryLine[3]); + m_BforLab[i] = b; + m_GforLab[i] = g; + m_RforLab[i] = r; + i++; + } + sr3.Close(); + fs3.Close(); + } + + public void CalcTransMat(string path_colorMatrixXYZ) + { + Mat RGBmat = new Mat(24, 17, MatType.CV_64F, new Scalar(1.0f)); + Mat XYZmat = new Mat(24, 4, MatType.CV_64F, new Scalar(1.0f)); + + // ColorChartのXYZ読み込み + string line; + string[] split = new string[3]; + double valueX = 0, valueY = 0, valueZ = 0; + System.Text.Encoding encoding3 = GetType(path_colorMatrixXYZ); + System.IO.FileStream fs3 = new System.IO.FileStream(path_colorMatrixXYZ, System.IO.FileMode.Open, System.IO.FileAccess.Read); + System.IO.StreamReader sr3 = new System.IO.StreamReader(fs3, encoding3); + for (int i = 0; i < 24; i++) + { + line = sr3.ReadLine(); + split = line.Split(' '); + valueX = Convert.ToDouble(split[0]); + valueY = Convert.ToDouble(split[1]); + valueZ = Convert.ToDouble(split[2]); + XYZmat.Set(i, 0, valueX); + XYZmat.Set(i, 1, valueY); + XYZmat.Set(i, 2, valueZ); + } + sr3.Close(); + fs3.Close(); + + // 変換行列の計算 + for (int j = 0; j < 24; j++) + { + //順番 + //R,G,B,RG,RB,GB,R^2,G^2,B^2 + //R^2B,R^2G,G^2,R,G^2B,B^2R,B^2G,RGB + RGBmat.Set(j, 2, m_BforLab[j]); + RGBmat.Set(j, 1, m_GforLab[j]); + RGBmat.Set(j, 0, m_RforLab[j]); + //2V次の項 + RGBmat.Set(j, 3, m_RforLab[j] * m_GforLab[j]); + RGBmat.Set(j, 4, m_RforLab[j] * m_BforLab[j]); + RGBmat.Set(j, 5, m_GforLab[j] * m_BforLab[j]); + RGBmat.Set(j, 6, m_RforLab[j] * m_RforLab[j]); + RGBmat.Set(j, 7, m_GforLab[j] * m_GforLab[j]); + RGBmat.Set(j, 8, m_BforLab[j] * m_BforLab[j]); + //3V次の項 + RGBmat.Set(j, 9, m_RforLab[j] * m_RforLab[j] * m_BforLab[j]); + RGBmat.Set(j, 10, m_RforLab[j] * m_RforLab[j] * m_GforLab[j]); + RGBmat.Set(j, 11, m_GforLab[j] * m_GforLab[j] * m_RforLab[j]); + RGBmat.Set(j, 12, m_GforLab[j] * m_GforLab[j] * m_BforLab[j]); + RGBmat.Set(j, 13, m_BforLab[j] * m_BforLab[j] * m_RforLab[j]); + RGBmat.Set(j, 14, m_BforLab[j] * m_BforLab[j] * m_GforLab[j]); + RGBmat.Set(j, 15, m_RforLab[j] * m_BforLab[j] * m_GforLab[j]); + } + // 変換行列の生成 + Mat translation = new Mat(); + var canSolve = Cv2.Solve(RGBmat, XYZmat, translation, DecompTypes.SVD); + + // 保存 + string CSVfilename = "translateMatrix.csv"; + FileStream CSV_file = File.Open(CSVfilename, FileMode.OpenOrCreate, FileAccess.Write); + CSV_file.Seek(0, SeekOrigin.Begin); + CSV_file.SetLength(0); + CSV_file.Close(); + StreamWriter CSV_data = new StreamWriter(CSVfilename); + string s2 = ""; + for (int i = 0; i <= 16; i++) + { + for (int j = 0; j <= 2; j++) + { + double s1 = translation.At(i, j); + s2 += s1.ToString() + ","; + } + CSV_data.WriteLine(s2); + s2 = ""; + } + CSV_data.Close(); + } + + public void Read_TranslationMatrix() + { + // 変換行列を読み込みなおす + System.Text.Encoding encoding = GetType("translateMatrix.csv"); + System.IO.FileStream fs1 = new System.IO.FileStream("translateMatrix.csv", System.IO.FileMode.Open, System.IO.FileAccess.Read); + System.IO.StreamReader sr1 = new System.IO.StreamReader(fs1, encoding); + for (int i = 0; i < 17; i++) + { + a[i] = 0; + b[i] = 0; + c[i] = 0; + } + k = 0; + string strLine = ""; + string[] aryLine = null; + while ((strLine = sr1.ReadLine()) != null) + { + aryLine = strLine.Split(','); + a[k] = Convert.ToSingle(aryLine[0]); + b[k] = Convert.ToSingle(aryLine[1]); + c[k] = Convert.ToSingle(aryLine[2]); + k++; + } + sr1.Close(); + fs1.Close(); + } + + private OpenCvSharp.Scalar CalcLab(OpenCvSharp.Scalar BgrValue) + { + // CIELabの計算 + // XYZに変換 + double X, Y, Z; + double m_B = BgrValue.Val0; + double m_G = BgrValue.Val1; + double m_R = BgrValue.Val2; + X = m_R * a[0] + m_G * a[1] + m_B * a[2] + + a[3] * m_R * m_G + a[4] * m_R * m_B + a[5] * m_G * m_B + + a[6] * m_R * m_R + a[7] * m_G * m_G + a[8] * m_B * m_B + + a[9] * m_R * m_R * m_B + a[10] * m_R * m_R * m_G + + a[11] * m_G * m_G * m_R + a[12] * m_G * m_G * m_B + + a[13] * m_B * m_B * m_R + a[14] * m_B * m_B * m_G + + a[15] * m_R * m_G * m_B + + a[16]; + Y = m_R * b[0] + m_G * b[1] + m_B * b[2] + + b[3] * m_R * m_G + b[4] * m_R * m_B + b[5] * m_G * m_B + + b[6] * m_R * m_R + b[7] * m_G * m_G + b[8] * m_B * m_B + + b[9] * m_R * m_R * m_B + b[10] * m_R * m_R * m_G + + b[11] * m_G * m_G * m_R + b[12] * m_G * m_G * m_B + + b[13] * m_B * m_B * m_R + b[14] * m_B * m_B * m_G + + b[15] * m_R * m_G * m_B + + b[16]; + Z = m_R * c[0] + m_G * c[1] + m_B * c[2] + + c[3] * m_R * m_G + c[4] * m_R * m_B + c[5] * m_G * m_B + + c[6] * m_R * m_R + c[7] * m_G * m_G + c[8] * m_B * m_B + + c[9] * m_R * m_R * m_B + c[10] * m_R * m_R * m_G + + c[11] * m_G * m_G * m_R + c[12] * m_G * m_G * m_B + + c[13] * m_B * m_B * m_R + c[14] * m_B * m_B * m_G + + c[15] * m_R * m_G * m_B + + c[16]; + if (X < 0) X = 0; + if (Y < 0) Y = 0; + if (Z < 0) Z = 0; + + // Labに変換(固定の計算式) + // TIAS 光源 (測定値20201023)  + // double Xn = 99.5829; + // double Yn = 100.0; + // double Zn = 57.1402; + + // Tangさん,竹田さんが使用してた値 (おそらく昔のTIAS光源の測定値) + double Xn = 102.07; + double Yn = 100.0; + double Zn = 79.41; + + // 石川さん,中口先生が使用している値 人口太陽照明? + //double Xn = 92.219; + //double Yn = 100.0; + //double Zn = 95.965; + double cL = 116.0 * Math.Pow((Y / Yn), 1.0 / 3.0) - 16.0; + double ca = 500.0 * (Math.Pow((X / Xn), 1.0 / 3.0) - Math.Pow((Y / Yn), 1.0 / 3.0)); + double cb = 200.0 * (Math.Pow((Y / Yn), 1.0 / 3.0) - Math.Pow((Z / Zn), 1.0 / 3.0)); + + return new OpenCvSharp.Scalar(cL, ca, cb); + } + + public void Write8colors(List list_8Bgr, List list_8Lab) + { + // 保存 + string CSVfilename = "CalculatedLab.csv"; + FileStream CSV_file = File.Open(CSVfilename, FileMode.OpenOrCreate, FileAccess.Write); + CSV_file.Seek(0, SeekOrigin.Begin); + CSV_file.SetLength(0); + CSV_file.Close(); + + StreamWriter CSV_data = new StreamWriter(CSVfilename); + CSV_data.WriteLine("Area,R,G,B,L,a,B"); + for (int i = 0; i < list_8Bgr.Count(); i++) + { + string str = (i + 1).ToString() + ","; + str += + list_8Bgr[i].Val2.ToString("0.0000") + "," + + list_8Bgr[i].Val1.ToString("0.0000") + "," + + list_8Bgr[i].Val0.ToString("0.0000") + "," + + list_8Lab[i].Val0.ToString("0.0000") + "," + + list_8Lab[i].Val1.ToString("0.0000") + "," + + list_8Lab[i].Val2.ToString("0.0000"); + CSV_data.WriteLine(str); + } + CSV_data.Close(); + } + + private List IndexOfAll(List li, int target) + { + int num = li.IndexOf(target); + var li_num = new List(); + if (num > 0) + { + li_num.Add(num); + // IndexOfメソッドで見つからなくなるまで繰り返す + while (num > 0) + { + //見つかった位置の次の位置から検索 + num = li.IndexOf(target, num + 1); + if (num > 0) + { + li_num.Add(num); + } + } + } + else + { + Console.WriteLine("{0}は見つかりませんでした", target); + } + return li_num; + } + + + public static System.Text.Encoding GetType(string FILE_NAME) + { + System.IO.FileStream fs = new System.IO.FileStream(FILE_NAME, System.IO.FileMode.Open, System.IO.FileAccess.Read); + System.Text.Encoding r = GetType(fs); + fs.Close(); + return r; + } + + public static System.Text.Encoding GetType(System.IO.FileStream fs) + { + byte[] Unicode = new byte[] { 0xFF, 0xFE, 0x41 }; + byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF, 0x00 }; + byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF }; + System.Text.Encoding reVal = System.Text.Encoding.Default; + + System.IO.BinaryReader r = new System.IO.BinaryReader(fs, System.Text.Encoding.Default); + int i; + int.TryParse(fs.Length.ToString(), out i); + byte[] ss = r.ReadBytes(i); + if (IsUTF8Bytes(ss) || (ss[0] == 0xEF && ss[1] == 0xBB && ss[2] == 0xBF)) + { + reVal = System.Text.Encoding.UTF8; + } + else if (ss[0] == 0xFE && ss[1] == 0xFF && ss[2] == 0x00) + { + reVal = System.Text.Encoding.BigEndianUnicode; + } + else if (ss[0] == 0xFF && ss[1] == 0xFE && ss[2] == 0x41) + { + reVal = System.Text.Encoding.Unicode; + } + r.Close(); + return reVal; + } + + private static bool IsUTF8Bytes(byte[] data) + { + int charByteCounter = 1; + byte curByte; + for (int i = 0; i < data.Length; i++) + { + curByte = data[i]; + if (charByteCounter == 1) + { + if (curByte >= 0x80) + { + while (((curByte <<= 1) & 0x80) != 0) + { + charByteCounter++; + } + if (charByteCounter == 1 || charByteCounter > 6) + { + return false; + } + } + } + else + { + if ((curByte & 0xC0) != 0x80) + { + return false; + } + charByteCounter--; + } + } + if (charByteCounter > 1) + { + throw new Exception("Error"); + } + return true; + } + } + + + // Chiu methodに基づく色抽出 + // https://pubmed.ncbi.nlm.nih.gov/10661393/ + public partial class ColorExtractor_Chiu + { + public Mat Get3areaMask(Mat mat_mask) + { + // 表示用 + var mat_dst = mat_mask.Clone(); + Cv2.CvtColor(mat_dst, mat_dst, ColorConversionCodes.GRAY2BGR); + + // 舌領域画素の座標 + var mat_nonZeroCoordinates = new Mat(); + Cv2.FindNonZero(mat_mask, mat_nonZeroCoordinates); + + // 舌領域上の点をすべてlistに詰める + var list_X = new List(); + var list_Y = new List(); + var pts_nonZeroCoordinates = new List(); + for (int i = 0; i < mat_nonZeroCoordinates.Total(); i++) + { + var x = mat_nonZeroCoordinates.At(i).X; + var y = mat_nonZeroCoordinates.At(i).Y; + list_X.Add(x); + list_Y.Add(y); + pts_nonZeroCoordinates.Add(new OpenCvSharp.Point(x, y)); + } + + // 端 + var p_top = mat_nonZeroCoordinates.At(list_Y.IndexOf(list_Y.Min())); + var p_bottom = mat_nonZeroCoordinates.At(list_Y.IndexOf(list_Y.Max())); + var p_left = mat_nonZeroCoordinates.At(list_X.IndexOf(list_X.Min())); + var p_right = mat_nonZeroCoordinates.At(list_X.IndexOf(list_X.Max())); + + // 縦分割 Tip + var height = p_bottom.Y - p_top.Y; + var y_tip = (int)(p_bottom.Y - (height * 0.2)); + var p_tip_left = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_tip).Min()); + var p_tip_right = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_tip).Max()); + var pts_tip = pts_nonZeroCoordinates.Where(p => p.Y >= y_tip).ToList(); + + // 横分割 + var width = p_right.X - p_left.X; + var pts_lateral_left = new List(); + var pts_lateral_right = new List(); + var pts_middle = new List(); + for (int i = 1; i < (y_tip - p_top.Y); i++) + { + var y_tar = p_top.Y + i; + var left = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_tar).Min()); + var right = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_tar).Max()); + var tar_width = right.X - left.X; + if (tar_width < width * 0.3) + continue; + + var x_left = (int)(left.X + (width * 0.2)); + var x_right = (int)(right.X - (width * 0.2)); + var p_tar_left = new OpenCvSharp.Point(x_left, y_tar); + var p_tar_right = new OpenCvSharp.Point(x_right, y_tar); + + if (IndexOfAll(list_Y, y_tar).Count() == 0) + continue; + foreach (var index in IndexOfAll(list_Y, y_tar)) + { + var p = mat_nonZeroCoordinates.At(index); + if (p.X <= x_left) + pts_lateral_left.Add(p); + else if (p.X >= x_right) + pts_lateral_right.Add(p); + else + pts_middle.Add(p); + } + } + + // 領域表示 root, tip + var li_pts = new List>(); + pts_lateral_left.AddRange(pts_lateral_right); + li_pts.Add(pts_lateral_left); + li_pts.Add(pts_middle); + li_pts.Add(pts_tip); + + var li_color = new List { + new Vec3b(0, 0, 255), new Vec3b(255, 0, 0), new Vec3b(0, 128, 255) }; + + var j = 0; + foreach (var pts in li_pts) + { + foreach (var p in pts) + mat_dst.Set(p.Y, p.X, li_color[j]); + j++; + } + return mat_dst; + } + + + public List Calc3areaLab(Mat mat_input, List> li_pts) + { + var li_dst = new List(); + double al = 0.0, aa = 0.0, ab = 0.0; + + foreach (var pts in li_pts) + { + var c = 0; + var mat_l = new Mat(new OpenCvSharp.Size(1, pts.Count()), MatType.CV_8UC1); + var mat_a = new Mat(new OpenCvSharp.Size(1, pts.Count()), MatType.CV_8UC1); + var mat_b = new Mat(new OpenCvSharp.Size(1, pts.Count()), MatType.CV_8UC1); + + foreach (var p in pts) + { + var color_tar = mat_input.At(p.Y, p.X); + al += color_tar[0]; + aa += color_tar[1]; + ab += color_tar[2]; + + mat_l.Set(0, c, color_tar[0]); + mat_a.Set(0, c, color_tar[1]); + mat_b.Set(0, c, color_tar[2]); + } + al = al / (double)pts.Count(); + aa = aa / (double)pts.Count(); + ab = ab / (double)pts.Count(); + li_dst.Add(new OpenCvSharp.Scalar(al, aa, ab)); + } + return li_dst; + } + + private List IndexOfAll(List li, int target) + { + int num = li.IndexOf(target); + var li_num = new List(); + if (num > 0) + { + li_num.Add(num); + // IndexOfメソッドで見つからなくなるまで繰り返す + while (num > 0) + { + //見つかった位置の次の位置から検索 + num = li.IndexOf(target, num + 1); + if (num > 0) + { + li_num.Add(num); + } + } + } + else + { + Console.WriteLine("{0}は見つかりませんでした", target); + } + return li_num; + } + } + + // 拡張したChiu methodに基づく色抽出 + public partial class ColorExtractor_ExtendedChiu + { + public Mat Get4areaMask(Mat mat_mask) + { + // 表示用 + var mat_dst = mat_mask.Clone(); + Cv2.CvtColor(mat_dst, mat_dst, ColorConversionCodes.GRAY2BGR); + + // マスクの舌領域画素座標 + var mat_nonZeroCoordinates = new Mat(); + Cv2.FindNonZero(mat_mask, mat_nonZeroCoordinates); + + // 舌領域上の点をすべてlistに詰める + var list_X = new List(); + var list_Y = new List(); + var pts_nonZeroCoordinates = new List(); + for (int i = 0; i < mat_nonZeroCoordinates.Total(); i++) + { + var x = mat_nonZeroCoordinates.At(i).X; + var y = mat_nonZeroCoordinates.At(i).Y; + list_X.Add(x); + list_Y.Add(y); + pts_nonZeroCoordinates.Add(new OpenCvSharp.Point(x, y)); + } + + // 端 + var p_top = mat_nonZeroCoordinates.At(list_Y.IndexOf(list_Y.Min())); + var p_bottom = mat_nonZeroCoordinates.At(list_Y.IndexOf(list_Y.Max())); + var p_left = mat_nonZeroCoordinates.At(list_X.IndexOf(list_X.Min())); + var p_right = mat_nonZeroCoordinates.At(list_X.IndexOf(list_X.Max())); + + // 縦分割 Tip + var height = p_bottom.Y - p_top.Y; + var y_tip = (int)(p_bottom.Y - (height * 0.2)); + var p_tip_left = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_tip).Min()); + var p_tip_right = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_tip).Max()); + + // 縦分割 Root + var y_root = (int)(p_top.Y + (height * 0.2)); + var p_root_left = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_root).Min()); + var p_root_right = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_root).Max()); + + // 横分割 upper lateral + var width_root = p_root_right.X - p_root_left.X; + var p_lateral_lefttop = new OpenCvSharp.Point((int)(p_root_left.X + (width_root * 0.2)), p_root_left.Y); + var p_lateral_righttop = new OpenCvSharp.Point((int)(p_root_right.X - (width_root * 0.2)), p_root_right.Y); + + // 横分割 lower lateral + var width_tip = p_tip_right.X - p_tip_left.X; + var p_lateral_leftbottom = new OpenCvSharp.Point((int)(p_tip_left.X + (width_tip * 0.2)), p_tip_left.Y); + var p_lateral_rightbottom = new OpenCvSharp.Point((int)(p_tip_right.X - (width_tip * 0.2)), p_tip_right.Y); + + // 横分割 + var pts_lateral_left = new List(); + var pts_lateral_right = new List(); + var pts_middle = new List(); + for (int i = 0; i < (y_tip - y_root); i++) + { + var y_tar = y_root + i; + var left = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_tar).Min()); + var right = mat_nonZeroCoordinates.At(IndexOfAll(list_Y, y_tar).Max()); + var width = right.X - left.X; + var x_left = (int)(left.X + (width * 0.2)); + var x_right = (int)(right.X - (width * 0.2)); + var p_tar_left = new OpenCvSharp.Point(x_left, y_tar); + var p_tar_right = new OpenCvSharp.Point(x_right, y_tar); + + //Cv2.Circle(mat_dst, p_tar_left, 1, new Scalar(255, 0, 0), -1); + //Cv2.Circle(mat_dst, p_tar_right, 1, new Scalar(255, 0, 0), -1); + foreach (var index in IndexOfAll(list_Y, y_tar)) + { + var p = mat_nonZeroCoordinates.At(index); + if (p.X <= x_left) + pts_lateral_left.Add(p); + else if (p.X >= x_right) + pts_lateral_right.Add(p); + else + pts_middle.Add(p); + } + } + + // 領域表示 root, tip + var pts_root = pts_nonZeroCoordinates.Where(p => p.Y <= y_root).ToList(); + var pts_tip = pts_nonZeroCoordinates.Where(p => p.Y >= y_tip).ToList(); + var li_pts = new List>(); + pts_lateral_left.AddRange(pts_lateral_right); + li_pts.Add(pts_lateral_left); + li_pts.Add(pts_root); + li_pts.Add(pts_middle); + li_pts.Add(pts_tip); + + var li_color = new List { + new Vec3b(0, 0, 255), new Vec3b(0, 255, 0), new Vec3b(255, 0, 0), new Vec3b(0, 128, 255) }; + + var j = 0; + foreach (var pts in li_pts) + { + foreach (var p in pts) + mat_dst.Set(p.Y, p.X, li_color[j]); + j++; + } + return mat_dst; + } + + public List Calc4areaLab(Mat mat_input, List> li_pts) + { + var li_dst = new List(); + double al = 0.0, aa = 0.0, ab = 0.0; + + foreach (var pts in li_pts) + { + var c = 0; + var mat_l = new Mat(new OpenCvSharp.Size(1, pts.Count()), MatType.CV_8UC1); + var mat_a = new Mat(new OpenCvSharp.Size(1, pts.Count()), MatType.CV_8UC1); + var mat_b = new Mat(new OpenCvSharp.Size(1, pts.Count()), MatType.CV_8UC1); + + foreach (var p in pts) + { + var color_tar = mat_input.At(p.Y, p.X); + al += color_tar[0]; + aa += color_tar[1]; + ab += color_tar[2]; + + mat_l.Set(0, c, color_tar[0]); + mat_a.Set(0, c, color_tar[1]); + mat_b.Set(0, c, color_tar[2]); + } + al = al / (double)pts.Count(); + aa = aa / (double)pts.Count(); + ab = ab / (double)pts.Count(); + li_dst.Add(new OpenCvSharp.Scalar(al, aa, ab)); + + + //var mat_drawHist = new Mat(400, 512, MatType.CV_8UC3, Scalar.Black); + //var mat_histL = new Mat(); + //var mat_hista = new Mat(); + //var mat_histb = new Mat(); + //Cv2.CalcHist(new Mat[] { mat_l }, new int[] { 0 }, null, mat_histL, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) }); + //Cv2.CalcHist(new Mat[] { mat_a }, new int[] { 0 }, null, mat_hista, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) }); + //Cv2.CalcHist(new Mat[] { mat_b }, new int[] { 0 }, null, mat_histb, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) }); + //DrawHist(mat_drawHist, mat_histL, new Scalar(128, 128, 128)); + //DrawHist(mat_drawHist, mat_hista, new Scalar(64, 64, 256)); + //DrawHist(mat_drawHist, mat_histb, new Scalar(256, 64, 64)); + //Cv2.ImShow("aaaa", mat_drawHist); + //Cv2.WaitKey(); + } + + return li_dst; + } + + private void DrawHist(Mat histImage, Mat hist, Scalar color) + { + int bin = histImage.Width / 256; + for (int i = 0; i < 255; i++) + { + float value1 = hist.At(i, 0); + float value2 = hist.At(i + 1, 0); + var pt1 = new OpenCvSharp.Point(i * bin, histImage.Height - value1); + var pt2 = new OpenCvSharp.Point((i + 1) * bin, histImage.Height - value2); + Cv2.Line(histImage, pt1, pt2, color, 4); + } + } + + public List Calc4areaRGB(Mat mat_input, List> li_pts) + { + var li_dst = new List(); + double ab = 0.0, ag = 0.0, ar = 0.0; + foreach (var pts in li_pts) + { + foreach (var p in pts) + { + var color_tar = mat_input.At(p.Y, p.X); + ab += color_tar[0]; + ag += color_tar[1]; + ar += color_tar[2]; + } + ab = ab / (double)pts.Count(); + ag = ag / (double)pts.Count(); + ar = ar / (double)pts.Count(); + li_dst.Add(new OpenCvSharp.Scalar(ab, ag, ar)); + } + return li_dst; + } + + private List IndexOfAll(List li, int target) + { + int num = li.IndexOf(target); + var li_num = new List(); + if (num > 0) + { + li_num.Add(num); + // IndexOfメソッドで見つからなくなるまで繰り返す + while (num > 0) + { + //見つかった位置の次の位置から検索 + num = li.IndexOf(target, num + 1); + if (num > 0) + { + li_num.Add(num); + } + } + } + else + { + Console.WriteLine("{0}は見つかりませんでした", target); + } + return li_num; + } + + } +} diff --git a/Tongue extraction/DeepTIAS2.7.csproj b/Tongue extraction/DeepTIAS2.7.csproj new file mode 100644 index 0000000..b0c7421 --- /dev/null +++ b/Tongue extraction/DeepTIAS2.7.csproj @@ -0,0 +1,288 @@ + + + + + + + + + Debug + AnyCPU + {AFD610B1-8D23-423A-AA0F-B09BA769BDD7} + WinExe + Properties + Tongue_extraction + DeepTIAS2.7 + v4.8 + 512 + true + + + + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + DeepTIAS_Icon.ico + + + false + + + false + + + + + Form + + + Form1.cs + + + + + True + True + Settings.settings + + + Form1.cs + Designer + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + PreserveNewest + + + + SettingsSingleFileGenerator + Settings2.Designer.cs + + + PreserveNewest + + + + + + + + + + Always + + + + + False + Microsoft .NET Framework 4.7.1 %28x86 および x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + ..\packages\Microsoft.ML.1.6.0\lib\netstandard2.0\Microsoft.ML.dll + + + ..\packages\Microsoft.ML.1.6.0\lib\netstandard2.0\Microsoft.ML.Core.dll + + + ..\packages\Microsoft.ML.CpuMath.1.6.0\lib\netstandard2.0\Microsoft.ML.CpuMath.dll + + + ..\packages\Microsoft.ML.1.6.0\lib\netstandard2.0\Microsoft.ML.Data.dll + + + ..\packages\Microsoft.ML.DataView.1.6.0\lib\netstandard2.0\Microsoft.ML.DataView.dll + + + ..\packages\Microsoft.ML.1.6.0\lib\netstandard2.0\Microsoft.ML.KMeansClustering.dll + + + ..\packages\Microsoft.ML.OnnxRuntime.Managed.1.9.0\lib\netstandard2.0\Microsoft.ML.OnnxRuntime.dll + + + ..\packages\Microsoft.ML.1.6.0\lib\netstandard2.0\Microsoft.ML.PCA.dll + + + ..\packages\Microsoft.ML.1.6.0\lib\netstandard2.0\Microsoft.ML.StandardTrainers.dll + + + ..\packages\Microsoft.ML.1.6.0\lib\netstandard2.0\Microsoft.ML.Transforms.dll + + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + + ..\packages\OpenCvSharp4.4.9.0.20240103\lib\net48\OpenCvSharp.dll + + + False + ..\..\..\packages\OpenCvSharp3-AnyCPU.3.2.0.20170107\lib\net40\OpenCvSharp.Blob.dll + + + ..\packages\OpenCvSharp4.Extensions.4.9.0.20240103\lib\net48\OpenCvSharp.Extensions.dll + + + ..\packages\OpenCvSharp4.WpfExtensions.4.9.0.20240103\lib\net48\OpenCvSharp.WpfExtensions.dll + + + + + C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.1\System.dll + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + ..\packages\System.CodeDom.4.4.0\lib\net461\System.CodeDom.dll + + + ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll + + + C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.1\System.ComponentModel.Composition.dll + + + C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.1\System.ComponentModel.Composition.Registration.dll + + + C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.1\System.ComponentModel.DataAnnotations.dll + + + + + + + ..\packages\System.Drawing.Common.7.0.0\lib\net462\System.Drawing.Common.dll + + + + + + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.1\System.Reflection.Context.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + + ..\packages\System.Threading.Channels.4.7.1\lib\net461\System.Threading.Channels.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + + + + + + + ..\packages\TensorFlowSharp.1.15.1\lib\net471\TensorFlowSharp.dll + + + + + + + このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tongue extraction/DeepTIAS2.7.csproj.user b/Tongue extraction/DeepTIAS2.7.csproj.user new file mode 100644 index 0000000..aeeb0cb --- /dev/null +++ b/Tongue extraction/DeepTIAS2.7.csproj.user @@ -0,0 +1,13 @@ + + + + publish\ + + + + + + ja-JP + false + + \ No newline at end of file diff --git a/Tongue extraction/DeepTIAS_Icon.ico b/Tongue extraction/DeepTIAS_Icon.ico new file mode 100644 index 0000000..54e10bd --- /dev/null +++ b/Tongue extraction/DeepTIAS_Icon.ico Binary files differ diff --git a/Tongue extraction/DeepTongue_Icon.ico b/Tongue extraction/DeepTongue_Icon.ico new file mode 100644 index 0000000..85fef71 --- /dev/null +++ b/Tongue extraction/DeepTongue_Icon.ico Binary files differ diff --git a/Tongue extraction/Form1 - backups.cs b/Tongue extraction/Form1 - backups.cs new file mode 100644 index 0000000..d359c26 --- /dev/null +++ b/Tongue extraction/Form1 - backups.cs @@ -0,0 +1,1556 @@ +using System; +using System.Windows.Forms; +using System.IO; +using System.Drawing; +using System.Drawing.Imaging; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Threading; +using System.Diagnostics; +using System.Linq; +using System.Collections.Generic; +using TensorFlow; +using OpenCvSharp; + +namespace Tongue_extraction +{ + public partial class Form1 : Form + { + //Use the RemoveSmallRegionDLL + [DllImport(@"RemoveSmallRegionDLL.dll", EntryPoint = "RemoveSmallRegion", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.StdCall)] + extern static void RemoveSmallRegion(string name, string name2, int AreaLimit, int CheckMode, int NeihborMode); + + Mat mat_drawBox = new Mat(1024, 1280, MatType.CV_8UC3, 1); + Mat mat_roi = new Mat(1024, 1280, MatType.CV_8UC3, 1); + Mat mat_input = new Mat(1024, 1280, MatType.CV_8UC3, 1); + Mat mat_roi256 = new Mat(256, 256, MatType.CV_8UC3, 1); + Mat mat_roisize = new Mat(1024, 1280, MatType.CV_8UC3, 1); + Mat mat_output = new Mat(1024, 1280, MatType.CV_8UC1, 1); + Mat mat_outputNoBox = new Mat(1024, 1280, MatType.CV_8UC1, 1); + Mat mat_outputChanged = new Mat(1024, 1280, MatType.CV_8UC1, 1); + Mat mat_mask = new Mat(1024, 1280, MatType.CV_8UC1, 1); + Mat mat_extraction = new Mat(1024, 1280, MatType.CV_8UC3, 1); + Mat mat_cropped; + Mat mat_outputSRGNoBox = new Mat(1024, 1280, MatType.CV_8UC1, 1); + Mat mat_outputSRG = new Mat(1024, 1280, MatType.CV_8UC1, 1); + Mat mat_maskSRG = new Mat(1024, 1280, MatType.CV_8UC1, 1); + + public static Bitmap bitmap_bitch; + + OpenCvSharp.Rect rectangle; + + byte[] byte_inputDetection; + byte[] byte_inputSegmentation; + OpenCvSharp.Point P1 = new OpenCvSharp.Point(); + OpenCvSharp.Point P2 = new OpenCvSharp.Point(); + + byte[] mask = new byte[200000]; + + string basepath; + string imageFile; + string time; + string modelFile; + int ii = 0; + int count; + int areaCount; + + Rect roi = new Rect(); + int mmp = 0; + int pmm = 0; + + int check_detection = 0; + float max_score = 0; + + private ManualResetEvent manualReset = new ManualResetEvent(true); + + string path_infocsv = Directory.GetCurrentDirectory() + "//info//" + DateTime.Now.ToLocalTime().ToString("yyyyMMddhhmmss") + ".csv"; + + string path_procList = @"ProcList.txt"; + + bool IsLastFileChanged = false; + + public Form1() + { + InitializeComponent(); + } + + private void Form1_Load(object sender, EventArgs e) + { + comboBox_mode.SelectedIndex = 2; + } + + private void Button_start_Click(object sender, EventArgs e) + { + button_start.Enabled = false; + button_pause.Enabled = true; + pictureBox_input.Image = null; + pictureBox_input.Refresh(); + pictureBox_detection.Image = null; + pictureBox_detection.Refresh(); + pictureBox_cropResized.Image = null; + pictureBox_cropResized.Refresh(); + pictureBox_output.Image = null; + pictureBox_output.Refresh(); + pictureBox_outputSRG.Image = null; + pictureBox_outputSRG.Refresh(); + pictureBox_maskSRG.Image = null; + pictureBox_maskSRG.Refresh(); + pictureBox_extraction.Image = null; + pictureBox_extraction.Refresh(); + pictureBox_last.Image = null; + pictureBox_last.Refresh(); + comboBox_mode.Enabled = false; + + if (comboBox_mode.SelectedIndex == 0) + { + // 画像のみ入力し,マスク等を得る + this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.BackgroundWorker1_DoWork_Image); + backgroundWorker1.RunWorkerAsync(); + } + else if (comboBox_mode.SelectedIndex == 1) + { + // 画像と校正画像を入力し,Lab色を得る + this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.BackgroundWorker1_DoWork_ImageandCalib); + backgroundWorker1.RunWorkerAsync(); + } + else if (comboBox_mode.SelectedIndex == 2) + { + // TIASフォルダを入力し,Lab色を得る + this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.BackgroundWorker1_DoWork_TIASdata); + backgroundWorker1.RunWorkerAsync(); + } + else + { + MessageBox.Show("Please select the mode on the combobox"); + } + } + + public static class ImageUtil + { + // Convert the image in filename to a Tensor suitable as input to the Inception model. + public static TFTensor CreateTensorFromImageFile(byte[] contents, TFDataType destinationDataType = TFDataType.UInt8) + { + // DecodeJpeg uses a scalar String-valued tensor as input. + var tensor = TFTensor.CreateString(contents); + + TFGraph graph; + TFOutput input, output; + + // Construct a graph to normalize the image + ConstructGraphToNormalizeImage(out graph, out input, out output, destinationDataType); + + // Execute that graph to normalize this one image + using (var session = new TFSession(graph)) + { + var normalized = session.Run( + inputs: new[] { input }, + inputValues: new[] { tensor }, + outputs: new[] { output }); + + if (session != null) + { + session.Dispose(); + } + if (tensor != null) + { + tensor.Dispose(); + } + if (graph != null) + { + graph.Dispose(); + } + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + return normalized[0]; + } + } + + // The inception model takes as input the image described by a Tensor in a very + // specific normalized format (a particular image size, shape of the input tensor, + // normalized pixel values etc.). + // + // This function constructs a graph of TensorFlow operations which takes as + // input a JPEG-encoded string and returns a tensor suitable as input to the + // inception model. + + public static void ConstructGraphToNormalizeImage(out TFGraph graph, out TFOutput input, out TFOutput output, TFDataType destinationDataType = TFDataType.UInt8) + { + const int W = 256; + const int H = 256; + const float Mean = 0; + const float Scale = 1; + graph = new TFGraph(); + input = graph.Placeholder(TFDataType.String); + output = graph.Cast(graph.Div( + x: graph.Sub( + x: graph.ResizeBilinear( + images: graph.ExpandDims( + input: graph.Cast( + graph.DecodeJpeg(contents: input, channels: 3), DstT: destinationDataType), + dim: graph.Const(0, "make_batch")), + size: graph.Const(new int[] { W, H }, "size")), + y: graph.Const(Mean, "mean")), + y: graph.Const(Scale, "scale")), destinationDataType); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + } + + public static class ImageUtil2 + { + // Convert the image in filename to a Tensor suitable as input to the Inception model. + public static TFTensor CreateTensorFromImageFile(byte[] contents, TFDataType destinationDataType = TFDataType.Float) + { + // DecodeJpeg uses a scalar String-valued tensor as input. + var tensor = TFTensor.CreateString(contents); + + TFGraph graph; + TFOutput input, output; + + // Construct a graph to normalize the image + ConstructGraphToNormalizeImage(out graph, out input, out output, destinationDataType); + + // Execute that graph to normalize this one image + using (var session = new TFSession(graph)) + { + var normalized = session.Run( + inputs: new[] { input }, + inputValues: new[] { tensor }, + outputs: new[] { output }); + + if (session != null) + { + session.Dispose(); + } + if (tensor != null) + { + tensor.Dispose(); + } + if (graph != null) + { + graph.Dispose(); + } + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + return normalized[0]; + } + } + + // The inception model takes as input the image described by a Tensor in a very + // specific normalized format (a particular image size, shape of the input tensor, + // normalized pixel values etc.). + // + // This function constructs a graph of TensorFlow operations which takes as + // input a JPEG-encoded string and returns a tensor suitable as input to the + // inception model. + + public static void ConstructGraphToNormalizeImage(out TFGraph graph, out TFOutput input, out TFOutput output, TFDataType destinationDataType = TFDataType.Float) + { + const int W = 256; + const int H = 256; + //const int W = 512; + //const int H = 512; + + const float Mean = 128; + const float Scale = 128; + graph = new TFGraph(); + input = graph.Placeholder(TFDataType.String); + output = graph.Cast(graph.Div( + x: graph.Sub( + x: graph.ResizeBilinear( + images: graph.ExpandDims( + input: graph.Cast( + graph.DecodeJpeg(contents: input, channels: 3), DstT: TFDataType.Float), + dim: graph.Const(0, "make_batch")), + size: graph.Const(new int[] { W, H }, "size")), + y: graph.Const(Mean, "mean")), + y: graph.Const(Scale, "scale")), destinationDataType); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + } + + public static byte[] Bitmap2Byte(Bitmap bitmap) + { + using (MemoryStream stream = new MemoryStream()) + { + bitmap.Save(stream, ImageFormat.Jpeg); + byte[] data = new byte[stream.Length]; + stream.Seek(0, SeekOrigin.Begin); + stream.Read(data, 0, Convert.ToInt32(stream.Length)); + return data; + } + } + + private static string DownloadDefaultModel(string dir) + { + var modelFile = Path.Combine(dir, "pruning101_step11999.pb"); + return modelFile; + } + + private static string DownloadDefaultModel_noBoxPix2Pix(string dir) + { + var modelFile = Path.Combine(dir, "424_256_64_5999_scale300_enhancment_L1loss0.02001.pb"); + return modelFile; + } + + public static Bitmap ToGrayBitmap(byte[] rawValues, int width, int height) + { + //// Apply for a target bitmap variable and lock its memory area + Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed); + BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); + + //// Get image parameters + int stride = bmpData.Stride; // Width of the scan line + int offset = stride - width; // Show gap between width and scan line width + IntPtr iptr = bmpData.Scan0; // Get the memory start position of bmpData + int scanBytes = stride * height;// Use stride width to indicate that this is the size of the memory area + + //// The following is to convert the original display size byte array to the byte array actually stored in memory. + int posScan = 0, posReal = 0;// Set two position pointers respectively, pointing to the source array and the target array + byte[] pixelValues = new byte[scanBytes]; //Allocate memory for the target array + + for (int x = 0; x < height; x++) + { + //// The following loop section is a simulated line scan + for (int y = 0; y < width; y++) + { + pixelValues[posScan++] = rawValues[posReal++]; + } + posScan += offset; //At the end of the line scan, move the target position pointer over that "gap" + } + + //// Use Marshal's Copy method to copy the just obtained memory byte array into BitmapData. + System.Runtime.InteropServices.Marshal.Copy(pixelValues, 0, iptr, scanBytes); + bmp.UnlockBits(bmpData); // Unlock the memory area + + //// The following code is to modify the index table of the generated bitmap, from pseudo color to grayscale + ColorPalette tempPalette; + using (Bitmap tempBmp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed)) + { + tempPalette = tempBmp.Palette; + } + for (int i = 0; i < 256; i++) + { + tempPalette.Entries[i] = Color.FromArgb(i, i, i); + } + + bmp.Palette = tempPalette; + + //// The algorithm ends here and returns the result. + return bmp; + } + + private void BackgroundWorker1_DoWork_Image(object sender, DoWorkEventArgs e) + { + MessageBox.Show("this mode isn't implemented"); + } + + private void BackgroundWorker1_DoWork_ImageandCalib(object sender, DoWorkEventArgs e) + { + + // boundingboxなどのinfo出力用 + var sw = new StreamWriter(path_infocsv, false, System.Text.Encoding.GetEncoding("shift_jis")); + // 出力用csvの準備 + sw.Write( + "image" + "," + + "L1" + "," + "a1" + "," + "b1" + "," + + "L2" + "," + "a2" + "," + "b2" + "," + + "L3" + "," + "a3" + "," + "b3" + "," + + "L4" + "," + "a4" + "," + "b4" + ); + + sw.WriteLine(); + sw.Close(); + + using (MemoryStream ms = new MemoryStream()) + { + // ディレクトリglobの取得 + var glob_dir = Directory.GetDirectories("input"); + + for (int a = 0; a < glob_dir.Length; a++) + { + manualReset.WaitOne(); + + // 画像と校正ファイルのパス + var glob_file = Directory.GetFiles(glob_dir[a]); + var path_calib = glob_file.Where(n => n.Contains("csv")).ToList()[0]; + var path_image = glob_file.Where(n => n.Contains("bmp") || n.Contains("png")).ToList()[0]; + var path_base = Directory.GetCurrentDirectory(); + var name_image = Path.GetFileName(path_image); + var name_dir = Path.GetFileName(glob_dir[a]); + + // ステータスの表示 + Invoke((MethodInvoker)delegate + { + label_processingFileName.Text = "Processing File: " + "\\" + name_dir; + count = a + 1; + label_totalProgress.Text = "Total Progress: " + count + "/" + glob_dir.Length; + }); + + // 入力画像読み込み・表示 + mat_input = Cv2.ImRead(path_image, ImreadModes.Color); + + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_input); + Invoke((MethodInvoker)delegate + { + pictureBox_input.Image = bitmap_bitch; + pictureBox_input.Refresh(); + }); + + // Detection + label8.BackColor = Color.White; + label1.BackColor = Color.Red; + mat_drawBox = mat_input.Clone(); + mat_cropped = new Mat(mat_input.Size(), MatType.CV_8UC3, 1); + byte_inputDetection = Bitmap2Byte(bitmap_bitch); + using (var graph = new TFGraph()) + { + var model = File.ReadAllBytes(path_base + "/Detection_Normal.pb"); + graph.Import(model, ""); + + using (var session = new TFSession(graph)) + { + var tensor = ImageUtil.CreateTensorFromImageFile(byte_inputDetection, TFDataType.UInt8); + + var runner = session.GetRunner(); + runner + .AddInput(graph["image_tensor"][0], tensor) + .Fetch("detection_boxes", "detection_scores", "detection_classes", "num_detections"); + + var output = runner.Run(); + var boxes = (float[,,])output[0].GetValue(); + var scores = (float[,])output[1].GetValue(); + var classes = (float[,])output[2].GetValue(); + var detections = (float[])output[3].GetValue(); + check_detection = 0; + max_score = 0; + for (int i = 0; i < scores.Length; i++) + { + if ((scores[0, i] > 0.5) && (scores[0, i] > max_score)) + { + max_score = scores[0, i]; + float y_min = boxes[0, i, 0] * (float)bitmap_bitch.Height; + float x_min = boxes[0, i, 1] * (float)bitmap_bitch.Width; + float y_max = boxes[0, i, 2] * (float)bitmap_bitch.Height; + float x_max = boxes[0, i, 3] * (float)bitmap_bitch.Width; + P1.X = (int)x_min; + P1.Y = (int)y_min; + P2.X = (int)x_max; + P2.Y = (int)y_max; + Cv2.Rectangle(mat_drawBox, P1, P2, new Scalar(0, 255, 0), 5); + rectangle.X = (int)x_min; + rectangle.Y = (int)y_min; + rectangle.Width = (int)(x_max - x_min); + rectangle.Height = (int)(y_max - y_min); + + check_detection = 1; + } + } + } + } + // Detection結果表示・保存 + mat_drawBox.SaveImage(path_base + "\\detection" + "\\" + name_dir + ".bmp"); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_drawBox); + Invoke((MethodInvoker)delegate + { + pictureBox_detection.Image = bitmap_bitch; + pictureBox_detection.Refresh(); + }); + label1.BackColor = Color.White; + + // 舌検出領域でcrop + label2.BackColor = Color.Red; + for (int i = P1.Y; i < P2.Y; i++) + { + for (int j = P1.X; j < P2.X; j++) + { + Vec3b pix = mat_input.At(i, j); + mat_cropped.Set(i, j, pix); + } + } + mat_cropped.SaveImage(path_base + "\\cropped" + "\\" + name_dir + ".bmp"); + + // 舌検出領域でresize + OpenCvSharp.Size size_roi = new OpenCvSharp.Size(); + size_roi.Height = rectangle.Height; + size_roi.Width = rectangle.Width; + roi = new Rect(P1, size_roi); + + + // 20210201 + var mat_input2 = mat_input.Clone(); + mat_input2 = mat_input2 * 1.5; + + mat_roisize = mat_input2.Clone(roi); + Cv2.Resize(mat_roisize, mat_roi, mat_roi256.Size()); + mat_roi.SaveImage(path_base + "\\cropresized" + "\\" + name_dir + ".bmp"); + + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_roi); + Invoke((MethodInvoker)delegate + { + pictureBox_cropResized.Image = bitmap_bitch; + pictureBox_cropResized.Refresh(); + }); + label2.BackColor = Color.White; + + // Segmenation + label3.BackColor = Color.Red; + + byte_inputSegmentation = Bitmap2Byte(bitmap_bitch); + Thread.Sleep(1000); + modelFile = DownloadDefaultModel(path_base); + ii = 0; + using (var graph = new TFGraph()) + { + var model = File.ReadAllBytes(modelFile); + graph.Import(model, ""); + + using (var session = new TFSession(graph)) + { + var tensor = ImageUtil2.CreateTensorFromImageFile(byte_inputSegmentation); + var runner = session.GetRunner(); + runner + //.AddInput(graph["generator/input_image"][0], tensor) + //.Fetch(graph["generator/prediction"][0]); + + .AddInput(graph["input_image"][0], tensor) + .Fetch(graph["generator1/decoder_1/Tanh"][0]); + + var output = runner.Run(); + float[,,,] resultfloat = (float[,,,])output[0].GetValue(jagged: false); + + for (int p = 0; p < 256; p++) + { + for (int q = 0; q < 256; q++) + { + float check = resultfloat[0, p, q, 0]; + if (check < 0) + { + mask[ii] = 0; + } + else + { + mask[ii] = 255; + } + ii++; + } + } + } + } + GC.Collect(); + Thread.Sleep(1000); + + // segmentation結果表示・保存 + bitmap_bitch = ToGrayBitmap(mask, 256, 256); + mat_output = OpenCvSharp.Extensions.BitmapConverter.ToMat(bitmap_bitch); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_output); + Invoke((MethodInvoker)delegate + { + pictureBox_output.Image = bitmap_bitch; + pictureBox_output.Refresh(); + }); + label3.BackColor = Color.White; + label4.BackColor = Color.Red; + mat_output.SaveImage(path_base + "\\output256" + "\\" + name_dir + ".bmp"); + + // 後処理(領域拡張法)でノイズ除去 iteration 2 + try + { + RemoveSmallRegion(path_base + "\\output256" + "\\" + name_dir + ".bmp", path_base + "\\output_changed1" + "\\" + name_dir + ".bmp", 500, 1, 1); + RemoveSmallRegion(path_base + "\\output_changed1" + "\\" + name_dir + ".bmp", path_base + "\\output_changed2" + "\\" + name_dir + ".bmp", 500, 0, 0); + } + catch + { + MessageBox.Show("Error: Unable to reprocess! Please check is there [RemoveSmallRegionDLL.dll] file in floder?", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + break; + } + mat_outputSRG = new Mat(path_base + "\\output_changed2" + "\\" + name_dir + ".bmp", ImreadModes.GrayScale); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_outputSRG); + Invoke((MethodInvoker)delegate + { + pictureBox_outputSRG.Image = bitmap_bitch; + pictureBox_outputSRG.Refresh(); + }); + label4.BackColor = Color.White; + label5.BackColor = Color.Red; + + // バウンディングボックスのサイズに戻す + Cv2.Resize(mat_outputSRG, mat_outputChanged, mat_roisize.Size()); + mat_outputChanged.SaveImage(path_base + "\\output_resized" + "\\" + name_dir + ".bmp"); ; + + // 入力と同じサイズでマスクを作成(なんかたまに不安定.resizeで微妙にサイズ変わってrange overかも) + // 気休め + Thread.Sleep(100); + GC.Collect(); + + mat_mask = new Mat(mat_input.Size(), MatType.CV_8UC1, 0); + var y_mask = 0; + for (int y = P1.Y; y < P2.Y; y++) + { + if (y_mask >= mat_outputChanged.Height) + break; + + var x_mask = 0; + for (int x = P1.X; x < P2.X; x++) + { + if (x_mask >= mat_outputChanged.Width) + break; + + int pix = mat_outputChanged.At(y_mask, x_mask); + mat_mask.Set(y, x, pix); + x_mask++; + } + y_mask++; + } + mmp = 0; + Cv2.Resize(mat_mask, mat_mask, mat_input.Size()); + mat_mask.SaveImage(path_base + "\\mask" + "\\" + name_dir + ".bmp"); + + // ノイズ処理 + try + { + RemoveSmallRegion(path_base + "\\mask" + "\\" + name_dir + ".bmp", path_base + "\\mask_changed1" + "\\" + name_dir + ".bmp", 500, 1, 1); + RemoveSmallRegion(path_base + "\\mask_changed1" + "\\" + name_dir + ".bmp", path_base + "\\mask_changed2" + "\\" + name_dir + ".bmp", 500, 0, 0); + } + catch + { + MessageBox.Show("Error: Unable to reprocess! Please check is there [RemoveSmallRegionDLL.dll] file in floder?", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + break; + } + mat_maskSRG = new Mat(path_base + "\\mask_changed2" + "\\" + name_dir + ".bmp", ImreadModes.GrayScale); + Cv2.Threshold(mat_maskSRG, mat_maskSRG, 128, 255, ThresholdTypes.Binary); + + // 2値マスクの最終結果 + mat_maskSRG.SaveImage(path_base + "\\mask_final" + "\\" + name_dir + ".bmp"); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_maskSRG); + Invoke((MethodInvoker)delegate + { + pictureBox_maskSRG.Image = bitmap_bitch; + pictureBox_maskSRG.Refresh(); + }); + label5.BackColor = Color.White; + + // 元画像とマスクを合わせて,舌領域を抜き出す  + // todo: opencv等によるマスク処理と領域計算へ + label6.BackColor = Color.Red; + mat_extraction = mat_input.Clone(); + areaCount = 0; + for (int i = 0; i < mat_input.Height; i++) + { + for (int j = 0; j < mat_input.Width; j++) + { + Vec3b pix = mat_extraction.At(i, j); + if (mat_maskSRG.At(i, j) == 0) + { + pix[0] = (byte)(255); + pix[1] = (byte)(255); + pix[2] = (byte)(255); + mat_extraction.Set(i, j, pix); + } + else + { + pix[0] = (byte)(mat_extraction.At(i, j).Item0); + pix[1] = (byte)(mat_extraction.At(i, j).Item1); + pix[2] = (byte)(mat_extraction.At(i, j).Item2); + mat_extraction.Set(i, j, pix); + areaCount++; + } + } + } + mat_extraction.SaveImage(path_base + "\\extraction" + "\\" + name_dir + ".bmp"); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_extraction); + Invoke((MethodInvoker)delegate + { + pictureBox_extraction.Image = bitmap_bitch; + pictureBox_extraction.Refresh(); + }); + label6.BackColor = Color.White; + + /// 5点クリックによる色抽出処理 + var path_colorMatrixXYZ = "xyz.txt"; + + // 色抽出処理のクラス + ColorExtractor ce = new ColorExtractor(); + + // マスクされた舌領域画像の作成 + Mat mat_finalMask = mat_maskSRG.Clone(); + Cv2.ImWrite(path_base + "\\final_mask" + "\\" + name_dir + ".bmp", mat_finalMask); + + Mat mat_maskedImg = new Mat(); + mat_input.CopyTo(mat_maskedImg, mat_finalMask); + + // 5点クリック法(2010石川) + List list_5points_3 = ce.Get5points(mat_finalMask, ColorExtractor.FivePointMethod.Method3); + + // 8領域の取得 + List list_8area_3 = ce.Get8area(list_5points_3); + + // 領域の表示 + var mat_areaDicision = ce.ShowResult(mat_input.Clone(), list_5points_3, list_8area_3); + + // 色抽出 + List list_4Bgr = ce.Get4colors(mat_maskedImg, list_8area_3); + + // 色変換(RGB->XYZ->Lab) + List list_4Lab = ce.Calc4Lab(list_4Bgr, path_calib, path_colorMatrixXYZ); + + // 保存 + string CSVfilename = path_base + "\\color" + "\\" + name_dir + ".csv"; + FileStream CSV_file = File.Open(CSVfilename, FileMode.OpenOrCreate, FileAccess.Write); + CSV_file.Seek(0, SeekOrigin.Begin); + CSV_file.SetLength(0); + CSV_file.Close(); + + StreamWriter CSV_data = new StreamWriter(CSVfilename); + CSV_data.WriteLine("Area,R,G,B,L,a,b"); + for (int i = 0; i < list_4Bgr.Count(); i++) + { + string str = (i + 1).ToString() + ","; + str += + list_4Bgr[i].Val2.ToString("0.0000") + "," + + list_4Bgr[i].Val1.ToString("0.0000") + "," + + list_4Bgr[i].Val0.ToString("0.0000") + "," + + list_4Lab[i].Val0.ToString("0.0000") + "," + + list_4Lab[i].Val1.ToString("0.0000") + "," + + list_4Lab[i].Val2.ToString("0.0000"); + CSV_data.WriteLine(str); + } + CSV_data.Close(); + + // 廃棄 + GC.Collect(); + System.Threading.Thread.Sleep(100); + + mat_areaDicision.SaveImage(path_base + "\\autoAreaDecision" + "\\" + name_dir + ".bmp"); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_areaDicision); + Invoke((MethodInvoker)delegate + { + pictureBox_last.Image = bitmap_bitch; + pictureBox_last.Refresh(); + }); + label7.BackColor = Color.White; + + // 処理log + label8.BackColor = Color.Red; + time = DateTime.Now.ToLocalTime().ToString(); + File.AppendAllText("Log.txt ", time + " " + "\\" + name_dir + ".bmp" + " Done!\n"); + + // info出力 + sw = new StreamWriter(path_infocsv, true, System.Text.Encoding.GetEncoding("shift_jis")); + + string stra = ""; + for (int i = 0; i < list_4Bgr.Count(); i++) + { + stra += + list_4Lab[i].Val0.ToString("0.0000") + "," + + list_4Lab[i].Val1.ToString("0.0000") + "," + + list_4Lab[i].Val2.ToString("0.0000") + ","; + } + sw.Write( + name_dir.ToString() + "," + + stra + ); + sw.Write("\n"); + + sw.Close(); + // 破棄 + + } + GC.Collect(); + MessageBox.Show("Finished!"); + + Invoke((MethodInvoker)delegate + { + button_start.Enabled = true; + button_pause.Enabled = false; + label_processingFileName.Text = "Processing File: None"; + }); + } + } + + // Debugs + private void BackgroundWorker1_DoWork_TIASdata(object sender, DoWorkEventArgs e) + { + // boundingboxなどのinfo出力用 + var sw = new StreamWriter(path_infocsv, false, System.Text.Encoding.GetEncoding("shift_jis")); + // 出力用csvの準備 + sw.WriteLine( + "image" + "," + + "top left X" + "," + "top left Y" + "," + "bottom right X" + "," + "bottom right Y" + "," + + "Width" + "," + "Height" + "," + "Area"); + sw.Close(); + + // TIASフォルダのパスを取得 + var path_procBase = ""; + Invoke((MethodInvoker)delegate + { + //ダイアログを表示する + if (folderBrowserDialog1.ShowDialog() == DialogResult.OK) + { + path_procBase = folderBrowserDialog1.SelectedPath; + label_processingFileName.Text = "処理フォルダ: " + path_procBase; + Console.WriteLine("処理フォルダ: " + path_procBase); + } + else + { + MessageBox.Show("プログラムを再起動してください。"); + return; + } + }); + + + // 色抽出(TongueAnalysisbin2.3)のため,Proclistを初期化 + FileStream fs = File.Open(path_procList, FileMode.OpenOrCreate, FileAccess.Write); + fs.Seek(0, SeekOrigin.Begin); + fs.SetLength(0); + StreamWriter sw_ = new StreamWriter(fs); + sw_.Write("BASEDIR=" + Directory.GetCurrentDirectory() + "\\input"); + sw_.WriteLine(); + sw_.Close(); + fs.Close(); + + try + { + // inputとoutputフォルダを空にする ToDo: 過去に処理したデータがあれば再利用 + var _target = new DirectoryInfo("input"); + foreach (FileInfo file in _target.GetFiles()) + file.Delete(); + foreach (DirectoryInfo dir in _target.GetDirectories()) + dir.Delete(true); + _target = new DirectoryInfo("output"); + foreach (FileInfo file in _target.GetFiles()) + file.Delete(); + foreach (DirectoryInfo dir in _target.GetDirectories()) + dir.Delete(true); + + // 選択されたTIASdirの全日付dir + var glob_tias_dirs = Directory.GetDirectories(path_procBase); + for (int a = 0; a < glob_tias_dirs.Length; a++) + { + // 全日付dirに入っている全dir + var glob_date_dirs = Directory.GetDirectories(glob_tias_dirs[a]); + + // 日付dirをコピー + var path_data_dst = @"input\" + Path.GetFileName(glob_tias_dirs[a]); + if (!Directory.Exists(path_data_dst)) + { + Directory.CreateDirectory(path_data_dst); + } + else + { + Console.WriteLine(path_data_dst + "は既に存在します"); + } + + // 画像dirと校正dir + var path_calib_dirs = glob_date_dirs.Where(n => n.Contains("校正")).ToList(); + var path_notCalib_dirs = glob_date_dirs.Where(n => !n.Contains("校正")).ToList(); + var path_img_dirs = path_notCalib_dirs.Where(n => File.Exists(n + @"\Shot0001.bmp")).ToList(); + + // yyyy-MM-dd + var st_time_tiasDir = Path.GetFileName(glob_tias_dirs[a]).ToString(); + + // 色抽出(TongueAnalysisbin2.3)のため,Proclistを記述 + sw_ = new StreamWriter(path_procList, true); + sw_.WriteLine(st_time_tiasDir); + sw_.Close(); + + // 画像dirに対して処理 + for (int b = 0; b < path_img_dirs.Count(); b++) + { + Console.WriteLine("img dir:"); + Console.WriteLine(path_img_dirs[b]); + + // もし舌裏撮影なら飛ばす + if (File.Exists(path_img_dirs[b] + @"\NShot0001.bmp")) + continue; + // もし舌画像がなければ飛ばす + if (!File.Exists(path_img_dirs[b] + @"\Shot0001.bmp")) + continue; + + // HH_mm_ss-患者ID-来院回数 + var st_time_imgDir = Path.GetFileName(path_img_dirs[b]).ToString().Substring(0, 8); + Console.WriteLine(st_time_imgDir); + + // 日付時刻文字列をDatetimeフォーマットへ変換 + string strTime = st_time_tiasDir + "-" + st_time_imgDir; + string format = "yyyy-MM-dd-HH_mm_ss"; + DateTime time_img_dir = DateTime.ParseExact(strTime, format, null); + + // 舌画像dirから見て最新calib_dirを探索 + var path_newCalibDir = path_calib_dirs[0]; + foreach (var path_calibDir in path_calib_dirs) + { + string target = st_time_tiasDir + "-" + Path.GetFileName(path_calibDir).ToString().Substring(0, 8); + DateTime time_target = DateTime.ParseExact(target, format, null); + + string newone = st_time_tiasDir + "-" + Path.GetFileName(path_newCalibDir).ToString().Substring(0, 8); + DateTime time_newone = DateTime.ParseExact(newone, format, null); + + // imgDirより前に作成され,かつその中で最新 + if (time_img_dir > time_target) + if (time_newone < time_target) + path_newCalibDir = path_calibDir; + } + Console.WriteLine("入力画像dir"); + Console.WriteLine(path_img_dirs[b]); + Console.WriteLine("入力校正dir"); + Console.WriteLine(path_newCalibDir); + + // それぞれのファイルとフォルダーをコピー + // (条件 処理が二回目以降でinfo.csvが既に存在するなら,info.csvを生成しない) + const string DEFAULT_FILENAME = "Shot0001.bmp"; + var path_imgdir_dst = path_data_dst + "\\" + Path.GetFileName(path_img_dirs[b]); + if (!Directory.Exists(path_imgdir_dst)) + { + Directory.CreateDirectory(path_imgdir_dst); + File.Copy(path_img_dirs[b] + "\\" + DEFAULT_FILENAME, path_imgdir_dst + "\\" + DEFAULT_FILENAME); + } + else + { + Console.WriteLine(path_imgdir_dst + "は存在します"); + } + var path_calibdir_dst = path_data_dst + "\\" + Path.GetFileName(path_newCalibDir); + if (!Directory.Exists(path_calibdir_dst)) + { + Directory.CreateDirectory(path_calibdir_dst); + File.Copy(path_newCalibDir + "\\" + DEFAULT_FILENAME, path_calibdir_dst + "\\" + DEFAULT_FILENAME); + } + else + { + Console.WriteLine(path_imgdir_dst + "は存在します"); + } + } + } + GC.Collect(); + } + catch + { + Console.WriteLine("フォルダの読み込み,校正ファイルの検索における例外"); + MessageBox.Show("選択されたフォルダはTIASデータフォルダではありません\nプログラムを再起動してください。"); + return; + } + + + using (MemoryStream ms = new MemoryStream()) + { + // ディレクトリglobの取得 + var glob_tias_dirs_ = Directory.GetDirectories("input"); + for (int a = 0; a < glob_tias_dirs_.Length; a++) + { + // tiasDirに入っている全dir + var glob_date_dirs = Directory.GetDirectories(glob_tias_dirs_[a]); + + // 画像dirと校正dir + var path_calib_dirs = glob_date_dirs.Where(n => n.Contains("校正")).ToList(); + var path_img_dirs = glob_date_dirs.Where(n => !n.Contains("校正")).ToList(); + + // yyyy-MM-dd + var st_time_tiasDir = Path.GetFileName(glob_tias_dirs_[a]).ToString(); + Console.WriteLine(st_time_tiasDir.ToString()); + + // 画像dir + for (int b = 0; b < path_img_dirs.Count(); b++) + { + Console.WriteLine(path_img_dirs.Count()); + Console.WriteLine(path_img_dirs[b]); + + // HH_mm_ss-患者ID-来院回数 + var st_time_imgDir = Path.GetFileName(path_img_dirs[b]).ToString().Substring(0, 8); + Console.WriteLine(st_time_imgDir); + + // 日付時刻文字列をDatetimeフォーマットへ変換 + string strTime = st_time_tiasDir + "-" + st_time_imgDir; + string format = "yyyy-MM-dd-HH_mm_ss"; + DateTime time_img_dir = DateTime.ParseExact(strTime, format, null); + + // 舌画像dirから見て最新calib_dirを探索 + var path_newCalibDir = path_calib_dirs[0]; + foreach (var path_calibDir in path_calib_dirs) + { + string target = st_time_tiasDir + "-" + Path.GetFileName(path_calibDir).ToString().Substring(0, 8); + DateTime time_target = DateTime.ParseExact(target, format, null); + + string newone = st_time_tiasDir + "-" + Path.GetFileName(path_newCalibDir).ToString().Substring(0, 8); + DateTime time_newone = DateTime.ParseExact(newone, format, null); + + // imgDirより前に作成され,かつその中で最新 + if (time_img_dir > time_target) + if (time_newone < time_target) + path_newCalibDir = path_calibDir; + } + Console.WriteLine("入力画像dir"); + Console.WriteLine(path_img_dirs[b]); + Console.WriteLine("入力校正dir"); + Console.WriteLine(path_newCalibDir); + + // 画像に対する繰り返し(今は最も古い画像(0001.bmp)のみを処理,ゆくゆくは全画像(0001-0200)を処理) + var glob_image_path = Directory.GetFiles(path_img_dirs[b]).Where(n => n.Contains(".bmp")); ; + foreach (var path_img in glob_image_path) + { + // DeepTIAS処理 + manualReset.WaitOne(); + + // 画像と校正ファイルのパス + var path_calib = Directory.GetFiles(path_newCalibDir).Where(n => n.Contains("Shot0001.bmp")).ToList()[0]; + var path_base = Directory.GetCurrentDirectory(); + var name_image = Path.GetFileName(path_img); + var name_dir = Path.GetFileName(st_time_tiasDir); + + // ステータスの表示 + Invoke((MethodInvoker)delegate + { + label_processingFileName.Text = "Processing File: " + name_dir + "\\" + Path.GetFileName(path_img_dirs[b]); + count = a + 1; + label_totalProgress.Text = "Total Progress: " + count + "/" + glob_tias_dirs_.Length; + }); + + // 入力画像読み込み・表示 + mat_input = Cv2.ImRead(path_img, ImreadModes.Color); + + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_input); + Invoke((MethodInvoker)delegate + { + pictureBox_input.Image = bitmap_bitch; + pictureBox_input.Refresh(); + }); + + // Detection + label8.BackColor = Color.White; + label1.BackColor = Color.Red; + mat_drawBox = mat_input.Clone(); + mat_cropped = new Mat(mat_input.Size(), MatType.CV_8UC3, 1); + byte_inputDetection = Bitmap2Byte(bitmap_bitch); + using (var graph = new TFGraph()) + { + var model = File.ReadAllBytes(path_base + "/Detection_Normal.pb"); + graph.Import(model, ""); + + using (var session = new TFSession(graph)) + { + var tensor = ImageUtil.CreateTensorFromImageFile(byte_inputDetection, TFDataType.UInt8); + + var runner = session.GetRunner(); + runner + .AddInput(graph["image_tensor"][0], tensor) + .Fetch("detection_boxes", "detection_scores", "detection_classes", "num_detections"); + + var output = runner.Run(); + var boxes = (float[,,])output[0].GetValue(); + var scores = (float[,])output[1].GetValue(); + var classes = (float[,])output[2].GetValue(); + var detections = (float[])output[3].GetValue(); + check_detection = 0; + max_score = 0; + for (int i = 0; i < scores.Length; i++) + { + if ((scores[0, i] > 0.5) && (scores[0, i] > max_score)) + { + max_score = scores[0, i]; + float y_min = boxes[0, i, 0] * (float)bitmap_bitch.Height; + float x_min = boxes[0, i, 1] * (float)bitmap_bitch.Width; + float y_max = boxes[0, i, 2] * (float)bitmap_bitch.Height; + float x_max = boxes[0, i, 3] * (float)bitmap_bitch.Width; + P1.X = (int)x_min; + P1.Y = (int)y_min; + P2.X = (int)x_max; + P2.Y = (int)y_max; + Cv2.Rectangle(mat_drawBox, P1, P2, new Scalar(0, 255, 0), 5); + rectangle.X = (int)x_min; + rectangle.Y = (int)y_min; + rectangle.Width = (int)(x_max - x_min); + rectangle.Height = (int)(y_max - y_min); + + check_detection = 1; + } + } + } + } + // Detection結果表示・保存 + mat_drawBox.SaveImage(path_base + "\\detection" + "\\" + name_dir + ".bmp"); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_drawBox); + Invoke((MethodInvoker)delegate + { + pictureBox_detection.Image = bitmap_bitch; + pictureBox_detection.Refresh(); + }); + label1.BackColor = Color.White; + + // 舌検出領域でcrop + label2.BackColor = Color.Red; + for (int i = P1.Y; i < P2.Y; i++) + { + for (int j = P1.X; j < P2.X; j++) + { + Vec3b pix = mat_input.At(i, j); + mat_cropped.Set(i, j, pix); + } + } + mat_cropped.SaveImage(path_base + "\\cropped" + "\\" + name_dir + ".bmp"); + + // 舌検出領域でresize + OpenCvSharp.Size size_roi = new OpenCvSharp.Size(); + size_roi.Height = rectangle.Height; + size_roi.Width = rectangle.Width; + roi = new Rect(P1, size_roi); + + // 20210201 + //var mat_input2 = mat_input.Clone(); + //mat_input2 = mat_input2 * 1.5; + + mat_roisize = mat_input.Clone(roi); + Cv2.Resize(mat_roisize, mat_roi, mat_roi256.Size()); + mat_roi.SaveImage(path_base + "\\cropresized" + "\\" + name_dir + ".bmp"); + + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_roi); + Invoke((MethodInvoker)delegate + { + pictureBox_cropResized.Image = bitmap_bitch; + pictureBox_cropResized.Refresh(); + }); + label2.BackColor = Color.White; + + // Segmenation + label3.BackColor = Color.Red; + + byte_inputSegmentation = Bitmap2Byte(bitmap_bitch); + Thread.Sleep(1000); + modelFile = DownloadDefaultModel(path_base); + ii = 0; + using (var graph = new TFGraph()) + { + var model = File.ReadAllBytes(modelFile); + graph.Import(model, ""); + + using (var session = new TFSession(graph)) + { + var tensor = ImageUtil2.CreateTensorFromImageFile(byte_inputSegmentation); + var runner = session.GetRunner(); + runner + //.AddInput(graph["generator/input_image"][0], tensor) + //.Fetch(graph["generator/prediction"][0]); + + .AddInput(graph["input_image"][0], tensor) + .Fetch(graph["generator1/decoder_1/Tanh"][0]); + + var output = runner.Run(); + float[,,,] resultfloat = (float[,,,])output[0].GetValue(jagged: false); + + for (int p = 0; p < 256; p++) + { + for (int q = 0; q < 256; q++) + { + float check = resultfloat[0, p, q, 0]; + if (check < 0) + { + mask[ii] = 0; + } + else + { + mask[ii] = 255; + } + ii++; + } + } + } + } + GC.Collect(); + Thread.Sleep(1000); + + // segmentation結果表示・保存 + bitmap_bitch = ToGrayBitmap(mask, 256, 256); + mat_output = OpenCvSharp.Extensions.BitmapConverter.ToMat(bitmap_bitch); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_output); + Invoke((MethodInvoker)delegate + { + pictureBox_output.Image = bitmap_bitch; + pictureBox_output.Refresh(); + }); + label3.BackColor = Color.White; + label4.BackColor = Color.Red; + mat_output.SaveImage(path_base + "\\output256" + "\\" + name_dir + ".bmp"); + + // 後処理(領域拡張法)でノイズ除去 iteration 2 + try + { + RemoveSmallRegion(path_base + "\\output256" + "\\" + name_dir + ".bmp", path_base + "\\output_changed1" + "\\" + name_dir + ".bmp", 500, 1, 1); + RemoveSmallRegion(path_base + "\\output_changed1" + "\\" + name_dir + ".bmp", path_base + "\\output_changed2" + "\\" + name_dir + ".bmp", 500, 0, 0); + } + catch + { + MessageBox.Show("Error: Unable to reprocess! Please check is there [RemoveSmallRegionDLL.dll] file in floder?", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + break; + } + mat_outputSRG = new Mat(path_base + "\\output_changed2" + "\\" + name_dir + ".bmp", ImreadModes.GrayScale); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_outputSRG); + Invoke((MethodInvoker)delegate + { + pictureBox_outputSRG.Image = bitmap_bitch; + pictureBox_outputSRG.Refresh(); + }); + label4.BackColor = Color.White; + label5.BackColor = Color.Red; + + // バウンディングボックスのサイズに戻す + Cv2.Resize(mat_outputSRG, mat_outputChanged, mat_roisize.Size()); + mat_outputChanged.SaveImage(path_base + "\\output_resized" + "\\" + name_dir + ".bmp"); + + // 入力と同じサイズでマスクを作成(なんかたまに不安定.resizeで微妙にサイズ変わってrange overかも) + // 気休め + Thread.Sleep(100); + GC.Collect(); + + mat_mask = new Mat(mat_input.Size(), MatType.CV_8UC1, 0); + var y_mask = 0; + for (int y = P1.Y; y < P2.Y; y++) + { + if (y_mask >= mat_outputChanged.Height) + break; + + var x_mask = 0; + for (int x = P1.X; x < P2.X; x++) + { + if (x_mask >= mat_outputChanged.Width) + break; + + int pix = mat_outputChanged.At(y_mask, x_mask); + mat_mask.Set(y, x, pix); + x_mask++; + } + y_mask++; + } + mmp = 0; + Cv2.Resize(mat_mask, mat_mask, mat_input.Size()); + mat_mask.SaveImage(path_base + "\\mask" + "\\" + name_dir + ".bmp"); + + // ノイズ処理 + try + { + RemoveSmallRegion(path_base + "\\mask" + "\\" + name_dir + ".bmp", path_base + "\\mask_changed1" + "\\" + name_dir + ".bmp", 500, 1, 1); + RemoveSmallRegion(path_base + "\\mask_changed1" + "\\" + name_dir + ".bmp", path_base + "\\mask_changed2" + "\\" + name_dir + ".bmp", 500, 0, 0); + } + catch + { + MessageBox.Show("Error: Unable to reprocess! Please check is there [RemoveSmallRegionDLL.dll] file in floder?", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + break; + } + mat_maskSRG = new Mat(path_base + "\\mask_changed2" + "\\" + name_dir + ".bmp", ImreadModes.GrayScale); + Cv2.Threshold(mat_maskSRG, mat_maskSRG, 128, 255, ThresholdTypes.Binary); + + // 2値マスクの最終結果 + mat_maskSRG.SaveImage(path_base + "\\mask_final" + "\\" + name_dir + ".bmp"); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_maskSRG); + Invoke((MethodInvoker)delegate + { + pictureBox_maskSRG.Image = bitmap_bitch; + pictureBox_maskSRG.Refresh(); + }); + label5.BackColor = Color.White; + + // 元画像とマスクを合わせて,舌領域を抜き出す  + // todo: opencv等によるマスク処理と領域計算へ + label6.BackColor = Color.Red; + mat_extraction = mat_input.Clone(); + areaCount = 0; + for (int i = 0; i < mat_input.Height; i++) + { + for (int j = 0; j < mat_input.Width; j++) + { + Vec3b pix = mat_extraction.At(i, j); + if (mat_maskSRG.At(i, j) == 0) + { + pix[0] = (byte)(255); + pix[1] = (byte)(255); + pix[2] = (byte)(255); + mat_extraction.Set(i, j, pix); + } + else + { + pix[0] = (byte)(mat_extraction.At(i, j).Item0); + pix[1] = (byte)(mat_extraction.At(i, j).Item1); + pix[2] = (byte)(mat_extraction.At(i, j).Item2); + mat_extraction.Set(i, j, pix); + areaCount++; + } + } + } + mat_extraction.SaveImage(path_base + "\\extraction" + "\\" + name_dir + ".bmp"); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_extraction); + Invoke((MethodInvoker)delegate + { + pictureBox_extraction.Image = bitmap_bitch; + pictureBox_extraction.Refresh(); + }); + label6.BackColor = Color.White; + + /// 5点クリックによる色抽出処理 + var path_colorMatrixXYZ = "xyz.txt"; + + // 色抽出処理のクラス + ColorExtractor ce = new ColorExtractor(); + + // マスクされた舌領域画像の作成 + Mat mat_finalMask = mat_maskSRG.Clone(); + Cv2.ImWrite(path_base + "\\final_mask" + "\\" + name_dir + ".bmp", mat_finalMask); + + Mat mat_maskedImg = new Mat(); + mat_input.CopyTo(mat_maskedImg, mat_finalMask); + + // 5点クリック法(2010石川) + List list_5points_3 = ce.Get5points(mat_finalMask, ColorExtractor.FivePointMethod.Method3); + + // 5点座標を入力画像,校正画像と同じディレクトリに保存 + var path_infocsv = path_img_dirs[b] + "\\Info.csv"; + FileStream CSV_file = File.Open(path_infocsv, FileMode.OpenOrCreate, FileAccess.Write); + CSV_file.Seek(0, SeekOrigin.Begin); + CSV_file.SetLength(0); + CSV_file.Close(); + + Console.WriteLine(path_infocsv); + + StreamWriter CSV_data = new StreamWriter(path_infocsv); + CSV_data.WriteLine(Path.GetFileName(path_img)); + foreach (var item in list_5points_3) + { + string str = item.X + "," + item.Y; + CSV_data.WriteLine(str); + } + CSV_data.Close(); + + // 8領域の取得 + List list_8area_3 = ce.Get8area(list_5points_3); + + // 領域の表示 + var mat_areaDicision = ce.ShowResult(mat_input.Clone(), list_5points_3, list_8area_3); + + mat_areaDicision.SaveImage(path_base + "\\autoAreaDecision" + "\\" + name_dir + ".bmp"); + bitmap_bitch = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_areaDicision); + Invoke((MethodInvoker)delegate + { + pictureBox_last.Image = bitmap_bitch; + pictureBox_last.Refresh(); + }); + label7.BackColor = Color.White; + + //csvにboundingbox情報を保存 + sw = new StreamWriter(this.path_infocsv, true, System.Text.Encoding.GetEncoding("shift_jis")); + sw.WriteLine( + path_img.Substring(6) + "," + + P1.X.ToString() + "," + P1.Y.ToString() + "," + + P2.X.ToString() + "," + P2.Y.ToString() + "," + + Math.Abs(P1.X - P2.X).ToString() + "," + Math.Abs(P1.Y - P2.Y).ToString() + "," + + areaCount.ToString() + ); + sw.Close(); + + // 処理log + label8.BackColor = Color.Red; + time = DateTime.Now.ToLocalTime().ToString(); + File.AppendAllText("Log.txt ", time + " " + "\\" + name_dir + ".bmp" + " Done!\n"); + Thread.Sleep(100); + + // 破棄 + GC.Collect(); + } + } + } + + // 色抽出のためproclist + sw_ = new StreamWriter(path_procList, true); + sw_.WriteLine("END"); + sw_.Close(); + + // 最後の日付のデータを監視し,更新され次第,tongueanalysisを閉じる + var fs2 = File.Open(path_procList, FileMode.Open, FileAccess.Read); + var sr = new StreamReader(fs2); + fs2.Seek(0, SeekOrigin.Begin); + string line = ""; + string line_l = ""; + var path_watchedFile = ""; + while ((line = sr.ReadLine()) != null) + { + if (line != "END") + { + line_l = line; + continue; + } + Console.WriteLine("Last folder:"); + var path_watchedFolder = Directory.GetCurrentDirectory() + "\\input\\" + line_l; + var path_img_dirs = Directory.GetDirectories(path_watchedFolder).Where(n => !n.Contains("校正")).ToList(); + path_watchedFolder = path_img_dirs[path_img_dirs.Count() - 1]; + path_watchedFile = path_watchedFolder + "\\" + "analysis.csv"; + Console.WriteLine("Watching:" + path_watchedFile); + } + sr.Close(); + fs2.Close(); + + // proclist最後のファイルについて,analysis.csvの更新を非同期監視 + System.IO.FileSystemWatcher watcher = new System.IO.FileSystemWatcher(); + watcher.Path = Path.GetDirectoryName(path_watchedFile); + watcher.NotifyFilter = (System.IO.NotifyFilters.LastAccess + | System.IO.NotifyFilters.LastWrite + | System.IO.NotifyFilters.FileName + | System.IO.NotifyFilters.DirectoryName); + watcher.Filter = "analysis.csv"; + //UIのスレッドにマーシャリングする + //コンソールアプリケーションでの使用では必要ない + watcher.SynchronizingObject = this; + //イベントハンドラの追加 + watcher.Changed += new System.IO.FileSystemEventHandler(watcher_Changed); + watcher.Created += new System.IO.FileSystemEventHandler(watcher_Changed); + watcher.Deleted += new System.IO.FileSystemEventHandler(watcher_Changed); + watcher.Renamed += new System.IO.RenamedEventHandler(watcher_Renamed); + + //監視を開始する + watcher.EnableRaisingEvents = true; + Console.WriteLine("監視を開始しました。"); + + // TongueAnalysis_bin2.3を動かす + var proc = Process.Start(@"TongueAnalysis_bin2.3\TongueAnalysis.exe"); + + while(true) + { + if (IsLastFileChanged) + { + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + proc.Kill(); + break; + } + else + { + proc.WaitForExit(100); + } + } + + // 生成されたファイルをわかりやすい場所(exeの親dir)にコピー + var path_finaloutputDir = @"..\Output"; + if (!Directory.Exists(path_finaloutputDir)) + Directory.CreateDirectory(path_finaloutputDir); + else + { + Console.WriteLine(path_finaloutputDir + "は既に存在します"); + Directory.Delete(path_finaloutputDir, true); + Directory.CreateDirectory(path_finaloutputDir); + } + var _target = new DirectoryInfo("output"); + foreach (FileInfo file in _target.GetFiles()) + { + if (file.Name != "analysis.csv") + file.CopyTo(path_finaloutputDir + @"\" + file.Name); + } + + // 形状に関する出力csvと色に関するcsvを合わせる + var path_infocsv_ = path_infocsv; + var path_colorcsv_ = @"output\analysis.csv"; + var path_result_ = path_finaloutputDir + @"\AnalysisResult.csv"; + var isFirst = true; + using (var sr_info = new StreamReader(path_infocsv_)) + using (var sr_color = new StreamReader(path_colorcsv_)) + using (var sw_result = new StreamWriter(path_result_)) + { + while ((sr_info.EndOfStream == false) && (sr_color.EndOfStream == false)) + { + string line_info = sr_info.ReadLine(); + line_info = line_info.Substring(line_info.IndexOf(",") + 1); + Console.WriteLine(line_info); + string line_color = sr_color.ReadLine(); + Console.WriteLine(line_color); + + + string line_result = line_color + line_info; + + if (isFirst) + { + var header = + "date,time-case,image,L1,a1,b1,L2,a2,b2,L3,a3,b3,L4,a4,b4," + + "top left X,top left Y,bottom right X,bottom right Y,Width,Height,Area"; + sw_result.WriteLine(header); + + + isFirst = false; + continue; + } + + sw_result.WriteLine(line_result); + } + sw_result.Close(); + sr_info.Close(); + sr_color.Close(); + } + + + + // 終了処理 + GC.Collect(); + MessageBox.Show("Finished!"); + + Invoke((MethodInvoker)delegate + { + button_start.Enabled = true; + button_pause.Enabled = false; + label_processingFileName.Text = "Processing File: None"; + }); + } + } + + //イベントハンドラ + private void watcher_Changed(System.Object source, + System.IO.FileSystemEventArgs e) + { + switch (e.ChangeType) + { + case System.IO.WatcherChangeTypes.Changed: + Console.WriteLine( + "ファイル 「" + e.FullPath + "」が変更されました。"); + IsLastFileChanged = true; + break; + case System.IO.WatcherChangeTypes.Created: + Console.WriteLine( + "ファイル 「" + e.FullPath + "」が作成されました。"); + break; + case System.IO.WatcherChangeTypes.Deleted: + Console.WriteLine( + "ファイル 「" + e.FullPath + "」が削除されました。"); + break; + } + } + + private void watcher_Renamed(System.Object source, + System.IO.RenamedEventArgs e) + { + Console.WriteLine( + "ファイル 「" + e.FullPath + "」の名前が変更されました。"); + } + + private void Button_pause_Click(object sender, EventArgs e) + { + if (button_pause.Text == "Pause") + { + manualReset.Reset(); + button_pause.Text = "Continue"; + } + else + { + manualReset.Set(); + button_pause.Text = "Pause"; + } + } + + private void Form1_FormClosing(object sender, FormClosingEventArgs e) + { + } + + private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + + } + } +} \ No newline at end of file diff --git a/Tongue extraction/Form1.Designer.cs b/Tongue extraction/Form1.Designer.cs new file mode 100644 index 0000000..124635a --- /dev/null +++ b/Tongue extraction/Form1.Designer.cs @@ -0,0 +1,500 @@ +namespace Tongue_extraction +{ + partial class Form1 + { + /// + /// 必要なデザイナー変数です。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 使用中のリソースをすべてクリーンアップします。 + /// + /// マネージ リソースを破棄する場合は true を指定し、その他の場合は false を指定します。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows フォーム デザイナーで生成されたコード + + /// + /// デザイナー サポートに必要なメソッドです。このメソッドの内容を + /// コード エディターで変更しないでください。 + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); + this.button_start = new System.Windows.Forms.Button(); + this.pictureBox_regions = new System.Windows.Forms.PictureBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.panel4 = new System.Windows.Forms.Panel(); + this.panel3 = new System.Windows.Forms.Panel(); + this.label9 = new System.Windows.Forms.Label(); + this.comboBox_mode = new System.Windows.Forms.ComboBox(); + this.label_state = new System.Windows.Forms.Label(); + this.panel1 = new System.Windows.Forms.Panel(); + this.pictureBox_calibration = new System.Windows.Forms.PictureBox(); + this.pictureBox_input = new System.Windows.Forms.PictureBox(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.label8 = new System.Windows.Forms.Label(); + this.pictureBox_detection = new System.Windows.Forms.PictureBox(); + this.pictureBox_segmentation = new System.Windows.Forms.PictureBox(); + this.pictureBox_mask = new System.Windows.Forms.PictureBox(); + this.pictureBox_extracted = new System.Windows.Forms.PictureBox(); + this.pictureBox_last = new System.Windows.Forms.PictureBox(); + this.label_processingFileName = new System.Windows.Forms.Label(); + this.label_totalProgress = new System.Windows.Forms.Label(); + this.panel2 = new System.Windows.Forms.Panel(); + this.button_pause = new System.Windows.Forms.Button(); + this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); + this.folderBrowserDialog1 = new System.Windows.Forms.FolderBrowserDialog(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_regions)).BeginInit(); + this.tableLayoutPanel1.SuspendLayout(); + this.panel3.SuspendLayout(); + this.panel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_calibration)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_input)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_detection)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_segmentation)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_mask)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_extracted)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_last)).BeginInit(); + this.panel2.SuspendLayout(); + this.SuspendLayout(); + // + // button_start + // + this.button_start.Dock = System.Windows.Forms.DockStyle.Fill; + this.button_start.Location = new System.Drawing.Point(0, 0); + this.button_start.Name = "button_start"; + this.button_start.Size = new System.Drawing.Size(250, 49); + this.button_start.TabIndex = 0; + this.button_start.Text = "Start"; + this.button_start.UseVisualStyleBackColor = true; + this.button_start.Click += new System.EventHandler(this.Button_start_Click); + // + // pictureBox_regions + // + this.pictureBox_regions.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox_regions.Location = new System.Drawing.Point(511, 241); + this.pictureBox_regions.Margin = new System.Windows.Forms.Padding(1, 2, 1, 2); + this.pictureBox_regions.Name = "pictureBox_regions"; + this.pictureBox_regions.Size = new System.Drawing.Size(250, 209); + this.pictureBox_regions.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox_regions.TabIndex = 4; + this.pictureBox_regions.TabStop = false; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.Outset; + this.tableLayoutPanel1.ColumnCount = 4; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F)); + this.tableLayoutPanel1.Controls.Add(this.panel4, 3, 4); + this.tableLayoutPanel1.Controls.Add(this.panel3, 2, 4); + this.tableLayoutPanel1.Controls.Add(this.label_state, 2, 5); + this.tableLayoutPanel1.Controls.Add(this.panel1, 0, 4); + this.tableLayoutPanel1.Controls.Add(this.pictureBox_calibration, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.pictureBox_input, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.label2, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.label3, 2, 1); + this.tableLayoutPanel1.Controls.Add(this.label4, 3, 1); + this.tableLayoutPanel1.Controls.Add(this.label5, 0, 3); + this.tableLayoutPanel1.Controls.Add(this.label6, 1, 3); + this.tableLayoutPanel1.Controls.Add(this.label7, 2, 3); + this.tableLayoutPanel1.Controls.Add(this.label8, 3, 3); + this.tableLayoutPanel1.Controls.Add(this.pictureBox_detection, 2, 0); + this.tableLayoutPanel1.Controls.Add(this.pictureBox_regions, 2, 2); + this.tableLayoutPanel1.Controls.Add(this.pictureBox_segmentation, 3, 0); + this.tableLayoutPanel1.Controls.Add(this.pictureBox_mask, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.pictureBox_extracted, 1, 2); + this.tableLayoutPanel1.Controls.Add(this.pictureBox_last, 3, 2); + this.tableLayoutPanel1.Controls.Add(this.label_processingFileName, 0, 5); + this.tableLayoutPanel1.Controls.Add(this.label_totalProgress, 1, 5); + this.tableLayoutPanel1.Controls.Add(this.panel2, 1, 4); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(1, 2, 1, 2); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 6; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 44.44445F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 44.44444F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11111F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(1018, 554); + this.tableLayoutPanel1.TabIndex = 5; + // + // panel4 + // + this.panel4.Dock = System.Windows.Forms.DockStyle.Left; + this.panel4.Location = new System.Drawing.Point(765, 478); + this.panel4.Margin = new System.Windows.Forms.Padding(1, 2, 1, 2); + this.panel4.Name = "panel4"; + this.panel4.Size = new System.Drawing.Size(246, 49); + this.panel4.TabIndex = 24; + // + // panel3 + // + this.panel3.Controls.Add(this.label9); + this.panel3.Controls.Add(this.comboBox_mode); + this.panel3.Dock = System.Windows.Forms.DockStyle.Left; + this.panel3.Location = new System.Drawing.Point(511, 478); + this.panel3.Margin = new System.Windows.Forms.Padding(1, 2, 1, 2); + this.panel3.Name = "panel3"; + this.panel3.Size = new System.Drawing.Size(246, 49); + this.panel3.TabIndex = 23; + // + // label9 + // + this.label9.AutoSize = true; + this.label9.Dock = System.Windows.Forms.DockStyle.Fill; + this.label9.Font = new System.Drawing.Font("Arial Narrow", 15F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label9.Location = new System.Drawing.Point(0, 0); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(47, 24); + this.label9.TabIndex = 1; + this.label9.Text = "Input"; + // + // comboBox_mode + // + this.comboBox_mode.Dock = System.Windows.Forms.DockStyle.Bottom; + this.comboBox_mode.FormattingEnabled = true; + this.comboBox_mode.Items.AddRange(new object[] { + "Image(画像のみ)", + "Image&Calib(画像とカラーチャート)"}); + this.comboBox_mode.Location = new System.Drawing.Point(0, 29); + this.comboBox_mode.Name = "comboBox_mode"; + this.comboBox_mode.Size = new System.Drawing.Size(246, 20); + this.comboBox_mode.TabIndex = 0; + // + // label_state + // + this.label_state.AutoSize = true; + this.label_state.BackColor = System.Drawing.Color.White; + this.label_state.Dock = System.Windows.Forms.DockStyle.Fill; + this.label_state.Location = new System.Drawing.Point(767, 531); + this.label_state.Name = "label_state"; + this.label_state.Size = new System.Drawing.Size(246, 21); + this.label_state.TabIndex = 22; + this.label_state.Text = "Push \"Start button\""; + this.label_state.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // panel1 + // + this.panel1.Controls.Add(this.button_start); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(3, 478); + this.panel1.Margin = new System.Windows.Forms.Padding(1, 2, 1, 2); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(250, 49); + this.panel1.TabIndex = 6; + // + // pictureBox_calibration + // + this.pictureBox_calibration.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox_calibration.Location = new System.Drawing.Point(257, 4); + this.pictureBox_calibration.Margin = new System.Windows.Forms.Padding(1, 2, 1, 2); + this.pictureBox_calibration.Name = "pictureBox_calibration"; + this.pictureBox_calibration.Size = new System.Drawing.Size(250, 209); + this.pictureBox_calibration.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox_calibration.TabIndex = 2; + this.pictureBox_calibration.TabStop = false; + // + // pictureBox_input + // + this.pictureBox_input.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox_input.Location = new System.Drawing.Point(3, 4); + this.pictureBox_input.Margin = new System.Windows.Forms.Padding(1, 2, 1, 2); + this.pictureBox_input.Name = "pictureBox_input"; + this.pictureBox_input.Size = new System.Drawing.Size(250, 209); + this.pictureBox_input.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox_input.TabIndex = 1; + this.pictureBox_input.TabStop = false; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.BackColor = System.Drawing.Color.White; + this.label1.Dock = System.Windows.Forms.DockStyle.Fill; + this.label1.Location = new System.Drawing.Point(5, 217); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(246, 20); + this.label1.TabIndex = 7; + this.label1.Text = "Input"; + this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.BackColor = System.Drawing.Color.White; + this.label2.Dock = System.Windows.Forms.DockStyle.Fill; + this.label2.Location = new System.Drawing.Point(259, 217); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(246, 20); + this.label2.TabIndex = 8; + this.label2.Text = "Calibration"; + this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.BackColor = System.Drawing.Color.White; + this.label3.Dock = System.Windows.Forms.DockStyle.Fill; + this.label3.Location = new System.Drawing.Point(513, 217); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(246, 20); + this.label3.TabIndex = 9; + this.label3.Text = "Detection"; + this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.BackColor = System.Drawing.Color.White; + this.label4.Dock = System.Windows.Forms.DockStyle.Fill; + this.label4.Location = new System.Drawing.Point(767, 217); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(246, 20); + this.label4.TabIndex = 10; + this.label4.Text = "Segmentation"; + this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.BackColor = System.Drawing.Color.White; + this.label5.Dock = System.Windows.Forms.DockStyle.Fill; + this.label5.Location = new System.Drawing.Point(5, 454); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(246, 20); + this.label5.TabIndex = 11; + this.label5.Text = "Mask"; + this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.BackColor = System.Drawing.Color.White; + this.label6.Dock = System.Windows.Forms.DockStyle.Fill; + this.label6.Location = new System.Drawing.Point(259, 454); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(246, 20); + this.label6.TabIndex = 12; + this.label6.Text = "Extraction"; + this.label6.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.BackColor = System.Drawing.Color.White; + this.label7.Dock = System.Windows.Forms.DockStyle.Fill; + this.label7.Location = new System.Drawing.Point(513, 454); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(246, 20); + this.label7.TabIndex = 13; + this.label7.Text = "view"; + this.label7.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label8 + // + this.label8.AutoSize = true; + this.label8.BackColor = System.Drawing.Color.White; + this.label8.Dock = System.Windows.Forms.DockStyle.Fill; + this.label8.Location = new System.Drawing.Point(767, 454); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(246, 20); + this.label8.TabIndex = 14; + this.label8.Text = "view2"; + this.label8.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // pictureBox_detection + // + this.pictureBox_detection.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox_detection.Location = new System.Drawing.Point(511, 4); + this.pictureBox_detection.Margin = new System.Windows.Forms.Padding(1, 2, 1, 2); + this.pictureBox_detection.Name = "pictureBox_detection"; + this.pictureBox_detection.Size = new System.Drawing.Size(250, 209); + this.pictureBox_detection.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox_detection.TabIndex = 3; + this.pictureBox_detection.TabStop = false; + // + // pictureBox_segmentation + // + this.pictureBox_segmentation.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox_segmentation.Location = new System.Drawing.Point(767, 5); + this.pictureBox_segmentation.Name = "pictureBox_segmentation"; + this.pictureBox_segmentation.Size = new System.Drawing.Size(246, 207); + this.pictureBox_segmentation.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox_segmentation.TabIndex = 15; + this.pictureBox_segmentation.TabStop = false; + // + // pictureBox_mask + // + this.pictureBox_mask.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox_mask.Location = new System.Drawing.Point(5, 242); + this.pictureBox_mask.Name = "pictureBox_mask"; + this.pictureBox_mask.Size = new System.Drawing.Size(246, 207); + this.pictureBox_mask.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox_mask.TabIndex = 16; + this.pictureBox_mask.TabStop = false; + // + // pictureBox_extracted + // + this.pictureBox_extracted.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox_extracted.Location = new System.Drawing.Point(259, 242); + this.pictureBox_extracted.Name = "pictureBox_extracted"; + this.pictureBox_extracted.Size = new System.Drawing.Size(246, 207); + this.pictureBox_extracted.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox_extracted.TabIndex = 17; + this.pictureBox_extracted.TabStop = false; + // + // pictureBox_last + // + this.pictureBox_last.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox_last.Location = new System.Drawing.Point(767, 242); + this.pictureBox_last.Name = "pictureBox_last"; + this.pictureBox_last.Size = new System.Drawing.Size(246, 207); + this.pictureBox_last.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox_last.TabIndex = 18; + this.pictureBox_last.TabStop = false; + // + // label_processingFileName + // + this.label_processingFileName.AutoSize = true; + this.label_processingFileName.BackColor = System.Drawing.Color.White; + this.tableLayoutPanel1.SetColumnSpan(this.label_processingFileName, 2); + this.label_processingFileName.Dock = System.Windows.Forms.DockStyle.Fill; + this.label_processingFileName.Location = new System.Drawing.Point(5, 531); + this.label_processingFileName.Name = "label_processingFileName"; + this.label_processingFileName.Size = new System.Drawing.Size(500, 21); + this.label_processingFileName.TabIndex = 19; + this.label_processingFileName.Text = "Processing File: None"; + this.label_processingFileName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label_totalProgress + // + this.label_totalProgress.AutoSize = true; + this.label_totalProgress.BackColor = System.Drawing.Color.White; + this.label_totalProgress.Dock = System.Windows.Forms.DockStyle.Fill; + this.label_totalProgress.Location = new System.Drawing.Point(513, 531); + this.label_totalProgress.Name = "label_totalProgress"; + this.label_totalProgress.Size = new System.Drawing.Size(246, 21); + this.label_totalProgress.TabIndex = 20; + this.label_totalProgress.Text = "Total Progress: 0/0"; + this.label_totalProgress.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // panel2 + // + this.panel2.Controls.Add(this.button_pause); + this.panel2.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel2.Location = new System.Drawing.Point(259, 479); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(246, 47); + this.panel2.TabIndex = 21; + // + // button_pause + // + this.button_pause.Dock = System.Windows.Forms.DockStyle.Fill; + this.button_pause.Enabled = false; + this.button_pause.Location = new System.Drawing.Point(0, 0); + this.button_pause.Name = "button_pause"; + this.button_pause.Size = new System.Drawing.Size(246, 47); + this.button_pause.TabIndex = 0; + this.button_pause.Text = "Pause"; + this.button_pause.UseVisualStyleBackColor = true; + this.button_pause.Click += new System.EventHandler(this.Button_pause_Click); + // + // backgroundWorker1 + // + this.backgroundWorker1.WorkerSupportsCancellation = true; + // + // folderBrowserDialog1 + // + this.folderBrowserDialog1.Description = "TIASデータを選択"; + this.folderBrowserDialog1.RootFolder = System.Environment.SpecialFolder.MyComputer; + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1018, 554); + this.Controls.Add(this.tableLayoutPanel1); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Margin = new System.Windows.Forms.Padding(1, 2, 1, 2); + this.Name = "Form1"; + this.Text = "DeepTIAS2.7"; + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_regions)).EndInit(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.panel3.ResumeLayout(false); + this.panel3.PerformLayout(); + this.panel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_calibration)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_input)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_detection)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_segmentation)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_mask)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_extracted)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox_last)).EndInit(); + this.panel2.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button button_start; + private System.Windows.Forms.PictureBox pictureBox_regions; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Panel panel1; + private System.ComponentModel.BackgroundWorker backgroundWorker1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.PictureBox pictureBox_segmentation; + private System.Windows.Forms.PictureBox pictureBox_mask; + private System.Windows.Forms.PictureBox pictureBox_extracted; + private System.Windows.Forms.PictureBox pictureBox_last; + private System.Windows.Forms.Label label_processingFileName; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.Button button_pause; + private System.Windows.Forms.Label label_totalProgress; + private System.Windows.Forms.Label label_state; + private System.Windows.Forms.PictureBox pictureBox_calibration; + private System.Windows.Forms.PictureBox pictureBox_input; + private System.Windows.Forms.PictureBox pictureBox_detection; + private System.Windows.Forms.Panel panel3; + private System.Windows.Forms.ComboBox comboBox_mode; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.FolderBrowserDialog folderBrowserDialog1; + private System.Windows.Forms.Panel panel4; + } +} + diff --git a/Tongue extraction/Form1.cs b/Tongue extraction/Form1.cs new file mode 100644 index 0000000..48eee6f --- /dev/null +++ b/Tongue extraction/Form1.cs @@ -0,0 +1,1280 @@ +using System; +using System.Windows.Forms; +using System.IO; +using System.Drawing; +using System.Drawing.Imaging; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Threading; +using System.Diagnostics; +using System.Linq; +using System.Collections.Generic; +using TensorFlow; +using OpenCvSharp; +using Microsoft.ML; +using Microsoft.ML.Data; +using Microsoft.ML.OnnxRuntime; +using Microsoft.ML.OnnxRuntime.Tensors; + +namespace Tongue_extraction +{ + public partial class Form1 : Form + { + [DllImport("TongueAnalysisDLL.dll")] + private static extern int AnalysisProc2(string path_img, string path_calib, string path_info, string path_output); + + [DllImport("TongueAnalysisDLL.dll")] + private static extern int AnalysisProc3(string path_img, string path_calib, string path_output); + + //Use the RemoveSmallRegionDLL + [DllImport(@"RemoveSmallRegionDLL.dll", EntryPoint = "RemoveSmallRegion", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.StdCall)] + extern static void RemoveSmallRegion(string name, string name2, int AreaLimit, int CheckMode, int NeihborMode); + + // 削除予定 + public static Bitmap bitmap_bitch; + float max_score = 0; + private ManualResetEvent manualReset = new ManualResetEvent(true); + + string path_detectionModel = @"models/Detection_Normal.pb"; + string path_segmentationModel = @"models/ResUNet.onnx"; + + public Form1() + { + InitializeComponent(); + + comboBox_mode.DropDownStyle = ComboBoxStyle.DropDownList; + comboBox_mode.SelectedIndex = 0; + + } + + private void Button_start_Click(object sender, EventArgs e) + { + button_start.Enabled = false; + button_pause.Enabled = true; + pictureBox_input.Image = null; + pictureBox_input.Refresh(); + pictureBox_calibration.Image = null; + pictureBox_calibration.Refresh(); + pictureBox_detection.Image = null; + pictureBox_detection.Refresh(); + pictureBox_segmentation.Image = null; + pictureBox_segmentation.Refresh(); + pictureBox_mask.Image = null; + pictureBox_mask.Refresh(); + pictureBox_extracted.Image = null; + pictureBox_extracted.Refresh(); + pictureBox_regions.Image = null; + pictureBox_regions.Refresh(); + pictureBox_last.Image = null; + pictureBox_last.Refresh(); + comboBox_mode.Enabled = false; + + + + if (comboBox_mode.SelectedIndex == 0) + { + // 画像のみ入力し,マスク等を得る + this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.BackgroundWorker1_DoWork_Image); + backgroundWorker1.RunWorkerAsync(); + } + else if (comboBox_mode.SelectedIndex == 1) + { + // 画像と校正画像を入力し,Lab色を得る + this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.BackgroundWorker1_DoWork_ImageAndCalib); + backgroundWorker1.RunWorkerAsync(); + } + else + { + MessageBox.Show("Please select the mode on the combobox"); + } + } + + private void BackgroundWorker1_DoWork_Image(object sender, DoWorkEventArgs e) + { + // ステータスの表示 + Invoke((MethodInvoker)delegate + { + label_state.Text = "準備中.."; + }); + + // Path + var path_imgDir = @"..\Input\TongueImage"; + var path_outputDir = @"..\Output/" + DateTime.Now.ToLocalTime().ToString("yyyyMMddhhmmss"); + MyMakeDir(path_outputDir); + FolderCopy("template_outputDir", path_outputDir); + var path_outputCsv = path_outputDir + @"\analysis.csv"; + + var count = 0; + foreach (var path_img in Directory.GetFiles(path_imgDir)) + { + var name_file = Path.GetFileNameWithoutExtension(path_img); + + // ステータスの表示 + Invoke((MethodInvoker)delegate + { + label_processingFileName.Text = "Processing File: " + path_img; + count += 1; + label_totalProgress.Text = "Total Progress: " + count + "/" + Directory.GetFiles(path_imgDir).Length; + }); + + // Init + manualReset.WaitOne(); + Invoke((MethodInvoker)delegate + { + label2.Text = ""; + label8.Text = ""; + label8.BackColor = Color.White; + label1.BackColor = Color.Red; + }); + + // 舌画像表示 + Mat mat_input = Cv2.ImRead(path_img, ImreadModes.Color); + Invoke((MethodInvoker)delegate + { + label1.BackColor = Color.White; + label3.BackColor = Color.Red; + pictureBox_input.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_input); + pictureBox_input.Refresh(); + label_state.Text = "舌検出及び分割"; + }); + + // Detection + var dst_rect = new Rect(); + try + { + dst_rect = TongueDetection(mat_input); + } + catch (Exception) + { + using (var sw = new StreamWriter(path_outputCsv, true)) + { + var swLine = name_file + "," + "Tongue detection error"; + sw.WriteLine(swLine); + } + continue; + } + if ((dst_rect.Width == 0) || (dst_rect.Height == 0)) + { + using (var sw = new StreamWriter(path_outputCsv, true)) + { + var swLine = name_file + "," + "Tongue detection error"; + sw.WriteLine(swLine); + } + continue; + } + + var mat_drawBox = mat_input.Clone(); + Cv2.Rectangle(mat_drawBox, dst_rect, new Scalar(0, 255, 0), 3); + MyMakeDir(path_outputDir + @"\1-detection\1-drawBox\"); + mat_drawBox.SaveImage(path_outputDir + @"\1-detection\1-drawBox\" + name_file + ".png"); + + // Detection領域crop + var mat_cropped = new Mat(mat_input, dst_rect); + mat_cropped.SaveImage(path_outputDir + @"\1-detection\2-cropped\" + name_file + ".png"); + + // Resize + var mat_roisize = mat_input.Clone(dst_rect); + var mat_roi = new Mat(); + Cv2.Resize(mat_roisize, mat_roi, new OpenCvSharp.Size(256, 256)); + mat_roi.SaveImage(path_outputDir + @"\1-detection\3-resized\" + name_file + ".png"); + + // Detection 表示 + Invoke((MethodInvoker)delegate + { + label3.BackColor = Color.White; + label4.BackColor = Color.Red; + pictureBox_detection.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_roi); ; + pictureBox_detection.Refresh(); + }); + + // Segmenation + var mat_output = TongueSegmentation(mat_roi); + Cv2.Threshold(mat_output, mat_output, 128, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary); + Thread.Sleep(100); + GC.Collect(); + mat_output.SaveImage(path_outputDir + @"\2-segmentation\1-modelOutput\" + name_file + ".png"); + + // 2値化 + var mat_binOutput = new Mat(); + Cv2.Threshold(mat_output, mat_binOutput, 0, 255, ThresholdTypes.Otsu); + mat_binOutput.SaveImage(path_outputDir + @"\2-segmentation\2-binOutput\" + name_file + ".png"); + + // Segmenation 表示 + Invoke((MethodInvoker)delegate + { + label3.BackColor = Color.White; + label4.BackColor = Color.Red; + pictureBox_segmentation.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_binOutput); ; + pictureBox_segmentation.Refresh(); + }); + + // Segmentationの例外処理 + var edges = new Mat(); + OpenCvSharp.Point[][] contours; + HierarchyIndex[] hierarchyIndexes; + Cv2.Canny(mat_binOutput, edges, 50, 200); + Cv2.FindContours(edges, out contours, out hierarchyIndexes, RetrievalModes.List, ContourApproximationModes.ApproxSimple); + + // 1 or 2つ以外の輪郭が検出された場合 + var num_obj = contours.Count(); + if ((num_obj == 0) || (num_obj > 2)) + { + Console.WriteLine("Segmentation result has over 1 area : extracted areas = {0}", num_obj); + try + { + RemoveSmallRegion(path_outputDir + @"\2-segmentation\2-binOutput\" + name_file + ".png", path_outputDir + @"\2-segmentation\2-binOutput(refined)\" + name_file + ".png", 500, 1, 1); + RemoveSmallRegion(path_outputDir + @"\2-segmentation\2-binOutput(refined)\" + name_file + ".png", path_outputDir + @"\2-segmentation\2-binOutput(refined)\" + name_file + ".png", 500, 0, 0); + } + catch + { + MessageBox.Show("Error: Unable to reprocess! Please check is there [RemoveSmallRegionDLL.dll] file in floder?", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + break; + } + // 再度確認 + mat_binOutput = Cv2.ImRead(path_outputDir + @"\2-segmentation\2-binOutput(refined)\" + name_file + ".png", ImreadModes.Grayscale); + Cv2.ImShow("dst2", mat_binOutput); + Cv2.WaitKey(); + + Cv2.Canny(mat_binOutput, edges, 50, 200); + Cv2.FindContours(edges, out contours, out hierarchyIndexes, RetrievalModes.List, ContourApproximationModes.ApproxSimple); + num_obj = contours.Count(); + if ((num_obj == 0) || (num_obj > 2)) + { + using (var sw = new StreamWriter(path_outputCsv, true)) + { + Console.WriteLine("Tongue segmentation error"); + var swLine = name_file + "," + "Tongue segmentation error"; + sw.WriteLine(swLine); + } + continue; + } + } + + // Resize + var mat_resizedOutput = new Mat(); + Cv2.Resize(mat_binOutput, mat_resizedOutput, dst_rect.Size); + mat_resizedOutput.SaveImage(path_outputDir + @"\2-segmentation\3-resized\" + name_file + ".png"); + + // マスクの作成  + var mat_mask = new Mat(mat_input.Size(), MatType.CV_8UC1, 0); + for (int i = dst_rect.Top; i < dst_rect.Bottom; i++) + for (int j = dst_rect.Left; j < dst_rect.Right; j++) + { + var value = mat_resizedOutput.At(i - dst_rect.Top, j - dst_rect.Left); + mat_mask.Set(i, j, value); + } + mat_mask.SaveImage(path_outputDir + @"\2-segmentation\4-mask\" + name_file + ".png"); + + // マスク 表示 + Invoke((MethodInvoker)delegate + { + label4.BackColor = Color.White; + label5.BackColor = Color.Red; + pictureBox_mask.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_mask); + pictureBox_mask.Refresh(); + }); + + // 舌領域の抽出 + var mat_extraction = new Mat(); + mat_input.CopyTo(mat_extraction, mat_mask); + mat_extraction.SaveImage(path_outputDir + @"\2-segmentation\5-extraction\" + name_file + ".png"); + + // 舌領域の抽出 表示 + Invoke((MethodInvoker)delegate + { + pictureBox_extracted.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_extraction); + pictureBox_extracted.Refresh(); + label5.BackColor = Color.White; + label6.BackColor = Color.Red; + label_state.Text = "舌特徴抽出中..."; + }); + + // 面積,舌検出座標 保存 + var areaCount = 0; + using (var mat_temp = new Mat()) + { + Cv2.CvtColor(mat_extraction, mat_temp, ColorConversionCodes.BGR2GRAY); + areaCount = Cv2.CountNonZero(mat_temp); + } + var out_dir = path_outputDir + @"\4-featureExtraction\shape\"; + MyMakeDir(out_dir); + using (var sw = new StreamWriter(out_dir + name_file + ".csv")) + { + sw.WriteLine(",width,height,area," + + "topLeftX,topLeftY,bottomRightX,bottomRightY"); + sw.Write( + "shape," + dst_rect.Width.ToString() + "," + dst_rect.Height.ToString() + "," + areaCount.ToString() + "," + + dst_rect.TopLeft.X.ToString() + "," + dst_rect.TopLeft.Y.ToString() + "," + + dst_rect.BottomRight.X.ToString() + "," + dst_rect.BottomRight.Y.ToString()); + } + + // 舌分画に基づく特徴量計算1: Chiu + var chiu = new ColorExtractor_Chiu(); + var mat_chiu = chiu.Get3areaMask(mat_mask); + var path_chiuMask = path_outputDir + @"\3-imgProc\devideRegion\chiu\" + name_file + ".png"; + mat_chiu.SaveImage(path_outputDir + @"\3-imgProc\devideRegion\chiu\" + name_file + ".png"); + + // 舌領域の抽出 表示 + Invoke((MethodInvoker)delegate + { + label6.BackColor = Color.White; + label7.BackColor = Color.Red; + pictureBox_regions.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_chiu); + pictureBox_regions.Refresh(); + }); + + // 舌分画に基づく特徴量計算2: ExtendedChiu + var exChiu = new ColorExtractor_ExtendedChiu(); + var mat_exChiu = exChiu.Get4areaMask(mat_mask); + mat_exChiu.SaveImage(path_outputDir + @"\3-imgProc\devideRegion\exChiu\" + name_file + ".png"); + + // 舌領域の抽出 表示 + Invoke((MethodInvoker)delegate + { + pictureBox_regions.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_exChiu); + pictureBox_regions.Refresh(); + }); + + // 舌分画に基づく特徴量計算3: fiveclick [ishikawa, 2010] + var fiveClick = new ColorExtractor_fiveClick(); + var mat_fiveClick = fiveClick.Get4areaMask(mat_mask); + mat_fiveClick.SaveImage(path_outputDir + @"\3-imgProc\devideRegion\fiveClick\" + name_file + ".png"); + + // 舌領域の抽出 表示 + Invoke((MethodInvoker)delegate + { + pictureBox_regions.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_fiveClick); + pictureBox_regions.Refresh(); + label6.BackColor = Color.White; + }); + + var regionMethod = new List { "chiu", "exChiu", "fiveClick" }; + // Lab + for (int m = 0; m < regionMethod.Count(); m++) + { + // 色統計量,GLCM計算 + var path_regionMask = path_outputDir + @"\3-imgProc\devideRegion\" + regionMethod[m] + "\\" + name_file + ".png"; + var path_GLCM = path_outputDir + @"\4-featureExtraction\region\" + regionMethod[m] + @"\GLCM\" + name_file + ".csv"; + var path_colorStats = path_outputDir + @"\4-featureExtraction\region\" + regionMethod[m] + @"\colorStats\" + name_file + ".csv"; + var pInfo_py = new ProcessStartInfo(@"python\TongueFeatures.exe") + { + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true, + Arguments = path_img + " " + path_regionMask + " " + path_colorStats + " " + path_GLCM + }; + using (var p = Process.Start(pInfo_py)) + { + p.WaitForExit(); + p.Close(); + } + } + + // CSVの保存 + Invoke((MethodInvoker)delegate + { + label7.BackColor = Color.White; + label_state.Text = "保存中..."; + }); + + // featuresを1つにまとめる + using (var sw = new StreamWriter(path_outputCsv, true)) + { + var swLine = name_file + ","; + using (var sr = new StreamReader(path_outputDir + @"\4-featureExtraction\shape\" + name_file + ".csv")) + { + sr.ReadLine(); + var l = sr.ReadLine(); + var l_ = l.Substring(l.IndexOf(',') + 1) + ","; + swLine += l_; + } + + for (int m = 0; m < regionMethod.Count(); m++) + { + using (var sr = new StreamReader(path_outputDir + @"\4-featureExtraction\region\" + regionMethod[m] + @"\GLCM\" + name_file + ".csv")) + { + sr.ReadLine(); + var l = ""; + var l_dst = ""; + while ((l = sr.ReadLine()) != null) + { + var l_ = l.Substring(l.IndexOf(',') + 1); + l_dst += l_ + ","; + } + swLine += l_dst; + } + } + + for (int m = 0; m < regionMethod.Count(); m++) + { + using (var sr = new StreamReader(path_outputDir + @"\4-featureExtraction\region\" + regionMethod[m] + @"\colorStats\" + name_file + ".csv")) + { + sr.ReadLine(); + var l = ""; + var l_dst = ""; + while ((l = sr.ReadLine()) != null) + { + var l_ = l.Substring(l.IndexOf(',') + 1); + l_dst += l_ + ","; + } + swLine += l_dst; + } + } + + swLine = swLine.Substring(0, swLine.LastIndexOf(',')); + sw.WriteLine(swLine); + } + + // 終了処理 + var faster = false; + var waitTime = faster ? 0 : 1000; + Thread.Sleep(waitTime); + + Invoke((MethodInvoker)delegate + { + pictureBox_input.Image = null; + pictureBox_input.Refresh(); + pictureBox_calibration.Image = null; + pictureBox_calibration.Refresh(); + pictureBox_detection.Image = null; + pictureBox_detection.Refresh(); + pictureBox_segmentation.Image = null; + pictureBox_segmentation.Refresh(); + pictureBox_mask.Image = null; + pictureBox_mask.Refresh(); + pictureBox_extracted.Image = null; + pictureBox_extracted.Refresh(); + pictureBox_regions.Image = null; + pictureBox_regions.Refresh(); + pictureBox_last.Image = null; + pictureBox_last.Refresh(); + }); + GC.Collect(); + } + + MessageBox.Show("Finished!"); + + Invoke((MethodInvoker)delegate + { + button_start.Enabled = true; + button_pause.Enabled = false; + label_processingFileName.Text = "Processing File: None"; + }); + + + } + + + // + private void BackgroundWorker1_DoWork_ImageAndCalib(object sender, DoWorkEventArgs e) + { + // ステータスの表示 + Invoke((MethodInvoker)delegate + { + label_state.Text = "準備中.."; + }); + + // Path + var path_imgDir = @"..\Input\TongueImage"; + var path_calibDir = @"..\Input\CalibrationColorChart"; + var path_outputDir = @"..\Output/" + DateTime.Now.ToLocalTime().ToString("yyyyMMddhhmmss"); + MyMakeDir(path_outputDir); + FolderCopy("template_outputDir_withCalib", path_outputDir); + path_outputDir = @"..\Output/" + "debug"; // for debug + var path_outputCsv = path_outputDir + @"\analysis.csv"; + + var count = 0; + foreach (var path_img in Directory.GetFiles(path_imgDir)) + { + var name_file = Path.GetFileNameWithoutExtension(path_img); + + // 校正画像探索 + var path_calib = Directory.GetFiles(path_calibDir).Where(x => Path.GetFileNameWithoutExtension(path_img) == Path.GetFileNameWithoutExtension(x)).ToList()[0]; + if (path_calib == null) + Console.Write("No calib sheet"); + + // ステータスの表示 + Invoke((MethodInvoker)delegate + { + label_processingFileName.Text = "Processing File: " + path_img; + count += 1; + label_totalProgress.Text = "Total Progress: " + count + "/" + Directory.GetFiles(path_imgDir).Length; + }); + + // Init + manualReset.WaitOne(); + Invoke((MethodInvoker)delegate + { + label8.Text = ""; + label8.BackColor = Color.White; + label1.BackColor = Color.Red; + }); + + // 舌画像表示 + Mat mat_input = Cv2.ImRead(path_img, ImreadModes.Color); + Invoke((MethodInvoker)delegate + { + label1.BackColor = Color.White; + label2.BackColor = Color.Red; + pictureBox_input.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_input); + pictureBox_input.Refresh(); + label_state.Text = "色校正"; + + }); + + // 色校正・変換 RGB->lab + var path_labCsv = path_outputDir + @"\3-imgProc\cvtColor\LabCsv\" + name_file + ".csv"; + var pInfo = new ProcessStartInfo(); + pInfo.Arguments = path_img + " " + path_calib + " " + path_labCsv; + pInfo.FileName = @"rgb2lab\TongueAnalysis.exe"; + using (var p = Process.Start(pInfo)) { + p.WaitForExit(); + p.Close(); + } + + // Lab画像読み込み 変換 保存 + Mat mat_lab = new Mat(mat_input.Size(), MatType.CV_32FC3); + Mat mat_lab_show = new Mat(mat_input.Size(), MatType.CV_8UC3); + using (var sr_lab = new StreamReader(path_labCsv)) + { + for (int i = 0; i < mat_input.Height; i++) + { + var line = sr_lab.ReadLine(); + var strs = line.Split(','); + for (int j = 0; j < mat_input.Width; j++) + { + var c = j * 3; + var str_l = strs[c]; + var str_a = strs[c + 1]; + var str_b = strs[c + 2]; + float l_, a_, b_; + float.TryParse(str_l, out l_); + float.TryParse(str_a, out a_); + float.TryParse(str_b, out b_); + mat_lab.Set(i, j, new Vec3f(l_, a_, b_)); + } + } + } + Cv2.CvtColor(mat_lab, mat_lab_show, ColorConversionCodes.Lab2BGR); + mat_lab_show.ConvertTo(mat_lab_show, MatType.CV_8UC3, 256.0); + Cv2.ImWrite(path_outputDir + @"\3-imgProc\cvtColor\LabImg\" + name_file + ".png", mat_lab_show); + + // Lab画像 表示 + Invoke((MethodInvoker)delegate + { + label2.BackColor = Color.White; + label3.BackColor = Color.Red; + pictureBox_calibration.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_lab_show); + pictureBox_calibration.Refresh(); + }); + + // Detection + var dst_rect = new Rect(); + try + { + dst_rect = TongueDetection(mat_lab_show); + } + catch (Exception) + { + using (var sw = new StreamWriter(path_outputCsv, true)) + { + var swLine = name_file + "," + "Tongue detection error"; + sw.WriteLine(swLine); + } + continue; + } + if ((dst_rect.Width == 0) || (dst_rect.Height == 0)) + { + using (var sw = new StreamWriter(path_outputCsv, true)) + { + var swLine = name_file + "," + "Tongue detection error"; + sw.WriteLine(swLine); + } + continue; + } + + var mat_drawBox = mat_lab_show.Clone(); + Cv2.Rectangle(mat_drawBox, dst_rect, new Scalar(0, 255, 0), 3); + mat_drawBox.SaveImage(path_outputDir + @"\1-detection\1-drawBox\" + name_file + ".png"); + + // Detection領域crop + var mat_cropped = new Mat(mat_lab_show, dst_rect); + mat_cropped.SaveImage(path_outputDir + @"\1-detection\2-cropped\" + name_file + ".png"); + + // Resize + var mat_roisize = mat_lab_show.Clone(dst_rect); + var mat_roi = new Mat(); + Cv2.Resize(mat_roisize, mat_roi, new OpenCvSharp.Size(256, 256)); + mat_roi.SaveImage(path_outputDir + @"\1-detection\3-resized\" + name_file + ".png"); + + // Detection 表示 + Invoke((MethodInvoker)delegate + { + label3.BackColor = Color.White; + label4.BackColor = Color.Red; + pictureBox_detection.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_roi); ; + pictureBox_detection.Refresh(); + }); + + // Segmenation + var mat_output = TongueSegmentation(mat_roi); + Cv2.Threshold(mat_output, mat_output, 128, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary); + Thread.Sleep(100); + GC.Collect(); + mat_output.SaveImage(path_outputDir + @"\2-segmentation\1-modelOutput\" + name_file + ".png"); + + // 2値化 + var mat_binOutput = new Mat(); + Cv2.Threshold(mat_output, mat_binOutput, 0, 255, ThresholdTypes.Otsu); + mat_binOutput.SaveImage(path_outputDir + @"\2-segmentation\2-binOutput\" + name_file + ".png"); + + // Segmenation 表示 + Invoke((MethodInvoker)delegate + { + label3.BackColor = Color.White; + label4.BackColor = Color.Red; + pictureBox_segmentation.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_binOutput); ; + pictureBox_segmentation.Refresh(); + }); + + // Segmentationの例外処理 + var edges = new Mat(); + OpenCvSharp.Point[][] contours; + HierarchyIndex[] hierarchyIndexes; + Cv2.Canny(mat_binOutput, edges, 50, 200); + Cv2.FindContours(edges, out contours, out hierarchyIndexes, RetrievalModes.List, ContourApproximationModes.ApproxSimple); + + // 1 or 2つ以外の輪郭が検出された場合 + var num_obj = contours.Count(); + if ((num_obj == 0) || (num_obj > 2)) + { + Console.WriteLine("Segmentation result has over 1 area : extracted areas = {0}", num_obj); + try + { + RemoveSmallRegion(path_outputDir + @"\2-segmentation\2-binOutput\" + name_file + ".png", path_outputDir + @"\2-segmentation\2-binOutput(refined)\" + name_file + ".png", 500, 1, 1); + RemoveSmallRegion(path_outputDir + @"\2-segmentation\2-binOutput(refined)\" + name_file + ".png", path_outputDir + @"\2-segmentation\2-binOutput(refined)\" + name_file + ".png", 500, 0, 0); + } + catch + { + MessageBox.Show("Error: Unable to reprocess! Please check is there [RemoveSmallRegionDLL.dll] file in floder?", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + break; + } + // 再度確認 + mat_binOutput = Cv2.ImRead(path_outputDir + @"\2-segmentation\2-binOutput(refined)\" + name_file + ".png", ImreadModes.Grayscale); + Cv2.ImShow("dst2", mat_binOutput); + Cv2.WaitKey(); + + Cv2.Canny(mat_binOutput, edges, 50, 200); + Cv2.FindContours(edges, out contours, out hierarchyIndexes, RetrievalModes.List, ContourApproximationModes.ApproxSimple); + num_obj = contours.Count(); + if ((num_obj == 0) || (num_obj > 2)) + { + using (var sw = new StreamWriter(path_outputCsv, true)) + { + Console.WriteLine("Tongue segmentation error"); + var swLine = name_file + "," + "Tongue segmentation error"; + sw.WriteLine(swLine); + } + continue; + } + } + + // Resize + var mat_resizedOutput = new Mat(); + Cv2.Resize(mat_binOutput, mat_resizedOutput, dst_rect.Size); + mat_resizedOutput.SaveImage(path_outputDir + @"\2-segmentation\3-resized\" + name_file + ".png"); + + // マスクの作成  + var mat_mask = new Mat(mat_input.Size(), MatType.CV_8UC1, 0); + for (int i = dst_rect.Top; i < dst_rect.Bottom; i++) + for (int j = dst_rect.Left; j < dst_rect.Right; j++) + { + var value = mat_resizedOutput.At(i - dst_rect.Top, j - dst_rect.Left); + mat_mask.Set(i, j, value); + } + mat_mask.SaveImage(path_outputDir + @"\2-segmentation\4-mask\" + name_file + ".png"); + + // マスク 表示 + Invoke((MethodInvoker)delegate + { + label4.BackColor = Color.White; + label5.BackColor = Color.Red; + pictureBox_mask.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_mask); + pictureBox_mask.Refresh(); + }); + + // 舌領域の抽出 + var mat_extraction = new Mat(); + mat_input.CopyTo(mat_extraction, mat_mask); + mat_extraction.SaveImage(path_outputDir + @"\2-segmentation\5-extraction\" + name_file + ".png"); + + + // 舌領域の抽出 表示 + Invoke((MethodInvoker)delegate + { + pictureBox_extracted.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_extraction); + pictureBox_extracted.Refresh(); + label5.BackColor = Color.White; + + label6.BackColor = Color.Red; + label_state.Text = "舌特徴抽出中..."; + }); + + // 面積,舌検出座標 保存 + var areaCount = 0; + using (var mat_temp = new Mat()) + { + Cv2.CvtColor(mat_extraction, mat_temp, ColorConversionCodes.BGR2GRAY); + areaCount = Cv2.CountNonZero(mat_temp); + } + using (var sw = new StreamWriter(path_outputDir + @"\4-featureExtraction\shape\" + name_file + ".csv")) + { + sw.WriteLine(",width,height,area," + + "topLeftX,topLeftY,bottomRightX,bottomRightY"); + sw.Write( + "shape," + dst_rect.Width.ToString() + "," + dst_rect.Height.ToString() + "," + areaCount.ToString() + "," + + dst_rect.TopLeft.X.ToString() + "," + dst_rect.TopLeft.Y.ToString() + "," + + dst_rect.BottomRight.X.ToString() + "," + dst_rect.BottomRight.Y.ToString()); + } + + + // 舌分画に基づく特徴量計算1: Chiu + var chiu = new ColorExtractor_Chiu(); + var mat_chiu = chiu.Get3areaMask(mat_mask); + var path_chiuMask = path_outputDir + @"\3-imgProc\devideRegion\chiu\" + name_file + ".png"; + mat_chiu.SaveImage(path_outputDir + @"\3-imgProc\devideRegion\chiu\" + name_file + ".png"); + + // 舌領域の抽出 表示 + Invoke((MethodInvoker)delegate + { + label6.BackColor = Color.White; + label7.BackColor = Color.Red; + pictureBox_regions.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_chiu); + pictureBox_regions.Refresh(); + }); + + // 舌分画に基づく特徴量計算2: ExtendedChiu + var exChiu = new ColorExtractor_ExtendedChiu(); + var mat_exChiu = exChiu.Get4areaMask(mat_mask); + mat_exChiu.SaveImage(path_outputDir + @"\3-imgProc\devideRegion\exChiu\" + name_file + ".png"); + + // 舌領域の抽出 表示 + Invoke((MethodInvoker)delegate + { + pictureBox_regions.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_exChiu); + pictureBox_regions.Refresh(); + }); + + // 舌分画に基づく特徴量計算3: fiveclick [ishikawa, 2010] + var fiveClick = new ColorExtractor_fiveClick(); + var mat_fiveClick = fiveClick.Get4areaMask(mat_mask); + mat_fiveClick.SaveImage(path_outputDir + @"\3-imgProc\devideRegion\fiveClick\" + name_file + ".png"); + + // 舌領域の抽出 表示 + Invoke((MethodInvoker)delegate + { + pictureBox_regions.Image = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_fiveClick); + pictureBox_regions.Refresh(); + label7.BackColor = Color.White; + }); + + var regionMethod = new List { "chiu", "exChiu", "fiveClick" }; + // Lab + for (int m = 0; m < regionMethod.Count(); m++) + { + // 色統計量,GLCM計算 + var path_regionMask = path_outputDir + @"\3-imgProc\devideRegion\" + regionMethod[m] + "\\" + name_file + ".png"; + var path_GLCM = path_outputDir + @"\4-featureExtraction\region\" + regionMethod[m] + @"\GLCM\" + name_file + ".csv"; + var path_colorStats = path_outputDir + @"\4-featureExtraction\region\" + regionMethod[m] + @"\colorStats\" + name_file + ".csv"; + var pInfo_py = new ProcessStartInfo(@"python\TongueFeatures.exe") + { + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true, + Arguments = path_labCsv + " " + path_regionMask + " " + path_colorStats + " " + path_GLCM + }; + using (var p = Process.Start(pInfo_py)) + { + + p.WaitForExit(); + p.Close(); + } + } + + // 舌領域の抽出 表示 + Invoke((MethodInvoker)delegate + { + label_state.Text = "保存中..."; + }); + + // featuresを1つにまとめる + using (var sw = new StreamWriter(path_outputCsv, true)) + { + var swLine = name_file + ","; + using (var sr = new StreamReader(path_outputDir + @"\4-featureExtraction\shape\" + name_file + ".csv")) + { + sr.ReadLine(); + var l = sr.ReadLine(); + var l_ = l.Substring(l.IndexOf(',') + 1) + ","; + swLine += l_; + } + + for (int m = 0; m < regionMethod.Count(); m++) + { + using (var sr = new StreamReader(path_outputDir + @"\4-featureExtraction\region\" + regionMethod[m] + @"\GLCM\" + name_file + ".csv")) + { + sr.ReadLine(); + var l = ""; + var l_dst = ""; + while ((l = sr.ReadLine()) != null) + { + var l_ = l.Substring(l.IndexOf(',') + 1); + l_dst += l_ + ","; + } + swLine += l_dst; + } + + } + for (int m = 0; m < regionMethod.Count(); m++) + { + using (var sr = new StreamReader(path_outputDir + @"\4-featureExtraction\region\" + regionMethod[m] + @"\colorStats\" + name_file + ".csv")) + { + sr.ReadLine(); + var l = ""; + var l_dst = ""; + while ((l = sr.ReadLine()) != null) + { + var l_ = l.Substring(l.IndexOf(',') + 1); + l_dst += l_ + ","; + } + swLine += l_dst; + } + } + + swLine = swLine.Substring(0, swLine.LastIndexOf(',')); + sw.WriteLine(swLine); + } + + // 終了処理 + var faster = false; + var waitTime = faster ? 0 : 1000; + Thread.Sleep(waitTime); + + Invoke((MethodInvoker)delegate + { + pictureBox_input.Image = null; + pictureBox_input.Refresh(); + pictureBox_calibration.Image = null; + pictureBox_calibration.Refresh(); + pictureBox_detection.Image = null; + pictureBox_detection.Refresh(); + pictureBox_segmentation.Image = null; + pictureBox_segmentation.Refresh(); + pictureBox_mask.Image = null; + pictureBox_mask.Refresh(); + pictureBox_extracted.Image = null; + pictureBox_extracted.Refresh(); + pictureBox_regions.Image = null; + pictureBox_regions.Refresh(); + pictureBox_last.Image = null; + pictureBox_last.Refresh(); + }); + GC.Collect(); + } + + MessageBox.Show("Finished!"); + + Invoke((MethodInvoker)delegate + { + button_start.Enabled = true; + button_pause.Enabled = false; + label_processingFileName.Text = "Processing File: None"; + }); + + } + + private OpenCvSharp.Rect TongueDetection(Mat mat_input) + { + var byte_input = Bitmap2Byte(OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat_input)); + var rect_dst = new OpenCvSharp.Rect(); + using (var graph = new TFGraph()) + { + var model = File.ReadAllBytes(path_detectionModel); + graph.Import(model, ""); + using (var session = new TFSession(graph)) + { + var tensor = ImageUtil.CreateTensorFromImageFile(byte_input, TFDataType.UInt8); + var runner = session.GetRunner(); + runner + .AddInput(graph["image_tensor"][0], tensor) + .Fetch("detection_boxes", "detection_scores", "detection_classes", "num_detections"); + var output = runner.Run(); + var boxes = (float[,,])output[0].GetValue(); + var scores = (float[,])output[1].GetValue(); + var classes = (float[,])output[2].GetValue(); + var detections = (float[])output[3].GetValue(); + var check_detection = 0; + max_score = 0; + for (int i = 0; i < scores.Length; i++) + { + if ((scores[0, i] > 0.5) && (scores[0, i] > max_score)) + { + max_score = scores[0, i]; + float y_min = boxes[0, i, 0] * (float)mat_input.Height; + float x_min = boxes[0, i, 1] * (float)mat_input.Width; + float y_max = boxes[0, i, 2] * (float)mat_input.Height; + float x_max = boxes[0, i, 3] * (float)mat_input.Width; + rect_dst.X = (int)x_min; + rect_dst.Y = (int)y_min; + rect_dst.Width = (int)(x_max - x_min); + rect_dst.Height = (int)(y_max - y_min); + + check_detection = 1; + } + } + } + } + return rect_dst; + } + + // Ref:https://book2525stack.com/2021/06/03/net_hand_written_digit_recognition/ + private Mat TongueSegmentation(Mat mat_input) + { + var mat_dst = new Mat(mat_input.Size(), MatType.CV_8UC1); + + // 推論 + using (var session = new InferenceSession(path_segmentationModel)) + { + var inputMeta = session.InputMetadata; + var container = new List(); + + /* input shape = [1, 3, 256, 256] + * [batchsize, channels, w, h] + */ + var inputData = new float[1, mat_input.Channels(), mat_input.Width, mat_input.Height]; + var inputData_flat = new float[mat_input.Channels() * mat_input.Width * mat_input.Height]; + for (int c = 0; c < mat_input.Channels(); c++) + for (int i = 0; i < mat_input.Height; i++) + for (int j = 0; j < mat_input.Width; j++) + inputData_flat[j + (i * mat_input.Width) + (c * (mat_input.Width * mat_input.Height))] = + (float)(mat_input.At(i, j)[c] / 255.0); + var tensor = new DenseTensor(inputData_flat, inputMeta[inputMeta.Keys.First()].Dimensions, false); + var namedOnnxValues = new List + { + NamedOnnxValue.CreateFromTensor(inputMeta.Keys.First(), tensor) + }; + + /* output shape = [1, 2, 256, 256] + * [batchsize, class proba, w, h] + */ + using (var results = session.Run(namedOnnxValues)) + { + var result = results.First().AsTensor().ToArray(); + var mat_result = new float[mat_input.Width, mat_input.Height]; + var list_result = new List(); + for (int c = 0; c < 2; c++) + for (int i = 0; i < mat_input.Height; i++) + for (int j = 0; j < mat_input.Width; j++) + { + var proba = result[j + (i * mat_input.Width) + (c * (mat_input.Width * mat_input.Height))]; + if (c == 0) + { + mat_result[j, i] = proba; + list_result.Add(proba); + } + } + + for (int i = 0; i < mat_input.Height; i++) + for (int j = 0; j < mat_input.Width; j++) + { + // Sigmoid * 255 + var value = (byte)((float)(1.0 / (1.0 + Math.Pow(Math.E, -(mat_result[j, i])))) * 255.0); + mat_dst.Set(i, j, value); + } + } + } + GC.Collect(); + return mat_dst; + } + + private static class ImageUtil + { + // Convert the image in filename to a Tensor suitable as input to the Inception model. + public static TFTensor CreateTensorFromImageFile(byte[] contents, TFDataType destinationDataType = TFDataType.UInt8) + { + // DecodeJpeg uses a scalar String-valued tensor as input. + var tensor = TFTensor.CreateString(contents); + + TFGraph graph; + TFOutput input, output; + + // Construct a graph to normalize the image + ConstructGraphToNormalizeImage(out graph, out input, out output, destinationDataType); + + // Execute that graph to normalize this one image + using (var session = new TFSession(graph)) + { + var normalized = session.Run( + inputs: new[] { input }, + inputValues: new[] { tensor }, + outputs: new[] { output }); + + if (session != null) + { + session.Dispose(); + } + if (tensor != null) + { + tensor.Dispose(); + } + if (graph != null) + { + graph.Dispose(); + } + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + return normalized[0]; + } + } + + // The inception model takes as input the image described by a Tensor in a very + // specific normalized format (a particular image size, shape of the input tensor, + // normalized pixel values etc.). + // + // This function constructs a graph of TensorFlow operations which takes as + // input a JPEG-encoded string and returns a tensor suitable as input to the + // inception model. + + public static void ConstructGraphToNormalizeImage(out TFGraph graph, out TFOutput input, out TFOutput output, TFDataType destinationDataType = TFDataType.UInt8) + { + const int W = 256; + const int H = 256; + const float Mean = 0; + const float Scale = 1; + graph = new TFGraph(); + input = graph.Placeholder(TFDataType.String); + output = graph.Cast(graph.Div( + x: graph.Sub( + x: graph.ResizeBilinear( + images: graph.ExpandDims( + input: graph.Cast( + graph.DecodeJpeg(contents: input, channels: 3), DstT: destinationDataType), + dim: graph.Const(0, "make_batch")), + size: graph.Const(new int[] { W, H }, "size")), + y: graph.Const(Mean, "mean")), + y: graph.Const(Scale, "scale")), destinationDataType); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + } + + private static class ImageUtil2 + { + // Convert the image in filename to a Tensor suitable as input to the Inception model. + public static TFTensor CreateTensorFromImageFile(byte[] contents, TFDataType destinationDataType = TFDataType.Float) + { + // DecodeJpeg uses a scalar String-valued tensor as input. + var tensor = TFTensor.CreateString(contents); + + TFGraph graph; + TFOutput input, output; + + // Construct a graph to normalize the image + ConstructGraphToNormalizeImage(out graph, out input, out output, destinationDataType); + + // Execute that graph to normalize this one image + using (var session = new TFSession(graph)) + { + var normalized = session.Run( + inputs: new[] { input }, + inputValues: new[] { tensor }, + outputs: new[] { output }); + + if (session != null) + { + session.Dispose(); + } + if (tensor != null) + { + tensor.Dispose(); + } + if (graph != null) + { + graph.Dispose(); + } + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + return normalized[0]; + } + } + + // The inception model takes as input the image described by a Tensor in a very + // specific normalized format (a particular image size, shape of the input tensor, + // normalized pixel values etc.). + // + // This function constructs a graph of TensorFlow operations which takes as + // input a JPEG-encoded string and returns a tensor suitable as input to the + // inception model. + + public static void ConstructGraphToNormalizeImage(out TFGraph graph, out TFOutput input, out TFOutput output, TFDataType destinationDataType = TFDataType.Float) + { + const int W = 256; + const int H = 256; + //const int W = 512; + //const int H = 512; + + const float Mean = 128; + const float Scale = 128; + graph = new TFGraph(); + input = graph.Placeholder(TFDataType.String); + output = graph.Cast(graph.Div( + x: graph.Sub( + x: graph.ResizeBilinear( + images: graph.ExpandDims( + input: graph.Cast( + graph.DecodeJpeg(contents: input, channels: 3), DstT: TFDataType.Float), + dim: graph.Const(0, "make_batch")), + size: graph.Const(new int[] { W, H }, "size")), + y: graph.Const(Mean, "mean")), + y: graph.Const(Scale, "scale")), destinationDataType); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + } + + private static byte[] Bitmap2Byte(Bitmap bitmap) + { + using (MemoryStream stream = new MemoryStream()) + { + bitmap.Save(stream, ImageFormat.Jpeg); + byte[] data = new byte[stream.Length]; + stream.Seek(0, SeekOrigin.Begin); + stream.Read(data, 0, Convert.ToInt32(stream.Length)); + return data; + } + } + + private static Bitmap ToGrayBitmap(byte[] rawValues, int width, int height) + { + //// Apply for a target bitmap variable and lock its memory area + Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed); + BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); + + //// Get image parameters + int stride = bmpData.Stride; // Width of the scan line + int offset = stride - width; // Show gap between width and scan line width + IntPtr iptr = bmpData.Scan0; // Get the memory start position of bmpData + int scanBytes = stride * height;// Use stride width to indicate that this is the size of the memory area + + //// The following is to convert the original display size byte array to the byte array actually stored in memory. + int posScan = 0, posReal = 0;// Set two position pointers respectively, pointing to the source array and the target array + byte[] pixelValues = new byte[scanBytes]; //Allocate memory for the target array + + for (int x = 0; x < height; x++) + { + //// The following loop section is a simulated line scan + for (int y = 0; y < width; y++) + { + pixelValues[posScan++] = rawValues[posReal++]; + } + posScan += offset; //At the end of the line scan, move the target position pointer over that "gap" + } + + //// Use Marshal's Copy method to copy the just obtained memory byte array into BitmapData. + System.Runtime.InteropServices.Marshal.Copy(pixelValues, 0, iptr, scanBytes); + bmp.UnlockBits(bmpData); // Unlock the memory area + + //// The following code is to modify the index table of the generated bitmap, from pseudo color to grayscale + ColorPalette tempPalette; + using (Bitmap tempBmp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed)) + { + tempPalette = tempBmp.Palette; + } + for (int i = 0; i < 256; i++) + { + tempPalette.Entries[i] = Color.FromArgb(i, i, i); + } + + bmp.Palette = tempPalette; + + //// The algorithm ends here and returns the result. + return bmp; + } + + private void watcher_Renamed(System.Object source, + System.IO.RenamedEventArgs e) + { + Console.WriteLine( + "ファイル 「" + e.FullPath + "」の名前が変更されました。"); + } + + private void Button_pause_Click(object sender, EventArgs e) + { + if (button_pause.Text == "Pause") + { + manualReset.Reset(); + button_pause.Text = "Continue"; + } + else + { + manualReset.Set(); + button_pause.Text = "Pause"; + } + } + + private void MyMakeDir(string path) + { + // ディレクトリが存在しなければ作成,存在すれば空にする + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + else + { + foreach (var dir in new DirectoryInfo(path).GetDirectories()) + dir.Delete(true); + foreach (var file in new DirectoryInfo(path).GetFiles()) + file.Delete(); + } + } + + static void FolderCopy(string src_str, string dest_str) + { + DirectoryInfo src_info = new DirectoryInfo(src_str); + + // コピー元の存在チェック + if (!src_info.Exists) + { + Console.WriteLine("ERROR:" +src_str + " doesn’t exist!"); + return; + } + + // コピー先のフォルダ作成 + Directory.CreateDirectory(dest_str); + + // コピー元のフォルダとファイル情報を取得 + DirectoryInfo[] src_folders = src_info.GetDirectories(); + FileInfo[] src_files = src_info.GetFiles(); + + // コピー元のファイルを全てコピー先のフォルダに上書きコピー + foreach (FileInfo file in src_files) + { + string path = Path.Combine(dest_str, file.Name); + file.CopyTo(path, true); + } + + // 再起呼び出しによりコピー元のフォルダを全てコピー先のフォルダにコピー + foreach (DirectoryInfo subfolder in src_folders) + { + string path = Path.Combine(dest_str, subfolder.Name); + FolderCopy(subfolder.FullName, path); + } + + } + } +} \ No newline at end of file diff --git a/Tongue extraction/Form1.resx b/Tongue extraction/Form1.resx new file mode 100644 index 0000000..2547995 --- /dev/null +++ b/Tongue extraction/Form1.resx @@ -0,0 +1,1262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 181, 17 + + + 58 + + + + + AAABAAEAgIAAAAEAIAAoCAEAFgAAACgAAACAAAAAAAEAAAEAIAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAKtjAwK3dQ1DuHYOSbd1DQUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsW0FEbt8FJC7fBSctHEJGQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAKJWAACybgY4voAY0b6AGN60cQlFqGAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArmgBCrNvB3jBhBz2woUe+7Rx + CYiuZwEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACwawMntHEJvsSJIf/GiyT/tXIKzbBrAzG0cQkBAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsm4GBbFsBGC1cgrwyI4n/8qR + Kv+2dAz3sWwEc7JuBggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAALJuBgCybgYZsW0FrLZ0Df/Lkyz/zZYv/7h3D/+xbQW9sm4GIrJuBgAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsm4GArJuBkqxbQXmuXgQ/86X + MP/QmjP/unoT/7JtBe6ybgZdsm4GBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAALJuBgCybgYNsm4Gl7JuBvu7exT/0Joz/9KdNv+9fhf/sm4G/LJuBqqybgYUsm4GAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsm4GALJuBjeybgbYsm4G/r5/ + GP/SnTb/0584/8CDG/+zbwf/sm4G47JuBkiybgYBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACybgYGsm4GgLJuBvezbwf/wYQd/9OfOP/UoDn/xIgh/7NwCP+ybgb5sm4GlLJu + BgsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALJuBiiybgbHsm4G/rRw + CP/FiSL/1KA5/9WhOv/HjSb/tXIK/7JuBv6ybgbUsm4GNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACybgYCsm4Ga7JuBu6ybgb/tXIK/8iPJ//VoTr/1aE6/8uTK/+2dAz/sm4G/7Ju + BvSybgZ/sm4GBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALJuBhyybgaysm4G/LJu + Bv+2dQ3/zJQs/9WhOv/UoTr/zpcw/7h3D/+ybgb/sm4G/rJuBsKybgYpAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACybgYAsm4GVbJuBuKybgb/sm4G/7l4EP/PmDH/1KE6/tSgOf7RmzT/u3sT/7Ju + Bv+ybgb/sm4G67JuBmmybgYCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALJuBhKybgaasm4G+rJu + Bv+ybgb/u3wU/9GbNP/UoDnc1KA52NKeN/2+fxj/sm4G/7JuBv+ybgb9sm4GrbJuBhwAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACybgYAsm4GQLJuBtSybgb/sm4G/7JuBv+/gBn/05439dSgOYjUoDl605847cKF + Hf+zbwf/sm4G/7JuBv+ybgbfsm4GUrJuBgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALJuBgmybgaBsm4G9bJu + Bv+ybgb/s28H/8KGHv/UnzjW1KA5P9SgOS/UoDnJxYoj/rNwCP+ybgb/sm4G/7JuBvuybgaVsm4GEQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACybgYHsm4GRLJuBseybgb/sm4G/7JuBv+0cAj/xosk+9ShOp7UoDkQ1KA5CdWh + OorJjyj2tXIK/7JuBv+ybgb/sm4G/7JuBtWybgZQsm4GCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACybgYAsm4GIrJuBmuybgbDsm4G+LJu + Bv+ybgb/sm4G/7VyC//Ijyjk1aI7VwAAAAAAAAAA1aI7Q8uTLNq2dA3/sm4G/7JuBv+ybgb/sm4G+7Ju + BsqybgZxsm4GJbJuBgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAsm4GFLJuBleybgatsm4G7LJuBv+ybgb/sm4G/7JuBv+ybgb/t3UN/MqSKrXXpD0cAAAAAAAA + AADYpT4QzZUuobh3D/qybgb/sm4G/7JuBv+ybgb/sm4G/7JuBu6ybgawsm4GWLJuBhQAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsm4GCbJuBkGybgaasm4G4LJuBv6ybgb/sm4G/7Ju + Bv+ybgb/sm4G/7JuBv+4dg7wypEpbN2uRwIAAAAAAAAAAP/7lgDNlS5VuXkR57JuBv+ybgb/sm4G/7Ju + Bv+ybgb/sm4G/7JuBv6ybgbgsm4GmrJuBj+ybgYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACybgYAsm4GBbJu + BiqybgaCsm4G07JuBviybgb/sm4G/7JuBv+ybgb/sm4G/7JuBv+ybgb/sm4G/LZ0DcPGiyQnAAAAAAAA + AAAAAAAAAAAAAMmQKRq4dg6wsm8H+rJuBv+ybgb/sm4G/7JuBv+ybgb/sm4G/7JuBv+ybgb4sm4G0bJu + BnyybgYlsm4GA7JuBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAsm4GA7JuBhqybgZlsm4GwrJuBvOybgb/sm4G/7JuBv+ybgb/sm4G/7Ju + Bv+ybgb/s3AI/bZzDO24dg6tuHYPRsCCGgQAAAAAAAAAAAAAAAAAAAAAv4EZA7h2Djm4dg6jtnQM6rNw + CP2ybgb/sm4G/7JuBv+ybgb/sm4G/7JuBv+ybgb/sm4G8bJuBryybgZbsm4GFbJuBgIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsm4GAbJuBhGybgZKsm4GqrJu + Bu2ybgb9sm4G/7JuBv+ybgb/sm4G/7JuBv+ybwf/tXIK/7l5EfS/gRrExIghaMWKIxu9fhcDAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAuXkRAsWJIhfEiCFgv4IawLl5EfO1cgr/sm4G/7JuBv+ybgb/sm4G/7Ju + Bv+ybgb/sm4G/bJuBuiybgaesm4GP7JuBgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAALJuBguybgY4sm4GjrJuBuCybgb8sm4G/7JuBv+ybgb/sm4G/7JtBf+zbwf/t3UN/71+ + FvnFiSLVzJQshdGbNCzTnzgF5aY/AAAAAAAAAAAARY8iDkWUIwu9bgcHs28HFrJuBgcAAAAAAAAAANSf + OATRmzQpzJQsgsSJItS9fhb5tnQM/7NvB/+ybQX/sm4G/7JuBv+ybgb/sm4G/7JuBvuybgbXsm4GfrJu + BiyybgYGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + Gf9wqBn/X58d/0WPIv9AjSP/Q44j/1qcHf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+o + GedtqBhY558/HNSgObzUoDn/zpcw/7d1Df+ybgb/sm4G/7JuBv+ybgb+sm4GdbJuBggAAAAAAAAAAP// + /wDUoDleyI4m7rZ0DP+ybQX/sm4G/7h2Dv/NlS7f16U/RrBrBEyxbQXhvH0W/9CbNP/GiyT2s3AIoLJu + Bp2ybgb/sW0F/7+BGv/MlS3/wIMc/s+ZMf/UoDn/1KA5/9SgOf7UoDmc1KA5FgAAAABBjSMDQY0jVUGN + I/FBjSP/QY0j/0GNI/9Zmx7+bqcZ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAG6nGe9bnB3/Qo0j/0GNI/9BjSP/QY0j4UGNIzsLaDEAcqoYAG+o + GUtvqBnqb6gZ/2+oGf9vqBn/b6gZ/2+oGf9vqBn/cKgZ/2SiG/9JkSH/QI0j/0GNI/lBjSNxQY0jBQAA + AAAAAAAAb6gZAG+oGU9vqBnhb6gZ/2+oGf9vqBn/b6gZ/3CoGf9gnxz/RpAi/0CNI/9HkSL/ZaIb/2+o + Gf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9vqBn/b6gZ522oGFjnnz8c1KA5vNSgOf/PmTL/uXgQ/7Ju + Bv+ybgb/sm4G/7JuBv6ybgZ0sm4GCAAAAAAAAAAAAAAAANWhOlTSnDXnwIMb/7NvB/+ybgb/t3UN/8yU + LezWoz1lsWwEUbJuBuLDhx//0p02/8OGH/WybweUsm4GkrJuBv+ybQX/wIMc/8+ZMvvKkSrizpgx99Kc + Nf/Tnzj/1KA5/9SgOc3UoToz9LtVAD2MIgNBjSNUQY0j8UGNI/9BjSP/Qo4j/12dHf5uqBnbAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAb6gZ716e + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAb6gZ72+oGf9dnR3/Q44j/0GN + I/9BjSPgQY0jOxp2KwAAAAAAb6gZSG+oGehvqBn/b6gZ/2+oGf9vqBn/bqcZ/1mbHv9CjSP/QY0j/0GN + I/9BjSP/QY0j2UGNIzBmoxsBb6gZO2+oGdhvqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9ppRr/VZgf+0OO + I8dXmh7ebKYa/2+oGf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9vqBnnbagYWOef + PxzUoDm81KA5/9SgOf/PmDH/uXgQ/7JtBf+ybgb/sm4G/rJuBnSybgYIAAAAAAAAAAAAAAAA1KA5CNSg + OYrUoDn61KA5/9SgOf/RmzT5woUelL5/KAzUoDmF1KA5+9GcNf+7exP+sm4GzrJuBi2ybgZ5sW0F/bh2 + Dv/NlS7/1aE6+dKdNmy1cwsasm0Fg7NvB/nBhB3/0p02/9SgOezUoDlJmpkwA0GNI0VBjSPsQY0j/0OO + I/9enh3/b6gZ/m+oGd8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABvqBnvb6gZ/2CfHP9FjyL/QI0j/0GNI9pBjSM1AAAAAAAAAABvqBlIb6gZ6G+o + Gf9vqBn/b6gZ/2+oGf9sphr/UZcf/0GNI/9BjSP/QY0j/0GNI/9BjSPTQI0jKWymGgZvqBlnb6gZ9W+o + Gf9vqBn/b6gZ/2+oGf9vqBn/aaQa/1OYH/9DjiPnP4wjmk6VINVopBr/b6gZ/2+oGf9vqBn/b6gZ/2+o + Gf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGedtqBhY558/HNSgObzUoDn/1KA5/9CaM/+7exP/sW0F/7Ju + Bv+ybgb+sm4GdLJuBggAAAAAAAAAAAAAAADUoDkF1KA5ftSgOfbUoDn/1KA5/9CaM/q/gRqcvH0fEdSg + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABvqBn/b6gZ/22nGv9VmR//QY0j/0GNI9VBjSMyAAAAAAAA + AABvqBlIb6gZ6G+oGf9vqBn/b6gZ/2OhHP9FkCL/QY0j/0GNI/9BjSP/QY0j/0GNI/9BjSOrZKIbT2+o + GeFvqBn/aKQb/1KXH/9CjiP/QY0j/0GNI/9BjSP/QY0j8EGNI3RBjSMBQY0jbUGNI/BCjiP/W50d/2+o + Gf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGedtqBhY558/HNSgObzUoDn/1KA5/9Sg + Of/Jjyj/tHEJ/7JuBv+ybgb+sm4GdLJuBggAAAAAAAAAAAAAAAAAAAAA1KA5PdSgOdXUoDn/1KA5/8uT + K/+2cwvHvH0WOdWiO5zSnTb/vn8X/7JuBvqybgaLsm4GDLJuBmyybgb4wIMb/9KdNv/Unzj/wIMb/7d2 + Dv++fxjuu3wUkrd2Dia1cgoCAAAAAAAAAABBjSMAQY0jO0GNI+FBjSP/Vpkf/22nGf9vqBn+b6gZ3wAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG+o + Gf9vqBn/bqcZ/1iaHv9CjSP/QY0j1UGNIzIAAAAAAAAAAG+oGUhvqBnob6gZ/2+oGf9vqBn/W5wd/0OO + I/9BjSP/QY0j/0GNI/9BjSP/QY0j/0KNI6dppRqAb6gZ9mikGv9Tlx//Qo4j/0GNI/9BjSP/QY0j/0GN + I/9BjSPaQY0jPkGNIwBBjSN4QY0j80GNI/9Tlx//bacZ/2+oGf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+o + Gf9vqBn/b6gZ522oGFjnnz8c1KA5vNSgOf/UoDn/1KA5/8uSK/+1cwv/sm0F/7JuBv6ybgZ0sm4GCAAA + AAAAAAAAAAAAAAAAAADUoDkz1KA5ztSgOf/UoDn/ypEq/7VyCs28fBVD1qI7n9CbNP+7exP/sm4G+bJu + BoCybgYLsm4GaLJuBvbBhB3/0543/9SfOP/BhBz/uHYO/7RwCP6ybgbysm4GobFsBCeKMwABAAAAAEKN + IwBBjSM7QY0j4UKOI/9Zmx7/bqgZ/2+oGf5vqBnmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAb6gZ/2+oGf9vqBn/W5wd/0OOI/9BjSPVQY0jMgAA + AAAAAAAAb6gZSG+oGehvqBn/b6gZ/22nGf9Slx//QY0j/0GNI/9BjSP/QY0j/0GNI/9BjSP/RY8irGym + GrVppBr7U5cf/0KOI/9BjSP/QY0j/0GNI/9BjSP/QY0j/UGNI7JBjSMVQY0jBEGNI4FBjSP2QY0j/0uT + If9ppRr/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9vqBnnbagYWOefPxzUoDm81KA5/9Sg + Of/UoDn/zZUu/7d1Df+ybQX/sm4G/rJuBnSybgYIAAAAAAAAAAAAAAAAAAAAANSgOSrUoDnG1KA5/9Sg + Of/JkCj/tHEJ0bx9FU/Wozynzpcw/7l4EP+ybQX5sm4GdbJuBgiybQVfsm8H88OGH//Tnzj/1J84/8OH + H/+/gBn/tXIL/7JuBv+ybgb1tnMMicGFHRIAAAAAOYsgAEGNIztBjSPhQ44j/1ydHf9vqBn/b6gZ/2+o + Ge8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABvqBn/b6gZ/2+oGf9enh3/RI8i/0CNI9VBjSMyAAAAAAAAAABvqBlIb6gZ6G+oGf9vqBn/aaUa/0uT + If9BjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/9MkyHDZaIb4FOYH/5DjiP/QY0j/0GNI/9BjSP/QY0j/0GN + I/9BjSP0QY0jeUGNIwJBjSMJQY0ji0GNI/lBjSP/RpAi/2OhHP9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+o + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABvqBn/b6gZ/2+oGf9vqBn/b6gZ/2Kg + HMpQliAoAAAAAAAAAABgoBwkaqUan2+oGe5vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+o + Gf9vqBn/Wpwe/0KNI/9BjSP7QY0jnUGNIxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBjSNmQY0j8UGN + I/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/9UmB//bKYa/2+oGf9vqBn/b6gZ/2+oGedtqBhY558/HNSg + ObzUoDn/1KA5/9SgOf/UoDn/1aE6/8mQKf+1cwv/sW0FhLJuBgwAAAAAAAAAAAAAAAAAAAAAAAAAANSg + OQfUoDmay5Ms+bVyCv+zcAj/v4Ea/71/F/+ybgb/sm4G97JuBmaybgYFAAAAALh3ETvIjifX1KA5/9Sh + Ov/MlCz/tnQM/7JuBv+ybgb/sm4G0bJuBkKhVQABAAAAAAAAAAAAAAAAT5UgMmGgHNVvqBn/b6gZ/2+o + Gf9vqBn/b6gZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAG+oGf9vqBn/b6gZ/2+oGf9vqBn/ZqMbylmbHigAAAAAAAAAAG+oGQNwqRkeb6gZaG+o + GcdvqBn4b6gZ/m+oGf9vqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGf9cnR3/Q44j/0GNI+tBjSNnQY0jAgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGNI2ZBjSPxQY0j/0GNI/9BjSP/QY0j/0GNI/9BjSP/QI0j/02U + IP9ppBr/b6gZ/2+oGf9vqBn/b6gZ522oGFjnnz8c1KA5vNSgOf/UoDn/1KA5/9SgOf/VoTr/y5Mr/7d1 + Df+xbASEsm4GDAAAAAAAAAAAAAAAAAAAAAAAAAAA1KA5BNSgOYzKkSr3tHEK/7VzC//HjSb/xIki/7Nw + CP+ybgbzsm4GXLJvBgQAAAAAu3sVO8qRKdjUoDn/1KE6/8mPKP+1cgr/sm4G/7JuBuyybgZ2sm4GCQAA + AAAAAAAAAAAAAAAAAABVmR8xZaIb1W+oGf9vqBn/b6gZ/2+oGf9vqBn/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAb6gZ/2+oGf9vqBn/b6gZ/2+o + Gf9sphrXaKQbQmymGgYAAAAAAAAAAAAAAABvqBkHb6gZMG+oGYtvqBnfb6gZ+2+oGf9vqBn/b6gZ/2+o + Gf9vqBn/b6gZ/1+eHf9DjiP/QY0j0EGNIzUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQY0jZkGN + I/FBjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/9AjSP/RpAi/2OhHP9vqBn/b6gZ/2+oGf9vqBnnbagYWOef + PxzUoDm81KA5/9SgOf/UoDn/1KA5/9WhOv/NlS7/uHcP/7FsBISybgYMAAAAAAAAAAAAAAAAAAAAAAAA + AADUoDkC1KA5gMmPKPW0cQn/uHcP/86XMP/Kkir/tnQM/7FtBe+ybgZSs28GAwAAAAC+fxk7y5Ms2NSg + Of/UoDn/xosj/7RwCP+ybgbysm4GlLJuBhkAAAAAAAAAAAAAAAAAAAAAb6gZAV+fHDhqpRrYb6gZ/2+o + Gf9vqBn/b6gZ/2+oGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABvqBn/b6gZ/2+oGf9vqBn/b6gZ/2+oGfpvqBnHb6gZZm+oGR1vqBkDAAAAAAAA + AABvqBkBb6gZDm+oGUpvqBmtb6gZ7G+oGf5vqBn/b6gZ/2+oGf9vqBn/YaAc/0SPIvtBjSOlQY0jEQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBjSNmQY0j8UGNI/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GN + I/9CjiP/XJ0d/26oGf9vqBn/b6gZ/2+oGedtqBhY558/HNSgObzUoDn/1KA5/9SgOf/UoDn/1aE6/8+Y + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQY0jBEGNIzBBjSOAQY0j0kGN + I/1BjSP/QY0j/0GNI/9AjSP/QY0j/0WQIv9PlSD/W5wd/WaiG9htpxmMcqoYNnmuFwYAAAAAAAAAAAAA + AABEjyIKQI8hC9+hOwjUoDkX1KA5BQAAAAAAAAAAibgTAXOqGCRvqBlzaaQaxl+fHPdUmB//SZEh/0KO + I/9AjSP/QY0j/0GNI/9BjSP/QY0j/0GNI+dBjSOfQY0jSUGNIw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGNIw1BjSNBQY0jmEGNI+VBjSP/QY0j/0GNI/9BjSP/QI0j/0GN + I/9EjyL/TJMh/1eaHvZioBzHa6UadHKqGCKJtxMBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwdkHAHSr + GBRtpxlbZaIbtFqcHe1PlSD/RpAi/0GNI/9AjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/NBjSO1QY0jXEGN + IxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGN + IwBBjSMXQY0jVkGNI7RBjSPyQY0j/kGNI/9BjSP/QY0j/0GNI/9BjSP/Q44j/0mSIf9Slx/tXJ0dtWWi + G1hsphoTYaAcAQAAAAAAAAAAZ6MbAWymGgtnoxtCX58coFWZH+NMkyH9RI8i/0GNI/9AjSP/QY0j/0GN + I/9BjSP/QY0j/0GNI/lBjSPNQY0jckGNIyZBjSMFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBjSMFQY0jIkGNI3JBjSPOQY0j+EGN + I/9BjSP/QY0j/0GNI/9BjSP/QY0j/0KOI/9GkCL8TZQg5FSYH51Zmx49VpkfClKXHwdanB4sVpoehU+V + INhIkSL5Q44j/0GNI/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/tBjSPgQY0jj0GNIzVBjSMJAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABBjSMAQY0jCEGNIzRBjSOSQY0j30GNI/tBjSP/QY0j/0GNI/9BjSP/QY0j/0GN + I/9BjSP/Q44i+UeQItZIkSKBR5Ehc0iRIsVFjyL2Qo0j/0GNI/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GN + I/5BjSPqQY0jq0GNI0tBjSMPQY0jAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQY0jAUGN + Iw5BjSNNQY0jrEGNI+lBjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/9BjSP/QY0j/kGNI/hCjSP2QY0j/UGN + I/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/9BjSPxQY0jwEGNI2ZBjSMZQY0jAkGNIwAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGNIwFBjSMbQY0jaUGNI79BjSPzQY0j/0GN + I/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/9BjSP5QY0jz0GN + I39BjSMqQY0jAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABBjSMDQY0jLkGNI4JBjSPSQY0j/EGNI/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GN + I/9BjSP/QY0j/0GNI/9BjSP/QY0j4EGNI5dBjSNAQY0jCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQY0jDEGN + I0RBjSOZQY0j5EGNI/9BjSP/QY0j/0GNI/9BjSP/QY0j/0GNI/9BjSP/QY0j7kGNI6xBjSNWQY0jFQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGNIxhBjSNYQY0jskGNI/FBjSP/QY0j/0GN + I/9BjSP/QY0j90GNI8NBjSNqQY0jIkGNIwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAEGNIwRBjSMjQY0jbkGNI8pBjSP4QY0j+0GNI9dBjSOAQY0jL0GNIwgAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBjSMKQY0jO0GN + I6FBjSOwQY0jS0GNIw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA//////////w////////////////////8P///////////////////+B// + //////////////////gf///////////////////4D///////////////////8A////////////////// + /+AH///////////////////gB///////////////////wAP//////////////////8AD//////////// + ///////AA///////////////////wAP//////////////////4AB//////////////////+AAf////// + ////////////AAD//////////////////wAA//////////////////4AAH/////////////////+AAB/ + /////////////////AAAP/////////////////ABgA/////////////////gAYAH//////////////// + gAGAAf///////////////AADwAA///////////////gAA8AAH//////////////gAAfgAA////////// + ////wAAMGAAD/////////////wAAcAYAAP////////////4AAcADgAB////////////4AAOAAMAAH/// + ////////4AAOAAAwAAf//////////wAAMAAADAAB//////////4AAGAAAAYAAH/////////8AAHAAAAB + gAA/////////8AADAAAAAMAAH////////8AAHwAAAAAwAAf///////+AAH4AAAAADAAB///////+AAD+ + AAAAAAcAAP//////+AAD/gAAAAAHwAA//////8AAD/wAAAAABGAAB/////+AAB/8AAAAAAQYAAP///// + AABx/AAAAAAEBAAA/////gAA4fgAAAAABAIAAH////4AA4H4AAAAAAQBwAB////+AA4B+AAAAAAEAHAA + f////gA8AfAAAAAABAA4AH////4A8AHwAAAAAAQADgB////+AYAB4AAAAAAEAA+Af////gEAAeAAAAAA + BAAHgH////4AAAHgAAAAAAQAB4B////+AAAB4AAAAAAEAAOAf////gAAAeAAAAAABAADgH////4AAAHg + AAAAAAAAAYB////+AAAB4AAAAAAAAAGAf////gAAAcAAAAAYAAAAgH////4AAAHAAAAAGAAAAIB////+ + AAABgAAAABwAAAAAf////gEAAYAAAAAcAAAAAH////4BAAGAAAAAHAAAAAB////+AQABgAAAABwAAABA + f////gEAAQAAAAAcAAAAAH////4BAAEAAAAAHAAAAAB////+AQABAAAAABwAAAAAf////gEAAAAAAAAc + AAAAAH////4BAAAAAAAAHAAAAAB////+AQACAAAAABwAAAAAf////gEAAAAAAAAcAAAAAH////4DAAAA + AAAAHAAAAAB////+AwAAAAAAABwAAAAAf////gMAAAAAAAAeAAAAAH////4DAAAAAAAAHgAAAAB////+ + AwAAAAAAAB4AAACAf////gMAAAAAAAAeAAABgH////4DAAAAAAAAHgAAAYB////+AwAAAAAAAB4AAACA + f////gMAAAAAAAAeAAAAgH////4DAAAAAAAAHgAAAAB////+AwAAAQAAAB4AAABAf////gMAAAEAAAAe + AAAAQH////4DAAABAAAAHgAAAAB////+AwAAAQAAAB4AAAAAf////gMAAAEAAAAeAAAAAH////4DAAAB + AAAAHgAAAAB////+AwAAAwAAAB8AAAAAf////gMAAAcAAAAfAAAAAH////4DAAAHAAAAHwAAAAB////+ + AwAABwAAAB8AEABAf////gMAAA8AAAAfABAAQH////4DAAAPAAAAHwAQAEB////+AwAADwAAAB8AEABA + f////gMAAA8AAAAfABAAQH////4DAAAfAAAAHwAQAMB////+AwAAHwAAAB8AEADAf////gMAAD8AAAAf + ABABwH////4DAAA/AAAAHwAQAcB////+AwAAPwAAAB8AEAPAf////gHAAH8AAAAfABAHgH////4AYAB/ + AAAAHwAQBwB////+ADAAfwAAAB+AEAwAf////gAOAP8AAAAfgBA4AH////4AA4D/AAAAH4Aw4AB////+ + AAHg/wAAAB+AN4AAf////wAAcP8AAAAfgD4AAP/////AAA//AAAAH4AwAAH/////8AAH/wAAAB+AIAAH + //////gAAf8AAAAfgMAAD//////8AAD/AAAAH4EAAD///////4AAPgAAAB+OAAD////////gAB4AAAAf + +AAD////////+AAHAAAAH+AAD/////////wAAcAAAB+AAD//////////AAAgAAAeAAB//////////4AA + GAAAGAAB///////////gAAwAADAAA///////////8AADAADAAAf///////////4AAMADgAA///////// + ////gABwDgAA/////////////8AAHBgAA//////////////wAAfgAA//////////////+AABgAAf//// + //////////4AAAAAf///////////////AAAAAP///////////////8AAAAH////////////////wAAAP + /////////////////AAAP/////////////////8AAP//////////////////wAH///////////////// + /+AH///////////////////4H/////////8= + + + \ No newline at end of file diff --git a/Tongue extraction/Program.cs b/Tongue extraction/Program.cs new file mode 100644 index 0000000..c2f1ac0 --- /dev/null +++ b/Tongue extraction/Program.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Text; + +namespace Tongue_extraction +{ + static class Program + { + [STAThread] + static void Main() + { + try + { + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); + Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); + AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + catch (Exception ex) + { + string str = GetExceptionMsg(ex, string.Empty); + MessageBox.Show(str, "System Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + + static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) + { + string str = GetExceptionMsg(e.Exception, e.ToString()); + MessageBox.Show(str, "System Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + //LogManager.WriteLog(str); + } + + static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + string str = GetExceptionMsg(e.ExceptionObject as Exception, e.ToString()); + MessageBox.Show(str, "System Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + //LogManager.WriteLog(str); + } + + static string GetExceptionMsg(Exception ex, string backStr) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("****************************Exception Text****************************"); + sb.AppendLine("【Time】:" + DateTime.Now.ToString()); + if (ex != null) + { + sb.AppendLine("【Exception Type】:" + ex.GetType().Name); + sb.AppendLine("【Exception Information】:" + ex.Message); + sb.AppendLine("【Stack Call】:" + ex.StackTrace); + } + else + { + sb.AppendLine("【Unhandled Exception】:" + backStr); + } + sb.AppendLine("***************************************************************"); + return sb.ToString(); + } + } +} diff --git a/Tongue extraction/Properties/AssemblyInfo.cs b/Tongue extraction/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..604a626 --- /dev/null +++ b/Tongue extraction/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 +// アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更してください。 +[assembly: AssemblyTitle("Tongue extraction")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tongue extraction")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから +// 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります +[assembly: Guid("d382f9e7-a41d-4d82-a59b-cf4095134d6b")] + +// アセンブリのバージョン情報は次の 4 つの値で構成されています: +// +// メジャー バージョン +// マイナー バージョン +// ビルド番号 +// Revision +// +// すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を +// 既定値にすることができます: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tongue extraction/Properties/Resources.Designer.cs b/Tongue extraction/Properties/Resources.Designer.cs new file mode 100644 index 0000000..64d0936 --- /dev/null +++ b/Tongue extraction/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// このコードはツールによって生成されました。 +// ランタイム バージョン:4.0.30319.42000 +// +// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 +// コードが再生成されるときに損失したりします。 +// +//------------------------------------------------------------------------------ + +namespace Tongue_extraction.Properties { + using System; + + + /// + /// ローカライズされた文字列などを検索するための、厳密に型指定されたリソース クラスです。 + /// + // このクラスは StronglyTypedResourceBuilder クラスが ResGen + // または Visual Studio のようなツールを使用して自動生成されました。 + // メンバーを追加または削除するには、.ResX ファイルを編集して、/str オプションと共に + // ResGen を実行し直すか、または VS プロジェクトをビルドし直します。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// このクラスで使用されているキャッシュされた ResourceManager インスタンスを返します。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Tongue_extraction.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// すべてについて、現在のスレッドの CurrentUICulture プロパティをオーバーライドします + /// 現在のスレッドの CurrentUICulture プロパティをオーバーライドします。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Tongue extraction/Properties/Resources.resx b/Tongue extraction/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Tongue extraction/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Tongue extraction/Properties/Settings.Designer.cs b/Tongue extraction/Properties/Settings.Designer.cs new file mode 100644 index 0000000..15b8fb1 --- /dev/null +++ b/Tongue extraction/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Tongue_extraction.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Tongue extraction/Properties/Settings.settings b/Tongue extraction/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/Tongue extraction/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Tongue extraction/Properties/Settings1.Designer.cs b/Tongue extraction/Properties/Settings1.Designer.cs new file mode 100644 index 0000000..2ae6ade --- /dev/null +++ b/Tongue extraction/Properties/Settings1.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// このコードはツールによって生成されました。 +// ランタイム バージョン:4.0.30319.42000 +// +// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 +// コードが再生成されるときに損失したりします。 +// +//------------------------------------------------------------------------------ + +namespace Tongue_extraction.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Tongue extraction/Properties/Settings2.Designer.cs b/Tongue extraction/Properties/Settings2.Designer.cs new file mode 100644 index 0000000..8198153 --- /dev/null +++ b/Tongue extraction/Properties/Settings2.Designer.cs @@ -0,0 +1,24 @@ +//------------------------------------------------------------------------------ +// +// このコードはツールによって生成されました。 +// ランタイム バージョン:4.0.30319.42000 +// +// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 +// コードが再生成されるときに損失したりします。 +// +//------------------------------------------------------------------------------ + +namespace Tongue_extraction.Properties { + + + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Tongue extraction/models/Detection_Normal.pb b/Tongue extraction/models/Detection_Normal.pb new file mode 100644 index 0000000..9598725 --- /dev/null +++ b/Tongue extraction/models/Detection_Normal.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a2a839d71eb715fd2802f51d6e485a5d19c2b9953a3ae0091a30ff45e22625c +size 12945714 diff --git a/Tongue extraction/models/ResUnet_0.9518.pb b/Tongue extraction/models/ResUnet_0.9518.pb new file mode 100644 index 0000000..c120e94 --- /dev/null +++ b/Tongue extraction/models/ResUnet_0.9518.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4314a7f31cb0a4d47fb7508313806df0c9e597f09b474b3fa425ecc0726109dd +size 71189534 diff --git a/Tongue extraction/packages.config b/Tongue extraction/packages.config new file mode 100644 index 0000000..5cc0f81 --- /dev/null +++ b/Tongue extraction/packages.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file