pynovisao.py 37.4 KB
Newer Older
1 2 3 4
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
"""
5 6 7 8
    This file must contain the implementation code for all actions of pynovisao.
    
    Name: pynovisao.py
    Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
9 10 11
"""

from collections import OrderedDict
12
import numpy as np
13
import os
14
import interface
15 16
import types
import cv2
17
from interface.interface import InterfaceException as IException
18
from PIL import Image
19

20
import segmentation
21
import extraction
22
from extraction import FeatureExtractor
23
import classification
24
from classification import Classifier
25

26 27
import util
from util.config import Config
28
from util.file_utils import File
29
from util.utils import TimeUtils
30 31
from util.utils import MetricUtils
from util.x11_colors import X11Colors
32

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
33

34
class Act(object):
35
    """Store all actions of Pynovisao."""
36

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
37
    def __init__(self, tk, args):
38 39 40 41 42 43 44 45 46
        """Constructor.

        Parameters
        ----------
        tk : Interface
            Pointer to interface that handles UI.
        args : Dictionary
            Arguments of program.
        """
47
        self.tk = tk
48 49 50
        
        self.segmenter = [segmentation._segmenter_list[segmenter].meta for segmenter in segmentation._segmenter_list
                            if segmentation._segmenter_list[segmenter].value == True ][0]()
51 52 53
        
        self.extractors = [extraction._extractor_list[extractor].meta for extractor in extraction._extractor_list
                            if extraction._extractor_list[extractor].value == True ]
54 55 56 57 58 59
        
        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
60

61 62
        self._image = None
        self._const_image = None
63
        self._mask_image = None
64
        self._image_name = None
65
        self._image_path = None
66
                    
67 68
        self._init_dataset(args["dataset"])
        self._init_classes(args["classes"], args["colors"])
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
69

70
        self._dataset_generator = True
71 72
        self._ground_truth = False
        self._gt_segments = None
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
73
        self.weight_path = None
74

75
    
76
    def _init_dataset(self, directory):
77 78 79 80 81 82 83
        """Initialize the directory of image dataset.

        Parameters
        ----------
        directory : string
            Path to directory.
        """
84 85 86 87
        if(directory[-1] == '/'):
            directory = directory[:-1]
            
        self.dataset = directory
88
        File.create_dir(self.dataset)
89
    
90
    def _init_classes(self, classes = None, colors = None):
91 92 93 94 95 96 97 98 99 100
        """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.
        """
101
        self.classes = []
102 103 104 105 106 107 108 109 110 111 112 113 114 115

        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()
116 117 118 119 120 121 122

        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')
123
                
124
        self._current_class = 0
125
        
126

127
    def open_image(self, imagename = None):
128 129 130 131 132 133 134
        """Open a new image.

        Parameters
        ----------
        imagename : string, optional, default = None
            Filepath of image. If not informed open a dialog to choose.
        """
135 136
        
        def onclick(event):
137
            """Binds dataset generator event to click on image."""
138
            if event.xdata != None and event.ydata != None and int(event.ydata) != 0 and self._dataset_generator == True:
139 140
                x = int(event.xdata)
                y = int(event.ydata)
141 142 143 144 145 146 147
                self.tk.write_log("Coordinates: x = %d y = %d", x, y)
                
                segment, size_segment, idx_segment, run_time = self.segmenter.get_segment(x, y)
                
                if size_segment > 0:
                    self.tk.append_log("\nSegment = %d: %0.3f seconds", idx_segment, run_time)
                    
148
                    self._image, run_time = self.segmenter.paint_segment(self._image, self.classes[self._current_class]["color"].value, x, y)
149
                    self.tk.append_log("Painting segment: %0.3f seconds", run_time)
150
                    self.tk.refresh_image(self._image)
151
                    
152 153 154 155
                    if self._ground_truth == True:
                        self._gt_segments[idx_segment] = self.classes[self._current_class]["name"].value

                    elif self._dataset_generator == True:
156
                        filepath = File.save_class_image(segment, self.dataset, self.classes[self._current_class]["name"].value, self._image_name, idx_segment)
157 158
                        if filepath:
                            self.tk.append_log("\nSegment saved in %s", filepath)
159 160 161
        
        if imagename is None:
            imagename = self.tk.utils.ask_image_name()
