Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/calstep_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ CalstepDialog::CalstepDialog(wxWindow *parent, int focalLength, double pixelSize

// binning
wxArrayString opts;
GuideCamera::GetBinningOpts(pCamera ? pCamera->MaxHwBinning : 1, &opts);
bool includeSwBinning = false; // TODO: SW binning UI
GuideCamera::GetBinningOpts(&opts, pCamera ? pCamera->MaxHwBinning : 1, includeSwBinning);
m_binningChoice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, opts);
m_binningChoice->Enable(!pFrame->pGuider || !pFrame->pGuider->IsCalibratingOrGuiding());
m_binningChoice->Bind(wxEVT_CHOICE, &CalstepDialog::OnText, this);
Expand Down
147 changes: 134 additions & 13 deletions src/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ GuideCamera::GuideCamera()
m_pixelSize = GetProfilePixelSize();
MaxHwBinning = 1;
HwBinning = pConfig->Profile.GetInt("/camera/binning", 1);
SwBinning = wxClip(pConfig->Profile.GetInt("/camera/SoftwareBinning", 1), 1, (int) GuideCamera::MAX_SOFTWARE_BINNING);
CurrentDarkFrame = nullptr;
CurrentDefectMap = nullptr;
}
Expand Down Expand Up @@ -721,15 +722,23 @@ int GuideCamera::GetDefaultCameraGain()

bool GuideCamera::SetBinning(int binning)
{
if (binning < 1)
binning = 1;
if (binning > MaxHwBinning)
binning = MaxHwBinning;
auto hwSwBin = GetHwAndSwBinning(binning);
auto hwBin = hwSwBin.first;
auto swBin = hwSwBin.second;
return SetBinning(hwBin, swBin);
}

