pynovisao.py 52.7 KB
Newer Older
1 2
#
"""
3 4 5 6
    This file must contain the implementation code for all actions of pynovisao.
    
    Name: pynovisao.py
    Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
7
"""
8
import sys
9
import gc
10
from collections import OrderedDict
11
import numpy as np
12
import os
13
import interface
14 15
import types
import cv2
16
from interface.interface import InterfaceException as IException
17
from PIL import Image
18 19
from pascal_voc_writer import Writer as wr
import shutil
20
from tkinter import *
21
import segmentation
22
import extraction
23
from extraction import FeatureExtractor
24
import time
25
import classification
26
from classification import Classifier
27

28
import util
29 30
from extraction.extractor_frame_video import ExtractFM

31
from util.config import Config
32
from util.file_utils import File
33
from util.utils import TimeUtils
34 35
from util.utils import MetricUtils
from util.x11_colors import X11Colors
36
import functools as ft
37 38
import multiprocessing
from multiprocessing import Process, Manager
39
import threading as th
40
from tqdm import tqdm
41
import psutil
42
class Act(object):
43
    """Store all actions of Pynovisao."""
44

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
45
    def __init__(self, tk, args):
46 47 48 49 50 51 52 53 54
        """Constructor.

        Parameters
        ----------
        tk : Interface
            Pointer to interface that handles UI.
        args : Dictionary
            Arguments of program.
        """
55
        self.tk = tk
56
        self.has_trained = False
57 58 59
        
        self.segmenter = [segmentation._segmenter_list[segmenter].meta for segmenter in segmentation._segmenter_list
                            if segmentation._segmenter_list[segmenter].value == True ][0]()
60 61 62
        
        self.extractors = [extraction._extractor_list[extractor].meta for extractor in extraction._extractor_list
                            if extraction._extractor_list[extractor].value == True ]
63 64 65 66 67 68
        
        try:
            self.classifier = [classification._classifier_list[classifier].meta for classifier in classification._classifier_list
                                if classification._classifier_list[classifier].value == True ][0]()
        except:
            self.classifier = None
69

70 71
        self._image = None
        self._const_image = None
72
        self._mask_image = None
73
        self._image_name = None
74
        self._image_path = None
75
        self._xml_file = None
76 77 78 79 80 81 82
        current_path=os.getcwd()
        current_path=current_path[-1::-1]
        current_path=current_path[3::1]
        current_path=current_path[-1::-1]
        current_path=current_path+"data"
        self._img_folder=current_path+"/images"
        self._seg_folder=current_path+"/demo"
83
                    
84 85
        self._init_dataset(args["dataset"])
        self._init_classes(args["classes"], args["colors"])
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
86

87
        self._dataset_generator = True
88 89
        self._ground_truth = False
        self._gt_segments = None
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
90
        self.weight_path = None
91

92
    
93
    def _init_dataset(self, directory):
94 95 96 97 98 99 100
        """Initialize the directory of image dataset.

        Parameters
        ----------
        directory : string
            Path to directory.
        """
101 102 103 104
        if(directory[-1] == '/'):
            directory = directory[:-1]
            
        self.dataset = directory
105
        File.create_dir(self.dataset)
106
    
107
    def _init_classes(self, classes = None, colors = None):
108 109 110 111 112 113 114 115 116 117
        """Initialize the classes of dataset.

        Parameters
        ----------
        classes : list of string, optional, default = None
            List of classes. If not informed, the metod set as classes all classes in dataset. 
            If there's no classes in dataset, adds two default classes.
        colors : list of string, optional, default = None
            List de colors representing the color of classe, in same order. If not informed, chooses a color at random.
        """
118
        self.classes = []
119 120 121 122 123 124 125 126 127 128 129 130 131 132

        dataset_description_path = File.make_path(self.dataset, '.dataset_description.txt')

        if os.path.exists(dataset_description_path):
            colors = []
            classes = []
            file = open(dataset_description_path, "r") 
            for line in file:
                class_info = line.replace("\n", "").split(",")
                classes.append(class_info[0])
                colors.append(class_info[1])                 
        else:
            classes = sorted(File.list_dirs(self.dataset)) if classes is None else classes.split()
            colors = [] if colors is None else colors.split()