162 163

        if imagename:
164 165
            self._image = File.open_image(imagename)
            self._image_name = File.get_filename(imagename)
166

167 168 169
            self.tk.write_log("Opening %s...", self._image_name)
            self.tk.add_image(self._image, self._image_name, onclick)
            self._const_image = self._image
170
            
171
            self.segmenter.reset()
172
            self._gt_segments = None
173

174
        
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
175 176 177 178

    def open_weight(self):
        """Open a new weight."""
        self.weight_path = self.tk.utils.ask_weight_name()
179
        self.classifier.weight_path = self.weight_path
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
180
        
181
    def restore_image(self):
182 183
        """Refresh the image and clean the segmentation.
        """
184 185 186 187
        if self._const_image is not None:
            self.tk.write_log("Restoring image...")
            self.tk.refresh_image(self._const_image)
            
188
            self.segmenter.reset()
189
            self._gt_segments = None
190 191
        
    def close_image(self):
192
        """Close the image.
193
        
194 195 196 197 198
        Raises
        ------
        IException 'Image not found'
            If there's no image opened.
        """
199
        if self._const_image is None:
200 201 202 203
            raise IException("Image not found")
        
        if self.tk.close_image():
            self.tk.write_log("Closing image...")
204
            self._const_image = None
205
            self._image = None
206
            self._image_path = None
207 208

    def add_class(self, dialog = True, name = None, color = None):
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
        """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.
        """
226 227 228
        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)
229
                
230
        def edit_class(index):
231
            """Calls method that edit the class."""
232
            self.edit_class(index)
233 234
            
        def update_current_class(index):
235
            """Calls method that update the class."""
236
            self.update_current_class(index)
237 238
        
        def process_config():
239
            """Add the class and refresh the panel of classes."""
240
            new_class = self.tk.get_config_and_destroy()
241
            new_class["name"].value = '_'.join(new_class["name"].value.split())
242 243 244

            self.classes.append( new_class )
            self.tk.write_log("New class: %s", new_class["name"].value)
245
            self.tk.refresh_panel_classes(self.classes, self._current_class)
246
            
247 248
        if name is None:
            name = "Class_%02d" % (n_classes+1)
249
        if color is None:
250
            color = util.X11Colors.random_color()
251 252
            
        class_config = OrderedDict()
253
        class_config["name"] = Config(label="Name", value=name, c_type=str)
254
        class_config["color"] = Config(label="Color (X11 Colors)", value=color, c_type='color')
255 256
        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)
257 258 259 260 261 262 263
        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"
264 265 266
        self.tk.dialogue_config(title, class_config, process_config)        
      

267
    def edit_class(self, index):
268 269 270 271 272 273 274
        """Edit a class.

        Parameters
        ----------
        index : integer.
            Index of class in list self.classes.
        """
275
        def process_update(index):
276
            """Update the class."""
277
            updated_class = self.tk.get_config_and_destroy()
278
            updated_class["name"].value = '_'.join(updated_class["name"].value.split())
279 280 281
            
            self.classes[index] = updated_class
            self.tk.write_log("Class updated: %s", updated_class["name"].value)
282
            self.tk.refresh_panel_classes(self.classes, self._current_class)
283 284 285 286 287 288
        
        current_config = self.classes[index]
            
        title = "Edit class %s" % current_config["name"].value
        self.tk.dialogue_config(title, current_config, lambda *_ : process_update(index))
            
289
    def update_current_class(self, index):
290 291
        """Update the current class.
        """
292
        self._current_class = index
293 294
        
    def get_class_by_name(self, name):
295 296 297 298
        """Return the index for class.
        
        Parameters
        ----------
299
        name : string
300 301 302 303
            Name of class.
            
        Returns
        -------
304
        index : integer
305 306 307 308 309 310 311
            Index of class in list self.classes.

        Raises
        ------
        Exception 'Class not found'
            If name not found in self.classes.
        """
312 313 314 315 316 317
        name = name.strip()
        
        for cl in self.classes:
            if cl["name"].value == name:
                return cl
        raise Exception("Class not found")
318

319
        
320
    def set_dataset_path(self):
321 322
        """Open a dialog to choose the path to directory of image dataset.
        """
323 324
        directory = self.tk.utils.ask_directory(default_dir = self.dataset)
        if directory:
