import copy
import numpy as np
from Orange import data
from ..data.value import Value

class Fitter:
    def __call__(self, data):
        model = copy.deepcopy(self)
        model.fit(data.X)
        return model


class Model:
    Value = 0
    Probs = 1
    ValueProbs =2

    def __init__(self, domain):
        self.domain = domain

    def predict(self, data):
        raise TypeError("Descendants of Model must overload method predict")

    def __call__(self, data, ret=Value):
        if not 0 <= ret <= 2:
            raise ValueError("invalid value of argument 'ret'")
        if ret > 0 and any(isinstance(v, data.ContinuousVariable)
                           for v in self.domain.class_vars):
            raise ValueError("cannot predict continuous distributions")

        # Call the predictor
        if isinstance(data, np.array):
            prediction = self.predict(np.atleast_2d(data))
        elif isinstance(data, data.Instance):
            if data.domain != self.domain:
                data = data.Instance(self.domain, data)
            prediction = self.predict(np.atleast_2d(data._values))
        elif isinstance(data, data.Table):
            if data.domain != self.domain:
                data = data.Table.new_from_table(self.domain, data)
            prediction = self.predict(data.X)
        else:
            raise TypeError("Unrecognized argument (instance of '%s')",
                            type(data).__name__)

        # Parse the result into value and probs
        multitarget = len(self.domain.class_vars) > 1
        if isinstance(prediction, tuple):
            value, probs = prediction
        elif prediction.ndim == 1 + multitarget:
            value, probs = prediction, None
        elif prediction.ndim == 2 + multitarget:
            value, probs = None, prediction
        else:
            raise TypeError("model returned a %i-dimensional array",
                            prediction.ndim)

        # Ensure that we have what we need to return
        if ret != Model.Probs and value is None:
            value = np.argmax(probs, axis=1)
        if ret != Model.Value and probs is None:
            if multitarget:
                max_card = max(len(c.values) for c in self.domain.class_vars)
                probs = np.zeros(value.shape + (max_card,), float)
                for i, cvar in enumerate(self.domain.class_vars):
                    for cval in len(cvar.values):
                        probs[i, value[:, i]==cval] = 1
            else:
                probs = np.zeros((len(value), len(self.domain.class_var.values)),
                                 float)
                for cval in probs.shape[1]:
                    probs[value==cval] = 1
            return probs

        # Return what we need to
        if ret == Model.Value:
            if isinstance(data, data.Instance) and not multitarget:
                value = Value(self.domain.class_var, value[0])
            return value
        if ret == Model.Probs:
            return probs
        else: # ret == Model.ValueProbs
            return value, probs

@@ -80,16 +80,17 @@
  Value%0A%0A
+%0A
 class Fi
@@ -103,116 +103,765 @@
     
-def __call__(self, data):%0A        model = copy.deepcopy(self)%0A        model.fit(data.X)%0A        return model
+%22%22%22%0A    Return a model build from the data given as a :class:%60data.Table%60%0A%0A    Models can be implemented using one or two classes.%0A%0A    In a two-class model, the first class is derived from %60Fitter%60 and overloads%0A    the %60__call__%60 method. The method gets a :class:%60~Orange.data.Table%60 and%0A    returns an instance of the corresponding Model class. The %60Model%60 should%0A    implement method %60predict%60 (see documentation on :class:%60Model%60).%0A%0A    In a single-class model, the class should be derived from :class:%60Fitter%60%0A    and :class:%60Model%60 and provide two methods:%0A%0A    - %60fit%60 gets X, Y, and weights (or %60None%60) as arguments and initializes the%0A       model's parameters%0A%0A    - %60predict%60 (see documentation on :class:%60~Orange.classification.Model%60).%0A    %22%22%22
 %0A%0A%0Ac
@@ -919,16 +919,17 @@
 obs =2%0A%0A
+%0A
     def 
@@ -949,18 +949,162 @@
 , domain
+=None):%0A        if isinstance(self, Fitter):%0A            domain = None%0A        elif not domain:%0A            raise ValueError(%22unspecified domain%22
 )
-:
 %0A       
@@ -1126,16 +1126,17 @@
 domain%0A%0A
+%0A
     def 
@@ -1230,24 +1230,25 @@
  predict%22)%0A%0A
+%0A
     def __ca
@@ -1272,24 +1272,324 @@
 ret=Value):%0A
+        # Check if this is an unfitted single-class model%0A        if isinstance(self, Fitter) and self.domain is None:%0A            model = copy.deepcopy(self)%0A            model.fit(data.X, data.Y, data.W if data.has_weights else None)%0A            model.domain = data.domain%0A            return model%0A%0A
         if n

Change Fitter and Model: single-class models now have to be derived from both