bool GuideCamera::SetBinning(int hwBinning, int swBinning)
{
HwBinning = wxClip(hwBinning, 1, MaxHwBinning);
SwBinning = wxClip(swBinning, 1, (int) MAX_SOFTWARE_BINNING);

Debug.Write(wxString::Format("camera: set binning = %u\n", (unsigned int) binning));
auto binning = GetBinning();
Debug.Write(wxString::Format("camera: set binning = %u (hw = %u, sw = %u)\n", (unsigned int) binning,
(unsigned int) HwBinning, (unsigned int) SwBinning));

HwBinning = binning;
pConfig->Profile.SetInt("/camera/binning", binning);
pConfig->Profile.SetInt("/camera/binning", HwBinning);
pConfig->Profile.SetInt("/camera/SoftwareBinning", SwBinning);

// if a Limit Frame ROI is in use, adjust it for the new binning
if (HasFrameLimiting)
Expand All @@ -754,8 +763,9 @@ bool GuideCamera::SetLimitFrame(const wxRect& roi, int binning, wxString *errorM
// binning to a lower binning. For example, if we have a limit frame coordinate
// value of 101 at bin 1, after switching to bin 2 the coordinate will be 50; and
// switching back to bin 1 we can recover the inital value of 101.
for (int new_bin = 1; new_bin <= MaxHwBinning; new_bin++)
for (auto choice : GetBinningChoices())
{
auto new_bin = choice.first;
wxRect const limit_frame(roi.x * binning / new_bin, roi.y * binning / new_bin, roi.width * binning / new_bin,
roi.height * binning / new_bin);
wxString const key = wxString::Format("/camera/LimitFrameBin%d", new_bin);
Expand Down Expand Up @@ -983,7 +993,8 @@ CameraConfigDialogCtrlSet::CameraConfigDialogCtrlSet(wxWindow *pParent, GuideCam
// Binning
m_binning = 0;
wxArrayString opts;
m_pCamera->GetBinningOpts(&opts);
bool includeSwBinning = false; // TODO: SW binning UI
m_pCamera->GetBinningOpts(&opts, includeSwBinning);
int width = StringArrayWidth(opts);
m_binning = new wxChoice(GetParentWindow(AD_szBinning), wxID_ANY, wxDefaultPosition, wxSize(width + 35, -1), opts);
AddLabeledCtrl(CtrlMap, AD_szBinning, _("Binning"), m_binning, _("Camera pixel binning"));
Expand Down Expand Up @@ -1241,10 +1252,63 @@ void CameraConfigDialogCtrlSet::SetBinning(int binning)
SetIntChoice(m_binning, binning);
}

void GuideCamera::GetBinningOpts(int maxBin, wxArrayString *opts)
void GuideCamera::GetBinningOpts(wxArrayString *opts, int maxHwBinning, bool includeSwBinning)
{
for (auto choice : GetBinningChoices(maxHwBinning))
{
if (includeSwBinning || choice.second.second == 1)
opts->Add(wxString::Format("%d", choice.first));
}
}

// Get all the available binning choices for both hardware and software binning.
// Hardware binning takes precedence over software binning.
//
// The max combined binning level is the camera's max binning or the software max
// binning (4), whichever is greater.
//
// Example: maxHwBin = 2
// combined hw sw
// 1 1 1
// 2 2 1
// 3 1 3
// 4 2 2
BinningChoices GuideCamera::GetBinningChoices(int maxHwBin)
{
int maxSwBin = GuideCamera::MAX_SOFTWARE_BINNING;
int maxCombinedBin = wxMax(maxHwBin, maxSwBin);
BinningChoices choices;
for (int hwBin = 1; hwBin <= maxHwBin; hwBin++)
for (int swBin = 1; swBin <= maxSwBin; swBin++)
{
auto combined = hwBin * swBin;
if (combined > maxCombinedBin)
continue;
auto it = choices.find(combined);
if (it == choices.end() || hwBin > it->second.first)
choices[combined] = std::make_pair(hwBin, swBin);
}
return choices;
}

// Get the hardware and software binning levels for a given combined binning level and
// maximum hardware binning level.
//
// Uses GuideCamera::GetBinningChoices() to generate the list of valid choices, then
// selects the closest match having a combined binning value less than or equal to the
// requested value.
std::pair<int, int> GuideCamera::GetHwAndSwBinning(int maxHwBinning, int combinedBinning)
{
for (int i = 1; i <= maxBin; i++)
opts->Add(wxString::Format("%d", i));
auto prev = std::make_pair(1, 1);
for (auto choice : GetBinningChoices(maxHwBinning))
{
if (choice.first == combinedBinning)
return choice.second;
if (choice.first > combinedBinning)
return prev;
prev = choice.second;
}
return prev;
}

wxString GuideCamera::GetSettingsSummary()
Expand Down Expand Up @@ -1443,15 +1507,72 @@ void GuideCamera::DisconnectWithAlert(const wxString& msg, ReconnectType reconne

void GuideCamera::InitCapture() { }

// convert a rectangle from binned coordinates to un-binned coordinates
inline static wxRect unbinned_rect(const wxRect& binnedRect, int binning)
{
auto x = binnedRect.GetLeft() * binning;
auto y = binnedRect.GetTop() * binning;
auto width = binnedRect.GetWidth() * binning;
auto heigth = binnedRect.GetHeight() * binning;
return wxRect(x, y, width, heigth);
}

// convert a rectangle from un-binned coordinates to binned coordinates
inline static wxRect binned_rect(const wxRect& unbinnedRect, int binning)
{
auto x = unbinnedRect.GetLeft() / binning;
auto y = unbinnedRect.GetTop() / binning;
auto width = unbinnedRect.GetWidth() / binning;
auto heigth = unbinnedRect.GetHeight() / binning;
return wxRect(x, y, width, heigth);
}

inline static wxSize binned_size(const wxSize& unbinnedSize, int binning)
{
return wxSize(unbinnedSize.x / binning, unbinnedSize.y / binning);
}

bool GuideCamera::Capture(GuideCamera *camera, usImage& img, const CaptureParams& captureParams)
{
// The subframe and LimitFrame are in software-binned coordinates, but the camera
// subclass Capture methods work with hardware coordinates.
int swBinning = captureParams.swBinning;
CaptureParams cameraParams(captureParams);
if (swBinning > 1)
{
cameraParams.limitFrame = unbinned_rect(captureParams.limitFrame, swBinning);
cameraParams.subframe = unbinned_rect(captureParams.subframe, swBinning);
}

img.InitImgStartTime();
img.LimitFrame = captureParams.limitFrame;
img.Binning = captureParams.hwBinning;
img.BitsPerPixel = captureParams.bpp;
img.Gain = captureParams.gain;
img.ImgExpDur = captureParams.duration;
bool err = camera->Capture(img, captureParams);

bool err = camera->Capture(img, cameraParams);
if (err)
return err;

// perform software binning if needed
if (swBinning > 1)
{
usImage binnedImage;
if (binnedImage.Init(binned_size(img.Size, swBinning)))
{
camera->DisconnectWithAlert(CAPT_FAIL_MEMORY);
return true;
}
BinPixels(binnedImage.ImageData, img.ImageData, img.Size, swBinning);
img.SwapImageData(binnedImage);
img.Size = binnedImage.Size;
img.NPixels = binnedImage.NPixels;
img.Binning *= swBinning;
// scale the subframe from camera coords to binned coords
img.Subframe = binned_rect(img.Subframe, swBinning);
}

return err;
}

Expand Down
61 changes: 53 additions & 8 deletions src/camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,16 @@ struct CaptureParams
int gain;
int captureOptions;
wxByte hwBinning;
wxByte swBinning;
wxByte bpp;

// combined binning level - hardware + software
int CombinedBinning() const { return hwBinning; }
int CombinedBinning() const { return hwBinning * swBinning; }
};

// mapping from combined binning to (hwBinning, swBinning) pair
typedef std::map<int, std::pair<int, int>> BinningChoices;

class GuideCamera : public wxMessageBoxProxy, public OnboardST4
{
friend class CameraConfigDialogPane;
Expand All @@ -132,6 +136,11 @@ class GuideCamera : public wxMessageBoxProxy, public OnboardST4
unsigned short m_saturationADU;

public:
enum
{
MAX_SOFTWARE_BINNING = 4
};

static const double UnknownPixelSize;

int GuideCameraGain;
Expand All @@ -152,7 +161,8 @@ class GuideCamera : public wxMessageBoxProxy, public OnboardST4
bool HasSubframes;
bool HasFrameLimiting;
wxByte MaxHwBinning; // max hardware binning level
wxByte HwBinning;
wxByte HwBinning; // hardware binning level
wxByte SwBinning; // software binning level
bool ShutterClosed; // false=light, true=dark
bool UseSubframes;
bool HasCooler;
Expand Down Expand Up @@ -197,10 +207,19 @@ class GuideCamera : public wxMessageBoxProxy, public OnboardST4
CameraConfigDialogCtrlSet *GetConfigDlgCtrlSet(wxWindow *pParent, GuideCamera *pCamera, AdvancedDialog *pAdvancedDialog,
BrainCtrlIdMap& CtrlMap);

static void GetBinningOpts(int maxBin, wxArrayString *opts);
void GetBinningOpts(wxArrayString *opts);
static BinningChoices GetBinningChoices(int maxHwBinning);
BinningChoices GetBinningChoices() const;
static std::pair<int, int> GetHwAndSwBinning(int maxHwBinning, int combinedBinning);
std::pair<int, int> GetHwAndSwBinning(int combinedBinning) const;
static void GetBinningOpts(wxArrayString *opts, int maxHwBinning, bool includeSwBinning);
void GetBinningOpts(wxArrayString *opts, bool includeSwBinning) const;
static bool GetOfferSwBinning(int maxHwBinning);
bool GetOfferSwBinning() const;
int GetBinning() const;
// set the combined binning level
bool SetBinning(int binning);
// set the hardware and software binning levels
bool SetBinning(int hwBinning, int swBinning);
bool SetLimitFrame(const wxRect& roi, int binning, wxString *errorMessage);
void LoadLimitFrame(int binning);

Expand Down Expand Up @@ -264,21 +283,47 @@ inline int GuideCamera::GetTimeoutMs() const
return m_timeoutMs;
}

inline void GuideCamera::GetBinningOpts(wxArrayString *opts)
inline BinningChoices GuideCamera::GetBinningChoices() const
{
return GetBinningChoices(MaxHwBinning);
}

inline std::pair<int, int> GuideCamera::GetHwAndSwBinning(int combinedBinning) const
{
return GetHwAndSwBinning(MaxHwBinning, combinedBinning);
}

inline void GuideCamera::GetBinningOpts(wxArrayString *opts, bool includeSwBinning) const
{
GetBinningOpts(MaxHwBinning, opts);
GetBinningOpts(opts, MaxHwBinning, includeSwBinning);
}

// get the combined binning level -- hardware and software binning
inline int GuideCamera::GetBinning() const
{
return HwBinning;
return HwBinning * SwBinning;
}

// Returns true when software binning should be offered in the UI.
//
// For cameras with adequate hardware binning capability (>= 4x), returns false.
inline bool GuideCamera::GetOfferSwBinning(int maxHwBinning)
{
return maxHwBinning < MAX_SOFTWARE_BINNING;
}

// Returns true when software binning should be offered in the UI.
//
// For cameras with adequate hardware binning capability (>= 4x), returns false.
inline bool GuideCamera::GetOfferSwBinning() const
{
return GetOfferSwBinning(MaxHwBinning);
}

// returns the expected frame size after software binning
inline wxSize GuideCamera::GetFrameSize() const
{
return FrameSize;
return wxSize(FrameSize.GetWidth() / SwBinning, FrameSize.GetHeight() / SwBinning);
}

inline double GuideCamera::GetCameraPixelSize() const
Expand Down
4 changes: 4 additions & 0 deletions src/darks_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,10 @@ bool DarksDialog::CreateMasterDarkFrame(usImage& darkFrame, int expTime, int fra
CaptureParams captureParams;
captureParams.duration = expTime;
captureParams.hwBinning = pCamera->HwBinning;
// darks are not software-binned as this allows the dark library to be re-used
// for any software binning level (dark subtraction / bad-pixel map correction
// is performed before software binning)
captureParams.swBinning = 1;
captureParams.bpp = pCamera->BitsPerPixel();
captureParams.gain = pCamera->GuideCameraGain;
captureParams.captureOptions = CAPTURE_DARK;
Expand Down
30 changes: 27 additions & 3 deletions src/event_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,25 @@ static void save_image(JObj& response, const json_value *params)
response << jrpc_result(rslt);
}

static bool IsValidBinning(GuideCamera *camera, int binning, wxString *message)
{
auto choices = camera->GetBinningChoices();
if (choices.find(binning) != choices.end())
return true;
wxString buf = wxString::Format("Invalid binning value (%d). Valid choices are: ", binning);
bool first = true;
for (auto choice : choices)
{
if (first)
first = false;
else
buf.Append(", ");
buf.Append(wxString::Format("%d", choice.first));
}
*message = buf;
return false;
}

static void capture_single_frame(JObj& response, const json_value *params)
{
if (pFrame->CaptureActive)
Expand Down Expand Up @@ -1409,10 +1428,15 @@ static void capture_single_frame(JObj& response, const json_value *params)
wxByte binning = pCamera->GetBinning();
if ((j = p.param("binning")) != nullptr)
{
if (j->type != JSON_INT || j->int_value < 1 || j->int_value > pCamera->MaxHwBinning)
if (j->type != JSON_INT)
{
response << jrpc_error(JSONRPC_INVALID_PARAMS, "binning value must be an integer");
return;
}
wxString message;
if (!IsValidBinning(pCamera, j->int_value, &message))
{
response << jrpc_error(JSONRPC_INVALID_PARAMS,
wxString::Format("invalid binning value: min=1, max=%d", pCamera->MaxHwBinning));
response << jrpc_error(JSONRPC_INVALID_PARAMS, message);
return;
}
binning = j->int_value;
Expand Down
Loading