Note
When a region of interest is identified in an image, one often needs to find it back later on.
In this session, we’ll implement a few I/O methods for the already existing shapes to be able to save and restore them. We’ll of course build atop the code we already developed during the course of the previous sessions.
In order to easily read back a set of shapes, our strategy to load a shape from disk and its implementation will consist of:
No data handling is asked for at this stage, it will be performed later on.
For the moment, we’ll only read a file holding just one shape. The grammar for the shape files is available and documented on this page.
Create a file shape.txt holding a square centered in (x=50, y=90) and of size 100. During the “projects session(s)”, we’ll augment this file with a set of shapes.
Commit this new file in svn under data directory of your DrawQt package:
$> cd data
$> svn add shape.txt
$> svn commit -m "adding my first shape file" shape.txt
When clicking on the bottom-right read button of DrawQt, we wish to be able to read a file holding one shape. The connection of this button to the void Visu2D::ReadSelection() method has already been implemented for you, you can nevertheless have a look at how this has been performed in the WFrame class.
Implement the void Visu2D::ReadSelection() method. It shall be able to:
Note
The return type of the QFileDialog will be a QString, but that the reading method of Selection is an ifstream. Thereafter how to convert this QString into a ifstream:
// First, create an ifstream pointer
std::ifstream f;
// Then convert the QString into an std::string using QString::toStdString() method
std::string my_name = fname.toStdString();
// Open the file
f.open(my_name.c_str());
Now you could call the reading method of Selection
When a region of interest is identified in an image, one often needs to find it back later on.
call the reading method of Selection
print a message in the class Selection to check everything is correct
Warning
Take care when you call an object method, that the object has been created and initialized before using it. Ex:
Selection my_selection = NULL;
....
my_selection->Read();
In this case my_selection is initialized to NULL. Using a method on a NULL pointer has disastrous effects on a program at runtime... You MUST protect your method calls by “if (my_selection)” statement.
Note
Similar to the reading of Image files, we’ll implement a method to read back Shape files in this exercize as described there.
The method to implement is given below. It can be used to cross-check with your implementation for the reading of Image files.
/**
The grammar for a shape-file is the following:
shapes ::= shape | shape shapes | comment shapes
shape ::= 'SHAPE' <number>
'TYPE' type_name data
'SHAPE_END'
comment ::= 'COMMENT' ... 'COMMENT_END'
type_name ::= 'SQUARE' | 'RECTANGLE' | 'CIRCLE' | 'ELLIPSE' | 'POLYGON'
data ::= envelope | list
envelope ::= 'ORIGIN' <x> <y>
'WIDTH' <width>
'HEIGHT' <height>
list ::= 'NB_POINTS' <number> points
points ::= point points | point
point ::= 'POINT' <x> <y>
*/
bool Selection::Read(std::ifstream &f)
{
bool status = false;
if ( !f.is_open() ) {
std::cout << "Error. File ..." << std::endl;
return false;
}
std::string word = "";
std::string type = "";
std::string tmp = "";
unsigned int id_nbr = 0;
unsigned int origin_x = 0;
unsigned int origin_y = 0;
unsigned int height = 0;
unsigned int width = 0;
unsigned int nb_points = 0;
unsigned int pt_x = 0;
unsigned int pt_y = 0;
// define a state variable to identify words located inside the definition
// of a given Shape.
/**
* @brief Enum state
* Identifies the state of the analysis w.r.t the Shape
*/
enum {
void_state,
shape,
comment,
type_name,
data,
envelope,
list,
points
} state;
// state initialization
state = void_state;
while ( !f.eof() ) {
if ( void_state == state ) {
f >> word;
// grammar reminder:
// -----------------
// shapes ::= shape | shape shapes | comment shapes
// shape ::= 'SHAPE' <number>
// 'TYPE' type_name data
// 'SHAPE_END'
// comment ::= 'COMMENT' ... 'COMMENT_END'
// in such a state, the allowed keywords are:
if ( "COMMENT" == word ) {
state = comment;
} else if ("SHAPE" == word) {
f >> id_nbr;
state = shape;
}
} else if (comment == state) {
// comment ::= 'COMMENT' ... 'COMMENT_END'
f >> tmp;
if ( "COMMENT_END" == tmp ) {
// go to next state...
state = void_state;
}
} else if (shape == state) {
// shape ::= 'SHAPE' <number>
// 'TYPE' type_name data
// 'SHAPE_END'
// => need to read the tokens following the word 'SHAPE': <number>
f >> word;
if ("TYPE" == word) {
state = type_name;
} else if ("SHAPE_END" == word) {
// end of shape OK.
status = true;
state = void_state;
} else {
QMessageBox msgBox;
msgBox.setText("Read shapes\n Error: invalid shape file");
msgBox.exec();
return false;
// error case(s) to be better handled...
}
} else if (type_name == state) {
// type_name ::= 'SQUARE' | 'RECTANGLE' | 'CIRCLE' | 'ELLIPSE' | 'POLYGON'
f >> type;
if ("SQUARE" == type) {
} else if ("RECTANGLE" == type) {
} else if ("CIRCLE" == type) {
} else if ("ELLIPSE" == type) {
} else if ("POLYGON" == type) {
} else {
QMessageBox msgBox;
msgBox.setText("Read shapes\n Error: invalid shape file");
msgBox.exec();
return false;
// error case(s) to be better handled...
}
state = data;
} else if (data == state) {
// data ::= envelope | list
f >> word;
if ("ORIGIN" == word) {
state = envelope;
} else if ("NB_POINTS" == word) {
state = list;
} else {
QMessageBox msgBox;
msgBox.setText("Read shapes\n Error: invalid shape file");
msgBox.exec();
return false;
// error case(s) to be better handled...
}
} else if (envelope == state) {
// envelope ::= 'ORIGIN' <x> <y>
// 'WIDTH' <width>
// 'HEIGHT' <height>
f >> origin_x;
f >> origin_y;
f >> word;
if ("WIDTH" != word) {
// handle error
}
f >> width;
f >> word;
if ("HEIGHT" != word) {
// handle error
}
f >> height;
state = shape;
} else if (list == state) {
// list ::= 'NB_POINTS' <number> points
f >> nb_points;
state = points;
} else if (points == state) {
// points ::= point points | point
// point ::= 'POINT' <x> <y>
for (unsigned int a = 1; a <= nb_points; ++a) {
f >> word;
if ("POINT" == word) {
f >> pt_x;
f >> pt_y;
} else {
// handle error
}
}
state = shape;
}
} //> while-loop
return status;
}
As we don’t handle (yet) the shapes parameters, nothing is visible because no shape is painted. The only means to check everything is working properly is to print out messages (so we know the reading is performing well).
As usual, build, test and commit to svn:
$> cmt make
$> svn commit -m "implemented data file reading method with keywords"
Note
The reading method being implemented, we’re still left with actually completing its skeleton (allocating shapes, handling of errors, ...)
For each shape we read from disk, we need to actually create an instance of the according class. To this end, a method Shape* AllocateShape(SHAPE_TYPE type) needs to be called with the correct type argument.
Warning
Do not forget to handle all the possible error cases (e.g. a non existing shape name).
Note
Do not bother (for the moment) with the type POLYGON which will be tackled during a dedicated project later on.
Build, test and commit to svn:
$> cmt make
$> svn commit -m "type shape reading and shape allocation"
Note
Last step for the Shape class: we need to store the properties of each shape (WIDTH, HEIGHT, ...)
The class Shape owns an object of type BoundingBox. This is the object which will hold the properties of a shape.
Note
Do not bother (for the moment) with the type POLYGON which will be tackled during a dedicated project later on.
Note
In order to draw the new shape read from the file, the method update() needs to be called at the end of the reading (in the class Visu2D). This method will send the repaint signal, connected to the method paintEvent().
Rebuild, test and commit into svn:
$> cmt make
$> svn commit -m "reading of the shape parameters"
Additional cross checks and errors handling can be implemented. The most usual types of errors are listed below:
A file error-shapes is provided to test a few of these frequent errors.
For this exercize, we’ll save objects in a file. We’ll strive to correctly format the output file so it closely matches the original file format: ideally, the output file should be readable by another application using the Shape class (as we did it for our application).
Therefore, we’ll implement a couple of new save methods which will deal with the details of the file formatting.
We wish to be able to save a file holding a shape when clicking on the button save at the bottom right of the DrawQt window. In a similar fashion than for the reading, connect this button to the method void Visu2D::SaveSelection()
This method will:
In the method Selection::Save(std::ofstream&):
Note
At this point, you are reading to start with the mini projects. But before that, please call us so we can check everything is correct and compatible with the projects you’ll tackle.