325
            self._init_dataset(directory)
326 327
            self.tk.write_log("Image dataset defined: %s", self.dataset)
            
328
            self._init_classes()
329
            self.tk.refresh_panel_classes(self.classes)
330
            
331 332
            if self.classifier: self.classifier.reset()
            
333
    def toggle_dataset_generator(self):
334 335
        """Enable/disable the dataset generator on click in image.
        """
336
        self._dataset_generator = not self._dataset_generator
337

338 339
            
    def select_segmenter(self):
340 341
        """Open a dialog to choose the segmenter.
        """
342 343
        title = "Choosing a segmenter"
        self.tk.write_log(title)
344

345
        current_config = segmentation.get_segmenter_config()
346
        
347
        def process_config():
348
            """Update the current segmenter."""
349
            new_config = self.tk.get_config_and_destroy()
350

351 352 353
            self.segmenter = [new_config[segmenter].meta for segmenter in new_config
                                if new_config[segmenter].value == True ][0]()

354
            self.tk.append_log("\nSegmenter: %s\n%s", str(self.segmenter.get_name()), str(self.segmenter.get_summary_config()))
355 356 357 358 359
            segmentation.set_segmenter_config(new_config)

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

    def config_segmenter(self):
360 361
        """Open a dialog to configure the current segmenter.
        """
362 363 364 365 366 367
        title = "Configuring %s" % self.segmenter.get_name()
        self.tk.write_log(title)

        current_config = self.segmenter.get_config()
        
        def process_config():
368
            """Update the configs of current segmenter."""
369 370 371
            new_config = self.tk.get_config_and_destroy()

            self.segmenter.set_config(new_config)
372
            self.tk.append_log("\nConfig updated:\n%s", str(self.segmenter.get_summary_config()))
373
            self.segmenter.reset()
374 375

        self.tk.dialogue_config(title, current_config, process_config)
376
        
377
    def run_segmenter(self, refresh_image=True):
378
        """Do the segmentation of image, using the current segmenter.
379
        
380 381 382 383 384
        Raises
        ------
        IException 'Image not found'
            If there's no image opened.
        """
385 386 387
        if self._const_image is None:
            raise IException("Image not found")
        
388
        self.tk.write_log("Running %s...", self.segmenter.get_name())
389 390 391 392 393

        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)
        
394 395
        self._gt_segments = [None]*(max(self.segmenter.get_list_segments())+1)
        
396 397
        if refresh_image:
            self.tk.refresh_image(self._image)
398 399


400
    def select_extractors(self):
401
        """Open a dialog to select the collection of extractors.
402
        
403 404 405 406 407
        Raises
        ------
        IException 'Please select at least one extractor'
            If no extractor was selected.
        """
408 409 410 411 412 413
        title = "Selecting extractors"
        self.tk.write_log(title)

        current_config = extraction.get_extractor_config()
        
        def process_config():
414
            """Update the collection of extractors."""
415 416 417 418
            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 ]
419 420 421
                                
            if len(self.extractors) == 0:
                raise IException("Please select at least one extractor")
422 423 424 425 426 427 428

            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)
429 430
        
    def run_extractors(self):
431 432
        """Perform a feature extraction on all images of dataset, using the current collection of extractors.
        """
433
        self.tk.write_log("Running extractors on all images in %s", self.dataset)
434

435 436 437
        fextractor = FeatureExtractor(self.extractors)
        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 ]))
438
        
439
        output_file, run_time = fextractor.extract_all(self.dataset, "training")
440 441
        self.tk.append_log("\nOutput file saved in %s", output_file)
        self.tk.append_log("Time elapsed: %0.3f seconds", run_time)
442 443
        
        if self.classifier: self.classifier.reset()
444

445 446
        
    def select_classifier(self):
447 448 449 450 451 452 453
        """Open a dialog to select the classifier.
        
        Raises
        ------
        IException 'You must install python-weka-wrapper'
            The user must install the required dependencies to classifiers.
        """
454
        if self.classifier is None:
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
455
            raise IException("Classifier not found!")
456 457 458 459 460
        
        title = "Choosing a classifier"
        self.tk.write_log(title)

        current_config = classification.get_classifier_config()
461

462 463
        
        def process_config():