133 134 135 136 137 138 139

        if(len(classes) > 0):
            for i in range(0, len(classes)):
                self.add_class(dialog = False, name=classes[i], color=colors[i] if i < len(colors) else None)
        else:
            self.add_class(dialog = False, color='Green')
            self.add_class(dialog = False, color='Yellow')
140
                
141 142
        self._current_class = 0

143
    def open_image(self, imagename = None):
144
        """Open a new image and starts a new XML instance for such image.
145 146 147 148 149 150

        Parameters
        ----------
        imagename : string, optional, default = None
            Filepath of image. If not informed open a dialog to choose.
        """
151 152
        
        def onclick(event):
153
            """Binds dataset generator event to click on image."""
154
            print(event)
155
            if event.xdata != None and event.ydata != None and int(event.ydata) != 0 and self._dataset_generator == True:
156 157
                x = int(event.xdata)
                y = int(event.ydata)
158 159
                self.tk.write_log("Coordinates: x = %d y = %d", x, y)
                
160
                segment, size_segment, self._xml_file, idx_segment, run_time = self.segmenter.get_segment(x, y, self._xml_file, self.classes[self._current_class]["name"].value)
161 162 163 164
                
                if size_segment > 0:
                    self.tk.append_log("\nSegment = %d: %0.3f seconds", idx_segment, run_time)
                    
165
                    self._image, run_time = self.segmenter.paint_segment(self._image, self.classes[self._current_class]["color"].value, x, y)
166
                    self.tk.append_log("Painting segment: %0.3f seconds", run_time)
167
                    self.tk.refresh_image(self._image)
168
                    
169 170 171 172
                    if self._ground_truth == True:
                        self._gt_segments[idx_segment] = self.classes[self._current_class]["name"].value

                    elif self._dataset_generator == True:
173
                        filepath = File.save_class_image(segment, self.dataset, self.classes[self._current_class]["name"].value, self._image_name, idx_segment)
174 175
                        if filepath:
                            self.tk.append_log("\nSegment saved in %s", filepath)
176 177
        if imagename is None:
            imagename = self.tk.utils.ask_image_name()
178 179

        if imagename:
180 181
            self._image = File.open_image(imagename)
            self._image_name = File.get_filename(imagename)
182
            self._xml_file=wr(self._image_name,self._image.shape[0],self._image.shape[1])
183 184 185
            self.tk.write_log("Opening %s...", self._image_name)
            self.tk.add_image(self._image, self._image_name, onclick)
            self._const_image = self._image
186

187
            self.segmenter.reset()
188
            self._gt_segments = None
189

190
        
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
191 192 193 194

    def open_weight(self):
        """Open a new weight."""
        self.weight_path = self.tk.utils.ask_weight_name()
195
        self.classifier.weight_path = self.weight_path
196
        print(self.weight_path)
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
197
        
198
    def restore_image(self):
199 200
        """Refresh the image and clean the segmentation.
        """
201 202 203 204
        if self._const_image is not None:
            self.tk.write_log("Restoring image...")
            self.tk.refresh_image(self._const_image)
            
205
            self.segmenter.reset()
206
            self._gt_segments = None
207 208
        
    def close_image(self):
209
        """Close the image.
210
        
211 212 213 214 215
        Raises
        ------
        IException 'Image not found'
            If there's no image opened.
        """
216
        if self._const_image is None:
217
            raise IException("Image not found!  Open an image to test, select in the menu the option File>Open Image!")
218 219 220
        
        if self.tk.close_image():
            self.tk.write_log("Closing image...")
221
            self._const_image = None
222
            self._image = None
223
            self._image_path = None
224 225

    def add_class(self, dialog = True, name = None, color = None):
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
        """Add a new class.

        Parameters
        ----------
        dialog : boolean, optional, default = True
            If true open a config dialog to add the class.
        name : string, optional, default = None
            Name of class. If not informed set the name 'Class_nn' to class.
        color : string, optional, default = None
            Name of color in X11Color format, representing the class. It will used to paint the segments of class.
            If not informed choose a color at random.
            
        Raises
        ------
        IException 'You have reached the limite of %d classes'
            If you already have created self.tk.MAX_CLASSES classes.
        """
243 244 245
        n_classes = len(self.classes)
        if n_classes >= self.tk.MAX_CLASSES:
            raise IException("You have reached the limite of %d classes" % self.tk.MAX_CLASSES)
