Beispiel #1
0
bool Camera_SSAGClass::Capture(int duration, usImage& img, int options, const wxRect& subframe)
{
    // Only does full frames

    unsigned short *dptr;
    int xsize = FullSize.GetWidth();
    int ysize = FullSize.GetHeight();
    bool firstimg = true;

    //qglogfile->AddLine(wxString::Format("Capturing dur %d",duration)); //qglogfile->Write();

    _SSAG_ProgramCamera(0, 0, 1280, 1024, (GuideCameraGain * 63 / 100));

    if (img.Init(FullSize))
    {
        DisconnectWithAlert(CAPT_FAIL_MEMORY);
        return true;
    }

    CameraWatchdog watchdog(duration, GetTimeoutMs());

    _SSAG_ThreadedExposure(duration, NULL);

    //qglogfile->AddLine("Exposure programmed"); //qglogfile->Write();

    if (duration > 100)
    {
        if (WorkerThread::MilliSleep(duration - 100, WorkerThread::INT_ANY) &&
            (WorkerThread::TerminateRequested() || StopExposure()))
        {
            return true;
        }
    }

    while (_SSAG_isExposing())
    {
        wxMilliSleep(50);
        if (WorkerThread::InterruptRequested() &&
            (WorkerThread::TerminateRequested() || StopExposure()))
        {
            return true;
        }
        if (watchdog.Expired())
        {
            DisconnectWithAlert(CAPT_FAIL_TIMEOUT);
            return true;
        }
    }

    //qglogfile->AddLine("Exposure done"); //qglogfile->Write();

    dptr = img.ImageData;
    _SSAG_GETBUFFER(dptr, img.NPixels * 2);

    if (options & CAPTURE_SUBTRACT_DARK) SubtractDark(img);

    //qglogfile->AddLine("Image loaded"); //qglogfile->Write();

    return false;
}
Beispiel #2
0
bool Camera_WDMClass::CaptureOneFrame(usImage& img, int options, const wxRect& subframe)
{
    bool bError = false;

    try
    {
        if (BeginCapture(img, CAPTURE_ONE_FRAME))
        {
            throw ERROR_INFO("BeingCapture() failed");
        }

        EndCapture();

        if (options & CAPTURE_SUBTRACT_DARK)
        {
            SubtractDark(img);
        }
    }
    catch (wxString Msg)
    {
        POSSIBLY_UNUSED(Msg);
        bError = true;
    }

    return bError;
}
Beispiel #3
0
bool Camera_WDMClass::Capture(int duration, usImage& img, int options, const wxRect& subframe)
{
    bool bError = false;

    try
    {
        if (BeginCapture(img, CAPTURE_STACK_FRAMES))
        {
            throw ERROR_INFO("BeingCapture() failed");
        }

        // accumulate for the requested duration
        WorkerThread::MilliSleep(duration, WorkerThread::INT_ANY);

        EndCapture();

        pFrame->StatusMsg(wxString::Format("%d frames", m_nFrames));

        if (options & CAPTURE_SUBTRACT_DARK)
        {
            SubtractDark(img);
        }
    }
    catch (wxString Msg)
    {
        POSSIBLY_UNUSED(Msg);
        bError = true;
    }

    return bError;
}
Beispiel #4
0
bool Camera_SSPIAGClass::Capture(int duration, usImage& img, int options, const wxRect& subframe)
{
// Only does full frames still
    static int last_dur = 0;
    static int last_gain = 60;
    unsigned char *bptr;
    unsigned short *dptr;
    int  x,y;
    int xsize = FullSize.GetWidth();
    int ysize = FullSize.GetHeight();
//  bool firstimg = true;

    if (img.Init(FullSize)) {
        DisconnectWithAlert(CAPT_FAIL_MEMORY);
        return true;
    }

    if (duration != last_dur) {
        Q5V_SetLongExpTime(duration);
        last_dur = duration;
    }
    else if (GuideCameraGain != last_gain) {
        Q5V_SetQHY5VGlobalGain(GuideCameraGain * 63 / 100);
        last_gain = GuideCameraGain;
//      Q5V_GetFullSizeImage(RawBuffer);
    }

    bptr = RawBuffer;
    Q5V_GetFullSizeImage(bptr);

    // Load and crop from the 800 x 525 image that came in
    dptr = img.ImageData;
    for (y=0; y<ysize; y++) {
        bptr = RawBuffer + 800*(y+4) + 47;
        for (x=0; x<xsize; x++, bptr++, dptr++) { // CAN SPEED THIS UP
            *dptr=(unsigned short) *bptr;
        }
    }

    if (options & CAPTURE_SUBTRACT_DARK) SubtractDark(img);

    // Do quick L recon to remove bayer array
    if (options & CAPTURE_RECON) QuickLRecon(img);

    return false;
}
Beispiel #5
0
bool Camera_OpticstarPL130Class::Capture(int duration, usImage& img, int options, const wxRect& subframe)
{
    bool still_going = true;

    int mode = 3 * (int) Color;
    if (img.Init(FullSize))
    {
        DisconnectWithAlert(CAPT_FAIL_MEMORY);
        return true;
    }
    if (OSPL130_Capture(mode,duration)) {
        pFrame->Alert(_("Cannot start exposure"));
        return true;
    }
    if (duration > 100) {
        wxMilliSleep(duration - 100); // wait until near end of exposure, nicely
        wxGetApp().Yield();
//      if (Abort) {
//          MeadeCam->AbortImage();
//          return true;
//      }
    }
    while (still_going) {  // wait for image to finish and d/l
        wxMilliSleep(20);
        OSPL130_IsExposing(&still_going);
        wxGetApp().Yield();
    }
    // Download
    OSPL130_GetRawImage(0,0,FullSize.GetWidth(),FullSize.GetHeight(), (void *) img.ImageData);
    unsigned short *dataptr;
    dataptr = img.ImageData;
    // byte swap

    if (options & CAPTURE_SUBTRACT_DARK) SubtractDark(img);
    if (Color && (options & CAPTURE_RECON))
        QuickLRecon(img);

    return false;
}
Beispiel #6
0
bool Camera_Altair::Capture(int duration, usImage& img, int options, const wxRect& subframe)
{
    if (img.Init(FullSize))
    {
        DisconnectWithAlert(CAPT_FAIL_MEMORY);
        return true;
    }

    wxRect frame;
    wxPoint subframePos; // position of subframe within frame

    //bool useSubframe = UseSubframes;

    //if (subframe.width <= 0 || subframe.height <= 0)
    //    useSubframe = false;

    //if (useSubframe)
    //{
    //    // ensure transfer size is a multiple of 1024
    //    //  moving the sub-frame or resizing it is somewhat costly (stopCapture / startCapture)

    //    frame.SetLeft(round_down(subframe.GetLeft(), 32));
    //    frame.SetRight(round_up(subframe.GetRight() + 1, 32) - 1);
    //    frame.SetTop(round_down(subframe.GetTop(), 32));
    //    frame.SetBottom(round_up(subframe.GetBottom() + 1, 32) - 1);

    //    subframePos = subframe.GetLeftTop() - frame.GetLeftTop();
    //}
    //else
    //{
        frame = wxRect(FullSize);
//    }

    long exposureUS = duration * 1000;
    unsigned int cur_exp;
    if (Altair_get_ExpoTime(m_handle, &cur_exp) == 0 &&
        cur_exp != exposureUS)
    {
        Debug.AddLine("Altair: set CONTROL_EXPOSURE %d", exposureUS);
        Altair_put_ExpoTime(m_handle, exposureUS);
    }

    long new_gain = cam_gain(m_minGain, m_maxGain, GuideCameraGain);
    unsigned short cur_gain;
    if (Altair_get_ExpoAGain(m_handle, &cur_gain) == 0 &&
        new_gain != cur_gain)
    {
        Debug.AddLine("Altair: set CONTROL_GAIN %d%% %d", GuideCameraGain, new_gain);
        Altair_put_ExpoAGain(m_handle, new_gain);
    }

  /*  bool size_change = frame.GetSize() != m_frame.GetSize();
    bool pos_change = frame.GetLeftTop() != m_frame.GetLeftTop();

    if (size_change || pos_change)
    {
        m_frame = frame;
        Debug.AddLine("Altair: frame (%d,%d)+(%d,%d)", m_frame.x, m_frame.y, m_frame.width, m_frame.height);
    }

    if (size_change)
    {
        StopCapture();

        ASI_ERROR_CODE status = ASISetROIFormat(m_handle, frame.GetWidth(), frame.GetHeight(), 1, ASI_IMG_Y8);
        if (status != ASI_SUCCESS)
            Debug.AddLine("Altair: setImageFormat(%d,%d) => %d", frame.GetWidth(), frame.GetHeight(), status);
    }

    if (pos_change)
    {
        ASI_ERROR_CODE status = ASISetStartPos(m_handle, frame.GetLeft(), frame.GetTop());
        if (status != ASI_SUCCESS)
            Debug.AddLine("Altair: setStartPos(%d,%d) => %d", frame.GetLeft(), frame.GetTop(), status);
    }*/

    // the camera and/or driver will buffer frames and return the oldest frame,
    // which could be quite stale. read out all buffered frames so the frame we
    // get is current

    //flush_buffered_image(m_handle, img);

    if (!m_capturing)
    {
        Debug.AddLine("Altair: startcapture");
		m_frameReady = false;
		Altair_StartPullModeWithCallback(m_handle, CameraCallback, this);
        m_capturing = true;
    }

    int frameSize = frame.GetWidth() * frame.GetHeight();

    int poll = wxMin(duration, 100);

    CameraWatchdog watchdog(duration, duration + GetTimeoutMs() + 10000); // total timeout is 2 * duration + 15s (typically)

    if (WorkerThread::MilliSleep(duration, WorkerThread::INT_ANY) &&
        (WorkerThread::TerminateRequested() || StopCapture()))
    {
        return true;
    }

    while (true)
    {
		if (m_frameReady)
		{
			m_frameReady = false;
			unsigned int width, height;
			if (SUCCEEDED(Altair_PullImage(m_handle, m_buffer, 8, &width, &height)))
				break;
		}
		WorkerThread::MilliSleep(poll, WorkerThread::INT_ANY);
        if (WorkerThread::InterruptRequested())
        {
            StopCapture();
            return true;
        }
        if (watchdog.Expired())
        {
            Debug.AddLine("Altair: getimagedata failed");
            StopCapture();
            DisconnectWithAlert(CAPT_FAIL_TIMEOUT);
            return true;
        }
    }

    //if (useSubframe)
    //{
    //    img.Subframe = subframe;

    //    // Clear out the image
    //    img.Clear();

    //    for (int y = 0; y < subframe.height; y++)
    //    {
    //        const unsigned char *src = m_buffer + (y + subframePos.y) * frame.width + subframePos.x;
    //        unsigned short *dst = img.ImageData + (y + subframe.y) * FullSize.GetWidth() + subframe.x;
    //        for (int x = 0; x < subframe.width; x++)
    //            *dst++ = *src++;
    //    }
    //}
    //else
    {
        for (int i = 0; i < img.NPixels; i++)
            img.ImageData[i] = m_buffer[i];
    }

    if (options & CAPTURE_SUBTRACT_DARK) SubtractDark(img);

    return false;
}
Beispiel #7
0
bool Camera_QHY::Capture(int duration, usImage& img, int options, const wxRect& subframe)
{
    bool useSubframe = UseSubframes && !subframe.IsEmpty();

    if (Binning != m_curBin)
    {
        FullSize = wxSize(m_maxSize.GetX() / Binning, m_maxSize.GetY() / Binning);
        m_curBin = Binning;
        useSubframe = false; // subframe may be out of bounds now
    }

    if (img.Init(FullSize))
    {
        DisconnectWithAlert(CAPT_FAIL_MEMORY);
        return true;
    }

    wxRect frame = useSubframe ? subframe : wxRect(FullSize);
    if (useSubframe)
        img.Clear();

    wxRect roi;

    if (useSubframe)
    {
        // Use a larger ROI around the subframe to avoid changing the ROI as the centroid
        // wobbles around. Changing the ROI introduces a lag of several seconds.
        // This also satifies the constraint that ROI width and height must be multiples of 4.
        enum { PAD = 1 << 5 };
        roi.SetLeft(round_down(subframe.GetLeft(), PAD));
        roi.SetRight(round_up(subframe.GetRight() + 1, PAD) - 1);
        roi.SetTop(round_down(subframe.GetTop(), PAD));
        roi.SetBottom(round_up(subframe.GetBottom() + 1, PAD) - 1);
    }
    else
    {
        roi = frame;
    }

    uint32_t ret = QHYCCD_ERROR;
    // lzr from QHY says this needs to be set for every exposure
    ret = SetQHYCCDBinMode(m_camhandle, Binning, Binning);
    if (ret != QHYCCD_SUCCESS)
    {
        Debug.Write(wxString::Format("SetQHYCCDBinMode failed! ret = %d\n", (int)ret));
    }

    if (m_roi != roi)
    {
        // when roi changes, must call this
        ret = CancelQHYCCDExposingAndReadout(m_camhandle);
        if (ret == QHYCCD_SUCCESS)
        {
            Debug.Write("CancelQHYCCDExposingAndReadout success\n");
        }
        else
        {
            Debug.Write("CancelQHYCCDExposingAndReadout failed\n");
        }

        ret = SetQHYCCDResolution(m_camhandle, roi.GetLeft(), roi.GetTop(), roi.GetWidth(), roi.GetHeight());
        if (ret == QHYCCD_SUCCESS)
        {
            m_roi = roi;
        }
        else
        {
            Debug.Write(wxString::Format("SetQHYCCDResolution(%d,%d,%d,%d) failed! ret = %d\n",
                roi.GetLeft(), roi.GetTop(), roi.GetWidth(), roi.GetHeight(), (int)ret));
        }
    }

    if (duration != m_curExposure)
    {
        ret = SetQHYCCDParam(m_camhandle, CONTROL_EXPOSURE, duration * 1000.0); // QHY duration is usec
        if (ret == QHYCCD_SUCCESS)
        {
            m_curExposure = duration;
        } 
        else
        {
            Debug.Write(wxString::Format("QHY set exposure ret %d\n", (int)ret));
            pFrame->Alert(_("Failed to set camera exposure"));
        }
    }

    if (GuideCameraGain != m_curGain)
    {
        double gain = m_gainMin + GuideCameraGain * (m_gainMax - m_gainMin) / 100.0;
        gain = floor(gain / m_gainStep) * m_gainStep;
        Debug.Write(wxString::Format("QHY set gain %g (%g..%g incr %g)\n", gain, m_gainMin, m_gainMax, m_gainStep));
        ret = SetQHYCCDParam(m_camhandle, CONTROL_GAIN, gain);
        if (ret == QHYCCD_SUCCESS)
        {
            m_curGain = GuideCameraGain;
        }
        else
        {
            Debug.Write(wxString::Format("QHY set gain ret %d\n", (int)ret));
            pFrame->Alert(_("Failed to set camera gain"));
        }
    }

    ret = ExpQHYCCDSingleFrame(m_camhandle);
    if (ret == QHYCCD_ERROR)
    {
        Debug.Write(wxString::Format("QHY exp single frame ret %d\n", (int)ret));
        DisconnectWithAlert(_("QHY exposure failed"), NO_RECONNECT);
        return true;
    }
    if (ret == QHYCCD_SUCCESS)
    {
        Debug.Write(wxString::Format("QHY: 200ms delay needed\n"));
        WorkerThread::MilliSleep(200);
    }
    if (ret == QHYCCD_READ_DIRECTLY)
    {
        //Debug.Write("QHYCCD_READ_DIRECTLY\n");
    }

    uint32_t w, h, bpp, channels;
    ret = GetQHYCCDSingleFrame(m_camhandle, &w, &h, &bpp, &channels, RawBuffer);
    if (ret != QHYCCD_SUCCESS || (bpp != 8 && bpp != 16))
    {
        Debug.Write(wxString::Format("QHY get single frame ret %d bpp %u\n", ret, bpp));
        // users report that reconnecting the camera after this failure allows
        // them to resume guiding so we'll try to reconnect automatically
        DisconnectWithAlert(_("QHY get frame failed"), RECONNECT);
        return true;
    }

    if (useSubframe)
    {
        img.Subframe = frame;

        int xofs = subframe.GetLeft() - roi.GetLeft();
        int yofs = subframe.GetTop() - roi.GetTop();

        int dxr = w - frame.width - xofs;
        if (bpp == 8)
        {
            const unsigned char *src = RawBuffer + yofs * w;
            unsigned short *dst = img.ImageData + frame.GetTop() * FullSize.GetWidth() + frame.GetLeft();
            for (int y = 0; y < frame.height; y++)
            {
                unsigned short *d = dst;
                src += xofs;
                for (int x = 0; x < frame.width; x++)
                    *d++ = (unsigned short) *src++;
                src += dxr;
                dst += FullSize.GetWidth();
            }
        }
        else // bpp == 16
        {
            const unsigned short *src = (const unsigned short *) RawBuffer + yofs * w;
            unsigned short *dst = img.ImageData + frame.GetTop() * FullSize.GetWidth() + frame.GetLeft();
            for (int y = 0; y < frame.height; y++)
            {
                src += xofs;
                memcpy(dst, src, frame.width * sizeof(unsigned short));
                src += frame.width + dxr;
                dst += FullSize.GetWidth();
            }
        }
    }
    else
    {
        if (bpp == 8)
        {
            const unsigned char *src = RawBuffer;
            unsigned short *dst = img.ImageData;
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    *dst++ = (unsigned short) *src++;
                }
            }
        }
        else // bpp == 16
        {
            memcpy(img.ImageData, RawBuffer, w * h * sizeof(unsigned short));
        }
    }

    if (options & CAPTURE_SUBTRACT_DARK)
        SubtractDark(img);
    if (Color && Binning == 1 && (options & CAPTURE_RECON))
        QuickLRecon(img);

    return false;
}
Beispiel #8
0
bool Camera_DSIClass::Capture(int duration, usImage& img, int options, const wxRect& subframe)
{
    MeadeCam->SetGain((unsigned int) (GuideCameraGain * 63 / 100));
    MeadeCam->SetExposureTime(duration);

    if (img.Init(MeadeCam->GetWidth(), MeadeCam->GetHeight()))
    {
        DisconnectWithAlert(CAPT_FAIL_MEMORY);
        return true;
    }

    bool retval = MeadeCam->GetImage(img.ImageData, true);
    if (!retval)
        return true;

// The AbortImage method does not appear to work with the DSI camera.  If abort is called and the thread is terminated, the
// pending image is still downloaded and PHD2 will crash
#if AbortActuallyWorks
    CameraWatchdog watchdog(duration, GetTimeoutMs());

    // wait for image to finish and d/l
    while (!MeadeCam->ImageReady)
    {
        wxMilliSleep(20);
        if (WorkerThread::InterruptRequested())
        {
            MeadeCam->AbortImage();
            return true;
        }
        if (watchdog.Expired())
        {
            MeadeCam->AbortImage();
            DisconnectWithAlert(CAPT_FAIL_TIMEOUT);
            return true;
        }
    }
#else // handle the pending image download, regardless

    // We also need to prevent the thread from being killed when phd2 is closed
    WorkerThreadKillGuard _guard;

    if (duration > 100) {
        wxMilliSleep(duration - 100); // wait until near end of exposure, nicely
    }
    bool still_going = true;
    while (still_going) {  // wait for image to finish and d/l
        wxMilliSleep(20);
        still_going = !(MeadeCam->ImageReady);
    }

#endif // end of waiting for the image

    if (options & CAPTURE_SUBTRACT_DARK) SubtractDark(img);

    if (options & CAPTURE_RECON)
    {
        if (MeadeCam->IsColor)
            QuickLRecon(img);
        if (MeadeCam->IsDsiII)
            SquarePixels(img, 8.6, 8.3);
        else if (!MeadeCam->IsDsiIII)           // Original DSI
            SquarePixels(img, 9.6, 7.5);
    }

    return false;
}
Beispiel #9
0
bool Camera_INDIClass::Capture(int duration, usImage& img, int options, const wxRect& subframeArg)
{
    if (Connected) {

        bool takeSubframe = UseSubframes;
        wxRect subframe(subframeArg);

        // we can set the exposure time directly in the camera
        if (expose_prop) {
            if (Binning != m_curBinning)
            {
                FullSize = wxSize(m_maxSize.x / Binning, m_maxSize.y / Binning);
                binning_x->value = Binning;
                binning_y->value = Binning;
                sendNewNumber(binning_prop);
                m_curBinning = Binning;
            }

            if (subframe.width <= 0 || subframe.height <= 0)
            {
                takeSubframe = false;
            }

            // Program the size
            if (!takeSubframe)
            {
                subframe = wxRect(0, 0, FullSize.GetWidth(), FullSize.GetHeight());
            }

            if (subframe != m_roi)
            {
                frame_x->value = subframe.x*Binning;
                frame_y->value = subframe.y*Binning;
                frame_width->value = subframe.width*Binning;
                frame_height->value = subframe.height*Binning;
                sendNewNumber(frame_prop);
                m_roi = subframe;
            }
            //printf("Exposing for %d(ms)\n", duration);

            // set the exposure time, this immediately start the exposure
            expose_prop->np->value = (double)duration/1000;
            sendNewNumber(expose_prop);

            modal = true;  // will be reset when the image blob is received

            unsigned long loopwait = duration > 100 ? 10 : 1;

            CameraWatchdog watchdog(duration, GetTimeoutMs());

            while (modal) {
                wxMilliSleep(loopwait);
                if (WorkerThread::TerminateRequested())
                    return true;
                if (watchdog.Expired())
                {
                    DisconnectWithAlert(CAPT_FAIL_TIMEOUT);
                    return true;
                }
            }
        }
        // for video camera without exposure time setting
        else if (video_prop) {
            takeSubframe = false;
            //printf("Enabling video capture\n");
            ISwitch *v_on = IUFindSwitch(video_prop,"ON");
            ISwitch *v_off = IUFindSwitch(video_prop,"OFF");
            v_on->s = ISS_ON;
            v_off->s = ISS_OFF;
            // start capture, every video frame is received as a blob
            sendNewSwitch(video_prop);

            // wait the required time
            wxMilliSleep(duration); // TODO : add the frames received during exposure

            //printf("Stop video capture\n");
            v_on->s = ISS_OFF;
            v_off->s = ISS_ON;
            sendNewSwitch(video_prop);
        }
        else {
            return true;
        }

        //printf("Exposure end\n");

        if (strcmp(cam_bp->format, ".fits") == 0) {
            //printf("Processing fits file\n");
            // for CCD camera
            if ( ! ReadFITS(img,takeSubframe,subframe) ) {
                if (options & CAPTURE_SUBTRACT_DARK) {
                    //printf("Subtracting dark\n");
                    SubtractDark(img);
                }
                if (options & CAPTURE_RECON) {
                    if (PixSizeX != PixSizeY) SquarePixels(img, PixSizeX, PixSizeY);
                }
                return false;
            } else {
                return true;
            }
        } else if (strcmp(cam_bp->format, ".stream") == 0) {
            //printf("Processing stream file\n");
            // for video camera
            return ReadStream(img);
        } else {
            pFrame->Alert(_("Unknown image format: ") + wxString::FromAscii(cam_bp->format));
            return true;
        }

    }
    else {
        // in case the camera is not connected
        return true;
    }
    // we must never go here
    return true;
}
Beispiel #10
0
bool Camera_ASCOMLateClass::Capture(int duration, usImage& img, int options, const wxRect& subframeArg)
{
    bool retval = false;
    bool takeSubframe = UseSubframes;
    wxRect subframe(subframeArg);

    if (subframe.width <= 0 || subframe.height <= 0)
    {
        takeSubframe = false;
    }

    bool binning_changed = false;
    if (Binning != m_curBin)
    {
        FullSize = wxSize(m_maxSize.x / Binning, m_maxSize.y / Binning);
        binning_changed = true;
    }

    // Program the size
    if (!takeSubframe)
    {
        subframe = wxRect(0, 0, FullSize.GetWidth(), FullSize.GetHeight());
    }

    if (img.Init(FullSize))
    {
        pFrame->Alert(_("Cannot allocate memory to download image from camera"));
        return true;
    }

    GITObjRef cam(m_gitEntry);

    EXCEPINFO excep;

    if (binning_changed)
    {
        if (ASCOM_SetBin(cam.IDisp(), Binning, &excep))
        {
            pFrame->Alert(_("The ASCOM camera failed to set binning. See the debug log for more information."));
            return true;
        }
        m_curBin = Binning;
    }

    if (subframe != m_roi)
    {
        ASCOM_SetROI(cam.IDisp(), subframe, &excep);
        m_roi = subframe;
    }

    bool takeDark = HasShutter && ShutterClosed;

    // Start the exposure
    if (ASCOM_StartExposure(cam.IDisp(), (double)duration / 1000.0, takeDark, &excep))
    {
        Debug.AddLine(ExcepMsg("ASCOM_StartExposure failed", excep));
        pFrame->Alert(ExcepMsg(_("ASCOM error -- Cannot start exposure with given parameters"), excep));
        return true;
    }

    CameraWatchdog watchdog(duration, GetTimeoutMs());

    if (duration > 100)
    {
        // wait until near end of exposure
        if (WorkerThread::MilliSleep(duration - 100, WorkerThread::INT_ANY) &&
            (WorkerThread::TerminateRequested() || AbortExposure()))
        {
            return true;
        }
    }

    while (true)  // wait for image to finish and d/l
    {
        wxMilliSleep(20);
        bool ready;
        EXCEPINFO excep;
        if (ASCOM_ImageReady(cam.IDisp(), &ready, &excep))
        {
            Debug.AddLine(ExcepMsg("ASCOM_ImageReady failed", excep));
            pFrame->Alert(ExcepMsg(_("Exception thrown polling camera"), excep));
            return true;
        }
        if (ready)
            break;
        if (WorkerThread::InterruptRequested() &&
            (WorkerThread::TerminateRequested() || AbortExposure()))
        {
            return true;
        }
        if (watchdog.Expired())
        {
            DisconnectWithAlert(CAPT_FAIL_TIMEOUT);
            return true;
        }
    }

    // Get the image
    if (ASCOM_Image(cam.IDisp(), img, takeSubframe, subframe, &excep))
    {
        Debug.AddLine(ExcepMsg(_T("ASCOM_Image failed"), excep));
        pFrame->Alert(ExcepMsg(_("Error reading image"), excep));
        return true;
    }

    if (options & CAPTURE_SUBTRACT_DARK)
        SubtractDark(img);
    if (Color && Binning == 1 && (options & CAPTURE_RECON))
        QuickLRecon(img);

    return false;
}