public class Constants {

    /**
     * Number of objects which can be segmented in the application
     */
    public static final int NUMBER_OF_OBJECTS = 2;

    /**
     * The path to the user preferences containing the configuration specific to
     * the user.
     */
    public static final String USER_PREF_FILE_PATH = "." + File.separatorChar + "cfg" + File.separatorChar + "userPreferences.txt";

    /**
     * Defines the when max size of the brush for which the mouse is an arrow.
     * If the brush size is greater than the specified value, an icon is
     * generated for the mouse.
     */
    public static final int MIN_MOUSE_BRUSH_ICON = 2;

    /**
     * The width of the result panel where the segmented image is shown.
     */
    public static final int RESULT_PANEL_WIDTH = 400;
    /**
     * The height of the result panel where the segmented image is shown.
     */
    public static final int RESULT_PANEL_HEIGHT = 300;

    /**
     * The path to where the User manual of Pixie is stored.
     */
    public static final String APPLICATION_MANUAL = "." + File.separatorChar + "doc" + File.separatorChar + "Pixie_Manual.pdf";

    /**
     * The release number of the software.
     */
    public static final String RELEASE_NUMBER = "0.5";
    /**
     * The number of the current sprint.
     */
    public static final String SPRINT_NUMBER = "3";
    /**
     * The software version is made of the release number together with the
     * sprint number. This will help in tracking the implemented issue for each
     * release.
     */
    public static final String SOFTWARE_VERSION = RELEASE_NUMBER + "." + SPRINT_NUMBER;

    /**
     * The version of JCuda used in the current software.
     */
    public static final String JCUDA_VERSION = "0.8.0";
    /**
     * The version of JCuda used in the current software.
     */
    public static final String JOCL_VERSION = "2.0.0";
    /**
     * The version of the jar used for the drawing tablet communication.
     */
    public static final String JPEN_VERSION = "2";
    /**
     * The version of the jar used for the drawing tablet communication.
     */
    public static final String HAMCREST_VERSION = "1.3";
    /**
     * The version of the JUnit for which the tests are running.
     */
    public static final String JUNIT_VERSION = "4.12";
    /**
     * The version of openCV used in the project.
     */
    public static final String OPENCV_VERSION = "3.2";

    /**
     * Play video file in forward mode
     */
    public static final int PLAY_MODE_FORWARD = 0;

    /**
     * Play video file in backward mode
     */
    public static final int PLAY_MODE_BACKWARD = 1;

    /**
     * Marks an action regarding the next frame.
     */
    public static final int NEXT_FRAME = 0;
    /**
     * Marks an action regarding the previous frame.
     */
    public static final int PREV_FRAME = 1;
    /**
     * Marks an action regarding the jump to a certain frame.
     */
    public static final int JUMP_TO_FRAME = 2;

    /**
     * Shows that the matting algorithm shall be run on the original image.
     */
    public static final int RUN_MATT_ORIG_IMG = 0;
    /**
     * Shows that the matting algorithm shall be run on the highlighted image.
     */
    public static final int RUN_MATT_HIGHLIGHT_IMG = 1;

    /**
     * The percentage of border which has to be added to the box, in preview
     * mode.
     */
    public static final float BORDER_PERCENTAGE = 0.2f;
    /**
     * The min border which shall be added to the box, in preview mode.
     */
    public static final int MIN_BORDER = 10;

    /**
     * Allow the max percentage of the screen to be used for resizing the
     * BBoxes/Crops
     */
    public static final double MAX_SCREEN_PERCENT_RESIZE = 0.85;

    /**
     * How much percentage shall be left for insets and other screen
     * decorations, on the horizontal direction.
     */
    public static final float TOLERANCE_WIDTH = 0.06f;

    /**
     * How much percentage shall be left for insets and other screen
     * decorations, on the vertical direction.
     */
    public static final float TOLERANCE_HEIGHT = 0.12f;

    /**
     * The text used to mark an invalid attribute.
     */
    public static final String INVALID_ATTRIBUTE_TEXT = "undefined";
    
    /**
     * The default text in the root of the object attributes tree.
     */
    public static final String OBJECT_ATTRIBUTES_TEXT = "Object Attributes";
    
     /**
     * The default text in the root of the frame attributes tree.
     */
    public static final String FRAME_ATTRIBUTES_TEXT = "Frame Attributes";

    /**
     * The name of the original image.
     */
    public static final String ORIGINAL_IMG = "Original Image";
    /**
     * The name of the segmented image.
     */
    public static final String SEGMENTED_IMG = "Working Panel";
    /**
     * The name of the segmented image.
     */
    public static final String SEGMENTED_IMG_RESIZED = "Working Panel Resized";
    /**
     * The name of the result image.
     */
    public static final String SEMANTIC_SEGMENTATION_IMG = "Semantic Segmentation";
    /**
     * The name of the joined images.
     */
    public static final String JOINED_IMGS = "Joined Images";

    /**
     * The name of the default folder where to export files.
     */
    public static final String EXPORT_IMGS_PATH = new File("").getAbsolutePath() + File.separatorChar + "Export";

    /**
     * The possible options of getting a grayscale value out of an rgb one.
     */
    public enum GrayscaleOption {
        /**
         * Standard luminance grayscale option.
         */
        STANDARD_LUMINANCE, // L = 0.2126*R + 0.7152*G + 0.0722*B
        /**
         * Gimp luminance grayscale option.
         */
        GIMP_LUMINANCE, // L = 0.222*R + 0.717*G + 0.061*B
        /**
         * Fast grayscale grayscale option.
         */
        FAST_GRAYSCALE          // L = ((2*R + 5*G + 1*B) / 8) = ((r<<1 + g<<2 + g + b) >> 3)
    }

    /**
     * The possible normalisation technics of a vector.
     */
    public enum Normalisation {
        /**
         * L 2 norm block normalisation.
         */
        L2_NORM_BLOCK, // |x| = sqrt(x1^2 + x2^2 + x3^2 + ... + xn^2)
        /**
         * L 2 hys block normalisation.
         */
        L2_HYS_BLOCK, // l2-norm followd by clipping (limiting the maximum values to 0.2) and renormalising
        /**
         * Tile level normalisation.
         */
        TILE_LEVEL, // normalisation at tile level (divide by the max of the tile)
        /**
         * Block level normalisation.
         */
        BLOCK_LEVEL, // normalisation at block level (divide by the max of the block)
        /**
         * Image level normalisation.
         */
        IMAGE_LEVEL, // normalisation at image level (divide by the max of the image)
        /**
         * Do nothing normalisation.
         */
        DO_NOTHING
    }

    /**
     * Defines the possible modes of the application.
     */
    public enum AppConfigMode {
        /**
         * Premium edition app config mode.
         */
        PREMIUM_EDITION, // the application opens videos coming from the server and sends the data back to be saved in the database
        /**
         * The Community edition.
         */
        COMMUNITY_EDITION       // the application opens pictures coming from a local source and saves the data locally on the machine (in a file)
    }

    /**
     * Defines the possible errors or interruptions which could happen during the save of ground
     * truth.
     */
    public enum GTSaveInterruptCodes {
        /**
         * No error gt save interrupt codes.
         */
        NO_ERROR,
        /**
         * Frame attrib not set gt save interrupt codes.
         */
        FRAME_ATTRIB_NOT_SET,
        /**
         * Data not found gt save interrupt codes.
         */
        DATA_NOT_FOUND
    }

    /**
     * The constant IMG_EXTENSION_LIST.
     */
    public static final List<String> IMG_EXTENSION_LIST = Arrays.asList("bmp", "jpg", "jpeg", "png");
    /**
     * The constant VIDEO_EXTENSION_LIST.
     */
    public static final List<String> VIDEO_EXTENSION_LIST = Arrays.asList("mp4", "nv12", "bgr", "avi");
    /**
     * The constant EXTENSION_LIST.
     */
    public static final List<String> EXTENSION_LIST = Stream.concat(IMG_EXTENSION_LIST.stream(), VIDEO_EXTENSION_LIST.stream()).collect(Collectors.toList());

    /**
     * The path to the file containing the frame attributes, for offline mode.
     */
    public static final String FRAME_ATTRIBUTES_PATH = "cfg" + File.separator + "frameAttributes.txt";

    /**
     * The path to the file containing the object attributes, for offline mode.
     */
    public static final String OBJECT_ATTRIBUTES_PATH = "cfg" + File.separator + "objectAttributes.txt";