246
                
247
        def edit_class(index):
248
            """Calls method that edit the class."""
249
            self.edit_class(index)
250 251
            
        def update_current_class(index):
252
            """Calls method that update the class."""
253
            self.update_current_class(index)
254 255
        
        def process_config():
256
            """Add the class and refresh the panel of classes."""
257
            new_class = self.tk.get_config_and_destroy()
258
            new_class["name"].value = '_'.join(new_class["name"].value.split())
259 260 261

            self.classes.append( new_class )
            self.tk.write_log("New class: %s", new_class["name"].value)
262
            self.tk.refresh_panel_classes(self.classes, self._current_class)
263
            
264 265
        if name is None:
            name = "Class_%02d" % (n_classes+1)
266
        if color is None:
267
            color = util.X11Colors.random_color()
268 269
            
        class_config = OrderedDict()
270
        class_config["name"] = Config(label="Name", value=name, c_type=str)
271
        class_config["color"] = Config(label="Color (X11 Colors)", value=color, c_type='color')
272 273
        class_config["callback"] = Config(label=None, value=update_current_class, c_type=None, hidden=True)
        class_config["callback_color"] = Config(label=None, value=edit_class, c_type=None, hidden=True)
274 275 276 277 278 279 280
        class_config["args"] = Config(label=None, value=n_classes, c_type=int, hidden=True)
        
        if dialog == False:
            self.classes.append( class_config )
            return 

        title = "Add a new classe"
281 282 283
        self.tk.dialogue_config(title, class_config, process_config)        
      

284
    def edit_class(self, index):
285 286 287 288 289 290 291
        """Edit a class.

        Parameters
        ----------
        index : integer.
            Index of class in list self.classes.
        """
292
        def process_update(index):
293
            """Update the class."""
294
            updated_class = self.tk.get_config_and_destroy()
295
            updated_class["name"].value = '_'.join(updated_class["name"].value.split())
296 297 298
            
            self.classes[index] = updated_class
            self.tk.write_log("Class updated: %s", updated_class["name"].value)
299
            self.tk.refresh_panel_classes(self.classes, self._current_class)
300 301 302 303 304 305
        
        current_config = self.classes[index]
            
        title = "Edit class %s" % current_config["name"].value
        self.tk.dialogue_config(title, current_config, lambda *_ : process_update(index))
            
306
    def update_current_class(self, index):
307 308
        """Update the current class.
        """
309
        self._current_class = index
310 311
        
    def get_class_by_name(self, name):
312 313 314 315
        """Return the index for class.
        
        Parameters
        ----------
316
        name : string
317 318 319 320
            Name of class.
            
        Returns
        -------
321
        index : integer
322 323 324 325 326 327 328
            Index of class in list self.classes.

        Raises
        ------
        Exception 'Class not found'
            If name not found in self.classes.
        """
329 330 331 332 333 334
        name = name.strip()
        
        for cl in self.classes:
            if cl["name"].value == name:
                return cl
        raise Exception("Class not found")
335

336
        
337
    def set_dataset_path(self):
338 339
        """Open a dialog to choose the path to directory of image dataset.
        """
340 341
        directory = self.tk.utils.ask_directory(default_dir = self.dataset)
        if directory:
342
            self._init_dataset(directory)
343 344
            self.tk.write_log("Image dataset defined: %s", self.dataset)
            
345
            self._init_classes()
346
            self.tk.refresh_panel_classes(self.classes)
347
            
348
            if self.classifier: self.classifier.reset()
349
        self.has_trained=False
350
            
351
    def toggle_dataset_generator(self):
352 353
        """Enable/disable the dataset generator on click in image.
        """
354
        self._dataset_generator = not self._dataset_generator
355

356 357
            
    def select_segmenter(self):
358 359
        """Open a dialog to choose the segmenter.
        """
360 361
        title = "Choosing a segmenter"
        self.tk.write_log(title)
362

363
        current_config = segmentation.get_segmenter_config()
364
        
365
        def process_config():
366
            """Update the current segmenter."""
367
            new_config = self.tk.get_config_and_destroy()
368

369 370 371
            self.segmenter = [new_config[segmenter].meta for segmenter in new_config
                                if new_config[segmenter].value == True ][0]()

