class PolygonClassifier(object):
    """A classifier that classifies polygons
    """
    __metaclass__ = ABCMeta

    @abstractmethod
    def predict(self, image, polygon):
        """Predict the class associated with the given polygon

        Parameters
        ----------
        image: Image
            The image the object of interest delimited by the polygon
        polygon: shapely.geometry.Polygon
            The polygon of which the class must be predicted

        Returns
        -------
        prediction: int
            An integer code indicating the predicted class
        probability: float (in [0,1])
            The prediction probability
        """
        pass

    def predict_batch(self, image, polygons):
        """Predict the classes associated with the given polygons

        Parameters
        ----------
        image: Image
            The image the objects of interest delimited by the polygons
        polygons: iterable (subtype: shapely.geometry.Polygon, size: N)
            The polygons of which the classes must be predicted

        Returns
        -------
        predictions: iterable (subtype: int, size: N)
            An iterable of integer codes indicating the predicted classes
        probabilities: iterable (subtype: int, range: [0,1], size: N)
            The probabilities associated with the classes predicted for each polygon
        """
        return zip(*[self.predict(image, polygon) for polygon in polygons]) class DispatchingRule(object):
    """An interface to be implemented by any class that defined a dispatching rule for polygons
    """
    __metaclass__ = ABCMeta

    @abstractmethod
    def evaluate(self, image, polygon):
        """Evaluate a polygon

        Parameters
        ----------
        image: Image
            The image from which is extracted the polygon
        polygon: shapely.geometry.Polygon
            The polygon to evaluate

        Returns
        -------
        result: bool
            True if the dispatching rule matched the polygon, False otherwise.
        """
        pass

    def evaluate_batch(self, image, polygons):
        """Evaluate the polygons

        Parameters
        ----------
        image: Image
            The image from which is extracted the polygons
        polygons: iterable (subtype: shapely.geometry.Polygon)
            The list of polygons to dispatch

        Returns
        -------
        result: iterable (subtype: bool)
            Iterable of which the ith element is True if the ith polygon is evaluated to True, False otherwise
        """
        return [self.evaluate(image, polygon) for polygon in polygons] class Logger(object):
    """A base class loggers. A logger is an object which print or not the messages it is passed according to
    the verbosity level. Base class are responsible for defining the printing policy.
    """
    SILENT = 0
    ERROR = 1
    WARNING = 2
    INFO = 3
    DEBUG = 4

    def __init__(self, level, prefix=True, pid=True):
        """Build a logger object with the given level of verbosity
        Parameters
        ----------
        level: int
            Verbosity level
        prefix: boolean:
            True for displaying the prefix, false otherwise
        pid: boolean
            True for displaying the pid in the prefix, false for the tid
        """
        self._level = level
        self._prefix = prefix
        self._pid = pid

    @property
    def level(self):
        return self._level

    @level.setter
    def level(self, level):
        self._level = level

    def d(self, msg):
        """Alias for self.debug
        Parameters
        ----------
        msg: string
            The message to log
        """
        self.debug(msg)

    def debug(self, msg):
        """Logs a information message if the level of verbosity is above or equal DEBUG
        Parameters
        ----------
        msg: string
            The message to log
        """
        self._log(Logger.DEBUG, msg)

    def i(self, msg):
        """Alias for self.info
        Parameters
        ----------
        msg: string
            The message to log
        """
        self.info(msg)

    def info(self, msg):
        """Logs a information message if the level of verbosity is above or equal INFO
        Parameters
        ----------
        msg: string
            The message to log
        """
        self._log(Logger.INFO, msg)

    def w(self, msg):
        """Alias for self.warning
        Parameters
        ----------
        msg: string
            The message to log
        """
        self.warning(msg)

    def warning(self, msg):
        """Logs a information message if the level of verbosity is above or equal WARNING
        Parameters
        ----------
        msg: string
            The message to log
        """
        self._log(Logger.WARNING, msg)

    def e(self, msg):
        """Alias for self.error
        Parameters
        ----------
        msg: string
            The message to log
        """
        self.error(msg)

    def error(self, msg):
        """Logs a information message if the level of verbosity is above or equal ERROR
        Parameters
        ----------
        msg: string
            The message to log
        """
        self._log(Logger.ERROR, msg)

    def _log(self, level, msg):
        """Check the verbosity level, if it is above, actually prints the message
        Parameters
        ----------
        level: int
            Verbosity level of the message
        msg: string
            The message
        """
        if self._level >= level:
            formatted = self._format_msg(level, msg)
            self._print(formatted)

    @abstractmethod
    def _print(self, formatted_msg):
        pass

    def prefix(self, level):
        from datetime import datetime
        now = datetime.now().isoformat()
        if self._pid:
            pid = "{}".format(os.getpid()).zfill(6)
            fid = "pid:{}".format(pid)
        else:
            tid = "{}".format(threading.current_thread().ident).zfill(6)
            fid = "tid:{}".format(tid)
        return "[{}][{}][{}]".format(fid, now, self.level2str(level))

    @classmethod
    def level2str(cls, level):
        if level == cls.DEBUG:
            return "DEBUG"
        elif level == cls.WARNING:
            return "WARN "
        elif level == cls.ERROR:
            return "ERROR"
        else:  # info
            return "INFO "

    def _format_msg(self, level, msg):
        if self._prefix:
            rows = ["{} {}".format(self.prefix(level), row) for row in msg.splitlines()]
            return os.linesep.join(rows)
        else:
            return msg class StandardOutputLogger(Logger):
    """A logger printing the messages on the standard output
    """
    def _print(self, formatted_msg):
        print (formatted_msg) class Segmenter(SemanticSegmenter):
    """For backward-compatibility 
    
    Interface to be implemented by any class which implements a (binary) segmentation algorithm.
    
    Background pixels are represented by the value 0 ('black') while foreground ones are represented 
    by the value 255 ('white') by default.
    """
    __metaclass__ = ABCMeta

    def __init__(self, classes=None):
        super(Segmenter, self).__init__(classes=[0, 255] if classes is None else classes) def report_timing(timing, logger, indent="  "):
    """Report the recorded times in the given workflow timing object
    Parameters
    ----------
    timing: WorkflowTiming
        The timing containing to times to report
    logger: Logger
        The logger through which the times must be reported
    indent: str
        Indent to use to separate the different phase levels
    """
    logger.i("Timing report:")
    _report_timing(timing.get_phases_hierarchy(), "", 0, timing, logger, indent=indent) class SLDCWorkflowBuilder(WorkflowBuilder):
    """A class for building SLDC Workflow objects. When several instances of SLDCWorkflow should be built, they should
    be with the same Builder object, especially if the workflows should work in parallel.
    """
    def __init__(self):
        """Constructor for WorkflowBuilderObjects
        Parameters
        ----------
        n_jobs: int
            Number of jobs to use for executing the workflow
        """
        # Fields below are reset after each get()
        super(SLDCWorkflowBuilder, self).__init__()
        self._segmenter = None
        self._rules = None
        self._dispatching_labels = None
        self._one_shot_dispatcher = None
        self._classifiers = None
        self._parallel_dc = None
        self._reset()

    def _reset(self):
        """Reset the sldc workflow fields to their default values"""
        super(SLDCWorkflowBuilder, self)._reset()
        self._segmenter = None
        self._rules = []
        self._dispatching_labels = []
        self._one_shot_dispatcher = None
        self._classifiers = []
        self._parallel_dc = False

    def get_kwargs(self):
        """extends parent method with SLDCWorkflow specifics"""
        # define the dispatcher and classifier
        if self._one_shot_dispatcher is None:
            dispatcher = RuleBasedDispatcher(
                self._rules,
                labels=self._dispatching_labels,
                logger=self._logger
            )
        else:
            dispatcher = self._one_shot_dispatcher

        dispatcher_classifier = DispatcherClassifier(
            dispatcher,
            self._classifiers,
            logger=self._logger
        )

        return {
            "segmenter": self._segmenter,
            "dispatcher_classifier": dispatcher_classifier,
            "parallel_dispatch_classify": self._parallel_dc,
            **super().get_kwargs()
        }

    def set_parallel_dc(self, parallel_dc):
        """Specify whether the dispatching and classification will be parallelized at the workflow level (optional)
        Parameters
        ----------
        parallel_dc: boolean
            True for enabling parallelization of dispatching and classification at the workflow level

        Returns
        -------
        builder: SLDCWorkflowBuilder
            The builder
        """
        self._parallel_dc = parallel_dc
        return self

    def set_segmenter(self, segmenter):
        """Set the segmenter (mandatory)
        Parameters
        ----------
        segmenter: Segmenter
            The segmenter

        Returns
        -------
        builder: SLDCWorkflowBuilder
            The builder
        """
        self._segmenter = segmenter
        return self

    def add_classifier(self, rule, classifier, dispatching_label=None):
        """Add a classifier to which polygons can be dispatched (mandatory, at least on time).

        Parameters
        ----------
        rule: DispatchingRule
            The dispatching rule that matches the polygons to be dispatched to the classifier
        classifier: PolygonClassifier
            The polygon that classifies polygons
        dispatching_label: key (optional, default: None)
            The dispatching label for this classifier. By default, (n) is used where n is the number of rules and
            classifiers added before (n is formatted as a string)

        Returns
        -------
        builder: SLDCWorkflowBuilder
            The builder
        """
        if self._one_shot_dispatcher is not None:
            raise InvalidBuildingException("Cannot use a rule based dispatcher alongside a one shot dispatcher.")
        self._dispatching_labels.append(dispatching_label if dispatching_label is not None else str(len(self._rules)))
        self._rules.append(rule)
        self._classifiers.append(classifier)
        return self

    def add_catchall_classifier(self, classifier, dispatching_label="catchall"):
        """Add a classifier which is dispatched all the polygons that were note dispatched by the previously added
        classifiers.

        Parameters
        ----------
        classifier: PolygonClassifier
            The classifier
        dispatching_label: key (optional, default: "catchall")
            The dispatching label
        """
        if self._one_shot_dispatcher is not None:
            raise InvalidBuildingException("Cannot use a rule based dispatcher alongside a one shot dispatcher.")
        return self.add_classifier(CatchAllRule(), classifier, dispatching_label=dispatching_label)

    def set_one_shot_dispatcher(self, dispatcher, classifier_mapping):
        """Use the one shot user dispatching strategy and sets the dispatcher and classifiers.

        Parameters
        ----------
        dispatcher: Dispatcher
            A dispatcher
        classifier_mapping: dict (key: hashable, subtype: PolygonClassifier)
            Maps labels returned by the dispatcher with their corresponding classifiers.
        """
        if len(self._rules) > 0:
            raise InvalidBuildingException("Cannot use a one shot dispatcher alongside "
                                           "a rule based one (already defined {} rules).".format(len(self._rules)))

        # extract mapping and classifiers
        self._one_shot_dispatcher = dispatcher
        dispatcher.mapping, self._classifiers = zip(*[
            (label, classifier) for label, classifier in classifier_mapping.items()
        ])

    def get(self):
        if self._segmenter is None:
            raise MissingComponentException("Missing segmenter.")
        if self._tile_builder is None:
            raise MissingComponentException("Missing tile builder.")
        if self._one_shot_dispatcher is None and len(self._rules) == 0:
            raise MissingComponentException("Missing dispatching strategy. Either one shot or rule based "
                                            "dispatching must be used.")
        if len(self._classifiers) == 0:
            raise MissingComponentException("Missing classifiers.")
        workflow = SLDCWorkflow(**self.get_kwargs())
        self._reset()
        return workflow def draw_square(image, side, center, color):
    """Draw a square centered in 'center' and of which the side has 'side'"""
    top_left = (center[1] - side / 2, center[0] - side / 2)
    top_right = (center[1] + side / 2, center[0] - side / 2)
    bottom_left = (center[1] - side / 2, center[0] + side / 2)
    bottom_right = (center[1] + side / 2, center[0] + side / 2)
    p = Polygon([top_left, top_right, bottom_right, bottom_left, top_left])
    return draw_poly(image, p, color) def draw_circle(image, radius, center, color=255, return_circle=False):
    """Draw a circle of radius 'radius' and centered in 'centered'"""
    circle_center = Point(*center)
    circle_polygon = circle_center.buffer(radius)
    image_out = draw_poly(image, circle_polygon, color)
    if return_circle:
        return image_out, circle_polygon
    else:
        return image_out class NumpyImage(Image):
    def __init__(self, np_image):
        """An image represented as a numpy ndarray"""
        self._np_image = np_image

    @property
    def np_image(self):
        return self._np_image

    @property
    def channels(self):
        shape = self._np_image.shape
        return shape[2] if len(shape) == 3 else 1

    @property
    def width(self):
        return self._np_image.shape[1]

    @property
    def height(self):
        return self._np_image.shape[0]
import os
import numpy as np
from sldc import Segmenter, DispatchingRule, PolygonClassifier, report_timing, StandardOutputLogger, Logger
from sldc.builder import SLDCWorkflowBuilder
from test import NumpyImage, draw_circle, draw_square


""" (1) Define a segmenting procedure for locating the objects """
class BasicSegmenter(Segmenter):
    def __init__(self):
        super(BasicSegmenter, self).__init__()

    def segment(self, image):
        """Assume grayscale image"""
        mask = (image > 0).astype(np.uint8)
        mask[mask == 1] = 255
        return mask


""" (2) Define a dispatching rule for identifying circle and squares"""

class ShapeRule(DispatchingRule):