pynovisao.py 44 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

21
import segmentation
22
import extraction
23
from extraction import FeatureExtractor
24

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

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

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

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

78
        self._dataset_generator = True
79 80
        self._ground_truth = False
        self._gt_segments = None
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
81
        self.weight_path = None
82

83
    
84
    def _init_dataset(self, directory):
85 86 87 88 89 90 91
        """Initialize the directory of image dataset.

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

        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()
124 125 126 127 128 129 130

        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')
131
                
132 133
        self._current_class = 0

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

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

                    elif self._dataset_generator == True:
164
                        filepath = File.save_class_image(segment, self.dataset, self.classes[self._current_class]["name"].value, self._image_name, idx_segment)
165 166
                        if filepath:
                            self.tk.append_log("\nSegment saved in %s", filepath)
167

168 169
        if imagename is None:
            imagename = self.tk.utils.ask_image_name()
170 171

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

179
            self.segmenter.reset()
180
            self._gt_segments = None
181

182
        
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
183 184 185 186

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

        current_config = classification.get_classifier_config()
475

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

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

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

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

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

536
        #self.classifier.set
537

538 539
        start_time = TimeUtils.get_time()

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

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

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

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

556
        len_segments = {}
557 558 559 560 561 562 563 564 565 566 567

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

568

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

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

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

581 582 583
        # 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))
584

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

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

592 593
            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)
594

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

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

625

626
        end_time = TimeUtils.get_time()
627

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

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

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

655 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
    
    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)
696

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

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

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


759
    def about(self):
760
        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")
761 762
        
            
763
    def func_not_available(self):
764
        """Use this method to bind menu options not available."""
765
        self.tk.write_log("This functionality is not available right now.")
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
    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:
871
            raise IException("Classifier not found! Select from the menu the option Training>Choose Classifier!")
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 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966

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

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