Nori  23
ttest.cpp
1 /*
2  This file is part of Nori, a simple educational ray tracer
3 
4  Copyright (c) 2015 by Wenzel Jakob
5 
6  Nori is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License Version 3
8  as published by the Free Software Foundation.
9 
10  Nori is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include <nori/scene.h>
20 #include <nori/bsdf.h>
21 #include <nori/camera.h>
22 #include <nori/integrator.h>
23 #include <nori/sampler.h>
24 #include <hypothesis.h>
25 #include <pcg32.h>
26 
27 /*
28  * =======================================================================
29  * WARNING WARNING WARNING WARNING WARNING WARNING
30  * =======================================================================
31  * Remember to put on SAFETY GOGGLES before looking at this file. You
32  * are most certainly not expected to read or understand any of it.
33  * =======================================================================
34  */
35 
36 NORI_NAMESPACE_BEGIN
37 
61 class StudentsTTest : public NoriObject {
62 public:
63  StudentsTTest(const PropertyList &propList) {
64  /* The null hypothesis will be rejected when the associated
65  p-value is below the significance level specified here. */
66  m_significanceLevel = propList.getFloat("significanceLevel", 0.01f);
67 
68  /* This parameter specifies a list of incidence angles that will be tested */
69  std::vector<std::string> angles = tokenize(propList.getString("angles", ""));
70  for (auto angle : angles)
71  m_angles.push_back(toFloat(angle));
72 
73  /* This parameter specifies a list of distances from the origin, for light source testing */
74  std::vector<std::string> distances = tokenize(propList.getString("distances", ""));
75  for (auto distance : distances)
76  m_distances.push_back(toFloat(distance));
77 
78  /* This parameter specifies a list of reference values, one for each angle */
79  std::vector<std::string> references = tokenize(propList.getString("references", ""));
80  for (auto angle : references)
81  m_references.push_back(toFloat(angle));
82 
83  /* Number of BSDF samples that should be generated (default: 100K) */
84  m_sampleCount = propList.getInteger("sampleCount", 100000);
85  }
86 
87  virtual ~StudentsTTest() {
88  for (auto bsdf : m_bsdfs)
89  delete bsdf;
90  for (auto emitter : m_emitters)
91  delete emitter;
92  for (auto scene : m_scenes)
93  delete scene;
94  }
95 
96  virtual void addChild(NoriObject *obj) override {
97  switch (obj->getClassType()) {
98  case EBSDF:
99  m_bsdfs.push_back(static_cast<BSDF *>(obj));
100  break;
101 
102  case EEmitter:
103  m_emitters.push_back(static_cast<Emitter *>(obj));
104  break;
105  // TODO: allow area lights?
106 
107  case EScene:
108  m_scenes.push_back(static_cast<Scene *>(obj));
109  break;
110 
111  default:
112  throw NoriException("StudentsTTest::addChild(<%s>) is not supported!",
113  classTypeName(obj->getClassType()));
114  }
115  }
116 
118  virtual void activate() override {
119  int total = 0, passed = 0;
120  pcg32 random;
121 
122  if (!m_bsdfs.empty()) {
123  if (m_references.size() != m_bsdfs.size() * m_angles.size())
124  throw NoriException("Specified a different number of angles and reference values! %d != %d x %d",
125  m_references.size(), m_bsdfs.size(), m_angles.size());
126  if (!m_emitters.empty() || !m_scenes.empty())
127  throw NoriException("Cannot test BSDFs, emitters, and scenes at the same time!");
128 
129  /* Test each registered BSDF */
130  int ctr = 0;
131  for (auto bsdf : m_bsdfs) {
132  for (float angle : m_angles) {
133  float reference = m_references[ctr++];
134 
135  cout << "------------------------------------------------------" << endl;
136  cout << "Testing (angle=" << angle << "): " << bsdf->toString() << endl;
137  ++total;
138 
139  BSDFQueryRecord bRec(sphericalDirection(degToRad(angle), 0));
140 
141  cout << "Drawing " << m_sampleCount << " samples .. " << endl;
142  double mean=0, variance = 0;
143  for (int k=0; k<m_sampleCount; ++k) {
144  Point2f sample(random.nextFloat(), random.nextFloat());
145  double result = (double) bsdf->sample(bRec, sample).getLuminance();
146 
147  /* Numerically robust online variance estimation using an
148  algorithm proposed by Donald Knuth (TAOCP vol.2, 3rd ed., p.232) */
149  double delta = result - mean;
150  mean += delta / (double) (k+1);
151  variance += delta * (result - mean);
152  }
153  variance /= m_sampleCount - 1;
154  std::pair<bool, std::string>
155  result = hypothesis::students_t_test(mean, variance, reference,
156  m_sampleCount, m_significanceLevel, (int) m_references.size());
157 
158  if (result.first)
159  ++passed;
160  cout << result.second << endl;
161  }
162  }
163  } else if (!m_emitters.empty()) {
164  if (m_references.size() != m_emitters.size() * m_distances.size())
165  throw NoriException("Specified a different number of distances and reference values! %d != %d x %d",
166  m_references.size(), m_emitters.size(), m_distances.size());
167  if (!m_bsdfs.empty() || !m_scenes.empty())
168  throw NoriException("Cannot test BSDFs, emitters, and scenes at the same time!");
169 
170  /* Test each registered emitter */
171  int ctr = 0;
172  for (auto emitter : m_emitters) {
173  for (float distance : m_distances) {
174  float reference = m_references[ctr++];
175 
176  cout << "------------------------------------------------------" << endl;
177  cout << "Testing (distance=" << distance << "): " << emitter->toString() << endl;
178  ++total;
179 
180  Point3f ref(0, 0, distance);
181  EmitterQueryRecord lRec(ref);
182 
183  cout << "Drawing " << m_sampleCount << " samples .. " << endl;
184  double mean=0, variance = 0;
185  bool fields_correct = true;
186  bool eval_correct = true;
187  bool pdf_correct = true;
188  std::string reason;
189  for (int k=0; k<m_sampleCount; ++k) {
190  Point2f sample(random.nextFloat(), random.nextFloat());
191  double result = (double) emitter->sample(lRec, sample).getLuminance();
192 
193  // Check if the fields of the EmitterQueryRecord are correctly set
194  Vector3f ref2p = lRec.p - lRec.ref;
195  Vector3f wi = ref2p.normalized();
196  auto vectorEqual = [](const Vector3f &a, const Vector3f &b) {
197  return (a - b).squaredNorm() < 1e-5f;
198  };
199  if (!vectorEqual(ref, lRec.ref)) {
200  fields_correct = false;
201  reason = "`ref` is incorrect";
202  }
203  else if (!vectorEqual(wi, lRec.wi)) {
204  fields_correct = false;
205  reason = "`wi` is incorrect";
206  }
207  else if (!vectorEqual(ref, lRec.shadowRay.o) || !vectorEqual(wi, lRec.shadowRay.d)) {
208  fields_correct = false;
209  reason = "`shadowRay.o` or `shadowRay.d` is incorrect";
210  }
211  else if (std::abs(lRec.shadowRay.maxt - lRec.shadowRay.mint - ref2p.norm()) > 1e-3) {
212  fields_correct = false;
213  reason = "`shadowRay.maxt` - `shadowRay.mint` should equal to the distance between `ref` and `p`";
214  }
215  if (std::abs((double) emitter->eval(lRec).getLuminance() / lRec.pdf - result) > 1e-6) {
216  eval_correct = false;
217  }
218  else if (std::abs((double) emitter->pdf(lRec) - lRec.pdf) > 1e-6) {
219  pdf_correct = false;
220  }
221 
222  /* Numerically robust online variance estimation using an
223  algorithm proposed by Donald Knuth (TAOCP vol.2, 3rd ed., p.232) */
224  double delta = result - mean;
225  mean += delta / (double) (k+1);
226  variance += delta * (result - mean);
227  }
228 
229  variance /= m_sampleCount - 1;
230  std::pair<bool, std::string>
231  result = hypothesis::students_t_test(mean, variance, reference,
232  m_sampleCount, m_significanceLevel, (int) m_references.size());
233 
234  cout << result.second << endl;
235 
236  if (!result.first) {
237  cout << "The return of `sample` method is incorrect" << endl;
238  continue;
239  }
240  if (!fields_correct) {
241  cout << "EmitterQueryRecord fields are not set correctly during sampling: " << reason << endl;
242  continue;
243  }
244  if (!eval_correct) {
245  cout << "Either `eval` method is wrongly implemented or `pdf` field is incorrectly set" << endl;
246  continue;
247  }
248  if (!pdf_correct) {
249  cout << "Either `pdf` method is wrongly implemented or `pdf` field is incorrectly set" << endl;
250  continue;
251  }
252 
253  ++passed;
254  }
255  }
256  } else {
257  if (m_references.size() != m_scenes.size())
258  throw NoriException("Specified a different number of scenes and reference values!");
259  if (!m_bsdfs.empty() || !m_emitters.empty())
260  throw NoriException("Cannot test BSDFs, emitters, and scenes at the same time!");
261 
262  Sampler *sampler = static_cast<Sampler *>(
264 
265  int ctr = 0;
266  for (auto scene : m_scenes) {
267  const Integrator *integrator = scene->getIntegrator();
268  const Camera *camera = scene->getCamera();
269  float reference = m_references[ctr++];
270 
271  cout << "------------------------------------------------------" << endl;
272  cout << "Testing scene: " << scene->toString() << endl;
273  ++total;
274 
275  cout << "Generating " << m_sampleCount << " paths.. " << endl;
276 
277  double mean = 0, variance = 0;
278  for (int k=0; k<m_sampleCount; ++k) {
279  /* Sample a ray from the camera */
280  Ray3f ray;
281  Point2f pixelSample = (sampler->next2D().array()
282  * camera->getOutputSize().cast<float>().array()).matrix();
283  Color3f value = camera->sampleRay(ray, pixelSample, sampler->next2D());
284 
285  /* Compute the incident radiance */
286  value *= integrator->Li(scene, sampler, ray);
287 
288  /* Numerically robust online variance estimation using an
289  algorithm proposed by Donald Knuth (TAOCP vol.2, 3rd ed., p.232) */
290  double result = (double) value.getLuminance();
291  double delta = result - mean;
292  mean += delta / (double) (k+1);
293  variance += delta * (result - mean);
294  }
295  variance /= m_sampleCount - 1;
296 
297  std::pair<bool, std::string>
298  result = hypothesis::students_t_test(mean, variance, reference,
299  m_sampleCount, m_significanceLevel, (int) m_references.size());
300 
301  if (result.first)
302  ++passed;
303  cout << result.second << endl;
304  }
305  }
306  cout << "Passed " << passed << "/" << total << " tests." << endl;
307 
308  if (passed < total) {
309  throw std::runtime_error("Failed some of the tests");
310  }
311  }
312 
313  virtual std::string toString() const override {
314  return tfm::format(
315  "StudentsTTest[\n"
316  " significanceLevel = %f,\n"
317  " sampleCount= %i\n"
318  "]",
319  m_significanceLevel,
320  m_sampleCount
321  );
322  }
323 
324  virtual EClassType getClassType() const override { return ETest; }
325 private:
326  std::vector<BSDF *> m_bsdfs;
327  std::vector<Emitter *> m_emitters;
328  std::vector<Scene *> m_scenes;
329  std::vector<float> m_angles;
330  std::vector<float> m_distances;
331  std::vector<float> m_references;
332  float m_significanceLevel;
333  int m_sampleCount;
334 };
335 
336 NORI_REGISTER_CLASS(StudentsTTest, "ttest");
337 NORI_NAMESPACE_END
Superclass of all bidirectional scattering distribution functions.
Definition: bsdf.h:63
Generic camera interface.
Definition: camera.h:35
const Vector2i & getOutputSize() const
Return the size of the output image in pixels.
Definition: camera.h:62
virtual Color3f sampleRay(Ray3f &ray, const Point2f &samplePosition, const Point2f &apertureSample) const =0
Importance sample a ray according to the camera's response function.
Superclass of all emitters.
Definition: emitter.h:64
Abstract integrator (i.e. a rendering technique)
Definition: integrator.h:35
virtual Color3f Li(const Scene *scene, Sampler *sampler, const Ray3f &ray) const =0
Sample the incident radiance along a ray.
Simple exception class, which stores a human-readable error description.
Definition: common.h:148
static NoriObject * createInstance(const std::string &name, const PropertyList &propList)
Construct an instance from the class of the given name.
Definition: object.h:158
Base class of all objects.
Definition: object.h:32
static std::string classTypeName(EClassType type)
Turn a class type into a human-readable string.
Definition: object.h:51
virtual EClassType getClassType() const =0
Return the type of object (i.e. Mesh/BSDF/etc.) provided by this instance.
This is an associative container used to supply the constructors of NoriObject subclasses with parame...
Definition: proplist.h:32
float getFloat(const std::string &name) const
Get a float property, and throw an exception if it does not exist.
std::string getString(const std::string &name) const
Get a string property, and throw an exception if it does not exist.
int getInteger(const std::string &name) const
Get an integer property, and throw an exception if it does not exist.
Abstract sample generator.
Definition: sampler.h:63
virtual Point2f next2D()=0
Retrieve the next two component values from the current sample.
Main scene data structure.
Definition: scene.h:34
virtual void activate() override
Invoke a series of t-tests on the provided input.
Definition: ttest.cpp:118
virtual void addChild(NoriObject *obj) override
Add a child object to the current instance.
Definition: ttest.cpp:96
virtual EClassType getClassType() const override
Return the type of object (i.e. Mesh/BSDF/etc.) provided by this instance.
Definition: ttest.cpp:324
virtual std::string toString() const override
Return a brief string summary of the instance (for debugging purposes)
Definition: ttest.cpp:313
Convenience data structure used to pass multiple parameters to the evaluation and sampling routines i...
Definition: bsdf.h:30
Represents a linear RGB color value.
Definition: color.h:29
float getLuminance() const
Return the associated luminance.
Definition: common.cpp:233
Data record for conveniently querying and sampling the direct illumination technique implemented by a...
Definition: emitter.h:31
float pdf
Probability.
Definition: emitter.h:41
Vector3f wi
Direction between the hit point and the emitter point.
Definition: emitter.h:39
Point3f ref
Origin point from which we sample the emitter.
Definition: emitter.h:33
Ray3f shadowRay
Shadow ray.
Definition: emitter.h:43
Point3f p
Sampled point on the emitter.
Definition: emitter.h:35
VectorType d
Ray direction.
Definition: ray.h:44
Scalar mint
Minimum position on the ray segment.
Definition: ray.h:46
Scalar maxt
Maximum position on the ray segment.
Definition: ray.h:47
PointType o
Ray origin.
Definition: ray.h:43