    /**
     * The list of colors used in the application for the objects and which are
     * reserved for object segmentation. The user cannot select and use one of
     * them.
     */
    public static final List<Color> COLORS_LIST = Arrays.asList(Color.black, Color.green, Color.blue, Color.yellow, Color.magenta, Color.cyan,
            (new Color(153, 255, 153)), (new Color(153, 51, 255)), (new Color(51, 204, 255)), (new Color(255, 153, 102)), (new Color(102, 102, 255)),
            (new Color(255, 99, 71)), (new Color(165, 42, 42)), (new Color(154, 205, 50)), (new Color(0, 206, 209)), (new Color(75, 0, 130)),
            (new Color(175, 159, 106)), (new Color(119, 159, 106)), (new Color(0, 159, 106)), (new Color(0, 80, 106)), (new Color(0, 200, 106)),
            (new Color(124, 128, 39)), (new Color(124, 128, 236)), (new Color(185, 112, 56)), (new Color(185, 75, 125)), (new Color(128, 0, 0)),
            (new Color(128, 128, 0)), (new Color(0, 128, 0)), (new Color(128, 0, 128)), (new Color(0, 128, 128)), (new Color(0, 0, 128)),
            (new Color(238, 130, 238)), (new Color(245, 222, 179)), (new Color(210, 105, 30)), (new Color(105, 105, 105)), (new Color(30, 144, 255))
    );

    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors. Java adds an implicit public
     * constructor to every class which does not define at least one explicitly.
     * Hence, at least one non-public constructor should be defined.
     */
    private Constants() {
        throw new IllegalStateException("Utility class, do not instantiate!");
    }
} public final class LTFileFilter extends FileFilter {

    private HashMap<String, LTFileFilter> filters;
    private String description = null;
    private String fullDescription = null;
    private boolean useExtensionsInDescription = true;

    /**
     * Creates a file filter. If no filters are added, then all files are
     * accepted.
     *
     * @see #addExtension #addExtension
     */
    public LTFileFilter() {
        this.filters = null;
        this.filters = new HashMap<>();
    }

    /**
     * Creates a file filter that accepts files with the given extension.
     * Example: new ExampleFileFilter("jpg");
     *
     * @param extension the extension of the file
     * @see #addExtension #addExtension
     */
    public LTFileFilter(String extension) {
        this(extension, null);
        this.filters = null;
    }

    /**
     * Creates a file filter that accepts the given file type. Example: new
     * ExampleFileFilter("jpg", "JPEG Image Images");
     * <p>
     * Note that the "." before the extension is not needed. If provided, it
     * will be ignored.
     *
     * @param extension   the extension of the file
     * @param description the description of the extension
     * @see #addExtension #addExtension
     */
    public LTFileFilter(String extension, String description) {
        this();
        this.filters = null;
        if (extension != null) {
            addExtension(extension);
        }
        if (description != null) {
            setDescription(description);
        }
    }

    /**
     * Creates a file filter from the given string array. Example: new
     * ExampleFileFilter(String {"gif", "jpg"});
     * <p>
     * Note that the "." before the extension is not needed and will be ignored.
     *
     * @param filters a string array containing the extensions accepted by the filter
     * @see #addExtension #addExtension
     */
    public LTFileFilter(String[] filters) {
        this(filters, null);
        this.filters = null;
    }

    /**
     * Creates a file filter from the given string array and description.
     * Example: new ExampleFileFilter(String {"gif", "jpg"}, "Gif and JPG
     * Images");
     * <p>
     * Note that the "." before the extension is not needed and will be ignored.
     *
     * @param filters     the list of accepted extensions
     * @param description the description of the extension list
     * @see #addExtension #addExtension
     */
    public LTFileFilter(String[] filters, String description) {
        this();
        this.filters = null;
        for (String filter : filters) {
            // add filters one by one
            addExtension(filter);
        }
        if (description != null) {
            setDescription(description);
        }
    }

    /**
     * Return true if this file should be shown in the directory pane, false if
     * it shouldn't.
     *
     * Files that begin with "." are ignored.
     *
     * @param f the file being analyzed if it should be shown in the directory
     * pane
     * @return true if this file should be shown in the directory pane, false if
     * it shouldn't.
     * @see #getExtension
     */
    @Override
    public boolean accept(File f) {
        if (f != null) {
            if (f.isDirectory()) {
                return true;
            }
            String extension = getExtension(f);
            if (extension != null && filters.get(getExtension(f)) != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * Return the extension portion of the file's name .
     *
     * @param f the file for which the file extension should be extracted
     * @return a string representing the file extension
     * @see #getExtension #getExtension
     * @see FileFilter#accept FileFilter#accept
     */
    public String getExtension(File f) {
        if (f != null) {
            String filename = f.getName();
            int i = filename.lastIndexOf('.');
            if (i > 0 && i < filename.length() - 1) {
                return filename.substring(i + 1).toLowerCase(Locale.ENGLISH);
            }
        }
        return null;
    }

    /**
     * Adds a filetype "dot" extension to filter against.
     * <p>
     * For example: the following code will create a filter that filters out all
     * files except those that end in ".jpg" and ".tif":
     * <p>
     * LTFileFilter filter = new LTFileFilter(); filter.addExtension("jpg");
     * filter.addExtension("tif");
     * <p>
     * Note that the "." before the extension is not needed and will be ignored.
     *
     * @param extension the extension which should be added to the list of possible file extensions
     */
    public void addExtension(String extension) {
        if (filters == null) {
            filters = new HashMap<>();
        }
        filters.put(extension.toLowerCase(Locale.ENGLISH), this);
        fullDescription = null;
    }

    /**
     * Returns the human readable description of this filter. For example: "JPEG
     * and GIF Image Files (*.jpg, *.gif)"
     *
     * @return the description of the extensions, in readable form, explanatory
     * for the user
     * @see setDescription
     * @see setExtensionListInDescription
     * @see isExtensionListInDescription
     * @see FileFilter#getDescription
     */
    @Override
    public String getDescription() {
        if (fullDescription == null) {
            if (description == null || isExtensionListInDescription()) {
                fullDescription = description == null ? "(" : description + " (";
                // build the description from the extension list
                Set<String> extensions = filters.keySet();
                if (extensions != null) {
                    Iterator<String> it = extensions.iterator();
                    fullDescription += "." + it.next();
                    for (it = extensions.iterator(); it.hasNext();) {
                        fullDescription += ", " + it.next();
                    }
                }

////                if (!filters.isEmpty()) {
////                    fullDescription += "." + (String) ;
////                    while (extensions.hasMoreElements()) {
////                        fullDescription += ", " + (String) extensions.nextElement();
////                    }
////                }
//                if (extensions != null) {
//                    fullDescription += "." + (String) extensions.nextElement();
//                    while (extensions.hasMoreElements()) {
//                        fullDescription += ", " + (String) extensions.nextElement();
//                    }
//                }
                fullDescription += ")";
            } else {
                fullDescription = description;
            }
        }
        return fullDescription;
    }

    /**
     * Sets the human readable description of this filter. For example:
     * filter.setDescription("Gif and JPG Images");
     *
     * @param description the description of the extensions, in readable form, explanatory for the user
     * @see setDescription
     * @see setExtensionListInDescription
     * @see isExtensionListInDescription
     */
    public void setDescription(String description) {
        this.description = description;
        fullDescription = null;
    }

    /**
     * Determines whether the extension list (.jpg, .gif, etc) should show up in
     * the human readable description.
     * <p>
     * Only relevant if a description was provided in the constructor or using
     * setDescription();
     *
     * @param showExtensions true if the list of extensions should show in the description; false otherwise
     * @see getDescription
     * @see setDescription
     * @see isExtensionListInDescription
     */
    public void setExtensionListInDescription(boolean showExtensions) {
        useExtensionsInDescription = showExtensions;
        fullDescription = null;
    }

    /**
     * Returns whether the extension list (.jpg, .gif, etc) should show up in
     * the human readable description.
     * <p>
     * Only relevant if a description was provided in the constructor or using
     * setDescription();
     *
     * @return true if the list of extensions should show in the description; false otherwise
     * @see getDescription
     * @see setDescription
     * @see setExtensionListInDescription
     */
    public boolean isExtensionListInDescription() {
        return useExtensionsInDescription;
    }
} public class ObservedActions {

    /**
     * The enum Action.
     */
    public enum Action {
        /**
         * Do nothing.
         */
        DO_NOTHING,
        /**
         * A notification saying that a new crop was drawn and it has to be
         * displayed
         */
        OPEN_CROP_SEGMENTATION,
        /**
         * A bounding box was drawn and an window with the preview has to pop up
         * for further checks and attributes choosing.
         */
        OPEN_BBOX_SEGMENTATION,
        /**
         * A polygon was drawn and an window with the preview has to pop up for
         * further checks and attributes choosing.
         */
        OPEN_POLYGON_SEGMENTATION,
        /**
         * The attributes of the bounding box were chosen and the object has to
         * be saved in the object list.
         */
        SAVE_BOUNDING_BOX,
        /**
         * A new crop was segmented, which belongs to an object. Add it to the
         * parent object.
         */
        ADD_CROP_TO_OBJECT,
        /**
         * Notify that an object was saved and it has to be displayed on the
         * panel of the application.
         */
        ADD_OBJECT_ON_PANEL,
        /**
         * The segmentation for the opened crop was done and the result image is
         * available. Refresh the display in order to have the latest image.
         */
        REFRESH_CROP_RESULT,
        /**
         * The number of the frame changed, refresh the display.
         */
        REFRESH_FRAME_NO,
        /**
         * The size of the brush changed. Refresh the display.
         */
        REFRESH_BRUSH_SIZE,
        /**
         * An object has been removed from the list of objects; remove it from
         * the objects list as well.
         */
        REFRESH_OBJ_LIST_PANEL,
        /**
         * Remove the key event dispatcher because another one has to be used.
         */
        REMOVE_GUI_KEY_EVENT_DISPATCHER,
        /**
         * Remove the object, one crop of the object or the bounding box object.
         */
        REMOVE_SELECTED_OBJECT,
        /**
         * A crop was selected for edit. Notify in order to open a window and
         * edit the crop.
         */
        EDIT_OBJECT_ACTION,
        /**
         * A bounding box object is/was in edit mode and has to be saved.
         */
        UPDATE_BOUNDING_BOX,
        /**
         * A complete scribble object is/was in edit mode and it has to be
         * saved.
         */
        UPDATE_OBJECT_SCRIBBLE,
        /**
         * A crop was edited and now it has to be updated inside the parent
         * object.
         */
        UPDATE_CROP_OF_OBJECT,
        /**
         * A polygon object was edited and now it has to be updated.
         */
        UPDATE_POLYGON,
        /**
         * Add the key event dispatcher.
         */
        ADD_GUI_KEY_EVENT_DISPATCHER,
        /**
         * An object which is under edit mode moved and the operation has to be
         * reflected in the GUI.
         */
        SELECTED_OBJECT_MOVED,
        /**
         * One object was selected and now it is dragged/moved.
         */
        MOVE_OBJECT_DRAG,
        /**
         * An object was selected and the corresponding button on the interface
         * has to be highlighted.
         */
        HIGHLIGHT_OBJECT,
        /**
         * A frame was loaded from the database and its annotations have to be
         * displayed on the gui.
         */
        LOAD_FRAME_ANNOTATION,
        /**
         * Notify that the current object was cancel.
         */
        CANCEL_CURRENT_OBJECT,
        /**
         * Shows that the output object map shall be further filter because
         * there are outliers.
         */
        FILTER_OBJECT_MAP,
        /**
         * The Ctrl + mouse released event happened. Choose how to treat it.
         */
        CTRL_MOUSE_EVENT,
        /**
         * Refresh the application with the current configuration.
         */
        REFRESH_DISPLAY,
        /**
         * There were some changes and the panels should be updated; show/hide
         * some panels based on the existence of a scribble object in the list.
         */
        REFRESH_PANEL_OPTIONS,
        /**
         * The object has to be saved and sent to the ground truth manager.
         */
        SAVE_LABEL,
        /**
         * The object or frame attributes were changed. Reload them.
         */
        RELOAD_ATTRIBUTES,
        /**
         * The user wants to see a preview of the application with another DPI
         * value.
         */
        REFRESH_APPLICATION_VIEW,
        /**
         * The window for the edit of attributes should be shown.
         */
        DISPLAY_EDIT_ATTRIBUTES_WIN,
        /**
         * The vertices of the polygon were changed and the original object has
         * to be updated.
         */
        UPDATE_POLYGON_VERTICES
    }
} public class Timings {

    private long startTime;     // in ns
    private long endTime;       // in ns
    private long neededTime;    // in ms

    private long startElpasedTime;
    private long stopElpasedTime;

    /**
     * logger instance
     */
    private final Logger log = LoggerFactory.getLogger("common.Timings");

    /**
     * Initialises the timer with the current value.
     */
    public void start() {
        startTime = System.nanoTime();
    }

    /**
     * Initialises the timer with the current value.
     */
    public void startElapsed() {
        startElpasedTime = System.nanoTime();
    }

    /**
     * Computes the elapsed time
     *
     * @param description - the description of the measured task
     */
    public void stopElapsed(String description) {
        stopElpasedTime = System.nanoTime();
        neededTime = (stopElpasedTime - startElpasedTime) / 1000000;
        log.info("{}\r\n{} min, {} sec\r\n{}",
                description,
                (TimeUnit.MILLISECONDS.toMinutes(neededTime) % 60),
                TimeUnit.MILLISECONDS.toSeconds(neededTime) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(neededTime)),
                description);
    }

    /**
     * Computes the time which passed between when the timer was started and the
     * present time.
     *
     * @param description - the description of the measured task
     */
    public void stop(String description) {
        endTime = System.nanoTime();
        neededTime = (endTime - startTime) / 1000000;
        log.info("{} {} ms, {} h, {} min, {} sec",
                description,
                neededTime,
                TimeUnit.MILLISECONDS.toHours(neededTime),
                (TimeUnit.MILLISECONDS.toMinutes(neededTime) % 60),
                TimeUnit.MILLISECONDS.toSeconds(neededTime) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(neededTime)));
    }

    /**
     * Computes the time which passed between when the timer was started and the
     * present time.
     */
    public void stop() {
        endTime = System.nanoTime();
        neededTime = (endTime - startTime) / 1000000;
        log.info("{} ms, {} h, {} min, {} sec",
                neededTime,
                TimeUnit.MILLISECONDS.toHours(neededTime),
                (TimeUnit.MILLISECONDS.toMinutes(neededTime) % 60),
                TimeUnit.MILLISECONDS.toSeconds(neededTime) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(neededTime)));
    }

    /**
     * Computes the time in milliseconds which passed between when the timer was
     * started and the present time.
     */
    public void stopMS() {
        endTime = System.nanoTime();
        neededTime = (endTime - startTime) / 1000000;
        log.info("{} ms", neededTime);
    }

    /**
     * Computes the time in milliseconds which passed between when the timer was
     * started and the present time.
     *
     * @param description - the description of the measured task
     */
    public void stopMS(String description) {
        endTime = System.nanoTime();
        neededTime = (endTime - startTime) / 1000000;
        log.info("{} {} ms", description, neededTime);
    }

    /**
     * Computes the time in seconds which passed between when the timer was
     * started and the present time.
     */
    public void stopSec() {
        endTime = System.nanoTime();
        neededTime = (endTime - startTime) / 1000000;
        log.info("{} sec", TimeUnit.MILLISECONDS.toSeconds(neededTime));
    }

    /**
     * Computes the time in seconds which passed between when the timer was
     * started and the present time.
     *
     * @param description - the description of the measured task
     */
    public void stopSec(String description) {
        endTime = System.nanoTime();
        neededTime = (endTime - startTime) / 1000000;
        log.info("{} {} sec", description, TimeUnit.MILLISECONDS.toSeconds(neededTime));
    }

    /**
     * Returns the difference between the start time and the stop time, in
     * milliseconds.
     *
     * @return - the amount of milliseconds passed between start and stop timer
     */
    public long getNeededTime() {
        return neededTime;
    }

    /**
     * Returns the time when the timer was started.
     *
     * @return - the time, in nanoseconds, when the timer was started
     */
    public long getStartTime() {
        return startTime;
    }

    /**
     * Sets the time when the timer was started, for the case when the timer is
     * started by an external source.
     *
     * @param startTime - the time, in nanoseconds, when the timer was started
     */
    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }

    /**
     * Returns the difference between the start time and the current time
     *
     * @return - the amount of milliseconds passed between start and current time
     */
    public long readTimer() {
        if (startTime == 0) {
            return 0;
        } else {
            return (System.nanoTime() - startTime) / 1000000;
        }
    }

    /**
     * Returns the difference between the start time and the current time
     *
     * @param description timer description
     * @return - the amount of milliseconds passed between start and current time
     */
    public long readTimer(String description) {
        long retVal;
        if (startTime == 0) {
            retVal = 0;
        } else {
            retVal = (System.nanoTime() - startTime) / 1000000;
        }
        log.trace("{} time needed in miliseconds {}", description, retVal);
        return retVal;
    }
} public class UserPreferences implements Serializable {

    /**
     * Serial class version in form of MAJOR_MINOR_BUGFIX_DAY_MONTH_YEAR
     */
    private static final long serialVersionUID = 0x00_06_01_11_09_2017L;

    /**
     * logger instance
     */
    private final transient org.slf4j.Logger log = LoggerFactory.getLogger(UserPreferences.class);

    /**
     * The path to the last used directory.
     */
    private String lastDirectory;

    /**
     * Indicator for knowing if the background should be merged or not in the
     * image.
     */
    private boolean mergeBKG;

    /**
     * Indicator for knowing if the scribbles should be shown on the work panel.
     */
    private boolean showScribbles;

    /**
     * Indicator for knowing if the crops should be shown on the work panel.
     */
    private boolean showCrops;

    /**
     * Indicator for knowing if only the crops and scribbles of the current
     * object have to be displayed on the working panel.
     */
    private boolean showJustCurrentObj;

    /**
     * Indicator for knowing if the objects should be displayed to the user,
     * after pressing next and going to the next file, for review.
     */
    private boolean popUpObjects;

    /**
     * Indicator for knowing if play mode of the video shall be forward or
     * backward.
     */
    private boolean playBackward;

    /**
     * Indicator for knowing if file map shall be saved when the data is saved
     * in the database.
     */
    private boolean saveFrameObjMap;

    /**
     * Indicator for knowing if the user wants to have the object highlighted.
     * True = draws a transparent layer over the object to highlight it.
     */
    private boolean showObjHighlight;

    /**
     * The value of the alpha to be used for the highlight of the object.
     */
    private int objAlphaVal;

    /**
     * Indicator for knowing if the image shall be flipped vertically or not.
     */
    private boolean flipVertically;

    /**
     * Indicator for knowing if the image shall be mirrored or not.
     */
    private boolean mirrorImage;

    /**
     * The preferred value of the DPI for the display of the application.
     */
    private int preferredDPI;

    /**
     * Indicator for knowing if the application is running for the first time.
     */
    private boolean firstRun;

    /**
     * The path where to export images using the Export option.
     */
    private String imgExportPath;

    /**
     * The extension used for the export of images.
     */
    private String imgExportExtension;

    /**
     * Export every frame the selected type of image.
     */
    private boolean imgAutoExportEveryFrame;

    /**
     * The type of image which is to be saved every frame.
     */
    private String imgTypeForExport;

    /**
     * The type of images to be joined for the export of every frame.
     */
    private String imgTypeJoinedExport;

    /**
     * Indicator for knowing if the frame annotations should be checked to
     * insure they are valid.
     */
    private boolean checkFrameAnnotations;

    /**
     * Indicator for knowing if the object attributes should be checked to
     * insure they are valid.
     */
    private boolean checkObjectAttributes;

    /**
     * Instantiate a new user preferences class, which is meant to read the user
     * configuration file and set all its preferences in the application when it
     * starts.
     */
    public UserPreferences() {
        String readLine;

        setDefaults();

        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(Constants.USER_PREF_FILE_PATH), Charset.forName("UTF-8")))) {
            while ((readLine = br.readLine()) != null) {
                String[] wordsList = readLine.split("=");

                // if the words list is not correct, jump over the line
                if ((wordsList == null) || (wordsList.length < 2)) {
                    continue;
                }

                switch (wordsList[0]) {
                    case "lastUsedDirectory":
                        lastDirectory = wordsList[wordsList.length - 1];
                        break;

                    case "mergeBKG":
                        mergeBKG = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "showScribbles":
                        showScribbles = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "showCrops":
                        showCrops = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "showJustCurrentObj":
                        showJustCurrentObj = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "popUpObjects":
                        popUpObjects = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "playBackward":
                        playBackward = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "saveFrameObjMap":
                        saveFrameObjMap = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;


                    case "showObjHighlight":
                        showObjHighlight = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "objAlphaVal":
                        objAlphaVal = Integer.parseInt(wordsList[wordsList.length - 1]);
                        break;

                    case "flipVertically":
                        flipVertically = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "mirrorImage":
                        mirrorImage = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "preferredDPI":
                        preferredDPI = Integer.parseInt(wordsList[wordsList.length - 1]);
                        break;

                    case "firstRun":
                        firstRun = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "imgExportPath":
                        imgExportPath = wordsList[wordsList.length - 1];
                        break;

                    case "imgExportExtension":
                        imgExportExtension = wordsList[wordsList.length - 1];
                        break;

                    case "imgAutoExportEveryFrame":
                        imgAutoExportEveryFrame = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "imgTypeForExport":
                        imgTypeForExport = wordsList[wordsList.length - 1];
                        break;

                    case "imgTypeJoinedExport":
                        imgTypeJoinedExport = wordsList[wordsList.length - 1];
                        break;

                    case "checkFrameAnnotations":
                        checkFrameAnnotations = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "checkObjectAttributes":
                        checkObjectAttributes = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    default:
                        log.info("The userPreferences.txt file contains an unknown key: {}", wordsList[0]);
                        break;
                }
            }
        } catch (IOException ex) {
            log.error("The user preferences file was not loaded! Will load default values!!");
            log.debug("The user preferences file was not loaded! Will load default values!! {}", ex);
        }
    }

    /**
     * Returns the directory of the last opened file.
     *
     * @return - the path to the directory from where the last file was opened
     */
    public String getLastDirectory() {
        return lastDirectory;
    }

    /**
     * Saves the path to the last opened file. This helps the user to not always
     * navigate through folders.
     *
     * @param lastDirectory - the path to the directory from where the last file was opened
     */
    public void setLastDirectory(String lastDirectory) {
        this.lastDirectory = lastDirectory;
    }

    /**
     * Shows if the user wants to have the background merged into the big result
     * or not. This is scribble labeling specific.
     *
     * @return - true if the user used last time the merge background and false otherwise
     */
    public boolean isMergeBKG() {
        return mergeBKG;
    }

    /**
     * Shows if the user wants to have the scribbles of the segmented objects,
     * visible on the drawing panel.
     *
     * @return - true if the user used last time the show scribbles and false otherwise
     */
    public boolean isShowScribbles() {
        return showScribbles;
    }

    /**
     * Shows if the user wants to have just the current object visible in the
     * drawing panel.
     *
     * @return - true if the user used last time the show just current object and false otherwise
     */
    public boolean isShowJustCurrentObj() {
        return showJustCurrentObj;
    }

    /**
     * Sets if the user wants to have the background merged into the big result
     * or not. This is scribble labeling specific.
     *
     * @param mergeBKG - true if the user used last time the merge background and false otherwise
     */
    public void setMergeBKG(boolean mergeBKG) {
        this.mergeBKG = mergeBKG;
    }

    /**
     * Sets if the user wants to have the scribbles of the segmented objects,
     * visible on the drawing panel.
     *
     * @param showScribbles - true if the user used last time the show scribbles and false otherwise
     */
    public void setShowScribbles(boolean showScribbles) {
        this.showScribbles = showScribbles;
    }

    /**
     * Sets if the user wants to have just the current object visible in the
     * drawing panel.
     *
     * @param showJustCurrentObj - true if the user used last time the show just current object and false otherwise
     */
    public void setShowJustCurrentObj(boolean showJustCurrentObj) {
        this.showJustCurrentObj = showJustCurrentObj;
    }

    /**
     * Shows if the user wants to have the crops of the segmented objects,
     * visible on the drawing panel.
     *
     * @return true if the user used last time the show crops and false otherwise
     */
    public boolean isShowCrops() {
        return showCrops;
    }

    /**
     * Sets if the user wants to have the crops of the segmented objects,
     * visible on the drawing panel.
     *
     * @param showCrops true if the user used last time the show crops and false otherwise
     */
    public void setShowCrops(boolean showCrops) {
        this.showCrops = showCrops;
    }

    /**
     * Shows if the user wants to have the objects pop up for review, after the
     * get next frame call.
     *
     * @return - true if the user wants to review the objects, therefore the user wants them to pop up and false otherwise
     */
    public boolean isPopUpObjects() {
        return popUpObjects;
    }

    /**
     * Sets if the user wants to have the objects pop up for review, after the
     * get next frame call.
     *
     * @param popUpObjects - true if the user wants to review the objects, therefore the user wants them to pop up and false otherwise
     */
    public void setPopUpObjects(boolean popUpObjects) {
        this.popUpObjects = popUpObjects;
    }

    /**
     * Shows if the user wants to have the video played forward or backward.
     *
     * @return - true if the user wants the video played backward and false if the user wants the video played forward
     */
    public boolean isPlayBackward() {
        return playBackward;
    }

    /**
     * Sets if the user wants to have the video played forward or backward.
     *
     * @param playBackward - true if the user wants the video played backward and false if the user wants the video played forward
     */
    public void setPlayBackward(boolean playBackward) {
        this.playBackward = playBackward;
    }

    /**
     * Shows if the user wants to save the file object map, when saving data in
     * the database.
     *
     * @return - true if the user wants to save the file object map and false if no file shall be saved
     */
    public boolean isSaveFrameObjMap() {
        return saveFrameObjMap;
    }

    /**
     * Sets if the user wants to save the file object map, when saving data in
     * the database.
     *
     * @param saveFrameObjMap - true if the user wants to save the file object map and false if no file shall be saved
     */
    public void setSaveFrameObjMap(boolean saveFrameObjMap) {
        this.saveFrameObjMap = saveFrameObjMap;
    }

    /**
     * Shows if the user wants to have the object highlighted.
     *
     * @return true if the user wants to have drawn a transparent layer over the object to highlight it and false if the user wants to have just the border of the object and no filling
     */
    public boolean isShowObjHighlight() {
        return showObjHighlight;
    }

    /**
     * Sets if the user wants to have the object highlighted.
     *
     * @param showObjHighlight true if the user wants to have drawn a transparent layer over the object to highlight it and false if the user wants to have just the border of the object and no filling
     */
    public void setShowObjHighlight(boolean showObjHighlight) {
        this.showObjHighlight = showObjHighlight;
    }

    /**
     * Shows the value of the alpha to be used for the highlight of the object.
     *
     * @return the integer value, between 0 and 255, for the alpha to be used for highlighting the object
     */
    public int getObjAlphaVal() {
        return objAlphaVal;
    }

    /**
     * Sets the value of the alpha to be used for the highlight of the object.
     *
     * @param objAlphaVal the integer value, between 0 and 255, for the alpha to be used for highlighting the object
     */
    public void setObjAlphaVal(int objAlphaVal) {
        this.objAlphaVal = objAlphaVal;
    }

    /**
     * Show if the user wants to have the image flipped vertically or not.
     *
     * @return true if the image shall be flipped vertically and false if the image shall not be changed
     */
    public boolean isFlipVertically() {
        return flipVertically;
    }

    /**
     * Sets if the user wants to have the image flipped vertically or not.
     *
     * @param flipVertically true if the image shall be flipped vertically and false if the image shall not be changed
     */
    public void setFlipVertically(boolean flipVertically) {
        this.flipVertically = flipVertically;
    }

    /**
     * Shows if the user wants to have the image mirrored or not.
     *
     * @return true if the image shall be mirrored and false if the image shall not be changed
     */
    public boolean isMirrorImage() {
        return mirrorImage;
    }

    /**
     * Sets if the user wants to have the image mirrored or not.
     *
     * @param mirrorImage true if the image shall be mirrored and false if the image shall not be changed
     */
    public void setMirrorImage(boolean mirrorImage) {
        this.mirrorImage = mirrorImage;
    }

    /**
     * Returns the preferred value of the DPI, used for rescaling the screen
     * image.
     *
     * @return the last used value for the DPI
     */
    public int getPreferredDPI() {
        return preferredDPI;
    }

    /**
     * Sets the preferred value of the DPI, used for rescaling the screen image.
     *
     * @param preferredDPI the user defined value for the DPI
     */
    public void setPreferredDPI(int preferredDPI) {
        this.preferredDPI = preferredDPI;
    }

    /**
     * Shows if the application is running for the first time.
     *
     * @return true if the application is running for the first time and false otherwise
     */
    public boolean isFirstRun() {
        return firstRun;
    }

    /**
     * Sets the flag regarding the application running for the first time.
     *
     * @param firstRun true if the application is running for the first time and false otherwise
     */
    public void setFirstRun(boolean firstRun) {
        this.firstRun = firstRun;
    }

    /**
     * Get the path where the user prefers to export images.
     *
     * @return the string representing the path where the images should be saved
     */
    public String getImgExportPath() {
        return imgExportPath;
    }

    /**
     * Set the path where the user prefers to export images.
     *
     * @param imgExportPath the string representing the path where the images should be saved
     */
    public void setImgExportPath(String imgExportPath) {
        this.imgExportPath = imgExportPath;
    }

    /**
     * Get the file type the user prefers for the export of images.
     *
     * @return the string representing the file type used for the saved images
     */
    public String getImgExportExtension() {
        return imgExportExtension;
    }

    /**
     * Set the file type the user prefers for the export of images.
     *
     * @param imgExportExtension the string representing the file type used for the saved images
     */
    public void setImgExportExtension(String imgExportExtension) {
        this.imgExportExtension = imgExportExtension;
    }

    /**
     * Set the possibility to export a certain type of image, every frame, till
     * the user changes the option.
     *
     * @return true if every frame should be exported; false if no automatic export shall be done
     */
    public boolean isImgAutoExportEveryFrame() {
        return imgAutoExportEveryFrame;
    }

    /**
     * Find out if the application should export a certain type of image, every
     * frame, till the user changes the option.
     *
     * @param imgAutoExportEveryFrame true if every frame should be exported; false if no automatic export shall be done
     */
    public void setImgAutoExportEveryFrame(boolean imgAutoExportEveryFrame) {
        this.imgAutoExportEveryFrame = imgAutoExportEveryFrame;
    }

    /**
     * Get the type of image to be exported: original, segmented or result
     * image.
     *
     * @return a string representing the type of the image exported every frame
     */
    public String getImgTypeForExport() {
        return imgTypeForExport;
    }

    /**
     * Set the type of image to be exported: original, segmented or result
     * image.
     *
     * @param imgTypeForExport a string representing the type of the image exported every frame
     */
    public void setImgTypeForExport(String imgTypeForExport) {
        this.imgTypeForExport = imgTypeForExport;
    }

    /**
     * Get the types of images to be joined for export: original + segmented +
     * result image.
     *
     * @return string representing the type of the images to be joined for exporting them every frame
     */
    public String getImgTypeJoinedExport() {
        return imgTypeJoinedExport;
    }

    /**
     * Set the types of images to be joined for export: original + segmented +
     * result image.
     *
     * @param imgTypeJoinedExport a string representing the type of the images to be joined for exporting them every frame
     */
    public void setImgTypeJoinedExport(String imgTypeJoinedExport) {
        this.imgTypeJoinedExport = imgTypeJoinedExport;
    }

    /**
     * Get the state of the check of frame attributes option (checks if the
     * frame attributes are set to a different value than the default).
     *
     * @return true if the frame attributes should be checked and false otherwise
     */
    public boolean isCheckFrameAnnotations() {
        return checkFrameAnnotations;
    }

    /**
     * Enables/Disables the check of frame attributes (checks if the frame
     * attributes are set to a different value than the default).
     *
     * @param checkFrameAnnotations true if the frame attributes should be checked and false otherwise
     */
    public void setCheckFrameAnnotations(boolean checkFrameAnnotations) {
        this.checkFrameAnnotations = checkFrameAnnotations;
    }

    /**
     * Get the state of the check of object attributes option (checks if the
     * object attributes are set to a different value than the default).
     *
     * @return true if the object attributes should be checked and false
     * otherwise
     */
    public boolean isCheckObjectAttributes() {
        return checkObjectAttributes;
    }

    /**
     * Enables/Disables the check of object attributes (checks if the object
     * attributes are set to a different value than the default).
     *
     * @param checkObjectAttributes true if the frame attributes should be
     * checked and false otherwise
     */
    public void setCheckObjectAttributes(boolean checkObjectAttributes) {
        this.checkObjectAttributes = checkObjectAttributes;
    }

    /**
     * Saves into the user preferences file the latest wishes of the user.
     */
    public void saveUserPreferences() {
        try {
            String fileContent = "";
            try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(Constants.USER_PREF_FILE_PATH), Charset.forName("UTF-8")))) {
                fileContent += "lastUsedDirectory=" + lastDirectory + "\r\n";
                fileContent += "mergeBKG=" + mergeBKG + "\r\n";
                fileContent += "showScribbles=" + showScribbles + "\r\n";
                fileContent += "showCrops=" + showCrops + "\r\n";
                fileContent += "showJustCurrentObj=" + showJustCurrentObj + "\r\n";
                fileContent += "popUpObjects=" + popUpObjects + "\r\n";
                fileContent += "playBackward=" + playBackward + "\r\n";
                fileContent += "saveFrameObjMap=" + saveFrameObjMap + "\r\n";
                fileContent += "showObjHighlight=" + showObjHighlight + "\r\n";
                fileContent += "objAlphaVal=" + objAlphaVal + "\r\n";
                fileContent += "flipVertically=" + flipVertically + "\r\n";
                fileContent += "mirrorImage=" + mirrorImage + "\r\n";
                fileContent += "preferredDPI=" + preferredDPI + "\r\n";
                fileContent += "firstRun=" + false + "\r\n";
                fileContent += "imgExportPath=" + imgExportPath + "\r\n";
                fileContent += "imgExportExtension=" + imgExportExtension + "\r\n";
                fileContent += "imgAutoExportEveryFrame=" + imgAutoExportEveryFrame + "\r\n";
                fileContent += "imgTypeForExport=" + imgTypeForExport + "\r\n";
                fileContent += "imgTypeJoinedExport=" + imgTypeJoinedExport + "\r\n";
                fileContent += "checkFrameAnnotations=" + checkFrameAnnotations + "\r\n";
                fileContent += "checkObjectAttributes=" + checkObjectAttributes + "\r\n";

                bw.write(fileContent, 0, fileContent.length());
                bw.flush();
            }
        } catch (IOException ex) {
            Logger.getLogger(UserPreferences.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Set default values for user configuration parameters.
     */
    private void setDefaults() {
        // set some default initialisation values because the configuration file was not found
        lastDirectory = "";
        mergeBKG = false;
        showScribbles = true;
        showCrops = true;
        showJustCurrentObj = false;
        popUpObjects = true;
        playBackward = false;
        saveFrameObjMap = false;
        showObjHighlight = true;
        objAlphaVal = 60;
        flipVertically = false;
        mirrorImage = false;
        preferredDPI = 144;
        firstRun = true;
        imgExportPath = Constants.EXPORT_IMGS_PATH;
        imgExportExtension = "jpeg";
        imgAutoExportEveryFrame = false;
        imgTypeForExport = Constants.SEGMENTED_IMG;
        imgTypeJoinedExport = Constants.ORIGINAL_IMG + "," + Constants.SEGMENTED_IMG;
        checkFrameAnnotations = true;
        checkObjectAttributes = true;
    }
} public class Utils {

    /**
     * logger instance
     */
    private static final Logger LOG = LoggerFactory.getLogger(Utils.class);

    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors. Java adds an implicit public
     * constructor to every class which does not define at least one explicitly.
     * Hence, at least one non-public constructor should be defined.
     */
    private Utils() {
        throw new IllegalStateException("Utility class, do not instantiate!");
    }

    /**
     * Get the image from the specified box.
     *
     * @param origImg - the original image from where the selection shall be
     * copied
     * @param posOrig - the position where the image has to be copied from
     * @return - the image contained in the specified crop
     */
    public static BufferedImage getSelectedImg(BufferedImage origImg, Rectangle posOrig) {
        Rectangle pos = posOrig;
        BufferedImage tempImg = null;

        if ((pos != null) && (pos.width > 0) && (pos.height > 0)) {
            tempImg = new BufferedImage(pos.width, pos.height, origImg.getType());

            for (int y = pos.y; y < pos.y + pos.height; y++) {
                for (int x = pos.x; x < pos.x + pos.width; x++) {
                    tempImg.setRGB(x - pos.x, y - pos.y, origImg.getRGB(x, y));
                }
            }
        }

        return tempImg;
    }

    /**
     * Compute the l^2-Norm of the input vector.
     * <p>
     * |x| = sqrt(x1^2 + x2^2 + x3^2 + ... + xn^2)
     *
     * @param vector - the input vector
     * @return - the value of the norm of the vector
     */
    public static double computeVectorL2Norm(double[] vector) {
        double sum = 0.0;

        // compute the sum of squares
        for (int index = 0; index < vector.length; index++) {
            sum += vector[index] * vector[index];
        }

        // compute the compute square root of the sum of squares
        return Math.sqrt(sum);
    }

    /**
     * Compute squared euclidian distance double.
     *
     * @param vectorA the vector a
     * @param vectorB the vector b
     * @return the double
     */
    public static double computeSquaredEuclidianDistance(double[] vectorA, double[] vectorB) {
        if (vectorA.length != vectorB.length) {
            LOG.error("The input arrays have different sizes!!!");
        }

        double squaredSum = 0.0;

        // compute the sum of square differences
        for (int index = 0; index < vectorA.length; index++) {
            squaredSum += (vectorA[index] - vectorB[index]) * (vectorA[index] - vectorB[index]);
        }

        return (squaredSum / vectorA.length);
    }

    /**
     * Gets the current date and time in the form of "yyyy-MM-dd HH:mm:ss".
     *
     * @return - the current date and time in the format: "yyyy-MM-dd HH:mm:ss"
     */
    public static String getCurrentTimeStamp() {
        java.util.Date dt = new java.util.Date();

        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        return sdf.format(dt);
    }

    /**
     * Compute the location of the window relative to the mouse location. The
     * window shall be centered on the mouse location.
     *
     * @param winDim - the dimension of the window to be centered
     * @return - the point where the window shall be located
     */
    public static Point winLocRelativeToMouse(Dimension winDim) {
        Point mouse = MouseInfo.getPointerInfo().getLocation();
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

        int x = (int) (mouse.getX() - (winDim.width / 2.0));
        int y = (int) (mouse.getY() - (winDim.height / 2.0));

        // check if the window will get out of the image
        x = Math.min(Math.max(0, x), Math.max(0, (screenSize.width - winDim.width)));
        y = Math.min(Math.max(0, y), Math.max(0, (screenSize.height - winDim.height - 100)));

        return (new Point(x, y));
    }

    /**
     * Check if the component com1 fits inside component comp2.
     *
     * @param comp1 - the size of the component to be checked if it fits
     * @param comp2 - the size of the component where the other component should
     * fit in
     * @return - return true if the component 1 fits in the component 2
     */
    public static boolean checkPlausability(Dimension comp1, Dimension comp2) {
        return ((comp1.width > 0) && (comp1.width <= comp2.width))
                && (comp1.height > 0) && (comp1.height <= comp2.height);
    }

    /**
     * Checks the extension of the given file and returns true if it is an image
     * file, or false otherwise.
     *
     * @param fileName - the name of the file to be checked
     * @return - true for image files and false for other file types
     */
    public static boolean isImageFile(String fileName) {
        return checkExtension(fileName, common.Constants.IMG_EXTENSION_LIST);
    }

    /**
     * Checks the extension of the given file and returns true if it is a video
     * file, or false otherwise.
     *
     * @param fileName - the name of the file to be checked
     * @return - true for video files and false for other file types
     */
    public static boolean isVideoFile(String fileName) {
        return checkExtension(fileName, common.Constants.VIDEO_EXTENSION_LIST);
    }

    /**
     * Get the extension of a file.
     *
     * @param f - the file itself
     * @return - the string representing the extension of the file
     */
    public static String getExtension(File f) {
        String fileName = f.getName();
        return getExtension(fileName);
    }

    /**
     * Get the extension of the string representing the name of a file.
     *
     * @param fileName - the name of the file
     * @return - the string representing the extension of the file
     */
    public static String getExtension(String fileName) {
        String ext = null;
        int i = fileName.lastIndexOf('.');
        if ((i > 0) && (i < fileName.length() - 1)) {
            ext = fileName.substring(i + 1).toLowerCase(Locale.ENGLISH);
        }
        return ext;
    }

    /**
     * Obtains the data from the raster of the given image as a byte array.
     *
     * @param image The image
     * @return The image data
     * @throws IllegalArgumentException If the given image is not backed by a
     * DataBufferByte. Usually, only BufferedImages of with the types
     * BufferedImage.TYPE_BYTE_* have a DataBufferByte
     */
    public static byte[] getByteData(BufferedImage image) {
        DataBuffer dataBuffer = image.getRaster().getDataBuffer();
        if (!(dataBuffer instanceof DataBufferByte)) {
            throw new IllegalArgumentException(
                    "Image does not contain a DataBufferByte");
        }
        DataBufferByte dataBufferByte = (DataBufferByte) dataBuffer;
        byte data[] = dataBufferByte.getData();
        return data;
    }

    /**
     * Copy one buffered image to another.
     *
     * @param src - source image
     * @param dst - destination image
     */
    public static void copySrcIntoDstAt(final BufferedImage src, final BufferedImage dst) {
        byte[] srcBuf = getByteData(src);
        byte[] dstBuf = getByteData(dst);
        int representationFactor;

        // width * hight * 3 because of rgb components and byte representation
        switch (src.getType()) {
            case BufferedImage.TYPE_BYTE_GRAY:
            case BufferedImage.TYPE_BYTE_INDEXED:
                representationFactor = 1;
                break;
            case BufferedImage.TYPE_3BYTE_BGR:
                representationFactor = 3;
                break;
            case BufferedImage.TYPE_4BYTE_ABGR:
                representationFactor = 4;
                break;
            default:
                representationFactor = 3;
                break;
        }

        System.arraycopy(srcBuf, 0, dstBuf, 0, src.getWidth() * src.getHeight() * representationFactor);
    }

    /**
     * Copy one buffered image to another and return the copied image.
     *
     * @param src - source image
     * @return - the copied image of the original
     */
    public static BufferedImage createImageCopy(final BufferedImage src) {
        BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());

        byte[] srcBuf = getByteData(src);
        byte[] dstBuf = getByteData(dst);
        int representationFactor;

        // width * hight * 3 because of rgb components and byte representation
        switch (src.getType()) {
            case BufferedImage.TYPE_BYTE_GRAY:
                representationFactor = 1;
                break;
            case BufferedImage.TYPE_3BYTE_BGR:
                representationFactor = 3;
                break;
            case BufferedImage.TYPE_4BYTE_ABGR:
                representationFactor = 4;
                break;
            default:
                representationFactor = 3;
                break;
        }

        System.arraycopy(srcBuf, 0, dstBuf, 0, src.getWidth() * src.getHeight() * representationFactor);

        return dst;
    }

    /**
     * Convert a matrix into a gray scale buffered image.
     *
     * @param width - the width of the image
     * @param height - the height of the image
     * @param imageType - the type of image to create
     * @param imgMatrix - the matrix with the pixels of the image
     * @return - a buffered image, based on the input matrix
     */
    public static BufferedImage convertToBuffImage(int width, int height, int imageType, double[][] imgMatrix) {
        BufferedImage bi = new BufferedImage(width, height, imageType);
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int pixel = Utils.limit(0, (int) imgMatrix[x][y], 255);
                bi.setRGB(x, y, new Color(pixel, pixel, pixel).getRGB());
            }
        }

        return bi;
    }

    /**
     * Apply the histogram equalisation for the input image.
     *
     * @param originalImage - the image to be processed
     * @return - the image with the equalisation of histogram applied - in
     * grayscale
     */
    public static BufferedImage histogramEqGrayscale(BufferedImage originalImage) {
        BufferedImage workImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), originalImage.getType());

        int[] rgb;
        int[] meanIntensity = new int[256];
        int[] histCum = new int[256];
        int[] transf = new int[256];

        for (int i = 0; i < originalImage.getWidth(); i++) {
            for (int j = 0; j < originalImage.getHeight(); j++) {
                rgb = getRGB(originalImage.getRGB(i, j));

                int average = (int) (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]);

                meanIntensity[average]++;
            }
        }
        histCum[0] = meanIntensity[0];

        for (int i = 1; i < 256; i++) {
            histCum[i] = meanIntensity[i] + histCum[i - 1];
        }

        for (int i = 0; i < 256; i++) {
            transf[i] = (255 * histCum[i]) / (originalImage.getWidth() * originalImage.getHeight());
        }

        for (int i = 0; i < originalImage.getWidth(); i++) {
            for (int j = 0; j < originalImage.getHeight(); j++) {
                rgb = getRGB(originalImage.getRGB(i, j));
                int average = (int) (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]);
                workImage.setRGB(i, j, new Color(transf[average], transf[average], transf[average]).getRGB());
            }
        }
        return workImage;

    }

    /**
     * Apply the histogram equalisation for the input image.
     *
     * @param originalImage - the image to be processed
     * @return - the image with the equalisation of histogram applied - in color
     */
    public static BufferedImage histogramEqColor(BufferedImage originalImage) {
        BufferedImage workImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), originalImage.getType());

        int[] rgb;
        int[] meanIntensityR = new int[256];
        int[] histCumR = new int[256];
        int[] transfR = new int[256];

        int[] meanIntensityG = new int[256];
        int[] histCumG = new int[256];
        int[] transfG = new int[256];

        int[] meanIntensityB = new int[256];
        int[] histCumB = new int[256];
        int[] transfB = new int[256];

        for (int i = 0; i < originalImage.getWidth(); i++) {
            for (int j = 0; j < originalImage.getHeight(); j++) {
                rgb = getRGB(originalImage.getRGB(i, j));
                meanIntensityR[rgb[0]]++;
                meanIntensityG[rgb[1]]++;
                meanIntensityB[rgb[2]]++;
            }
        }
        histCumR[0] = meanIntensityR[0];
        histCumG[0] = meanIntensityG[0];
        histCumB[0] = meanIntensityB[0];

        for (int i = 1; i < 256; i++) {
            histCumR[i] = meanIntensityR[i] + histCumR[i - 1];
            histCumG[i] = meanIntensityG[i] + histCumG[i - 1];
            histCumB[i] = meanIntensityB[i] + histCumB[i - 1];
        }

        for (int i = 0; i < 256; i++) {
            transfR[i] = (255 * histCumR[i]) / (originalImage.getWidth() * originalImage.getHeight());
            transfG[i] = (255 * histCumG[i]) / (originalImage.getWidth() * originalImage.getHeight());
            transfB[i] = (255 * histCumB[i]) / (originalImage.getWidth() * originalImage.getHeight());
        }

        for (int i = 0; i < originalImage.getWidth(); i++) {
            for (int j = 0; j < originalImage.getHeight(); j++) {
                rgb = getRGB(originalImage.getRGB(i, j));
                workImage.setRGB(i, j, new Color(transfR[rgb[0]], transfG[rgb[1]], transfB[rgb[2]]).getRGB());
            }
        }
        return workImage;
    }

    /**
     * Changes the input color to a similar color, which is not reserved.
     *
     * @param inputColor - the input color
     * @param objColorsList the obj colors list
     * @return - a color which is not reserved and it is similar to the input
     * one
     */
    public static Color changeColor(Color inputColor, List<Color> objColorsList) {
        int[] rgb = getRGB(inputColor.getRGB());
        Random rand = new Random();
        int randRange = 60;

        // change the color
        for (int index = 0; index < rgb.length; index++) {

            // add random values in the range [-0.5*range; +0.5*range)
            rgb[index] += (rand.nextInt(randRange) - (int) (randRange * 0.5f));

            // limit the value to byte
            rgb[index] = limit(0, rgb[index], 255);
        }

        // set the new color 
        Color color = new Color(rgb[0], rgb[1], rgb[2]);

        // make sure that the color is not reserved as well
        if (objColorsList.contains(color) || Constants.COLORS_LIST.contains(color)) {
            changeColor(color, objColorsList);
        }

        return color;
    }

    /**
     * Parse the given file path and name in order to extract just the file name
     * (with or without the extension, depending on the choice).
     *
     * @param fileNamePath the original file name, including its path
     * @param withExtension true if the returned name should include the file
     * extension; false if just the name of the file alone should be provided
     * @return the name of the file (with or without extension - as specified),
     * without its path
     */
    public static String getFileName(String fileNamePath, boolean withExtension) {
        // extract the name of the file and save it to create a folder there and store the info
        String[] tempStr;
        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win")) {
            tempStr = fileNamePath.split(File.separator + File.separator);
        } else {
            tempStr = fileNamePath.split(File.separator);
        }

        if (withExtension) {
            return tempStr[tempStr.length - 1];
        } else {
            // get the name of the file without extension
            return tempStr[tempStr.length - 1].substring(0, tempStr[tempStr.length - 1].lastIndexOf('.'));
        }
    }

    /**
     * Adjusts the contrast of the image - NOT TESTED yet - to be checked and
     * optimised.
     *
     * @param workImage the work image
     * @param min the min
     * @param max the max
     * @return buffered image
     */
    public static BufferedImage contrast(BufferedImage workImage, int min, int max) {
        int[] rgb;
        int minimR = 255;
        int maximR = 0;
        int minimG = 255;
        int maximG = 0;
        int minimB = 255;
        int maximB = 0;

        // find the minimum and maximum values for R G and B
        for (int i = 0; i < workImage.getWidth(); i++) {
            for (int j = 0; j < workImage.getHeight(); j++) {
                rgb = getRGB(workImage.getRGB(i, j));

                minimR = Math.min(minimR, rgb[0]);
                minimG = Math.min(minimG, rgb[1]);
                minimB = Math.min(minimB, rgb[2]);

                maximR = Math.max(maximR, rgb[0]);
                maximG = Math.max(maximG, rgb[1]);
                maximB = Math.max(maximB, rgb[2]);
            }
        }

        // adjust the intensities
        for (int i = 0; i < workImage.getWidth(); i++) {
            for (int j = 0; j < workImage.getHeight(); j++) {
                rgb = getRGB(workImage.getRGB(i, j));
                int intensR = (rgb[0] - minimR) * (max - min) / (maximR - minimR) + min;
                int intensG = (rgb[1] - minimG) * (max - min) / (maximG - minimG) + min;
                int intensB = (rgb[2] - minimB) * (max - min) / (maximB - minimB) + min;
                workImage.setRGB(i, j, new Color(intensR, intensG, intensB).getRGB());
            }
        }
        return workImage;
    }

    /**
     * Extract the RGB information from a pixel.
     *
     * @param pixel - the packed value of the color of the pixel
     * @return - returns an array which represents in this order the values of
     * R, G and B components of the pixel
     */
    public static int[] getRGB(int pixel) {
        int[] rgb = new int[3];
        rgb[0] = ((pixel & 0x00FF0000) >>> 16);   //red color
        rgb[1] = ((pixel & 0x0000FF00) >>> 8);    //green color
        rgb[2] = (pixel & 0x000000FF);            //blue color
        return rgb;
    }

    /**
     * Returns a list of all the files (jpg and png) contained by the specified
     * folder.
     *
     * @param folder - the folder for which the list of image files is wanted
     * @return - an array of strings representing all the image files found
     * inside
     */
    public static List<String> listFilesForFolder(final File folder) {
        List<String> filesList = new ArrayList<>();

        if (folder.length() > 0) {
            for (final File fileEntry : folder.listFiles()) {
                if (fileEntry.isDirectory()) {
                    listFilesForFolder(fileEntry);
                } else if (fileEntry.getName().endsWith(".jpg") || fileEntry.getName().endsWith(".JPG")
                        || fileEntry.getName().endsWith(".png") || fileEntry.getName().endsWith(".PNG")) {
                    filesList.add(fileEntry.getAbsolutePath());
                    LOG.trace(fileEntry.getAbsolutePath());
                }
            }
        }

        return filesList;
    }

    /**
     * Fill in a matrix with a default value.
     *
     * @param width - the width of the matrix
     * @param height - the height of the matrix
     * @param value - the value to be filled in
     * @return - the matrix filled in with the wanted default value
     */
    public static int[][] initMatrix(int width, int height, int value) {
        int[][] array = new int[height][width];

        for (int y = 0; y < height; y++) {
            Arrays.fill(array[y], value);
        }

        return array;
    }

    /**
     * Fill in a matrix with a default value.
     *
     * @param width - the width of the matrix
     * @param height - the height of the matrix
     * @param value - the value to be filled in
     * @return - the matrix containing the wanted default value
     */
    public static float[][] initMatrix(int width, int height, float value) {
        float[][] array = new float[height][width];

        for (int y = 0; y < height; y++) {
            Arrays.fill(array[y], value);
        }

        return array;
    }

    /**
     * Generate a custom mouse icon to have it as preview while drawing
     * scribbles.
     *
     * @param brushOpt - the configuration of the icon to be generated
     * @param actionType - the id of the object for which the icon is created
     * @param objColor the obj color
     * @return - the cursor to be displayed by the mouse
     */
    public static Cursor generateCustomIcon(BrushOptions brushOpt, int actionType, Color objColor) {
        Cursor cursor;
        if (brushOpt.getBrushSize() > Constants.MIN_MOUSE_BRUSH_ICON) {
            Toolkit toolkit = Toolkit.getDefaultToolkit();

            Dimension dim = toolkit.getBestCursorSize(brushOpt.getBrushSize(), brushOpt.getBrushSize());
            BufferedImage buffered = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = buffered.createGraphics();
            float density = brushOpt.getBrushDensity();
            Random rand = new Random();
            byte[][] circleMask = getCircleMask(brushOpt.getBrushSize());  // the mask making the mouse pointer a circle

            if (brushOpt.getBrushSize() <= 5) {
                density = 1.0f;
            } else if (brushOpt.getBrushSize() > 5 && brushOpt.getBrushSize() < 10) {
                density = Math.max(0.5f, density);
            }

            // objects smaller than 0 are for erase purposes and the density is always full
            if (actionType < 0) {
                density = 1.0f;
            }

            // draw the inner part of the brush, the one which will show after a click
            g.setColor(getDrawingColor(actionType, objColor));

            for (int x = 1; x <= brushOpt.getBrushSize(); x++) {
                for (int y = 1; y <= brushOpt.getBrushSize(); y++) {
                    float randFloat = rand.nextFloat();
                    if ((randFloat < density) && (circleMask[x - 1][y - 1] == (byte) 1)) {
                        g.drawLine(x, y, x, y);
                    }
                }
            }

            // draw a white box around the brush for better visibility
            g.setColor(Color.white);

            g.drawOval(0, 0, brushOpt.getBrushSize() + 1, brushOpt.getBrushSize() + 1);

            g.dispose();

            int drawlLocation = (int) (brushOpt.getBrushSize() * 0.5f);
            cursor = toolkit.createCustomCursor(buffered, new Point(drawlLocation, drawlLocation), "myCursor");

        } else {
            cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
        }

        return cursor;
    }

    /**
     * Choose the best background for text: white for dark text, black for
     * bright text.
     *
     * @param color - the color for which the background shall be computed.
     * @param transparency - the wanted transparency for the returned color
     * @return - the adviced color for the background based on luminance
     */
    public static Color getContrastColor(Color color, int transparency) {
        // no precision needed here
        int y = (299 * color.getRed() + 587 * color.getGreen() + 114 * color.getBlue()) / 1000;

        return ((y >= 128) ? new Color(0, 0, 0, transparency) : new Color(255, 255, 255, transparency));
    }

    /**
     * Returns the color: 0 = red (bkg), 1 = the color of the current object
     * (object), other: white (eraser).
     *
     * @param id - the value for which the color has to be established
     * @param objColor - the color of the object (object = not bkg , not erase)
     * @return - the corresponding color 0 = red, 1 = the color of the current
     * object, other = white
     */
    public static Color getDrawingColor(int id, Color objColor) {
        switch (id) {
            case 0:
                return Color.red;
            case 1:
                return objColor;
            default:
                return Color.white;
        }
    }

    /**
     * Maps the objects to colors.
     *
     * @param id - the id of the object, in the database
     * @return - the color of the object with the specified id
     */
    public static Color getColorOfObjByID(long id) {
        // compute the index of the color based on the object id
        int colorIndex = (int) (id % Constants.COLORS_LIST.size());

        return Constants.COLORS_LIST.get(colorIndex);
    }

    /**
     * Limit value between specified min and max values
     * <p>
     * value = [min,max]
     *
     * @param min min value
     * @param value value to be limited
     * @param max max value
     * @return returns the bounded value
     */
    public static int limit(int min, int value, int max) {
        return (value < min) ? min : ((value > max) ? max : value);
    }

    /**
     * Limit value between specified min and max values
     * <p>
     * value = [min,max]
     *
     * @param min min value
     * @param value value to be limited
     * @param max max value
     * @return returns the bounded value
     */
    public static long limit(long min, long value, long max) {
        return (value < min) ? min : ((value > max) ? max : value);
    }

    /**
     * Computes the CRC32 for a specified file
     *
     * @param fileName file for which the CRC32 is calculated
     * @return CRC32 for the specified file in String format
     */
    public static String CRC32(String fileName) {
        try (FileInputStream fi = new FileInputStream(fileName)) {

            /* allocates a buffer of 1MB for reading the video file
               this size seems to provide the fastest runtime
             */
            byte[] b = new byte[1024 * 1024];
            int len;
            CRC32 crc = new CRC32();

            while ((len = fi.read(b)) >= 0) {
                crc.update(b, 0, len);
            }

            // create the hash
            return (new BigInteger(Long.toString(crc.getValue())).toString(16));
        } catch (FileNotFoundException ex) {
            LOG.error("File {} not found", fileName);
            LOG.debug("File {} not found exception {}", fileName, ex);
        } catch (IOException ex) {
            LOG.error("File {} not accessible", fileName);
            LOG.debug("File {} not accessible exception {}", fileName, ex);
        }
        return null;
    }

    /**
     * Filter the object map array.
     * <p>
     * The filtering is done by sliding a window over the input array and
     * compute the sum of the values in the window (sum of 1s). If the sum is
     * smaller than a threshold, make the middle value of the window 0, else 1.
     * <p>
     * The filter aims to keep the white pixels which are in a neighbourhood of
     * other white pixels and discard the lonely pixels.
     *
     * @param objMap - the object map to be filtered
     */
    public static void filterObjectMap(byte[][] objMap) {
        if (objMap == null) {
            return;
        }

        // run N times the filtering algorithm
        // for (int steps = 0; steps < 10; steps++) {
        int filterSize = 3;
        byte[][] inputMap = new byte[objMap.length][objMap[0].length];

        for (int line = 0; line < objMap.length; line++) {
            inputMap[line] = objMap[line].clone();
        }

        for (int y = 0; y < inputMap[0].length - 2; y++) {
            for (int x = 0; x < inputMap.length - 2; x++) {
                int sum = 0;
                for (int j = 0; j < filterSize; j++) {
                    for (int i = 0; i < filterSize; i++) {
                        sum += inputMap[x + i][y + j];
                    }
                }

                if (sum < Math.ceil(filterSize * filterSize * 0.5)) {
                    objMap[x + (int) (filterSize * 0.5f)][y + (int) (filterSize * 0.5f)] = 0;
                } else {
                    objMap[x + (int) (filterSize * 0.5f)][y + (int) (filterSize * 0.5f)] = 1;
                }
            }
        }

        // filter the first and last line
        for (int x = 0; x < inputMap.length - 2; x++) {
            int sum0 = 0;   // sum for the first line
            int sumN = 0;   // sum for the last line
            for (int j = 0; j < filterSize - 1; j++) {
                for (int i = 0; i < filterSize; i++) {
                    sum0 += inputMap[x + i][j];
                    sumN += inputMap[x + i][inputMap[0].length - 1 - j];
                }
            }
            // filter first line
            if (sum0 < Math.ceil((filterSize - 1) * filterSize * 0.5)) {
                objMap[x + (int) (filterSize * 0.5f)][0] = 0;
            } else {
                objMap[x + (int) (filterSize * 0.5f)][0] = 1;
            }

            // filter last line
            if (sumN < Math.ceil((filterSize - 1) * filterSize * 0.5)) {
                objMap[x + (int) (filterSize * 0.5f)][inputMap[0].length - 1] = 0;
            } else {
                objMap[x + (int) (filterSize * 0.5f)][inputMap[0].length - 1] = 1;
            }
        }

        // filter the first and last column
        for (int y = 0; y < inputMap[0].length - 2; y++) {
            int sum0 = 0;   // sum for the first column
            int sumN = 0;   // sum for the last column
            for (int j = 0; j < filterSize; j++) {
                for (int i = 0; i < filterSize - 1; i++) {
                    sum0 += inputMap[i][y + j];
                    sumN += inputMap[inputMap.length - 1 - i][y + j];
                }
            }

            // filter first column
            if (sum0 < Math.ceil(filterSize * (filterSize - 1) * 0.5)) {
                objMap[0][y + (int) (filterSize * 0.5f)] = 0;
            } else {
                objMap[0][y + (int) (filterSize * 0.5f)] = 1;
            }

            // filter last column
            if (sumN < Math.ceil(filterSize * (filterSize - 1) * 0.5)) {
                objMap[inputMap.length - 1][y + (int) (filterSize * 0.5f)] = 0;
            } else {
                objMap[inputMap.length - 1][y + (int) (filterSize * 0.5f)] = 1;
            }
        }

        // filter the corners
        // left - top
        int sum = objMap[0][0] + objMap[0][1] + objMap[1][0] + objMap[1][1];
        objMap[0][0] = (byte) ((sum < 3) ? 0 : 1);

        // right - top
        sum = objMap[inputMap.length - 1][0] + objMap[inputMap.length - 1][1] + objMap[inputMap.length - 2][0] + objMap[inputMap.length - 2][1];
        objMap[inputMap.length - 1][0] = (byte) ((sum < 3) ? 0 : 1);

        // left - bottom
        sum = objMap[0][inputMap[0].length - 1] + objMap[0][inputMap[0].length - 2] + objMap[1][inputMap[0].length - 1] + objMap[1][inputMap[0].length - 2];
        objMap[0][inputMap[0].length - 1] = (byte) ((sum < 3) ? 0 : 1);

        // right - bottom
        sum = objMap[inputMap.length - 1][inputMap[0].length - 1]
                + objMap[inputMap.length - 2][inputMap[0].length - 1]
                + objMap[inputMap.length - 1][inputMap[0].length - 2]
                + objMap[inputMap.length - 2][inputMap[0].length - 2];
        objMap[inputMap.length - 1][inputMap[0].length - 1] = (byte) ((sum < 3) ? 0 : 1);

    }

    /**
     * Computes the integer logarithm in base 2 of the input number.
     *
     * @param n - the input number
     * @return - the integer value of the log base 2 of n
     */
    public static int log2N(int n) {
        if (n == 0) {
            return 0;
        }
        return (31 - Integer.numberOfLeadingZeros(n));
    }

    /**
     * A sigmoid function is a mathematical function having an "S" shaped curve
     * (sigmoid curve).
     *
     * @param value the value/point for which the sigmoid function has to be
     * computed
     * @return - the value of the sigmoid function in the given point
     */
    public static double sigmoidFunction(double value) {
        return 1.0 / (1.0 + Math.exp(-value));
    }

    /**
     * Based on the size of the generated box, compute a border size to be added
     * to the displayed image.
     *
     * @param box - the original box size
     * @param borderPercentageWidth - the percentage of border to be added on
     * width
     * @param borderPercentageHeight - the percentage of border to be added on
     * height
     * @param minBorder - the min border in pixels
     * @param frameSize - the max size the box can have, after being bordered
     * @return - the size of the possible border for the input box
     */
    public static BoxLRTB getBorderedSize(Rectangle box,
            double borderPercentageWidth,
            double borderPercentageHeight,
            int minBorder,
            Dimension frameSize) {

        int borderWidth = (int) (borderPercentageWidth * box.width);
        int borderHeight = (int) (borderPercentageHeight * box.height);

        // if the border is too small, make it at least 10 pixels
        if (borderWidth < minBorder) {
            borderWidth = minBorder;
        }

        if (borderHeight < minBorder) {
            borderHeight = minBorder;
        }

        // set the border as 20% of the size of the box
        BoxLRTB borderSize = new BoxLRTB(borderWidth, borderHeight);

        // limit the bordering in such way as to not get out of the image
        if ((box.x - borderWidth) < 0) {
            borderSize.setxLeft(box.x);
        }

        if ((box.y - borderHeight) < 0) {
            borderSize.setyTop(box.y);
        }

        if ((box.x + box.width + borderWidth) > frameSize.width) {
            borderSize.setxRight(frameSize.width - (box.x + box.width));
        }

        if ((box.y + box.height + borderHeight) > frameSize.height) {
            borderSize.setyBottom(frameSize.height - (box.y + box.height));
        }

        // return the new size of the border to be added
        return borderSize;
    }

    /**
     * Based on the size of the generated box, compute a border size to be added
     * to the displayed image.
     *
     * @param box - the original box size
     * @param targetBorder - the wanted border in pixels
     * @param frameSize - the max size the box can have, after being bordered
     * @return - the size of the possible border for the input box
     */
    public static BoxLRTB getBorderedSize(Rectangle box,
            int targetBorder,
            Dimension frameSize) {

        // set the border as 20% of the size of the box
        BoxLRTB borderSize = new BoxLRTB(targetBorder, targetBorder);

        // limit the bordering in such way as to not get out of the image
        if ((box.x - targetBorder) < 0) {
            borderSize.setxLeft(box.x);
        }

        if ((box.y - targetBorder) < 0) {
            borderSize.setyTop(box.y);
        }

        if ((box.x + box.width + targetBorder) > frameSize.width) {
            borderSize.setxRight(frameSize.width - (box.x + box.width));
        }

        if ((box.y + box.height + targetBorder) > frameSize.height) {
            borderSize.setyBottom(frameSize.height - (box.y + box.height));
        }

        // return the new size of the border to be added
        return borderSize;
    }

    /**
     * Check if it is possible to connect to the specified server.
     *
     * @param serverAddress the address of the server where the connection shall
     * be checked
     * @param portNo the number of the port on which the connection shall be
     * done
     * @return true if the server is reachable and false if the address and port
     * does not represent a valid path to a server
     */
    public static boolean isServerOnline(String serverAddress, int portNo) {
        try {
            InetSocketAddress socketAddress = new InetSocketAddress(serverAddress, portNo);
            // conect to the server
            try (Socket socket = new Socket()) {
                // conect to the server
                socket.connect(socketAddress, 100);
            }
            return true;
        } catch (Exception e) {
            LOG.error("Communication with the server couldn't be established");
            LOG.debug("Communication with the server couldn't be established {}", e);
            return false;
        }
    }

    /**
     * Generates a mask, representing a circle, of the size specified by the
     * size parameter. The mask contains a circle with 1s and background (as
     * 0s).The mask is to be used in the drawing of the mouse brush and for the
     * scribbles generation.
     * <p>
     * <p>
     * x = cx + r * cos(a)
     * <p>
     * y = cy + r * sin(a)
     * <p>
     * Where r is the radius, cx,cy the origin, and a the angle (in radian).
     *
     * @param size - the size of the mask
     * @return - the mask representing a filled circle, of radius = size / 2
     */
    public static byte[][] getCircleMask(int size) {
        switch (size) {
            case 3:
                return CircleTemplate.CIRCLE_MASK_3X3;

            case 4:
                return CircleTemplate.CIRCLE_MASK_4X4;

            case 5:
                return CircleTemplate.CIRCLE_MASK_5X5;

            case 6:
                return CircleTemplate.CIRCLE_MASK_6X6;

            case 7:
                return CircleTemplate.CIRCLE_MASK_7X7;

            case 8:
                return CircleTemplate.CIRCLE_MASK_8X8;

            case 9:
                return CircleTemplate.CIRCLE_MASK_9X9;

            case 10:
                return CircleTemplate.CIRCLE_MASK_10X10;

            case 11:
                return CircleTemplate.CIRCLE_MASK_11X11;

            case 12:
                return CircleTemplate.CIRCLE_MASK_12X12;

            case 13:
                return CircleTemplate.CIRCLE_MASK_13X13;

            case 14:
                return CircleTemplate.CIRCLE_MASK_14X14;

            case 15:
                return CircleTemplate.CIRCLE_MASK_15X15;

            case 16:
                return CircleTemplate.CIRCLE_MASK_16X16;

            case 17:
                return CircleTemplate.CIRCLE_MASK_17X17;

            case 18:
                return CircleTemplate.CIRCLE_MASK_18X18;

            case 19:
                return CircleTemplate.CIRCLE_MASK_19X19;

            case 20:
                return CircleTemplate.CIRCLE_MASK_20X20;

            case 21:
                return CircleTemplate.CIRCLE_MASK_21X21;

            case 22:
                return CircleTemplate.CIRCLE_MASK_22X22;

            case 23:
                return CircleTemplate.CIRCLE_MASK_23X23;

            case 24:
                return CircleTemplate.CIRCLE_MASK_24X24;

            case 25:
                return CircleTemplate.CIRCLE_MASK_25X25;

            case 26:
                return CircleTemplate.CIRCLE_MASK_26X26;

            case 27:
                return CircleTemplate.CIRCLE_MASK_27X27;

            case 28:
                return CircleTemplate.CIRCLE_MASK_28X28;

            case 29:
                return CircleTemplate.CIRCLE_MASK_29X29;

            case 30:
                return CircleTemplate.CIRCLE_MASK_30X30;

            default:
                return CircleTemplate.CIRCLE_MASK_5X5;
        }
    }

    /**
     * Returns the capitalised string
     *
     * @param name string to be capitalised
     * @return the capitalised string
     */
    public static String capitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }

        return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
    }

    /**
     * Create the directories structure for the given path.
     *
     * @param path - the path which has to be created if it does not exist
     */
    public static void createFolderPath(String path) {
        // create the directory structure
        File file = new File(path + "text.txt");
        file.getParentFile().mkdirs();
    }

    /**
     * Check if the extension of the file is one of the wanted. Only files which
     * represent images can be loaded.
     *
     * @param fileName the name of the current file in the directory structure
     * @param collection the collection which has to be checked for matching
     * (the list of extensions)
     * @return true if the file is a wanted type; false if the file represents a
     * not wanted format
     */
    public static boolean checkExtension(String fileName, List<String> collection) {
        // convert the name of the file to lower case, to avoid missing file types (jpg vs JPG)        
        return collection.stream().anyMatch((String extension) -> fileName.toLowerCase().endsWith("." + extension));
    }

    /**
     * Get an input string and replace all non alpha numeric characters with a
     * given string.
     *
     * @param inputStr the string to be changed
     * @param replaceStr the character to replace any non alpha numeric
     * character in the input string
     * @return string
     */
    public static String replaceNonAlphaNum(String inputStr, String replaceStr) {
        String output = inputStr.replaceAll("[^A-Za-z0-9 ]", replaceStr);

        // if the output string ends with a replace string, remove it
        if (output.endsWith(replaceStr)) {
            output = output.substring(0, output.length() - replaceStr.length());
        }

        return output;
    }

    /**
     * Converts a number representing bytes to the biggest possible multiple.
     *
     * @param bytes the number to be converted
     * @param si true if the display system should be decimal (kilobyte - kB,
     * megabyte - MB etc.); false if the display system should be binary
     * (kibibyte - KB, mebibyte - MiB, gibibyte - GiB etc.)
     * @return the transformation of the number to the biggest metric
     */
    public static String humanReadableByteCount(long bytes, boolean si) {
        int unit = si ? 1000 : 1024;
        if (bytes < unit) {
            return bytes + " B";
        }
        int exp = (int) (Math.log(bytes) / Math.log(unit));
        String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
        return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
    }

    /**
     * Mirror the image/buffer data
     *
     * @param image the image containing the pixel data which has to be
     * mirrored. This function will alter the bytes of the input image, so that
     * after this function the input image will contain the mirrored information
     */
    public static void mirrorImage(BufferedImage image) {
        byte[] bgr = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        byte[] mirror = new byte[bgr.length];
        final int multiplier = 3; // BGR

        for (int j = 0; j < image.getHeight(); j++) {
            for (int i = 0; i < image.getWidth(); i++) {
                mirror[(j * (image.getWidth() * multiplier)) + ((((image.getWidth() - 1) - i) * multiplier) + 0)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 0)]; // R
                mirror[(j * (image.getWidth() * multiplier)) + ((((image.getWidth() - 1) - i) * multiplier) + 1)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 1)]; // G
                mirror[(j * (image.getWidth() * multiplier)) + ((((image.getWidth() - 1) - i) * multiplier) + 2)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 2)]; // B
            }
        }

        System.arraycopy(mirror, 0, bgr, 0, image.getWidth() * image.getHeight() * multiplier);
    }

    /**
     * Flip vertically the image
     *
     * @param image the image containing the pixel data which has to be flipped
     * vertically. This function will alter the bytes of input image, so that
     * after this function the input image will contain the flipped information
     */
    public static void flipVerticallyImage(BufferedImage image) {
        byte[] bgr = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        byte[] flipped = new byte[bgr.length];
        final int multiplier = 3; // BGR

        for (int j = 0; j < image.getHeight(); j++) {
            for (int i = 0; i < image.getWidth(); i++) {
                flipped[((image.getHeight() - 1) - j) * (image.getWidth() * multiplier) + (i * multiplier + 0)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 0)]; // R
                flipped[((image.getHeight() - 1) - j) * (image.getWidth() * multiplier) + (i * multiplier + 1)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 1)]; // G
                flipped[((image.getHeight() - 1) - j) * (image.getWidth() * multiplier) + (i * multiplier + 2)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 2)]; // B
            }
        }

        System.arraycopy(flipped, 0, bgr, 0, image.getWidth() * image.getHeight() * multiplier);
    }

    /**
     * Export the original image and the result image, concatenated, for demo
     * purpose.
     *
     * @param leftImage the image to be displayed on the left side of the output
     * image
     * @param rightImage the image to be displayed on the right side of the
     * output image
     * @param outputPath the path where to output the image
     * @param fileType the file type/extension for the export
     * @param bkgColor the color used for the background between the images
     */
    public static void exportTwoImages(BufferedImage leftImage, BufferedImage rightImage, String outputPath, String fileType, Color bkgColor) {
        // inform the user if the images are different
        if ((leftImage.getWidth() != rightImage.getWidth()) || (leftImage.getHeight() != rightImage.getHeight())) {
            LOG.info("The width or height of the exported images differs! left: {}x{}, right: {}x{}",
                    leftImage.getWidth(), leftImage.getHeight(), rightImage.getWidth(), rightImage.getHeight());
        }

        // compute the size of the result image, adding also some space between them
        int width = leftImage.getWidth() + 50 + rightImage.getWidth();      // 50 represents the inset between the images
        int height = Math.max(leftImage.getHeight(), rightImage.getHeight());

        // generate a new image composed of proposed images
        BufferedImage resultImg = new BufferedImage(width, height, leftImage.getType());
        Graphics2D g2DResImg = (Graphics2D) resultImg.getGraphics();

        g2DResImg.setColor(bkgColor);
        g2DResImg.fillRect(0, 0, width, height);

        // put the left image
        g2DResImg.drawImage(leftImage, 0, 0, leftImage.getWidth(), leftImage.getHeight(), null);

        // add the right image
        g2DResImg.drawImage(rightImage,
                resultImg.getWidth() - rightImage.getWidth() - 1,
                0, rightImage.getWidth(), rightImage.getHeight(), null);

        g2DResImg.dispose();

        // export the image
        ExportImage.exportImage(resultImg, outputPath, fileType);
    }

    /**
     * Searches the list of radio buttons of the given button group, for the
     * specified text. When found, the radio button is selected.
     *
     * @param group the group being searched
     * @param text the text being searched
     */
    public static void selectRadioButton(ButtonGroup group, String text) {
        // get the list of elements of the button group
        Enumeration<AbstractButton> elements = group.getElements();

        // search in the button group for the user selected value
        while (elements.hasMoreElements()) {
            AbstractButton button = elements.nextElement();

            // if the selected value is found, select the corresponding radio button
            if (button.getActionCommand().equals(text)) {
                group.setSelected(button.getModel(), true);
                break;
            }
        }
    }

    /**
     * Allow the user to choose a path where to save its files and set it in the
     * path text field.
     *
     * @param currentDirectory the directory where the browser should open
     * @param parent the parent frame/dialog of the browser dialog
     * @return the path selected by the user
     */
    public static String browseFilePath(String currentDirectory, Component parent) {
        String selectedPath = "";

        JFileChooser chooser = new JFileChooser();

        // set the initial directory
        chooser.setCurrentDirectory(new File(currentDirectory));

        // allow only the choosing of directories
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

        if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
            // set the chosen text as the path where to save the file
            try {
                selectedPath = chooser.getSelectedFile().getCanonicalPath();
            } catch (IOException ex) {
                java.util.logging.Logger.getLogger(ExportImagesGUI.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        return selectedPath;
    }

    /**
     * Check if the sent text equals to the predefined invalid attribute. When
     * this is true, it means the attribute was not correctly chosen.
     *
     * @param text the text where to append the invalid attribute
     * @param attribute the attribute to be checked
     * @param attributeName the name of the attribute being checked
     */
    public static void checkAttributeValid(StringBuilder text, String attribute, String attributeName) {
        if (Constants.INVALID_ATTRIBUTE_TEXT.equalsIgnoreCase(attribute)) {
            text.append(attributeName);
            text.append(", ");
        }
    }

    /**
     * Asks the user to correct the attributes. If the user chooses to correct
     * them, the application does not continue with the saving of data; it will
     * cancel the process and go back to the normal state.
     *
     * @param invalidAttribute a string with the invalid attributes (is any)
     * @param editAttribAvailable true if the edit of attributes is allowed
     * @param parent the parent component of the message window
     * @return the choice of the user, whether he wants to save the data as it
     * is or the saving of data shall not be done yet, because the user wants to
     * correct it (or the attributes)
     */
    public static int messageAttributesSet(String invalidAttribute, boolean editAttribAvailable, Component parent) {
        // if the application is running offline, show the path to the attributes
        String attributesPathMsg = editAttribAvailable ? "If you do not find the wanted attributes, please go to Menu->Options->Attributes<br>"
                + "and define the missing attributes!<br><br>" : "";

        // ask the user to correct the missing attributes
        String message = "<html>There are invalid attributes (or attributes which were not yet set):<br>"
                + invalidAttribute.toLowerCase(Locale.ENGLISH) + ".<br><br>"
                + "<font color=#4d0000>"
                + "<b>Do you want to correct them now?</b><br>"
                + "</font>"
                + "<i>(If you choose No, the current attributes will be saved as ground truth)<br>"
                + attributesPathMsg
                + "</i></html>";

        // display the warning dialog
        if (editAttribAvailable) {
            Object[] options = {"Yes", "No", "Edit Attributes"};
            return Messages.showQuestionMessage(parent, message, "Invalid attributes!!!", JOptionPane.YES_NO_CANCEL_OPTION, options, options[0]);
        } else {
            Object[] options = {"Yes", "No"};
            return Messages.showQuestionMessage(parent, message, "Invalid attributes!!!", JOptionPane.YES_NO_OPTION, options, options[0]);
        }
    }

    /**
     * Opens the folder or file from the specified path, in an explorer window
     * or in a default file specific application.
     *
     * @param path the path to the wanted folder/file
     */
    public static void openFileFolderInExplorer(String path) {
        try {
            if (new File(path).exists()) {
                Desktop.getDesktop().open(new File(path));
            }
        } catch (IOException e) {
            LOG.debug("The open file/folder in explorer failed!");
            LOG.debug("The open file/folder in explorer failed! {}", e);
        }
    }

    /**
     * Check if the point is inside the bounds.
     *
     * @param point the point to be checked if it is inside the bounds
     * @param bounds the max values that the point's x and y can have
     * @return return true if the point is inside the bounds or false otherwise
     */
    public static boolean checkBounds(Point point, Dimension bounds) {
        return (point.x >= 0 && point.x < bounds.width)
                && point.y >= 0 && point.y < bounds.height;
    }

    /**
     * Clone the input tree; create a new tree, which is the copy of the input
     * one.
     *
     * @param rootNode the root node of the tree to be copied
     * @return a new tree, with new nodes, which represent the deep copy of the
     * input tree
     */
    public static CustomTreeNode cloneTree(CustomTreeNode rootNode) {
        // the list of nodes of the input tree
        List<CustomTreeNode> nodesList = new ArrayList<>();
        // the list of nodes of the cloned tree
        List<CustomTreeNode> clonedList = new ArrayList<>();
        // the new tree, representing the deep copy of the input one
        CustomTreeNode clonedTree = new CustomTreeNode(rootNode.getRoot());

        // in the nodes list, add the first nodes
        nodesList.add(rootNode);
        clonedList.add(clonedTree);

        // keep track of the size of the list of nodes
        int listLength = nodesList.size();

        // take every node and create copies of the children node
        for (int index = 0; index < listLength; index++) {
            CustomTreeNode node = nodesList.get(index);
            // add the list of children to the list of nodes to be copied
            List<CustomTreeNode> children = node.getChildrenNodes();
            nodesList.addAll(children);
            // by adding new chi
            listLength += children.size();

            // create new nodes, based on the children of the current node
            clonedList.get(index).addChildrenStr(node.getChildren());
            clonedList.addAll(clonedList.get(index).getChildrenNodes());
        }

        return clonedTree;
    }

    /**
     * Check if a string is numeric.
     *
     * @param str the string to be checked
     * @return true if the string contains just number specific characters:
     * 0...9 , -
     */
    public static boolean isNumeric(String str) {
        return str.matches("-?\\d+(\\.\\d+)?");  //match a number with optional '-' and decimal.
    }

    /**
     * Compute the distance from the point P to the line defined by the points A
     * and B.
     *
     * https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
     *
     * @param a the point A of the line
     * @param b the point B of the line
     * @param p the point from where the distance is computed
     * @return
     */
    public static float distPointToLine(Point a, Point b, Point p) {
        float nominator = (b.y - a.y) * p.x - (b.x - a.x) * p.y + b.x * a.y - b.y * a.x;
        float denominator = (b.y - a.y) * (b.y - a.y) + (b.x - a.x) * (b.x - a.x);

        return (float) (Math.abs(nominator) / Math.sqrt(denominator));

    }

    /**
     * In analytic geometry, the distance between two points of the xy-plane can
     * be found using the distance formula. The distance between (x1, y1) and
     * (x2, y2) is given by:
     *
     * d = sqrt((Δx)^2 + (Δy)^2) = sqrt((x2 − x1)^2 + (y2 − y1)^2)
     *
     * @param a the point A of the segment
     * @param b the point B of the segment
     * @return The length of the segment formed by the two points, A and B.
     */
    public static float distPointToPoint(Point a, Point b) {
        return (float) Math.sqrt((b.y - a.y) * (b.y - a.y) + (b.x - a.x) * (b.x - a.x));
    }

    /**
     *
     * @param a the point A of the triangle
     * @param b the point B of the triangle
     * @param v the point V of the triangle
     * @return
     */
    public static float distPointToSegment(Point a, Point b, Point v) {
        float distVA = distPointToPoint(a, v);
        float distVB = distPointToPoint(b, v);
        float distAB = distPointToPoint(a, b);

        return ((distVA + distVB) / distAB);

    }

    /**
     * Create a deep copy of the polygon.
     *
     * @param poly the polygon to be replicated
     * @return a new polygon, copy of the initial one
     */
    public static Polygon deepCopyPoly(Polygon poly) {
        return new Polygon(poly.xpoints, poly.ypoints, poly.npoints);
    }

    /**
     * Open and decode a binary greyscale image. The file shall contain just the
     * bytes of the image.
     *
     * @param chosenFileName the name of the file to be opened
     * @param width the width of the image
     * @param height the height of the image
     * @return the image from the specified file
     */
    public static BufferedImage getBinaryByteImage(String chosenFileName, int width, int height) {
        int value, x, y, pos = 0;
        // create the image
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);

        try (DataInputStream in = new DataInputStream(new FileInputStream(chosenFileName))) {
            while (in.available() > 0) {
                // read the image byte by byte
                value = in.readByte();
                
                // the data is uint8
                if (value < 0) {
                    value += 255;
                }
                // compute the position in the image
                y = pos / width;
                x = pos % width;
                bi.setRGB(x, y, new Color(value, value, value).getRGB());
                
                pos++;
            }
        } catch (IOException ex) {
            java.util.logging.Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex);
        }

        return bi;
    }
} public class JPenFunctions implements PenListener {

    private int brushSize;
    private int brushSizePrev;

    /**
     * Makes the marked methods observable. It is part of the mechanism to
     * notify the frame on top about changes.
     */
    private final NotifyObservers observable = new NotifyObservers();

    /**
     * Instantiates a new J pen functions.
     */
    public JPenFunctions() {

    }

    @Override
    public void penButtonEvent(PButtonEvent ev) {

        PKind type = ev.pen.getKind();
        // Discard events from mouse
        if (type == PKind.valueOf(PKind.Type.CURSOR)) {
            return;
        }

        // Pen pressed is LEFT
        if (ev.pen.getButtonValue(PButton.Type.LEFT)) {
//            Utils.outputText("LEFT");
        }
        if (ev.pen.getButtonValue(PButton.Type.CENTER)) {
//            Utils.outputText("CENTER");
        }
        // Pen button pressed is RIGHT
        if (ev.pen.getButtonValue(PButton.Type.RIGHT)) {
//            Utils.outputText("RIGHT");
        }
    }

    @Override
    public void penKindEvent(PKindEvent ev) {
//        Utils.outputText("Kind event");
    }

    @Override
    public void penLevelEvent(PLevelEvent evt) {
        // Get kind of event: does it come from mouse (CURSOR), STYLUS or ERASER?
        PKind type = evt.pen.getKind();

        // Discard events from mouse
        if (type == PKind.valueOf(PKind.Type.CURSOR)) {
            return;
        }

        // Set the brush's size relative to the pressure
        brushSize = (int) ((evt.pen.getLevelValue(PLevel.Type.PRESSURE) * 60.0f) * 0.5f);

        // notify the main thread that the brush size is changed
        observable.notifyObservers(ObservedActions.Action.REFRESH_BRUSH_SIZE);

    }

    @Override
    public void penScrollEvent(PScrollEvent ev) {
//        Utils.outputText("scroll event");
    }

    @Override
    public void penTock(long availableMillis) {
//          Utils.outputText("TOCK - available period fraction: " + availableMillis);
    }

    /**
     * Gets brush size.
     *
     * @return the brush size
     */
    public int getBrushSize() {
        return brushSize;
    }

    /**
     * Gets brush size prev.
     *
     * @return the brush size prev
     */
    public int getBrushSizePrev() {
        return brushSizePrev;
    }

    /**
     * Sets brush size prev.
     *
     * @param size the size
     */
    public void setBrushSizePrev(int size) {
        this.brushSizePrev = size;
    }

    /**
     * Allows another module to put an observer into the current module.
     *
     * @param o - the observer to be added
     */
    public void addObserver(Observer o) {
        observable.addObserver(o);
    }

    /**
     * Allows another module to erase an observer from the current module.
     *
     * @param o - the observer to be deleted
     */
    public void deleteObserver(Observer o) {
        observable.deleteObserver(o);
    }
} public class ScreenResolution {

    /**
     * The resolution of the current screen
     */
    private Dimension resolution;

    /**
     * Constructor where the resolution is computed.
     *
     * @param comp - the frame of the gui - needed to get the resolution of the screen where the app is showed
     */
    public ScreenResolution(Component comp) {
        //set the current screen resolution                
        GraphicsConfiguration graphicsCfg = comp.getGraphicsConfiguration();
        DisplayMode displayMode = graphicsCfg.getDevice().getDisplayMode();
        resolution = new Dimension(displayMode.getWidth(), displayMode.getHeight());
    }

    /**
     * Gets screen resolution.
     *
     * @return Returns the value of the resolution of the screen.
     */
    public Dimension getScreenResolution() {
        return resolution;
    }

    /**
     * Gets screen width.
     *
     * @return the width of the screen
     */
    public int getScreenWidth() {
        return resolution.width;
    }

    /**
     * Gets screen height.
     *
     * @return the height of the screen
     */
    public int getScreenHeight() {
        return resolution.height;
    }

    /**
     * Saves the resolution of the screen where the component is showed.
     *
     * @param comp - the frame for which the resolution of the containing screen is needed
     */
    public void setScreenResolution(Component comp) {
        GraphicsConfiguration graphicsCfg = comp.getGraphicsConfiguration();
        DisplayMode displayMode = graphicsCfg.getDevice().getDisplayMode();
        resolution = new Dimension(displayMode.getWidth(), displayMode.getHeight());
    }
} public class GUIController implements Observer {

    /**
     * The panel containing the original image, where the segmentation/labeling
     * is happening.
     */
    protected DrawingPanel dPImgToLabel;

    /**
     * The panel with the result of the segmentation based on the inputed pixel
     * list.
     */
    protected ResultPanel dPImgResult;

    /**
     * A thread running the play video function.
     */
    protected Thread videoThread;

    /**
     * The list of objects which were segmented.
     */
    protected List<Objects> objectList;

    /**
     * The temporary list of objects which were segmented. It is intended to be
     * used for copy-paste of objects.
     */
    protected List<Objects> tempObjectList;

    /**
     * The object being currently segmented.
     */
    private Objects currentObject;

    /**
     * The information regarding the frame currently displayed.
     */
    private final FrameInfo currentFrameInfo;

    /**
     * The crop object keeping the position of the crop in the image, the
     * scribble map and the object map.
     */
    private CropObject cropObj;

    /**
     * Makes the marked methods observable. It is part of the mechanism to
     * notify the frame on top about changes.
     */
    protected final NotifyObservers observable = new NotifyObservers();

    /**
     * An instance of the window displaying the crop to be labeled.
     */
    private CropEdit cropWindow;
    private CropWindowConfig cropWindowCfg;

    /**
     * Display the segmented box and the attributes to be chosen for the object.
     */
    private EditWindow editWindow;

    /**
     * User preferences related to the application.
     */
    protected final UserPreferences userPrefs;

    /**
     * Encapsulates the list of object attributes: type, class, value.
     */
    protected CustomTreeNode objectAttributes;

    /**
     * Encapsulates the list of frame attributes: illumination, weather etc.
     */
    protected LAttributesFrame frameAttributes;

    /**
     * The default value of the object id before being saved and receiving an
     * object id.
     */
    protected static final int DEFAULT_OBJECT_ID = 0;

    /**
     * The default color of a new object (it is important especially for the
     * scribble mode).
     */
    private static final Color DEFAULT_OBJECT_COLOR = new Color(108, 221, 41);

    /**
     * The list of colors which were used for the segmented objects in the
     * current frame. It helps to prevent the segmentation of two objects with
     * the same color.
     */
    private List<Color> objColorsList = new ArrayList<>();

    /**
     * logger instance
     */
    private final Logger log = LoggerFactory.getLogger(GUIController.class);

    /**
     * The amount of space available on the GUi for drawing the image to be
     * labeled.
     */
    protected Dimension availableDrawSize;

    /**
     * Shows if a scribble object exists and helps at the display of the
     * interface (some fields should be displayed just if an scribble object was
     * created).
     */
    private boolean existsScribbleObj;

    /**
     * The list of files found in the directory structure of the selected folder
     * (the selected folder or the folder containing the selected file)
     */
    private List<String> fileList;

    /**
     * The name of the selected file.
     */
    private String chosenFileName;

    /**
     * The path to the chosen directory or the path to directory containing the
     * chosen file.
     */
    private String chosenPath;

    /**
     * The index of the current frame.
     */
    private long currFrameNo;

    /**
     * The image representing the current frame.
     */
    private BufferedImage currentFrame;

    /**
     * Defines how the video shall be played: 0 = forward playing; 1 = reverse
     * playing.
     */
    private int playMode;

    /**
     * True - the image shall be flipped vertically; false - the image shall be
     * as it is loaded from the file, without any processing.
     */
    private boolean flipVertically;

    /**
     * True - the image shall be mirrored; false - the image shall be as it is
     * loaded from the file, without any processing.
     */
    private boolean mirror;

    /**
     * A runnable running the play images function. The play of the images shall
     * run on a separate thread and the user has to be able to pause it from the
     * gui.
     */
    private PlayImagesRunnable imagesRunnable;

    /**
     * The data manager which is handling the writing and read to/from the data
     * files.
     */
    private final JSONDataManager jsonDataManag = new JSONDataManager();

    /**
     * Instantiates a new Gui controller.
     *
     * @param userPreferences user preferences regarding application
     * configuration
     * @param currentFrameInfo info regarding the current frame: annotations
     * regarding the scene
     */
    public GUIController(UserPreferences userPreferences, FrameInfo currentFrameInfo) {
        this.userPrefs = userPreferences;
        this.currentFrameInfo = currentFrameInfo;

        cropWindowCfg = new CropWindowConfig();
        cropWindowCfg.setMergeBKG(userPrefs.isMergeBKG());

        // init the file list to be able to add data inside
        fileList = new ArrayList<>();

        // get the object attributes from the server to be able to display them in the preview window
        objectAttributes = loadObjectAttributes();
    }

    /**
     * Load the attributes of the object from a file.
     *
     * @return the object attributes to be displayed on the interface, as an
     * array list of strings.
     */
    private CustomTreeNode loadObjectAttributes() {
        if (new File(Constants.OBJECT_ATTRIBUTES_PATH).exists()) {
            try (FileInputStream fin = new FileInputStream(Constants.OBJECT_ATTRIBUTES_PATH);
                    ObjectInputStream ois = new ObjectInputStream(fin)) {

                // read the user preferences object
                objectAttributes = (CustomTreeNode) ois.readObject();
            } catch (IOException | ClassNotFoundException ex) {
                String msg = "Read the attributes from the file error";
                log.error(msg);
                log.debug("{} {}", msg, ex);
            }
        }
        return objectAttributes;
    }

    /**
     * Load the selected image for labeling, create and show the panel.
     *
     * @param drawType - the type of drawing used on the panel: bbox, scribble,
     * crop etc.
     */
    public void loadImageOnPanel(DrawConstants.DrawType drawType) {

        // set the name of the file to be labeled
        String fileNameToLabel = getFileToLabelName();

        // load the first frame to be displayed of the data sequence (it can have any index, depending from where the user wants to start)
        BufferedImage firstFrame = getFirstFrame();

        // set the play mode, mirror and flip options
        if (userPrefs.isPlayBackward()) {
            firstFrame = changePlayMode(getPlayMode(userPrefs.isPlayBackward()));
        }

        if (userPrefs.isFlipVertically()) {
            firstFrame = changeFlipVerticallyImage(userPrefs.isFlipVertically());
        }

        if (userPrefs.isMirrorImage()) {
            firstFrame = changeMirrorImage(userPrefs.isMirrorImage());
        }

        // compute the optimal size of the drawing panel (to fit the screen resolution)
        Dimension drawPanelRes = PanelResolution.computeOptimalPanelSize(new Dimension(firstFrame.getWidth(), firstFrame.getHeight()), availableDrawSize);

        // Load the file to be labeled in the panel and show it         
        dPImgToLabel = new DrawingPanel(firstFrame, drawPanelRes, drawType);

        // set the drawing panels characteristics
        dPImgToLabel.setBackground(new java.awt.Color(255, 255, 255));
        dPImgToLabel.setLayout(new java.awt.GridBagLayout());

        // set the user preferences for other fields (set the option regarding the display of the scribbles, alpha etc.)
        refreshUserConfigs();

        // add observer to be notified when there are changes on the panel
        dPImgToLabel.addObserver(this);

        String outputFolder = "GT" + File.separatorChar + Utils.getFileName(fileNameToLabel, false) + File.separatorChar;
        Utils.createFolderPath(outputFolder);

        log.info("Loaded the video: {}", fileNameToLabel);

        // empty the obj list and load the objects from the ground truth storage
        loadGroundTruthData();
    }

    /**
     * Get the path to the name of the file being labeled.
     *
     * @return a string representing the path to the file being labeled
     */
    public String getPathToFileName() {
        return userPrefs.getLastDirectory();
    }

    /**
     * Get the name of the file being labeled.
     *
     * @return a string representing the name of the file being labeled
     */
    public String getFileToLabelName() {
        return chosenFileName;
    }

    /**
     * Get the image representing the first frame of the data to be labeled.
     *
     * @return an image with the first frame of the data to be labeled
     */
    protected BufferedImage getFirstFrame() {
        return getChosenFrameImage();
    }

    /**
     * Create a new bounding box object and open the bounding box segmentation
     * window.
     */
    private void createNewBoundingBox() {
        // a notification saying: the mouse was released while in bounding box labeling mode

        // create a new current object of bounding box type
        initCurrentObject();

        // set the start time of the labeling of the current object
        currentObject.newLabelingDuration().setStartTime(dPImgToLabel.getMousePressedTime());

        // get the position from the drawing panel
        Rectangle curBBoxImage = dPImgToLabel.getCurBBoxImageCoords();

        currentObject.setOuterBBox(curBBoxImage);

        if ((currentObject.getOuterBBox().width > 0) && (currentObject.getOuterBBox().height > 0)) {

            editWindow = new BoxEdit(null,
                    dPImgToLabel.getOrigImg(),
                    currentObject,
                    objectAttributes,
                    ObservedActions.Action.SAVE_BOUNDING_BOX,
                    objColorsList,
                    userPrefs);

            editWindow.addObserver(this);

            editWindow.removeOtherKeyEventDispatcher();

            editWindow.setLocation(Utils.winLocRelativeToMouse(editWindow.getSize()));

            editWindow.setVisible(true);
        }
    }

    /**
     * Create a new crop based on the segmented peace of image. Open the crop
     * segmentation window and add the new crop to the scribble object, if the
     * user wants to save it.
     */
    private void createNewCrop() {
        if ((currentObject == null) || (!(currentObject instanceof ObjectScribble))) {
            return;
        }

        // a notification saying that a new crop was drawn and it has to be displayed
        cropObj = new CropObject();

        cropObj.setPositionOrig(dPImgToLabel.getCurBBoxImageCoords());

        // set the color for the scribbles of the object
        cropWindowCfg.setObjectColor(currentObject.getColor());

        // set object id for display purposes in the crop window
        cropWindowCfg.setObjectId(currentObject.getObjectId());

        if ((cropObj.getPositionOrig().getWidth() > 0) && (cropObj.getPositionOrig().getHeight() > 0)) {
            cropWindow = new gui.editobject.CropEdit(null,
                    dPImgToLabel.getOrigImg(),
                    cropObj,
                    cropWindowCfg,
                    getObjAttribTree(objectAttributes, userPrefs.isCheckObjectAttributes()),
                    ObservedActions.Action.ADD_CROP_TO_OBJECT,
                    objColorsList,
                    userPrefs,
                    currentObject.getUserPreference());

            cropWindow.addObserver(this);
            cropWindow.removeOtherKeyEventDispatcher();

            cropWindow.setLocation(Utils.winLocRelativeToMouse(cropWindow.getSize()));

            // set the obj attributes as they were selected earlier (if they were selected)
            if ((currentObject.getObjectType() != null)
                    && (currentObject.getObjectClass() != null)
                    && (currentObject.getObjectValue() != null)) {
                cropWindow.setTypeClassValOcc(currentObject.getObjectType(),
                        currentObject.getObjectClass(),
                        currentObject.getObjectValue(),
                        currentObject.getOccluded());
            }

            cropWindow.setVisible(true);
        }
    }

    /**
     * Get the selected object and open it in edit mode.
     */
    public void editObject() {
        if (dPImgToLabel != null) {
            // get the id of the crop which was clicked
            DisplayBBox selectedBox = dPImgToLabel.getSelectedBox();

            if (selectedBox != null) {
                for (Objects obj : objectList) {
                    if (obj.contains(selectedBox.getPanelBox(), dPImgToLabel.getResize())) {
                        currentObject = obj;

                        // start counting the times for edit
                        currentObject.newLabelingDuration().start();

                        if (obj instanceof ObjectBBox) {
                            // open bounding box edit mode
                            editBox();
                            return;
                        } else if (obj instanceof ObjectPolygon) {
                            editPolygon();
                        } else if (obj instanceof ObjectScribble) {
                            Rectangle objBoxPanel = dPImgToLabel.getResize().originalToResized(obj.getOuterBBox());
                            if (objBoxPanel.equals(selectedBox.getPanelBox()) && (((ObjectScribble) obj).getCrop(selectedBox.getPanelBox(), dPImgToLabel.getResize()) == null)) {
                                // open object scribble edit mode
                                editObjectScribble();
                                return;
                            } else {
                                // crop selected - open crop edit mode
                                editCrop(selectedBox.getPanelBox());
                                return;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Edit the whole object; open it in a new window.
     */
    private void editObjectScribble() {
        editWindow = new SemanticObjEdit(null,
                dPImgToLabel.getOrigImg(),
                currentObject,
                getObjAttribTree(objectAttributes, userPrefs.isCheckObjectAttributes()),
                ObservedActions.Action.UPDATE_OBJECT_SCRIBBLE,
                objColorsList,
                userPrefs);

        editWindow.addObserver(this);
        editWindow.removeOtherKeyEventDispatcher();

        editWindow.setLocationRelativeTo(dPImgToLabel);

        if ((currentObject.getObjectType() != null)
                && (currentObject.getObjectClass() != null)
                && (currentObject.getObjectValue() != null)) {
            editWindow.setTypeClassValOcc(currentObject.getObjectType(),
                    currentObject.getObjectClass(),
                    currentObject.getObjectValue(),
                    currentObject.getOccluded());
        }

        editWindow.setVisible(true);
    }

    /**
     * Edit the selected bounding box.
     */
    private void editBox() {
        editWindow = new BoxEdit(null,
                dPImgToLabel.getOrigImg(),
                currentObject,
                getObjAttribTree(objectAttributes, userPrefs.isCheckObjectAttributes()),
                ObservedActions.Action.UPDATE_BOUNDING_BOX,
                objColorsList,
                userPrefs);

        editWindow.addObserver(this);
        editWindow.removeOtherKeyEventDispatcher();

        editWindow.setLocationRelativeTo(dPImgToLabel);

        editWindow.setTypeClassValOcc(currentObject.getObjectType(),
                currentObject.getObjectClass(),
                currentObject.getObjectValue(),
                currentObject.getOccluded());

        editWindow.setVisible(true);
    }

    /**
     * Edit the selected bounding polygon.
     */
    private void editPolygon() {
        editWindow = new PolygonObjEdit(null,
                dPImgToLabel.getOrigImg(),
                currentObject,
                getObjAttribTree(objectAttributes, userPrefs.isCheckObjectAttributes()),
                ObservedActions.Action.UPDATE_POLYGON,
                objColorsList,
                userPrefs);

        editWindow.addObserver(this);
        editWindow.removeOtherKeyEventDispatcher();

        editWindow.setLocationRelativeTo(dPImgToLabel);

        editWindow.setTypeClassValOcc(currentObject.getObjectType(),
                currentObject.getObjectClass(),
                currentObject.getObjectValue(),
                currentObject.getOccluded());

        editWindow.setVisible(true);
    }

    /**
     * Get the selected crop and open it in a normal crop panel.
     *
     * @param coordPanelBox - the panel coordinates of the crop
     */
    private void editCrop(Rectangle coordPanelBox) {
        cropObj = ((ObjectScribble) currentObject).getCrop(coordPanelBox, dPImgToLabel.getResize());

        if (cropObj == null) {
            return;
        }

        // get scribble list
        List<ScribbleInfo> scribbleList = cropObj.getScribbleList();

        if ((cropObj.getPositionOrig().width > 0) && (cropObj.getPositionOrig().height > 0)) {
            // set object color
            cropWindowCfg.setObjectColor(currentObject.getColor());

            // set object id for display purposes in the crop window
            cropWindowCfg.setObjectId(currentObject.getObjectId());

            // create new window
            cropWindow = new gui.editobject.CropEdit(null,
                    dPImgToLabel.getOrigImg(),
                    cropObj,
                    cropWindowCfg,
                    getObjAttribTree(objectAttributes, userPrefs.isCheckObjectAttributes()),
                    ObservedActions.Action.UPDATE_CROP_OF_OBJECT,
                    objColorsList,
                    userPrefs,
                    currentObject.getUserPreference());

            cropWindow.addObserver(this);
            cropWindow.removeOtherKeyEventDispatcher();

            cropWindow.setLocationRelativeTo(dPImgToLabel);

            // set the obj attributes as they were selected earlier (if they were selected)
            if ((currentObject.getObjectType() != null)
                    && (currentObject.getObjectClass() != null)
                    && (currentObject.getObjectValue() != null)) {
                cropWindow.setTypeClassValOcc(currentObject.getObjectType(),
                        currentObject.getObjectClass(),
                        currentObject.getObjectValue(),
                        currentObject.getOccluded());
            }

            if (scribbleList != null) {
                cropWindow.setScribbleList(scribbleList);

                // run the algorithm in order to display the results asap
                cropWindow.runMatting(Constants.RUN_MATT_ORIG_IMG);
            }

            cropWindow.setVisible(true);
        }
    }

    /**
     * Add the new crop to the object as being part of it.
     */
    private void addNewCropToObject() {
        // save the config of the window, to be able to open it in the same state as it was left
        cropWindowCfg = cropWindow.getCropConfig();

        // get the scribble list and the object map
        cropObj.setScribbleList(cropWindow.getScribbleList());
        cropObj.setObjectMap(cropWindow.getObjectMap());

        // update the current object with the saved work
        if (currentObject instanceof ObjectScribble) {
            ((ObjectScribble) currentObject).addToCropList(cropObj);
        }

        // update the color of the scribbles to match the color selected in the crop window
        currentObject.setColor(cropWindow.getScribbleColor());

        // update the color of the image result panel for current color
        dPImgResult.setObjColor(cropWindow.getScribbleColor());

        // the object has a new crop, the outer bounding box can be computed
        currentObject.computeOuterBBoxCurObj();

        // get the object attributes
        currentObject.setObjectType(cropWindow.getObjType());
        currentObject.setObjectClass(cropWindow.getObjClass());
        currentObject.setObjectValue(cropWindow.getObjValue());
        currentObject.setOccluded(cropWindow.getObjOccluded());

        // merge the result and refresh the panel the result image
        dPImgResult.mergeCrop(cropWindow.getObjectMap(), cropObj.getPositionOrig(), currentObject.getObjectId(), cropWindowCfg.isMergeBKG());
        dPImgResult.updateResultImg(objectList);

        // refresh the data to be displayed        
        refreshDisplayList();
    }

    /**
     * Update one crop of the list of crops of the object
     */
    private void updateCropOfObject() {
        // save the edit time of the crop
        currentObject.getCurrentLabelingDuration().stop("crop edit");

        // save the config of the window, to be able to open it in the same state as it was left
        cropWindowCfg = cropWindow.getCropConfig();

        // get the scribble list and the object map
        cropObj.setScribbleList(cropWindow.getScribbleList());
        cropObj.setObjectMap(cropWindow.getObjectMap());

        // recompute the outer box
        currentObject.computeOuterBBoxCurObj();

        // get the object attributes
        currentObject.setObjectType(cropWindow.getObjType());
        currentObject.setObjectClass(cropWindow.getObjClass());
        currentObject.setObjectValue(cropWindow.getObjValue());
        currentObject.setOccluded(cropWindow.getObjOccluded());

        // update the object color      
        currentObject.setColor(cropWindowCfg.getObjectColor());

        // update the color of the image result panel for current color
        dPImgResult.setObjColor(cropWindow.getScribbleColor());

        // merge the result and refresh the panel the result image
        dPImgResult.mergeCrop(cropWindow.getObjectMap(), cropObj.getPositionOrig(), currentObject.getObjectId(), cropWindowCfg.isMergeBKG());
        dPImgResult.updateResultImg(objectList);

        // the color of the object might have changed, update it
        reinitObjectColorsList();

        // refresh the data to be diplayed
        refreshDisplayList();

        // notify that the object might be changed, therefore reload the objects
        observable.notifyObservers(ObservedActions.Action.REFRESH_OBJ_LIST_PANEL);

        // set the labeling source: human or machine
        currentObject.setSegmentationSource(ConstantsLabeling.LABEL_SOURCE_MANUAL);
    }

    /**
     * Saves the segmented bounding box in the objects list.
     */
    private void saveBoundingBoxObj() {
        // set the segmentation type used for labeling the object
        currentObject.setSegmentationType(ConstantsLabeling.LABEL_2D_BOUNDING_BOX);

        // save the object in the objects list 
        addObjToObjectList();

        // display the objects
        setBBoxToDisplay();
    }

    /**
     * Updates the selected bounding box with the data which was changed.
     */
    private void updateBBoxObj() {
        // save the edit time of the bbox
        currentObject.getCurrentLabelingDuration().stop("bounding box edit");

        // notify that the object might be changed, therefore reload the objects
        observable.notifyObservers(ObservedActions.Action.REFRESH_OBJ_LIST_PANEL);

        // the color of the object might have changed, update it
        reinitObjectColorsList();

        // refresh the objects displayed (for color changes)
        refreshDisplayList();
    }

    /**
     * Updates the scribble object with the new data.
     */
    private void updateObjScribble() {
        // save the edit time of the object
        currentObject.getCurrentLabelingDuration().stop("scribble object edit");

        // update the color of the image result panel for current color
        dPImgResult.setObjColor(editWindow.getObjectColor());

        // merge the result and refresh the panel the result image
        dPImgResult.mergeCrop(((ObjectScribble) currentObject).getObjectMap(), currentObject.getOuterBBox(), currentObject.getObjectId(), false);
        dPImgResult.updateResultImg(objectList);

        // notify that the object might be changed, therefore reload the objects
        observable.notifyObservers(ObservedActions.Action.REFRESH_OBJ_LIST_PANEL);

        // the color of the object might have changed, update it
        reinitObjectColorsList();

        // refresh the objects displayed (for color changes)
        refreshDisplayList();
    }

    /**
     * Updates the polygon object with the new data.
     */
    private void updateObjPolygon() {
        // save the edit time of the object
        currentObject.getCurrentLabelingDuration().stop("polygon object edit");

        // recompute the outer box
        currentObject.computeOuterBBoxCurObj();

        // notify that the object might be changed, therefore reload the objects
        observable.notifyObservers(ObservedActions.Action.REFRESH_OBJ_LIST_PANEL);

        // the color of the object might have changed, update it
        reinitObjectColorsList();

        // refresh the objects displayed (for color changes)
        refreshDisplayList();
    }

    /**
     * Load the segmented result image to a visualisation panel.
     *
     * @param resultImg - the image to be displayed
     */
    public void loadImgResult(BufferedImage resultImg) {

        // load the viewer of the result
        dPImgResult = new ResultPanel(resultImg, new Dimension(Constants.RESULT_PANEL_WIDTH, Constants.RESULT_PANEL_HEIGHT), Color.red);
        dPImgResult.updateResultImg();
        dPImgResult.setBackground(new java.awt.Color(255, 255, 255));
    }

    /**
     * Move the object with the specified offsets.
     *
     * @param xOffset - the amount of pixels to move on the Ox axis
     * @param yOffset - the amount of pixels to move on the Oy axis
     */
    public void moveSelection(int xOffset, int yOffset) {
        // get the box which was selected
        DisplayBBox selectedBox = dPImgToLabel.getSelectedBox();

        if (selectedBox != null) {
            for (Objects obj : objectList) {
                if (obj.contains(selectedBox.getPanelBox(), dPImgToLabel.getResize())) {
                    obj.move(xOffset, yOffset, selectedBox.getPanelBox(), dPImgToLabel.getResize(), dPImgToLabel.getOrigImgSize());

                    // remove the object from the object map if the obj is scribble because else it will distroy the object map
                    cancelObjectMap(obj, dPImgToLabel.getResize().resizedToOriginal(selectedBox.getPanelBox()));

                }
            }

            // refresh the data to be diplayed
            refreshDisplayList();
        }
    }

    /**
     * Move the object at the position of the mouse.
     */
    public void moveDragSelection() {
        // get the box which was selected
        DisplayBBox selectedBox = dPImgToLabel.getSelectedBox();

        if (selectedBox != null) {
            for (Objects obj : objectList) {
                if (obj.contains(selectedBox.getPanelBox(), dPImgToLabel.getResize())) {
                    // transform the offset from panel to image coordinates
                    Point mouseOffsetPanel = dPImgToLabel.getMouseMovementOffsetPanel();
                    Point mouseOffsetImg = dPImgToLabel.getResize().resizedToOriginal(mouseOffsetPanel);

                    // move the object with the computed offset
                    obj.move(mouseOffsetImg.x, mouseOffsetImg.y, selectedBox.getPanelBox(), dPImgToLabel.getResize(), dPImgToLabel.getOrigImgSize());

                    // remove the object from the object map if the obj is scribble because else it will distroy the object map
                    cancelObjectMap(obj, dPImgToLabel.getResize().resizedToOriginal(selectedBox.getPanelBox()));
                }
            }

            // refresh the data to be diplayed
            refreshDisplayList();
        }
    }

    /**
     * Resize the selection with the specified amount of pixels on each
     * direction.
     *
     * @param left - the amount of pixels to be added on the left side
     * @param top - the amount of pixels to be added on the top part
     * @param right - the amount of pixels to be added on the right side
     * @param bottom - the amount of pixels to be added on the bottom part
     */
    public void changeSizeSelection(int left, int top, int right, int bottom) {
        // get the box which was selected
        DisplayBBox selectedBox = dPImgToLabel.getSelectedBox();

        if (selectedBox != null) {
            for (Objects obj : objectList) {
                if (obj.contains(selectedBox.getPanelBox(), dPImgToLabel.getResize())) {
                    obj.changeSize(left, top, right, bottom, selectedBox.getPanelBox(), dPImgToLabel.getResize(), dPImgToLabel.getOrigImgSize());

                    // remove the object from the object map if the obj is scribble because else it will distroy the object map
                    cancelObjectMap(obj, dPImgToLabel.getResize().resizedToOriginal(selectedBox.getPanelBox()));
                }
            }

            // refresh the data to be diplayed
            refreshDisplayList();
        }
    }

    /**
     * Compute the outer bounding box and send the new list of scribbles and
     * bounding boxes to the display module.
     */
    public void refreshDisplayList() {
        // send the crop and the scribbles to be drawn on the drawing panel
        setBBoxToDisplay();
        setScribblesToDisplay();
        setPolygonToDisplay();

        // notify to enable some options, based on the existing list of objects
        observable.notifyObservers(ObservedActions.Action.REFRESH_PANEL_OPTIONS);
    }

    /**
     * Do not select any crop / Remove selection.
     */
    public void resetSelectedObject() {
        dPImgToLabel.resetIdSelectedBox();
    }

    /**
     * Remove the selection from the list.
     */
    public void removeSelection() {
        // get the box which was selected
        DisplayBBox selectedBox = dPImgToLabel.getSelectedBox();
        if (selectedBox != null) {

            for (Iterator<Objects> it = objectList.iterator(); it.hasNext();) {
                Objects obj = it.next();
                if (obj.contains(selectedBox.getPanelBox(), dPImgToLabel.getResize())) {

                    //remove a box from the object
                    if (obj.remove(selectedBox.getPanelBox(), dPImgToLabel.getResize())) {
                        obj.computeOuterBBoxCurObj();

                        // remove the crop from the object map if the obj is scribble
                        cancelObjectMap(obj, dPImgToLabel.getResize().resizedToOriginal(selectedBox.getPanelBox()));

                    } else {
                        // remove the object from the object map if the obj is scribble
                        cancelObjectMap(obj, obj.getOuterBBox());

                        // remove the object from the object list
                        it.remove();

                        // update object panel
                        observable.notifyObservers(ObservedActions.Action.REFRESH_OBJ_LIST_PANEL);
                    }
                }
            }

            // disable the selection and erase the current object
            resetSelectedObject();
            eraseCurrentObject();

            // refresh the data to be diplayed
            refreshDisplayList();
        }
    }

    /**
     * Cancel the creation of a new object.
     *
     * @return true if the object was cancel; false if it was not cancel due to
     * another window active
     */
    public boolean cancelCurrentObject() {
        if (cropWindow == null || !cropWindow.isActive()) {

            eraseCurrentObject();

            // send the crop and the scribbles to be drawn on the drawing panel
            setBBoxToDisplay();
            setScribblesToDisplay();
            setPolygonToDisplay();
            return true;
        }
        return false;
    }

    /**
     * Make the current object null.
     */
    public void eraseCurrentObject() {
        currentObject = null;
    }

    /**
     * Set the option of displaying; refresh the configuration.
     */
    public void refreshUserConfigs() {
        dPImgToLabel.setDrawScribbleHistory(userPrefs.isShowScribbles());
        dPImgToLabel.setDrawAlphaObj(userPrefs.isShowObjHighlight());
        dPImgToLabel.setObjAlpha(userPrefs.getObjAlphaVal());
        currentFrameInfo.setSaveFrameObjMap(userPrefs.isSaveFrameObjMap());
    }

    /**
     * Create a new thread which plays a video and start it.
     *
     * @param gui - the gui, which has to be notified when changes happen
     */
    public void playVideo(GUILabelingTool gui) {
        imagesRunnable = new PlayImagesRunnable(this);

        imagesRunnable.addObserver(gui);  // the event has to be reported in the gui not in the controller

        videoThread = new Thread(imagesRunnable);
        videoThread.setName("Play Images Thread");
        videoThread.start();
    }

    /**
     * Play or pause the thread, depending on the current state. Invert the
     * current state.
     */
    public void pausePlayVideo() {
        if (imagesRunnable != null) {
            // if the thread exists and is playing, pause it
            imagesRunnable.setRun(!imagesRunnable.isRun());
        }
    }

    /**
     * Save all the information in the ground truth storage.
     */
    public void saveDataAsGroundTruth() {
        jsonDataManag.initWriteFile(getGTFilePath());

        // send the objects to be saved, one by one
        for (Objects obj : objectList) {
            jsonDataManag.addObject(obj);
        }
        jsonDataManag.addFrame(getCurrentFrameInfo(), dPImgResult.getObjMap());

        jsonDataManag.writeFile();
    }

    /**
     * Save all the information in the communication object
     *
     * @param obj Pixie's current object information
     */
    public void sendLabel(Objects obj) {
        // assign an id to the object when the id is not already set
        if (obj.getObjectId() == DEFAULT_OBJECT_ID) {
            long maxId = 0L;

            // the new id has to be greater than the existing max id
            for (Objects object : objectList) {
                if (maxId < object.getObjectId()) {
                    maxId = object.getObjectId();
                }
            }
            obj.setObjectId(maxId + 1L);
        }
    }

    /**
     * Chooses which actions to happen based on the feedback from the observers.
     */
    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof ObservedActions.Action) {
            ObservedActions.Action type = ((ObservedActions.Action) arg);

            switch (type) {

                case OPEN_CROP_SEGMENTATION:
                    // create a new crop and add it to the object
                    createNewCrop();
                    break;

                case OPEN_BBOX_SEGMENTATION:
                    // create a new object of type bounding box
                    createNewBoundingBox();
                    break;

                case CTRL_MOUSE_EVENT:
                    ctrlMouseEdit();
                    break;

                case ADD_CROP_TO_OBJECT:
                    addNewCropToObject();
                    break;
                case UPDATE_CROP_OF_OBJECT:
                    updateCropOfObject();

                    // send the object to server
                    sendLabel(currentObject);
                    break;

                case EDIT_OBJECT_ACTION:
                    editObject();
                    break;

                case SAVE_BOUNDING_BOX:
                    saveBoundingBoxObj();
                    break;

                case UPDATE_BOUNDING_BOX:
                    updateBBoxObj();

                    // send the object to server
                    sendLabel(currentObject);
                    break;

                case UPDATE_OBJECT_SCRIBBLE:
                    updateObjScribble();

                    // send the object to server
                    sendLabel(currentObject);
                    break;

                case UPDATE_POLYGON:
                    updateObjPolygon();

                    // send the object to server
                    sendLabel(currentObject);
                    break;

                case REMOVE_SELECTED_OBJECT:
                    removeSelection();
                    break;

                case SELECTED_OBJECT_MOVED:
                    refreshDisplayList();
                    break;

                case MOVE_OBJECT_DRAG:
                    moveDragSelection();
                    break;

                case SAVE_LABEL:
                    // send the object to server
                    sendLabel(currentObject);
                    break;

                // redirect the notification which cannot be treated in the controller
                case ADD_GUI_KEY_EVENT_DISPATCHER:
                    // the crop/bbox window was closed and the preview shape and size have to be removed
                    dPImgToLabel.setShowGuideShape(false);
                case REMOVE_GUI_KEY_EVENT_DISPATCHER:
                case HIGHLIGHT_OBJECT:
                case OPEN_POLYGON_SEGMENTATION:
                case DISPLAY_EDIT_ATTRIBUTES_WIN:
                    observable.notifyObservers(type);
                    break;

                case CANCEL_CURRENT_OBJECT:
                    if ((dPImgToLabel.getDrawType() != DrawConstants.DrawType.EDIT_MODE)) {
                        cancelCurrentObject();
                    }

                    // refresh the display list because some things might have changed
                    refreshDisplayList();
                    break;

                case RELOAD_ATTRIBUTES:
                    reloadAttributes();
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * Implement and edit mode based on the control key and mouse movement.
     *
     * Scribble: add a new crop to the selected object.
     *
     * Bounding box: replace existing box with the new one.
     */
    private void ctrlMouseEdit() {
        if (currentObject instanceof ObjectScribble) {
            // if the selected object is a scribble, add the new crop to the object
            createNewCrop();
        } else if (currentObject instanceof ObjectBBox) {
            // if the selected object is a bounding box, replace the current object with the new drawn object
            // get the position from the drawing panel
            Rectangle curBBoxImage = dPImgToLabel.getCurBBoxImageCoords();
            currentObject.setOuterBBox(curBBoxImage);

            // open the edit window and allow the user to refine the position
            editBox();
        }
    }

    /**
     * Allows another module to put an observer into the current module.
     *
     * @param o - the observer to be added
     */
    public void addObserver(Observer o) {
        observable.addObserver(o);
    }

    /**
     * Allows another module to erase an observer from the current module.
     *
     * @param o - the observer to be deleted
     */
    public void deleteObserver(Observer o) {
        observable.deleteObserver(o);
    }

    /**
     * Set the type of label which will be used for the frame.
     *
     * @param drawType - the type of drawing which will be used for labeling
     */
    public void setLabelingType(DrawConstants.DrawType drawType) {
        dPImgToLabel.setDrawType(drawType);
    }

    /**
     * Switch to the previous frame.
     */
    public void prevFrame() {
        // display the previous frame
        dPImgToLabel.newFrame(getPrevFrame(), availableDrawSize);

        // empty the obj list and load the objects from the ground truth
        loadGroundTruthData();
    }

    /**
     * Move to the next frame. Track objects
     */
    public void nextFrame() {
        BufferedImage prevFrame = new BufferedImage(dPImgToLabel.getOrigImg().getWidth(), dPImgToLabel.getOrigImg().getHeight(), dPImgToLabel.getOrigImg().getType());

        // copy current frame as prev frame
        Utils.copySrcIntoDstAt(dPImgToLabel.getOrigImg(), prevFrame);

        // display and move to the next frame
        dPImgToLabel.newFrame(getNextFrame(), availableDrawSize);

        // load the saved objects if there are some
        loadGroundTruth();

        /* update the gui with the new object coordinates */
        refreshDisplayList();
    }

    /**
     * Jump to the specified frame.
     *
     * @param frameNo - the frame number where it should jump
     */
    public void jumpToFrame(long frameNo) {
        if (isNotEndOfDataFile(frameNo)) {
            dPImgToLabel.newFrame(getJumpToFrame(frameNo), availableDrawSize);

            // empty the obj list and load the objects from the ground truth
            loadGroundTruthData();
        }
    }

    /**
     * Returns the image from the specified frame number without moving the
     * video pointer in the file. It is extracting a frame without affecting the
     * normal functionality of the frame grabber.
     *
     * @param frameNr the frame which shall retrieved
     * @return the image representing the specified frame number, without moving
     * the video pointer in the frame grabber
     */
    public BufferedImage getFrame(long frameNr) {
        // return the current frame as it is
        return currentFrame;
    }

    /**
     * The retrieving of the previous frame is depending on the application
     * mode, therefore it will be implemented differently for different
     * configuration.
     *
     * @return the image representing the previous frame to be displayed;
     * modifies the video pointer
     */
    protected BufferedImage getPrevFrame() {
        return getJumpToFrame(currFrameNo - 1);
    }

    /**
     * The retrieving of the next frame is depending on the application mode,
     * therefore it will be implemented differently for different configuration.
     *
     * @return the image representing the next frame to be displayed; modifies
     * the video pointer
     */
    public BufferedImage getNextFrame() {
        return getJumpToFrame(currFrameNo + 1);
    }

    /**
     * The retrieving of the jumpTo frame is depending on the application mode,
     * therefore it will be implemented differently for different configuration.
     *
     * @param jumpToNo the number of the frame to be jumping to
     * @return the image representing the frame with the specified number, from
     * the current video; modifies the video pointer
     */
    protected BufferedImage getJumpToFrame(long jumpToNo) {
        long frameNo = jumpToNo;

        if (playMode == Constants.PLAY_MODE_BACKWARD) {
            // compute the index of the frame
            frameNo = fileList.size() - jumpToNo;

            // make sure the index is in the wanted range (+1 is added due to the fact that the first frame is 1, not 0)
            if (isNotEndOfDataFile(frameNo + 1)) {
                // set the name of the chosen file and the number of the current frame
                chosenFileName = fileList.get((int) frameNo);
                currFrameNo = fileList.size() - frameNo;

                // load the wanted frame
                return getChosenFrameImage();
            } else {
                // the index is out of bounds, return the current frame as it is
                return currentFrame;
            }
        }

        // make sure the jump is done inside the file
        if (isNotEndOfDataFile(frameNo)) {
            // set the number of the current frame and the name of the chosen file
            currFrameNo = frameNo;
            chosenFileName = fileList.get((int) currFrameNo - 1);

            // load the wanted frame
            return getChosenFrameImage();
        } else {
            // the index is out of bounds, return the current frame as it is
            return currentFrame;
        }
    }

    /**
     * Checks input frame number is out of the data file.
     *
     * @param frameNo the frame number for which the end of file is checked
     * @return the boolean state for end of data file
     * <p>
     * true = end of data file
     * <p>
     * false = not end of data file
     * <p>
     * where data file can be a video, a folder of pictures etc.
     */
    public boolean isNotEndOfDataFile(long frameNo) {
        return ((frameNo > 0) && (frameNo <= fileList.size()));
    }

    /**
     * Return the number of frames of the video.
     *
     * @return - the total number of frames of the video
     */
    public long getNoFrames() {
        return fileList.size();
    }

    /**
     * Gets frame rate.
     *
     * @return the video's frame rate
     */
    public long getFrameRate() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    /**
     * Return the frame number.
     *
     * @return - the current frame number of the video
     */
    public long getFrameNo() {
        return currFrameNo;
    }

    /**
     * Return the play mode wanted by the user: forward or backward.
     *
     * @return the play mode: forward or backward
     */
    public int getPlayMode() {
        return playMode;
    }

    /**
     * Return the play mode wanted by the user: forward or backward.
     *
     * @param playBackward true if the player shall play backwards and false
     * otherwise
     *
     * @return the play mode: forward or backward
     */
    private int getPlayMode(boolean playBackward) {
        return (playBackward ? Constants.PLAY_MODE_BACKWARD : Constants.PLAY_MODE_FORWARD);
    }

    /**
     * Return the segmented image; the result of the segmentation.
     *
     * @return an image representing the result of the labeling
     */
    public BufferedImage getImageResult() {
        if (dPImgResult != null) {
            return dPImgResult.getWorkImg();
        }
        return null;
    }

    /**
     * Gets p img to label.
     *
     * @return the p img to label
     */
    public DrawingPanel getdPImgToLabel() {
        return dPImgToLabel;
    }

    /**
     * Gets p img result.
     *
     * @return the p img result
     */
    public ResultPanel getdPImgResult() {
        return dPImgResult;
    }

    /**
     * Init current object.
     */
    public void initCurrentObject() {
        if (null == dPImgToLabel.getDrawType()) {
            return;
        }

        switch (dPImgToLabel.getDrawType()) {
            case DRAW_CROP:
                this.currentObject = new ObjectScribble();
                break;

            case DRAW_BOUNDING_BOX:
                this.currentObject = new ObjectBBox();
                break;

            case DRAW_POLYGON:
                this.currentObject = new ObjectPolygon();
                break;

            default:
                break;
        }

        // assign a new object id
        this.currentObject.setObjectId(DEFAULT_OBJECT_ID);

        // assign a new color to the object
        this.currentObject.setColor(DEFAULT_OBJECT_COLOR);
    }

    /**
     * Start current object timer.
     */
    public void startCurrentObjectTimer() {
        // init the needed time for the segmentation of the object
        currentObject.newLabelingDuration().start();
    }

    /**
     * Sets the label type used for the segmentation of the current object.
     *
     * @param labelType - the type of label used for the segmentation of the
     * object: 2D/3D bounding box, scribbles
     */
    public void setSegmentationType(String labelType) {
        currentObject.setSegmentationType(labelType);
    }

    /**
     * Sets the label source used for the segmentation of the current object.
     *
     * @param labelSource - the source of the label used for the segmentation of
     * the object: manual, automatic etc.
     */
    public void setSegmentationSource(String labelSource) {
        currentObject.setSegmentationSource(labelSource);
    }

    /**
     * Add the current object to the list of objects.
     */
    public void addObjToObjectList() {
        // stop the timer of the current object; the segmentation is finished
        // init the needed time for the segmentation of the object
        currentObject.getCurrentLabelingDuration().stop("object segmentation");

        // check the object; make sure it is not empty
        if (currentObject.getOuterBBox() != null) {

            // send the object to server
            sendLabel(currentObject);

            // update the object id in the object map
            updateDefaultObjIdMap();

            Color currentColor = (currentObject.getColor() == DEFAULT_OBJECT_COLOR) ? Utils.getColorOfObjByID(currentObject.getObjectId()) : currentObject.getColor();

            // assign a new color to the object
            this.currentObject.setColor(currentColor);

            // set the color for the scribbles of the object
            cropWindowCfg.setObjectColor(currentColor);

            // update the color of the image result panel for current color
            dPImgResult.setObjColor(currentColor);

            /* update the drawing panel*/
            refreshDisplayList();

            // the object seems correct
            objectList.add(currentObject);

            // notify that a new object was added on the list and it has to be displayed in the list
            observable.notifyObservers(ObservedActions.Action.ADD_OBJECT_ON_PANEL);

            // add the used color to the list of used colors
            objColorsList.add(currentObject.getColor());

            // erase the object which was saved
            eraseCurrentObject();
        }
    }

    /**
     * Returns the current object; the one on which the user is working on.
     *
     * @return - the current object of the application
     */
    public Objects getCurrentObject() {
        return currentObject;
    }

    /**
     * Return the last saved object.
     *
     * @return - the last saved object
     */
    public Objects getLastSavedObj() {
        return objectList.get(objectList.size() - 1);
    }

    /**
     * Return the list of objects from the saved object list.
     *
     * @return - the list of objects available in the objects list
     */
    public List<Objects> getObjectsList() {
        return objectList;
    }

    /**
     * Returns the number of objects saved in the objects list.
     *
     * @return - the size of the object list
     */
    public int getObjectsListSize() {
        if (objectList != null) {
            return objectList.size();
        }
        return 0;   // the list was not even initialized yet
    }

    /**
     * Reinit the list of used colors, to make sure each object has its own
     * color.
     */
    public void reinitObjectColorsList() {
        objColorsList = new ArrayList<>();

        for (Objects objects : objectList) {
            objColorsList.add(objects.getColor());
        }
    }

    /**
     * Get the list of bounding boxes and the list of scribbles from the objects
     * and display it on the drawing panel.
     */
    public void setBBoxToDisplay() {
        List<DisplayBBox> positions = new ArrayList<>();

        // for scribble segmentation the current object parts have to be displayed before the save of the object
        if ((currentObject != null) && (dPImgToLabel.getDrawType() != DrawConstants.DrawType.EDIT_MODE)) {
            // add the current object which is in progress
            showCrops(currentObject, positions);

            // add the outer bounding box containing all the objects
            if (currentObject.getOuterBBox() != null) {
                positions.add(new DisplayBBox(dPImgToLabel.getResize().originalToResized(currentObject.getOuterBBox()),
                        currentObject.getColor(), Long.toString(currentObject.getObjectId()), false));
            }
        }

        // if the user just wants to see the current object, display it and return
        if (userPrefs.isShowJustCurrentObj()) {
            dPImgToLabel.setBBoxList(positions);
            return;
        }

        // the user wants to see all the objects
        // add the saved objects
        for (Objects obj : objectList) {
            // TODO TEMP CODE - to be deleted later
            showPredictions(obj, positions);

            // display the crops
            showCrops(obj, positions);

            // add the outer bounding box containing all the objects
            positions.add(new DisplayBBox(dPImgToLabel.getResize().originalToResized(obj.getOuterBBox()),
                    obj.getColor(), Long.toString(obj.getObjectId()), false));

        }

        dPImgToLabel.setBBoxList(positions);
    }

    /**
     * Show the crops of the scribble object.
     *
     * @param obj the object for which the crops shall be displayed (if the
     * object is an instance of the scribble object)
     * @param positions the list of boxes to be displayed on the screen
     */
    private void showCrops(Objects obj, List<DisplayBBox> positions) {
        if ((obj instanceof ObjectScribble) && (userPrefs.isShowCrops())) {
            for (CropObject cropObj : ((ObjectScribble) obj).getCropList()) {
                // add bounding boxes
                Rectangle cropPosPanel = dPImgToLabel.getResize().originalToResized(cropObj.getPositionOrig());
                positions.add(new DisplayBBox(cropPosPanel, obj.getColor(), true));
            }
        }
    }

    /**
     * Show the predictions for the given object.
     *
     * @param obj the object for which the predictions shall be displayed
     * @param positions the list of boxes to be displayed on the screen
     */
    private void showPredictions(Objects obj, List<DisplayBBox> positions) {

        // add the predicted HOG box
        if (obj.getHogPredictedBBox() != null) {
            positions.add(new DisplayBBox(dPImgToLabel.getResize().originalToResized(obj.getHogPredictedBBox()),
                    obj.getColor().darker().darker(), Long.toString(obj.getObjectId()), true));
        }

        // add the predicted SVM with HOG box
        if (obj.getSvmPredictedBBox() != null) {
            positions.add(new DisplayBBox(dPImgToLabel.getResize().originalToResized(obj.getSvmPredictedBBox()),
                    Color.cyan.darker(), Long.toString(obj.getObjectId()), true));
        }
    }

    /**
     * Select the object with the given id and return its type.
     *
     * @param objId - the id of the selected object
     * @return - the type of object which was selected
     */
    public String selectObject(long objId) {
        for (Objects obj : objectList) {
            if (obj.getObjectId() == objId) {
                // set the selected object as the current object
                currentObject = obj;

                // refresh the display to not loose the show current obj functionality
                refreshDisplayList();

                // select the object
                dPImgToLabel.setSelectedBox(obj.getOuterBBox());

                // return the object type
                if (obj instanceof ObjectScribble) {
                    return ConstantsLabeling.LABEL_SCRIBBLE;
                } else if (obj instanceof ObjectBBox) {
                    return ConstantsLabeling.LABEL_2D_BOUNDING_BOX;
                } else if (obj instanceof ObjectPolygon) {
                    return ConstantsLabeling.LABEL_POLYGON;
                }
            }
        }
        return "";
    }

    /**
     * Get the list of bounding boxes and the list of scribbles from the objects
     * and display it on the drawing panel.
     */
    public void setScribblesToDisplay() {
        List<DisplayScribbles> cropScribbles = new ArrayList<>();
        existsScribbleObj = false;

        if ((currentObject != null) && (currentObject instanceof ObjectScribble)) {
            // mark the fact that a scribble object was found
            existsScribbleObj = true;

            // add the scribbles of the current object which is in progress
            for (CropObject cropObject : ((ObjectScribble) currentObject).getCropList()) {
                // add scribbles from each crop available
                Rectangle cropPosPanel = dPImgToLabel.getResize().originalToResized(cropObject.getPositionOrig());

                cropScribbles.addAll(cropObject.getDisplayScribbleList(cropPosPanel, currentObject.getColor(), dPImgToLabel.getResize()));
            }
        }

        // if the user just wants to see the current object, display it and return
        if (userPrefs.isShowJustCurrentObj()) {
            dPImgToLabel.addScriblesToDisplay(cropScribbles);
            return;
        }

        // the user wants to see all the objects
        // add the saved objects
        for (Objects obj : objectList) {
            if (!(obj instanceof ObjectScribble)) {
                // if the object is not scribble, jump over it
                continue;
            }

            if (((ObjectScribble) obj).getCropList() == null) {
                continue;
            }

            // mark the fact that a scribble object was found
            existsScribbleObj = true;

            for (CropObject cropObject : ((ObjectScribble) obj).getCropList()) {
                // add scribbles from each crop available
                Rectangle cropPosPanel = dPImgToLabel.getResize().originalToResized(cropObject.getPositionOrig());

                cropScribbles.addAll(cropObject.getDisplayScribbleList(cropPosPanel, obj.getColor(), dPImgToLabel.getResize()));
            }

        }

        dPImgToLabel.addScriblesToDisplay(cropScribbles);
    }

    /**
     * Build and display the list of polygons, in panel coordinates, from the
     * saved objects.
     */
    public void setPolygonToDisplay() {
        List<DisplayPolygon> displayPoly = new ArrayList<>();

        if (!userPrefs.isShowJustCurrentObj()) {
            // add the saved objects
            for (Objects obj : objectList) {
                if (obj instanceof ObjectPolygon) {
                    Polygon polygonPanel = dPImgToLabel.getResize().originalToResized(((ObjectPolygon) obj).getPolygon());

                    DisplayPolygon poly = new DisplayPolygon(obj.getColor(), polygonPanel, false);

                    displayPoly.add(poly);
                }
            }
        }

        if ((currentObject != null) && (currentObject instanceof ObjectPolygon)) {
            Polygon polygon = ((ObjectPolygon) currentObject).getPolygon();
            if (polygon != null) {
                Polygon polygonPanel = dPImgToLabel.getResize().originalToResized(polygon);

                DisplayPolygon poly = new DisplayPolygon(currentObject.getColor(), polygonPanel, false);

                displayPoly.add(poly);
            }
        }

        dPImgToLabel.setPolygonDisplayList(displayPoly);
    }

    /**
     * Reset the position of the places where the user executed a click
     * press-release action. Used to reset the bounding boxes and crops from the
     * gui.
     */
    public void resetBBox() {
        dPImgToLabel.resetMousePosition();
    }

    /**
     * Set the label source for all the objects as specified by the sent
     * parameter.
     *
     * @param labelSource - the type of segmentation used for the object:
     * manual, automatic etc.
     */
    public void setAllObjsLabelSource(String labelSource) {
        objectList.stream().forEach((obj) -> {
            obj.setSegmentationSource(labelSource);
        });
    }

    /**
     * Reset the labeling duration for all the objects.
     */
    public void resetAllObjsDuration() {
        objectList.stream().forEach((obj) -> {
            obj.setLabelingDuration(new ArrayList<>());
        });
    }

    /**
     * Get the selected object.
     *
     * @return - the id of the selected object
     */
    public Objects getSelectedObject() {
        // get the box which was selected
        DisplayBBox selectedBox = dPImgToLabel.getSelectedBox();

        if (selectedBox != null) {
            for (Objects obj : objectList) {
                if (obj.contains(selectedBox.getPanelBox(), dPImgToLabel.getResize())) {
                    // set the selected object as the current object
                    currentObject = obj;

                    return obj;
                }
            }
        }
        return null;
    }

    /**
     * Check the object list and see with which labeling type the object was
     * segmented. Depending on the labeling type, return a color.
     *
     * @param objId - the id of the object
     * @return - the color of the labeling type
     */
    public Color getLabelTypeColor(long objId) {
        for (Objects obj : objectList) {
            if (obj.getObjectId() == objId) {
                return getLabelTypeColor(obj);
            }
        }
        return Color.red;
    }

    /**
     * Check the object type and see with which labeling type the object was
     * segmented. Depending on the labeling type, return a color.
     *
     * @param obj - the object for which the color is searched
     * @return - the color of the labeling type
     */
    public Color getLabelTypeColor(Objects obj) {
        if (obj instanceof ObjectBBox) {
            return new Color(179, 255, 255);
        } else if (obj instanceof ObjectScribble) {
            return new Color(179, 255, 179);
        } else if (obj instanceof ObjectPolygon) {
            return new Color(179, 179, 255);
        }

        return Color.red;
    }

    /**
     * Terminate the running threads.
     */
    public void terminateThreads() {
        //terminate the video thread
        if (videoThread != null) {
            videoThread.interrupt();
        }
    }

    /**
     * Sets the play mode and updates the displayed image, according to the new
     * play mode.
     *
     * @param playMode set the video play mode(backward/forward)
     */
    public void setPlayMode(int playMode) {
        dPImgToLabel.newFrame(changePlayMode(playMode), availableDrawSize);
    }

    /**
     * Changes the play mode and get the new image, from the corresponding
     * location in the video, considering the new play mode.
     *
     * @param playMode set the video play mode(backward/forward)
     * @return the new frame, based on the new play mode
     */
    protected BufferedImage changePlayMode(int playMode) {
        this.playMode = playMode;

        long frame = Utils.limit(1L, currFrameNo, getNoFrames());

        return getJumpToFrame(frame);
    }

    /**
     * Checks if the data was loaded. It is considered that the data is loaded
     * if the frame grabber is not null, if a folder with files was loaded etc.
     *
     * @return - true if the data was loaded and false otherwise
     */
    public boolean isDataLoaded() {
        return (currentFrame != null);
    }

    /**
     * Loads the saved objects for the current frame (if there were saved).
     */
    protected void loadGroundTruth() {
        // reinit the list of objects read from the saved ground truth
        objectList = new ArrayList<>();

        // read the ground truth from the saved file and load the list of objects and the frame attributes
        jsonDataManag.readFile(getGTFilePath(), getCurrentFrameInfo(), objectList);

        // notify the gui to change the annotations
        observable.notifyObservers(ObservedActions.Action.LOAD_FRAME_ANNOTATION);

        // notify that a new object was added on the list and it has to be displayed in the list
        observable.notifyObservers(ObservedActions.Action.REFRESH_OBJ_LIST_PANEL);
    }

    /**
     * Returns the current frame object, containing the current frame
     * information.
     *
     * @return - the current frame information
     */
    public FrameInfo getCurrentFrameInfo() {
        return currentFrameInfo;
    }

    /**
     * Checks if the object exists in the objects list.
     *
     * @param obj - the object to check if it exists in the list
     * @return - true if the object exists and false otherwise
     */
    protected boolean isObjInObjList(Objects obj) {
        return objectList.stream().anyMatch((object) -> (object.getObjectId() == obj.getObjectId()));
    }

    /**
     * Load a new list of objects from the ground truth storage. Erase the
     * current list of objects and put just the loaded ones.
     */
    private void loadGroundTruthData() {
        // remove all the objects from the object list
        removeAllObjects();

        // load the saved objects if there are some
        loadGroundTruth();

        /* update the gui with the new object coordinates */
        refreshDisplayList();
    }

    /**
     * Cancel object map.
     *
     * @param object the object
     * @param area the area
     */
    public void cancelObjectMap(Objects object, Rectangle area) {
        // update the result image if it exists
        if ((dPImgResult != null) && (object instanceof ObjectScribble)) {

            // remove the object id from the object map
            dPImgResult.removeObj(object.getObjectId(), area);

            // update the output image result with the new map
            dPImgResult.updateResultImg(objectList);
        }
    }

    /**
     * Enables the user to draw one time a crop, which will be added to the
     * current object.
     */
    public void setAddCropToObj() {
        // enable only if the current object is scribble
        if (currentObject instanceof ObjectScribble) {
            dPImgToLabel.setAddCropToObj(true);
        }
    }

    /**
     * Get the information from the crops and run the matting algorithm. Compute
     * the outer box and update the result panel
     *
     * @param obj - the object for which the matting has to run
     */
    private void runMattingAllCrops(Objects obj) {
        // run the matting algorithm for all the crops              
        ((ObjectScribble) obj).getCropList().forEach((crop) -> {
            // get the cropped image
            BufferedImage bi = Utils.getSelectedImg(dPImgToLabel.getOrigImg(), crop.getPositionOrig());

            // instantiate the matting class
            MattingThreading matt = new MattingThreading(bi, crop.getScribbleList(), ObservedActions.Action.DO_NOTHING);

            // run the matting algorithm and save its output - the object map
            crop.setObjectMap(matt.runMattingAlgo());

            // merge the object map into the result map
            dPImgResult.mergeCrop(crop.getObjectMap(), crop.getPositionOrig(), obj.getObjectId(), false);
        });

        obj.computeOuterBBoxCurObj();
    }

    /**
     * Run the matting algorithm for all the scribble objects, crop by crop, and
     * build the object map, its outer box and result image.
     */
    public void runMattingForObjList() {
        objectList.stream().filter((obj) -> (obj instanceof ObjectScribble)).forEachOrdered(this::runMattingAllCrops);

        /* Original code:
        for (Objects obj : objectList) {
            if (obj instanceof ObjectScribble) {
                runMattingAllCrops(obj);
            }
        }
         */
        // update the result image with all the merged objects
        dPImgResult.updateResultImg(objectList);

        // refresh the display list because the object might have changed
        refreshDisplayList();
    }

    /**
     * Update the result image with all the segmented objects.
     */
    public void updateResultPanel() {
        // update the result image with all the merged objects
        dPImgResult.updateResultImg(objectList);
    }

    /**
     * Enables the drawing of the guide lines and the size of the drawn box.
     *
     * @param show - true if the guide lines shall be visible and false
     * otherwise
     */
    public void setShowGuideShape(boolean show) {
        dPImgToLabel.setShowGuideShape(show);
    }

    /**
     * Initialise the polygon to be displayed on the screen.
     */
    public void initPolygon() {
        dPImgToLabel.initCurrentPolygon();
    }

    /**
     * Reset the current drawn polygon.
     */
    public void resetPolygon() {
        dPImgToLabel.resetCurrentPolygon();
    }

    /**
     * Open the polygon object preview window.
     */
    public void openPolyPrevWin() {
        java.awt.Polygon polygon = dPImgToLabel.getCurrentPolygonImg();

        if (polygon == null) {
            cancelCurrentObject();
            return;
        }

        if (polygon.npoints > 0) {
            // put the polygon in the object
            ((ObjectPolygon) currentObject).setPolygon(polygon);

            // compute the object outerbox
            currentObject.computeOuterBBoxCurObj();

            editWindow = new gui.editobject.PolygonObjEdit(null,
                    dPImgToLabel.getOrigImg(),
                    currentObject,
                    getObjAttribTree(objectAttributes, userPrefs.isCheckObjectAttributes()),
                    ObservedActions.Action.OPEN_POLYGON_SEGMENTATION,
                    objColorsList,
                    userPrefs);

            editWindow.addObserver(this);

            editWindow.removeOtherKeyEventDispatcher();

            editWindow.setLocation(Utils.winLocRelativeToMouse(editWindow.getSize()));

            editWindow.setVisible(true);
        }
    }

    /**
     * Set the flip option for the frame grabber. Activate/Deactivate the flip
     * vertically mode. Activating the flip vertically mode, the video component
     * will return a flipped frame
     *
     * @param selected activates/deactivates the flip vertically
     * <p>
     * true - activate the flip
     * <p>
     * false - deactivate the flip
     */
    public void setFlipVerticallyImage(boolean selected) {
        dPImgToLabel.newFrame(changeFlipVerticallyImage(selected), availableDrawSize);

        // refresh current image
        dPImgToLabel.reloadWorkImg();

        // refresh the result image
        dPImgResult.refreshImage(dPImgToLabel.getOrigImg());
    }

    /**
     * Set the mirror option for the frame grabber. Activate/Deactivate the
     * mirroring mode. Activating/Deactivating the mirroring mode, the video
     * component will return a mirrored frame.
     *
     * @param selected activates/deactivates the mirroring
     * <p>
     * true - activate the mirroring
     * <p>
     * false - deactivate the mirroring
     */
    public void setMirrorImage(boolean selected) {
        dPImgToLabel.newFrame(changeMirrorImage(selected), availableDrawSize);

        // refresh current image
        dPImgToLabel.reloadWorkImg();

        // refresh the result image
        dPImgResult.refreshImage(dPImgToLabel.getOrigImg());
    }

    /**
     * Activate/Deactivate the flip vertically mode. Activating the flip
     * vertically mode, the data component will return a flipped frame.
     *
     * @param selected activates/deactivates the flip vertically
     * <p>
     * true - activate the flip
     * <p>
     * false - deactivate the flip
     * @return the current image, flipped vertically
     */
    protected BufferedImage changeFlipVerticallyImage(boolean selected) {
        this.flipVertically = selected;

        Utils.flipVerticallyImage(currentFrame);

        return currentFrame;
    }

    /**
     * Activate/Deactivate the mirroring mode. Activating/Deactivating the
     * mirroring mode, the data component will return a mirrored frame.
     *
     * @param selected activates/deactivates the mirroring
     * <p>
     * true - activate the mirroring
     * <p>
     * false - deactivate the mirroring
     * @return the current image, mirrored
     */
    protected BufferedImage changeMirrorImage(boolean selected) {
        mirror = selected;

        Utils.mirrorImage(currentFrame);

        return currentFrame;
    }

    /**
     * Enable/Disable the drawing of a highlight of the object to be able to see
     * faster the segmented objects.
     *
     * @param drawAlphaObj true - draws an alpha on top of the object, of the
     * color of the object; false - does not draw anything additional to the
     * object borders
     */
    public void setDrawAlphaObj(boolean drawAlphaObj) {
        dPImgToLabel.setDrawAlphaObj(drawAlphaObj);
    }

    /**
     * Set the value of the alpha used to highlight the object.
     *
     * @param objAlpha the value of the alpha to be used for highlighting the
     * object (0-255)
     */
    public void setObjAlpha(int objAlpha) {
        dPImgToLabel.setObjAlpha(objAlpha);
    }

    /**
     * Remove the objects from the list of objects. Start a new fresh list and
     * discard the old one.
     */
    public void removeAllObjects() {
        // create a new object
        objectList = new ArrayList<>();

        // remove all the objects from the side panel object list
        observable.notifyObservers(ObservedActions.Action.REFRESH_OBJ_LIST_PANEL);
    }

    /**
     * Get the attributes of the frame from the application configuration.
     *
     * @return the frame attributes to be displayed on the interface, as an
     * array list of strings.
     */
    public LAttributesFrame getFrameAttributes() {
        if (frameAttributes == null) {
            return loadFrameAttributes();
        } else {
            return frameAttributes;
        }
    }

    /**
     * Get the attributes of the object from the application configuration.
     *
     * @return the object attributes to be displayed on the interface, as an
     * array list of strings.
     */
    public CustomTreeNode getObjectAttributes() {
        return loadObjectAttributes();
    }

    /**
     * Change the old object id with the new one.
     */
    private void updateDefaultObjIdMap() {
        if (currentObject instanceof ObjectScribble) {
            dPImgResult.changeObjId(DEFAULT_OBJECT_ID, currentObject.getObjectId());
        }
    }

    /**
     * Set the name of the file which was chosen by the user.
     *
     * @param fileName the name of the selected file/folder chosen by the user.
     */
    public void setChosenFile(String fileName) {
        // init the files list
        fileList = new ArrayList<>();

        // if the file chosen is a directory, the load shall go recursivly in all subfolders (if any)
        File temp = new File(fileName);

        if (temp.isDirectory()) {
            chosenPath = fileName;
        } else {
            chosenPath = temp.getParent();
            chosenFileName = fileName;
        }

        // create the list of files based on the given path
        getFilesList(chosenPath);

        if ((chosenFileName == null) || (!chosenFileName.equals(fileName))) {
            if (!fileList.isEmpty()) {
                currFrameNo = 1L;
                chosenFileName = fileList.get((int) (currFrameNo - 1L));
            }
        } else {
            // find the index of the chosen file
            currFrameNo = fileList.lastIndexOf(chosenFileName) + 1L;
        }
    }

    /**
     * Create the list of files found in the directory and its subfolders.
     *
     * @param dirName the path to the directory which has to be indexed
     */
    private void getFilesList(String dirName) {
        File[] faFiles = new File(dirName).listFiles();
        for (File file : faFiles) {
            if (common.Utils.checkExtension(file.getName(), Constants.IMG_EXTENSION_LIST)) {
                fileList.add(file.getAbsolutePath());
            }
            if (file.isDirectory()) {
                getFilesList(file.getAbsolutePath());
            }
        }
    }

    /**
     * Allows the user to edit the frame and object attributes.
     *
     * @param parent the parent component of the dialog
     * @return an instance of the attributes definition window
     */
    public AttributesDefinition editAttributes(java.awt.Frame parent) {
        AttributesDefinition attributes = new AttributesDefinition(parent, frameAttributes, objectAttributes);
        // add observer to the frame
        attributes.addObserver(this);

        return attributes;
    }

    /**
     * Refresh the displayed image. The gui might have changed (by maximization
     * etc.), the panel sizes could be different and the image, together with
     * the ground truth, have to be refreshed.
     */
    public void refreshImage() {
        // resize panel and image
        dPImgToLabel.refreshImage(dPImgToLabel.getOrigImg(), availableDrawSize);

        // display the objects based on the new resize ratio
        refreshDisplayList();
    }

    /**
     * Set the amount of space available on the gui for displaying the drawing
     * panel.
     *
     * @param availableDrawSize the amount of space available on the gui for
     * displaying the drawing panel
     */
    public void setAvailableDrawSize(Dimension availableDrawSize) {
        this.availableDrawSize = availableDrawSize;
    }

    /**
     * Get the amount of space available on the gui for displaying the drawing
     * panel.
     *
     * @return the dimension reserved for displaying the drawing panel
     */
    public Dimension getAvailableDrawSize() {
        return availableDrawSize;
    }

    /**
     * The attributes were changed and have to be updated for both frame and
     * object.
     */
    private void reloadAttributes() {
        // at the level of controller, update object attributes
        objectAttributes = null;
        getObjectAttributes();

        // apply changes when it is the case
        reloadObjectAttributes();

        // notify further the main gui viewer to update frame attributes
        frameAttributes = null;
        observable.notifyObservers(ObservedActions.Action.RELOAD_ATTRIBUTES);
    }

    /**
     * Reload the object attributes on the object edit windows.
     */
    private void reloadObjectAttributes() {
        if (currentObject == null) {
            return;
        }

        if (editWindow != null) {
            editWindow.setObjectAttributes(getObjAttribTree(objectAttributes, userPrefs.isCheckObjectAttributes()));
            editWindow.setTypeClassValOcc(currentObject.getObjectType(),
                    currentObject.getObjectClass(),
                    currentObject.getObjectValue(),
                    currentObject.getOccluded());
        }
        if (cropWindow != null) {
            cropWindow.setObjectAttributes(getObjAttribTree(objectAttributes, userPrefs.isCheckObjectAttributes()));
            cropWindow.setTypeClassValOcc(currentObject.getObjectType(),
                    currentObject.getObjectClass(),
                    currentObject.getObjectValue(),
                    currentObject.getOccluded());
        }
    }

    /**
     * Is exists scribble obj boolean.
     *
     * @return true if at least one object of scribble type exists in the list
     */
    public boolean isExistsScribbleObj() {
        return existsScribbleObj;
    }

    /**
     * Set true if at least one object of scribble type exists in the list.
     *
     * @param existsScribbleObj true if at least one object of scribble type
     * exists in the list
     */
    public void setExistsScribbleObj(boolean existsScribbleObj) {
        this.existsScribbleObj = existsScribbleObj;
    }

    /**
     * The display tree can be the same with the object attributes tree, or it
     * can contain also an invalid attributes branch, based on the user
     * configuration.
     *
     * @param objAttributes the list of object attributes, defined by the user
     * @param addInvalidAttribBranch true if a branch with the predefined
     * invalid attribute, should be added to the tree
     * @return a tree, which will be used for the synchronization of the
     * type-class-value
     */
    protected CustomTreeNode getObjAttribTree(CustomTreeNode objAttributes, boolean addInvalidAttribBranch) {
        CustomTreeNode extendedTree;

        if (addInvalidAttribBranch) {
            // create the invalid branch of the tree by creating the levels with invalid nodes
            CustomTreeNode invalidL1 = new CustomTreeNode(Constants.INVALID_ATTRIBUTE_TEXT);
            CustomTreeNode invalidL2 = invalidL1.addChild(Constants.INVALID_ATTRIBUTE_TEXT);
            invalidL2.addChild(Constants.INVALID_ATTRIBUTE_TEXT);

            extendedTree = new CustomTreeNode(objAttributes.getRoot());
            // add the invalid branch
            extendedTree.addChild(invalidL1);
            // add the rest of the tree branches
            extendedTree.addChildren(Utils.cloneTree(objAttributes).getChildrenNodes());
        } else {
            extendedTree = objAttributes;
        }

        return extendedTree;
    }

    /**
     * Returns the image specified in the chosenFileName member.
     *
     * @return the image representing the specified file name
     */
    private BufferedImage getChosenFrameImage() {
        try {
            // Load the file to be labeled in the panel and show it
            currentFrame = ImageIO.read(new File(chosenFileName));

            // make sure the frame has a standard format
            convertToStandardFormat();
        } catch (IOException ex) {
            java.util.logging.Logger.getLogger(GUIController.class.getName()).log(Level.SEVERE, null, ex);
        }

        // flip, mirror according to the selections
        if (flipVertically) {
            changeFlipVerticallyImage(flipVertically);
        }

        if (mirror) {
            changeMirrorImage(mirror);
        }

        return currentFrame;
    }

    /**
     * Create the path to the file where the ground truth shall be stored.
     *
     * @return a string representing the path to the ground truth file
     */
    private String getGTFilePath() {
        // name the path to the ground truth folder
        String gtPath = chosenPath + File.separator + "GT" + File.separator;

        // create the path to the ground truth folder
        common.Utils.createFolderPath(gtPath);

        // name the file where the ground truth shall be saved (remove the extension of the file)
        String fileName = new File(chosenFileName).getName();
        int position = fileName.lastIndexOf('.');
        if (position != -1) {
            fileName = fileName.substring(0, position);
        }

        return (gtPath + fileName);
    }

    /**
     * Convert the current frame to a standard format (TYPE_3BYTE_BGR) from
     * other standards (4 byte ABGR, int ARGB etc.).
     */
    private void convertToStandardFormat() {
        // if the image is not 3 byte RGB, modify it
        if (currentFrame.getType() != BufferedImage.TYPE_3BYTE_BGR) {
            // create the empty image
            BufferedImage convertedImage = new BufferedImage(currentFrame.getWidth(), currentFrame.getHeight(), BufferedImage.TYPE_3BYTE_BGR);

            // draw the frame into the new image
            convertedImage.createGraphics().drawImage(currentFrame, 0, 0, currentFrame.getWidth(), currentFrame.getHeight(), null);

            // point the current image to the modified one
            currentFrame = convertedImage;
        }
    }

    /**
     * Load the attributes of the frame from a file.
     *
     * @return the frame attributes to be displayed on the interface, as an
     * array list of strings.
     */
    private LAttributesFrame loadFrameAttributes() {
        frameAttributes = new LAttributesFrame();
        String readLine;

        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(Constants.FRAME_ATTRIBUTES_PATH), Charset.forName("UTF-8")))) {
            while ((readLine = br.readLine()) != null) {

                String[] wordsList = readLine.split("=");
                String[] attributes = wordsList[wordsList.length - 1].split(",");

                switch (wordsList[0]) {
                    case "illumination":
                        frameAttributes.getIlluminationList().addAll(Arrays.asList(attributes));
                        break;

                    case "weather":
                        frameAttributes.getWeatherList().addAll(Arrays.asList(attributes));
                        break;

                    case "road_type":
                        frameAttributes.getRoadTypeList().addAll(Arrays.asList(attributes));
                        break;

                    case "road_event":
                        frameAttributes.getRoadEventList().addAll(Arrays.asList(attributes));
                        break;

                    case "country":
                        frameAttributes.getCountryList().addAll(Arrays.asList(attributes));
                        break;

                    default:
                        break;
                }
            }
        } catch (IOException ex) {
            java.util.logging.Logger.getLogger(GUIController.class.getName()).log(Level.SEVERE, null, ex);
        }

        return frameAttributes;
    }

    /**
     * Copy the list of segmented objects in a temp list, in order to paste it
     * in another frame.
     */
    public void copyObjList() {
        tempObjectList = new ArrayList<>();
        for (Objects object : objectList) {
            tempObjectList.add(object);
        }
    }

    /**
     * Paste the saved list of objects, in the current list. Do it just for the
     * cases when the object id is not already in the current list.
     */
    public void pasteObjList() {
        for (Objects object : tempObjectList) {
            if (!isObjectInList(object)) {
                objectList.add(object);
                // notify that a new object was added on the list and it has to be displayed in the list
                observable.notifyObservers(ObservedActions.Action.ADD_OBJECT_ON_PANEL);
            }
        }

        /* update the drawing panel*/
        refreshDisplayList();
    }

    /**
     * Check if the specified object exists in the objects list; or if an object
     * with the same id exists in list.
     *
     * @param object the object being verified
     * @return true if an object with the specified id exists in list; false if
     * the id is not found in list
     */
    public boolean isObjectInList(Objects object) {
        for (Objects obj : objectList) {
            if (obj.getObjectId() == object.getObjectId()) {
                return true;
            }
        }

        return false;
    }
} public class ConstantsLabeling {

    //----------------------Label type definition-----------------------------//
    /**
     * Label type definition: 2D bounding box.
     */
    public static final String LABEL_2D_BOUNDING_BOX = "2D_bounding_box";

    /**
     * Label type definition: 3D bounding box.
     */
    public static final String LABEL_3D_BOUNDING_BOX = "3D_bounding_box";

    /**
     * Label type definition: pixel segmentation.
     */
    public static final String LABEL_SCRIBBLE = "pixel_segmentation_scribble";

    /**
     * Label type definition: polygon segmentation.
     */
    public static final String LABEL_POLYGON = "polygon_segmentation";

    /**
     * Label type definition: free drawing segmentation.
     */
    public static final String LABEL_FREE_DRAW = "free_drawing_segmentation";

    //----------------------Label source definition---------------------------//
    /**
     * Label source definition: manual.
     */
    public static final String LABEL_SOURCE_MANUAL = "manual";

    //----------------------Actions while in scribble mode--------------------//
    /**
     * Types of possible actions while in scribble mode: erase scribbles.
     */
    public static final int ACTION_TYPE_ERASE = -1;

    /**
     * Types of possible actions while in scribble mode: draw background
     * scribble.
     */
    public static final int ACTION_TYPE_BACKGROUND = 0;

    /**
     * Types of possible actions while in scribble mode: draw object scribble.
     */
    public static final int ACTION_TYPE_OBJECT = 1;

    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors. Java adds an implicit public
     * constructor to every class which does not define at least one explicitly.
     * Hence, at least one non-public constructor should be defined.
     */
    private ConstantsLabeling() {
        throw new IllegalStateException("Utility class, do not instantiate!");
    }
} public class ExportImage {

    /**
     * logger instance
     */
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ExportImage.class);

    /**
     * Export the image to a JPEG file format. JPEG is a lossy format.
     *
     * @param image    - the image to be exported
     * @param fileName - the name of the file
     */
    public static void exportAsJPEG(BufferedImage image, String fileName) {
        exportImage(image, fileName, "JPEG");
    }

    /**
     * Export the image to a PNG file format. PNG is a loss-less format.
     *
     * @param image    - the image to be exported
     * @param fileName - the name of the file
     */
    public static void exportAsPNG(BufferedImage image, String fileName) {
        exportImage(image, fileName, "PNG");
    }

    /**
     * Export the image to a BMP file format. BMP is a loss-less format.
     *
     * @param image    - the image to be exported
     * @param fileName - the name of the file
     */
    public static void exportAsBMP(BufferedImage image, String fileName) {
        exportImage(image, fileName, "BMP");
    }

    /**
     * Export the image to a self specified file format.
     *
     * @param image     the image to be exported
     * @param fileName  the name of the file
     * @param extension the file type/extension
     */
    public static void exportImage(BufferedImage image, String fileName, String extension) {
        try {
            Utils.createFolderPath(fileName);

            boolean done = ImageIO.write(image, extension, new File(fileName));
            LOG.info((done ? "Exported the file {}!" : "Export did not succeed; the file type might be unknown!"), fileName);
        } catch (IOException ex) {
            LOG.warn("The file export (from exportImage(..)) was not correctly done! {}", ex);
        }
    }
} public class TextAreaAppender extends AppenderBase<ILoggingEvent> {

    /**
     * The text area component where the logger should output its messages.
     */
    private JTextArea jTALogger;

    /**
     * The text area component where the logger should output its messages.
     */
    private JTextArea jTAShortLogger;

    /**
     * Set the text area component where the logger should output its messages
     * and exceptions.
     *
     * @param jTALogger      the JTextArea component where to output messages
     * @param jTAShortLogger the j ta short logger
     */
    public void setTextArea(JTextArea jTALogger, JTextArea jTAShortLogger) {
        this.jTALogger = jTALogger;
        this.jTAShortLogger = jTAShortLogger;
    }

    @Override
    protected void append(ILoggingEvent eventObject) {
        // add the new text
        jTALogger.append(eventObject.getFormattedMessage() + "\n");

        // always scroll down to see the latest text
        jTALogger.setCaretPosition(jTALogger.getDocument().getLength());

        // add the new text
        jTAShortLogger.append(eventObject.getFormattedMessage() + "\n");

        // always scroll down to see the latest text
        jTAShortLogger.setCaretPosition(jTALogger.getDocument().getLength());
    }
} public class Icons {

    /**
     * The common path for all the icons.
     */
    private static final String BASE_PATH_ICONS = "img" + File.separator + "icons" + File.separator;

    /**
     * The path to the folder containing 16x16 icons.
     */
    private static final String PATH_16x16 = BASE_PATH_ICONS + "16x16" + File.separator;

    /**
     * The path to the folder containing 32x32 icons.
     */
    private static final String PATH_32X32 = BASE_PATH_ICONS + "32x32" + File.separator;

    /**
     * The path to the folder containing 48x48 icons.
     */
    private static final String PATH_48X48 = BASE_PATH_ICONS + "48x48" + File.separator;

    /**
     * The path to the folder containing logos icons.
     */
    public static final String PATH_LOGO = "img" + File.separator + "logos" + File.separator;

    /**
     * The path to the reindeer icon.
     */
    public static final String REINDEER_ICON_PATH = PATH_LOGO + "Reindeer.PNG";

    /**
     * The path to the exclamation/attention icon.
     */
    public static final String EXCLAMATION_ICON_PATH = PATH_48X48 + "Dark_icon_Attention.png";

    /**
     * The path to the splash screen image.
     */
    public static final String SPLASH_SCREEN_PATH = PATH_LOGO + "SplashScreenPixie.png";

    /**
     * The icon used for info.
     */
    public static final ImageIcon INFO_ICON = new ImageIcon(PATH_48X48 + "Dark_icon_I.png");

    /**
     * The icon used for OK.
     */
    public static final ImageIcon OK_ICON = new ImageIcon(PATH_48X48 + "Dark_icon_OK.png");

    /**
     * The icon used for exclamation, attention etc.
     */
    public static final ImageIcon EXCLAMATION_ICON = new ImageIcon(EXCLAMATION_ICON_PATH);

    /**
     * The icon used for error.
     */
    public static final ImageIcon ERROR_ICON = new ImageIcon(PATH_48X48 + "Dark_icon_X.png");

    /**
     * The icon used for question.
     */
    public static final ImageIcon QUESTION_ICON = new ImageIcon(PATH_48X48 + "Dark_icon_Question.png");

    /**
     * The icon used for file loading.
     */
    public static final ImageIcon LOAD_FILE_ICON = new ImageIcon(PATH_32X32 + "Dark_icon_LoadFile.png");

    /**
     * The icon used for previous.
     */
    public static final ImageIcon PREVIOUS_ICON = new ImageIcon(PATH_32X32 + "Dark_icon_Prev.png");

    /**
     * The icon used for play.
     */
    public static final ImageIcon PLAY_ICON = new ImageIcon(PATH_32X32 + "Dark_icon_Play.png");

    /**
     * The icon used for pause.
     */
    public static final ImageIcon PAUSE_ICON = new ImageIcon(PATH_32X32 + "Dark_icon_Pause.png");

    /**
     * The icon used for next.
     */
    public static final ImageIcon NEXT_ICON = new ImageIcon(PATH_32X32 + "Dark_icon_Next.png");

    /**
     * The icon representing the picture of reindeer (400 × 261).
     */
    public static final ImageIcon REINDEER_SMALL_ICON = new ImageIcon(PATH_LOGO + "Reindeer_small.PNG");

    /**
     * The icon representing the picture of reindeer (640 × 417).
     */
    public static final ImageIcon REINDEER_ICON = new ImageIcon(REINDEER_ICON_PATH);

    /**
     * The image representing the splash screen.
     */
    public static final ImageIcon SPLASH_SCREEN = new ImageIcon(SPLASH_SCREEN_PATH);

    /**
     * The image representing a blue tag.
     */
    public static final ImageIcon CLOSED_ICON_16X16 = new ImageIcon(PATH_16x16 + "tag_blue.png");

    /**
     * The image representing a green tag.
     */
    public static final ImageIcon OPEN_ICON_16X16 = new ImageIcon(PATH_16x16 + "tag_green.png");

    /**
     * The image representing a yellow tag.
     */
    public static final ImageIcon LEAF_ICON_16X16 = new ImageIcon(PATH_16x16 + "tag_yellow.png");

    /**
     * The image representing a settings icon.
     */
    public static final ImageIcon SETTINGS_ICON_16X16 = new ImageIcon(PATH_16x16 + "settings.png");

    /**
     * The image representing the exit application icon.
     */
    public static final ImageIcon EXIT_APPLICATION_ICON_16X16 = new ImageIcon(PATH_16x16 + "exit.png");

    /**
     * The image representing the export image icon.
     */
    public static final ImageIcon EXPORT_IMAGE_ICON_16X16 = new ImageIcon(PATH_16x16 + "export_image.png");

    /**
     * The image representing the help icon.
     */
    public static final ImageIcon HELP_BOOK_ICON_16X16 = new ImageIcon(PATH_16x16 + "help_book.png");

    /**
     * The image representing the info icon.
     */
    public static final ImageIcon INFO_ICON_16X16 = new ImageIcon(PATH_16x16 + "info_round.png");

    /**
     * The image representing the about icon.
     */
    public static final ImageIcon ABOUT_ICON_16X16 = new ImageIcon(PATH_16x16 + "info_square.png");

    /**
     * The image representing the key icon.
     */
    public static final ImageIcon KEY_ICON_16X16 = new ImageIcon(PATH_16x16 + "key.png");

    /**
     * The image representing the list icon.
     */
    public static final ImageIcon LIST_ICON_16X16 = new ImageIcon(PATH_16x16 + "list.png");

    /**
     * The image representing the select language icon.
     */
    public static final ImageIcon LANGUAGE_ICON_16X16 = new ImageIcon(PATH_16x16 + "language.png");

    /**
     * The image representing the previous icon.
     */
    public static final ImageIcon PREVIOUS_ICON_16X16 = new ImageIcon(PATH_16x16 + "previous.png");

    /**
     * The image representing the previous icon.
     */
    public static final ImageIcon NEXT_ICON_16X16 = new ImageIcon(PATH_16x16 + "next.png");

    /**
     * The image representing the folder icon.
     */
    public static final ImageIcon FOLDER_ICON_16X16 = new ImageIcon(PATH_16x16 + "folder.png");
} public class FrameInfo {

    private String illumination;
    private String weather;
    private String roadType;
    private String roadEvent;
    private String country;
    private boolean wipersVisible;
    private boolean dirtVisible;
    private boolean imageDistorted;
    private String frameDuration;
    private boolean saveFrameObjMap;

    /**
     * Instantiates a new Frame info.
     *
     * @param illumination    - the scene illumination
     * @param weather         - the scene weather
     * @param roadType        - the type of road in the scene
     * @param roadEvent       - the road event in the scene
     * @param country         - the country where the scene is
     * @param wipersVisible   - the wipers are visible in the image
     * @param dirtVisible     - there is dirt visible in the image
     * @param imageDistorted  - the image is distorted in some way
     * @param saveFrameObjMap - shows that the frame object should be saved while saving data in the database
     */
    public FrameInfo(String illumination, String weather, String roadType, String roadEvent, String country, boolean wipersVisible, boolean dirtVisible, boolean imageDistorted, boolean saveFrameObjMap) {
        this.illumination = illumination;
        this.weather = weather;
        this.roadType = roadType;
        this.roadEvent = roadEvent;
        this.country = country;
        this.wipersVisible = wipersVisible;
        this.dirtVisible = dirtVisible;
        this.imageDistorted = imageDistorted;
        this.saveFrameObjMap = saveFrameObjMap;
        this.frameDuration = "";
    }

    /**
     * Instantiates a new Frame info.
     */
    public FrameInfo() {
    }

    /**
     * Gets illumination.
     *
     * @return the illumination
     */
    public String getIllumination() {
        return illumination;
    }

    /**
     * Gets weather.
     *
     * @return the weather
     */
    public String getWeather() {
        return weather;
    }

    /**
     * Gets road type.
     *
     * @return the road type
     */
    public String getRoadType() {
        return roadType;
    }

    /**
     * Gets road event.
     *
     * @return the road event
     */
    public String getRoadEvent() {
        return roadEvent;
    }

    /**
     * Gets country.
     *
     * @return the country
     */
    public String getCountry() {
        return country;
    }

    /**
     * Is wipers visible boolean.
     *
     * @return the boolean
     */
    public boolean isWipersVisible() {
        return wipersVisible;
    }

    /**
     * Is dirt visible boolean.
     *
     * @return the boolean
     */
    public boolean isDirtVisible() {
        return dirtVisible;
    }

    /**
     * Is image distorted boolean.
     *
     * @return the boolean
     */
    public boolean isImageDistorted() {
        return imageDistorted;
    }

    /**
     * Set a new frame duration. This initializes completely the frame duration,
     * it does not add the value to the existing ones.
     *
     * @param frameDuration the new value for the frame duration
     */
    public void setFrameDuration(String frameDuration) {
        this.frameDuration = frameDuration;
    }

    /**
     * Gets frame duration.
     *
     * @return the frame duration
     */
    public String getFrameDuration() {
        return frameDuration;
    }

    /**
     * Is save frame obj map boolean.
     *
     * @return the boolean
     */
    public boolean isSaveFrameObjMap() {
        return saveFrameObjMap;
    }

    /**
     * Sets illumination.
     *
     * @param illumination the illumination
     */
    public void setIllumination(String illumination) {
        this.illumination = illumination;
    }

    /**
     * Sets weather.
     *
     * @param weather the weather
     */
    public void setWeather(String weather) {
        this.weather = weather;
    }

    /**
     * Sets road type.
     *
     * @param roadType the road type
     */
    public void setRoadType(String roadType) {
        this.roadType = roadType;
    }

    /**
     * Sets road event.
     *
     * @param roadEvent the road event
     */
    public void setRoadEvent(String roadEvent) {
        this.roadEvent = roadEvent;
    }

    /**
     * Sets country.
     *
     * @param country the country
     */
    public void setCountry(String country) {
        this.country = country;
    }

    /**
     * Sets wipers visible.
     *
     * @param wipersVisible the wipers visible
     */
    public void setWipersVisible(boolean wipersVisible) {
        this.wipersVisible = wipersVisible;
    }

    /**
     * Sets dirt visible.
     *
     * @param dirtVisible the dirt visible
     */
    public void setDirtVisible(boolean dirtVisible) {
        this.dirtVisible = dirtVisible;
    }

    /**
     * Sets image distorted.
     *
     * @param imageDistorted the image distorted
     */
    public void setImageDistorted(boolean imageDistorted) {
        this.imageDistorted = imageDistorted;
    }

    /**
     * Sets save frame obj map.
     *
     * @param saveFrameObjMap the save frame obj map
     */
    public void setSaveFrameObjMap(boolean saveFrameObjMap) {
        this.saveFrameObjMap = saveFrameObjMap;
    }

    /**
     * Sets attributes.
     *
     * @param illumination    the illumination
     * @param weather         the weather
     * @param roadType        the road type
     * @param roadEvent       the road event
     * @param country         the country
     * @param wipersVisible   the wipers visible
     * @param dirtVisible     the dirt visible
     * @param imageDistorted  the image distorted
     * @param saveFrameObjMap the save frame obj map
     */
    public void setAttributes(String illumination, String weather, String roadType, String roadEvent, String country, boolean wipersVisible, boolean dirtVisible, boolean imageDistorted, boolean saveFrameObjMap) {
        this.illumination = illumination;
        this.weather = weather;
        this.roadType = roadType;
        this.roadEvent = roadEvent;
        this.country = country;
        this.wipersVisible = wipersVisible;
        this.dirtVisible = dirtVisible;
        this.imageDistorted = imageDistorted;
        this.saveFrameObjMap = saveFrameObjMap;
    }

    @Override
    public String toString() {
        return "FrameInfo{" +
                "illumination='" + illumination + '\'' +
                ", weather='" + weather + '\'' +
                ", roadType='" + roadType + '\'' +
                ", roadEvent='" + roadEvent + '\'' +
                ", country='" + country + '\'' +
                ", wipersVisible=" + wipersVisible +
                ", dirtVisible=" + dirtVisible +
                ", imageDistorted=" + imageDistorted +
                ", frameDuration='" + frameDuration + '\'' +
                ", saveFrameObjMap=" + saveFrameObjMap +
                '}';
    }
} public class DisplayBBox {

    /**
     * The color of the bounding box
     */
    private final Color color;

    /**
     * The position and the size of the bounding box.
     */
    private final Rectangle panelBox;

    /**
     * The text to be displayed on top of the box.
     */
    private final String text;

    /**
     * Shows how the representation of the polygon shall be: true = the line
     * drawn is dashed, false = the line drawn is solid.
     */
    private final boolean useDashedLine;

    /**
     * Instantiates a new Display b box.
     *
     * @param panelBox      the panel box
     * @param color         the color
     * @param useDashedLine the use dashed line
     */
    public DisplayBBox(Rectangle panelBox, Color color, boolean useDashedLine) {
        this.color = color;
        this.panelBox = panelBox;
        this.text = "";
        this.useDashedLine = useDashedLine;
    }

    /**
     * Instantiates a new Display b box.
     *
     * @param panelBox      the panel box
     * @param color         the color
     * @param text          the text
     * @param useDashedLine the use dashed line
     */
    public DisplayBBox(Rectangle panelBox, Color color, String text, boolean useDashedLine) {
        this.color = color;
        this.panelBox = panelBox;
        this.text = text;
        this.useDashedLine = useDashedLine;
    }

    /**
     * The color of the box.
     *
     * @return the color used to draw the box
     */
    public Color getColor() {
        return color;
    }

    /**
     * The rectangle to be displayed.
     *
     * @return the rectangle to be drawn
     */
    public Rectangle getPanelBox() {
        return panelBox;
    }

    /**
     * The text to be displayed on top of the box.
     *
     * @return text to be displayed with the box
     */
    public String getText() {
        return text;
    }

    /**
     * Shows how the representation of the polygon shall be.
     *
     * @return true = the line drawn is dashed, false = the line drawn is solid
     */
    public boolean isUseDashedLine() {
        return useDashedLine;
    }

    /**
     * Equal panels boolean.
     *
     * @param pos the pos
     * @return the boolean
     */
    public boolean equalPanels(Rectangle pos) {
        return panelBox.equals(pos);
    }
} public class ObjectBBox extends Objects {

    /**
     * Instantiates a new Object b box.
     */
    public ObjectBBox() {
        super();
    }

    @Override
    public void computeOuterBBoxCurObj() {
    }

    @Override
    public void move(int xOffset, int yOffset, Dimension frameSize) {
        // move only if by moving we do not get out of the image
        if ((outerBBox.x + xOffset >= 0)
                && (outerBBox.y + yOffset >= 0)
                && (outerBBox.x + outerBBox.width + xOffset <= frameSize.width)
                && (outerBBox.y + outerBBox.height + yOffset <= frameSize.height)) {
            // move the outer bounding box; the crop will move as well because it is the same object
            moveBox(xOffset, yOffset, outerBBox);

            // if the label was changed, it means that the user touched it, therefore the segmentation is manual
            segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
        }
    }

    @Override
    public boolean remove(Rectangle coordPanelBox, Resize resizeRate) {
        // for boxes, it will always be the erase of the entire object, therefore nothing has to be done inside of the object.
        return false;
    }

    @Override
    public void changeSize(int left, int top, int right, int bottom, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
        int xTopRight = outerBBox.x + outerBBox.width;
        int xBottomRight = outerBBox.y + outerBBox.height;

        boolean leftCheck = ((outerBBox.x + left >= 0)
                && (outerBBox.x + left < xTopRight));

        boolean topCheck = ((outerBBox.y + top >= 0)
                && (outerBBox.y + top < xBottomRight));

        boolean rightCheck = ((xTopRight + right > outerBBox.x)
                && (xTopRight + right < frameSize.width));

        boolean bottomCheck = ((xBottomRight + bottom > outerBBox.y)
                && (xBottomRight + bottom < frameSize.height));

        if (leftCheck && topCheck && rightCheck && bottomCheck) {
            // change the outer bounding box size; the crop will move as well because it is the same object
            changeSize(left, top, right, bottom, outerBBox);

            // if the label was changed, it means that the user touched it, therefore the segmentation is manual
            segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
        }
    }

    /**
     * Changes the size of the box with the specified offsets.
     *
     * @param left - how much should the object be modified on the left side
     * @param top - how much should the object be modified on the top part
     * @param right - how much should the object be modified on the right side
     * @param bottom - how much should the object be modified on the bottom part
     * @param boxPosOrig - the initial position of the selected box in image
     * coordinates
     */
    private void changeSize(int left, int top, int right, int bottom, Rectangle bboxOrigPos) {
        bboxOrigPos.setBounds(bboxOrigPos.x + left, bboxOrigPos.y + top, bboxOrigPos.width + right - left, bboxOrigPos.height + bottom - top);
    }

    @Override
    public boolean contains(Rectangle coordPanelBox, Resize resizeRate) {
        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);

        return outerBBoxPanel.equals(coordPanelBox);
    }

	@Override
    public void move(int xOffset, int yOffset, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
        move(xOffset, yOffset, frameSize);
    }
} public class ObjectPolygon extends Objects {

    /**
     * The polygon saving the vertices of the object.
     */
    private Polygon polygon;

    /**
     * Instantiates a new Object polygon.
     */
    public ObjectPolygon() {
        super();
    }

    @Override
    public void computeOuterBBoxCurObj() {
        int minX = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE;
        int minY = Integer.MAX_VALUE;
        int maxY = Integer.MIN_VALUE;

        for (int index = 0; index < polygon.npoints; index++) {
            Point vertex = new Point(polygon.xpoints[index], polygon.ypoints[index]);

            if (minX > vertex.x) {
                minX = vertex.x;
            }

            if (maxX < vertex.x) {
                maxX = vertex.x;
            }

            if (minY > vertex.y) {
                minY = vertex.y;
            }

            if (maxY < vertex.y) {
                maxY = vertex.y;
            }
        }

        outerBBox = new Rectangle(minX, minY, maxX - minX, maxY - minY);
    }

    @Override
    public void move(int xOffset, int yOffset, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
        move(xOffset, yOffset, frameSize);
    }

    @Override
    public boolean remove(Rectangle coordPanelBox, Resize resizeRate) {
        // for polygon, it will always be the erase of the entire object, therefore nothing has to be done inside of the object.
        return false;
    }

    @Override
    public void changeSize(int left, int top, int right, int bottom, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
         // in order to change the size of the polygon, an edit window has to be opened; no other changes can be done in the GUI
    }

    @Override
    public boolean contains(Rectangle coordPanelBox, Resize resizeRate) {
        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);

        return outerBBoxPanel.equals(coordPanelBox);
    }

    /**
     * Moves one point with the specified offset and returns the new image
     * coordinates.
     *
     * @param xOffset      - how much should the point move on the X axis
     * @param yOffset      - how much the point should move on the Y axis
     * @param pointPosOrig - the position of the point in image coordinates
     */
    public void movePoint(int xOffset, int yOffset, Point pointPosOrig) {
        pointPosOrig.setLocation(pointPosOrig.x + xOffset, pointPosOrig.y + yOffset);
    }

    /**
     * Return the polygon with the list of points defining the edges of the
     * object.
     *
     * @return the polygon with the list of points representing the border of the object
     */
    public Polygon getPolygon() {
        return polygon;
    }

    /**
     * Set the polygon with the list of points defining the edges of the object.
     *
     * @param polygon the polygon with the list of points representing the border of the object
     */
    public void setPolygon(Polygon polygon) {
        this.polygon = polygon;
    }

	@Override
    public void move(int xOffset, int yOffset, Dimension frameSize) {
        for (int index = 0; index < polygon.npoints; index++) {
            Point vertex = new Point(polygon.xpoints[index], polygon.ypoints[index]);

            boolean moveValid = ((vertex.x + xOffset >= 0)
                    && (vertex.y + yOffset >= 0)
                    && (vertex.x + xOffset <= frameSize.width)
                    && (vertex.y + yOffset <= frameSize.height));

            if (moveValid) {
                movePoint(xOffset, yOffset, vertex);
                polygon.xpoints[index] = vertex.x;
                polygon.ypoints[index] = vertex.y;
            }
        }

        // recompute the outer bounding box
        computeOuterBBoxCurObj();

        // if the label was changed, it means that the user touched it, therefore the segmentation is manual
        segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
    }
} public class ObjectScribble extends Objects {

    /**
     * The list of crops which together define the object.
     */
    private final List<CropObject> cropList;

    /**
     * The object map built from the crops of the object (for scribble object).
     */
    private byte[][] objectMap;

    /**
     * logger instance
     */
    private final Logger log = LoggerFactory.getLogger("gui.support.ObjectScribble");

    /**
     * Instantiates a new Object scribble.
     */
    public ObjectScribble() {
        super();
        this.cropList = new ArrayList<>();
    }

    @Override
    public void computeOuterBBoxCurObj() {
        generateObjMap();
    }

    /**
     * Returns the array of crop objects which was needed for the segmentation
     * of the object.
     *
     * @return - the list of crops for the object
     */
    public List<CropObject> getCropList() {
        return cropList;
    }

    /**
     * Adds a new crop in the list of crops needed to segment the object.
     *
     * @param cropObj - the crop object which has to be added in the list of crops needed to segment the object
     */
    public void addToCropList(CropObject cropObj) {
        this.cropList.add(cropObj);
    }

    /**
     * Returns the map of the object. It is computed based on the crop maps.
     * <p>
     * 1 = object; 0 = background
     *
     * @return - the map of the object
     */
    public byte[][] getObjectMap() {
        return objectMap;
    }

    /**
     * Generate the object map out of the object parts (crops). Compute and set
     * the outer box of the object.
     */
    public void generateObjMap() {
        byte[][] objMap;

        Rectangle objPos = computeCropsOuterBox();

        /*--------------------------STEP 1------------------------------------*/
        // create the object map with border, by merging all the crops
        byte[][] tempMap = new byte[objPos.width][objPos.height];
        int objBitCounter = 0;

        for (CropObject crop : cropList) {
            for (int y = 0; y < crop.getPositionOrig().height; y++) {
                // compute the y position in outer box coordinates
                int yPos = crop.getPositionCornerOrig().y - objPos.y + y;

                for (int x = 0; x < crop.getPositionOrig().width; x++) {
                    // compute the x position in outer box coordinates
                    int xPos = crop.getPositionCornerOrig().x - objPos.x + x;
                    // merge just the objects, not the background
                    if (crop.getObjectMap()[x][y] > (byte) 0) {
                        tempMap[xPos][yPos] = crop.getObjectMap()[x][y];
                        objBitCounter++;
                    }
                }
            }
        }

        // stop the computation if there are no object parts
        if (objBitCounter == 0) {
            return;
        }

        /*--------------------------STEP 2------------------------------------*/
        int minX = Integer.MAX_VALUE;
        int minY = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE;
        int maxY = Integer.MIN_VALUE;

        // find the rectangle which contains the exact object
        for (int y = 0; y < objPos.height; y++) {
            for (int x = 0; x < objPos.width; x++) {
                // search for the edges of the object
                if (tempMap[x][y] > 0) {
                    if (x < minX) {
                        minX = x;
                    }
                    if (y < minY) {
                        minY = y;
                    }
                    if (x > maxX) {
                        maxX = x;
                    }
                    if (y > maxY) {
                        maxY = y;
                    }
                }
            }
        }

        /*--------------------------STEP 3------------------------------------*/
        int mapXPos = objPos.x + minX;
        int mapYPos = objPos.y + minY;

        // extract only the object and create its object map
        int mapWidth = Utils.limit(0, objPos.width - (objPos.width - maxX) - minX + 1, objPos.width);
        int mapHeight = Utils.limit(0, objPos.height - (objPos.height - maxY) - minY + 1, objPos.height);
        objMap = new byte[mapWidth][mapHeight];

        // copy the exact object in the obj map
        for (int y = 0; y < mapHeight; y++) {
            for (int x = 0; x < mapWidth; x++) {
                objMap[x][y] = tempMap[minX + x][minY + y];
            }
        }

        /*--------------------------STEP TEST--------------------------------*/
        // test
        int sumObjMap = 0;
        for (int y = 0; y < mapHeight; y++) {
            for (int x = 0; x < mapWidth; x++) {
                sumObjMap += objMap[x][y];
            }
        }

        int sumTempObjMap = 0;
        for (int y = 0; y < objPos.height; y++) {
            for (int x = 0; x < objPos.width; x++) {
                sumTempObjMap += tempMap[x][y];
            }
        }

        if (sumObjMap != sumTempObjMap) {
            log.error("THE OBJECT MAP IS DIFFERENT!!!! {} obj - temp = ", (sumObjMap - sumTempObjMap));
        }

        /*--------------------------STEP 4------------------------------------*/
        // set the object outer box as the borders of the object map
        setOuterBBox(new Rectangle(mapXPos, mapYPos, mapWidth, mapHeight));

        // set the object map of the current object
        objectMap = objMap;
    }

    /**
     * Compute the outer box of the crops; the box which contains all the edges
     * of the crops.
     *
     * @return - the rectangle containing all the crops
     */
    private Rectangle computeCropsOuterBox() {
        int minX = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE;
        int minY = Integer.MAX_VALUE;
        int maxY = Integer.MIN_VALUE;

        for (CropObject cropObj : cropList) {
            Rectangle cropPosImg = cropObj.getPositionOrig();

            int xLeftCrop = cropPosImg.x;
            int xRightCrop = cropPosImg.x + cropPosImg.width;
            int yLeftCrop = cropPosImg.y;
            int yRightCrop = cropPosImg.y + cropPosImg.height;

            if (minX > xLeftCrop) {
                minX = xLeftCrop;
            }

            if (maxX < xRightCrop) {
                maxX = xRightCrop;
            }

            if (minY > yLeftCrop) {
                minY = yLeftCrop;
            }

            if (maxY < yRightCrop) {
                maxY = yRightCrop;
            }
        }

        return (new Rectangle(minX, minY, maxX - minX, maxY - minY));
    }

    @Override
    public void move(int xOffset, int yOffset, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {

        // if the outer box is selected, move the whole object; else move only the selected crop
        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);

        if (outerBBoxPanel.equals(coordPanelBox)) {
            // check to be in the image with the boundaries
            if ((outerBBox.x + xOffset >= 0)
                    && (outerBBox.y + yOffset >= 0)
                    && (outerBBox.x + outerBBox.width + xOffset <= frameSize.width)
                    && (outerBBox.y + outerBBox.height + yOffset <= frameSize.height)) {

                moveObject(xOffset, yOffset);

                // if the label was changed, it means that the user touched it, therefore the segmentation is manual
                segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
            }
        } else {
            for (CropObject cropObj : cropList) {
                Rectangle cropPanelPos = resizeRate.originalToResized(cropObj.getPositionOrig());
                if (cropPanelPos.equals(coordPanelBox)) {
                    Rectangle cropPosOrig = cropObj.getPositionOrig();

                    // check to be in the image with the boundaries
                    if ((cropPosOrig.x + xOffset >= 0)
                            && (cropPosOrig.y + yOffset >= 0)
                            && (cropPosOrig.x + cropPosOrig.width + xOffset < frameSize.width)
                            && (cropPosOrig.y + cropPosOrig.height + yOffset < frameSize.height)) {

                        cropObj.setPositionOrig(moveBox(xOffset, yOffset, cropPosOrig));
                    }
                }
            }
        }

    }

    /**
     * Moves the object completely in the specified direction (crops, scribbles,
     * outer box etc.).
     *
     * @param xOffset - how much should the object move on the X axis
     * @param yOffset - how much the object should move on the Y axis
     */
    public void moveObject(int xOffset, int yOffset) {
        // move all the crops
        cropList.stream().forEach((cropObj) -> {
            cropObj.setPositionOrig(moveBox(xOffset, yOffset, cropObj.getPositionOrig()));
        });

        // move the outer bounding box
        outerBBox = moveBox(xOffset, yOffset, outerBBox);
    }

    @Override
    public boolean remove(Rectangle coordPanelBox, Resize resizeRate) {
        // if the outer box is selected, the whole object shall be erased, therefore it will be done in the parent method; else remove only the selected crop
        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);
        if (outerBBoxPanel.equals(coordPanelBox)) {
            return false;
        } else {
            for (Iterator<CropObject> it = cropList.iterator(); it.hasNext();) {
                Rectangle cropPanelPos = resizeRate.originalToResized(it.next().getPositionOrig());
                if (cropPanelPos.equals(coordPanelBox)) {
                    it.remove();
                }
            }
            return (!cropList.isEmpty());
        }
    }

    @Override
    public void changeSize(int left, int top, int right, int bottom, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
        for (CropObject cropObj : cropList) {
            Rectangle cropPanelPos = resizeRate.originalToResized(cropObj.getPositionOrig());

            if (cropPanelPos.equals(coordPanelBox)) {
                Rectangle cropPosOrig = cropObj.getPositionOrig();

                // check to be in the image with the boundaries
                int xTopRight = cropPosOrig.x + cropPosOrig.width;
                int xBottomRight = cropPosOrig.y + cropPosOrig.height;

                boolean leftCheck = ((cropPosOrig.x + left >= 0)
                        && (cropPosOrig.x + left < xTopRight));

                boolean topCheck = ((cropPosOrig.y + top >= 0)
                        && (cropPosOrig.y + top < xBottomRight));

                boolean rightCheck = ((xTopRight + right > cropPosOrig.x)
                        && (xTopRight + right < frameSize.width));

                boolean bottomCheck = ((xBottomRight + bottom > cropPosOrig.y)
                        && (xBottomRight + bottom < frameSize.height));

                if (leftCheck && topCheck && rightCheck && bottomCheck) {
                    // change the outer bounding box size; the crop will move as well because it is the same object
                    changeSize(left, top, right, bottom, cropPosOrig);

                    // remove the scribbles which are out of the boundaries
                    removeScribbles(cropObj);

                    // if the label was changed, it means that the user touched it, therefore the segmentation is manual
                    segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
                }
            }
        }
    }

    /**
     * Changes the size of the box with the specified offsets.
     *
     * @param left - how much should the object be modified on the left side
     * @param top - how much should the object be modified on the top part
     * @param right - how much should the object be modified on the right side
     * @param bottom - how much should the object be modified on the bottom part
     * @param bboxOrigPos - the initial position of the selected box in image
     * coordinates
     */
    private void changeSize(int left, int top, int right, int bottom, Rectangle bboxOrigPos) {
        bboxOrigPos.setBounds(bboxOrigPos.x + left, bboxOrigPos.y + top, bboxOrigPos.width + right - left, bboxOrigPos.height + bottom - top);
    }

    /**
     * Removes the scribbles which no longer fit in the image; the ones which
     * would generate a coordinates outer box exception.
     *
     * @param cropObj - the object for which the scribbles have to be reviewed
     */
    private void removeScribbles(CropObject cropObj) {
        for (Iterator<ScribbleInfo> it = cropObj.getScribbleList().iterator(); it.hasNext();) {
            ScribbleInfo si = it.next();

            if ((si.getImgPosX() >= cropObj.getPositionOrig().width)
                    || (si.getImgPosY() >= cropObj.getPositionOrig().height)) {
                // remove the scribble
                it.remove();
            }

        }
    }

    @Override
    public boolean contains(Rectangle coordPanelBox, Resize resizeRate) {
        for (CropObject cropObj : cropList) {
            Rectangle cropPanelPos = resizeRate.originalToResized(cropObj.getPositionOrig());
            if (cropPanelPos.equals(coordPanelBox)) {
                return true;
            }
        }

        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);

        return outerBBoxPanel.equals(coordPanelBox);
    }

    /**
     * Returns the crop which is located at the same coordinates as the
     * specified ones.
     *
     * @param coordPanelBox - the position where the crop is located in panel coordinates
     * @param resizeRate    - the resize ratio between the image and the panel
     * @return - the crop found at the wanted location
     */
    public CropObject getCrop(Rectangle coordPanelBox, Resize resizeRate) {
        for (CropObject cropObj : cropList) {
            Rectangle cropPanelPos = resizeRate.originalToResized(cropObj.getPositionOrig());
            if (cropPanelPos.equals(coordPanelBox)) {
                return cropObj;
            }
        }

        return null;
    }
	
	@Override
    public void move(int xOffset, int yOffset, Dimension frameSize) {
        // move only if by moving we do not get out of the image
        if ((outerBBox.x + xOffset >= 0)
                && (outerBBox.y + yOffset >= 0)
                && (outerBBox.x + outerBBox.width + xOffset <= frameSize.width)
                && (outerBBox.y + outerBBox.height + yOffset <= frameSize.height)) {
            // move the outer bounding box; the crop will move as well because it is the same object
            moveBox(xOffset, yOffset, outerBBox);

            // if the label was changed, it means that the user touched it, therefore the segmentation is manual
            segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
        }
    }
} public abstract class Objects {

    /**
     * The object id in the video - used to track the object in the video
     * (constant for several frames).
     */
    private long objectId;

    /**
     * The type of segmentation used: bounding box, scribble etc.
     *
     */
    private String segmentationType;

    /**
     * The segmentation source: manual, automatic.
     */
    protected String segmentationSource;

    /**
     * The type of segmented object.
     */
    private String objType;

    /**
     * The class of segmented object.
     */
    private String objClass;

    /**
     * The value of segmented object.
     */
    private String objValue;

    /**
     * Shows if the object is occluded or not.
     */
    private String occluded;

    /**
     * The color of the displayed object.
     */
    private Color color;

    /**
     * The outer bounding box containing all the crops - panel coordinates.
     */
    protected Rectangle outerBBox;

    /**
     * The predicted coordinates of the object in the next frame.
     */
    private volatile Rectangle predictedBBox;
    /**
     * The predicted coordinates of the object in the next frame using HOG
     * features.
     */
    private volatile Rectangle hogPredictedBBox;
    /**
     * The predicted coordinates of the object in the next frame using HOG
     * features and openCV SVM.
     */
    private volatile Rectangle svmPredictedBBox;

    /**
     * The prediction is valid and can be used.
     */
    private final AtomicBoolean validPrediction;

    /**
     * The list of timings needed to complete the segmentation of the object.
     */
    protected List<Timings> labelingDuration;

    /**
     * Local user preferences.
     */
    private ObjectPreferences userPreference;


    /**
     * The thread on which the tracker is running.
     */
    private Thread trackerThread;

    /**
     * Create an instance of the Objects class.
     */
    public Objects() {
        this.labelingDuration = new ArrayList<>();
        this.predictedBBox = new Rectangle(0, 0, 0, 0);
        this.hogPredictedBBox = new Rectangle(0, 0, 0, 0);
        this.svmPredictedBBox = new Rectangle(0, 0, 0, 0);
        this.userPreference = new ObjectPreferences();
        this.validPrediction = new AtomicBoolean(false);
        
        // avoid null pointer in the object attributes
        this.objType = "";
        this.objClass = "";
        this.objValue = "";
        this.occluded = "";
    }

    /**
     * Gets the user preferences for the current object
     *
     * @return user preferences
     */
    public ObjectPreferences getUserPreference() {
        return userPreference;
    }

    /**
     * Sets the user preferences for the current object
     *
     * @param userPreference user preferences
     */
    public void setUserPreference(ObjectPreferences userPreference) {
        this.userPreference = userPreference;
    }

    /**
     * Returns the id of the object.
     *
     * @return - Returns the id of the object.
     */
    public long getObjectId() {
        return objectId;
    }

    /**
     * Sets the id of the object.
     *
     * @param objectId - the id of the object
     */
    public void setObjectId(long objectId) {
        this.objectId = objectId;
    }

    /**
     * Returns the type of object: bounding box, scribble, 3D bounding box.
     *
     * @return - the type of object
     */
    public String getSegmentationType() {
        return segmentationType;
    }

    /**
     * Sets the type of object: bounding box, scribble, 3D bounding box.
     *
     * @param segmentationType - the type of object
     */
    public void setSegmentationType(String segmentationType) {
        this.segmentationType = segmentationType;
    }

    /**
     * Returns the type of segmentation used for the current object: manual,
     * automatic etc.
     *
     * @return - the type of segmentation used for the current object
     */
    public String getSegmentationSource() {
        return segmentationSource;
    }

    /**
     * Sets the type of segmentation used for the object.
     *
     * @param segmentationSource - the segmentation used for the object: manual, automatic etc.
     */
    public void setSegmentationSource(String segmentationSource) {
        this.segmentationSource = segmentationSource;
    }

    /**
     * Set the type of the object which is being labeled.
     *
     * @param objectType - the name of the object type
     */
    public void setObjectType(String objectType) {
        this.objType = objectType;
    }

    /**
     * Return the name of the type of object being labeled.
     *
     * @return - the name of the object type
     */
    public String getObjectType() {
        return this.objType;
    }

    /**
     * Set the class of the object which is being labeled.
     *
     * @param objectClass - the name of the object class
     */
    public void setObjectClass(String objectClass) {
        this.objClass = objectClass;
    }

    /**
     * Return the name of the class of object being labeled.
     *
     * @return - the name of the object class
     */
    public String getObjectClass() {
        return this.objClass;
    }

    /**
     * Set the value of the object which is being labeled.
     *
     * @param objectValue - the name of the object value
     */
    public void setObjectValue(String objectValue) {
        this.objValue = objectValue;
    }

    /**
     * Return the name of the value of object being labeled.
     *
     * @return - the name of the object value
     */
    public String getObjectValue() {
        return objValue;
    }

    /**
     * Return the occlusion characteristic of the object. It will be defined
     * with the customer every time.
     *
     * @return - the string defining if the object is occluded or not
     */
    public String getOccluded() {
        return occluded;
    }

    /**
     * Set the occlusion of the object.
     *
     * @param occluded - the string defining if the object is occluded or not
     */
    public void setOccluded(String occluded) {
        this.occluded = occluded;
    }

    /**
     * Returns the color used to draw the object.
     *
     * @return Returns the color used to draw the object.
     */
    public Color getColor() {
        return color;
    }

    /**
     * Sets the color of the object.
     *
     * @param color - the color of the object
     */
    public void setColor(Color color) {
        this.color = color;
    }

    /**
     * Returns the rectangle which limits the entire object, the outer bounds of
     * the object - image coordinates.
     *
     * @return - the rectangle containing the object
     */
    public Rectangle getOuterBBox() {
        return outerBBox;
    }

    /**
     * Return the openCV predicted bounding box.
     *
     * @return the predicted position of the object in the next frame
     */
    public Rectangle getPredictedBBox() {
        return predictedBBox;
    }

    /**
     * Return the position of the object in the next frame, based on HOG
     * features template matching.
     *
     * @return - the position of the object in the next frame
     */
    public Rectangle getHogPredictedBBox() {
        return hogPredictedBBox;
    }

    /**
     * Set the position of the object in the next frame. The prediction is done
     * based on matching of HOG features.
     *
     * @param hogPredictedBBox - the position of the object in the next frame
     */
    public void setHogPredictedBBox(Rectangle hogPredictedBBox) {
        this.hogPredictedBBox = hogPredictedBBox;
    }

    /**
     * Return the position of the object in the next frame, based on openCV SVM
     * with HOG features.
     *
     * @return - the position of the object in the next frame
     */
    public synchronized Rectangle getSvmPredictedBBox() {
        return svmPredictedBBox;
    }

    /**
     * Set the position of the object in the next frame. The prediction is done
     * based on openCV SVM with HOG features.
     *
     * @param svmPredictedBBox - the position of the object in the next frame
     */
    public synchronized void setSvmPredictedBBox(Rectangle svmPredictedBBox) {
        this.svmPredictedBBox = svmPredictedBBox;
    }

    /**
     * Compares if the given rectangle is the same with the outer box of the
     * object; it checks if there is overlapping.
     *
     * @param rectCompare - the bounding box which will be checked for overlapping
     * @return - true if the bounding boxes overlap and false otherwise
     */
    public boolean isEqualOuterBBox(Rectangle rectCompare) {
        return outerBBox.equals(rectCompare);
    }

    /**
     * Creates a new timings object to save the time needed for the object.
     * (position 0 is the segmentation time and all the others are edit times)
     *
     * @return - the last created timing, where to map the duration of the current action (new object, edit)
     */
    public Timings newLabelingDuration() {
        Timings times = new Timings();
        this.labelingDuration.add(times);
        return labelingDuration.get(labelingDuration.size() - 1);
    }

    /**
     * *Return the current timer of the object, which is always the last one.
     *
     * @return - the current timer for the object
     */
    public Timings getCurrentLabelingDuration() {
        return labelingDuration.get(labelingDuration.size() - 1);
    }

    /**
     * Returns the list of labeling durations for the object. The first entry is
     * the labeling time and the others are coming from the edit mode.
     *
     * @return - the list of times needed for the segmentation of the object
     */
    public String getLabelingDuration() {

        StringBuilder durationString = new StringBuilder();

        labelingDuration.forEach((times) -> {
            durationString.append(times.getNeededTime()).append(",");
        });

        // remove the last comma
        if (durationString.length() > 0) {
            durationString.setLength(durationString.length() - 1);
            return durationString.toString();
        }
        return "";
    }

    /**
     * Set the labeling duration with an existent duration.
     *
     * @param labelingDuration - the new labeling duration
     */
    public void setLabelingDuration(ArrayList<Timings> labelingDuration) {
        this.labelingDuration = labelingDuration;
    }

    /**
     * Return the thread on which the tracking is done.
     *
     * @return the thread on which the tracking is done
     */
    public Thread getTrackerThread() {
        return trackerThread;
    }

    /**
     * Return true if the prediction of the object is valid and false if not.
     *
     * @return - true if the prediction of the object is valid and false if not.
     */
    public Boolean isValidPrediction() {
        return validPrediction.get();
    }

    /**
     * Set the status of the prediction: true if the prediction of the object is
     * valid and false if not.
     *
     * @param validPrediction true if the prediction of the object is valid and false if not
     */
    public void setValidPrediction(Boolean validPrediction) {
        this.validPrediction.set(validPrediction);
    }

    /**
     * Moves the whole object, together with the components to the new location
     * specified by the parameter.
     *
     * @param newPositionImg - the new location of the object - image coordinates
     * @param frameSize      the size of the original image
     */
    public void move(Point newPositionImg, Dimension frameSize) {
        int xOffset = newPositionImg.x - outerBBox.x;
        int yOffset = newPositionImg.y - outerBBox.y;

        move(xOffset, yOffset, outerBBox, new Resize(1.0, 1.0), frameSize);
    }

    /**
     * Moves the whole object, together with the components to the new location
     * specified by the parameter.
     *
     * @param newPositionPanel - the new location of the object - image coordinates
     * @param resizeRate       - the resize ratio between the image and the panel
     * @param frameSize        the size of the original image
     */
    public void move(Point newPositionPanel, Resize resizeRate, Dimension frameSize) {
        Point newPositionImg = resizeRate.resizedToOriginal(newPositionPanel);

        int xOffset = newPositionImg.x - outerBBox.x;
        int yOffset = newPositionImg.y - outerBBox.y;

        // the coordinates are already image, therefore the resize shall be 1.0.
        move(xOffset, yOffset, outerBBox, new Resize(1.0, 1.0), frameSize);
    }

    /**
     * Changes the size of the object to the new specified size (the object
     * outer box is changed!!!).
     *
     * @param newSize   the new size of the box
     * @param frameSize the size of the original image
     */
    public void changeSize(Rectangle newSize, Dimension frameSize) {
        int offsetLeft = newSize.x - outerBBox.x;
        int offsetTop = newSize.y - outerBBox.y;
        int offsetRight = (newSize.x + newSize.width) - (outerBBox.x + outerBBox.width);
        int offsetBottom = (newSize.y + newSize.height) - (outerBBox.y + outerBBox.height);

        changeSize(offsetLeft, offsetTop, offsetRight, offsetBottom, outerBBox, new Resize(1.0, 1.0), frameSize);
    }

    /**
     * Save the object predicted coordinates for the next frame
     *
     * @param newPosition predicted position in the next frame
     */
    public void setPredictedCoordinates(Point newPosition) {
        predictedBBox.setLocation(newPosition.x, newPosition.y);
    }


    /**
     * Sets the rectangle which limits the entire object, the outer bounds of
     * the object.
     *
     * @param outerBBox - the rectangle containing the object
     */
    public void setOuterBBox(Rectangle outerBBox) {
        this.outerBBox = outerBBox;
    }

	/**
     * Moves one box with the specified offset and returns the new image
     * coordinates.
     *
     * @param xOffset - how much should the object move on the X axis
     * @param yOffset - how much the object should move on the Y axis
     * @param boxPosOrig - the position of the selected box in image coordinates
     * @return - the new image coordinates of the box
     */
    protected Rectangle moveBox(int xOffset, int yOffset, Rectangle boxPosOrig) {
        boxPosOrig.setLocation(boxPosOrig.x + xOffset, boxPosOrig.y + yOffset);

        return boxPosOrig;
    }
	
    /**
     * Compute the position and size of the outer bounding box containing the
     * crops of the object.
     */
    public abstract void computeOuterBBoxCurObj();

    /**
     * Moves the object completely in the specified direction (crops, scribbles,
     * outer box etc.).
     *
     * @param xOffset       - how much should the object move on the X axis
     * @param yOffset       - how much the object should move on the Y axis
     * @param coordPanelBox - the coordinates of the selected box in image coordinates
     * @param resizeRate    - the resize rate between the image coordinates and the panel coordinates
     * @param frameSize     the size of the original image
     */
    public abstract void move(int xOffset, int yOffset, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize);

	/**
     * Moves the object completely in the specified direction (crops, scribbles,
     * outer box etc.).
     *
     * @param xOffset - how much should the object move on the X axis
     * @param yOffset - how much the object should move on the Y axis
     * @param frameSize the size of the original image
     */
    public abstract void move(int xOffset, int yOffset, Dimension frameSize);
	
    /**
     * Delete the selected box. The method checkd if the box is the same as the
     * whole object. If it is true, it will not delete it. The parent function
     * is responsible for that. This method can manage only the parts of the
     * object, not the object itself.
     *
     * @param coordPanelBox - the coordinates of the selected box in image
     * @param resizeRate    - the resize rate between the image coordinates and the panel coordinates
     * @return - true if one of the boxes was erased and false if the whole object shall be erased from outside.
     */
    public abstract boolean remove(Rectangle coordPanelBox, Resize resizeRate);

    /**
     * Changes the size of the object in each of the directions, with the amount
     * of specified pixels.
     *
     * @param left          - how much should the object be modified on the left side
     * @param top           - how much should the object be modified on the top part
     * @param right         - how much should the object be modified on the right side
     * @param bottom        - how much should the object be modified on the bottom part
     * @param coordPanelBox - the coordinates of the selected box in image coordinates
     * @param resizeRate    - the resize rate between the image coordinates and the panel coordinates
     * @param frameSize     the size of the original image
     */
    public abstract void changeSize(int left, int top, int right, int bottom, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize);

    /**
     * Verifies if the object contains the given bounding box (as a crop or as
     * the objects outer box).
     *
     * @param coordPanelBox - the coordinates of the box in panel coordinates
     * @param resizeRate    - the resize ratio between the image and the panel
     * @return - true if the object contains it; false if not
     */
    public abstract boolean contains(Rectangle coordPanelBox, Resize resizeRate);
} public final class PanelResolution {

    /**
     * logger instance
     */
    private static final Logger log = LoggerFactory.getLogger(GUIController.class);

    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors.
     *
     * Java adds an implicit public constructor to every class which does not
     * define at least one explicitly. Hence, at least one non-public
     * constructor should be defined.
     */
    private PanelResolution() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * Computes the optimal size of the panel where the image should be
     * displayed.
     *
     * @param imageSize    the size of the image to be displayed
     * @param maxPanelSize the maximum possible size of the panel where the image can be displayed
     * @return the optimal size of the panel, according to the screen resolution
     */
    public static Dimension computeOptimalPanelSize(Dimension imageSize, Dimension maxPanelSize) {
        int width = 0;
        int height = 0;
        double aspectRatio = (double) imageSize.width / (double) imageSize.height;
        Dimension optimalSize = new Dimension(imageSize);

        if ( // image bigger than panel
                ((imageSize.width > maxPanelSize.width)
                && (imageSize.height > maxPanelSize.height))
                //panel bigger than image
                || ((imageSize.width <= maxPanelSize.width)
                && (imageSize.height <= maxPanelSize.height))) {
            // image bigger than panel

            //fix height, compute width
            int tempWidth = computeWidth(maxPanelSize.height, aspectRatio);

            // check if the image fits in the panel
            if (Utils.checkPlausability(new Dimension(tempWidth, maxPanelSize.height), maxPanelSize)) {
                width = tempWidth;
                height = maxPanelSize.height;
            } else {
                //fix width, compute height
                int tempHeight = computeHeigth(maxPanelSize.width, aspectRatio);

                // check if the image fits in the panel
                if (Utils.checkPlausability(new Dimension(maxPanelSize.width, tempHeight), maxPanelSize)) {
                    width = maxPanelSize.width;
                    height = tempHeight;
                }
            }

        } else if ((imageSize.width > maxPanelSize.width)
                && (imageSize.height <= maxPanelSize.height)) {
            //image width bigger than panel width

            //fix width, compute height
            int tempHeight = computeHeigth(maxPanelSize.width, aspectRatio);

            // check if the image fits in the panel
            if (Utils.checkPlausability(new Dimension(maxPanelSize.width, tempHeight), maxPanelSize)) {
                width = maxPanelSize.width;
                height = tempHeight;
            }

        } else if ((imageSize.width <= maxPanelSize.width)
                && (imageSize.height > maxPanelSize.height)) {
            //image height bigger than panel height

            //fix height, compute width
            int tempWidth = computeWidth(maxPanelSize.height, aspectRatio);

            // check if the image fits in the panel
            if (Utils.checkPlausability(new Dimension(tempWidth, maxPanelSize.height), maxPanelSize)) {
                width = tempWidth;
                height = maxPanelSize.height;
            }
        }

        if (width > 0 && height > 0) {
            optimalSize = new Dimension(width, height);
        } else {
            log.error("The resize of the image to fit the panel failed!!!");
        }

        return optimalSize;
    }

    /**
     * Compute the height, keeping the width and aspect ratio constant.
     */
    private static int computeHeigth(int width, double aspectRatio) {
        return (int) (width / aspectRatio);
    }

    /**
     * Compute the width, keeping the height and aspect ratio constant.
     */
    private static int computeWidth(int height, double aspectRatio) {
        return (int) (height * aspectRatio);
    }
} public class DrawOptions {

    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors. Java adds an implicit public
     * constructor to every class which does not define at least one explicitly.
     * Hence, at least one non-public constructor should be defined.
     */
    private DrawOptions() {
        throw new IllegalStateException("Utility class, do not instantiate!");
    }

    /**
     * Display mouse guiding lines for better precision in segmentation. It will
     * draw an horizontal and a vertical line at the point coordinates.
     *
     * @param g2d        the graphics object
     * @param dashedLine true = the line used for drawing the line will be dashed; false = the line will be solid/plain
     * @param point      the place where the lines should intersect
     * @param bounds     the bounds of the drawing
     */
    public static void drawMouseIndicator(Graphics2D g2d, boolean dashedLine, Point point, Dimension bounds) {
        g2d.setStroke(dashedLine ? getDashedStroke() : new BasicStroke(0));

        // draw mouse indicators
        g2d.setColor(Color.yellow);
        g2d.drawLine(point.x, 0, point.x, bounds.height);
        g2d.drawLine(0, point.y, bounds.width, point.y);
    }

    /**
     * Draw a point at the specified coordinates.
     *
     * @param g2d   the graphics object
     * @param point the point to be represented
     */
    public static void drawPoint(Graphics2D g2d, Point point) {
        g2d.drawLine(point.x, point.y, point.x, point.y);
    }

    /**
     * Draw a point at the specified coordinates, with the specified color.
     *
     * @param g2d   the graphics object
     * @param point the point to be represented
     * @param color the color of the point
     */
    public static void drawPoint(Graphics2D g2d, Point point, Color color) {
        g2d.setColor(color);

        g2d.drawLine(point.x, point.y, point.x, point.y);
    }

    /**
     * Draw a line from where the mouse was clicked, to the current point.
     *
     * @param g2d          the graphics object
     * @param initialPoint the point where the line starts
     * @param endPoint     the point where the line ends
     */
    public static void drawLine(Graphics2D g2d, Point initialPoint, Point endPoint) {
        g2d.drawLine(initialPoint.x, initialPoint.y, endPoint.x, endPoint.y);
    }

    /**
     * Fill a bounding box / rectangle starting at the specified position.
     *
     * @param g2d   the graphics object
     * @param box   the rectangle specification
     * @param color the color of the rectangle
     */
    public static void fillBBox(Graphics2D g2d, Rectangle box, Color color) {
        if ((box.width > 0) && (box.height > 0)) {
            g2d.setColor(color);
            g2d.fillRect(box.x, box.y, box.width, box.height);
        }
    }

    /**
     * Draw a bounding box / rectangle starting at the specified position,
     * having the specified line (dashed or plain).
     *
     * @param g2d        the graphics object
     * @param box        the rectangle specification
     * @param color      the color of the rectangle
     * @param dashedLine true if the line used for the drawing of the rectangle should be dashed; false if the line should be solid/plain
     */
    public static void drawBBox(Graphics2D g2d, Rectangle box, Color color, boolean dashedLine) {
        g2d.setStroke(dashedLine ? getDashedStroke() : new BasicStroke(0));

        if ((box.width > 0) && (box.height > 0)) {
            g2d.setColor(color);
            g2d.drawRect(box.x, box.y, box.width, box.height);
        }

        g2d.setStroke(new BasicStroke(0));
    }

    /**
     * Draw a bounding box / rectangle starting at the specified position,
     * having the specified line (dashed or plain), and being filled as
     * specified.
     *
     * @param g2d          the graphics object
     * @param box          the rectangle specification
     * @param color        the color of the rectangle
     * @param filledBox    true if the box should be filled with color
     * @param fillingColor the color used for the filling of the rectangle
     * @param dashedLine   true if the line used for the drawing of the rectangle should be dashed; false if the line should be solid/plain
     */
    public static void drawBBox(Graphics2D g2d, Rectangle box, Color color,
            boolean filledBox, Color fillingColor, boolean dashedLine) {
        g2d.setStroke(dashedLine ? getDashedStroke() : new BasicStroke(0));

        if (filledBox) {
            // draw the fill of the rectangle    
            g2d.setColor(fillingColor);
            g2d.fillRect(box.x, box.y, box.width, box.height);
        }

        drawBBox(g2d, box, color, dashedLine);
    }

    /**
     * Draw a polygon which passes through all the points drawn by the user.
     *
     * @param g2d        the graphics object
     * @param polygon    the polygon to be drawn on the interface
     * @param color      the color of the polygon
     * @param dashedLine true = the line used for drawing the polygon will be dashed; false = the line will be solid/plain
     */
    public static void drawPolygon(Graphics2D g2d, Polygon polygon, Color color, boolean dashedLine) {
        g2d.setStroke(dashedLine ? getDashedStroke() : new BasicStroke(0));

        if (polygon.npoints > 0) {
            g2d.setColor(color);
            g2d.drawPolygon(polygon);

            // make the line thicker
            g2d.setStroke(new BasicStroke(2));

            for (int index = 0; index < polygon.npoints; index++) {
                g2d.drawLine(polygon.xpoints[index], polygon.ypoints[index],
                        polygon.xpoints[index], polygon.ypoints[index]);
            }
        }

        g2d.setStroke(new BasicStroke(0));
    }

    /**
     * Draw a polygon which passes through all the points drawn by the user.
     *
     * @param g2d          the graphics object
     * @param polygon      the polygon to be drawn on the interface
     * @param color        the color of the polygon
     * @param filledPoly   true if the box should be filled with color
     * @param fillingColor the color used for the filling of the rectangle
     * @param dashedLine   true = the line used for drawing the polygon will be dashed; false = the line will be solid/plain
     */
    public static void drawPolygon(Graphics2D g2d, Polygon polygon, Color color,
            boolean filledPoly, Color fillingColor, boolean dashedLine) {
        if (filledPoly) {
            // draw the fill of the polygon
            g2d.setColor(fillingColor);
            g2d.fillPolygon(polygon);
        }

        drawPolygon(g2d, polygon, color, dashedLine);
    }

    /**
     * Compute the coordinates of a text box and add the specified text on top
     * of a box.
     *
     * @param g2d       the graphics object
     * @param outerBox  the box on top of which the text should be written
     * @param text      the text to be displayed
     * @param color     the color of the displayed text
     * @param imageSize the size of the image (to avoid getting out of bounds)
     */
    public static void displayObjectId(Graphics2D g2d, Rectangle outerBox, String text, Color color, Dimension imageSize) {
        // draw the background with a contrasting color
        g2d.setColor(Utils.getContrastColor(color, 160));

        // get the size of the text in order to compute the size of the bkg rectangle
        Rectangle2D textSize = g2d.getFontMetrics().getStringBounds(text, g2d);

        // compute the position of the box in the image
        Point pos = computeTextLocation(outerBox, new Dimension((int) textSize.getWidth(), (int) textSize.getHeight()), imageSize);

        // draw the background
        g2d.fillRect(pos.x,
                pos.y,
                (int) textSize.getWidth() + 4,
                (int) textSize.getHeight());

        // set the color for the text
        g2d.setColor(color);
        // make the text bold
        g2d.setFont(new Font(g2d.getFont().getFontName(), Font.BOLD, g2d.getFont().getSize()));
        // write the text at the specified coordinates
        g2d.drawString(text, pos.x + 2, pos.y + (int) textSize.getHeight() - 3);
    }

    /**
     * Compute the position where a box with text shall be displayed, in such
     * way that it will not go out of the image.
     *
     * @param box       the initial position of the box
     * @param labelSize the size of the text box
     * @param bounds    the bounds where the text should fit
     * @return the position where the box shall be drawn, in order to avoid getting out of the bounds.
     */
    public static Point computeTextLocation(Rectangle box, Dimension labelSize, Dimension bounds) {
        Point pos = new Point(box.x, box.y - labelSize.height - 1);

        if (pos.x < 0) {
            pos.setLocation(0, pos.y);
        }

        if (pos.y < 0) {
            pos.setLocation(pos.x, 0);
        }

        if ((box.x + labelSize.width) >= bounds.width) {
            pos.setLocation(bounds.width - labelSize.width - 1, pos.y);
        }

        if ((box.y + labelSize.height) >= bounds.height) {
            pos.setLocation(pos.x, bounds.height - labelSize.height - 1);
        }

        return pos;
    }

    /**
     * Get the configuration of the Stroke in such way as to have a dashed line.
     *
     * @return the basic stroke representing a dashed line
     */
    public static BasicStroke getDashedStroke() {
        return new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1f, new float[]{2f}, 0f);
    }
} public class Resize {

    /* The resize ratio on width (widthImg1 / widthImg2) */
    private double ratioWidth;

    /* The resize ratio on height (heightImg1 / heightImg2) */
    private double ratioHeight;

    /* The zoomed width in pixels */
    private int zoomingWidth;

    /* The zoomed height in pixels */
    private int zoomingHeight;

    /* Zooming index */
    private int zoomingIndex;

    /* Zooming factor 10 = 10%; 5 = 20%; 4 = 25%; 2 = 50%; 1 = 100%*/
    private static final int ZOOMING_FACTOR = 4;

    /**
     * Instantiates a new Resize.
     *
     * @param ratioWidth  the ratio width
     * @param ratioHeight the ratio height
     */
    public Resize(double ratioWidth, double ratioHeight) {
        this.ratioWidth = ratioWidth;
        this.ratioHeight = ratioHeight;
    }

    /**
     * Instantiates a new Resize.
     *
     * @param ratioWidth  the ratio width
     * @param ratioHeight the ratio height
     * @param width       the width
     * @param height      the height
     */
    public Resize(double ratioWidth, double ratioHeight, int width, int height) {
        this.ratioWidth = ratioWidth;
        this.ratioHeight = ratioHeight;
        this.zoomingWidth = width;
        this.zoomingHeight = height;
        this.zoomingIndex = 0;
    }

    /**
     * Resize the width and height to fit the max display size In case the
     * provided width and height are smaller than max allowed size they will be
     * zoomed in(maximized) to max allowed size. In case the width and height
     * are bigger than max allowed size they will be zoomed out(minimized) to
     * max allowed size
     *
     * @param width  object's width
     * @param height object's height
     */
    public Resize(int width, int height) {
        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        int widthSc = gd.getDisplayMode().getWidth();
        int heightSc = gd.getDisplayMode().getHeight();
        double ratioW = (double) width / (widthSc * MAX_SCREEN_PERCENT_RESIZE);
        double ratioH = (double) height / (heightSc * MAX_SCREEN_PERCENT_RESIZE);
        double cropRatio = (double) width / (double) height;

        computeResizeRatios(ratioW, ratioH, cropRatio, width, height, widthSc, heightSc);
    }

    /**
     * Resize the window to user prefer size. In case the zoomed user preference
     * is not set, then resize the width and height to fit the max display size
     * In case the provided width and height are smaller than max allowed size
     * they will be zoomed in(maximized) to max allowed size. In case the width
     * and height are bigger than max allowed size they will be zoomed
     * out(minimized) to max allowed size
     *
     * @param width           object's width
     * @param height          object's height
     * @param userZoomedIndex user's prefer zoomed index
     */
    public Resize(int width, int height, int userZoomedIndex) {
        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        int widthSc = gd.getDisplayMode().getWidth();
        int heightSc = gd.getDisplayMode().getHeight();
        double ratioW;
        double ratioH;
        double cropRatio;

        /* did the user saved its zoomed preference? */
        if (userZoomedIndex == Integer.MAX_VALUE) {
            ratioW = (double) width / (widthSc * MAX_SCREEN_PERCENT_RESIZE);
            ratioH = (double) height / (heightSc * MAX_SCREEN_PERCENT_RESIZE);
            cropRatio = (double) width / (double) height;
        } else {
            int tmpWidth = Math.abs(width) + userZoomedIndex * Math.abs(width / ZOOMING_FACTOR);
            int tmpHeight = Math.abs(height) + userZoomedIndex * Math.abs(height / ZOOMING_FACTOR);

            ratioW = (double) width / (double) tmpWidth;
            ratioH = (double) height / (double) tmpHeight;
            cropRatio = (double) width / (double) height;
        }

        computeResizeRatios(ratioW, ratioH, cropRatio, width, height, widthSc, heightSc);
    }

    /**
     * Resize the width and height to fit the max display size In case the
     * provided width and height are smaller than max allowed size they will be
     * zoomed in(maximized) to max allowed size. In case the width and height
     * are bigger than max allowed size they will be zoomed out(minimized) to
     * max allowed size
     *
     * @param currentWidth  object's width
     * @param currentHeight object's height
     * @param newWidth      - the wanted width for the object
     * @param newHeight     - the wanted height for the object
     */
    public Resize(int currentWidth, int currentHeight, int newWidth, int newHeight) {
        // compute the ratio for width and height
        this.ratioWidth = (double) currentWidth / (double) newWidth;
        this.ratioHeight = (double) currentHeight / (double) newHeight;

        this.zoomingIndex = ((int) ((double) currentHeight / ratioHeight) - currentHeight) / Math.abs(currentHeight / ZOOMING_FACTOR);

        this.zoomingWidth = currentWidth + zoomingIndex * Math.abs(currentWidth / ZOOMING_FACTOR);
        this.zoomingHeight = currentHeight + zoomingIndex * Math.abs(currentHeight / ZOOMING_FACTOR);

        this.zoomingWidth = Utils.limit(10, this.zoomingWidth, (int) newWidth);
        this.zoomingHeight = Utils.limit(10, this.zoomingHeight, (int) newHeight);
    }

    /**
     *
     * @param ratioW max possible ratio on width, such as the image will not use
     * more than a certain percentage of the screen width
     * @param ratioH max possible ratio on height, such as the image will not
     * use more than a certain percentage of the screen height
     * @param cropRatio the ratio of the crop; the ratio between the original
     * width and height of the crop
     * @param width object's width
     * @param height object's height
     * @param widthSc the width of the screen
     * @param heightSc the height of the screen
     */
    private void computeResizeRatios(double ratioW, double ratioH, double cropRatio, int width, int height, int widthSc, int heightSc) {
        /* keep original aspect ratio            |-|     |-----|
         * 1.75 = objects allmost squared        | |  or |     |
         * < 1.75 include all objects which are  |_|     |_____|
         */
        if (cropRatio < 1.75) {
            this.ratioWidth = ratioH;
            this.ratioHeight = ratioH;
            this.zoomingIndex = ((int) ((double) height / ratioH) - height) / Math.abs(height / ZOOMING_FACTOR);
            this.zoomingWidth = width + zoomingIndex * Math.abs(width / ZOOMING_FACTOR);
            this.zoomingHeight = height + zoomingIndex * Math.abs(height / ZOOMING_FACTOR);

            this.zoomingWidth = Utils.limit(10, this.zoomingWidth, (int) (widthSc * MAX_SCREEN_PERCENT_RESIZE));
            this.zoomingHeight = Utils.limit(10, this.zoomingHeight, (int) (heightSc * MAX_SCREEN_PERCENT_RESIZE));
        } else {
            this.ratioWidth = ratioW;
            this.ratioHeight = ratioW;
            this.zoomingIndex = ((int) ((double) width / ratioW) - width) / Math.abs(width / ZOOMING_FACTOR);
            this.zoomingWidth = width + zoomingIndex * Math.abs(width / ZOOMING_FACTOR);
            this.zoomingHeight = height + zoomingIndex * Math.abs(height / ZOOMING_FACTOR);

            this.zoomingWidth = Utils.limit(10, this.zoomingWidth, (int) (widthSc * MAX_SCREEN_PERCENT_RESIZE));
            this.zoomingHeight = Utils.limit(10, this.zoomingHeight, (int) (heightSc * MAX_SCREEN_PERCENT_RESIZE));
        }
    }

    /**
     * Resizes an image to the wanted size, based on the provided resize ratio.
     * The resize is done the same for width and height.
     *
     * @param origImg - the image to be resized
     * @return - returns the new resized image
     */
    public BufferedImage resizeImage(BufferedImage origImg) {
        if ((origImg == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        int w1 = origImg.getWidth();
        int h1 = origImg.getHeight();
        int w2 = (int) (w1 / ratioWidth);
        int h2 = (int) (h1 / ratioHeight);
        float wRatio = (float) w2 / (float) w1;
        float hRatio = (float) h2 / (float) h1;

        BufferedImage workImg = new BufferedImage(w2, h2, BufferedImage.TYPE_3BYTE_BGR);

        // for down-scale
        if ((ratioWidth >= 1.0f) && (ratioHeight >= 1.0f)) {
            for (int y = 0; y < h2; y++) {
                for (int x = 0; x < w2; x++) {
                    workImg.setRGB(x, y, origImg.getRGB((int) (x * ratioWidth + 0.5f), (int) (y * ratioHeight + 0.5f)));
                }
            }
        } else {
            // for up-scale
            for (int y = 0; y < h2; y++) {
                for (int x = 0; x < w2; x++) {
                    workImg.setRGB(x, y, origImg.getRGB((int) (x / wRatio), (int) (y / hRatio)));
                }
            }
        }

        return workImg;
    }

    /**
     * Computes the corespondent position in the original image, of the given
     * pixel from the resized image.
     *
     * @param x - the X coordinate of the pixel from the resized image
     * @param y - the Y coordinate of the pixel from the resized image
     * @return - returns the corresponding point in the original image
     */
    public Point resizedToOriginal(int x, int y) {
        Point origPoint = new Point();
        origPoint.setLocation((int) (x * ratioWidth), (int) (y * ratioHeight));
        return origPoint;
    }

    /**
     * Computes the corespondent value in the original image, of the given value
     * from the resized image.
     *
     * @param value the value from the resized image
     * @return the corresponding value in the original image
     */
    public int resizedToOriginal(int value) {
        return (int) (value * ratioWidth);
    }

    /**
     * Computes the corespondent dimension in the original image, of the given
     * dimension from the resized image.
     *
     * @param origImgDim the dimension from the resized image
     * @return the corresponding dimension in the original image
     */
    public Dimension resizedToOriginal(Dimension origImgDim) {
        int width = (int) Math.round(origImgDim.width * ratioWidth);
        int height = (int) Math.round(origImgDim.height * ratioHeight);
        return new Dimension(width, height);
    }

    /**
     * Computes the corespondent rectangle in the original image, of the given
     * rectangle from the resized image.
     *
     * @param resizedRectangle the rectangle from the resized image
     * @return the corresponding rectangle in the original image
     */
    public Rectangle resizedToOriginal(Rectangle resizedRectangle) {
        Rectangle origiRectangle = new Rectangle(0, 0, 0, 0);

        int x = (int) Math.round(resizedRectangle.x * ratioWidth);
        int y = (int) Math.round(resizedRectangle.y * ratioHeight);
        int xRightPanel = resizedRectangle.x + resizedRectangle.width;
        int yRightPanel = resizedRectangle.y + resizedRectangle.height;
        int width = ((int) Math.round(xRightPanel * ratioWidth) - x);
        int height = ((int) Math.round(yRightPanel * ratioHeight) - y);

        origiRectangle.setBounds(x, y, width, height);

        return origiRectangle;
    }

    /**
     * Computes the corespondent polygon in the original image, of the given
     * polygon from the resized image.
     *
     * @param polyResized the polygon from the resized image
     * @return the corresponding polygon in the original image
     */
    public Polygon resizedToOriginal(Polygon polyResized) {
        if ((polyResized == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        Polygon polyOrig = new Polygon();

        for (int index = 0; index < polyResized.npoints; index++) {
            Point point = resizedToOriginal(polyResized.xpoints[index], polyResized.ypoints[index]);
            polyOrig.addPoint(point.x, point.y);
        }

        return polyOrig;
    }

    /**
     * Computes the corespondent position in the original image, of the given
     * pixel from the resized image.
     *
     * @param resizedPoint - the (x,y) coordinate of the pixel from the resized image
     * @return - returns a Point object with two elements: x and y coordinates of the point in the original image (in this order)
     */
    public Point resizedToOriginal(Point resizedPoint) {
        if ((resizedPoint == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        Point origPoint = new Point();
        origPoint.setLocation((int) (resizedPoint.getX() * ratioWidth), (int) (resizedPoint.getY() * ratioHeight));
        return origPoint;
    }

    /**
     * Computes the corespondent value in the resized image, of the given value
     * from the original image.
     *
     * @param value the value from the original image
     * @return the corresponding value in the resized image
     */
    public int originalToResized(int value) {
        return (int) (value / ratioWidth);
    }

    /**
     * Computes the corespondent dimension in the resized image, of the given
     * dimension from the original image.
     *
     * @param origImgDim the dimension from the original image
     * @return the corresponding dimension in the resized image
     */
    public Dimension originalToResized(Dimension origImgDim) {
        int width = (int) Math.round(origImgDim.width / ratioWidth);
        int height = (int) Math.round(origImgDim.height / ratioHeight);
        return new Dimension(width, height);
    }

    /**
     * Computes the corespondent rectangle in the resized image, of the given
     * rectangle from the original image.
     *
     * @param origRectangle the rectangle from the original image
     * @return the corresponding rectangle in the resized image
     */
    public Rectangle originalToResized(Rectangle origRectangle) {
        Rectangle resizedRectangle = new Rectangle(0, 0, 0, 0);
        int x = (int) Math.round(origRectangle.x / ratioWidth);
        int y = (int) Math.round(origRectangle.y / ratioHeight);

        int xRightOrig = origRectangle.x + origRectangle.width;
        int yRightOrig = origRectangle.y + origRectangle.height;
        int width = ((int) Math.round(xRightOrig / ratioWidth) - x);
        int height = ((int) Math.round(yRightOrig / ratioHeight) - y);

        resizedRectangle.setBounds(x, y, width, height);

        return resizedRectangle;
    }

    /**
     * Computes the corespondent polygon in the resized image, of the given
     * polygon from the original image.
     *
     * @param polyOrig the polygon from the original image
     * @return the corresponding polygon in the resized image
     */
    public Polygon originalToResized(Polygon polyOrig) {
        if ((polyOrig == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        Polygon polyResized = new Polygon();

        for (int index = 0; index < polyOrig.npoints; index++) {
            Point point = originalToResized(polyOrig.xpoints[index], polyOrig.ypoints[index]);
            polyResized.addPoint(point.x, point.y);
        }

        return polyResized;
    }

    /**
     * Computes the corespondent position in the resized image, of the given
     * pixel from the original image.
     *
     * @param x - the (x,y) coordinate of the pixel from the original image
     * @param y - the (x,y) coordinate of the pixel from the original image
     * @return - returns a Point object with two elements: x and y coordinates of the point in the resized image (in this order)
     */
    public Point originalToResized(int x, int y) {
        return originalToResized(new Point(x, y));
    }

    /**
     * Computes the corespondent position in the resized image, of the given
     * pixel from the original image.
     *
     * @param origPoint - the (x,y) coordinate of the pixel from the original image
     * @return - returns a Point object with two elements: x and y coordinates of the point in the resized image (in this order)
     */
    public Point originalToResized(Point origPoint) {
        if ((origPoint == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        Point resizedPoint = new Point();
        resizedPoint.setLocation((int) (origPoint.getX() / ratioWidth), (int) (origPoint.getY() / ratioHeight));
        return resizedPoint;
    }

    /**
     * Gets ratio width.
     *
     * @return the ratio width
     */
    public double getRatioWidth() {
        return ratioWidth;
    }

    /**
     * Gets ratio height.
     *
     * @return the ratio height
     */
    public double getRatioHeight() {
        return ratioHeight;
    }

    /**
     * Increment the width ratio with the given value.
     *
     * @param value - the value used for incrementing the width. When the value is positive, an increment happens; when the value is negative, a decrement happens.
     */
    public void incrementRatioWidth(double value) {
        ratioWidth = ratioWidth + value;
    }

    /**
     * Increment the height ratio with the given value.
     *
     * @param value - the value used for incrementing the height. When the value is positive, an increment happens; when the value is negative, a decrement happens.
     */
    public void incrementRatioHeight(double value) {
        ratioHeight = ratioHeight + value;
    }

    /**
     * Increment both the width and height ratio with the given value.
     *
     * @param value - the value used for incrementing the width and height. When the value is positive, an increment happens; when the value is negative, a decrement happens.
     */
    public void incrementRatioWidthHeight(double value) {
        ratioWidth = Math.max((ratioWidth + value), 0.1);
        ratioHeight = Math.max((ratioHeight + value), 0.1);
    }

    /**
     * Compute the zoomed width and height based on the crop resolution and
     * zooming factor
     *
     * @param crop resolution of the crop
     * @return the zoomingIndex for the current object
     */
    public int incrementWidthHeight(Dimension crop) {

        /* chek if the user zooms in or out */
        if (crop.width > 0) {
            zoomingIndex++;
        } else {
            zoomingIndex--;
        }

        /* no zooming has been made */
        if (zoomingIndex == 0) {
            zoomingWidth = Math.abs(crop.width);
            zoomingHeight = Math.abs(crop.height);
        } /* zoomed image; recompute the size of the window */ else {
            zoomingWidth = Math.abs(crop.width) + zoomingIndex * Math.abs(crop.width / ZOOMING_FACTOR);
            zoomingHeight = Math.abs(crop.height) + zoomingIndex * Math.abs(crop.height / ZOOMING_FACTOR);
        }

        /* compute the new ratios based on the new size of the window */
        ratioWidth = (double) Math.abs(crop.width) / (double) zoomingWidth;
        ratioHeight = (double) Math.abs(crop.height) / (double) zoomingHeight;

        return zoomingIndex;
    }

    /**
     * Returns true if the two objects have the same resize ratio in both
     * coordinates.
     *
     * @param resizeObj - the object with which the current object should be
     * compared with
     * @return - true when the objects match (have the same ratio on width and
     * height) and false otherwise
     */
    @Override
    public boolean equals(Object resizeObj) {
        if ((resizeObj != null) && (resizeObj instanceof Resize)) {
            Resize resize = (Resize) resizeObj;
            return ((Double.compare(this.ratioWidth, resize.ratioWidth) == 0) && (Double.compare(this.ratioHeight, resize.ratioHeight) == 0));
        }

        return false;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 67 * hash + (int) (Double.doubleToLongBits(this.ratioWidth) ^ (Double.doubleToLongBits(this.ratioWidth) >>> 32));
        hash = 67 * hash + (int) (Double.doubleToLongBits(this.ratioHeight) ^ (Double.doubleToLongBits(this.ratioHeight) >>> 32));
        return hash;
    }

    /**
     * Check if by incrementing the size of the image, it will not become bigger
     * than the screen itself.
     *
     * @param crop      resolution of the crop
     * @param screenRes - the resolution of the current screen
     * @return - true if the resize shall be done; false if by increasing the size of the image, it will generate a bigger image than the screen
     */
    public boolean isSizeIncreaseOK(Dimension crop, Dimension screenRes) {
        int w2 = zoomingWidth + (crop.width / ZOOMING_FACTOR);
        int h2 = zoomingHeight + (crop.height / ZOOMING_FACTOR);

        /* limit the zoom in to not go beyond screen's resolution */
        return ((w2 <= (screenRes.width * MAX_SCREEN_PERCENT_RESIZE)) && (h2 <= (screenRes.height * MAX_SCREEN_PERCENT_RESIZE)));
    }

    /**
     * Check if by decreasing the size of the image, it will not become smaller
     * than the minimum allowed crop resolution.
     *
     * @param crop resolution of the crop
     * @return - true if the resize shall be done; false if by increasing the size of the image, it will generate a bigger image than the screen
     */
    public boolean isSizeDecreaseOK(Dimension crop) {
        int w2 = zoomingWidth + (crop.width / ZOOMING_FACTOR);
        int h2 = zoomingHeight + (crop.height / ZOOMING_FACTOR);

        /* limit the zooming out to a minimum 10x10 pixels */
        return ((w2 > 10) && (h2 > 10));
    }

    /**
     * Gets zooming factor.
     *
     * @return the zooming Factor
     */
    public int getZoomingFactor() {
        return ZOOMING_FACTOR;
    }

    /**
     * Resize the input rectangle with the resize ratio.
     *
     * @param rectangle - the rectangle to be resized
     * @return - the resize rectangle
     */
    public Rectangle resizeBox(Rectangle rectangle) {
        Rectangle resizedBox = new Rectangle(0, 0, 0, 0);

        int x = (int) Math.round(rectangle.x / ratioWidth);
        int y = (int) Math.round(rectangle.y / ratioHeight);
        int xRightPanel = rectangle.x + rectangle.width;
        int yRightPanel = rectangle.y + rectangle.height;

        int width = ((int) Math.round(xRightPanel / ratioWidth) - x);
        int height = ((int) Math.round(yRightPanel / ratioHeight) - y);

        resizedBox.setBounds(x, y, width, height);

        return resizedBox;
    }
} public class DrawConstants {

    /**
     * Definition of the types of drawing the draw panel supports.
     */
    public enum DrawType {
        /**
         * Do not draw draw type.
         */
        DO_NOT_DRAW,        // panel used just to show image, not to draw
        /**
         * Draw point draw type.
         */
        DRAW_POINT,         // draw points on the panel
        /**
         * Draw line draw type.
         */
        DRAW_LINE,          // draw lines on the panel
        /**
         * Draw bounding box draw type.
         */
        DRAW_BOUNDING_BOX,  // draw bounding boxes on the panel
        /**
         * Draw scribble draw type.
         */
        DRAW_SCRIBBLE,      // draw scribbles on the panel
        /**
         * Draw crop draw type.
         */
        DRAW_CROP,          // crop image
        /**
         * Draw polygon draw type.
         */
        DRAW_POLYGON,       // draw a polygon made of n vertices
	/**
         * Switch to the edit polygon mode.
         */
        EDIT_POLYGON_VERTICES,
        /**
         * The Edit mode.
         */
        EDIT_MODE           // allow the user to edit the crops which were drawen at the previous step
    }
    
    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors.
     *
     * Java adds an implicit public constructor to every class which does not
     * define at least one explicitly. Hence, at least one non-public
     * constructor should be defined.
     */
    private DrawConstants() {
        throw new IllegalStateException("Utility class DrawConstants! Do not instantiate!");
    }

} public class LAttributesFrame implements Serializable {

    /**
     * The list of attributes for the frame illumination.
     */
    private ArrayList<String> illuminationList;

    /**
     * The list of attributes for the frame weather.
     */
    private ArrayList<String> weatherList;

    /**
     * The list of attributes for the frame road type.
     */
    private ArrayList<String> roadTypeList;

    /**
     * The list of attributes for the frame road event.
     */
    private ArrayList<String> roadEventList;

    /**
     * The list of attributes for the country.
     */
    private ArrayList<String> countryList;
    
    /**
     * Serial class version in form of MAJOR_MINOR_BUGFIX_DAY_MONTH_YEAR
     */
    private static final long serialVersionUID = 0x00_01_01_05_06_2017L;

    /**
     * Instantiates a new L attributes frame.
     */
    public LAttributesFrame() {
        illuminationList = new ArrayList<>();
        weatherList = new ArrayList<>();
        roadTypeList = new ArrayList<>();
        roadEventList = new ArrayList<>();
        countryList = new ArrayList<>();
    }

    /**
     * Gets illumination list.
     *
     * @return the illumination list
     */
    public ArrayList<String> getIlluminationList() {
        return illuminationList;
    }

    /**
     * Sets illumination list.
     *
     * @param illuminationList the illumination list
     */
    public void setIlluminationList(ArrayList<String> illuminationList) {
        this.illuminationList = illuminationList;
    }

    /**
     * Gets weather list.
     *
     * @return the weather list
     */
    public ArrayList<String> getWeatherList() {
        return weatherList;
    }

    /**
     * Sets weather list.
     *
     * @param weatherList the weather list
     */
    public void setWeatherList(ArrayList<String> weatherList) {
        this.weatherList = weatherList;
    }

    /**
     * Gets road type list.
     *
     * @return the road type list
     */
    public ArrayList<String> getRoadTypeList() {
        return roadTypeList;
    }

    /**
     * Sets road type list.
     *
     * @param roadTypeList the road type list
     */
    public void setRoadTypeList(ArrayList<String> roadTypeList) {
        this.roadTypeList = roadTypeList;
    }

    /**
     * Gets road event list.
     *
     * @return the road event list
     */
    public ArrayList<String> getRoadEventList() {
        return roadEventList;
    }

    /**
     * Sets road event list.
     *
     * @param roadEventList the road event list
     */
    public void setRoadEventList(ArrayList<String> roadEventList) {
        this.roadEventList = roadEventList;
    }

    /**
     * Gets country list.
     *
     * @return the country list
     */
    public ArrayList<String> getCountryList() {
        return countryList;
    }

    /**
     * Sets country list.
     *
     * @param countryList the country list
     */
    public void setCountryList(ArrayList<String> countryList) {
        this.countryList = countryList;
    }
}
import common.Constants;
import common.LTFileFilter;
import observers.ObservedActions;
import common.Timings;
import common.UserPreferences;
import common.Utils;
import graphictablet.JPenFunctions;
import gui.support.ScreenResolution;
import gui.actions.GUIController;
import common.ConstantsLabeling;
import common.ExportImage;
import common.TextAreaAppender;
import common.Icons;
import gui.support.FrameInfo;
import gui.support.DisplayBBox;
import gui.support.ObjectBBox;
import gui.support.ObjectPolygon;
import gui.support.ObjectScribble;
import gui.support.Objects;
import gui.support.PanelResolution;
import library.DrawOptions;
import library.Resize;
import paintpanels.DrawConstants;
import gui.support.LAttributesFrame;
import java.awt.Color;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import ch.qos.logback.classic.LoggerContext;
import jpen.owner.multiAwt.AwtPenToolkit;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*
 * The MIT License
 *
 * Copyright 2017 Olimpia Popica, Benone Aligica
 *
 * Contact: contact[a(t)]annotate[(d){o}t]zone
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package gui.viewer;



/**
 * The type Gui labeling tool.
 *
 * @author Olimpia Popica
 */
public class GUILabelingTool extends javax.swing.JFrame implements Observer {

    /**
     * The resolution of the current screen where the application is showed.
     */
    private transient ScreenResolution screenRes;

    /**
     * User preferences related to the application.
     */
    private transient UserPreferences userPrefs;