diff --git a/AutoFocus.cs b/AutoFocus.cs index 721b058..04b19a0 100644 --- a/AutoFocus.cs +++ b/AutoFocus.cs @@ -9,6 +9,7 @@ This Source Code Form is subject to the terms of the Mozilla Public #endregion "copyright" +using Accord.Imaging.Filters; using Dasync.Collections; using EDSDKLib; using LensAF.Dockable; @@ -19,17 +20,24 @@ This Source Code Form is subject to the terms of the Mozilla Public using NINA.Core.Model; using NINA.Core.Model.Equipment; using NINA.Core.Utility; +using NINA.Core.Utility.Notification; +using NINA.Equipment.Equipment.MyCamera; using NINA.Equipment.Interfaces.Mediator; using NINA.Equipment.Model; using NINA.Image.ImageAnalysis; using NINA.Image.Interfaces; using NINA.Profile.Interfaces; +using NINA.WPF.Base.Mediator; +using NINA.WPF.Base.ViewModel.Equipment.Camera; using OxyPlot; using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Threading; using System.Threading.Tasks; +using System.Windows.Media; +using System.Windows.Media.Imaging; namespace LensAF { @@ -41,6 +49,8 @@ public class AutoFocus public static DateTime LastAF; public readonly IProfileService Profile; + private AutoFocusLogic Method; + public AutoFocus(CancellationToken token, IProgress progress, IProfileService profile) { Token = token; @@ -51,16 +61,25 @@ public AutoFocus(CancellationToken token, IProgress progress, /// /// Runs an automated focusing session /// - /// reference to the canon camera /// Instance of ICameraMediator /// Instance of IImagingMediator /// Instance of the AutoFocusSettings class /// AutoFocusResult containing all important informations - public async Task RunAF(IntPtr canon, ICameraMediator camera, IImagingMediator imaging, AutoFocusSettings settings) + public async Task RunAF(ICameraMediator camera, IImagingMediator imaging, AutoFocusSettings settings) { + IntPtr canon = Utility.GetCamera(camera); + + if (canon == IntPtr.Zero) + { + Notification.ShowError("Could not run AF: No canon camera connected"); + return new AutoFocusResult(false, new List(), TimeSpan.Zero, DateTime.Now, StepSizeToString()); + } + + DateTime start = DateTime.Now; bool Focused = false; int iteration = 0; + Method = GetSelectedLogic(); LensAFVM.Instance.AutoFocusIsRunning = true; List FocusPoints = new List(); try @@ -113,10 +132,21 @@ await liveViewEnumerable.ForEachAsync(async _ => 1), new PrepareImageParameters(true), Token, Progress); - StarDetectionResult detection = await PrepareImage(data); - FocusPoints.Add(new FocusPoint(detection)); - AddToPlot(detection, iteration); + if (Method == AutoFocusLogic.STARHFR) + { + StarDetectionResult detection = await PrepareImageForStarHFR(data); + FocusPoints.Add(new FocusPoint(detection)); + + AddToPlot(detection.AverageHFR, iteration); + } + else + { + ContrastDetectionResult detection = PrepareImageForContrast(data); + FocusPoints.Add(new FocusPoint(detection)); + + AddToPlot(detection.AverageContrast, iteration); + } } if (Token.IsCancellationRequested) @@ -135,7 +165,7 @@ await liveViewEnumerable.ForEachAsync(async _ => LastAF = DateTime.Now; AutoFocusResult res = new AutoFocusResult(Focused, FocusPoints, LastAF - start, LastAF, StepSizeToString()); - CameraInfo info = new CameraInfo(canon); + Util.CameraInfo info = new Util.CameraInfo(canon); GenerateLog(settings, res, info); if (LensAFVM.Instance != null) { @@ -144,6 +174,15 @@ await liveViewEnumerable.ForEachAsync(async _ => return res; } + private AutoFocusLogic GetSelectedLogic() + { + if (Settings.Default.AutoFocusLogic == 0) + { + return AutoFocusLogic.STARHFR; + } + return AutoFocusLogic.CONTRAST; + } + private string StepSizeToString() { if (Settings.Default.UseMixedStepSize) @@ -164,31 +203,46 @@ private string StepSizeToString() private int DetermineFinalFocusPoint(List points, int iterations) { - List hfrs = new List(); - List temp = new List(); - foreach (FocusPoint point in points) - { - hfrs.Add(point.HFR); - temp.Add(point.HFR); - } - hfrs.Sort(); int iteration; - int count = 0; + if (Method == AutoFocusLogic.STARHFR) + { + List hfrs = new List(); + List temp = new List(); + foreach (FocusPoint point in points) + { + hfrs.Add(point.HFR); + temp.Add(point.HFR); + } + hfrs.Sort(); + int count = 0; - do + do + { + iteration = temp.IndexOf(hfrs[count]); + count++; + } while (hfrs[count] == 0); + } + else { - iteration = temp.IndexOf(hfrs[count]); - count++; - } while (hfrs[count] == 0); - + List contrasts = new List(); + List temp = new List(); + foreach (FocusPoint point in points) + { + contrasts.Add(point.Contrast); + temp.Add(point.Contrast); + } + contrasts.Sort(); + iteration = temp.IndexOf(contrasts[contrasts.Count - 1]); + } + return iterations - iteration; } - private void AddToPlot(StarDetectionResult detection, int iteration) + private void AddToPlot(double detection, int iteration) { if (LensAFVM.Instance != null) { - LensAFVM.Instance.AddToPlot(new DataPoint(iteration, detection.AverageHFR)); + LensAFVM.Instance.AddToPlot(new DataPoint(iteration, detection)); } } @@ -416,22 +470,67 @@ private void DriveFocus(IntPtr cam, FocusDirection direction) EDSDK.EdsSendCommand(cam, EDSDK.CameraCommand_DriveLensEvf, (int)EDSDK.EvfDriveLens_Far1); } } + else + { + for (int i = 0; i < Settings.Default.StepSizeBig; i++) + { + if (direction == FocusDirection.Far) + { + EDSDK.EdsSendCommand(cam, EDSDK.CameraCommand_DriveLensEvf, (int)EDSDK.EvfDriveLens_Far2); + } + else + { + EDSDK.EdsSendCommand(cam, EDSDK.CameraCommand_DriveLensEvf, (int)EDSDK.EvfDriveLens_Near2); + } + Thread.Sleep(200); + } + if (Settings.Default.StepSizeLogic == 1 && Settings.Default.StepSizeBig > 0) + { + for (int i = 0; i < Settings.Default.StepSizeSmall; i++) + { + if (direction == FocusDirection.Far) + { + EDSDK.EdsSendCommand(cam, EDSDK.CameraCommand_DriveLensEvf, (int)EDSDK.EvfDriveLens_Near1); + } + else + { + EDSDK.EdsSendCommand(cam, EDSDK.CameraCommand_DriveLensEvf, (int)EDSDK.EvfDriveLens_Far1); + } + Thread.Sleep(200); + } + } + else + { + for (int i = 0; i < Settings.Default.StepSizeSmall; i++) + { + if (direction == FocusDirection.Far) + { + EDSDK.EdsSendCommand(cam, EDSDK.CameraCommand_DriveLensEvf, (int)EDSDK.EvfDriveLens_Far1); + } + else + { + EDSDK.EdsSendCommand(cam, EDSDK.CameraCommand_DriveLensEvf, (int)EDSDK.EvfDriveLens_Near1); + } + Thread.Sleep(200); + } + } + } Thread.Sleep(1000); // Wait for focus the finish rotating } - private async Task PrepareImage(IRenderedImage exposure) + private async Task PrepareImageForStarHFR(IRenderedImage exposure) { IImageData imageData = exposure.RawImageData; - System.Windows.Media.PixelFormat pixelFormat; + PixelFormat pixelFormat; if (imageData.Properties.IsBayered && Profile.ActiveProfile.ImageSettings.DebayerImage) { - pixelFormat = System.Windows.Media.PixelFormats.Rgb48; + pixelFormat = PixelFormats.Rgb48; } else { - pixelFormat = System.Windows.Media.PixelFormats.Gray16; + pixelFormat = PixelFormats.Gray16; } StarDetectionParams analysisParams = new StarDetectionParams @@ -440,14 +539,34 @@ private async Task PrepareImage(IRenderedImage exposure) NoiseReduction = NoiseReductionEnum.None, IsAutoFocus = true }; + return await new StarDetection().Detect(exposure, pixelFormat, analysisParams, Progress, Token); + } - StarDetectionResult result = await new StarDetection().Detect(exposure, pixelFormat, analysisParams, Progress, Token); - return result; + private ContrastDetectionResult PrepareImageForContrast(IRenderedImage exposure) + { + BitmapSource image = exposure.Image; + if (exposure.RawImageData.Properties.IsBayered && Profile.ActiveProfile.ImageSettings.DebayerImage) + { + using (var source = ImageUtility.BitmapFromSource(exposure.OriginalImage, System.Drawing.Imaging.PixelFormat.Format48bppRgb)) + { + using (var img = new Grayscale(0.2125, 0.7154, 0.0721).Apply(source)) + { + image = ImageUtility.ConvertBitmap(img, PixelFormats.Gray16); + image.Freeze(); + } + } + } + ContrastDetectionParams analysisParams = new ContrastDetectionParams() + { + Sensitivity = StarSensitivityEnum.Normal, + NoiseReduction = NoiseReductionEnum.None + }; + return new Util.ContrastDetection().Measure(image, exposure, analysisParams, Progress, Token); } /// /// This Calibrates the lens to the complete right (infinite) - /// Camera has to be in Live View!!! + /// Camera has to be in Live View! /// /// IntPtr for the camera /// @@ -462,8 +581,9 @@ private void CalibrateLens(IntPtr ptr) } } - public static void GenerateLog(AutoFocusSettings settings, AutoFocusResult result, CameraInfo info) + public void GenerateLog(AutoFocusSettings settings, AutoFocusResult result, Util.CameraInfo info) { + settings.AutoFocusMethod = GetSelectedLogic(); AutoFocusReport report = new AutoFocusReport() { Settings = settings, diff --git a/Dockable/FocusControl.xaml b/Dockable/FocusControl.xaml new file mode 100644 index 0000000..e7f66b2 --- /dev/null +++ b/Dockable/FocusControl.xaml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dockable/FocusControl.xaml.cs b/Dockable/FocusControl.xaml.cs new file mode 100644 index 0000000..b195d11 --- /dev/null +++ b/Dockable/FocusControl.xaml.cs @@ -0,0 +1,25 @@ +#region "copyright" + +/* + Copyright © 2022 Christian Palm (christian@palm-family.de) + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#endregion "copyright" + +using System.ComponentModel.Composition; +using System.Windows; + +namespace LensAF.Dockable +{ + [Export(typeof(ResourceDictionary))] + partial class FocusControl : ResourceDictionary + { + public FocusControl() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Dockable/FocusControlVM.cs b/Dockable/FocusControlVM.cs new file mode 100644 index 0000000..0b21e36 --- /dev/null +++ b/Dockable/FocusControlVM.cs @@ -0,0 +1,159 @@ +#region "copyright" + +/* + Copyright © 2022 Christian Palm (christian@palm-family.de) + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#endregion "copyright" + +using Dasync.Collections; +using EDSDKLib; +using LensAF.Properties; +using LensAF.Util; +using NINA.Core.Utility; +using NINA.Core.Utility.Notification; +using NINA.Equipment.Interfaces.Mediator; +using NINA.Equipment.Interfaces.ViewModel; +using NINA.Image.Interfaces; +using NINA.Profile.Interfaces; +using NINA.WPF.Base.Mediator; +using NINA.WPF.Base.ViewModel; +using NINA.WPF.Base.ViewModel.Equipment.Camera; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Threading; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace LensAF.Dockable +{ + [Export(typeof(IDockableVM))] + public class FocusControlVM : DockableVM + { + private List Issues; + private ICameraMediator Camera; + private bool _manualFocusControl = false; + private CancellationTokenSource FocusControlToken; + + public AsyncCommand StartFocusControl { get; set; } + public RelayCommand StopFocusControl { get; set; } + public RelayCommand MoveLeft { get; set; } + public RelayCommand MoveRight { get; set; } + + private BitmapSource image; + public BitmapSource Image + { + get { return image; } + set + { + image = value; + RaisePropertyChanged(); + } + } + + public bool ManualFocusControl + { + get { return _manualFocusControl; } + set + { + _manualFocusControl = value; + RaisePropertyChanged(); + } + } + + [ImportingConstructor] + public FocusControlVM(IProfileService profileService, ICameraMediator cam, IImagingMediator imaging) : base(profileService) + { + Camera = cam; + + Title = "Manual Focus Control"; + ResourceDictionary dict = new ResourceDictionary(); + dict.Source = new Uri("/LensAF;component/Options.xaml", UriKind.RelativeOrAbsolute); + ImageGeometry = (GeometryGroup)dict["PluginSVG"]; + ImageGeometry.Freeze(); + + Issues = new List(); + + StartFocusControl = new AsyncCommand(async _ => + { + if (!Validate()) + { + foreach (string issue in Issues) + { + Notification.ShowError($"Can't start AutoFocus: {issue}"); + Logger.Error($"Can't start AutoFocus: {issue}"); + } + return false; + } + FocusControlToken = new CancellationTokenSource(); + IAsyncEnumerable LiveView = Camera.LiveView(FocusControlToken.Token); + ManualFocusControl = true; + + await LiveView.ForEachAsync(async exposure => + { + IImageData data = await exposure.ToImageData(); + if (Settings.Default.PrepareImage) + { + IRenderedImage img = data.RenderImage(); + Image = (await img.Stretch(Settings.Default.Stretchfactor, Settings.Default.Blackclipping, true)).Image; + } + else + { + Image = data.RenderBitmapSource(); + } + + }); + return true; + }); + + StopFocusControl = new RelayCommand(_ => + { + if (ManualFocusControl) + { + FocusControlToken.Cancel(); + ManualFocusControl = false; + } + }); + + MoveRight = new RelayCommand(_ => + { + EDSDK.EdsSendCommand(Utility.GetCamera(Camera), EDSDK.CameraCommand_DriveLensEvf, (int)EDSDK.EvfDriveLens_Far1); + }); + + MoveLeft = new RelayCommand(_ => + { + EDSDK.EdsSendCommand(Utility.GetCamera(Camera), EDSDK.CameraCommand_DriveLensEvf, (int)EDSDK.EvfDriveLens_Near1); + }); + } + + private bool Validate() + { + Issues.Clear(); + bool cameraConnected = Camera.GetInfo().Connected; + + if (!cameraConnected) + { + Issues.Add("Camera not connected"); + } + + if (LensAFVM.Instance.AutoFocusIsRunning) + { + Issues.Add("Can't enable focus control when AF is running"); + } + + CameraVM cameraVM = (CameraVM)Utility.GetInstanceField((CameraMediator)Camera, "handler"); + + if (cameraVM.CameraChooserVM.SelectedDevice.Category != "Canon") + { + Issues.Add("No canon camera connected"); + } + + return !(Issues.Count > 0); + } + } +} diff --git a/Dockable/LensAFDockable.xaml b/Dockable/LensAFDockable.xaml index 59f2844..20b4c28 100644 --- a/Dockable/LensAFDockable.xaml +++ b/Dockable/LensAFDockable.xaml @@ -12,22 +12,6 @@ xmlns:oxy="http://oxyplot.org/wpf" xmlns:wpfutil="clr-namespace:NINA.WPF.Base.Utility;assembly=NINA.WPF.Base"> - - - - - - - - - - - - - - - - @@ -84,6 +68,13 @@ DataFieldY="HFR" ItemsSource="{Binding PlotFocusPoints}" Color="{Binding Path=Color, Source={StaticResource ButtonBackgroundBrush}}" /> + @@ -93,23 +84,6 @@ - - - - - - - + @@ -60,23 +31,12 @@ - - - - - + + @@ -87,24 +47,13 @@ - - - - + diff --git a/LensAF.cs b/LensAF.cs index 5fab302..cb30a63 100644 --- a/LensAF.cs +++ b/LensAF.cs @@ -11,17 +11,22 @@ This Source Code Form is subject to the terms of the Mozilla Public using LensAF.Dockable; using LensAF.Properties; -using NINA.Core; +using LensAF.Util; +using Newtonsoft.Json; using NINA.Core.Utility; using NINA.Core.Utility.Notification; using NINA.Plugin; using NINA.Plugin.Interfaces; +using System; +using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.Composition; +using System.Numerics; namespace LensAF { [Export(typeof(IPluginManifest))] - public class LensAF : PluginBase + public class LensAF : PluginBase, INotifyPropertyChanged { [ImportingConstructor] public LensAF() @@ -37,8 +42,35 @@ public LensAF() LensAFVM.Instance.AutoFocusIsRunning = false; Notification.ShowInformation($"Auto focus is running: {LensAFVM.Instance.AutoFocusIsRunning}"); }); + if (string.IsNullOrWhiteSpace(Settings.Default.AutoFocusProfiles)) + { + Profiles = new List(); + Profiles.Add(new AutoFocusProfile(0, 2)); + Profiles.Add(new AutoFocusProfile(1, 0)); + SaveProfile(); + } + else + { + Profiles = JsonConvert.DeserializeObject>(Settings.Default.AutoFocusProfiles); + } } + private Dictionary StepSizes = new Dictionary() + { + { 0, new Vector2(0, 1) }, + { 1, new Vector2(0, 3) }, + { 2, new Vector2(0, 5) }, + { 3, new Vector2(1, -5) }, + { 4, new Vector2(1, -3) }, + { 5, new Vector2(1, 0) }, + { 6, new Vector2(1, 3) }, + { 7, new Vector2(1, 5) } + }; + + public event PropertyChangedEventHandler PropertyChanged; + + public static List Profiles; + public int SelectedIndex { get @@ -49,6 +81,7 @@ public int SelectedIndex { Settings.Default.SelectedStepSize = value; CoreUtil.SaveSettings(Settings.Default); + SetStepSize(); } } @@ -75,6 +108,8 @@ public int StepSizeBig { Settings.Default.StepSizeBig = value; CoreUtil.SaveSettings(Settings.Default); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StepSizeBig))); + SaveProfile(); } } @@ -88,6 +123,8 @@ public int StepSizeSmall { Settings.Default.StepSizeSmall = value; CoreUtil.SaveSettings(Settings.Default); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StepSizeSmall))); + SaveProfile(); } } @@ -101,6 +138,7 @@ public bool UseMixedStepSize { Settings.Default.UseMixedStepSize = value; CoreUtil.SaveSettings(Settings.Default); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UseMixedStepSize))); } } @@ -114,9 +152,126 @@ public int StepSizeLogic { Settings.Default.StepSizeLogic = value; CoreUtil.SaveSettings(Settings.Default); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StepSizeLogic))); + } + } + + public int AutoFocusLogic + { + get + { + return Settings.Default.AutoFocusLogic; + } + set + { + Settings.Default.AutoFocusLogic = value; + CoreUtil.SaveSettings(Settings.Default); + } + } + + public bool ButtonEnabled + { + get + { + return Settings.Default.ButtonEnabled; + } + set + { + Settings.Default.ButtonEnabled = value; + CoreUtil.SaveSettings(Settings.Default); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ButtonEnabled))); + } + } + + public bool PrepareImage + { + get + { + return Settings.Default.PrepareImage; + } + set + { + Settings.Default.PrepareImage = value; + CoreUtil.SaveSettings(Settings.Default); + } + } + + public double Stretchfactor + { + get + { + return Settings.Default.Stretchfactor; + } + set + { + Settings.Default.Stretchfactor = value; + CoreUtil.SaveSettings(Settings.Default); + } + } + + public double Blackclipping + { + get + { + return Settings.Default.Blackclipping; + } + set + { + Settings.Default.Blackclipping = value; + CoreUtil.SaveSettings(Settings.Default); } } public RelayCommand ResetAF { get; set; } + + private void SetStepSize() + { + Vector2 stepSize; + if (Settings.Default.SelectedStepSize < 8) + { + stepSize = StepSizes[Settings.Default.SelectedStepSize]; + ButtonEnabled = true; + } + else if (Settings.Default.SelectedStepSize == 8) + { + stepSize = new Vector2(Profiles[0].StepsBig, Profiles[0].StepsSmall); + UseMixedStepSize = true; + ButtonEnabled = false; + } + else + { + stepSize = new Vector2(Profiles[1].StepsBig, Profiles[1].StepsSmall); + UseMixedStepSize = true; + ButtonEnabled = false; + } + + if (stepSize.Y < 0) + { + StepSizeLogic = 1; + } + else + { + StepSizeLogic = 0; + } + stepSize.Y = Math.Abs(stepSize.Y); + StepSizeBig = (int)stepSize.X; + StepSizeSmall = (int)stepSize.Y; + } + + private void SaveProfile() + { + if (Settings.Default.SelectedStepSize == 8) + { + Profiles[0].StepsBig = StepSizeBig; + Profiles[0].StepsSmall = StepSizeSmall; + } + else if (Settings.Default.SelectedStepSize == 9) + { + Profiles[1].StepsBig = StepSizeBig; + Profiles[1].StepsSmall = StepSizeSmall; + } + Settings.Default.AutoFocusProfiles = JsonConvert.SerializeObject(Profiles); + CoreUtil.SaveSettings(Settings.Default); + } } } diff --git a/LensAF.csproj b/LensAF.csproj index a4097fd..fe8695e 100644 --- a/LensAF.csproj +++ b/LensAF.csproj @@ -114,40 +114,40 @@ ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - ..\packages\NINA.Equipment.2.0.0.2021-beta\lib\net48\nikoncswrapper.dll + ..\packages\NINA.Equipment.2.0.0.2036-beta\lib\net48\nikoncswrapper.dll - - ..\packages\NINA.Astrometry.2.0.0.2021-beta\lib\net48\NINA.Astrometry.dll + + ..\packages\NINA.Astrometry.2.0.0.2036-beta\lib\net48\NINA.Astrometry.dll - - ..\packages\NINA.Core.2.0.0.2021-beta\lib\net48\NINA.Core.dll + + ..\packages\NINA.Core.2.0.0.2036-beta\lib\net48\NINA.Core.dll - - ..\packages\NINA.Equipment.2.0.0.2021-beta\lib\net48\NINA.Equipment.dll + + ..\packages\NINA.Equipment.2.0.0.2036-beta\lib\net48\NINA.Equipment.dll - - ..\packages\NINA.Image.2.0.0.2021-beta\lib\net48\NINA.Image.dll + + ..\packages\NINA.Image.2.0.0.2036-beta\lib\net48\NINA.Image.dll - - ..\packages\NINA.MGEN.2.0.0.2021-beta\lib\net48\NINA.MGEN.dll + + ..\packages\NINA.MGEN.2.0.0.2036-beta\lib\net48\NINA.MGEN.dll - - ..\packages\NINA.PlateSolving.2.0.0.2021-beta\lib\net48\NINA.PlateSolving.dll + + ..\packages\NINA.PlateSolving.2.0.0.2036-beta\lib\net48\NINA.PlateSolving.dll - - ..\packages\NINA.Plugin.2.0.0.2021-beta\lib\net48\NINA.Plugin.dll + + ..\packages\NINA.Plugin.2.0.0.2036-beta\lib\net48\NINA.Plugin.dll - - ..\packages\NINA.Profile.2.0.0.2021-beta\lib\net48\NINA.Profile.dll + + ..\packages\NINA.Profile.2.0.0.2036-beta\lib\net48\NINA.Profile.dll - - ..\packages\NINA.Sequencer.2.0.0.2021-beta\lib\net48\NINA.Sequencer.dll + + ..\packages\NINA.Sequencer.2.0.0.2036-beta\lib\net48\NINA.Sequencer.dll - - ..\packages\NINA.WPF.Base.2.0.0.2021-beta\lib\net48\NINA.WPF.Base.dll + + ..\packages\NINA.WPF.Base.2.0.0.2036-beta\lib\net48\NINA.WPF.Base.dll - - ..\packages\NINACustomControlLibrary.2.0.0.2021-beta\lib\net48\NINACustomControlLibrary.dll + + ..\packages\NINACustomControlLibrary.2.0.0.2036-beta\lib\net48\NINACustomControlLibrary.dll ..\packages\Nito.AsyncEx.Context.5.1.2\lib\net461\Nito.AsyncEx.Context.dll @@ -225,6 +225,7 @@ ..\packages\System.Data.SQLite.Linq.1.0.115.0\lib\net46\System.Data.SQLite.Linq.dll + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll @@ -285,6 +286,10 @@ + + FocusControl.xaml + + LensAFDockable.xaml @@ -293,7 +298,9 @@ + + Options.xaml @@ -318,6 +325,10 @@ + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/Options.xaml b/Options.xaml index 5232dff..02e153e 100644 --- a/Options.xaml +++ b/Options.xaml @@ -18,6 +18,18 @@ + + + + + + + + @@ -29,6 +41,8 @@ + + @@ -37,7 +51,8 @@ + IsChecked="{Binding UseMixedStepSize}" + IsEnabled="{Binding ButtonEnabled}" /> + + + + + + + + + diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 06cdbd6..66b5581 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -6,8 +6,8 @@ // [MANDATORY] The assembly versioning //Should be incremented for each new release build of a plugin -[assembly: AssemblyVersion("1.3.0.0")] -[assembly: AssemblyFileVersion("1.3.0.0")] +[assembly: AssemblyVersion("1.5.0.0")] +[assembly: AssemblyFileVersion("1.5.0.0")] // [MANDATORY] The name of your plugin [assembly: AssemblyTitle("Lens AF")] @@ -21,7 +21,7 @@ [assembly: AssemblyCopyright("Copyright © 2022")] // The minimum Version of N.I.N.A. that this plugin is compatible with -[assembly: AssemblyMetadata("MinimumApplicationVersion", "2.0.0.2021")] +[assembly: AssemblyMetadata("MinimumApplicationVersion", "2.0.0.2036")] // The license your plugin code is using [assembly: AssemblyMetadata("License", "MPL-2.0")] @@ -36,7 +36,7 @@ [assembly: AssemblyMetadata("Tags", "AF,Sequencer,Canon")] //[Optional] An in-depth description of your plugin -[assembly: AssemblyMetadata("LongDescription", @"This Plugin provides an instruction to run an AF with your camera lens attached to a Canon Camera +[assembly: AssemblyMetadata("LongDescription", @"This Plugin provides an instruction and triggers for the Advanced to run an AF with your camera lens attached to a Canon Camera ## Important Note! **Test the plugin before you use it in your imaging runs, because this plugin may not work for everyone! This plugin doesn't work with ASCOM.DSLR unfortuantely!** diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs index a114f3a..5ce25ee 100644 --- a/Properties/Settings.Designer.cs +++ b/Properties/Settings.Designer.cs @@ -106,5 +106,77 @@ public int StepSizeLogic { this["StepSizeLogic"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public int AutoFocusLogic { + get { + return ((int)(this["AutoFocusLogic"])); + } + set { + this["AutoFocusLogic"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string AutoFocusProfiles { + get { + return ((string)(this["AutoFocusProfiles"])); + } + set { + this["AutoFocusProfiles"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ButtonEnabled { + get { + return ((bool)(this["ButtonEnabled"])); + } + set { + this["ButtonEnabled"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool PrepareImage { + get { + return ((bool)(this["PrepareImage"])); + } + set { + this["PrepareImage"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0.12")] + public double Stretchfactor { + get { + return ((double)(this["Stretchfactor"])); + } + set { + this["Stretchfactor"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-2.8")] + public double Blackclipping { + get { + return ((double)(this["Blackclipping"])); + } + set { + this["Blackclipping"] = value; + } + } } } diff --git a/Properties/Settings.settings b/Properties/Settings.settings index ec1b6c7..e9c3567 100644 --- a/Properties/Settings.settings +++ b/Properties/Settings.settings @@ -23,5 +23,23 @@ 0 + + 0 + + + + + + True + + + False + + + 0.12 + + + -2.8 + \ No newline at end of file diff --git a/Util/AutoFocusHelper.cs b/Util/AutoFocusHelper.cs index 684491d..0c763e2 100644 --- a/Util/AutoFocusHelper.cs +++ b/Util/AutoFocusHelper.cs @@ -9,6 +9,8 @@ This Source Code Form is subject to the terms of the Mozilla Public #endregion "copyright" +using LensAF.Properties; +using NINA.Core.Utility; using NINA.Image.ImageAnalysis; using System; using System.Collections.Generic; @@ -17,10 +19,11 @@ namespace LensAF.Util { public class AutoFocusSettings { - public double ExposureTime = 5; + public double ExposureTime = Settings.Default.ExposureTime; public double BlackClipping = -2.8; public double StretchFactor = 0.15; public int Iterations = 9; + public AutoFocusLogic AutoFocusMethod = AutoFocusLogic.STARHFR; } public class AutoFocusResult @@ -43,14 +46,20 @@ public AutoFocusResult(bool successfull, List focusPoints, TimeSpan public class FocusPoint { - public int Stars { get; set; } - public double HFR { get; set; } + public int Stars { get; set; } = 0; + public double HFR { get; set; } = double.NaN; + public double Contrast { get; set; } = double.NaN; public FocusPoint(StarDetectionResult analysis) { Stars = analysis.DetectedStars; HFR = analysis.AverageHFR; } + + public FocusPoint(ContrastDetectionResult detection) + { + Contrast = detection.AverageContrast; + } } public class AutoFocusReport @@ -65,4 +74,10 @@ public enum FocusDirection Far, Near } + + public enum AutoFocusLogic + { + STARHFR, + CONTRAST + } } diff --git a/Util/AutoFocusProfile.cs b/Util/AutoFocusProfile.cs new file mode 100644 index 0000000..607d63a --- /dev/null +++ b/Util/AutoFocusProfile.cs @@ -0,0 +1,30 @@ +#region "copyright" + +/* + Copyright © 2022 Christian Palm (christian@palm-family.de) + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#endregion "copyright" + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LensAF.Util +{ + public class AutoFocusProfile + { + public int StepsBig; + public int StepsSmall; + public AutoFocusProfile(int stepsBig, int stepsSmall) + { + StepsBig = stepsBig; + StepsSmall = stepsSmall; + } + } +} diff --git a/Util/ContrastDetection.cs b/Util/ContrastDetection.cs new file mode 100644 index 0000000..22e59bb --- /dev/null +++ b/Util/ContrastDetection.cs @@ -0,0 +1,208 @@ +#region "copyright" + +/* + Copyright © 2021 Christian Palm (christian@palm-family.de) + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#endregion "copyright" + +using Accord.Imaging.Filters; +using NINA.Core.Enum; +using NINA.Core.Model; +using NINA.Core.Utility; +using NINA.Image.ImageAnalysis; +using NINA.Image.ImageData; +using NINA.Image.Interfaces; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Threading; +using System.Windows.Media.Imaging; + +namespace LensAF.Util +{ + public class ContrastDetection + { + private static int _maxWidth = 1552; + + public ContrastDetectionResult Measure(BitmapSource image, IRenderedImage render, ContrastDetectionParams p, IProgress progress, CancellationToken token) + { + var result = new ContrastDetectionResult(); + try + { + var state = GetInitialState(render, image, p); + + using (MyStopWatch.Measure()) + { + Stopwatch overall = Stopwatch.StartNew(); + progress?.Report(new ApplicationStatus() { Status = "Preparing image for contrast measurement" }); + + var _bitmapToAnalyze = ImageUtility.Convert16BppTo8Bpp(state._originalBitmapSource); + + token.ThrowIfCancellationRequested(); + + //Crop if there is ROI + + if (p.UseROI && p.InnerCropRatio < 1) + { + Rectangle cropRectangle = DetectionUtility.GetCropRectangle(_bitmapToAnalyze, p.InnerCropRatio); + _bitmapToAnalyze = new Crop(cropRectangle).Apply(_bitmapToAnalyze); + } + + if (p.NoiseReduction == NoiseReductionEnum.Median) + { + new Median().ApplyInPlace(_bitmapToAnalyze); + } + + //Make sure resizing is independent of Star Sensitivity + state._resizefactor = (double)_maxWidth / _bitmapToAnalyze.Width; + state._inverseResizefactor = 1.0 / state._resizefactor; + + /* Resize to speed up manipulation */ + _bitmapToAnalyze = DetectionUtility.ResizeForDetection(_bitmapToAnalyze, _maxWidth, state._resizefactor); + + progress?.Report(new ApplicationStatus() { Status = "Measuring Contrast" }); + + token.ThrowIfCancellationRequested(); + + if (p.Method == ContrastDetectionMethodEnum.Laplace) + { + if (p.NoiseReduction == NoiseReductionEnum.None || p.NoiseReduction == NoiseReductionEnum.Median) + { + int[,] kernel = new int[7, 7]; + kernel = DetectionUtility.LaplacianOfGaussianKernel(7, 1.0); + new Convolution(kernel).ApplyInPlace(_bitmapToAnalyze); + } + else if (p.NoiseReduction == NoiseReductionEnum.Normal) + { + int[,] kernel = new int[9, 9]; + kernel = DetectionUtility.LaplacianOfGaussianKernel(9, 1.4); + new Convolution(kernel).ApplyInPlace(_bitmapToAnalyze); + } + else if (p.NoiseReduction == NoiseReductionEnum.High) + { + int[,] kernel = new int[11, 11]; + kernel = DetectionUtility.LaplacianOfGaussianKernel(11, 1.8); + new Convolution(kernel).ApplyInPlace(_bitmapToAnalyze); + } + else + { + int[,] kernel = new int[13, 13]; + kernel = DetectionUtility.LaplacianOfGaussianKernel(13, 2.2); + new Convolution(kernel).ApplyInPlace(_bitmapToAnalyze); + } + //Get mean and standard dev + Accord.Imaging.ImageStatistics stats = new Accord.Imaging.ImageStatistics(_bitmapToAnalyze); + result.AverageContrast = stats.GrayWithoutBlack.Mean; + result.ContrastStdev = 0.01; //Stdev of convoluted image is not a measure of error - using same figure for all + } + else if (p.Method == ContrastDetectionMethodEnum.Sobel) + { + if (p.NoiseReduction == NoiseReductionEnum.None || p.NoiseReduction == NoiseReductionEnum.Median) + { + //Nothing to do + } + else if (p.NoiseReduction == NoiseReductionEnum.Normal) + { + _bitmapToAnalyze = new FastGaussianBlur(_bitmapToAnalyze).Process(1); + } + else if (p.NoiseReduction == NoiseReductionEnum.High) + { + _bitmapToAnalyze = new FastGaussianBlur(_bitmapToAnalyze).Process(2); + } + else + { + _bitmapToAnalyze = new FastGaussianBlur(_bitmapToAnalyze).Process(3); + } + int[,] kernel = { + {-1, -2, 0, 2, 1}, + {-2, -4, 0, 4, 2}, + {0, 0, 0, 0, 0}, + {2, 4, 0, -4, -2}, + {1, 2, 0, -2, -1} + }; + new Convolution(kernel).ApplyInPlace(_bitmapToAnalyze); + //Get mean and standard dev + Accord.Imaging.ImageStatistics stats = new Accord.Imaging.ImageStatistics(_bitmapToAnalyze); + result.AverageContrast = stats.GrayWithoutBlack.Mean; + result.ContrastStdev = 0.01; //Stdev of convoluted image is not a measure of error - using same figure for all + } + + token.ThrowIfCancellationRequested(); + + _bitmapToAnalyze.Dispose(); + overall.Stop(); + Debug.Print("Overall contrast detection: " + overall.Elapsed); + overall = null; + } + } + catch (OperationCanceledException) + { + } + finally + { + progress?.Report(new ApplicationStatus() { Status = string.Empty }); + } + return result; + } + + private class State + { + public IImageArray _iarr; + public ImageProperties imageProperties; + public BitmapSource _originalBitmapSource; + public double _resizefactor; + public double _inverseResizefactor; + public int _minStarSize; + public int _maxStarSize; + } + + private static State GetInitialState(IRenderedImage renderedImage, BitmapSource image, ContrastDetectionParams p) + { + var state = new State(); + var imageData = renderedImage.RawImageData; + state.imageProperties = imageData.Properties; + + state._iarr = imageData.Data; + //If image was debayered, use debayered array for star HFR and local maximum identification + if (state.imageProperties.IsBayered && (renderedImage is IDebayeredImage)) + { + var debayeredImage = (IDebayeredImage)renderedImage; + var debayeredData = debayeredImage.DebayeredData; + if (debayeredData != null && debayeredData.Lum != null && debayeredData.Lum.Length > 0) + { + state._iarr = new ImageArray(debayeredData.Lum); + } + } + + state._originalBitmapSource = image; + + state._resizefactor = 1.0; + if (state.imageProperties.Width > _maxWidth) + { + if (p.Sensitivity == StarSensitivityEnum.Highest) + { + state._resizefactor = Math.Max(0.625, (double)_maxWidth / state.imageProperties.Width); + } + else + { + state._resizefactor = (double)_maxWidth / state.imageProperties.Width; + } + } + state._inverseResizefactor = 1.0 / state._resizefactor; + + state._minStarSize = (int)Math.Floor(5 * state._resizefactor); + //Prevent Hotpixels to be detected + if (state._minStarSize < 2) + { + state._minStarSize = 2; + } + + state._maxStarSize = (int)Math.Ceiling(150 * state._resizefactor); + return state; + } + } +} diff --git a/Util/Utility.cs b/Util/Utility.cs index 8268ab7..f61308d 100644 --- a/Util/Utility.cs +++ b/Util/Utility.cs @@ -11,49 +11,43 @@ This Source Code Form is subject to the terms of the Mozilla Public using EDSDKLib; using NINA.Core.Utility; +using NINA.Core.Utility.Notification; +using NINA.Equipment.Equipment.MyCamera; +using NINA.Equipment.Interfaces.Mediator; +using NINA.WPF.Base.Mediator; +using NINA.WPF.Base.ViewModel.Equipment.Camera; using System; using System.Collections.Generic; +using System.Reflection; namespace LensAF.Util { public static class Utility { - public static string GetCamName(IntPtr cam) + public static object GetInstanceField(T instance, string fieldName) { - uint err = EDSDK.EdsGetDeviceInfo(cam, out EDSDK.EdsDeviceInfo info); - if (EDSDK.EDS_ERR_OK == err) - { - return info.szDeviceDescription; - } - return null; + BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetField; + FieldInfo field = typeof(T).GetField(fieldName, bindFlags); + return field.GetValue(instance); } - - public static List GetConnectedCams() + public static IntPtr GetCamera(ICameraMediator camera) { - List cams = new List(); try { - IntPtr devices; - uint err = EDSDK.EdsGetCameraList(out devices); - if (err == EDSDK.EDS_ERR_OK) - { - int count; - err = EDSDK.EdsGetChildCount(devices, out count); + CameraVM cameraVM = (CameraVM)GetInstanceField((CameraMediator)camera, "handler"); - for (int i = 0; i < count; i++) - { - IntPtr cam; - err = EDSDK.EdsGetChildAtIndex(devices, i, out cam); - - cams.Add(cam); - } + if (cameraVM.CameraChooserVM.SelectedDevice.Category != "Canon") + { + Notification.ShowError("No canon camera connected"); + return IntPtr.Zero; } - } - catch (Exception e) + return (IntPtr)GetInstanceField((EDCamera)cameraVM.CameraChooserVM.SelectedDevice, "_cam"); + } catch (Exception e) { Logger.Error(e); + Notification.ShowError(e.Message); + return IntPtr.Zero; } - return cams; } } } \ No newline at end of file diff --git a/app.config b/app.config index 83811e1..dbbfc69 100644 --- a/app.config +++ b/app.config @@ -80,6 +80,24 @@ 0 + + 0 + + + + + + True + + + False + + + 0.12 + + + -2.8 + diff --git a/packages.config b/packages.config index dd07c38..8c0d8e9 100644 --- a/packages.config +++ b/packages.config @@ -27,18 +27,18 @@ - - - - - - - - - - + + + + + + + + + + - +