464
            """Update the current classifier."""
465 466 467 468 469 470 471 472 473 474 475
            new_config = self.tk.get_config_and_destroy()

            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):
476 477 478 479 480 481 482
        """Set the configuration of current classifier.
        
        Raises
        ------
        IException 'You must install python-weka-wrapper'
            The user must install the required dependencies to classifiers.
        """
483
        if self.classifier is None:
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
484
            raise IException("Classifier not found!")
485 486 487 488 489 490 491 492 493 494 495
        
        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()))
496 497
            
            if self.classifier: self.classifier.reset()
498 499 500 501 502

        self.tk.dialogue_config(title, current_config, process_config)
    
    
    def run_classifier(self):
503 504 505 506 507 508 509 510 511 512
        """Run the classifier on the current image.
        As result, paint the image with color corresponding to predicted class of all segment.
        
        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.
        """
513
        if self.classifier is None:
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
514
            raise IException("Classifier not found!")
515 516 517 518 519 520 521
        
        if self._const_image is None:
            raise IException("Image not found")
        
        self.tk.write_log("Running %s...", self.classifier.get_name())
        self.tk.append_log("\n%s", str(self.classifier.get_summary_config()))
        
522 523
        #self.classifier.set
        
524 525
        start_time = TimeUtils.get_time()

526
        # Perform a segmentation, if needed.
527 528 529 530 531 532 533
        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))
            
            self._image, _ = self.segmenter.run(self._const_image)
            self.tk.refresh_image(self._image)        
            list_segments = self.segmenter.get_list_segments()
534
            self._gt_segments = [None]*(max(list_segments)+1)
535 536
        
        #  New and optimized classification
537
        tmp = ".tmp"
538
        File.remove_dir(File.make_path(self.dataset, tmp))
539

540 541 542 543 544
        self.tk.append_log("Generating test images... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
        
        len_segments = {}
        for idx_segment in list_segments:
            segment, size_segment, idx_segment = self.segmenter.get_segment(self, idx_segment=idx_segment)[:-1]
545
            
546
            filepath = File.save_class_image(segment, self.dataset, tmp, self._image_name, idx_segment)
547
            len_segments[idx_segment] = size_segment
548
            
549
        # Perform the feature extraction of all segments in image ( not applied to ConvNets ).
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
550
        if self.classifier.must_extract_features():
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
551 552
            self.tk.append_log("Running extractors on test images... (%0.3f seconds)", (TimeUtils.get_time() - start_time))    
            fextractor = FeatureExtractor(self.extractors)        
553 554 555
            output_file, _ = fextractor.extract_all(self.dataset, "test", dirs=[tmp])
                
        self.tk.append_log("Running classifier on test data... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
556 557

        # Get the label corresponding to predict class for each segment of image.
558 559
        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))
560

561 562 563 564 565 566 567
        # 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))
            
            # If ground truth mode, show alternative results
            if self._ground_truth == True:
                return self._show_ground_truth(list_segments, len_segments, labels, start_time)
568

569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
            # Create a popup with results of classification.
            popup_info = "%s\n" % str(self.classifier.get_summary_config())
            
            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)
            
            # 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):
                idx_segment = [ list_segments[idx] for idx in range(0, len(labels)) if cl["name"].value == labels[idx]]
                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)
                  
                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)
604 605

        
606 607 608 609
        end_time = TimeUtils.get_time()
            
        self.tk.append_log("\nClassification finished")
        self.tk.append_log("Time elapsed: %0.3f seconds", (end_time - start_time))
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
610 611 612

    def run_training(self):
        start_time = TimeUtils.get_time()
613
        
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
614 615 616 617 618 619 620 621 622 623 624 625
        if self._const_image is None:
            raise IException("Image not found")
        
        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
626
            self.classifier.train(self.dataset, "training")
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
627 628 629 630 631

            self.tk.append_log("DONE (%0.3f seconds)",  (TimeUtils.get_time() - start_time))
        
        self._image = self._const_image

632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
    
    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)
673

674 675 676 677 678
        end_time = TimeUtils.get_time()
            
        self.tk.append_log("\nClassification finished")
        self.tk.append_log("Time elapsed: %0.3f seconds", (end_time - start_time))
        
679