372
            self.tk.append_log("\nSegmenter: %s\n%s", str(self.segmenter.get_name()), str(self.segmenter.get_summary_config()))
373 374 375 376 377
            segmentation.set_segmenter_config(new_config)

        self.tk.dialogue_choose_one(title, current_config, process_config)

    def config_segmenter(self):
378 379
        """Open a dialog to configure the current segmenter.
        """
380 381 382 383 384 385
        title = "Configuring %s" % self.segmenter.get_name()
        self.tk.write_log(title)

        current_config = self.segmenter.get_config()
        
        def process_config():
386
            """Update the configs of current segmenter."""
387 388 389
            new_config = self.tk.get_config_and_destroy()

            self.segmenter.set_config(new_config)
390
            self.tk.append_log("\nConfig updated:\n%s", str(self.segmenter.get_summary_config()))
391
            self.segmenter.reset()
392 393

        self.tk.dialogue_config(title, current_config, process_config)
394
        
395
    def run_segmenter(self, refresh_image=True):
396
        """Do the segmentation of image, using the current segmenter.
397
        
398 399 400 401 402
        Raises
        ------
        IException 'Image not found'
            If there's no image opened.
        """
403
        if self._const_image is None:
404
            raise IException("Image not found!  Open an image to test, select in the menu the option File>Open Image!")
405
        
406
        self.tk.write_log("Running %s...", self.segmenter.get_name())
407 408 409 410 411

        self.tk.append_log("\nConfig: %s", str(self.segmenter.get_summary_config()))
        self._image, run_time = self.segmenter.run(self._const_image)
        self.tk.append_log("Time elapsed: %0.3f seconds", run_time)
        
412 413
        self._gt_segments = [None]*(max(self.segmenter.get_list_segments())+1)
        
414 415
        if refresh_image:
            self.tk.refresh_image(self._image)
416 417


418
    def select_extractors(self):
419
        """Open a dialog to select the collection of extractors.
420
        
421 422 423 424 425
        Raises
        ------
        IException 'Please select at least one extractor'
            If no extractor was selected.
        """
426 427 428 429 430 431
        title = "Selecting extractors"
        self.tk.write_log(title)

        current_config = extraction.get_extractor_config()
        
        def process_config():
432
            """Update the collection of extractors."""
433 434 435 436
            new_config = self.tk.get_config_and_destroy()

            self.extractors = [new_config[extractor].meta for extractor in new_config
                                if new_config[extractor].value == True ]
437
            
438
            if len(self.extractors) == 0:
439
                raise IException("Please select an extractor from the menu under Features Extraction> Select extractors! ")
440
            
441 442 443 444 445 446
            self.tk.append_log("\nConfig updated:\n%s", 
                                '\n'.join(["%s: %s" % (new_config[extractor].label, "on" if new_config[extractor].value==True else "off")
                                            for extractor in new_config]))
            extraction.set_extractor_config(new_config)

        self.tk.dialogue_select(title, current_config, process_config)
447 448
        
    def run_extractors(self):
449 450
        """Perform a feature extraction on all images of dataset, using the current collection of extractors.
        """
451
        self.tk.write_log("Running extractors on all images in %s", self.dataset)
452 453
        self.tk._root.update_idletasks()
        fextractor = FeatureExtractor(self.extractors,self.tk)
454 455
        self.tk.append_log("%s", '\n'.join([extraction._extractor_list[extractor].label for extractor in extraction._extractor_list
                                                if extraction._extractor_list[extractor].value == True ]))
456
        
457
        output_file, run_time = fextractor.extract_all(self.dataset, "training")
458 459
        self.tk.append_log("\nOutput file saved in %s", output_file)
        self.tk.append_log("Time elapsed: %0.3f seconds", run_time)
460 461
        
        if self.classifier: self.classifier.reset()
462

463 464 465 466 467
    def run_extract_frame(self):
        self.tk.write_log("Running extract frames from videos")
        extract_frame=ExtractFM()
        extract_frame.run(self.tk)

468
    def select_classifier(self):
469 470 471 472 473 474 475
        """Open a dialog to select the classifier.
        
        Raises
        ------
        IException 'You must install python-weka-wrapper'
            The user must install the required dependencies to classifiers.
        """
