pynovisao.py 51 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 39 40
import multiprocessing
from multiprocessing import Process, Manager
import  threading
from tqdm import tqdm
41
class Act(object):
42
    """Store all actions of Pynovisao."""
43

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

        Parameters
        ----------
        tk : Interface
            Pointer to interface that handles UI.
        args : Dictionary
            Arguments of program.
        """
54
        self.tk = tk
55
        self.has_trained = False
56 57 58
        
        self.segmenter = [segmentation._segmenter_list[segmenter].meta for segmenter in segmentation._segmenter_list
                            if segmentation._segmenter_list[segmenter].value == True ][0]()
59 60 61
        
        self.extractors = [extraction._extractor_list[extractor].meta for extractor in extraction._extractor_list
                            if extraction._extractor_list[extractor].value == True ]
62 63 64 65 66 67
        
        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
68

69 70
        self._image = None
        self._const_image = None
71
        self._mask_image = None
72
        self._image_name = None
73
        self._image_path = None
74
        self._xml_file = None
75 76
        self._seg_folder = os.getcwd()
        self._img_folder = os.getcwd()
77
                    
78 79
        self._init_dataset(args["dataset"])
        self._init_classes(args["classes"], args["colors"])
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
80

81
        self._dataset_generator = True
82 83
        self._ground_truth = False
        self._gt_segments = None
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
84
        self.weight_path = None
85

86
    
87
    def _init_dataset(self, directory):
88 89 90 91 92 93 94
        """Initialize the directory of image dataset.

        Parameters
        ----------
        directory : string
            Path to directory.
        """
95 96 97 98
        if(directory[-1] == '/'):
            directory = directory[:-1]
            
        self.dataset = directory
99
        File.create_dir(self.dataset)
100
    
101
    def _init_classes(self, classes = None, colors = None):
102 103 104 105 106 107 108 109 110 111
        """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.
        """
112
        self.classes = []
113 114 115 116 117 118 119 120 121 122 123 124 125 126

        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()
127 128 129 130 131 132 133

        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')
134
                
135 136
        self._current_class = 0

137
    def open_image(self, imagename = None):
138
        """Open a new image and starts a new XML instance for such image.
139 140 141 142 143 144

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

                    elif self._dataset_generator == True:
167
                        filepath = File.save_class_image(segment, self.dataset, self.classes[self._current_class]["name"].value, self._image_name, idx_segment)
168 169
                        if filepath:
                            self.tk.append_log("\nSegment saved in %s", filepath)
170 171
        if imagename is None:
            imagename = self.tk.utils.ask_image_name()
172 173

        if imagename:
174 175
            self._image = File.open_image(imagename)
            self._image_name = File.get_filename(imagename)
176
            self._xml_file=wr(self._image_name,self._image.shape[0],self._image.shape[1])
177 178 179
            self.tk.write_log("Opening %s...", self._image_name)
            self.tk.add_image(self._image, self._image_name, onclick)
            self._const_image = self._image
180

181
            self.segmenter.reset()
182
            self._gt_segments = None
183

184
        
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
185 186 187 188

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

    def add_class(self, dialog = True, name = None, color = None):
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
        """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.
        """
237 238 239
        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)
240
                
241
        def edit_class(index):
242
            """Calls method that edit the class."""
243
            self.edit_class(index)
244 245
            
        def update_current_class(index):
246
            """Calls method that update the class."""
247
            self.update_current_class(index)
248 249
        
        def process_config():
250
            """Add the class and refresh the panel of classes."""
251
            new_class = self.tk.get_config_and_destroy()
252
            new_class["name"].value = '_'.join(new_class["name"].value.split())
253 254 255

            self.classes.append( new_class )
            self.tk.write_log("New class: %s", new_class["name"].value)
256
            self.tk.refresh_panel_classes(self.classes, self._current_class)
257
            
258 259
        if name is None:
            name = "Class_%02d" % (n_classes+1)
260
        if color is None:
261
            color = util.X11Colors.random_color()
