1 /*
2  *  darks_dialog.cpp
3  *  PHD Guiding
4  *
5  *  Created by Bruce Waddington in collaboration with David Ault and Andy Galasso
6  *  Copyright (c) 2014 Bruce Waddington
7  *  All rights reserved.
8  *
9  *  This source code is distributed under the following "BSD" license
10  *  Redistribution and use in source and binary forms, with or without
11  *  modification, are permitted provided that the following conditions are met:
12  *    Redistributions of source code must retain the above copyright notice,
13  *     this list of conditions and the following disclaimer.
14  *    Redistributions in binary form must reproduce the above copyright notice,
15  *     this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution.
17  *    Neither the name of Bret McKee, Dad Dog Development,
18  *     Craig Stark, Stark Labs nor the names of its
19  *     contributors may be used to endorse or promote products derived from
20  *     this software without specific prior written permission.
21  *
22  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  *  POSSIBILITY OF SUCH DAMAGE.
33  *
34  */
35 
36 #include "phd.h"
37 #include "darks_dialog.h"
38 #include "wx/valnum.h"
39 
40 #include <algorithm>
41 #include <sstream>
42 
43 static const int DefDarkCount = 5;
44 static const int DefDMExpTime = 15;
45 static const int DefDMCount = 25;
46 
47 static const int MaxNoteLength = 65;            // For now
48 
49 // Utility function to add the <label, input> pairs to a flexgrid
AddTableEntryPair(wxWindow * parent,wxFlexGridSizer * pTable,const wxString & label,wxWindow * pControl)50 static void AddTableEntryPair(wxWindow *parent, wxFlexGridSizer *pTable, const wxString& label, wxWindow *pControl)
51 {
52     wxStaticText *pLabel = new wxStaticText(parent, wxID_ANY, label + _(": "), wxPoint(-1, -1), wxSize(-1, -1));
53     pTable->Add(pLabel, 1, wxALL, 5);
54     pTable->Add(pControl, 1, wxALL, 5);
55 }
56 
NewSpinnerInt(wxWindow * parent,const wxSize & size,int val,int minval,int maxval,int inc,const wxString & tooltip)57 static wxSpinCtrl *NewSpinnerInt(wxWindow *parent, const wxSize& size, int val, int minval, int maxval, int inc,
58                                  const wxString& tooltip)
59 {
60     wxSpinCtrl *pNewCtrl = pFrame->MakeSpinCtrl(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, wxSP_ARROW_KEYS, minval, maxval, val, _("Exposure time"));
61     pNewCtrl->SetValue(val);
62     pNewCtrl->SetToolTip(tooltip);
63     return pNewCtrl;
64 }
65 
GetExposureDurationStrings(wxArrayString * ary)66 static void GetExposureDurationStrings(wxArrayString *ary)
67 {
68     std::vector<int> d(pFrame->GetExposureDurations());
69     std::sort(d.begin(), d.end());
70     for (auto it = d.begin(); it != d.end(); ++it)
71         ary->Add(pFrame->ExposureDurationLabel(*it));
72 }
73 
MinExposureDefault()74 static wxString MinExposureDefault()
75 {
76     if (pMount && pMount->IsStepGuider())
77         return pFrame->ExposureDurationLabel(100);
78     else
79         return pFrame->ExposureDurationLabel(1000);
80 }
81 
MaxExposureDefault()82 static wxString MaxExposureDefault()
83 {
84     if (pMount && pMount->IsStepGuider())
85         return pFrame->ExposureDurationLabel(2500);
86     else
87         return pFrame->ExposureDurationLabel(6000);
88 }
89 
90 // Dialog operates in one of two modes: 1) To create a user-requested dark library or 2) To create a master dark frame
91 // and associated data files needed to construct a new defect map
DarksDialog(wxWindow * parent,bool darkLib)92 DarksDialog::DarksDialog(wxWindow *parent, bool darkLib) :
93     wxDialog(parent, wxID_ANY, _("Build Dark Library"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
94 {
95     buildDarkLib = darkLib;
96     if (!buildDarkLib)
97         this->SetTitle(_("Acquire Master Dark Frames for Bad Pixel Map Calculation"));
98     GetExposureDurationStrings(&m_expStrings);
99 
100     // Create overall vertical sizer
101     wxBoxSizer *pvSizer = new wxBoxSizer(wxVERTICAL);
102     if (buildDarkLib)
103     {
104         // Dark library controls
105         wxStaticBoxSizer *pDarkGroup = new wxStaticBoxSizer(wxVERTICAL, this, _("Dark Library"));
106         wxFlexGridSizer *pDarkParams = new wxFlexGridSizer(2, 4, 5, 15);
107 
108         m_pDarkMinExpTime = new wxComboBox(this, BUTTON_DURATION, wxEmptyString, wxDefaultPosition, wxDefaultSize,
109             m_expStrings, wxCB_READONLY);
110 
111         AddTableEntryPair(this, pDarkParams, _("Min Exposure Time"), m_pDarkMinExpTime);
112         m_pDarkMinExpTime->SetValue(pConfig->Profile.GetString("/camera/darks_min_exptime", MinExposureDefault()));
113         m_pDarkMinExpTime->SetToolTip(_("Minimum exposure time for darks. Choose a value corresponding to the shortest camera exposure you will use for guiding."));
114 
115         m_pDarkMaxExpTime = new wxComboBox(this, BUTTON_DURATION, wxEmptyString, wxDefaultPosition, wxDefaultSize,
116             m_expStrings, wxCB_READONLY);
117 
118         AddTableEntryPair(this, pDarkParams, _("Max Exposure Time"), m_pDarkMaxExpTime);
119         m_pDarkMaxExpTime->SetValue(pConfig->Profile.GetString("/camera/darks_max_exptime", MaxExposureDefault()));
120         m_pDarkMaxExpTime->SetToolTip(_("Maximum exposure time for darks. Choose a value corresponding to the longest camera exposure you will use for guiding."));
121 
122         m_pDarkCount = NewSpinnerInt(this, pFrame->GetTextExtent("9999"), pConfig->Profile.GetInt("/camera/darks_num_frames", DefDarkCount),
123             1, 20, 1, _("Number of dark frames for each exposure time"));
124         AddTableEntryPair(this, pDarkParams, _("Frames to take for each \n exposure time"), m_pDarkCount);
125         pDarkGroup->Add(pDarkParams, wxSizerFlags().Border(wxALL, 10));
126         pvSizer->Add(pDarkGroup, wxSizerFlags().Border(wxALL, 10).Expand());
127 
128         wxStaticBoxSizer *pBuildOptions = new wxStaticBoxSizer(wxVERTICAL, this, _("Options"));
129         wxBoxSizer *hSizer = new wxBoxSizer(wxHORIZONTAL);
130         wxStaticText *pInfo = new wxStaticText(this, wxID_ANY, wxEmptyString, wxPoint(-1, -1), wxSize(-1, -1));
131         m_rbModifyDarkLib = new wxRadioButton(this, wxID_ANY, _("Modify/extend existing dark library"));
132         m_rbModifyDarkLib->SetToolTip(_("Darks created now will replace older darks having matching exposure times. If different exposure times are used, "
133             "those darks will be added to the library."));
134         m_rbNewDarkLib = new wxRadioButton(this, wxID_ANY, _("Create entirely new dark library"));
135         m_rbNewDarkLib->SetToolTip(_("Darks created now will be used to build a completely new dark library - old dark frames will be discarded. You "
136             " MUST use this option if you've seen alert messages about incompatible frame sizes or mismatches with the current camera."));
137         if (pFrame->DarkLibExists(pConfig->GetCurrentProfileId(), false))
138         {
139             if (pFrame->LoadDarkHandler(true))
140             {
141                 double min_v, max_v;
142                 int num;
143                 pCamera->GetDarklibProperties(&num, &min_v, &max_v);
144                 pInfo->SetLabel(wxString::Format(_("Existing dark library covers %d exposure times in the range of %g s to %g s"),
145                     num, min_v / 1000., max_v / 1000.));
146                 m_rbModifyDarkLib->SetValue(true);
147             }
148             else
149             {
150                 pInfo->SetLabel(_("Existing dark library contains incompatible frames - it must be rebuilt from scratch"));
151                 m_rbModifyDarkLib->Enable(false);
152                 m_rbNewDarkLib->SetValue(true);
153             }
154         }
155         else
156         {
157             pInfo->SetLabel(_("No compatible dark library is available"));
158             m_rbModifyDarkLib->Enable(false);
159             m_rbNewDarkLib->SetValue(true);
160         }
161 
162         hSizer->Add(m_rbModifyDarkLib, wxSizerFlags().Border(wxALL, 10));
163         hSizer->Add(m_rbNewDarkLib, wxSizerFlags().Border(wxALL, 10));
164         pBuildOptions->Add(pInfo, wxSizerFlags().Border(wxALL, 10).Border(wxLEFT, 25));
165         pBuildOptions->Add(hSizer, wxSizerFlags().Border(wxALL, 10));
166         pvSizer->Add(pBuildOptions, wxSizerFlags().Expand());
167     }
168     else
169     {
170         // Defect map controls
171         wxStaticBoxSizer *pDMapGroup = new wxStaticBoxSizer(wxVERTICAL, this, _("Dark Frame Settings"));
172         wxFlexGridSizer *pDMapParams = new wxFlexGridSizer(2, 4, 5, 15);
173         m_pDefectExpTime = NewSpinnerInt(this, pFrame->GetTextExtent("9999"), pConfig->Profile.GetInt("/camera/dmap_exptime", DefDMExpTime),
174             5, 15, 1, _("Exposure time for building defect map"));
175         AddTableEntryPair(this, pDMapParams, _("Exposure Time"), m_pDefectExpTime);
176         m_pNumDefExposures = NewSpinnerInt(this, pFrame->GetTextExtent("9999"), pConfig->Profile.GetInt("/camera/dmap_num_frames", DefDMCount),
177             5, 25, 1, _("Number of exposures for building defect map"));
178         AddTableEntryPair(this, pDMapParams, _("Number of Exposures"), m_pNumDefExposures);
179         pDMapGroup->Add(pDMapParams, wxSizerFlags().Border(wxALL, 10));
180         pvSizer->Add(pDMapGroup, wxSizerFlags().Border(wxALL, 10));
181     }
182 
183     // Controls for notes and status
184     wxBoxSizer *phSizer = new wxBoxSizer(wxHORIZONTAL);
185     wxStaticText *pNoteLabel = new wxStaticText(this, wxID_ANY,  _("Notes: "), wxPoint(-1, -1), wxSize(-1, -1));
186     wxSize sz(38 * StringWidth(this, "M"), -1);
187     m_pNotes = new wxTextCtrl(this, wxID_ANY, _T(""), wxDefaultPosition, sz);
188     m_pNotes->SetToolTip(_("Free-form note, included in FITs header for each dark frame; max length=65"));
189     m_pNotes->SetMaxLength(MaxNoteLength);
190     m_pNotes->SetValue(pConfig->Profile.GetString("/camera/darks_note", ""));
191     phSizer->Add(pNoteLabel, wxSizerFlags().Border(wxALL, 5));
192     phSizer->Add(m_pNotes, wxSizerFlags().Border(wxALL, 5));
193     pvSizer->Add(phSizer, wxSizerFlags().Border(wxALL, 5));
194     phSizer = new wxBoxSizer(wxHORIZONTAL);
195     m_pProgress = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, sz);
196     m_pProgress->Enable(false);
197     pvSizer->Add(phSizer, wxSizerFlags().Border(wxALL, 5));
198     pvSizer->Add(m_pProgress, wxSizerFlags().Border(wxLEFT, 60));
199 
200     // Buttons
201     wxBoxSizer *pButtonSizer = new wxBoxSizer( wxHORIZONTAL );
202     m_pResetBtn = new wxButton(this, wxID_ANY, _("Reset"));
203     m_pResetBtn->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DarksDialog::OnReset, this);
204     m_pResetBtn->SetToolTip(_("Reset all parameters to application defaults"));
205 
206     m_pStartBtn = new wxButton(this, wxID_ANY, _("Start"));
207     m_pStartBtn->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DarksDialog::OnStart, this);
208     m_pStartBtn->SetToolTip("");
209 
210     m_pStopBtn = new wxButton(this, wxID_ANY, _("Cancel"));
211     m_pStopBtn->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DarksDialog::OnStop, this);
212     m_pStopBtn->SetToolTip("");
213 
214     pButtonSizer->Add(
215         m_pResetBtn,
216         wxSizerFlags(0).Align(0).Border(wxALL, 10));
217     pButtonSizer->Add(
218         m_pStartBtn,
219         wxSizerFlags(0).Align(0).Border(wxALL, 10));
220     pButtonSizer->Add(
221         m_pStopBtn,
222         wxSizerFlags(0).Align(0).Border(wxALL, 10));
223     pvSizer->Add(pButtonSizer, wxSizerFlags().Center().Border(wxALL, 10));
224 
225     // status bar
226     m_pStatusBar = new wxStatusBar(this, -1);
227     m_pStatusBar->SetFieldsCount(1);
228     m_pStatusBar->SetStatusText(_("Set your parameters, click 'Start' to begin"));
229     pvSizer->Add(m_pStatusBar, 0, wxGROW);
230 
231     SetAutoLayout(true);
232     SetSizerAndFit (pvSizer);
233 
234     m_cancelling = false;
235     m_started = false;
236 }
237 
OnStart(wxCommandEvent & evt)238 void DarksDialog::OnStart(wxCommandEvent& evt)
239 {
240     SaveProfileInfo();
241 
242     m_pStartBtn->Enable(false);
243     m_pResetBtn->Enable(false);
244     m_pStopBtn->SetLabel(_("Stop"));
245     m_pStopBtn->Refresh();
246     m_started = true;
247     wxYield();
248 
249     if (!pCamera->HasShutter)
250         wxMessageBox(_("Cover guide scope"));
251     pCamera->ShutterClosed = true;
252 
253     m_pProgress->SetValue(0);
254 
255     wxString wrapupMsg;
256 
257     bool err = false;
258 
259     if (buildDarkLib)
260     {
261         int darkFrameCount = m_pDarkCount->GetValue();
262         int minExpInx = m_pDarkMinExpTime->GetSelection();
263         int maxExpInx = m_pDarkMaxExpTime->GetSelection();
264 
265         std::vector<int> exposureDurations(pFrame->GetExposureDurations());
266         std::sort(exposureDurations.begin(), exposureDurations.end());
267 
268         int tot_dur = 0;
269         for (int i = minExpInx; i <= maxExpInx; i++)
270             tot_dur += exposureDurations[i] * darkFrameCount;
271 
272         m_pProgress->SetRange(tot_dur);
273         if (m_rbNewDarkLib->GetValue())           // User rebuilding from scratch
274             pCamera->ClearDarks();
275 
276         for (int inx = minExpInx; inx <= maxExpInx; inx++)
277         {
278             int darkExpTime = exposureDurations[inx];
279             if (darkExpTime >= 1000)
280                 ShowStatus (wxString::Format(_("Building master dark at %.1f sec:"), (double)darkExpTime / 1000.0), false);
281             else
282                 ShowStatus (wxString::Format(_("Building master dark at %d mSec:"), darkExpTime), false);
283             usImage *newDark = new usImage();
284             err = CreateMasterDarkFrame(*newDark, exposureDurations[inx], darkFrameCount);
285             wxYield();
286             if (m_cancelling || err)
287             {
288                 delete newDark;
289                 break;
290             }
291             else
292             {
293                 pCamera->AddDark(newDark);
294             }
295         }
296 
297         if (m_cancelling || err)
298         {
299             ShowStatus(m_cancelling ? _("Operation cancelled - no changes have been made") : _("Operation failed - no changes have been made"), false);
300             if (pFrame->DarkLibExists(pConfig->GetCurrentProfileId(), false))
301             {
302                 if (pFrame->LoadDarkHandler(true))
303                     Debug.AddLine("Dark library abort, dark library restored.");
304                 else
305                     Debug.AddLine("Dark library abort, dark library still invalid.");
306             }
307         }
308         else
309         {
310             pFrame->SaveDarkLibrary(m_pNotes->GetValue());
311             pFrame->LoadDarkHandler(true);          // Put it to use, including selection of matching dark frame
312             wrapupMsg = _("dark library built");
313             if (m_rbNewDarkLib)
314                 Debug.AddLine("Dark library - new dark lib created from scratch.");
315             else
316                 Debug.AddLine("Dark library - dark lib modified/extended.");
317             ShowStatus(wrapupMsg, false);
318         }
319     }
320     else
321     {
322         // Start by computing master dark frame with longish exposure times
323         ShowStatus(_("Taking darks to compute defect map: "),  false);
324 
325         int defectFrameCount = m_pNumDefExposures->GetValue();
326         int defectExpTime = m_pDefectExpTime->GetValue() * 1000;
327 
328         m_pProgress->SetRange(defectFrameCount * defectExpTime);
329         m_pProgress->SetValue(0);
330 
331         DefectMapDarks darks;
332         err = CreateMasterDarkFrame(darks.masterDark, defectExpTime, defectFrameCount);
333 
334         if (m_cancelling)
335         {
336             ShowStatus(_("Operation cancelled"), false);
337         }
338         else if (!err)
339         {
340             // Our role here is to build the dark-related files needed for defect map building
341             ShowStatus(_("Analyzing master dark..."), false);
342 
343             // create a median-filtered dark
344             Debug.AddLine("Starting construction of filtered master dark file");
345             darks.BuildFilteredDark();
346             Debug.AddLine("Completed construction of filtered master dark file");
347 
348             // save the master dark and the median filtered dark
349             darks.SaveDarks(m_pNotes->GetValue());
350 
351             ShowStatus(_("Master dark data files built"), false);
352 
353             wrapupMsg = _("Master dark data files built");
354         }
355     }
356 
357     m_pStartBtn->Enable(true);
358     m_pResetBtn->Enable(true);
359     pFrame->SetDarkMenuState();         // Hard to know where we are at this point
360 
361     if (m_cancelling || err)
362     {
363         m_pProgress->SetValue(0);
364         m_cancelling = false;
365         m_started = false;
366         m_pStopBtn->SetLabel(_("Cancel"));
367     }
368     else
369     {
370         // Put up a message showing results and maybe notice to uncover the scope; then close the dialog
371         pCamera->ShutterClosed = false; // Lights
372         if (!pCamera->HasShutter)
373             wrapupMsg = _("Uncover guide scope") + wxT("\n\n") + wrapupMsg;   // Results will appear in smaller font
374         wxMessageBox(wxString::Format(_("Operation complete: %s"), wrapupMsg));
375         EndDialog(wxOK);
376     }
377 }
378 
379 // Event handler for dual mode cancel/stop button
OnStop(wxCommandEvent & evt)380 void DarksDialog::OnStop(wxCommandEvent& evt)
381 {
382     if (m_started)
383     {
384         m_cancelling = true;
385         ShowStatus(_("Cancelling..."), false);
386     }
387     else
388         wxDialog::Close();
389 }
390 
OnReset(wxCommandEvent & evt)391 void DarksDialog::OnReset(wxCommandEvent& evt)
392 {
393     if (buildDarkLib)
394     {
395         m_pDarkMinExpTime->SetValue(MinExposureDefault());
396         m_pDarkMaxExpTime->SetValue(MaxExposureDefault());
397         m_pDarkCount->SetValue(DefDarkCount);
398     }
399     else
400     {
401         m_pDefectExpTime->SetValue(DefDMExpTime);
402         m_pNumDefExposures->SetValue(DefDMCount);
403         m_pNotes->SetValue("");
404     }
405 }
406 
ShowStatus(const wxString msg,bool appending)407 void DarksDialog::ShowStatus(const wxString msg, bool appending)
408 {
409     static wxString preamble;
410 
411     if (appending)
412         m_pStatusBar->SetStatusText(preamble + " " + msg);
413     else
414     {
415         m_pStatusBar->SetStatusText(msg);
416         preamble = msg;
417     }
418 }
419 
SaveProfileInfo()420 void DarksDialog::SaveProfileInfo()
421 {
422     if (buildDarkLib)
423     {
424         pConfig->Profile.SetString("/camera/darks_min_exptime", m_pDarkMinExpTime->GetValue());
425         pConfig->Profile.SetString("/camera/darks_max_exptime", m_pDarkMaxExpTime->GetValue());
426         pConfig->Profile.SetInt("/camera/darks_num_frames", m_pDarkCount->GetValue());
427     }
428     else
429     {
430         pConfig->Profile.SetInt("/camera/dmap_exptime", m_pDefectExpTime->GetValue());
431         pConfig->Profile.SetInt("/camera/dmap_num_frames", m_pNumDefExposures->GetValue());
432     }
433     pConfig->Profile.SetString("/camera/darks_note", m_pNotes->GetValue());
434 }
435 
436 struct Histogram
437 {
438     unsigned long val[256];
439     unsigned int median;
440     double mean;
441 
HistogramHistogram442     Histogram(const usImage& img)
443     {
444         memset(&val[0], 0, sizeof(val));
445         mean = 0.0;
446         for (unsigned int i = 0; i < img.NPixels; i++)
447         {
448             unsigned short v = img.ImageData[i];
449             mean += v;
450             v >>= (img.BitsPerPixel - 8);
451             if (v > 255)
452                 v = 255;  // should never happen if BitsPerPixel is valid
453             ++val[v];
454         }
455         mean /= img.NPixels;
456         // median (approx)
457         unsigned long sum = 0;
458         int i;
459         for (i = 0; i < 256; i++)
460         {
461             sum += val[i];
462             if (sum > img.NPixels / 2)
463                 break;
464         }
465         median = i << (img.BitsPerPixel - 8);
466     }
467 
DumpHistogram468     void Dump()
469     {
470         Debug.Write(wxString::Format("mean = %.f  median(approx) = %u\n", mean, median));
471         int i = 0;
472         for (int l = 0; l < 4; l++)
473         {
474             std::ostringstream os;
475             os << "histo[" << (l * 64) << ".." << ((l + 1) * 64 - 1) << "]";
476             for (int j = 0; j < 64; j++, i++)
477                 os << ' ' << val[i];
478             os << "\n";
479             Debug.Write(os.str());
480         }
481     }
482 };
483 
CreateMasterDarkFrame(usImage & darkFrame,int expTime,int frameCount)484 bool DarksDialog::CreateMasterDarkFrame(usImage& darkFrame, int expTime, int frameCount)
485 {
486     bool err = false;
487 
488     pCamera->InitCapture();
489     darkFrame.ImgExpDur = expTime;
490     darkFrame.ImgStackCnt = frameCount;
491 
492     unsigned int *avgimg = 0;
493 
494     for (int j = 1; j <= frameCount; j++)
495     {
496         wxYield();
497         if (m_cancelling)
498             break;
499         ShowStatus(wxString::Format(_("Taking dark frame %d/%d"), j, frameCount), true);
500 
501         Debug.Write(wxString::Format("Capture dark frame %d/%d exp=%d\n", j, frameCount, expTime));
502         err = GuideCamera::Capture(pCamera, expTime, darkFrame, CAPTURE_DARK);
503         if (err)
504         {
505             ShowStatus(wxString::Format(_("%.1f s dark FAILED"), (double)expTime / 1000.0), true);
506             pCamera->ShutterClosed = false;
507             break;
508         }
509 
510         m_pProgress->SetValue(m_pProgress->GetValue() + expTime);
511         wxYield();
512 
513         darkFrame.CalcStats();
514 
515         Debug.Write(wxString::Format("dark frame stats: bpp %u min %u max %u med %u filtmin %u filtmax %u\n",
516                                      darkFrame.BitsPerPixel, darkFrame.MinADU, darkFrame.MaxADU,
517                                      darkFrame.MedianADU, darkFrame.FiltMin, darkFrame.FiltMax));
518 
519         Histogram h(darkFrame);
520         h.Dump();
521         wxYield();
522 
523         if (!avgimg)
524         {
525             avgimg = new unsigned int[darkFrame.NPixels];
526             memset(avgimg, 0, darkFrame.NPixels * sizeof(*avgimg));
527         }
528 
529         unsigned int *iptr = avgimg;
530         const unsigned short *usptr = darkFrame.ImageData;
531         for (unsigned int i = 0; i < darkFrame.NPixels; i++)
532             *iptr++ += *usptr++;
533     }
534 
535     if (!m_cancelling && !err)
536     {
537         ShowStatus(_("Dark frames complete"), true);
538         const unsigned int *iptr = avgimg;
539         unsigned short *usptr = darkFrame.ImageData;
540         for (unsigned int i = 0; i < darkFrame.NPixels; i++)
541             *usptr++ = (unsigned short)(*iptr++ / frameCount);
542     }
543 
544     m_pProgress->SetValue(m_pProgress->GetValue() + expTime);
545     wxYield();
546 
547     delete[] avgimg;
548 
549     return err;
550 }
551 
~DarksDialog(void)552 DarksDialog::~DarksDialog(void)
553 {
554 }
555