476
        if self.classifier is None:
477
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
478 479 480 481 482
        
        title = "Choosing a classifier"
        self.tk.write_log(title)

        current_config = classification.get_classifier_config()
483

484 485
        
        def process_config():
486
            """Update the current classifier."""
487
            new_config = self.tk.get_config_and_destroy()
Geazy Menezes's avatar
Geazy Menezes committed
488
            
489 490 491 492 493 494 495 496 497
            self.classifier = [new_config[classifier].meta for classifier in new_config
                                if new_config[classifier].value == True ][0]()

            self.tk.append_log("\nClassifier: %s\n%s", str(self.classifier.get_name()), str(self.classifier.get_summary_config()))
            classification.set_classifier_config(new_config)

        self.tk.dialogue_choose_one(title, current_config, process_config)
        
    def configure_classifier(self):
498 499 500 501 502 503 504
        """Set the configuration of current classifier.
        
        Raises
        ------
        IException 'You must install python-weka-wrapper'
            The user must install the required dependencies to classifiers.
        """
505
        if self.classifier is None:
506
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
507 508 509 510 511 512 513 514 515 516 517
        
        title = "Configuring %s" % self.classifier.get_name()
        self.tk.write_log(title)

        current_config = self.classifier.get_config()
        
        def process_config():
            new_config = self.tk.get_config_and_destroy()

            self.classifier.set_config(new_config)
            self.tk.append_log("\nConfig updated:\n%s", str(self.classifier.get_summary_config()))
518 519
            
            if self.classifier: self.classifier.reset()
520 521 522 523 524

        self.tk.dialogue_config(title, current_config, process_config)
    
    
    def run_classifier(self):
525 526
        """Run the classifier on the current image.
        As result, paint the image with color corresponding to predicted class of all segment.
527

528 529 530 531 532 533 534
        Raises
        ------
        IException 'You must install python-weka-wrapper'
            The user must install the required dependencies to classifiers.
        IException 'Image not found'
            If there's no image opened.
        """
535
        if self.classifier is None:
536 537
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")

538
        if self._const_image is None:
539 540
            raise IException("Image not found!  Open an image to test, select in the menu the option File>Open Image!")

541 542
        self.tk.write_log("Running %s...", self.classifier.get_name())
        self.tk.append_log("\n%s", str(self.classifier.get_summary_config()))
543

544
        #self.classifier.set
545

546 547
        start_time = TimeUtils.get_time()

548
        # Perform a segmentation, if needed.
549 550 551
        list_segments = self.segmenter.get_list_segments()
        if len(list_segments) == 0:
            self.tk.append_log("Running %s... (%0.3f seconds)", self.segmenter.get_name(), (TimeUtils.get_time() - start_time))
552

553
            self._image, _ = self.segmenter.run(self._const_image)
554
            self.tk.refresh_image(self._image)
555
            list_segments = self.segmenter.get_list_segments()
556
            self._gt_segments = [None]*(max(list_segments)+1)
557

558
        #  New and optimized classification
559
        tmp = ".tmp"
560
        File.remove_dir(File.make_path(self.dataset, tmp))
561