262 263
            
        class_config = OrderedDict()
264
        class_config["name"] = Config(label="Name", value=name, c_type=str)
265
        class_config["color"] = Config(label="Color (X11 Colors)", value=color, c_type='color')
266 267
        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)
268 269 270 271 272 273 274
        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"
275 276 277
        self.tk.dialogue_config(title, class_config, process_config)        
      

278
    def edit_class(self, index):
279 280 281 282 283 284 285
        """Edit a class.

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

        Raises
        ------
        Exception 'Class not found'
            If name not found in self.classes.
        """
323 324 325 326 327 328
        name = name.strip()
        
        for cl in self.classes:
            if cl["name"].value == name:
                return cl
        raise Exception("Class not found")
329

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

350 351
            
    def select_segmenter(self):
352 353
        """Open a dialog to choose the segmenter.
        """
354 355
        title = "Choosing a segmenter"
        self.tk.write_log(title)
356

357
        current_config = segmentation.get_segmenter_config()
358
        
359
        def process_config():
360
            """Update the current segmenter."""
361
            new_config = self.tk.get_config_and_destroy()
362

363 364 365
            self.segmenter = [new_config[segmenter].meta for segmenter in new_config
                                if new_config[segmenter].value == True ][0]()

366
            self.tk.append_log("\nSegmenter: %s\n%s", str(self.segmenter.get_name()), str(self.segmenter.get_summary_config()))
367 368 369 370 371
            segmentation.set_segmenter_config(new_config)

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

    def config_segmenter(self):
372 373
        """Open a dialog to configure the current segmenter.
        """
374 375 376 377 378 379
        title = "Configuring %s" % self.segmenter.get_name()
        self.tk.write_log(title)

        current_config = self.segmenter.get_config()
        
        def process_config():
380
            """Update the configs of current segmenter."""
381 382 383
            new_config = self.tk.get_config_and_destroy()

            self.segmenter.set_config(new_config)
384
            self.tk.append_log("\nConfig updated:\n%s", str(self.segmenter.get_summary_config()))
385
            self.segmenter.reset()
386 387

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

        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)
        
406 407
        self._gt_segments = [None]*(max(self.segmenter.get_list_segments())+1)
        
408 409
        if refresh_image:
            self.tk.refresh_image(self._image)
410 411


412
    def select_extractors(self):
413
        """Open a dialog to select the collection of extractors.
414
        
415 416 417 418 419
        Raises
        ------
        IException 'Please select at least one extractor'
            If no extractor was selected.
        """
420 421 422 423 424 425
        title = "Selecting extractors"
        self.tk.write_log(title)

        current_config = extraction.get_extractor_config()
        
        def process_config():
426
            """Update the collection of extractors."""
427 428 429 430
            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 ]
431
            
432
            if len(self.extractors) == 0:
433
                raise IException("Please select an extractor from the menu under Features Extraction> Select extractors! ")
434
            
435 436 437 438 439 440
            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)
441 442
        
    def run_extractors(self):
443 444
        """Perform a feature extraction on all images of dataset, using the current collection of extractors.
        """
445
        self.tk.write_log("Running extractors on all images in %s", self.dataset)
446 447
        self.tk._root.update_idletasks()
        fextractor = FeatureExtractor(self.extractors,self.tk)
448 449
        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 ]))
450
        
451
        output_file, run_time = fextractor.extract_all(self.dataset, "training")
452 453
        self.tk.append_log("\nOutput file saved in %s", output_file)
        self.tk.append_log("Time elapsed: %0.3f seconds", run_time)
454 455
        
        if self.classifier: self.classifier.reset()
456

457 458 459 460 461
    def run_extract_frame(self):
        self.tk.write_log("Running extract frames from videos")
        extract_frame=ExtractFM()
        extract_frame.run(self.tk)

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

        current_config = classification.get_classifier_config()
477

478 479
        
        def process_config():
480
            """Update the current classifier."""
481
            new_config = self.tk.get_config_and_destroy()
