libeblearntools
|
00001 /*************************************************************************** 00002 * Copyright (C) 2009 by Pierre Sermanet * 00003 * pierre.sermanet@gmail.com * 00004 * All rights reserved. 00005 * 00006 * Redistribution and use in source and binary forms, with or without 00007 * modification, are permitted provided that the following conditions are met: 00008 * * Redistributions of source code must retain the above copyright 00009 * notice, this list of conditions and the following disclaimer. 00010 * * Redistributions in binary form must reproduce the above copyright 00011 * notice, this list of conditions and the following disclaimer in the 00012 * documentation and/or other materials provided with the distribution. 00013 * * Redistribution under a license not approved by the Open Source 00014 * Initiative (http://www.opensource.org) must display the 00015 * following acknowledgement in all advertising material: 00016 * This product includes software developed at the Courant 00017 * Institute of Mathematical Sciences (http://cims.nyu.edu). 00018 * * The names of the authors may not be used to endorse or promote products 00019 * derived from this software without specific prior written permission. 00020 * 00021 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED 00022 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 00023 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 00024 * DISCLAIMED. IN NO EVENT SHALL ThE AUTHORS BE LIABLE FOR ANY 00025 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 00026 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00027 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 00028 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 00029 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 00030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00031 ***************************************************************************/ 00032 00033 #ifndef PASCAL_DATASET_HPP_ 00034 #define PASCAL_DATASET_HPP_ 00035 00036 #include <algorithm> 00037 00038 #include "xml_utils.h" 00039 #include "tools_utils.h" 00040 00041 #ifdef __BOOST__ 00042 #define BOOST_FILESYSTEM_VERSION 2 00043 #include "boost/filesystem.hpp" 00044 #include "boost/regex.hpp" 00045 using namespace boost::filesystem; 00046 using namespace boost; 00047 #endif 00048 00049 using namespace std; 00050 00051 namespace ebl { 00052 00054 // constructors & initializations 00055 00056 template <class Tdata> 00057 pascal_dataset<Tdata>::pascal_dataset(const char *name_, const char *inroot_, 00058 bool ignore_diff, bool ignore_trunc, 00059 bool ignore_occl, 00060 const char *annotations, 00061 const char *ignore_path, 00062 bool ignore_bb) 00063 : dataset<Tdata>(name_, inroot_), 00064 min_aspect_ratio(-1), max_aspect_ratio(-1), minborders(), 00065 max_jitter_match(1.0) { 00066 // initialize pascal-specific members 00067 // if (inroot_) { 00068 // annroot = inroot; 00069 // annroot += "/Annotations/"; // look for xml files in annotations 00070 // imgroot = inroot; 00071 // imgroot += "JPEGImages/"; // image directory 00072 // } 00073 imgroot = inroot; 00074 if (annotations && strcmp(annotations, "")) 00075 annroot = annotations; 00076 else 00077 eblerror("expected annotations folder, please specify with -annotations"); 00078 if (ignore_path && strcmp(ignore_path, "")) 00079 ignore_root = ignore_path; 00080 ignore_difficult = ignore_diff; 00081 ignore_bbox = ignore_bb; 00082 ignore_truncated = ignore_trunc; 00083 ignore_occluded = ignore_occl; 00084 #ifndef __XML__ // return error if xml not enabled 00085 eblerror("XML libraries not available, install libxml++ and recompile"); 00086 #endif /* __XML__ */ 00087 extension = XML_PATTERN; 00088 cout << "Image search extension pattern: " << extension << endl; 00089 } 00090 00091 template <class Tdata> 00092 pascal_dataset<Tdata>::~pascal_dataset() { 00093 } 00094 00096 // data extraction 00097 00098 template <class Tdata> 00099 bool pascal_dataset<Tdata>::extract() { 00100 #ifdef __BOOST__ 00101 #ifdef __XML__ 00102 if (!allocated && !strcmp(save_mode.c_str(), DATASET_SAVE)) 00103 return false; 00104 cout << "Extracting samples from PASCAL files into dataset..." << endl; 00105 // adding data to dataset using all xml files in annroot 00106 path p(annroot); 00107 if (!exists(p)) 00108 eblerror("Annotation path " << annroot << " does not exist."); 00109 xtimer.start(); 00110 processed_cnt = 0; 00111 // find all xml files recursively 00112 list<string> *files = find_fullfiles(annroot, XML_PATTERN, NULL, true, 00113 true); 00114 if (!files || files->size() == 0) 00115 eblerror("no xml files found in " << annroot << " using file pattern " 00116 << XML_PATTERN); 00117 cout << "Found " << files->size() << " xml files." << endl; 00118 for (list<string>::iterator i = files->begin(); i != files->end(); ++i) { 00119 this->process_xml(*i); 00120 processed_cnt++; 00121 if (full()) 00122 break; 00123 } 00124 cout << "Extracted " << data_cnt << " elements into dataset." << endl; 00125 cout << "Extraction time: " << xtimer.elapsed() << endl; 00126 print_stats(); 00127 if (files) delete files; 00128 #endif /* __XML__ */ 00129 #endif /* __BOOST__ */ 00130 return true; 00131 } 00132 00133 template <class Tdata> 00134 void pascal_dataset<Tdata>::set_min_aspect_ratio(float minar) { 00135 cout << "Setting minimum aspect ratio to " << minar << endl; 00136 min_aspect_ratio = minar; 00137 } 00138 00139 template <class Tdata> 00140 void pascal_dataset<Tdata>::set_max_aspect_ratio(float maxar) { 00141 cout << "Setting maximum aspect ratio to " << maxar << endl; 00142 max_aspect_ratio = maxar; 00143 } 00144 00145 template <class Tdata> 00146 void pascal_dataset<Tdata>::set_minborders(idxdim &d) { 00147 minborders = d; 00148 cout << "Setting minimum distances allowed to image borders to " 00149 << minborders << endl; 00150 } 00151 00152 template <class Tdata> 00153 void pascal_dataset<Tdata>::set_max_jitter_match(float match) { 00154 max_jitter_match = match; 00155 cout << "Setting maximum jitter match between jittered rects and other " 00156 << "objects in an image to " << max_jitter_match << endl; 00157 } 00158 00160 // data 00161 00162 #ifdef __XML__ // disable some derived methods if XML not available 00163 #ifdef __BOOST__ 00164 00165 template <class Tdata> 00166 bool pascal_dataset<Tdata>::included(const string &class_name, 00167 uint difficult, uint truncated, 00168 uint occluded) { 00169 return dataset<Tdata>::included(class_name) 00170 && !(ignore_difficult && difficult) 00171 && !(ignore_truncated && truncated) 00172 && !(ignore_occluded && occluded); 00173 } 00174 00175 template <class Tdata> 00176 intg pascal_dataset<Tdata>::count_samples() { 00177 total_difficult = 0; 00178 total_truncated = 0; 00179 total_occluded = 0; 00180 total_ignored = 0; 00181 total_samples = 0; 00182 string xmlpath; 00183 path p(annroot); 00184 if (!exists(p)) 00185 eblthrow("Annotation path " << annroot << " does not exist."); 00186 cout << "Counting number of samples in " << annroot << " ..." << endl; 00187 // find all xml files recursively 00188 list<string> *files = find_fullfiles(annroot, XML_PATTERN, NULL, false, 00189 true); 00190 if (!files || files->size() == 0) 00191 eblthrow("no xml files found in " << annroot << " using file pattern " 00192 << XML_PATTERN); 00193 cout << "Found " << files->size() << " xml files." << endl; 00194 for (list<string>::iterator i = files->begin(); i != files->end(); ++i) { 00195 xmlpath = *i; 00196 // parse xml 00197 DomParser parser; 00198 parser.parse_file(xmlpath); 00199 if (parser) { 00200 // initialize root node and list 00201 const Node* pNode = parser.get_document()->get_root_node(); 00202 Node::NodeList list = pNode->get_children(); 00203 // parse all objects in image 00204 for(Node::NodeList::iterator iter = list.begin(); 00205 iter != list.end(); ++iter) { 00206 if (!strcmp((*iter)->get_name().c_str(), "object")) { 00207 // check for difficult flag in object node 00208 Node::NodeList olist = (*iter)->get_children(); 00209 count_sample(olist); 00210 } 00211 } 00212 } 00213 } 00214 if (files) delete files; 00215 cout << "Found: " << total_samples << " samples, including "; 00216 cout << total_difficult << " difficult, " << total_truncated; 00217 cout << " truncated and " << total_occluded << " occluded." << endl; 00218 ignore_difficult ? cout << "Ignoring" : cout << "Using"; 00219 cout << " difficult samples." << endl; 00220 ignore_truncated ? cout << "Ignoring" : cout << "Using"; 00221 cout << " truncated samples." << endl; 00222 ignore_occluded ? cout << "Ignoring" : cout << "Using"; 00223 cout << " occluded samples." << endl; 00224 total_samples = total_samples - total_ignored; 00225 return total_samples; 00226 } 00227 00228 template <class Tdata> 00229 void pascal_dataset<Tdata>::count_sample(Node::NodeList &olist) { 00230 uint difficult = 0, truncated = 0, occluded = 0; 00231 string obj_classname, pose; 00232 bool pose_found = false; 00233 Node::NodeList::iterator oiter; 00234 00235 for(oiter = olist.begin(); oiter != olist.end(); ++oiter) { 00236 if (!strcmp((*oiter)->get_name().c_str(), "difficult")) 00237 difficult = xml_get_uint(*oiter); 00238 else if (!strcmp((*oiter)->get_name().c_str(), "truncated")) 00239 truncated = xml_get_uint(*oiter); 00240 else if (!strcmp((*oiter)->get_name().c_str(), "occluded")) 00241 occluded = xml_get_uint(*oiter); 00242 else if (!strcmp((*oiter)->get_name().c_str(), "name")) 00243 xml_get_string(*oiter, obj_classname); 00244 else if (!strcmp((*oiter)->get_name().c_str(), "pose")) { 00245 xml_get_string(*oiter, pose); 00246 pose_found = true; 00247 } 00248 } 00249 00251 // object 00252 if (!usepartsonly) { 00253 // add object's class to dataset 00254 if (included(obj_classname, difficult, truncated, occluded)) { 00255 if (usepose && pose_found) { // append pose to class name 00256 obj_classname += "_"; 00257 obj_classname += pose; 00258 } 00259 if (included(obj_classname, difficult, truncated, occluded)) 00260 this->add_class(obj_classname); 00261 } 00262 } 00263 // increment samples numbers 00264 total_samples++; 00265 if (difficult) total_difficult++; 00266 if (truncated) total_truncated++; 00267 if (occluded) total_occluded++; 00268 if ((difficult && ignore_difficult) 00269 || (truncated && ignore_truncated) 00270 || (occluded && ignore_occluded)) 00271 total_ignored++; 00272 00274 // parts 00275 if (useparts || usepartsonly) { 00276 string part_classname; 00277 00278 // add part's class to dataset 00279 for(oiter = olist.begin();oiter != olist.end(); ++oiter) { 00280 if (!strcmp((*oiter)->get_name().c_str(), "part")) { 00281 // get part's name 00282 Node::NodeList plist = (*oiter)->get_children(); 00283 for(Node::NodeList::iterator piter = plist.begin(); 00284 piter != plist.end(); ++piter) { 00285 if (!strcmp((*piter)->get_name().c_str(), "name")) { 00286 xml_get_string(*piter, part_classname); 00287 // found a part and its name, add it 00288 if (included(part_classname, difficult, truncated, occluded)) { 00289 if (usepose && pose_found) { // append pose to class name 00290 part_classname += "_"; 00291 part_classname += pose; 00292 } 00293 if (dataset<Tdata>::included(part_classname)) { 00294 this->add_class(part_classname); 00295 // increment samples numbers 00296 this->total_samples++; 00297 if (difficult) total_difficult++; 00298 if (truncated) total_truncated++; 00299 if (occluded) total_occluded++; 00300 if ((difficult && ignore_difficult) 00301 || (truncated && ignore_truncated) 00302 || (occluded && ignore_occluded)) 00303 total_ignored++; 00304 } 00305 } 00306 } 00307 } 00308 } 00309 } 00310 } 00311 } 00312 00314 // process xml 00315 00316 template <class Tdata> 00317 bool pascal_dataset<Tdata>::process_xml(const string &xmlfile) { 00318 string image_filename, image_fullname, folder; 00319 int height = -1, width = -1, depth = -1; 00320 rect<int> *cropr = NULL; 00321 00322 // get image's properties 00323 if (!pascal_xml::get_properties(imgroot, xmlfile, image_filename, 00324 image_fullname, folder, height, width, 00325 depth, objects, &cropr, false)) 00326 return false; 00327 // get ignored boxes if present 00328 if (!ignore_root.empty()) { 00329 string bname = ebl::basename(xmlfile.c_str()); 00330 string dname = ebl::dirname(image_filename.c_str()); 00331 string xml, filename, fullname, folder; 00332 int h = -1, w = -1, d = -1; 00333 xml << ignore_root << "/" << dname << "/" << bname; 00334 if (file_exists(xml)) { 00335 if (!pascal_xml::get_properties(imgroot, xml, filename, fullname, 00336 folder, h, w, d, objects, &cropr, true)) 00337 return false; 00338 // check that ignored properties matches original properties 00339 if (fullname.compare(image_fullname) || (height != h) || (width != w)) 00340 eblerror("mistmatch between orignal image (" << image_fullname << ", " 00341 << height << "x" << width << ") and ignored infos (" 00342 << fullname << ", " << h << "x" << w << ")"); 00343 } else // ignore file doesnt exist 00344 cerr << "warning: ignore xml not found: " << xml << endl; 00345 } 00346 // process objects 00347 process_objects(objects, height, width, image_fullname, 00348 image_filename, cropr); 00349 // delete all objects 00350 for (uint i = 0; i < objects.size(); ++i) 00351 delete objects[i]; 00352 objects.clear(); 00353 if (cropr) delete cropr; 00354 return true; 00355 } 00356 00358 // process all objects 00359 00360 template <class Tdata> 00361 bool pascal_dataset<Tdata>:: 00362 process_objects(const vector<object*> &objs, int height, int width, 00363 const string &image_fullname, const string &image_filename, 00364 const rect<int> *cropr) { 00365 idx<Tdata> *img = NULL; 00366 // process all objects 00367 for (iobj = 0; iobj < objs.size(); ++iobj) { 00368 object &o = *(objs[iobj]); 00369 if (!o.ignored) { // only process non-ignored objects 00370 if (!usepartsonly) { 00371 // check that minimum visibility ratio is respected 00372 if (minvisibility > 0.0 && o.visible) { 00373 float match = o.match(*o.visible); 00374 if (match < minvisibility) { 00375 cout << "Rejecting object " << o.id << " from " 00376 << image_filename << " because visible ratio (" 00377 << match << ") is lower than " 00378 << "limit (" << minvisibility << ")" << endl; 00379 continue ; // skip this object 00380 } 00381 } 00382 // check that minimum aspect ratio is respected 00383 if (o.height == 0) continue ; // skip this object 00384 float ar = o.width / (float) o.height; 00385 if (min_aspect_ratio >= 0.0 && ar < min_aspect_ratio) { 00386 cout << "Rejecting object " << o.id << " from " << image_filename 00387 << " because aspect ratio of " << o << " (" << ar 00388 << ") is lower than " << "limit (" << min_aspect_ratio << ")" 00389 << endl; 00390 continue ; // skip this object 00391 } 00392 if (max_aspect_ratio >= 0.0 && ar > max_aspect_ratio) { 00393 cout << "Rejecting object " << o.id << " from " << image_filename 00394 << " because aspect ratio of " << o << " (" << ar 00395 << ") is bigger than " << "limit (" << max_aspect_ratio << ")" 00396 << endl; 00397 continue ; // skip this object 00398 } 00399 // check that object is larger than minimum size 00400 if (o.height < mindims.dim(0) || o.width < mindims.dim(1)) { 00401 cout << "Rejecting object " << o.id << " from " 00402 << image_filename << " because object's size (" 00403 << o << ") is smaller than minimum size " 00404 << "(" << mindims << ")" << endl; 00405 continue ; // skip this object 00406 } 00407 // check that bbox is not below image border distances 00408 if (!minborders.empty()) { 00409 if (height < 0 || width < 0) { // we don't know the image sizes 00410 eblerror("image sizes not found in xml"); 00411 } // from now on assume all images have the same size (TODO) 00412 int hmax = height - minborders.dim(0); 00413 int wmax = width - minborders.dim(1); 00414 if (o.h0 < (int) minborders.dim(0) || o.w0 < minborders.dim(1) 00415 || o.h0 + o.height > hmax || o.w0 + o.width > wmax) { 00416 cout << "Rejecting object " << o.id << " from " 00417 << image_filename << " because (cropped) bbox " << o 00418 << " is closer to image borders (" << height << "x" << width 00419 << ") than allowed (" << minborders << ")" << endl; 00420 continue ; // skip this object 00421 } 00422 } 00423 // process image 00424 if (included(o.name, o.difficult, o.truncated, o.occluded)) { 00425 // load image if not already loaded 00426 if (!img) { 00427 // check that image exists 00428 if (!file_exists(image_fullname)) { 00429 cerr << "Error: image not found " << image_fullname << endl; 00430 return false; 00431 } 00432 input_height = height; 00433 input_width = width; 00434 //load image 00435 load_data(image_fullname); 00436 } 00437 // remove jitters that overlap with other objects 00438 if (max_jitter_match > 0) 00439 remove_jitter_matches(objs, iobj, max_jitter_match); 00440 // extract object from image 00441 process_image(load_img, o, o.name, o.difficult, 00442 image_fullname, o.centroid, o.visible, cropr); 00443 load_img.clear(); 00444 } 00445 } 00446 } 00447 } 00448 if (img) delete img; 00449 return true; 00450 } 00451 00452 template <class Tdata> 00453 void pascal_dataset<Tdata>::load_data(const string &fname) { 00454 // load image 00455 dataset<Tdata>::load_data(fname); 00456 // check that image matches expected sizes 00457 if (load_img.get(0).dim(0) != input_height 00458 || load_img.get(0).dim(1) != input_width) { 00459 eblerror( "Error: expected image of size is different \ 00460 from loaded image size"); 00461 } 00462 } 00463 00465 // process object's image 00466 00467 template <class Tdata> 00468 void pascal_dataset<Tdata>:: 00469 process_image(midx<Tdata> &imgs, const rect<int> &r, 00470 string &obj_class, uint difficult, const string &image_filename, 00471 pair<int,int> *center, const rect<int> *visr, 00472 const rect<int> *cropr) { 00473 t_label label = this->get_label_from_class(obj_class); 00474 rect<int> rr = r; 00475 if (ignore_bbox) 00476 rr = rect<int>(0, 0, imgs.get(0).dim(0), imgs.get(0).dim(1)); 00477 add_data(imgs, label, &obj_class, image_filename.c_str(), &rr, center, 00478 visr, cropr, &objects); 00479 imgs.clear(); 00480 } 00481 00483 #endif /* __XML__ */ 00484 #endif /* __BOOST__ */ 00485 00486 template <class Tdata> 00487 void pascal_dataset<Tdata>::compute_random_jitter() { 00488 // recompute all possible jitters 00489 dataset<Tdata>::compute_random_jitter(); 00490 // remove jitters overlapping with other boxes given current box 00491 remove_jitter_matches(objects, iobj, max_jitter_match); 00492 } 00493 00494 template <class Tdata> 00495 void pascal_dataset<Tdata>:: 00496 remove_jitter_matches(const vector<object*> &objs, 00497 uint iobj, float max_match) { 00498 rect<int> ri = *objs[iobj]; 00499 // apply w/h ratio to object 00500 if (this->bbox_woverh > 0) 00501 ri.scale_width(this->bbox_woverh); 00502 // find ratio between current box and target 00503 float ratio = std::max(ri.height / (float) this->height, 00504 ri.width / (float) this->width); 00505 EDEBUG("jitter ratio is " << ratio); 00506 // check overlaps with other objects 00507 vector<jitter>::iterator i = random_jitter.begin(); 00508 for ( ; i != random_jitter.end(); ++i) { 00509 rect<int> rijitt = i->get_rect(ri, ratio); 00510 for (uint j = 0; j < objs.size(); ++j) { 00511 rect<int> rj = *objs[j]; 00512 // scale object the same way jitter has been scaled 00513 rj.scale_centered(i->s, i->s); 00514 // apply w/h ratio to object 00515 if (this->bbox_woverh > 0) 00516 rj.scale_width(this->bbox_woverh); 00517 if (rj.match(rijitt) > max_match) { 00518 // there is a match, remove this jitter 00519 // note: erasing in a vector is not efficient because it has to move 00520 // all elements, but this should be rare enough to be better than 00521 // creating a new vector to which we add all elements. 00522 cout << "(removing jitter) match with jitter is more than " 00523 << max_match << ": " << rj << " and " << rijitt 00524 << " match by " << rj.match(rijitt) << endl; 00525 i = random_jitter.erase(i); 00526 if (i == random_jitter.end()) 00527 return ; 00528 break ; // stop looking for match 00529 } 00530 } 00531 } 00532 } 00533 00534 template <class Tdata> 00535 void pascal_dataset<Tdata>::extract_statistics() { 00536 path p(annroot); 00537 if (!exists(p)) 00538 eblerror("Annotation path " << annroot << " does not exist."); 00539 // find all xml files recursively 00540 list<string> *files = find_fullfiles(annroot, XML_PATTERN, NULL, true, 00541 true); 00542 if (!files || files->size() == 0) 00543 eblerror("no xml files found in " << annroot << " using file pattern " 00544 << XML_PATTERN); 00545 cout << "Found " << files->size() << " xml files." << endl; 00546 // open an output file 00547 string fname; 00548 mkdir_full(outdir.c_str()); 00549 fname << outdir << "/stats.csv"; 00550 ofstream *fp = new ofstream(fname.c_str()); 00551 if (!fp) eblerror("failed to open " << fname); 00552 cout << "Extracting statistics into " << fname << " ..." << endl; 00553 // write header 00554 *fp << "filename; height; width; h/w ratio; bbox height; bbox width; bbox h/w ratio; " 00555 << "max context;" << endl; 00556 // write stats 00557 for (list<string>::iterator i = files->begin(); i != files->end(); ++i) 00558 this->write_statistics(*i, *fp); 00559 if (files) delete files; 00560 delete fp; 00561 } 00562 00563 template <class Tdata> 00564 void pascal_dataset<Tdata>::write_statistics(string &xml, ofstream &fp) { 00565 string image_filename, image_fullname, folder; 00566 int height = -1, width = -1, depth = -1; 00567 rect<int> *cropr = NULL; 00568 00569 // get image's properties 00570 if (!pascal_xml::get_properties(imgroot, xml, image_filename, 00571 image_fullname, folder, height, width, 00572 depth, objects, &cropr, false)) 00573 eblerror("error while getting properties of " << xml); 00574 // loop on objects 00575 for (uint i = 0; i < objects.size(); ++i) { 00576 object &o = *objects[i]; 00577 // compute maximum context ratio of object 00578 float maxh = (float) std::max(0, std::min(o.h0, height - o.h0 +o.height)); 00579 float maxw = (float) std::max(0, std::min(o.w0, width - o.w0 + o.width)); 00580 float cratio = std::min((maxh * 2 + o.height) / o.height, 00581 (maxw * 2 + o.width) / o.width); 00582 fp << xml << "; " << height << "; " << width 00583 << "; " << height / (float) width << "; " 00584 << o.height << "; " << o.width << "; " << o.height / (float) o.width 00585 << "; " << cratio << ';' << endl; 00586 } 00587 // delete all objects 00588 for (uint i = 0; i < objects.size(); ++i) 00589 delete objects[i]; 00590 objects.clear(); 00591 if (cropr) delete cropr; 00592 } 00593 00594 } // end namespace ebl 00595 00596 #endif /* PASCAL_DATASET_HPP_ */