Table Of Contents

Previous topic

5. DrawQt

Next topic

7. Projects

Download

6. Reading a file of shapes

6.1. Bootstrapping

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:

  • a class Selection:
    • holding a set of objects of type Shape (only one object for the time being)
    • holding a method bool Selection::Read(std::ifstream& f) which will read the WHOLE file of shapes.
  • a class Shape:
    • holding only one shape.

6.2. Step 1: adding the declaration of the new methods (45 min)

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:

    1. open a file selector (look at bool WFrame::ReadImage() for inspiration)

    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.

    1. call the reading method of Selection

    2. 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.

_images/validate-step.jpg

Validate this step


6.3. Step 2: implementing the keywords reading method (45 min)

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"
_images/validate-step.jpg

Validate this step


6.4. Step 3: completing the reading method (1h30)

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.

  1. In the reading method, add a new variable SHAPE_TYPE type_id. Assign it the type the method has just finished reading (see the header file defs.h)
  2. Allocate a new object of type Shape using the method AllocateShape(...)

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"
_images/validate-step.jpg

Validate this step


6.5. Step 4: reading the content of a shape (25 min)

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.

  1. Carefully inspect the documentation of the BoundingBox class.
  2. In the class Selection, the parameters read from the file will need to be properly handed over to the new Shape object (m_shape). Leverage the methods available in the Shape class to address this issue. If needed, implement new helper methods within the Shape class.

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"
_images/validate-step.jpg

Validate this step


6.6. Step 5: error handling (2-3h)

  1. Comments handling. It isn’t mandatory to store them but simply to be able to print them on screen when reading a shape-file, by means of e.g. a QMessageBox.

Additional cross checks and errors handling can be implemented. The most usual types of errors are listed below:

  • check the file is open and/or the stream is indeed valid,
  • check no keyword is missing,
  • check a circle has indeed identical height and width,
  • check the dimensions aren’t negative
  • ...

A file error-shapes is provided to test a few of these frequent errors.

_images/validate-step.jpg

Validate this step


6.7. Step 6: saving shapes (3-4h)

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:

  • open a file selector,
  • call the writing method of the Selection class: Selection::Save(std::ofstream&)

In the method Selection::Save(std::ofstream&):

  1. Complete this method as we did for its reading counterpart. To this end, add a writing method in the Shape class.
  2. Check everything is working correctly by saving a file and reading it back.

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.

_images/validate-step.jpg

Validate this step