Geazy Menezes's avatar
Geazy Menezes committed
482
            
483 484 485 486 487 488 489 490 491
            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):
492 493 494 495 496 497 498
        """Set the configuration of current classifier.
        
        Raises
        ------
        IException 'You must install python-weka-wrapper'
            The user must install the required dependencies to classifiers.
        """
499
        if self.classifier is None:
500
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
501 502 503 504 505 506 507 508 509 510 511
        
        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()))
512 513
            
            if self.classifier: self.classifier.reset()
514 515 516 517 518

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

522 523 524 525 526 527 528
        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.
        """
529
        if self.classifier is None:
530 531
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")

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

535 536
        self.tk.write_log("Running %s...", self.classifier.get_name())
        self.tk.append_log("\n%s", str(self.classifier.get_summary_config()))
537

538
        #self.classifier.set
539

540 541
        start_time = TimeUtils.get_time()

542
        # Perform a segmentation, if needed.
543 544 545
        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))
546

547
            self._image, _ = self.segmenter.run(self._const_image)
548
            self.tk.refresh_image(self._image)
549
            list_segments = self.segmenter.get_list_segments()
550
            self._gt_segments = [None]*(max(list_segments)+1)
551

552
        #  New and optimized classification
553
        tmp = ".tmp"
554
        File.remove_dir(File.make_path(self.dataset, tmp))
555

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

558
        len_segments = {}
559 560 561 562 563 564 565 566 567 568 569

        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()

570

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

577
        self.tk.append_log("Running classifier on test data... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
578 579

        # Get the label corresponding to predict class for each segment of image.
580 581
        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))
582

583 584 585
        # 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))
586

587 588 589
            # If ground truth mode, show alternative results
            if self._ground_truth == True:
                return self._show_ground_truth(list_segments, len_segments, labels, start_time)
590

591 592
            # Create a popup with results of classification.
            popup_info = "%s\n" % str(self.classifier.get_summary_config())
593

594 595
            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)
596

597 598 599 600 601
            # 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
602
                idx_segment = [ list_segments[idx] for idx in range(0, len(labels)) if cl["name"].value == labels[idx] or c == labels[idx]]
603 604 605 606 607
                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)
608

609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
                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)
626

627

628
        end_time = TimeUtils.get_time()
629

630 631
        self.tk.append_log("\nClassification finished")
        self.tk.append_log("Time elapsed: %0.3f seconds", (end_time - start_time))
632
        gc.collect()
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
633 634 635

    def run_training(self):
        start_time = TimeUtils.get_time()
636
        
637 638 639
        # 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
640 641 642 643 644 645 646 647 648 649
        
        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
650
            self.classifier.train(self.dataset, "training")
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
651 652 653 654

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

657 658 659 660 661 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
    
    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)
698

699 700 701 702 703
        end_time = TimeUtils.get_time()
            
        self.tk.append_log("\nClassification finished")
        self.tk.append_log("Time elapsed: %0.3f seconds", (end_time - start_time))
        
704

705 706 707 708 709
    def toggle_ground_truth(self):
        """Enable/disable ground truth mode.
        """
        self._ground_truth = not self._ground_truth
        
710
    def cross_validation(self):
711 712 713 714 715 716 717
        """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.
        """
718
        if self.classifier is None:
719
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
720 721 722 723 724 725 726 727 728 729 730 731 732 733
        
        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)
734 735
        
    def experimenter_all(self):
736 737 738 739 740 741 742
        """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.
        """
743
        if self.classifier is None:
744
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
745 746 747 748
        
        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...")
749

750 751 752 753 754 755 756 757 758
                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)
759 760


761
    def about(self):
762
        self.tk.show_info("Pynovisao\n\nVersion 1.0.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")
763 764
        
            
765
    def func_not_available(self):
766
        """Use this method to bind menu options not available."""
767
        self.tk.write_log("This functionality is not available right now.")
768

769 770 771 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
    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:
873
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
874 875 876 877 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 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961