3#include <gtest/gtest.h>
9char _me[] =
"MockCCDSimDriver";
23 ASSERT_TRUE(p.isValid());
24 ASSERT_NE(p.findWidgetByName(
"SIM_XRES"),
nullptr);
25 ASSERT_NE(p.findWidgetByName(
"SIM_YRES"),
nullptr);
26 ASSERT_NE(p.findWidgetByName(
"SIM_XSIZE"),
nullptr);
27 ASSERT_NE(p.findWidgetByName(
"SIM_YSIZE"),
nullptr);
28 ASSERT_NE(p.findWidgetByName(
"SIM_MAXVAL"),
nullptr);
29 ASSERT_NE(p.findWidgetByName(
"SIM_SATURATION"),
nullptr);
30 ASSERT_NE(p.findWidgetByName(
"SIM_LIMITINGMAG"),
nullptr);
31 ASSERT_NE(p.findWidgetByName(
"SIM_NOISE"),
nullptr);
32 ASSERT_NE(p.findWidgetByName(
"SIM_SKYGLOW"),
nullptr);
33 ASSERT_NE(p.findWidgetByName(
"SIM_OAGOFFSET"),
nullptr);
34 ASSERT_NE(p.findWidgetByName(
"SIM_POLAR"),
nullptr);
35 ASSERT_NE(p.findWidgetByName(
"SIM_POLARDRIFT"),
nullptr);
40 EXPECT_TRUE(isnan(
currentRA)) <<
"Field 'currentRA' is undefined when initializing CCDSim.";
41 EXPECT_TRUE(isnan(
currentDE)) <<
"Field 'currentDEC' is undefined when initializing CCDSim.";
48 double const arcsec = 1.0 / 3600.0;
81 void setupChip(
int xres,
int yres,
float pixelSize = 4.6f)
100 for (
int i = 0; i < xres * yres; i++)
107 uint16_t *buf =
fb();
109 for (
int i = 0; i < xres * yres; i++)
110 if (buf[i] > peak) peak = buf[i];
121 int const maxval = 65000;
128 renderer.setConfig(cfg);
129 renderer.setImageScale(1.0f, 1.0f);
130 setupChip(xres, yres);
132 renderer.drawImageStar(&chip, cfg.
saturationMag, xres / 2, yres / 2, 1.0f);
134 EXPECT_GT(peakPixel(xres, yres),
static_cast<uint16_t
>(maxval / 4))
135 <<
"a star at saturation magnitude for 1s should produce high ADU";
148 renderer.setConfig(cfg);
149 renderer.setImageScale(1.0f, 1.0f);
150 setupChip(xres, yres);
152 renderer.drawImageStar(&chip, cfg.
limitingMag, xres / 2, yres / 2, 1.0f);
154 EXPECT_LE(peakPixel(xres, yres), 2u)
155 <<
"a star at limiting magnitude for 1s should produce ~1 ADU";
168 renderer.setConfig(cfg);
169 renderer.setImageScale(1.0f, 1.0f);
170 setupChip(xres, yres);
172 renderer.drawImageStar(&chip, 10.0f, xres / 2, yres / 2, 1.0f);
180 int const cx = xres / 2;
181 int const cy = yres / 2;
188 renderer.setConfig(cfg);
189 renderer.setImageScale(1.0f, 1.0f);
191 setupChip(xres, yres);
192 renderer.drawImageStar(&chip, 8.0f, cx, cy, 1.0f);
193 long const sum_bright = sumFrame(xres, yres);
195 setupChip(xres, yres);
196 renderer.drawImageStar(&chip, 12.0f, cx, cy, 1.0f);
197 long const sum_dim = sumFrame(xres, yres);
199 ASSERT_GT(sum_bright, 0L);
200 ASSERT_GT(sum_dim, 0L);
201 EXPECT_GT(sum_bright, sum_dim);
210 int const maxval = 1000;
217 renderer.setConfig(cfg);
218 renderer.setImageScale(1.0f, 1.0f);
219 setupChip(xres, yres);
221 renderer.drawImageStar(&chip, 0.0f, xres / 2, yres / 2, 1000.0f);
223 uint16_t *buf = fb();
224 for (
int i = 0; i < xres * yres; i++)
225 EXPECT_LE(buf[i],
static_cast<uint16_t
>(maxval))
226 <<
"no pixel may exceed maxVal";
241 renderer.setConfig(cfg);
242 renderer.setImageScale(1.0f, 1.0f);
243 setupChip(xres, yres);
245 int const cx = xres / 2;
246 int const cy = yres / 2;
247 renderer.drawImageStar(&chip, 5.0f, cx, cy, 1.0f);
249 uint16_t *buf = fb();
250 uint16_t
const peak = buf[cy * xres + cx];
252 EXPECT_GT(peak, buf[cy * xres + cx + 1]);
253 EXPECT_GT(peak, buf[cy * xres + cx - 1]);
254 EXPECT_GT(peak, buf[(cy + 1) * xres + cx]);
255 EXPECT_GT(peak, buf[(cy - 1) * xres + cx]);
268 renderer.setConfig(cfg);
269 renderer.setImageScale(1.0f, 1.0f);
270 setupChip(xres, yres);
272 int const cx = xres / 2;
273 int const cy = yres / 2;
274 renderer.drawImageStar(&chip, 5.0f, cx, cy, 1.0f);
276 uint16_t *buf = fb();
277 for (
int d = 1; d <= 3; d++)
279 EXPECT_EQ(buf[cy * xres + cx + d], buf[cy * xres + cx - d])
280 <<
"horizontal symmetry at d=" << d;
281 EXPECT_EQ(buf[(cy + d) * xres + cx], buf[(cy - d) * xres + cx])
282 <<
"vertical symmetry at d=" << d;
296 renderer.setConfig(cfg);
297 renderer.setImageScale(1.0f, 1.0f);
298 setupChip(xres, yres);
300 int const cx = xres / 2;
301 int const cy = yres / 2;
302 renderer.drawImageStar(&chip, 5.0f, cx, cy, 1.0f);
304 uint16_t *buf = fb();
305 for (
int d = 0; d < 5; d++)
307 uint16_t
const inner = buf[cy * xres + cx + d];
308 uint16_t
const outer = buf[cy * xres + cx + d + 1];
309 EXPECT_GE(inner, outer)
310 <<
"profile must not increase at d=" << d <<
" -> " << d + 1;
324 renderer.setConfig(cfg);
325 renderer.setImageScale(1.0f, 1.0f);
326 setupChip(xres, yres);
328 int const cx = xres / 2;
329 int const cy = yres / 2;
330 renderer.drawImageStar(&chip, 10.0f, cx, cy, 1.0f);
332 uint16_t *buf = fb();
333 int const beyond =
static_cast<int>(3.0f * cfg.
seeing / 1.0f) + 2;
334 for (
int d = beyond; d < cx; d++)
336 EXPECT_EQ(buf[cy * xres + cx + d], 0u)
337 <<
"pixel at d=" << d <<
" is beyond the render box";
348 int const xres = 201;
349 int const yres = 201;
356 renderer.setConfig(cfg);
357 renderer.setImageScale(1.0f, 1.0f);
360 auto centroidX = [
this](
int nx,
int ny) ->
double
362 uint16_t *buf = fb();
363 double sumWeighted = 0.0;
364 double sumFlux = 0.0;
365 for (
int y = 0; y < ny; y++)
366 for (
int x = 0; x < nx; x++)
368 double const v = buf[y * nx + x];
369 sumWeighted += v * x;
372 return sumFlux > 0 ? sumWeighted / sumFlux : -1.0;
376 float const y = 100.0f;
378 setupChip(xres, yres);
379 renderer.drawImageStar(&chip, 6.0f, 100.3f, y, 1.0f);
380 double const cxLow = centroidX(xres, yres);
382 setupChip(xres, yres);
383 renderer.drawImageStar(&chip, 6.0f, 100.7f, y, 1.0f);
384 double const cxHigh = centroidX(xres, yres);
386 ASSERT_GT(cxLow, 0.0);
387 ASSERT_GT(cxHigh, 0.0);
390 EXPECT_NEAR(cxLow, 100.3, 0.15);
391 EXPECT_NEAR(cxHigh, 100.7, 0.15);
394 EXPECT_GT(cxHigh - cxLow, 0.2);
407 renderer.setConfig(cfg);
408 renderer.setImageScale(1.0f, 1.0f);
410 int const cx = xres / 2;
411 int const cy = yres / 2;
413 setupChip(xres, yres);
414 renderer.drawImageStar(&chip, 10.0f, cx, cy, 1.0f);
415 long const sum1s = sumFrame(xres, yres);
417 setupChip(xres, yres);
418 renderer.drawImageStar(&chip, 10.0f, cx, cy, 4.0f);
419 long const sum4s = sumFrame(xres, yres);
421 ASSERT_GT(sum1s, 0L);
422 EXPECT_NEAR(
static_cast<double>(sum4s) / sum1s, 4.0, 0.5)
423 <<
"total flux must scale linearly with exposure time";
436 renderer.setConfig(cfg);
437 renderer.setImageScale(1.0f, 1.0f);
438 setupChip(xres, yres);
440 renderer.drawImageStar(&chip, 5.0f, -10.0f, -10.0f, 1.0f);
441 renderer.drawImageStar(&chip, 5.0f, xres + 10.0f, yres + 10.0f, 1.0f);
442 EXPECT_EQ(sumFrame(xres, yres), 0);
449 int const xres = 100;
450 int const yres = 100;
451 float const pixelSizeUM = 5.0f;
452 double const focalLengthMM = 1000.0;
457 renderer.setConfig(cfg);
458 setupChip(xres, yres, pixelSizeUM);
460 renderer.renderFrame(&chip, 0.0, 0.0, focalLengthMM, 0.0, 1.0f,
false);
462 float const expectedScale =
static_cast<float>((pixelSizeUM / focalLengthMM) * 206.3);
463 EXPECT_NEAR(renderer.imageScaleX(), expectedScale, 0.001f);
464 EXPECT_NEAR(renderer.imageScaleY(), expectedScale, 0.001f);
477 renderer.setConfig(cfg);
478 setupChip(xres, yres);
480 int drawn = renderer.renderFrame(&chip, 0.0, 0.0, 500.0, 0.0, 10.0f,
false);
481 EXPECT_EQ(drawn, 0) <<
"renderStars=false must draw zero stars";
482 EXPECT_GT(sumFrame(xres, yres), 0L) <<
"sky glow must still be rendered";
495 renderer.setConfig(cfg);
496 setupChip(xres, yres);
498 renderer.renderFrame(&chip, 0.0, 0.0, 500.0, 0.0, 10.0f,
false);
500 uint16_t *buf = fb();
501 uint16_t
const center_val = buf[(yres / 2) * xres + xres / 2];
502 uint16_t
const corner_val = buf[0];
504 EXPECT_GT(center_val, 0u);
505 EXPECT_GT(center_val, corner_val)
506 <<
"vignetting must make center brighter than corners";
517 renderer.setConfig(cfg);
518 setupChip(xres, yres);
520 memset(chip.getFrameBuffer(), 0xFF, chip.getFrameBufferSize());
522 renderer.renderFrame(&chip, 0.0, 0.0, 500.0, 0.0, 0.001f,
false);
524 uint16_t *buf = fb();
525 for (
int i = 0; i < xres * yres; i++)
526 EXPECT_LT(buf[i], 10u) <<
"renderFrame must clear pre-existing data";
535 int const maxval = 65000;
536 int const bias = 1500;
537 int const maxNoise = 20;
541 renderer.setConfig(cfg);
542 setupChip(xres, yres);
544 renderer.applyReadoutNoise(&chip, bias, maxNoise);
546 uint16_t *buf = fb();
547 for (
int i = 0; i < xres * yres; i++)
549 EXPECT_GE(buf[i],
static_cast<uint16_t
>(bias));
550 EXPECT_LE(buf[i],
static_cast<uint16_t
>(bias + maxNoise));
558 int const maxval = 100;
562 renderer.setConfig(cfg);
563 setupChip(xres, yres);
566 renderer.applyReadoutNoise(&chip, 200, 10);
568 uint16_t *buf = fb();
569 for (
int i = 0; i < xres * yres; i++)
570 EXPECT_EQ(buf[i],
static_cast<uint16_t
>(maxval));
580 renderer.setConfig(cfg);
581 setupChip(xres, yres);
583 renderer.applyReadoutNoise(&chip, 1500, 0);
585 EXPECT_EQ(sumFrame(xres, yres), 0)
586 <<
"maxNoise <= 0 must skip noise application entirely";
601 renderer.setConfig(cfg);
602 renderer.setImageScale(1.0f, 1.0f);
603 setupChip(xres, yres);
605 auto const before = std::chrono::steady_clock::now();
606 int const loops = 200000;
607 for (
int i = 0; i < loops; i++)
609 float const m = (15.0f * rand()) /
static_cast<float>(RAND_MAX);
610 float const x =
static_cast<float>(xres * rand()) /
static_cast<float>(RAND_MAX);
611 float const y =
static_cast<float>(yres * rand()) /
static_cast<float>(RAND_MAX);
612 float const e = (100.0f * rand()) /
static_cast<float>(RAND_MAX);
613 renderer.drawImageStar(&chip, m, x, y, e);
615 auto const after = std::chrono::steady_clock::now();
616 auto const duration = std::chrono::duration_cast<std::chrono::nanoseconds>(after - before).count() / loops;
617 std::cout <<
"[ ] drawImageStar benchmark: " << duration <<
" ns/call" << std::endl;
622TEST(CCDSimulatorDriverTest, test_properties)
627TEST(CCDSimulatorDriverTest, test_guide_api)
637 ::testing::InitGoogleTest(&argc, argv);
638 return RUN_ALL_TESTS();
The CCDSim class provides an advanced simulator for a CCD that includes a dedicated on-board guide ch...
bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual IPState GuideSouth(uint32_t) override
Guide southward for ms milliseconds.
float GuideRate
Guide rate is 7 arcseconds per second.
void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual IPState GuideEast(uint32_t) override
Guide easward for ms milliseconds.
virtual IPState GuideWest(uint32_t) override
Guide westward for ms milliseconds.
virtual IPState GuideNorth(uint32_t) override
Guide northward for ms milliseconds.
INDI::PropertyNumber getNumber(const char *name) const
The CCDChip class provides functionality of a CCD Chip within a CCD.
void setResolution(uint32_t x, uint32_t y)
setResolution set CCD Chip resolution
void setFrame(uint32_t subx, uint32_t suby, uint32_t subw, uint32_t subh)
setFrame Set desired frame resolutoin for an exposure.
void setPixelSize(double x, double y)
setPixelSize Set CCD Chip pixel size
uint8_t * getFrameBuffer()
getFrameBuffer Get raw frame buffer of the CCD chip.
void setFrameBufferSize(uint32_t nbuf, bool allocMem=true)
setFrameBufferSize Set desired frame buffer size. The function will allocate memory accordingly....
int getFrameBufferSize() const
getFrameBufferSize Get allocated frame buffer size to hold the CCD image frame.
void setBPP(uint8_t bpp)
setBPP Set depth of CCD chip.
uint16_t peakPixel(int xres, int yres)
long sumFrame(int xres, int yres)
void setupChip(int xres, int yres, float pixelSize=4.6f)
static const loggerConf file_off
void configure(const std::string &outputFile, const loggerConf configuration, const int fileVerbosityLevel, const int screenVerbosityLevel)
Method to configure the logger. Called by the DEBUG_CONF() macro. To make implementation easier,...
static Logger & getInstance()
Method to get a reference to the object (i.e., Singleton) It is a static method.
TEST_F(SkyRendererTest, saturation_mag_produces_high_adu)
TEST(CCDSimulatorDriverTest, test_properties)