// Define the repainting behaviour void GuiderOneStar::OnPaint(wxPaintEvent& event) { //wxAutoBufferedPaintDC dc(this); wxClientDC dc(this); wxMemoryDC memDC; try { if (PaintHelper(dc, memDC)) { throw ERROR_INFO("PaintHelper failed"); } // PaintHelper drew the image and any overlays // now decorate the image to show the selection // display bookmarks if (m_showBookmarks && m_bookmarks.size() > 0) { dc.SetPen(wxPen(wxColour(0,255,255),1,wxSOLID)); dc.SetBrush(*wxTRANSPARENT_BRUSH); for (std::vector<wxRealPoint>::const_iterator it = m_bookmarks.begin(); it != m_bookmarks.end(); ++it) { wxPoint p((int)(it->x * m_scaleFactor), (int)(it->y * m_scaleFactor)); dc.DrawCircle(p, 3); dc.DrawCircle(p, 6); dc.DrawCircle(p, 12); } } GUIDER_STATE state = GetState(); bool FoundStar = m_star.WasFound(); if (state == STATE_SELECTED) { if (FoundStar) dc.SetPen(wxPen(wxColour(100,255,90), 1, wxSOLID)); // Draw the box around the star else dc.SetPen(wxPen(wxColour(230,130,30), 1, wxDOT)); DrawBox(dc, m_star, m_searchRegion, m_scaleFactor); } else if (state == STATE_CALIBRATING_PRIMARY || state == STATE_CALIBRATING_SECONDARY) { // in the calibration process dc.SetPen(wxPen(wxColour(32,196,32), 1, wxSOLID)); // Draw the box around the star DrawBox(dc, m_star, m_searchRegion, m_scaleFactor); } else if (state == STATE_CALIBRATED || state == STATE_GUIDING) { // locked and guiding if (FoundStar) dc.SetPen(wxPen(wxColour(32,196,32), 1, wxSOLID)); // Draw the box around the star else dc.SetPen(wxPen(wxColour(230,130,30), 1, wxDOT)); DrawBox(dc, m_star, m_searchRegion, m_scaleFactor); } // Image logging if (state >= STATE_SELECTED && pFrame->IsImageLoggingEnabled() && pFrame->m_frameCounter != pFrame->m_loggedImageFrame) { // only log each image frame once pFrame->m_loggedImageFrame = pFrame->m_frameCounter; if (pFrame->GetLoggedImageFormat() == LIF_RAW_FITS) // Save star image as a FITS { SaveStarFITS(); } else // Save star image as a JPEG { double LockX = LockPosition().X; double LockY = LockPosition().Y; wxBitmap SubBmp(60,60,-1); wxMemoryDC tmpMdc; tmpMdc.SelectObject(SubBmp); memDC.SetPen(wxPen(wxColor(0,255,0),1,wxDOT)); memDC.DrawLine(0, LockY * m_scaleFactor, XWinSize, LockY * m_scaleFactor); memDC.DrawLine(LockX*m_scaleFactor, 0, LockX*m_scaleFactor, YWinSize); #ifdef __APPLEX__ tmpMdc.Blit(0,0,60,60,&memDC,ROUND(m_star.X*m_scaleFactor)-30,Displayed_Image->GetHeight() - ROUND(m_star.Y*m_scaleFactor)-30,wxCOPY,false); #else tmpMdc.Blit(0,0,60,60,&memDC,ROUND(m_star.X*m_scaleFactor)-30,ROUND(m_star.Y*m_scaleFactor)-30,wxCOPY,false); #endif // tmpMdc.Blit(0,0,200,200,&Cdc,0,0,wxCOPY); wxString fname = Debug.GetLogDir() + PATHSEPSTR + "PHD_GuideStar" + wxDateTime::Now().Format(_T("_%j_%H%M%S")) + ".jpg"; wxImage subImg = SubBmp.ConvertToImage(); // subImg.Rescale(120, 120); zoom up (not now) if (pFrame->GetLoggedImageFormat() == LIF_HI_Q_JPEG) { // set high(ish) JPEG quality subImg.SetOption(wxIMAGE_OPTION_QUALITY, 100); } subImg.SaveFile(fname, wxBITMAP_TYPE_JPEG); tmpMdc.SelectObject(wxNullBitmap); } } } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); } }
bool GuiderOneStar::UpdateCurrentPosition(usImage *pImage, FrameDroppedInfo *errorInfo) { if (!m_star.IsValid() && m_star.X == 0.0 && m_star.Y == 0.0) { Debug.AddLine("UpdateCurrentPosition: no star selected"); errorInfo->starError = Star::STAR_ERROR; errorInfo->starMass = 0.0; errorInfo->starSNR = 0.0; errorInfo->status = _("No star selected"); return true; } bool bError = false; try { Star newStar(m_star); if (!newStar.Find(pImage, m_searchRegion, pFrame->GetStarFindMode())) { errorInfo->starError = newStar.GetError(); errorInfo->starMass = 0.0; errorInfo->starSNR = 0.0; errorInfo->status = StarStatusStr(newStar); m_star.SetError(newStar.GetError()); throw ERROR_INFO("UpdateCurrentPosition():newStar not found"); } // check to see if it seems like the star we just found was the // same as the original star. We do this by comparing the // mass m_massChecker->SetExposure(pFrame->RequestedExposureDuration()); double limits[3]; if (m_massChangeThresholdEnabled && m_massChecker->CheckMass(newStar.Mass, m_massChangeThreshold, limits)) { m_star.SetError(Star::STAR_MASSCHANGE); errorInfo->starError = Star::STAR_MASSCHANGE; errorInfo->starMass = newStar.Mass; errorInfo->starSNR = newStar.SNR; errorInfo->status = StarStatusStr(m_star); pFrame->SetStatusText(wxString::Format(_("Mass: %.0f vs %.0f"), newStar.Mass, limits[1]), 1); Debug.Write(wxString::Format("UpdateGuideState(): star mass new=%.1f exp=%.1f thresh=%.0f%% range=(%.1f, %.1f)\n", newStar.Mass, limits[1], m_massChangeThreshold * 100, limits[0], limits[2])); m_massChecker->AppendData(newStar.Mass); throw THROW_INFO("massChangeThreshold error"); } // update the star position, mass, etc. m_star = newStar; m_massChecker->AppendData(newStar.Mass); const PHD_Point& lockPos = LockPosition(); if (lockPos.IsValid()) { double distance = newStar.Distance(lockPos); UpdateCurrentDistance(distance); } pFrame->pProfile->UpdateData(pImage, m_star.X, m_star.Y); pFrame->AdjustAutoExposure(m_star.SNR); errorInfo->status.Printf(_T("m=%.0f SNR=%.1f"), m_star.Mass, m_star.SNR); } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); bError = true; pFrame->ResetAutoExposure(); // use max exposure duration } return bError; }
void GuiderOneStar::OnLClick(wxMouseEvent &mevent) { try { if (mevent.GetModifiers() == wxMOD_CONTROL) { double const scaleFactor = ScaleFactor(); wxRealPoint pt((double) mevent.m_x / scaleFactor, (double) mevent.m_y / scaleFactor); ToggleBookmark(pt); m_showBookmarks = true; pFrame->bookmarks_menu->Check(MENU_BOOKMARKS_SHOW, GetBookmarksShown()); Refresh(); Update(); return; } if (GetState() > STATE_SELECTED) { mevent.Skip(); throw THROW_INFO("Skipping event because state > STATE_SELECTED"); } if (mevent.GetModifiers() == wxMOD_SHIFT) { // Deselect guide star InvalidateCurrentPosition(true); } else { if ((mevent.m_x <= m_searchRegion) || (mevent.m_x + m_searchRegion >= XWinSize) || (mevent.m_y <= m_searchRegion) || (mevent.m_y + m_searchRegion >= YWinSize)) { mevent.Skip(); throw THROW_INFO("Skipping event because click outside of search region"); } usImage *pImage = CurrentImage(); if (pImage->NPixels == 0) { mevent.Skip(); throw ERROR_INFO("Skipping event m_pCurrentImage->NPixels == 0"); } double scaleFactor = ScaleFactor(); double StarX = (double) mevent.m_x / scaleFactor; double StarY = (double) mevent.m_y / scaleFactor; SetCurrentPosition(pImage, PHD_Point(StarX, StarY)); if (!m_star.IsValid()) { pFrame->SetStatusText(wxString::Format(_("No star found"))); } else { SetLockPosition(m_star); pFrame->SetStatusText(wxString::Format(_("Selected star at (%.1f, %.1f)"), m_star.X, m_star.Y), 1); pFrame->SetStatusText(wxString::Format(_T("m=%.0f SNR=%.1f"), m_star.Mass, m_star.SNR)); EvtServer.NotifyStarSelected(CurrentPosition()); SetState(STATE_SELECTED); pFrame->UpdateButtonsStatus(); pFrame->pProfile->UpdateData(pImage, m_star.X, m_star.Y); } Refresh(); Update(); } } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); } }
bool Star::Find(const usImage *pImg, int searchRegion, int base_x, int base_y, FindMode mode) { FindResult Result = STAR_OK; double newX = base_x; double newY = base_y; try { Debug.Write(wxString::Format("Star::Find(%d, %d, %d, %d, (%d,%d,%d,%d))\n", searchRegion, base_x, base_y, mode, pImg->Subframe.x, pImg->Subframe.y, pImg->Subframe.width, pImg->Subframe.height)); if (base_x < 0 || base_y < 0) { throw ERROR_INFO("coordinates are invalid"); } int minx, miny, maxx, maxy; if (pImg->Subframe.IsEmpty()) { minx = miny = 0; maxx = pImg->Size.GetWidth() - 1; maxy = pImg->Size.GetHeight() - 1; } else { minx = pImg->Subframe.GetLeft(); maxx = pImg->Subframe.GetRight(); miny = pImg->Subframe.GetTop(); maxy = pImg->Subframe.GetBottom(); } // search region bounds int start_x = wxMax(base_x - searchRegion, minx); int end_x = wxMin(base_x + searchRegion, maxx); int start_y = wxMax(base_y - searchRegion, miny); int end_y = wxMin(base_y + searchRegion, maxy); const unsigned short *imgdata = pImg->ImageData; int rowsize = pImg->Size.GetWidth(); int peak_x = 0, peak_y = 0; unsigned int peak_val = 0; unsigned short max3[3] = { 0, 0, 0 }; if (mode == FIND_PEAK) { for (int y = start_y; y <= end_y; y++) { for (int x = start_x; x <= end_x; x++) { unsigned short val = imgdata[y * rowsize + x]; if (val > peak_val) { peak_val = val; peak_x = x; peak_y = y; } } } } else { // find the peak value within the search region using a smoothing function // also check for saturation for (int y = start_y + 1; y <= end_y - 1; y++) { for (int x = start_x + 1; x <= end_x - 1; x++) { unsigned short p = imgdata[y * rowsize + x]; unsigned int val = 2 * (unsigned int) p + imgdata[(y - 1) * rowsize + (x + 0)] + imgdata[(y + 0) * rowsize + (x - 1)] + imgdata[(y + 0) * rowsize + (x + 1)] + imgdata[(y + 1) * rowsize + (x + 0)]; if (val > peak_val) { peak_val = val; peak_x = x; peak_y = y; } if (p > max3[0]) std::swap(p, max3[0]); if (p > max3[1]) std::swap(p, max3[1]); if (p > max3[2]) std::swap(p, max3[2]); } } } // meaure noise in the annulus with inner radius A and outer radius B int const A = 7; // inner radius int const B = 12; // outer radius int const A2 = A * A; int const B2 = B * B; // find the mean and stdev of the background double sum = 0.0; double a = 0.0; double q = 0.0; int n = 0; const unsigned short *row = imgdata + rowsize * start_y; for (int y = start_y; y <= end_y; y++, row += rowsize) { int dy = y - peak_y; int dy2 = dy * dy; for (int x = start_x; x <= end_x; x++) { int dx = x - peak_x; int r2 = dx * dx + dy2; // exclude points not in annulus if (r2 <= A2 || r2 > B2) continue; double const val = (double) row[x]; sum += val; ++n; double const k = (double) n; double const a0 = a; a += (val - a) / k; q += (val - a0) * (val - a); } } double const mean_bg = sum / (double) n; double const sigma_bg = sqrt(q / (double) (n - 1)); double cx = 0.0; double cy = 0.0; double mass = 0.0; if (mode == FIND_PEAK) { mass = peak_val; n = 1; } else { unsigned short const thresh = (unsigned short)(mean_bg + 2.0 * sigma_bg); // find pixels over threshold within aperture; compute mass and centroid start_x = wxMax(peak_x - A, minx); end_x = wxMin(peak_x + A, maxx); start_y = wxMax(peak_y - A, miny); end_y = wxMin(peak_y + A, maxy); n = 0; row = imgdata + rowsize * start_y; for (int y = start_y; y <= end_y; y++, row += rowsize) { int dy = y - peak_y; int dy2 = dy * dy; if (dy2 > A2) continue; for (int x = start_x; x <= end_x; x++) { int dx = x - peak_x; // exclude points outside aperture if (dx * dx + dy2 > A2) continue; // exclude points below threshold unsigned short val = row[x]; if (val < thresh) continue; double const d = (double) val - mean_bg; cx += dx * d; cy += dy * d; mass += d; ++n; } } } Mass = mass; SNR = n > 0 ? mass / (sigma_bg * n) : 0.0; double const LOW_SNR = 3.0; if (mass < 10.0) Result = STAR_LOWMASS; else if (SNR < LOW_SNR) Result = STAR_LOWSNR; else { newX = peak_x + cx / mass; newY = peak_y + cy / mass; // even at saturation, the max values may vary a bit due to noise // Call it saturated if the the top three values are within 32 parts per 65535 of max if ((unsigned int)(max3[0] - max3[2]) * 65535U < 32U * (unsigned int) max3[0]) Result = STAR_SATURATED; } } catch (const wxString& Msg) { POSSIBLY_UNUSED(Msg); if (Result == STAR_OK) { Result = STAR_ERROR; } } // update state SetXY(newX, newY); m_lastFindResult = Result; bool bReturn = WasFound(Result); if (!bReturn) { Mass = 0.0; SNR = 0.0; } Debug.AddLine(wxString::Format("Star::Find returns %d (%d), X=%.2f, Y=%.2f, Mass=%.f, SNR=%.1f", bReturn, Result, newX, newY, Mass, SNR)); return bReturn; }
bool GuiderOneStar::AutoSelect(void) { bool bError = false; usImage *pImage = CurrentImage(); try { if (!pImage || !pImage->ImageData) { throw ERROR_INFO("No Current Image"); } // If mount is not calibrated, we need to chose a star a bit farther // from the egde to allow for the motion of the star during // calibration // int edgeAllowance = 0; if (pMount && pMount->IsConnected() && !pMount->IsCalibrated()) edgeAllowance = wxMax(edgeAllowance, pMount->CalibrationTotDistance()); if (pSecondaryMount && pSecondaryMount->IsConnected() && !pSecondaryMount->IsCalibrated()) edgeAllowance = wxMax(edgeAllowance, pSecondaryMount->CalibrationTotDistance()); Star newStar; if (!newStar.AutoFind(*pImage, edgeAllowance, m_searchRegion)) { throw ERROR_INFO("Unable to AutoFind"); } m_massChecker->Reset(); if (!m_star.Find(pImage, m_searchRegion, newStar.X, newStar.Y, Star::FIND_CENTROID)) { throw ERROR_INFO("Unable to find"); } if (SetLockPosition(m_star)) { throw ERROR_INFO("Unable to set Lock Position"); } if (GetState() == STATE_SELECTING) { // immediately advance the state machine now, rather than waiting for // the next exposure to complete. Socket server clients are going to // try to start guiding after selecting the star, but guiding will fail // to start if state is still STATE_SELECTING Debug.AddLine("AutoSelect: state = %d, call UpdateGuideState", GetState()); UpdateGuideState(NULL, false); } UpdateImageDisplay(); pFrame->pProfile->UpdateData(pImage, m_star.X, m_star.Y); } catch (wxString Msg) { if (pImage && pImage->ImageData) { SaveAutoSelectFailedImg(pImage); } POSSIBLY_UNUSED(Msg); bError = true; } return bError; }
bool StepGuider::UpdateCalibrationState(const PHD_Point& currentLocation) { bool bError = false; try { if (!m_calibrationStartingLocation.IsValid()) { m_calibrationStartingLocation = currentLocation; Debug.AddLine(wxString::Format("Stepguider::UpdateCalibrationstate: starting location = %.2f,%.2f", currentLocation.X, currentLocation.Y)); } wxString status0, status1; int stepsRemainingUp = MaxPosition(UP) - CurrentPosition(UP); int stepsRemainingDown = MaxPosition(DOWN) - CurrentPosition(DOWN); int stepsRemainingRight = MaxPosition(RIGHT) - CurrentPosition(RIGHT); int stepsRemainingLeft = MaxPosition(LEFT) - CurrentPosition(LEFT); stepsRemainingUp /= m_calibrationStepsPerIteration; stepsRemainingDown /= m_calibrationStepsPerIteration; stepsRemainingRight /= m_calibrationStepsPerIteration; stepsRemainingLeft /= m_calibrationStepsPerIteration; int stepsRemainingDownAndRight = wxMax(stepsRemainingDown, stepsRemainingRight); assert(stepsRemainingUp >= 0); assert(stepsRemainingDown >= 0); assert(stepsRemainingRight >= 0); assert(stepsRemainingLeft >= 0); assert(stepsRemainingDownAndRight >= 0); bool moveUp = false; bool moveDown = false; bool moveRight = false; bool moveLeft = false; double x_dist; double y_dist; switch (m_calibrationState) { case CALIBRATION_STATE_GOTO_LOWER_RIGHT_CORNER: if (stepsRemainingDownAndRight > 0) { status0.Printf(_("Init Calibration: %3d"), stepsRemainingDownAndRight); moveDown = stepsRemainingDown > 0; moveRight = stepsRemainingRight > 0; break; } Debug.AddLine(wxString::Format("Falling through to state AVERAGE_STARTING_LOCATION, position=(%.2f, %.2f)", currentLocation.X, currentLocation.Y)); m_calibrationAverageSamples = 0; m_calibrationAveragedLocation.SetXY(0.0, 0.0); m_calibrationState = CALIBRATION_STATE_AVERAGE_STARTING_LOCATION; // fall through case CALIBRATION_STATE_AVERAGE_STARTING_LOCATION: m_calibrationAverageSamples++; m_calibrationAveragedLocation += currentLocation; status0.Printf(_("Averaging: %3d"), m_samplesToAverage - m_calibrationAverageSamples + 1); if (m_calibrationAverageSamples < m_samplesToAverage ) { break; } m_calibrationAveragedLocation /= m_calibrationAverageSamples; m_calibrationStartingLocation = m_calibrationAveragedLocation; m_calibrationIterations = 0; Debug.AddLine(wxString::Format("Falling through to state GO_LEFT, startinglocation=(%.2f, %.2f)", m_calibrationStartingLocation.X, m_calibrationStartingLocation.Y)); m_calibrationState = CALIBRATION_STATE_GO_LEFT; // fall through case CALIBRATION_STATE_GO_LEFT: if (stepsRemainingLeft > 0) { status0.Printf(_("Left Calibration: %3d"), stepsRemainingLeft); m_calibrationIterations++; moveLeft = true; x_dist = m_calibrationStartingLocation.dX(currentLocation); y_dist = m_calibrationStartingLocation.dY(currentLocation); GuideLog.CalibrationStep(this, "Left", stepsRemainingLeft, x_dist, y_dist, currentLocation, m_calibrationStartingLocation.Distance(currentLocation)); m_calibrationDetails.raSteps.push_back(wxRealPoint(x_dist, y_dist)); // Just put "left" in "ra" steps break; } Debug.AddLine(wxString::Format("Falling through to state AVERAGE_CENTER_LOCATION, position=(%.2f, %.2f)", currentLocation.X, currentLocation.Y)); m_calibrationAverageSamples = 0; m_calibrationAveragedLocation.SetXY(0.0, 0.0); m_calibrationState = CALIBRATION_STATE_AVERAGE_CENTER_LOCATION; // fall through case CALIBRATION_STATE_AVERAGE_CENTER_LOCATION: m_calibrationAverageSamples++; m_calibrationAveragedLocation += currentLocation; status0.Printf(_("Averaging: %3d"), m_samplesToAverage -m_calibrationAverageSamples+1); if (m_calibrationAverageSamples < m_samplesToAverage ) { break; } m_calibrationAveragedLocation /= m_calibrationAverageSamples; m_calibration.xAngle = m_calibrationStartingLocation.Angle(m_calibrationAveragedLocation); m_calibration.xRate = m_calibrationStartingLocation.Distance(m_calibrationAveragedLocation) / (m_calibrationIterations * m_calibrationStepsPerIteration); status1.Printf(_("angle=%.1f rate=%.2f"), m_calibration.xAngle * 180. / M_PI, m_calibration.xRate); GuideLog.CalibrationDirectComplete(this, "Left", m_calibration.xAngle, m_calibration.xRate); Debug.AddLine(wxString::Format("LEFT calibration completes with angle=%.1f rate=%.2f", m_calibration.xAngle * 180. / M_PI, m_calibration.xRate)); Debug.AddLine(wxString::Format("distance=%.2f iterations=%d", m_calibrationStartingLocation.Distance(m_calibrationAveragedLocation), m_calibrationIterations)); m_calibrationStartingLocation = m_calibrationAveragedLocation; m_calibrationIterations = 0; m_calibrationState = CALIBRATION_STATE_GO_UP; Debug.AddLine(wxString::Format("Falling through to state GO_UP, startinglocation=(%.2f, %.2f)", m_calibrationStartingLocation.X, m_calibrationStartingLocation.Y)); // fall through case CALIBRATION_STATE_GO_UP: if (stepsRemainingUp > 0) { status0.Printf(_("up Calibration: %3d"), stepsRemainingUp); m_calibrationIterations++; moveUp = true; x_dist = m_calibrationStartingLocation.dX(currentLocation); y_dist = m_calibrationStartingLocation.dY(currentLocation); GuideLog.CalibrationStep(this, "Up", stepsRemainingLeft, x_dist, y_dist, currentLocation, m_calibrationStartingLocation.Distance(currentLocation)); m_calibrationDetails.decSteps.push_back(wxRealPoint(x_dist, y_dist)); // Just put "up" in "dec" steps break; } Debug.AddLine(wxString::Format("Falling through to state AVERAGE_ENDING_LOCATION, position=(%.2f, %.2f)", currentLocation.X, currentLocation.Y)); m_calibrationAverageSamples = 0; m_calibrationAveragedLocation.SetXY(0.0, 0.0); m_calibrationState = CALIBRATION_STATE_AVERAGE_ENDING_LOCATION; // fall through case CALIBRATION_STATE_AVERAGE_ENDING_LOCATION: m_calibrationAverageSamples++; m_calibrationAveragedLocation += currentLocation; status0.Printf(_("Averaging: %3d"), m_samplesToAverage -m_calibrationAverageSamples+1); if (m_calibrationAverageSamples < m_samplesToAverage ) { break; } m_calibrationAveragedLocation /= m_calibrationAverageSamples; m_calibration.yAngle = m_calibrationAveragedLocation.Angle(m_calibrationStartingLocation); m_calibration.yRate = m_calibrationStartingLocation.Distance(m_calibrationAveragedLocation) / (m_calibrationIterations * m_calibrationStepsPerIteration); status1.Printf(_("angle=%.1f rate=%.2f"), m_calibration.yAngle * 180. / M_PI, m_calibration.yRate); GuideLog.CalibrationDirectComplete(this, "Up", m_calibration.yAngle, m_calibration.yRate); Debug.AddLine(wxString::Format("UP calibration completes with angle=%.1f rate=%.2f", m_calibration.yAngle * 180. / M_PI, m_calibration.yRate)); Debug.AddLine(wxString::Format("distance=%.2f iterations=%d", m_calibrationStartingLocation.Distance(m_calibrationAveragedLocation), m_calibrationIterations)); m_calibrationStartingLocation = m_calibrationAveragedLocation; m_calibrationState = CALIBRATION_STATE_RECENTER; Debug.AddLine(wxString::Format("Falling through to state RECENTER, position=(%.2f, %.2f)", currentLocation.X, currentLocation.Y)); // fall through case CALIBRATION_STATE_RECENTER: status0.Printf(_("Finish Calibration: %3d"), stepsRemainingDownAndRight/2); moveRight = (CurrentPosition(LEFT) >= m_calibrationStepsPerIteration); moveDown = (CurrentPosition(UP) >= m_calibrationStepsPerIteration); if (moveRight || moveDown) { Debug.AddLine(wxString::Format("CurrentPosition(LEFT)=%d CurrentPosition(UP)=%d", CurrentPosition(LEFT), CurrentPosition(UP))); break; } m_calibrationState = CALIBRATION_STATE_COMPLETE; Debug.AddLine(wxString::Format("Falling through to state COMPLETE, position=(%.2f, %.2f)", currentLocation.X, currentLocation.Y)); // fall through case CALIBRATION_STATE_COMPLETE: m_calibration.declination = 0.; m_calibration.pierSide = PIER_SIDE_UNKNOWN; m_calibration.rotatorAngle = Rotator::RotatorPosition(); SetCalibration(m_calibration); SetCalibrationDetails(m_calibrationDetails, m_calibration.xAngle, m_calibration.yAngle); status1 = _T("calibration complete"); GuideLog.CalibrationComplete(this); Debug.AddLine("Calibration Complete"); break; default: assert(false); break; } if (moveUp) { assert(!moveDown); pFrame->ScheduleCalibrationMove(this, UP, m_calibrationStepsPerIteration); } if (moveDown) { assert(!moveUp); pFrame->ScheduleCalibrationMove(this, DOWN, m_calibrationStepsPerIteration); } if (moveRight) { assert(!moveLeft); pFrame->ScheduleCalibrationMove(this, RIGHT, m_calibrationStepsPerIteration); } if (moveLeft) { assert(!moveRight); pFrame->ScheduleCalibrationMove(this, LEFT, m_calibrationStepsPerIteration); } if (m_calibrationState != CALIBRATION_STATE_COMPLETE) { if (status1.IsEmpty()) { double dX = m_calibrationStartingLocation.dX(currentLocation); double dY = m_calibrationStartingLocation.dY(currentLocation); double dist = m_calibrationStartingLocation.Distance(currentLocation); status1.Printf(_T("dx=%4.1f dy=%4.1f dist=%4.1f"), dX, dY, dist); } } if (!status0.IsEmpty()) { pFrame->SetStatusText(status0, 0); } if (!status1.IsEmpty()) { pFrame->SetStatusText(status1, 1); } } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); bError = true; ClearCalibration(); } return bError; }
Mount::MOVE_RESULT StepGuider::Move(const PHD_Point& cameraVectorEndpoint, bool normalMove) { MOVE_RESULT result = MOVE_OK; try { MOVE_RESULT mountResult = Mount::Move(cameraVectorEndpoint, normalMove); if (mountResult != MOVE_OK) Debug.AddLine("StepGuider::Move: Mount::Move failed!"); if (!m_guidingEnabled) { throw THROW_INFO("Guiding disabled"); } // keep a moving average of the AO position if (m_avgOffset.IsValid()) { static double const alpha = .33; // moderately high weighting for latest sample m_avgOffset.X += alpha * (m_xOffset - m_avgOffset.X); m_avgOffset.Y += alpha * (m_yOffset - m_avgOffset.Y); } else { m_avgOffset.SetXY((double) m_xOffset, (double) m_yOffset); } pFrame->pStepGuiderGraph->AppendData(m_xOffset, m_yOffset, m_avgOffset); // consider bumping the secondary mount if this is a normal move if (normalMove && pSecondaryMount && pSecondaryMount->IsConnected()) { int absX = abs(CurrentPosition(RIGHT)); int absY = abs(CurrentPosition(UP)); bool isOutside = absX > m_xBumpPos1 || absY > m_yBumpPos1; bool forceStartBump = false; if (m_forceStartBump) { Debug.Write("stepguider::Move: will start forced bump\n"); forceStartBump = true; m_forceStartBump = false; } // if the current bump has not brought us in, increase the bump size if (isOutside && m_bumpInProgress) { if (absX > m_xBumpPos2 || absY > m_yBumpPos2) { Debug.AddLine("FAR outside bump range, increase bump weight %.2f => %.2f", m_bumpStepWeight, m_bumpStepWeight + 1.0); m_bumpStepWeight += 1.0; } else { Debug.AddLine("outside bump range, increase bump weight %.2f => %.2f", m_bumpStepWeight, m_bumpStepWeight + 1./6.); m_bumpStepWeight += 1./6.; } } // if we are back inside, decrease the bump weight if (!isOutside && m_bumpStepWeight > 1.0) { double prior = m_bumpStepWeight; m_bumpStepWeight *= 0.5; if (m_bumpStepWeight < 1.0) m_bumpStepWeight = 1.0; Debug.AddLine("back inside bump range: decrease bump weight %.2f => %.2f", prior, m_bumpStepWeight); } if (m_bumpInProgress && !m_bumpTimeoutAlertSent) { long now = ::wxGetUTCTime(); if (now - m_bumpStartTime > BumpWarnTime) { pFrame->Alert(_("A mount \"bump\" was needed to bring the AO back to its center position,\n" "but the bump did not complete in a reasonable amount of time.\n" "You probably need to increase the AO Bump Step setting."), wxICON_INFORMATION); m_bumpTimeoutAlertSent = true; } } if ((isOutside || forceStartBump) && !m_bumpInProgress) { // start a new bump m_bumpInProgress = true; m_bumpStartTime = ::wxGetUTCTime(); m_bumpTimeoutAlertSent = false; Debug.AddLine("starting a new bump"); } // stop the bump if we are "close enough" to the center position if ((!isOutside || forceStartBump) && m_bumpInProgress) { int minDist = m_bumpCenterTolerance; if (m_avgOffset.X * m_avgOffset.X + m_avgOffset.Y * m_avgOffset.Y <= minDist * minDist) { Debug.AddLine("Stop bumping, close enough to center -- clearing m_bumpInProgress"); m_bumpInProgress = false; pFrame->pStepGuiderGraph->ShowBump(PHD_Point()); } } } if (m_bumpInProgress && pSecondaryMount->IsBusy()) Debug.AddLine("secondary mount is busy, cannot bump"); // if we have a bump in progress and the secondary mount is not moving, // schedule another move if (m_bumpInProgress && !pSecondaryMount->IsBusy()) { // compute incremental bump based on average position PHD_Point vectorEndpoint(xRate() * -m_avgOffset.X, yRate() * -m_avgOffset.Y); // we have to transform our notion of where we are (which is in "AO Coordinates") // into "Camera Coordinates" so we can bump the secondary mount to put us closer // to the center of the AO PHD_Point bumpVec; if (TransformMountCoordinatesToCameraCoordinates(vectorEndpoint, bumpVec)) { throw ERROR_INFO("MountToCamera failed"); } Debug.AddLine("incremental bump (%.3f, %.3f) isValid = %d", bumpVec.X, bumpVec.Y, bumpVec.IsValid()); double maxBumpPixelsX = m_calibration.xRate * m_bumpMaxStepsPerCycle * m_bumpStepWeight; double maxBumpPixelsY = m_calibration.yRate * m_bumpMaxStepsPerCycle * m_bumpStepWeight; double len = bumpVec.Distance(); double xBumpSize = bumpVec.X * maxBumpPixelsX / len; double yBumpSize = bumpVec.Y * maxBumpPixelsY / len; PHD_Point thisBump(xBumpSize, yBumpSize); // display the current bump vector on the stepguider graph { PHD_Point tcur; TransformCameraCoordinatesToMountCoordinates(thisBump, tcur); tcur.X /= xRate(); tcur.Y /= yRate(); pFrame->pStepGuiderGraph->ShowBump(tcur); } Debug.AddLine("Scheduling Mount bump of (%.3f, %.3f)", thisBump.X, thisBump.Y); pFrame->ScheduleSecondaryMove(pSecondaryMount, thisBump, false); } } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); result = MOVE_ERROR; } return result; }
bool usImage::Save(const wxString& fname, const wxString& hdrNote) const { bool bError = false; try { long fsize[3] = { (long)Size.GetWidth(), (long)Size.GetHeight(), 0L, }; long fpixel[3] = { 1, 1, 1 }; fitsfile *fptr; // FITS file pointer int status = 0; // CFITSIO status value MUST be initialized to zero! PHD_fits_create_file(&fptr, fname, true, &status); fits_create_img(fptr, USHORT_IMG, 2, fsize, &status); FITSHdrWriter hdr(fptr, &status); float exposure = (float) ImgExpDur / 1000.0; hdr.write("EXPOSURE", exposure, "Exposure time in seconds"); if (ImgStackCnt > 1) hdr.write("STACKCNT", (unsigned int) ImgStackCnt, "Stacked frame count"); if (!hdrNote.IsEmpty()) hdr.write("USERNOTE", static_cast<const char *>(hdrNote), 0); time_t now = wxDateTime::GetTimeNow(); struct tm *timestruct = gmtime(&now); char buf[100]; sprintf(buf, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d", timestruct->tm_year + 1900, timestruct->tm_mon + 1, timestruct->tm_mday, timestruct->tm_hour, timestruct->tm_min, timestruct->tm_sec); hdr.write("DATE", buf, "Time FITS file was created"); hdr.write("DATE-OBS", GetImgStartTime().c_str(), "Time image was captured"); hdr.write("CREATOR", wxString(APPNAME _T(" ") FULLVER).c_str(), "Capture software"); if (pCamera) { hdr.write("INSTRUME", pCamera->Name.c_str(), "Instrument name"); unsigned int b = pCamera->Binning; hdr.write("XBINNING", b, "Camera X Bin"); hdr.write("YBINNING", b, "Camera Y Bin"); hdr.write("CCDXBIN", b, "Camera X Bin"); hdr.write("CCDYBIN", b, "Camera Y Bin"); float sz = b * pCamera->PixelSize; hdr.write("XPIXSZ", sz, "pixel size in microns (with binning)"); hdr.write("YPIXSZ", sz, "pixel size in microns (with binning)"); } if (pPointingSource) { double ra, dec, st; pPointingSource->GetCoordinates(&ra, &dec, &st); hdr.write("RA", (float) (ra * 360.0 / 24.0), "Object Right Ascension in degrees"); hdr.write("DEC", (float) dec, "Object Declination in degrees"); { int h = (int) ra; ra -= h; ra *= 60.0; int m = (int) ra; ra -= m; ra *= 60.0; hdr.write("OBJCTRA", wxString::Format("%02d %02d %06.3f", h, m, ra).c_str(), "Object Right Ascension in hms"); } { int sign = dec < 0.0 ? -1 : +1; dec *= sign; int d = (int) dec; dec -= d; dec *= 60.0; int m = (int) dec; dec -= m; dec *= 60.0; hdr.write("OBJCTDEC", wxString::Format("%c%d %02d %06.3f", sign < 0 ? '-' : '+', d, m, dec).c_str(), "Object Declination in dms"); } } float sc = (float) pFrame->GetCameraPixelScale(); hdr.write("SCALE", sc, "Image scale (arcsec / pixel)"); hdr.write("PIXSCALE", sc, "Image scale (arcsec / pixel)"); fits_write_pix(fptr, TUSHORT, fpixel, NPixels, ImageData, &status); PHD_fits_close_file(fptr); bError = status ? true : false; } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); bError = true; } return bError; }
bool usImage::Load(const wxString& fname) { bool bError = false; try { if (!wxFileExists(fname)) { pFrame->Alert(_("File does not exist - cannot load ") + fname); throw ERROR_INFO("File does not exist"); } int status = 0; // CFITSIO status value MUST be initialized to zero! fitsfile *fptr; // FITS file pointer if (!PHD_fits_open_diskfile(&fptr, fname, READONLY, &status)) { int hdutype; if (fits_get_hdu_type(fptr, &hdutype, &status) || hdutype != IMAGE_HDU) { pFrame->Alert(_("FITS file is not of an image: ") + fname); throw ERROR_INFO("Fits file is not an image"); } // Get HDUs and size int naxis = 0; fits_get_img_dim(fptr, &naxis, &status); long fsize[3]; fits_get_img_size(fptr, 2, fsize, &status); int nhdus = 0; fits_get_num_hdus(fptr, &nhdus, &status); if ((nhdus != 1) || (naxis != 2)) { pFrame->Alert(_("Unsupported type or read error loading FITS file ") + fname); throw ERROR_INFO("unsupported type"); } if (Init((int) fsize[0], (int) fsize[1])) { pFrame->Alert(_("Memory allocation error loading FITS file ") + fname); throw ERROR_INFO("Memory Allocation failure"); } long fpixel[3] = { 1, 1, 1 }; if (fits_read_pix(fptr, TUSHORT, fpixel, (int)(fsize[0] * fsize[1]), NULL, ImageData, NULL, &status)) { // Read image pFrame->Alert(_("Error reading data from FITS file ") + fname); throw ERROR_INFO("Error reading"); } char *key = const_cast<char *>("EXPOSURE"); float exposure; status = 0; fits_read_key(fptr, TFLOAT, key, &exposure, NULL, &status); if (status == 0) ImgExpDur = (int) (exposure * 1000.0); key = const_cast<char *>("STACKCNT"); int stackcnt; status = 0; fits_read_key(fptr, TINT, key, &stackcnt, NULL, &status); if (status == 0) ImgStackCnt = (int) stackcnt; PHD_fits_close_file(fptr); } else { pFrame->Alert(_("Error opening FITS file ") + fname); throw ERROR_INFO("error opening file"); } } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); bError = true; } return bError; }
bool Camera_WDMClass::Connect(const wxString& camId) { bool bError = false; try { m_deviceNumber = pConfig->Profile.GetInt("/camera/WDM/deviceNumber", -1); m_deviceMode = pConfig->Profile.GetInt("/camera/WDM/deviceMode", -1); if (m_deviceNumber == -1 || m_deviceMode == -1) if (SelectDeviceAndMode()) throw ERROR_INFO("SelectDeviceAndMode failed"); // Setup VidCap library m_pVidCap = CVPlatform::GetPlatform()->AcquireVideoCapture(); // Init the library if (CVFAILED(m_pVidCap->Init())) { wxMessageBox(_T("Error initializing WDM services"),_("Error"),wxOK | wxICON_ERROR); throw ERROR_INFO("CVFAILED(VidCap->Init())"); } // Connect to camera if (CVSUCCESS(m_pVidCap->Connect(m_deviceNumber))) { int devNameLen = 0; // find the length of the name m_pVidCap->GetDeviceName(NULL, devNameLen); ++devNameLen; // now get the name char *devName = new char[devNameLen]; m_pVidCap->GetDeviceName(devName, devNameLen); Name = wxString::Format("%s", devName); delete[] devName; } else { wxMessageBox(wxString::Format("Error connecting to WDM device #%d", m_deviceNumber), _("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("Error connecting to WDM device"); } if (CVFAILED(m_pVidCap->SetMode(m_deviceMode))) { wxMessageBox(wxString::Format("Error activating video mode %d", m_deviceMode), _("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("setmode() failed"); } // get the x and y size CVVidCapture::VIDCAP_MODE modeInfo; if (CVFAILED(m_pVidCap->GetCurrentMode(modeInfo))) { wxMessageBox(wxString::Format("Error probing video mode %d", m_deviceMode),_("Error"),wxOK | wxICON_ERROR); throw ERROR_INFO("GetCurrentMode() failed"); } FullSize = wxSize(modeInfo.XRes, modeInfo.YRes); // Start the stream m_captureMode = NOT_CAPTURING; // Make sure we don't start saving yet if (CVFAILED(m_pVidCap->StartImageCap(CVImage::CVIMAGE_GREY, CaptureCallback, this))) { wxMessageBox(_T("Failed to start image capture!"),_("Error"),wxOK | wxICON_ERROR); throw ERROR_INFO("StartImageCap() failed"); } pFrame->StatusMsg(wxString::Format("%d x %d mode activated", modeInfo.XRes, modeInfo.YRes)); Connected = true; } catch (const wxString& msg) { POSSIBLY_UNUSED(msg); bError = true; if (m_pVidCap) { m_pVidCap->Uninit(); CVPlatform::GetPlatform()->Release(m_pVidCap); m_pVidCap = NULL; } } return bError; }
Mount::MOVE_RESULT ScopeASCOM::Guide(GUIDE_DIRECTION direction, int duration) { MOVE_RESULT result = MOVE_OK; try { Debug.AddLine("Guiding Dir = %d, Dur = %d", direction, duration); if (!IsConnected()) { throw ERROR_INFO("ASCOM Scope: attempt to guide when not connected"); } if (!m_canPulseGuide) { // Could happen if move command is issued on the Aux mount or CanPulseGuide property got changed on the fly pFrame->Alert(_("ASCOM driver does not support PulseGuide")); throw ERROR_INFO("ASCOM scope: guide command issued but PulseGuide not supported"); } GITObjRef scope(m_gitEntry); // First, check to see if already moving CheckSlewing(&scope, &result); if (IsGuiding(&scope)) { Debug.AddLine("Entered PulseGuideScope while moving"); int i; for (i = 0; i < 20; i++) { wxMilliSleep(50); CheckSlewing(&scope, &result); if (!IsGuiding(&scope)) break; Debug.AddLine("Still moving"); } if (i == 20) { Debug.AddLine("Still moving after 1s - aborting"); throw ERROR_INFO("ASCOM Scope: scope is still moving after 1 second"); } else { Debug.AddLine("Movement stopped - continuing"); } } // Do the move VARIANTARG rgvarg[2]; rgvarg[1].vt = VT_I2; rgvarg[1].iVal = direction; rgvarg[0].vt = VT_I4; rgvarg[0].lVal = (long) duration; DISPPARAMS dispParms; dispParms.cArgs = 2; dispParms.rgvarg = rgvarg; dispParms.cNamedArgs = 0; dispParms.rgdispidNamedArgs = NULL; wxStopWatch swatch; HRESULT hr; EXCEPINFO excep; Variant vRes; if (FAILED(hr = scope.IDisp()->Invoke(dispid_pulseguide, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispParms, &vRes, &excep, NULL))) { Debug.AddLine(wxString::Format("pulseguide: [%x] %s", hr, _com_error(hr).ErrorMessage())); // Make sure nothing got by us and the mount can really handle pulse guide - HIGHLY unlikely if (scope.GetProp(&vRes, L"CanPulseGuide") && vRes.boolVal != VARIANT_TRUE) { Debug.AddLine("Tried to guide mount that has no PulseGuide support"); // This will trigger a nice alert the next time through Guide m_canPulseGuide = false; } throw ERROR_INFO("ASCOM Scope: pulseguide command failed: " + ExcepMsg(excep)); } long elapsed = swatch.Time(); if (elapsed < (long)duration) { unsigned long rem = (unsigned long)((long)duration - elapsed); Debug.AddLine("PulseGuide returned control before completion, sleep %lu", rem + 10); if (WorkerThread::MilliSleep(rem + 10)) throw ERROR_INFO("ASCOM Scope: thread terminate requested"); } if (IsGuiding(&scope)) { Debug.AddLine("scope still moving after pulse duration time elapsed"); // try waiting a little longer. If scope does not stop moving after 1 second, try doing AbortSlew // if it still does not stop after 2 seconds, bail out with an error enum { GRACE_PERIOD_MS = 1000, TIMEOUT_MS = GRACE_PERIOD_MS + 1000, }; bool timeoutExceeded = false; bool didAbortSlew = false; while (true) { ::wxMilliSleep(20); if (WorkerThread::InterruptRequested()) throw ERROR_INFO("ASCOM Scope: thread interrupt requested"); CheckSlewing(&scope, &result); if (!IsGuiding(&scope)) { Debug.AddLine("scope move finished after %ld + %ld ms", (long)duration, swatch.Time() - (long)duration); break; } long now = swatch.Time(); if (!didAbortSlew && now > duration + GRACE_PERIOD_MS && m_abortSlewWhenGuidingStuck) { Debug.AddLine("scope still moving after %ld + %ld ms, try aborting slew", (long)duration, now - (long)duration); AbortSlew(&scope); didAbortSlew = true; continue; } if (now > duration + TIMEOUT_MS) { timeoutExceeded = true; break; } } if (timeoutExceeded && IsGuiding(&scope)) { throw ERROR_INFO("timeout exceeded waiting for guiding pulse to complete"); } } } catch (const wxString& msg) { POSSIBLY_UNUSED(msg); if (result == MOVE_OK) { result = MOVE_ERROR; if (!WorkerThread::InterruptRequested()) { pFrame->Alert(_("PulseGuide command to mount has failed - guiding is likely to be ineffective.")); } } } if (result == MOVE_STOP_GUIDING) { if (pConfig->Global.GetBoolean(SlewWarningEnabledKey(), true)) { pFrame->Alert(_("Guiding stopped: the scope started slewing."), _("Don't show\nthis again"), SuppressSlewAlert, 0); } } return result; }
bool Camera_WDMClass::SelectDeviceAndMode() { bool error = false; CVVidCapture *vidCap = 0; bool inited = false; bool connected = false; try { vidCap = CVPlatform::GetPlatform()->AcquireVideoCapture(); // Init the library if (CVFAILED(vidCap->Init())) { wxMessageBox(_T("Error initializing WDM services"),_("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("CVFAILED(VidCap->Init())"); } inited = true; // Enumerate devices int nDevices; if (CVFAILED(vidCap->GetNumDevices(nDevices))) { wxMessageBox(_T("Error detecting WDM devices"), _("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("CVFAILED(m_pVidCap->GetNumDevices(nDevices))"); } int deviceNumber; if (nDevices == 0) { deviceNumber = 0; } else { wxArrayString devices; for (int i = 0; i < nDevices; i++) { CVVidCapture::VIDCAP_DEVICE devInfo; if (CVSUCCESS(vidCap->GetDeviceInfo(i, devInfo))) { devices.Add(wxString::Format("%d: %s", i, devInfo.DeviceString)); } else { devices.Add(wxString::Format("%d: Not available"),i); } } deviceNumber = wxGetSingleChoiceIndex(_("Select WDM camera"), _("Camera choice"), devices); if (deviceNumber == -1) { throw ERROR_INFO("deviceNumber == -1"); } } // Connect to camera if (!CVSUCCESS(vidCap->Connect(deviceNumber))) { wxMessageBox(wxString::Format("Error connecting to WDM device #%d", deviceNumber), _("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("Error connecting to WDM device"); } connected = true; int numModes = 0; vidCap->GetNumSupportedModes(numModes); wxArrayString modeNames; for (int curmode = 0; curmode < numModes; curmode++) { CVVidCapture::VIDCAP_MODE modeInfo; if (CVSUCCESS(vidCap->GetModeInfo(curmode, modeInfo))) { modeNames.Add(wxString::Format("%dx%d (%s)", modeInfo.XRes, modeInfo.YRes, vidCap->GetFormatModeName(modeInfo.InputFormat))); } } // Let user choose mode int deviceMode = wxGetSingleChoiceIndex(_("Select camera mode"), _("Camera mode"), modeNames); if (deviceMode == -1) { // canceled throw ERROR_INFO("user did not choose a mode"); } m_deviceNumber = deviceNumber; m_deviceMode = deviceMode; pConfig->Profile.SetInt("/camera/WDM/deviceNumber", m_deviceNumber); pConfig->Profile.SetInt("/camera/WDM/deviceMode", m_deviceMode); } catch (const wxString& msg) { POSSIBLY_UNUSED(msg); error = true; } if (vidCap) { if (connected) vidCap->Disconnect(); if (inited) vidCap->Uninit(); CVPlatform::GetPlatform()->Release(vidCap); } return error; }
void MyFrame::GuideButtonClick(bool interactive) { try { if (pMount == NULL) { // no mount selected -- should never happen throw ERROR_INFO("pMount == NULL"); } if (!pMount->IsConnected()) { throw ERROR_INFO("Unable to guide with no scope Connected"); } if (!pCamera || !pCamera->Connected) { throw ERROR_INFO("Unable to guide with no camera Connected"); } if (pGuider->GetState() < STATE_SELECTED) { wxMessageBox(_T("Please select a guide star before attempting to guide")); throw ERROR_INFO("Unable to guide with state < STATE_SELECTED"); } ValidateDarksLoaded(); if (wxGetKeyState(WXK_SHIFT)) { bool recalibrate = true; if (pMount->IsCalibrated() || (pSecondaryMount && pSecondaryMount->IsCalibrated())) { recalibrate = ConfirmDialog::Confirm(_("Are you sure you want force recalibration?"), "/force_recalibration_ok", _("Force Recalibration")); } if (recalibrate) { pMount->ClearCalibration(); if (pSecondaryMount) pSecondaryMount->ClearCalibration(); } } if (interactive && pPointingSource && pPointingSource->IsConnected() && pPointingSource->CanReportPosition()) { bool proceed = true; bool error = pPointingSource->PreparePositionInteractive(); if (!error && fabs(pPointingSource->GetDeclination()) > Scope::DEC_COMP_LIMIT && !TheScope()->IsCalibrated() ) { proceed = ConfirmDialog::Confirm( _("Calibration this far from the celestial equator will be error-prone. For best results, calibrate at a declination of -20 to +20."), "/highdec_calibration_ok", _("Confirm Calibration at Large Declination") ); } if (error || !proceed) return; } StartGuiding(); } catch (const wxString& Msg) { POSSIBLY_UNUSED(Msg); pGuider->Reset(false); } }
GuideCamera *GuideCamera::Factory(const wxString& choice) { GuideCamera *pReturn = NULL; try { if (choice.IsEmpty()) { throw ERROR_INFO("CameraFactory called with choice.IsEmpty()"); } Debug.AddLine(wxString::Format("CameraFactory(%s)", choice)); if (false) // so else ifs can follow { } #if defined (ASCOM_LATECAMERA) // do ascom first since it includes many choices, some of which match other choices below (like Simulator) else if (choice.Find(_T("ASCOM")) != wxNOT_FOUND) { pReturn = new Camera_ASCOMLateClass(choice); } #endif else if (choice.Find(_("None")) + 1) { } else if (choice.Find(_T("Simulator")) + 1) { pReturn = new Camera_SimClass(); } #if defined (SAC42) else if (choice.Find(_T("SAC4-2")) + 1) { pReturn = new Camera_SAC42Class(); } #endif #if defined (ATIK16) else if (choice.Find(_T("Atik 16 series")) + 1) { Camera_Atik16Class *pNewGuideCamera = new Camera_Atik16Class(); pNewGuideCamera->HSModel = false; if (choice.Find(_T("color"))) pNewGuideCamera->Color = true; else pNewGuideCamera->Color = false; pReturn = pNewGuideCamera; } #endif #if defined (ATIK_GEN3) else if (choice.Find(_T("Atik Gen3")) + 1) { Camera_Atik16Class *pNewGuideCamera = new Camera_Atik16Class(); pNewGuideCamera->HSModel = true; if (choice.Find(_T("color"))) pNewGuideCamera->Color = true; else pNewGuideCamera->Color = false; pReturn = pNewGuideCamera; } #endif #if defined (QGUIDE) else if (choice.Find(_T("CCD Labs Q-Guider")) + 1) { pReturn = new Camera_QGuiderClass(); pReturn->Name = _T("Q-Guider"); } else if (choice.Find(_T("MagZero MZ-5")) + 1) { pReturn = new Camera_QGuiderClass(); pReturn->Name = _T("MagZero MZ-5"); } #endif #if defined (QHY_CAMERA) else if (choice.Find(_T("QHY Camera")) != wxNOT_FOUND) { pReturn = new Camera_QHY(); } #endif #if defined(ALTAIR) else if (choice.Find(_T("Altair Camera")) + 1) { pReturn = new Camera_Altair(); } #endif #if defined(ZWO_ASI) else if (choice.Find(_T("ZWO ASI Camera")) + 1) { pReturn = new Camera_ZWO(); } #endif #if defined (CAM_QHY5) // must come afer other QHY 5's since this pattern would match them else if (choice.Find(_T("QHY 5")) + 1) { pReturn = new Camera_QHY5Class(); } #endif #if defined (OPENSSAG) else if (choice.Find(_T("Orion StarShoot Autoguider")) + 1) { pReturn = new Camera_OpenSSAGClass(); } #endif #if defined (KWIQGUIDER) else if (choice.Find(_T("KWIQGuider")) + 1) { pReturn = new Camera_KWIQGuiderClass(); } #endif #if defined (SSAG) else if (choice.Find(_T("StarShoot Autoguider")) + 1) { pReturn = new Camera_SSAGClass(); } #endif #if defined (SSPIAG) else if (choice.Find(_T("StarShoot Planetary Imager & Autoguider")) + 1) { pReturn = new Camera_SSPIAGClass(); } #endif #if defined (ORION_DSCI) else if (choice.Find(_T("Orion StarShoot DSCI")) + 1) { pReturn = new Camera_StarShootDSCIClass(); } #endif #if defined (OPENCV_CAMERA) else if (choice.Find(_T("OpenCV webcam")) + 1) { int dev = 0; if (choice.Find(_T("2")) + 1) { dev = 1; } pReturn = new Camera_OpenCVClass(dev); } #endif #if defined (WDM_CAMERA) else if (choice.Find(_T("Windows WDM")) + 1) { pReturn = new Camera_WDMClass(); } #endif #if defined (VFW_CAMERA) else if (choice.Find(_T("Windows VFW")) + 1) { pReturn = new Camera_VFWClass(); } #endif #if defined (LE_SERIAL_CAMERA) else if (choice.Find(_T("Long exposure Serial webcam")) + 1) { pReturn = new Camera_LESerialWebcamClass(); } #endif #if defined (LE_PARALLEL_CAMERA) else if (choice.Find( _T("Long exposure Parallel webcam")) + 1) { pReturn = new Camera_LEParallelWebcamClass(); } #endif #if defined (LE_LXUSB_CAMERA) else if (choice.Find( _T("Long exposure LXUSB webcam")) + 1) { pReturn = new Camera_LELxUsbWebcamClass(); } #endif #if defined (MEADE_DSI) else if (choice.Find(_T("Meade DSI I, II, or III")) + 1) { pReturn = new Camera_DSIClass(); } #endif #if defined (STARFISH) else if (choice.Find(_T("Fishcamp Starfish")) + 1) { pReturn = new Camera_StarfishClass(); } #endif #if defined (SXV) else if (choice.Find(_T("Starlight Xpress SXV")) + 1) { pReturn = new Camera_SXVClass(); } #endif #if defined (OS_PL130) else if (choice.Find(_T("Opticstar PL-130M")) + 1) { Camera_OSPL130.Color=false; Camera_OSPL130.Name=_T("Opticstar PL-130M"); pReturn = new Camera_OSPL130Class(); } else if (choice.Find(_T("Opticstar PL-130C")) + 1) { Camera_OSPL130.Color=true; Camera_OSPL130.Name=_T("Opticstar PL-130C"); pReturn = new Camera_OSPL130Class(); } #endif #if defined (NEB_SBIG) else if (choice.Find(_T("Nebulosity")) + 1) { pReturn = new Camera_NebSBIGClass(); } #endif #if defined (SBIGROTATOR_CAMERA) // must go above SBIG else if (choice.Find(_T("SBIG Rotator")) + 1) { pReturn = new Camera_SBIGRotatorClass(); } #endif #if defined (SBIG) else if (choice.Find(_T("SBIG")) + 1) { pReturn = new Camera_SBIGClass(); } #endif #if defined (FIREWIRE) else if (choice.Find(_T("The Imaging Source (DCAM Firewire)")) + 1) { pReturn = new Camera_FirewireClass(); } #endif #if defined (INOVA_PLC) else if (choice.Find(_T("i-Nova PLC-M")) + 1) { pReturn = new Camera_INovaPLCClass(); } #endif #if defined (INDI_CAMERA) else if (choice.Find(_T("INDI Camera")) + 1) { pReturn = new Camera_INDIClass(); } #endif #if defined (V4L_CAMERA) else if (choice.Find(_T("V4L(2) Camera")) + 1) { // There is at least ONE V4L(2) device ... let's find out exactly DeviceInfo *deviceInfo = NULL; if (1 == Camera_VIDEODEVICE.NumberOfDevices()) { deviceInfo = Camera_VIDEODEVICE.GetDeviceAtIndex(0); Camera_VIDEODEVICE.SetDevice(deviceInfo->getDeviceName()); Camera_VIDEODEVICE.SetVendor(deviceInfo->getVendorId()); Camera_VIDEODEVICE.SetModel(deviceInfo->getModelId()); Camera_VIDEODEVICE.Name = deviceInfo->getProduct(); } else { wxArrayString choices; int choice = 0; if (-1 != (choice = wxGetSinglechoiceIndex(_("Select your camera"), _T("V4L(2) devices"), Camera_VIDEODEVICE.GetProductArray(choices)))) { deviceInfo = Camera_VIDEODEVICE.GetDeviceAtIndex(choice); Camera_VIDEODEVICE.SetDevice(deviceInfo->getDeviceName()); Camera_VIDEODEVICE.SetVendor(deviceInfo->getVendorId()); Camera_VIDEODEVICE.SetModel(deviceInfo->getModelId()); Camera_VIDEODEVICE.Name = deviceInfo->getProduct(); } else { throw ERROR_INFO("Camerafactory invalid V4L choice"); } } pReturn = new Camera_VIDEODEVICEClass(); } #endif else { throw ERROR_INFO("CameraFactory: Unknown camera choice"); } } catch (const wxString& Msg) { POSSIBLY_UNUSED(Msg); if (pReturn) { delete pReturn; pReturn = NULL; } } return pReturn; }
/* * OnExposeComplete is the dispatch routine that is called when an image has been taken * by the background thread. * * It: * - causes the image to be redrawn by calling pGuider->UpateImageDisplay() * - calls the routine to update the guider state (which may do nothing) * - calls any other appropriate state update routine depending upon the current state * - updates button state based on appropriate state variables * - schedules another exposure if CaptureActive is stil true * */ void MyFrame::OnExposeComplete(usImage *pNewFrame, bool err) { try { Debug.Write("Processing an image\n"); m_exposurePending = false; if (pGuider->GetPauseType() == PAUSE_FULL) { delete pNewFrame; Debug.Write("guider is paused, ignoring frame, not scheduling exposure\n"); return; } if (err) { Debug.Write("OnExposeComplete(): Capture Error reported\n"); delete pNewFrame; StopCapturing(); if (pGuider->IsCalibratingOrGuiding()) { pGuider->StopGuiding(); pGuider->UpdateImageDisplay(); } pGuider->Reset(false); CaptureActive = m_continueCapturing; UpdateButtonsStatus(); PhdController::AbortController("Error reported capturing image"); SetStatusText(_("Stopped.")); // some camera drivers disconnect the camera on error if (!pCamera->Connected) SetStatusText(wxEmptyString, 2); throw ERROR_INFO("Error reported capturing image"); } ++m_frameCounter; if (m_rawImageMode && !m_rawImageModeWarningDone) { WarnRawImageMode(); m_rawImageModeWarningDone = true; } // check for dark frame compatibility in case the frame size changed (binning changed) if (pCamera->DarkFrameSize() != m_prevDarkFrameSize) { CheckDarkFrameGeometry(); } pGuider->UpdateGuideState(pNewFrame, !m_continueCapturing); pNewFrame = NULL; // the guider owns it now PhdController::UpdateControllerState(); Debug.Write(wxString::Format("OnExposeCompete: CaptureActive=%d m_continueCapturing=%d\n", CaptureActive, m_continueCapturing)); CaptureActive = m_continueCapturing; if (CaptureActive) { ScheduleExposure(); } else { FinishStop(); } } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); UpdateButtonsStatus(); } }
bool WorkerThread::HandleExpose(MyFrame::EXPOSE_REQUEST *req) { bool bError = false; try { if (WorkerThread::MilliSleep(m_pFrame->GetTimeLapse(), INT_ANY)) { throw ERROR_INFO("Time lapse interrupted"); } if (pCamera->HasNonGuiCapture()) { Debug.Write(wxString::Format("Handling exposure in thread, d=%d o=%x r=(%d,%d,%d,%d)\n", req->exposureDuration, req->options, req->subframe.x, req->subframe.y, req->subframe.width, req->subframe.height)); if (GuideCamera::Capture(pCamera, req->exposureDuration, *req->pImage, req->options, req->subframe)) { throw ERROR_INFO("Capture failed"); } } else { Debug.Write(wxString::Format("Handling exposure in myFrame, d=%d o=%x r=(%d,%d,%d,%d)\n", req->exposureDuration, req->options, req->subframe.x, req->subframe.y, req->subframe.width, req->subframe.height)); wxSemaphore semaphore; req->pSemaphore = &semaphore; wxCommandEvent evt(REQUEST_EXPOSURE_EVENT, GetId()); evt.SetClientData(req); wxQueueEvent(m_pFrame, evt.Clone()); // wait for the request to complete req->pSemaphore->Wait(); bError = req->error; req->pSemaphore = NULL; } Debug.AddLine("Exposure complete"); if (!bError) { switch (m_pFrame->GetNoiseReductionMethod()) { case NR_NONE: break; case NR_2x2MEAN: QuickLRecon(*req->pImage); break; case NR_3x3MEDIAN: Median3(*req->pImage); break; } req->pImage->CalcStats(); } } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); bError = true; } return bError; }
bool StepGuider::MoveToCenter() { bool bError = false; try { int positionUpDown = CurrentPosition(UP); if (positionUpDown > 0) { MoveResultInfo result; Move(DOWN, positionUpDown, true, &result); if (result.amountMoved != positionUpDown) { throw ERROR_INFO("MoveToCenter() failed to step DOWN"); } } else if (positionUpDown < 0) { positionUpDown = -positionUpDown; MoveResultInfo result; Move(UP, positionUpDown, true, &result); if (result.amountMoved != positionUpDown) { throw ERROR_INFO("MoveToCenter() failed to step UP"); } } int positionLeftRight = CurrentPosition(LEFT); if (positionLeftRight > 0) { MoveResultInfo result; Move(RIGHT, positionLeftRight, true, &result); if (result.amountMoved != positionLeftRight) { throw ERROR_INFO("MoveToCenter() failed to step RIGHT"); } } else if (positionLeftRight < 0) { positionLeftRight = -positionLeftRight; MoveResultInfo result; Move(LEFT, positionLeftRight, true, &result); if (result.amountMoved != positionLeftRight) { throw ERROR_INFO("MoveToCenter() failed to step LEFT"); } } assert(m_xOffset == 0); assert(m_yOffset == 0); } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); bError = true; } return bError; }
Mount::MOVE_RESULT WorkerThread::HandleMove(MyFrame::PHD_MOVE_REQUEST *pArgs) { Mount::MOVE_RESULT result = Mount::MOVE_OK; try { if (pArgs->pMount->HasNonGuiMove()) { Debug.AddLine(wxString::Format("Handling move in thread for %s dir=%d", pArgs->pMount->GetMountClassName(), pArgs->direction)); if (pArgs->calibrationMove) { Debug.AddLine("calibration move"); result = pArgs->pMount->CalibrationMove(pArgs->direction, pArgs->duration); if (result != Mount::MOVE_OK) { throw ERROR_INFO("CalibrationMove failed"); } } else { Debug.AddLine(wxString::Format("endpoint = (%.2f, %.2f)", pArgs->vectorEndpoint.X, pArgs->vectorEndpoint.Y)); result = pArgs->pMount->Move(pArgs->vectorEndpoint, pArgs->moveType); if (result != Mount::MOVE_OK) { throw ERROR_INFO("Move failed"); } } } else { // we don't have a non-gui guide function, so we send this to the // main frame routine that handles guides requests Debug.AddLine("Sending move to myFrame"); wxSemaphore semaphore; pArgs->pSemaphore = &semaphore; wxCommandEvent evt(REQUEST_MOUNT_MOVE_EVENT, GetId()); evt.SetClientData(pArgs); wxQueueEvent(m_pFrame, evt.Clone()); // wait for the request to complete pArgs->pSemaphore->Wait(); pArgs->pSemaphore = NULL; result = pArgs->moveResult; if (result != Mount::MOVE_OK) { throw ERROR_INFO("myFrame handled move failed"); } } } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); if (result == Mount::MOVE_OK) result = Mount::MOVE_ERROR; } Debug.AddLine(wxString::Format("move complete, result=%d", result)); return result; }
Mount::MOVE_RESULT StepGuider::Move(GUIDE_DIRECTION direction, int steps, bool normalMove, MoveResultInfo *moveResult) { MOVE_RESULT result = MOVE_OK; bool limitReached = false; try { Debug.AddLine(wxString::Format("Move(%d, %d, %d)", direction, steps, normalMove)); // Compute the required guide steps if (!m_guidingEnabled) { throw THROW_INFO("Guiding disabled"); } // Acutally do the guide assert(steps >= 0); if (steps > 0) { int yDirection = 0; int xDirection = 0; switch (direction) { case UP: yDirection = 1; break; case DOWN: yDirection = -1; break; case RIGHT: xDirection = 1; break; case LEFT: xDirection = -1; break; default: throw ERROR_INFO("StepGuider::Move(): invalid direction"); break; } assert(yDirection == 0 || xDirection == 0); assert(yDirection != 0 || xDirection != 0); Debug.AddLine(wxString::Format("stepping direction=%d steps=%d xDirection=%d yDirection=%d", direction, steps, xDirection, yDirection)); if (WouldHitLimit(direction, steps)) { int new_steps = MaxPosition(direction) - 1 - CurrentPosition(direction); Debug.AddLine(wxString::Format("StepGuider step would hit limit: truncate move direction=%d steps=%d => %d", direction, steps, new_steps)); steps = new_steps; limitReached = true; } if (steps > 0) { if (Step(direction, steps)) { throw ERROR_INFO("step failed"); } m_xOffset += xDirection * steps; m_yOffset += yDirection * steps; Debug.AddLine(wxString::Format("stepped: xOffset=%d yOffset=%d", m_xOffset, m_yOffset)); } } } catch (const wxString& Msg) { POSSIBLY_UNUSED(Msg); steps = 0; result = MOVE_ERROR; } if (moveResult) { moveResult->amountMoved = steps; moveResult->limited = limitReached; } return result; }
double GuideAlgorithmResistSwitch::result(double input) { double dReturn = input; m_history.Add(input); m_history.RemoveAt(0); try { if (fabs(input) < m_minMove) { throw THROW_INFO("input < m_minMove"); } if (m_fastSwitchEnabled) { double thresh = 3.0 * m_minMove; if (sign(input) != m_currentSide && fabs(input) > thresh) { Debug.Write(wxString::Format("resist switch: large excursion: input %.2f thresh %.2f direction from %d to %d\n", input, thresh, m_currentSide, sign(input))); // force switch m_currentSide = 0; unsigned int i; for (i = 0; i < HISTORY_SIZE - 3; i++) m_history[i] = 0.0; for (; i < HISTORY_SIZE; i++) m_history[i] = input; } } int decHistory = 0; for (unsigned int i = 0; i < m_history.GetCount(); i++) { if (fabs(m_history[i]) > m_minMove) { decHistory += sign(m_history[i]); } } if (m_currentSide == 0 || sign(m_currentSide) == -sign(decHistory)) { if (abs(decHistory) < 3) { throw THROW_INFO("not compelling enough"); } double oldest = 0.0; double newest = 0.0; for (int i = 0; i < 3; i++) { oldest += m_history[i]; newest += m_history[m_history.GetCount() - (i + 1)]; } if (fabs(newest) <= fabs(oldest)) { throw THROW_INFO("Not getting worse"); } Debug.Write(wxString::Format("switching direction from %d to %d - decHistory=%d oldest=%.2f newest=%.2f\n", m_currentSide, sign(decHistory), decHistory, oldest, newest)); m_currentSide = sign(decHistory); } if (m_currentSide != sign(input)) { throw THROW_INFO("must have overshot -- vetoing move"); } } catch (const wxString& Msg) { POSSIBLY_UNUSED(Msg); dReturn = 0.0; } Debug.Write(wxString::Format("GuideAlgorithmResistSwitch::Result() returns %.2f from input %.2f\n", dReturn, input)); return dReturn * m_aggression; }
bool ScopeASCOM::Connect(void) { bool bError = false; try { Debug.AddLine("Connecting"); if (IsConnected()) { wxMessageBox("Scope already connected",_("Error")); throw ERROR_INFO("ASCOM Scope: Connected - Already Connected"); } DispatchObj pScopeDriver; if (!Create(pScopeDriver)) { wxMessageBox(_T("Could not establish instance of ") + m_choice, _("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("ASCOM Scope: Could not establish ASCOM Scope instance"); } // --- get the dispatch IDs we need ... // ... get the dispatch ID for the Connected property ... if (!pScopeDriver.GetDispatchId(&dispid_connected, L"Connected")) { wxMessageBox(_T("ASCOM driver problem -- cannot connect"),_("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("ASCOM Scope: Could not get the dispatch id for the Connected property"); } // ... get the dispatch ID for the "IsPulseGuiding" property .... m_bCanCheckPulseGuiding = true; if (!pScopeDriver.GetDispatchId(&dispid_ispulseguiding, L"IsPulseGuiding")) { m_bCanCheckPulseGuiding = false; Debug.AddLine("cannot get dispid_ispulseguiding"); // don't fail if we can't get the status on this - can live without it as it's really a safety net for us } // ... get the dispatch ID for the "Slewing" property .... if (!pScopeDriver.GetDispatchId(&dispid_isslewing, L"Slewing")) { wxMessageBox(_T("ASCOM driver missing the Slewing property"),_("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("ASCOM Scope: Could not get the dispatch id for the Slewing property"); } // ... get the dispatch ID for the "PulseGuide" property .... if (!pScopeDriver.GetDispatchId(&dispid_pulseguide, L"PulseGuide")) { wxMessageBox(_T("ASCOM driver missing the PulseGuide property"),_("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("ASCOM Scope: Could not get the dispatch id for the PulseGuide property"); } // ... get the dispatch ID for the "Declination" property .... m_bCanGetCoordinates = true; if (!pScopeDriver.GetDispatchId(&dispid_declination, L"Declination")) { m_bCanGetCoordinates = false; Debug.AddLine("cannot get dispid_declination"); } else if (!pScopeDriver.GetDispatchId(&dispid_rightascension, L"RightAscension")) { Debug.AddLine("cannot get dispid_rightascension"); m_bCanGetCoordinates = false; } else if (!pScopeDriver.GetDispatchId(&dispid_siderealtime, L"SiderealTime")) { Debug.AddLine("cannot get dispid_siderealtime"); m_bCanGetCoordinates = false; } if (!pScopeDriver.GetDispatchId(&dispid_sitelatitude, L"SiteLatitude")) { Debug.AddLine("cannot get dispid_sitelatitude"); } if (!pScopeDriver.GetDispatchId(&dispid_sitelongitude, L"SiteLongitude")) { Debug.AddLine("cannot get dispid_sitelongitude"); } m_bCanSlew = true; if (!pScopeDriver.GetDispatchId(&dispid_slewtocoordinates, L"SlewToCoordinates")) { m_bCanSlew = false; Debug.AddLine("cannot get dispid_slewtocoordinates"); } // ... get the dispatch IDs for the two guide rate properties - if we can't get them, no sweat, doesn't matter for actual guiding // Used for things like calibration sanity checking, backlash clearing, etc. m_bCanGetGuideRates = true; // Likely case, required for any ASCOM driver at V2 or later if (!pScopeDriver.GetDispatchId(&dispid_decguiderate, L"GuideRateDeclination")) { Debug.AddLine("cannot get dispid_decguiderate"); m_bCanGetGuideRates = false; // don't throw if we can't get this one } else if (!pScopeDriver.GetDispatchId(&dispid_raguiderate, L"GuideRateRightAscension")) { Debug.AddLine("cannot get dispid_raguiderate"); m_bCanGetGuideRates = false; // don't throw if we can't get this one } if (!pScopeDriver.GetDispatchId(&dispid_sideofpier, L"SideOfPier")) { Debug.AddLine("cannot get dispid_sideofpier"); dispid_sideofpier = DISPID_UNKNOWN; } if (!pScopeDriver.GetDispatchId(&dispid_abortslew, L"AbortSlew")) { Debug.AddLine("cannot get dispid_abortslew"); dispid_abortslew = DISPID_UNKNOWN; } struct ConnectInBg : public ConnectMountInBg { ScopeASCOM *sa; ConnectInBg(ScopeASCOM *sa_) : sa(sa_) { } bool Entry() { GITObjRef scope(sa->m_gitEntry); // ... set the Connected property to true.... if (!scope.PutProp(sa->dispid_connected, true)) { SetErrorMsg(ExcepMsg(scope.Excep())); return true; } return false; } }; ConnectInBg bg(this); // set the Connected property to true in a background thread if (bg.Run()) { wxMessageBox(_T("ASCOM driver problem during connection: ") + bg.GetErrorMsg(), _("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("ASCOM Scope: Could not set Connected property to true"); } // get the scope name Variant vRes; if (!pScopeDriver.GetProp(&vRes, L"Name")) { wxMessageBox(_T("ASCOM driver problem getting Name property"), _("Error"), wxOK | wxICON_ERROR); throw ERROR_INFO("ASCOM Scope: Could not get the scope name: " + ExcepMsg(pScopeDriver.Excep())); } m_Name = vRes.bstrVal; Debug.AddLine("Scope reports its name as " + m_Name); m_abortSlewWhenGuidingStuck = false; if (m_Name == _T("Gemini Telescope .NET")) { // Gemini2 firmware (2013 Oct 13 version, perhaps others) has been found to contain a // bug where a pulse guide command can fail to complete, with the Guiding property // returning true forever. The firmware developer suggests that PHD2 should issue an // AbortSlew when this condition is detected. Debug.AddLine("ASCOM scope: enabling stuck guide pulse workaround"); m_abortSlewWhenGuidingStuck = true; } // see if we can pulse guide m_bCanPulseGuide = true; if (!pScopeDriver.GetProp(&vRes, L"CanPulseGuide") || !vRes.boolVal) { Debug.AddLine("Connecting to ASCOM scope that does not support PulseGuide"); m_bCanPulseGuide = false; } // see if we can slew if (m_bCanSlew) { if (!pScopeDriver.GetProp(&vRes, L"CanSlew")) { Debug.AddLine("ASCOM scope got error invoking CanSlew: " + ExcepMsg(pScopeDriver.Excep())); m_bCanSlew = false; } else if (!vRes.boolVal) { Debug.AddLine("ASCOM scope reports CanSlew = false"); m_bCanSlew = false; } } pFrame->SetStatusText(Name()+_(" connected")); Scope::Connect(); Debug.AddLine("Connect success"); } catch (wxString Msg) { POSSIBLY_UNUSED(Msg); bError = true; } return bError; }
bool Star::Find(const usImage *pImg, int searchRegion, int base_x, int base_y, FindMode mode) { FindResult Result = STAR_OK; double newX = base_x; double newY = base_y; try { Debug.Write(wxString::Format("Star::Find(%d, %d, %d, %d, (%d,%d,%d,%d))\n", searchRegion, base_x, base_y, mode, pImg->Subframe.x, pImg->Subframe.y, pImg->Subframe.width, pImg->Subframe.height)); if (base_x < 0 || base_y < 0) { throw ERROR_INFO("coordinates are invalid"); } int minx, miny, maxx, maxy; if (pImg->Subframe.IsEmpty()) { minx = miny = 0; maxx = pImg->Size.GetWidth() - 1; maxy = pImg->Size.GetHeight() - 1; } else { minx = pImg->Subframe.GetLeft(); maxx = pImg->Subframe.GetRight(); miny = pImg->Subframe.GetTop(); maxy = pImg->Subframe.GetBottom(); } // search region bounds int start_x = wxMax(base_x - searchRegion, minx); int end_x = wxMin(base_x + searchRegion, maxx); int start_y = wxMax(base_y - searchRegion, miny); int end_y = wxMin(base_y + searchRegion, maxy); const unsigned short *imgdata = pImg->ImageData; int rowsize = pImg->Size.GetWidth(); int peak_x = 0, peak_y = 0; unsigned int peak_val = 0; unsigned short max3[3] = { 0, 0, 0 }; if (mode == FIND_PEAK) { for (int y = start_y; y <= end_y; y++) { for (int x = start_x; x <= end_x; x++) { unsigned short val = imgdata[y * rowsize + x]; if (val > peak_val) { peak_val = val; peak_x = x; peak_y = y; } } } PeakVal = peak_val; } else { // find the peak value within the search region using a smoothing function // also check for saturation for (int y = start_y + 1; y <= end_y - 1; y++) { for (int x = start_x + 1; x <= end_x - 1; x++) { unsigned short p = imgdata[y * rowsize + x]; unsigned int val = 4 * (unsigned int) p + imgdata[(y - 1) * rowsize + (x - 1)] + imgdata[(y - 1) * rowsize + (x + 1)] + imgdata[(y + 1) * rowsize + (x - 1)] + imgdata[(y + 1) * rowsize + (x + 1)] + 2 * imgdata[(y - 1) * rowsize + (x + 0)] + 2 * imgdata[(y + 0) * rowsize + (x - 1)] + 2 * imgdata[(y + 0) * rowsize + (x + 1)] + 2 * imgdata[(y + 1) * rowsize + (x + 0)]; if (val > peak_val) { peak_val = val; peak_x = x; peak_y = y; } if (p > max3[0]) std::swap(p, max3[0]); if (p > max3[1]) std::swap(p, max3[1]); if (p > max3[2]) std::swap(p, max3[2]); } } PeakVal = max3[0]; // raw peak val peak_val /= 16; // smoothed peak value } // meaure noise in the annulus with inner radius A and outer radius B int const A = 7; // inner radius int const B = 12; // outer radius int const A2 = A * A; int const B2 = B * B; // center window around peak value start_x = wxMax(peak_x - B, minx); end_x = wxMin(peak_x + B, maxx); start_y = wxMax(peak_y - B, miny); end_y = wxMin(peak_y + B, maxy); // find the mean and stdev of the background double sum = 0.0; double a = 0.0; double q = 0.0; unsigned int nbg = 0; const unsigned short *row = imgdata + rowsize * start_y; for (int y = start_y; y <= end_y; y++, row += rowsize) { int dy = y - peak_y; int dy2 = dy * dy; for (int x = start_x; x <= end_x; x++) { int dx = x - peak_x; int r2 = dx * dx + dy2; // exclude points not in annulus if (r2 <= A2 || r2 > B2) continue; double const val = (double) row[x]; sum += val; ++nbg; double const k = (double) nbg; double const a0 = a; a += (val - a) / k; q += (val - a0) * (val - a); } } double const mean_bg = sum / (double) nbg; double const sigma2_bg = q / (double) (nbg - 1); double const sigma_bg = sqrt(sigma2_bg); unsigned short thresh; double cx = 0.0; double cy = 0.0; double mass = 0.0; unsigned int n; std::vector<R2M> hfrvec; if (mode == FIND_PEAK) { mass = peak_val; n = 1; thresh = 0; } else { thresh = (unsigned short)(mean_bg + 3.0 * sigma_bg + 0.5); // find pixels over threshold within aperture; compute mass and centroid start_x = wxMax(peak_x - A, minx); end_x = wxMin(peak_x + A, maxx); start_y = wxMax(peak_y - A, miny); end_y = wxMin(peak_y + A, maxy); n = 0; row = imgdata + rowsize * start_y; for (int y = start_y; y <= end_y; y++, row += rowsize) { int dy = y - peak_y; int dy2 = dy * dy; if (dy2 > A2) continue; for (int x = start_x; x <= end_x; x++) { int dx = x - peak_x; // exclude points outside aperture if (dx * dx + dy2 > A2) continue; // exclude points below threshold unsigned short val = row[x]; if (val < thresh) continue; double const d = (double) val - mean_bg; cx += dx * d; cy += dy * d; mass += d; ++n; hfrvec.push_back(R2M(x, y, d)); } } } Mass = mass; // SNR estimate from: Measuring the Signal-to-Noise Ratio S/N of the CCD Image of a Star or Nebula, J.H.Simonetti, 2004 January 8 // http://www.phys.vt.edu/~jhs/phys3154/snr20040108.pdf double const gain = .5; // electrons per ADU, nominal SNR = n > 0 ? mass / sqrt(mass / gain + sigma2_bg * (double) n * (1.0 + 1.0 / (double) nbg)) : 0.0; double const LOW_SNR = 3.0; // a few scattered pixels over threshold can give a false positive // avoid this by requiring the smoothed peak value to be above the threshold if (peak_val <= thresh && SNR >= LOW_SNR) { Debug.Write(wxString::Format("Star::Find false star n=%u nbg=%u bg=%.1f sigma=%.1f thresh=%u peak=%u\n", n, nbg, mean_bg, sigma_bg, thresh, peak_val)); SNR = LOW_SNR - 0.1; } if (mass < 10.0) Result = STAR_LOWMASS; else if (SNR < LOW_SNR) Result = STAR_LOWSNR; else { newX = peak_x + cx / mass; newY = peak_y + cy / mass; HFD = 2.0 * hfr(hfrvec, newX, newY, mass); // even at saturation, the max values may vary a bit due to noise // Call it saturated if the the top three values are within 32 parts per 65535 of max for 16-bit cameras, // or within 1 part per 191 for 8-bit cameras unsigned int d = (unsigned int) (max3[0] - max3[2]); unsigned int mx = (unsigned int) max3[0]; // remove pedestal if (mx >= pImg->Pedestal) mx -= pImg->Pedestal; else mx = 0; // unlikely if (pImg->BitsPerPixel < 12) { if (d * 191U < 1U * mx) Result = STAR_SATURATED; } else { if (d * 65535U < 32U * mx) Result = STAR_SATURATED; } } } catch (const wxString& Msg) { POSSIBLY_UNUSED(Msg); if (Result == STAR_OK) { Result = STAR_ERROR; } } // update state SetXY(newX, newY); m_lastFindResult = Result; bool wasFound = WasFound(Result); if (!IsValid() || Result == STAR_ERROR) { Mass = 0.0; SNR = 0.0; HFD = 0.0; } Debug.Write(wxString::Format("Star::Find returns %d (%d), X=%.2f, Y=%.2f, Mass=%.f, SNR=%.1f, Peak=%hu HFD=%.1f\n", wasFound, Result, newX, newY, Mass, SNR, PeakVal, HFD)); return wasFound; }