680 681 682 683 684
    def toggle_ground_truth(self):
        """Enable/disable ground truth mode.
        """
        self._ground_truth = not self._ground_truth
        
685
    def cross_validation(self):
686 687 688 689 690 691 692
        """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.
        """
693
        if self.classifier is None:
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
694
            raise IException("Classifier not found!")
695 696 697 698 699 700 701 702 703 704 705 706 707 708
        
        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)
709 710
        
    def experimenter_all(self):
711 712 713 714 715 716 717
        """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.
        """
718
        if self.classifier is None:
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
719
            raise IException("Classifier not found!")
720 721 722 723 724 725 726 727 728 729 730 731 732 733
        
        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...")
                
                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)
734 735


736 737 738 739
    def about(self):
        self.tk.show_info("Pynovisao\n\nVersion 1.0.0\n\nAuthors:\nAlessandro Ferreira\nHemerson Pistori")
        
            
740
    def func_not_available(self):
741
        """Use this method to bind menu options not available."""
742
        self.tk.write_log("This functionality is not available right now.")
743

744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 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 873 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
    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:
            raise IException("Classifier not found!")

        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]

        fileimages.sort()

        all_accuracy = []
        all_IoU = []
        all_frequency_weighted_IU = []

        for file in fileimages:
            path_file = os.path.join(foldername, file)
            self.open_image(path_file)
            self.run_classifier()
            label_image = os.path.join(foldername, (os.path.splitext(file)[-2] + '_json'), 'label.png')
            self._image_gt = File.open_image_lut(label_image)
            self._image_gt_name = File.get_filename(label_image)

            tam_gt = self._image_gt.shape
            tam_im = self._mask_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

            
            confusion_matrix = MetricUtils.confusion_matrix(self._mask_image, self._image_gt)
            [mean_accuracy, accuracy] = MetricUtils.mean_accuracy(self._mask_image, self._image_gt)
            [mean_IoU, IoU] = MetricUtils.mean_IU(self._mask_image, self._image_gt)
            frequency_weighted_IU = MetricUtils.frequency_weighted_IU(self._mask_image, self._image_gt)

            print('Matriz de Confusao')
            print(confusion_matrix)

            print('Mean Pixel Accuracy')
            print(mean_accuracy)

            print('Pixel accuracy per class')
            print(accuracy)

            print('Mean Intersction over Union')
            print(mean_IoU)

            print('Intersction over Union per class')
            print(IoU)

            print('Frequency Weighted IU')
            print(frequency_weighted_IU)

            all_accuracy.append(accuracy)
            all_IoU.append(IoU)
            all_frequency_weighted_IU.append(frequency_weighted_IU)

            if not os.path.exists("../models_results/"):
                os.makedirs("../models_results/")
            
            path = File.make_path("../models_results/" + file + ".txt")
            path_img = File.make_path("../models_results/" + file + "_seg1.tif")
            path_img2 = File.make_path("../models_results/" + file + "_seg2.tif")

            img = Image.fromarray(self._image)
            img.save(path_img)
            img = Image.fromarray(self.class_color)
            img.save(path_img2)
            
            f=open(path,'ab')
            np.savetxt(f, ['Matriz de confusao'], fmt='%s')
            np.savetxt(f, confusion_matrix, fmt='%.5f')
            np.savetxt(f, ['\nAcuracia'], fmt='%s')
            np.savetxt(f, accuracy, fmt='%.5f')
            np.savetxt(f, ['\nInterseccao sobre uniao'], fmt='%s')
            np.savetxt(f, IoU, fmt='%.5f')
            np.savetxt(f, ['\nInterseccao sobre uniao com peso'], fmt='%s')
            np.savetxt(f, [frequency_weighted_IU], fmt='%.5f')
            f.close()


        path = File.make_path("../models_results/all_metrics.txt")
        f=open(path,'ab')
        np.savetxt(f, ['All Acuracia'], fmt='%s')
        np.savetxt(f, all_accuracy, fmt='%.5f')
        np.savetxt(f, ['\nAll IoU'], fmt='%s')
        np.savetxt(f, all_IoU, fmt='%.5f')
        np.savetxt(f, ['\nAll Frequency Weighted IU'], fmt='%s')
        np.savetxt(f, all_frequency_weighted_IU, fmt='%.5f')
        f.close()