562
        self.tk.append_log("Generating test images... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
563

564
        len_segments = {}
565 566 567 568 569 570 571 572 573 574 575

        print("Wait to complete processes all images!")
        with tqdm(total=len(list_segments)) as pppbar:
            for idx_segment in list_segments:
                segment, size_segment, idx_segment = self.segmenter.get_segment(self, idx_segment=idx_segment)[:-1]
                # Problem here! Dataset removed.
                filepath = File.save_only_class_image(segment, self.dataset, tmp, self._image_name, idx_segment)
                len_segments[idx_segment] = size_segment
                pppbar.update(1)
            pppbar.close()

576

577
        # Perform the feature extraction of all segments in image ( not applied to ConvNets ).
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
578
        if self.classifier.must_extract_features():
579 580
            self.tk.append_log("Running extractors on test images... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
            fextractor = FeatureExtractor(self.extractors)
581
            output_file, _ = fextractor.extract_all(self.dataset, "test", dirs=[tmp])
582

583
        self.tk.append_log("Running classifier on test data... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
584 585

        # Get the label corresponding to predict class for each segment of image.
586 587
        labels = self.classifier.classify(self.dataset, test_dir=tmp, test_data="test.arff", image=self._const_image)
        File.remove_dir(File.make_path(self.dataset, tmp))
588

589 590 591
        # Result is the class for each superpixel
        if type(labels) is types.ListType:
            self.tk.append_log("Painting segments... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
592

593 594 595
            # If ground truth mode, show alternative results
            if self._ground_truth == True:
                return self._show_ground_truth(list_segments, len_segments, labels, start_time)
596

597 598
            # Create a popup with results of classification.
            popup_info = "%s\n" % str(self.classifier.get_summary_config())
599

600 601
            len_total = sum([len_segments[idx] for idx in len_segments])
            popup_info += "%-16s%-16s%0.2f%%\n" % ("Total", str(len_total), (len_total*100.0)/len_total)
602

603 604 605 606 607
            # Paint the image.
            self._mask_image = np.zeros(self._const_image.shape[:-1], dtype="uint8")
            height, width, channels = self._image.shape
            self.class_color = np.zeros((height,width,3), np.uint8)
            for (c, cl) in enumerate(self.classes):
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
608
                idx_segment = [ list_segments[idx] for idx in range(0, len(labels)) if cl["name"].value == labels[idx] or c == labels[idx]]
609 610 611 612 613
                if len(idx_segment) > 0:
                    self._image, _ = self.segmenter.paint_segment(self._image, cl["color"].value, idx_segment=idx_segment, border=False)
                    for idx in idx_segment:
                        self._mask_image[self.segmenter._segments == idx] = c
                        self.class_color[self.segmenter._segments == idx] = X11Colors.get_color(cl["color"].value)
614

615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
                len_classes = sum([len_segments[idx] for idx in idx_segment])
                popup_info += "%-16s%-16s%0.2f%%\n" % (cl["name"].value, str(len_classes), (len_classes*100.0)/len_total)


            self.tk.refresh_image(self._image)
            self.tk.popup(popup_info)
        else:
            # Result is an image
            self._mask_image = labels
            height, width, channels = self._image.shape
            self.class_color = np.zeros((height,width,3), np.uint8)

            for (c, cl) in enumerate(self.classes):
                self.class_color[labels == c] = X11Colors.get_color(cl["color"].value)

            self._image = cv2.addWeighted(self._const_image, 0.7, self.class_color, 0.3, 0)
            self.tk.refresh_image(self._image)
632

633

634
        end_time = TimeUtils.get_time()
635

636 637
        self.tk.append_log("\nClassification finished")
        self.tk.append_log("Time elapsed: %0.3f seconds", (end_time - start_time))
638
        gc.collect()
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
639 640 641

    def run_training(self):
        start_time = TimeUtils.get_time()
642
        
643 644 645
        # Training do not need an image opened (consider removing these two lines)
        #      if self._const_image is None:
        #          raise IException("Image not found")
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
646 647 648 649 650 651 652 653 654 655
        
        if self.classifier.must_train():
            
            if self.classifier.must_extract_features():
                self.tk.append_log("Creating training data... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
                fextractor = FeatureExtractor(self.extractors)
                output_file, run_time = fextractor.extract_all(self.dataset, "training", overwrite = False)
        
            self.tk.append_log("Training classifier...")
            
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
656
            self.classifier.train(self.dataset, "training")
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
657 658 659 660

            self.tk.append_log("DONE (%0.3f seconds)",  (TimeUtils.get_time() - start_time))
        
        self._image = self._const_image
661
        self.has_trained=True
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
662

663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
    
    def _show_ground_truth(self, list_segments, len_segments, labels, start_time):
        """Paint only wrong classified segments and show ground truth confusion matrix.
        
        Parameters
        ----------
        list_segments : list of integer
            List of index segments.
        len_segments : list of integer
            List of segments sizes.
        labels : list of string
            List of predicted class name for each segment.
        start_time : floating point
            Start time of classification.
        """
        classes = list(set(labels))
        classes.sort()
        
        n_segments = len(labels)
        spx_matrix = np.zeros((len(classes), len(classes)), np.int) 
        px_matrix = np.zeros((len(classes), len(classes)), np.int) 

        # Create the confusion matrix and paint wrong classified segments individually.
        for idx_segment in list_segments:
            if self._gt_segments[idx_segment] is not None:
                gt_class = classes.index(self._gt_segments[idx_segment])
                predicted_class = classes.index(labels[idx_segment])
                
                spx_matrix[ gt_class ][ predicted_class ] += 1
                px_matrix[ gt_class ][ predicted_class ] += len_segments[idx_segment]
        
                if gt_class != predicted_class:
                    self._image, _ = self.segmenter.paint_segment(self._image, self.get_class_by_name(labels[idx_segment])["color"].value, idx_segment=[idx_segment], border=False)
        
        # Create a popup with results of classification.
        popup_info = "%s\n" % str(self.classifier.get_summary_config())
        popup_info += Classifier.confusion_matrix(classes, spx_matrix, "Superpixels")
        popup_info += Classifier.confusion_matrix(classes, px_matrix, "PixelSum")
        
        self.tk.refresh_image(self._image)
        self.tk.popup(popup_info)
704

705 706 707 708 709
        end_time = TimeUtils.get_time()
            
        self.tk.append_log("\nClassification finished")
        self.tk.append_log("Time elapsed: %0.3f seconds", (end_time - start_time))
        
710

711 712 713 714 715
    def toggle_ground_truth(self):
        """Enable/disable ground truth mode.
        """
        self._ground_truth = not self._ground_truth
        
716
    def cross_validation(self):
717 718 719 720 721 722 723
        """Run a cross validation on all generated segments in image dataset.
        
        Raises
        ------
        IException 'You must install python-weka-wrapper'
            The user must install the required dependencies to classifiers.
        """
724
        if self.classifier is None:
725
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
726 727 728 729 730 731 732 733 734 735 736 737 738 739
        
        if self.classifier.must_train():
            self.tk.write_log("Creating training data...")
            
            fextractor = FeatureExtractor(self.extractors)
            output_file, run_time = fextractor.extract_all(self.dataset, "training", overwrite = False)
            self.classifier.train(self.dataset, "training")
        
        self.tk.write_log("Running Cross Validation on %s...", self.classifier.get_name())
        self.tk.append_log("\n%s", str(self.classifier.get_summary_config()))
        
        popup_info = self.classifier.cross_validate()
        self.tk.append_log("Cross Validation finished")
        self.tk.popup(popup_info)
740 741
        
    def experimenter_all(self):
742 743 744 745 746 747 748
        """Perform a test in all availabel classifiers e show the results.
        
        Raises
        ------
        IException 'You must install python-weka-wrapper'
            The user must install the required dependencies to classifiers.
        """
749
        if self.classifier is None:
750
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
751 752 753 754
        
        if self.tk.ask_ok_cancel("Experimenter All", "This may take several minutes to complete. Are you sure?"):
            if self.classifier.must_train():
                self.tk.write_log("Creating training data...")
755

756 757 758 759 760 761 762 763 764
                fextractor = FeatureExtractor(self.extractors)
                output_file, run_time = fextractor.extract_all(self.dataset, "training", overwrite = False)
                self.classifier.train(self.dataset, "training")
                
            self.tk.write_log("Running Experimenter All on %s...", self.classifier.get_name())
            
            popup_info = self.classifier.experimenter()
            self.tk.append_log("\nExperimenter All finished")
            self.tk.popup(popup_info)
765 766


767
    def about(self):
768
        self.tk.show_info("Pynovisao\n\nVersion 1.1.0\n\nAuthors:\nAdair da Silva Oliveira Junior\nAlessandro dos Santos Ferreira\nDiego Andre Sant Ana\nDiogo Nunes Goncalves\nEverton Castelao Tetila\nFelipe Silveira\nGabriel Kirsten Menezes\nGilberto Astolfi\nHemerson Pistori\nNicolas Alessandro de Souza Belete\nFabio Prestes Cesar Rezende\nJoao Vitor de Andrade Porto")
769
    def func_not_available(self):
770
        """Use this method to bind menu options not available."""
771
        self.tk.write_log("This functionality is not available right now.")
772

773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876
    def assign_using_labeled_image(self, imagename = None, refresh_image=True):
        """Open a new image.

        Parameters
        ----------
        imagename : string, optional, default = None
            Filepath of image. If not informed open a dialog to choose.
        """

        if len(self.segmenter.get_list_segments()) == 0:
            self.tk.write_log("Error: Image not segmented")
            return

        if self._image is None:
            self.tk.write_log("Error: Open the image to be targeted")
            return

        if imagename is None:
            imagename = self.tk.utils.ask_image_name()

        if imagename:
            self._image_gt = File.open_image_lut(imagename)
            self._image_gt_name = File.get_filename(imagename)

            self.tk.write_log("Opening %s...", self._image_gt_name)

            qtd_classes = len(self.classes)
            qtd_superpixel = len(self.segmenter.get_list_segments())

        tam_gt = self._image_gt.shape
        tam_im = self._image.shape
        if len(tam_gt) > 2:
            self.tk.write_log("Color image is not supported. You must open a gray-scale image")
            return

        if tam_gt[0] != tam_im[0] or tam_gt[1] != tam_im[1]:
            self.tk.write_log("Images with different sizes")
            return
            
        #hist_classes_superpixels = np.zeros((qtd_superpixel, qtd_classes), np.int)      
    
        #for i in range(0, tam_gt[0]):
        #    for j in range(0, tam_gt[1]):          
        #        class_pixel = self._image_gt[i,j]
        #        if class_pixel > qtd_classes:
        #            self.tk.write_log("There is no class for the pixel [%d,%d] = %d on the image", i, j, class_pixel)
        #        else:
        #            #segment, size_segment, idx_segment, run_time = self.segmenter.get_segment(px = j, py = i)
        #            idx_segment = self.segmenter._segments[i, j]
        #            hist_classes_superpixels[idx_segment, class_pixel] = hist_classes_superpixels[idx_segment, class_pixel] + 1
        #    if i % 10 == 0:
        #        self.tk.write_log("Annotating row %d of %d", i, tam_gt[0])
                
        qtd_bad_superpixels = 0
        
        for idx_segment in range(0, qtd_superpixel):
            hist_classes_superpixels = np.histogram(self._image_gt[self.segmenter._segments == idx_segment], bins=range(0,len(self.classes)+1))[0]

            idx_class = np.argmax(hist_classes_superpixels)
            sum_vector = np.sum(hist_classes_superpixels)
            if refresh_image:
                self._image, run_time = self.segmenter.paint_segment(self._image, self.classes[idx_class]["color"].value, idx_segment = [idx_segment])
            #self.tk.append_log("posicao maior = %x  --  soma vetor %d", x, sum_vector)
            if hist_classes_superpixels[idx_class]/sum_vector < 0.5:
                qtd_bad_superpixels = qtd_bad_superpixels + 1

            if self._ground_truth == True:
                self._gt_segments[idx_segment] = self.classes[self._current_class]["name"].value

            elif self._dataset_generator == True:
                if idx_segment % 10 == 0:
                    self.tk.write_log("Saving %d of %d", (idx_segment+1), qtd_superpixel)

                segment, size_segment, idx_segment, run_time = self.segmenter.get_segment(idx_segment = idx_segment)
                filepath = File.save_class_image(segment, self.dataset, self.classes[idx_class]["name"].value, self._image_name, idx_segment)
                if filepath:
                    self.tk.append_log("\nSegment saved in %s", filepath)

        self.tk.refresh_image(self._image)
        self.tk.write_log("%d bad annotated superpixels of %d superpixel (%0.2f)", qtd_bad_superpixels, qtd_superpixel, (float(qtd_bad_superpixels)/qtd_superpixel)*100)



    def run_segmenter_folder(self, foldername=None):

        if foldername is None:
            foldername = self.tk.utils.ask_directory()

        valid_images_extension = ['.jpg', '.png', '.gif', '.jpeg', '.tif']

        fileimages = [name for name in os.listdir(foldername)
                    if os.path.splitext(name)[-1].lower() in valid_images_extension]

        for (i,file) in enumerate(fileimages):
            path_file = os.path.join(foldername, file)
            self.open_image(path_file)
            self.run_segmenter(refresh_image=False)
            label_image = (os.path.splitext(file)[-2] + '_json')
            self.assign_using_labeled_image(os.path.join(foldername, label_image, 'label.png'), refresh_image=False)
            self.tk.write_log("%d of %d images", i, len(fileimages))

    def run_classifier_folder(self, foldername=None):

        if self.classifier is None:
877
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945