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
import types
15

16
import cv2
17
from interface.interface import InterfaceException as IException
18
from PIL import Image
19 20
from pascal_voc_writer import Writer as wr
import shutil
21
from tkinter import *
22
import segmentation
23
import extraction
24
from extraction import FeatureExtractor
25
import time
26
import classification
27
from classification import Classifier
28

29
import util
30 31
from extraction.extractor_frame_video import ExtractFM

32
from util.config import Config
33
from util.file_utils import File
34
from util.utils import TimeUtils
35 36
from util.utils import MetricUtils
from util.x11_colors import X11Colors
37
import functools as ft
38 39
import multiprocessing
from multiprocessing import Process, Manager
40
import threading as th
41
from tqdm import tqdm
42
import psutil
43

44
class Act(object):
45
    """Store all actions of Pynovisao."""
46

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

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

72 73
        self._image = None
        self._const_image = None
74
        self._mask_image = None
75
        self._image_name = None
76
        self._image_path = None
77
        self._xml_file = None
78 79 80 81 82 83 84
        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"
85
                    
86 87
        self._init_dataset(args["dataset"])
        self._init_classes(args["classes"], args["colors"])
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
88

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

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

        Parameters
        ----------
        directory : string
            Path to directory.
        """
103 104 105 106
        if(directory[-1] == '/'):
            directory = directory[:-1]
            
        self.dataset = directory
107
        File.create_dir(self.dataset)
108
    
109
    def _init_classes(self, classes = None, colors = None):
110 111 112 113 114 115 116 117 118 119
        """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.
        """
120
        self.classes = []
121 122 123 124 125 126 127 128 129 130 131 132 133 134

        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()
135 136 137 138 139 140 141

        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')
142
                
143 144
        self._current_class = 0

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

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

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

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

189
            self.segmenter.reset()
190
            self._gt_segments = None
191

192
        
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
193 194 195 196

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        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)
        
414 415
        self._gt_segments = [None]*(max(self.segmenter.get_list_segments())+1)
        
416 417
        if refresh_image:
            self.tk.refresh_image(self._image)
418 419


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

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

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

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

        current_config = classification.get_classifier_config()
485

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

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

530 531 532 533 534 535 536
        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.
        """
537
        if self.classifier is None:
538 539
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")

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

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

546
        #self.classifier.set
547

548 549
        start_time = TimeUtils.get_time()

550
        # Perform a segmentation, if needed.
551 552 553
        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))
554

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

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

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

566
        len_segments = {}
567 568 569 570

        print("Wait to complete processes all images!")
        with tqdm(total=len(list_segments)) as pppbar:
            for idx_segment in list_segments:
Diego André Sant'Ana's avatar
alter  
Diego André Sant'Ana committed
571
                segment, size_segment,xml, idx_segment = self.segmenter.get_segment(self, idx_segment=idx_segment)[:-1]
572 573 574 575 576 577
                # 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()

578

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

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

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

592
        # Result is the class for each superpixel
593
        if len(labels)>0:
594
            self.tk.append_log("Painting segments... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
595

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

600 601
            # Create a popup with results of classification.
            popup_info = "%s\n" % str(self.classifier.get_summary_config())
602

603 604
            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)
605

606 607 608 609 610
            # 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
611
                idx_segment = [ list_segments[idx] for idx in range(0, len(labels)) if cl["name"].value == labels[idx] or c == labels[idx]]
612 613 614 615 616
                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)
617

618 619 620
                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)

621
           
622 623 624 625 626 627 628 629 630 631 632 633 634
            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)
635

636

637
        end_time = TimeUtils.get_time()
638

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

    def run_training(self):
        start_time = TimeUtils.get_time()
645
        
646 647 648
        # 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
649 650 651 652 653 654 655 656 657 658
        
        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
659
            self.classifier.train(self.dataset, "training")
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
660 661 662 663

            self.tk.append_log("DONE (%0.3f seconds)",  (TimeUtils.get_time() - start_time))
        
        self._image = self._const_image
664
        self.has_trained=True
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
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 704 705 706
    
    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)
707

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

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

759 760 761 762 763 764 765 766 767
                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)
768 769


770
    def about(self):
771
        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")
772
    def func_not_available(self):
773
        """Use this method to bind menu options not available."""
774
        self.tk.write_log("This functionality is not available right now.")
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
    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)

Diego André Sant'Ana's avatar
Diego André Sant'Ana committed
849
                segment, size_segment, xml,idx_segment, run_time = self.segmenter.get_segment(idx_segment = idx_segment)
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
                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:
880
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
Diogo Nunes Gonçalves's avatar