Instrument Neutral Distributed Interface INDI  2.0.2
test_ccd_simulator.cpp
Go to the documentation of this file.
1 #include "indicom.h"
2 #include "indilogger.h"
3 
4 #include <gtest/gtest.h>
5 #include <gmock/gmock.h>
6 
7 using ::testing::_;
8 using ::testing::StrEq;
9 
10 #include "ccd_simulator.h"
11 
12 char _me[] = "MockCCDSimDriver";
13 char *me = _me;
14 class MockCCDSimDriver: public CCDSim
15 {
16  public:
18  {
21  }
22 
24  {
25  auto p = getNumber("SIMULATOR_SETTINGS");
26  ASSERT_NE(p, nullptr);
27  ASSERT_NE(p.findWidgetByName("SIM_XRES"), nullptr);
28  ASSERT_NE(p.findWidgetByName("SIM_YRES"), nullptr);
29  ASSERT_NE(p.findWidgetByName("SIM_XSIZE"), nullptr);
30  ASSERT_NE(p.findWidgetByName("SIM_YSIZE"), nullptr);
31  ASSERT_NE(p.findWidgetByName("SIM_MAXVAL"), nullptr);
32  ASSERT_NE(p.findWidgetByName("SIM_SATURATION"), nullptr);
33  ASSERT_NE(p.findWidgetByName("SIM_LIMITINGMAG"), nullptr);
34  ASSERT_NE(p.findWidgetByName("SIM_NOISE"), nullptr);
35  ASSERT_NE(p.findWidgetByName("SIM_SKYGLOW"), nullptr);
36  ASSERT_NE(p.findWidgetByName("SIM_OAGOFFSET"), nullptr);
37  ASSERT_NE(p.findWidgetByName("SIM_POLAR"), nullptr);
38  ASSERT_NE(p.findWidgetByName("SIM_POLARDRIFT"), nullptr);
39  }
40 
41  void testGuideAPI()
42  {
43  // At init, current RA and DEC are undefined - message will not appear because the test passes
44  EXPECT_TRUE(isnan(currentRA)) << "Field 'currentRA' is undefined when initializing CCDSim.";
45  EXPECT_TRUE(isnan(currentDE)) << "Field 'currentDEC' is undefined when initializing CCDSim.";
46 
47  // Guide rate is fixed
48  EXPECT_EQ(GuideRate, 7 /* arcsec/s */);
49 
50  // Initial guide offsets are zero
51  EXPECT_EQ(guideNSOffset, 0);
52  EXPECT_EQ(guideWEOffset, 0);
53 
54  double const arcsec = 1.0 / 3600.0;
55 
56  // Guiding in DEC stores offset in arcsec for next simulation step
57  EXPECT_EQ(GuideNorth(1000.0), IPS_OK);
58  EXPECT_NEAR(guideNSOffset, +7 * arcsec, 1 * arcsec);
59  EXPECT_EQ(GuideSouth(1000.0), IPS_OK);
60  EXPECT_NEAR(guideNSOffset, +0 * arcsec, 1 * arcsec);
61  EXPECT_EQ(GuideSouth(1000.0), IPS_OK);
62  EXPECT_NEAR(guideNSOffset, -7 * arcsec, 1 * arcsec);
63  EXPECT_EQ(GuideNorth(1000.0), IPS_OK);
64  EXPECT_NEAR(guideNSOffset, +0 * arcsec, 1 * arcsec);
65 
66  // RA guiding rate depends on declination, we need a valid one
67  currentDE = 0;
68 
69  // Guiding in RA stores offset in arcsec for next simulation step
70  // There is an adjustemnt based on declination - here zero from previous test
71  EXPECT_EQ(GuideWest(1000.0), IPS_OK);
72  EXPECT_NEAR(guideWEOffset, +7 * arcsec, 15 * arcsec);
73  EXPECT_EQ(GuideEast(1000.0), IPS_OK);
74  EXPECT_NEAR(guideWEOffset, +0 * arcsec, 15 * arcsec);
75  EXPECT_EQ(GuideEast(1000.0), IPS_OK);
76  EXPECT_NEAR(guideWEOffset, -7 * arcsec, 15 * arcsec);
77  EXPECT_EQ(GuideWest(1000.0), IPS_OK);
78  EXPECT_NEAR(guideWEOffset, +0 * arcsec, 15 * arcsec);
79 
80  // TODO: verify DEC-biased RA guiding rate
81  // TODO: verify property-based guiding API
82  }
83 
84  void testDrawStar()
85  {
86  int const xres = 65;
87  int const yres = 65;
88  int const maxval = pow(2, 8) - 1;
89 
90  // Setup a 65x65, 16-bit depth, 4.6u square pixel sensor
91  auto p = getNumber("SIMULATOR_SETTINGS");
92  ASSERT_NE(p, nullptr);
93  p.findWidgetByName("SIM_XRES")->setValue((double) xres);
94  p.findWidgetByName("SIM_YRES")->setValue((double) yres);
95  // There is no way to set depth, it is hardcoded at 16-bit - so set maximum value instead
96  p.findWidgetByName("SIM_MAXVAL")->setValue((double) maxval);
97  p.findWidgetByName("SIM_XSIZE")->setValue(4.6);
98  p.findWidgetByName("SIM_YSIZE")->setValue(4.6);
99 
100  // Setup a saturation magnitude (max ADUs in one second) and limit magnitude (zero ADU whatever the exposure)
101  p.findWidgetByName("SIM_SATURATION")->setValue(0.0);
102  p.findWidgetByName("SIM_LIMITINGMAG")->setValue(30.0);
103 
104  // Setup some parameters to simplify verifications
105  p.findWidgetByName("SIM_SKYGLOW")->setValue(0.0);
106  p.findWidgetByName("SIM_NOISE")->setValue(0.0);
107  this->seeing = 1.0f; // No way to control seeing from properties
108 
109  // Setup
110  ASSERT_TRUE(setupParameters());
111 
112  // Assert our parameters
113  ASSERT_EQ(PrimaryCCD.getBPP(), 16) << "Simulator CCD depth is hardcoded at 16 bits";
114  ASSERT_EQ(PrimaryCCD.getXRes(), xres);
115  ASSERT_EQ(PrimaryCCD.getYRes(), yres);
116  ASSERT_EQ(PrimaryCCD.getPixelSizeX(), 4.6f);
117  ASSERT_EQ(PrimaryCCD.getPixelSizeY(), 4.6f);
118  ASSERT_NE(PrimaryCCD.getFrameBuffer(), nullptr) << "SetupParms creates the frame buffer";
119 
120  // Assert our simplifications
121  EXPECT_EQ(this->seeing, 1.0f);
122  EXPECT_EQ(this->ImageScalex, 1.0f);
123  EXPECT_EQ(this->ImageScaley, 1.0f);
124  EXPECT_EQ(this->m_SkyGlow, 0.0f);
125  EXPECT_EQ(this->m_MaxNoise, 0.0f);
126 
127  // Validate our expectations about flux
128  EXPECT_EQ(this->m_MaxVal, maxval);
129  EXPECT_NEAR(this->flux(this->m_SaturationMag), maxval, 0.001);
130  EXPECT_NEAR(this->flux(this->m_LimitingMag), 1.0, 0.001);
131 
132  // The CCD frame is NOT initialized after this call, so manually clear the buffer
133  memset(this->PrimaryCCD.getFrameBuffer(), 0, this->PrimaryCCD.getFrameBufferSize());
134 
135  // Draw a star at the center row/column of the sensor
136  // If we expose a magnitude of 0 for 1 second, we get max ADUs at center, gaussian decrement away by 4 pixels and zero elsewhere
137  DrawImageStar(&PrimaryCCD, 0.0f, xres / 2 + 1, xres / 2 + 1, 1.0f);
138 
139  // Get a pointer to the 16-bit frame buffer
140  uint16_t const * const fb = reinterpret_cast<uint16_t*>(PrimaryCCD.getFrameBuffer());
141 
142  // Look at center, and up to 3 pixels away, and find activated photosites there - there is no skyglow nor noise in our parameters
143  int const center = xres / 2 + 1 + (yres / 2 + 1) * xres;
144 
145  // The choice of the gaussian of unitary integral makes the center less than maximum
146  double const sigma = 1.0 / (2 * sqrt(2 * log(2)));
147  double const fa0 = 1.0 / (sigma * sqrt(2 * 3.1416));
148 
149  // Center photosite
150  uint16_t const ADU_at_center = static_cast<uint16_t>(fa0 * maxval);
151  EXPECT_EQ(fb[center], ADU_at_center) << "Recorded flux of magnitude 0.0 for 1 second at center is " << ADU_at_center <<
152  " ADUs";
153 
154  // Up, left, right and bottom photosites at one pixel
155  uint16_t const ADU_at_1pix = static_cast<uint16_t>(fa0 * maxval * exp(-(1 * 1 + 0 * 0) / (2 * sigma * sigma)));
156  EXPECT_EQ(fb[center - xres], ADU_at_1pix);
157  EXPECT_EQ(fb[center - 1], ADU_at_1pix);
158  EXPECT_EQ(fb[center + 1], ADU_at_1pix);
159  EXPECT_EQ(fb[center + xres], ADU_at_1pix);
160 
161  // Up, left, right and bottom photosites at two pixels
162  uint16_t const ADU_at_2pix = static_cast<uint16_t>(fa0 * maxval * exp(-(2 * 2 + 0 * 0) / (2 * sigma * sigma)));
163  EXPECT_EQ(fb[center - xres * 2], ADU_at_2pix);
164  EXPECT_EQ(fb[center - 1 * 2], ADU_at_2pix);
165  EXPECT_EQ(fb[center + 1 * 2], ADU_at_2pix);
166  EXPECT_EQ(fb[center + xres * 2], ADU_at_2pix);
167 
168  // Up, left, right and bottom photosite neighbors at three pixels
169  uint16_t const ADU_at_3pix = static_cast<uint16_t>(fa0 * maxval * exp(-(3 * 3 + 0 * 0) / (2 * sigma * sigma)));
170  EXPECT_EQ(fb[center - xres * 3], ADU_at_3pix);
171  EXPECT_EQ(fb[center - 1 * 3], ADU_at_3pix);
172  EXPECT_EQ(fb[center + 1 * 3], ADU_at_3pix);
173  EXPECT_EQ(fb[center + xres * 3], ADU_at_3pix);
174 
175  // Up, left, right and bottom photosite neighbors at four pixels
176  EXPECT_EQ(fb[center - xres * 4], 0.0);
177  EXPECT_EQ(fb[center - 1 * 4], 0.0);
178  EXPECT_EQ(fb[center + 1 * 4], 0.0);
179  EXPECT_EQ(fb[center + xres * 4], 0.0);
180 
181  // Conclude with a random benchmark
182  auto const before = std::chrono::steady_clock::now();
183  int const loops = 200000;
184  for (int i = 0; i < loops; i++)
185  {
186  float const m = (15.0f * rand()) / RAND_MAX;
187  float const x = static_cast<float>(xres * rand()) / RAND_MAX;
188  float const y = static_cast<float>(yres * rand()) / RAND_MAX;
189  float const e = (100.0f * rand()) / RAND_MAX;
190  DrawImageStar(&PrimaryCCD, m, x, y, e);
191  }
192  auto const after = std::chrono::steady_clock::now();
193  auto const duration = std::chrono::duration_cast <std::chrono::nanoseconds> (after - before).count() / loops;
194  std::cout << "[ ] DrawStarImage - randomized no-noise no-skyglow benchmark: " << duration << "ns per call" <<
195  std::endl;
196  }
197 };
198 
199 TEST(CCDSimulatorDriverTest, test_properties)
200 {
202 }
203 
204 TEST(CCDSimulatorDriverTest, test_guide_api)
205 {
207 }
208 
209 TEST(CCDSimulatorDriverTest, test_draw_star)
210 {
212 }
213 
214 int main(int argc, char **argv)
215 {
218 
219  ::testing::InitGoogleTest(&argc, argv);
220  ::testing::InitGoogleMock(&argc, argv);
221  return RUN_ALL_TESTS();
222 }
The CCDSim class provides an advanced simulator for a CCD that includes a dedicated on-board guide ch...
Definition: ccd_simulator.h:43
bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
float guideWEOffset
virtual IPState GuideSouth(uint32_t) override
Guide southward for ms milliseconds.
float ImageScalex
float GuideRate
Guide rate is 7 arcseconds per second.
float m_SkyGlow
void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
float seeing
float guideNSOffset
int DrawImageStar(INDI::CCDChip *targetChip, float, float, float, float ExposureTime)
int m_MaxVal
bool setupParameters()
virtual IPState GuideEast(uint32_t) override
Guide easward for ms milliseconds.
double flux(double magnitude) const
virtual IPState GuideWest(uint32_t) override
Guide westward for ms milliseconds.
double currentDE
float m_LimitingMag
virtual IPState GuideNorth(uint32_t) override
Guide northward for ms milliseconds.
float m_SaturationMag
double currentRA
int m_MaxNoise
float ImageScaley
INDI::PropertyNumber getNumber(const char *name) const
Definition: basedevice.cpp:89
uint8_t * getFrameBuffer()
getFrameBuffer Get raw frame buffer of the CCD chip.
Definition: indiccdchip.h:209
float getPixelSizeY() const
getPixelSizeY Get vertical pixel size in microns.
Definition: indiccdchip.h:158
int getBPP() const
getBPP Get CCD Chip depth (bits per pixel).
Definition: indiccdchip.h:167
float getPixelSizeX() const
getPixelSizeX Get horizontal pixel size in microns.
Definition: indiccdchip.h:149
int getXRes() const
getXRes Get the horizontal resolution in pixels of the CCD Chip.
Definition: indiccdchip.h:77
int getYRes() const
Get the vertical resolution in pixels of the CCD Chip.
Definition: indiccdchip.h:86
CCDChip PrimaryCCD
Definition: indiccd.h:629
@ IPS_OK
Definition: indiapi.h:162
Implementations for common driver routines.
static const loggerConf file_off
Definition: indilogger.h:219
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,...
Definition: indilogger.cpp:283
static Logger & getInstance()
Method to get a reference to the object (i.e., Singleton) It is a static method.
Definition: indilogger.cpp:339
int main(int argc, char **argv)
TEST(CCDSimulatorDriverTest, test_properties)
char _me[]
char * me