def __init__(self, source, name):

        ObjectWithTimeStamp.__init__(self)
        
        # When was this data last generated?
        # This time stamp is an integer number and it is intended to synchronize the activities of
        # the pipeline. It doesn't relates to the clock time of acquiring or processing the data.
        self._update_time = TimeStamp()
        
        # The maximum modification time of all upstream filters and outputs.
        # This does not include the modification time of this output.
        self._pipeline_time = 0
        
        self.connect_source(source, name)
        
        self.image = None
    def __init__(self):

        ObjectWithTimeStamp.__init__(self)
        
        self._filter_id = self._new_filter_id()
        
        # Time when generate_output_information was last called.
        self._output_information_time = TimeStamp()
        
        # This flag indicates when the pipeline is executing.
        # It prevents infinite recursion when pipelines have loops.
        self._updating = False
        
        self._inputs = dict()
        self._outputs = {name:ImageFilterOutput(self, name) for name in self.__output_names__}
        
        self.modified()
class ImageFilterOutput(ObjectWithTimeStamp):

    _logger = _module_logger.getChild('ImageFilterOutput')

    ##############################################

    def __init__(self, source, name):

        ObjectWithTimeStamp.__init__(self)
        
        # When was this data last generated?
        # This time stamp is an integer number and it is intended to synchronize the activities of
        # the pipeline. It doesn't relates to the clock time of acquiring or processing the data.
        self._update_time = TimeStamp()
        
        # The maximum modification time of all upstream filters and outputs.
        # This does not include the modification time of this output.
        self._pipeline_time = 0
        
        self.connect_source(source, name)
        
        self.image = None
        # self._image_format = None

    ##############################################

    @property
    def source(self):
        return self._source

    ##############################################

    @property
    def name(self):
        return "{}.{}".format(self._source.name, self._name)

    ##############################################

    @property
    def update_time(self):
        return self._update_time

    ##############################################

    @property
    def pipeline_time(self):
        return self._pipeline_time

    @pipeline_time.setter
    def pipeline_time(self, time):
        self._pipeline_time = time

    ##############################################

    # @property
    # def image(self):
    #     return self._image

    ##############################################

    @property
    def image_format(self):
        if self.image is not None:
            return self.image.image_format
        else:
            return None
        # return self._image_format

    ##############################################

    def connect_source(self, source, name):

        self._source = source # image_filter
        self._name = name
        self.modified()

    ##############################################

    def disconnect_source(self):

        self._source = None
        self._name = None
        self.modified()

    ##############################################

    def update(self):

        # Provides opportunity for the data object to insure internal consistency before
        # access. Also causes owning source/filter (if any) to update itself. The Update() method is
        # composed of UpdateOutputInformation(), PropagateRequestedRegion(), and
        # UpdateOutputData(). This method may call methods that throw an InvalidRequestedRegionError
        # exception. This exception will leave the pipeline in an inconsistent state. You will need
        # to call ResetPipeline() on the last ProcessObjectWithTimeStamp in your pipeline in order
        # to restore the pipeline to a state where you can call Update() again.

        self._logger.info(self.name)
        self.update_output_information()
        #!# self.propagate_requested_region()
        self.update_output_data()

    ##############################################

    def update_output_information(self):

        # Update the information for this DataObjectWithTimeStamp so that it can be used as an
        # output of a ProcessObjectWithTimeStamp. This method is used in the pipeline mechanism to
        # propagate information and initialize the meta data associated with a
        # DataObjectWithTimeStamp. Any implementation of this method in a derived class is assumed
        # to call its source's ProcessObjectWithTimeStamp::UpdateOutputInformation() which
        # determines modified times, LargestPossibleRegions, and any extra meta data like spacing,
        # origin, etc. Default implementation simply call's it's source's UpdateOutputInformation().

        self._logger.info(self.name)
        self._source.update_output_information()

    ##############################################

    def propagate_requested_region(self):

        # Methods to update the pipeline. Called internally by the pipeline mechanism. 

        self._logger.info(self.name)
        
        # If we need to update due to pipeline modification time, or the fact that our data was
        # released, then propagate the update region to the source if there is one.
        if int(self._update_time) < self._pipeline_time:
            self._source.propagate_requested_region(self)
        
        # Check that the requested region lies within the largest possible region
        # self.verify_requested_region()

    ##############################################

    def update_output_data(self):

        self._logger.info(self.name)

        # If we need to update due to pipeline modification time, or the fact that our data was
        # released, then propagate the UpdateOutputData to the source if there is one.
        if int(self._update_time) < self._pipeline_time:
            self._source.update_output_data()

    ##############################################

    def copy_information(self, image_format):

        # was input_

        # Copy information from the specified data set. This method is part of the pipeline
        # execution model. By default, a ProcessObjectWithTimeStamp will copy meta-data from the
        # first input to all of its outputs. See
        # ProcessObjectWithTimeStamp::GenerateOutputInformation(). Each subclass of
        # DataObjectWithTimeStamp is responsible for being able to copy whatever meta-data it needs
        # from from another DataObjectWithTimeStamp. The default implementation of this method is
        # empty. If a subclass overrides this method, it should always call its superclass' version.

        # self._logger.info("from {} to {}\n{}".format(input_.name, self.name,
        #                                              str(input_.image_format)))

        self._logger.info("Make output for {} with shape \n{}".format(self.name,
                                                                      str(image_format)))
        self.image = Image(image_format)

    ##############################################

    def data_has_been_generated(self):

        self._logger.info(self.name)
        self.modified()
        self._update_time.modified()
class ImageFilter(ObjectWithTimeStamp):

    __filter_name__ = None
    __input_names__ = None
    __output_names__ = None

    _last_filter_id = 0
    _logger = _module_logger.getChild('ImageFilter')

    ##############################################

    @staticmethod
    def _new_filter_id():

        ImageFilter._last_filter_id += 1
        
        return ImageFilter._last_filter_id

    ##############################################

    def __init__(self):

        ObjectWithTimeStamp.__init__(self)
        
        self._filter_id = self._new_filter_id()
        
        # Time when generate_output_information was last called.
        self._output_information_time = TimeStamp()
        
        # This flag indicates when the pipeline is executing.
        # It prevents infinite recursion when pipelines have loops.
        self._updating = False
        
        self._inputs = dict()
        self._outputs = {name:ImageFilterOutput(self, name) for name in self.__output_names__}
        
        self.modified()

    ##############################################

    def __del__(self):

        for output in self._outputs.values():
            output.disconnect_source()

    ##############################################

    def __repr__(self):

        return self.__filter_name__

    ##############################################

    @property
    def name(self):
        return self.__filter_name__

    @property
    def input_names(self):
        return self.__input_names__

    @property
    def output_names(self):
        return self.__output_names__

    @property
    def filter_id(self):
        return self._filter_id

    ##############################################

    def get_input(self, name):
        return self._inputs[name]

    ##############################################

    def connect_input(self, name, source):

        self._inputs[name] = source

    ##############################################

    def disconnect_input(self, name):

        if name in self._inputs:
            del self._inputs[name]

    ##############################################

    def get_output(self, name):
        return self._outputs[name]

    ##############################################

    def get_primary_input(self):
        return self.get_input(self.__input_names__[0])

    ##############################################

    def get_primary_output(self):
        return self.get_output(self.__output_names__[0])

    ##############################################

    def update(self):

        self._logger.info(self.name)
        self.get_primary_output().update()

    ##############################################

    def update_output_information(self):

        self._logger.info(self.name)
        
        # Watch out for loops in the pipeline
        if self._updating:
            self._logger.info("{} is updating!".format(self.name))
            # Since we are in a loop, we will want to update. But if we don't modify this filter,
            # then we will not execute because our output information modification time will be more
            # recent than the modification time of our output.
            self.modified()
            return
        
        # Verify that the process object has been configured correctly, that all required inputs are
        # set, and needed parameters are set appropriately before we continue the pipeline, i.e. is
        # the filter in a state that it can be run.
        # self.verify_preconditions()
        for input_name in self.__input_names__:
            if input_name not in self._inputs:
                raise NameError("Input {} is required".format(input_name))
        
        # We now wish to set the pipeline modification time of each output to the largest of this
        # filter's modification time, all input's pipeline modification time, and all input's
        # modification time.  We begin with the modification time of this filter.
        modified_time = self.modified_time
        
        # Loop through the inputs
        for input_ in self._inputs.values():
            # Propagate the update output information call
            self._updating = True
            input_.update_output_information()
            self._updating = False
            
            # What is the pipeline modification time of this input? Compare this against our current
            # computation to find the largest one.  Pipeline modification time of the input does not
            # include the modification time of the data object itself. Factor these modification
            # times into the next pipeline modification time
            modified_time = max(modified_time, input_.modified_time, input_.pipeline_time)
        
        # Call generate_output_information for subclass specific information.  Since
        # update_output_information propagates all the way up the pipeline, we need to be careful
        # here to call generate_output_information only if necessary. Otherwise, we may cause this
        # source to be modified which will cause it to execute again on the next update.
        if modified_time > int(self._output_information_time):
            for output in self._outputs.values():
                output.pipeline_time = modified_time
            
            # Verify that all the inputs are consistent with the requirements of the filter. For
            # example, subclasses might want to ensure all the inputs are in the same coordinate
            # frame.
            # self.verify_input_information()
            
            # Finally, generate the output information.
            self.generate_output_information()
            
            # Keep track of the last time GenerateOutputInformation() was called
            self._output_information_time.modified()

    ##############################################

    def generate_output_information(self):

        # Generate the information describing the output data. The default implementation of this
        # method will copy information from the input to the output. A filter may override this
        # method if its output will have different information than its input. For instance, a
        # filter that shrinks an image will need to provide an implementation for this method that
        # changes the spacing of the pixels. Such filters should call their superclass'
        # implementation of this method prior to changing the information values they need
        # (i.e. GenerateOutputInformation() should call Superclass::GenerateOutputInformation()
        # prior to changing the information.
        
        self._logger.info(self.name)
        
        # if self._inputs:
        #     primary_input = self.get_primary_input()
        #     for output in self._outputs.values():
        #         output.copy_information(primary_input)
        
        if self._inputs:
            for output in self._outputs.values():
                image_format = self.generate_image_format(output)
                output.copy_information(image_format)

    ##############################################

    def generate_image_format(self, output):

        raise NotImplementedError

    ##############################################

    def propagate_requested_region(self, output):

        # Send the requested region information back up the pipeline (to the filters that precede this one).

        self._logger.info(self.name)
        
        # check flag to avoid executing forever if there is a loop
        if self._updating:
            self._logger.info("{} is updating!".format(self.name))
            return
        
        # Give the subclass a chance to indicate that it will provide more data then required for
        # the output. This can happen, for example, when a source can only produce the whole output.
        # Although this is being called for a specific output, the source may need to enlarge all
        # outputs.
        # self.enlarge_output_requested_region(output)
        
        # Give the subclass a chance to define how to set the requested regions for each of its
        # outputs, given this output's requested region.  The default implementation is to make all
        # the output requested regions the same.  A subclass may need to override this method if
        # each output is a different resolution.
        # self.generate_output_requested_region(output)
        
        # Give the subclass a chance to request a larger requested region on the inputs. This is
        # necessary when, for example, a filter requires more data at the "internal" boundaries to
        # produce the boundary values - such as an image filter that derives a new pixel value by
        # applying some operation to a neighborhood of surrounding original values.
        # self.generate_input_requested_region()
        
        # Now that we know the input requested region, propagate this through all the inputs.
        self._updating = False
        for input_ in self._inputs.values():
            input_.propagate_requested_region()
        self._updating = False

    ##############################################

    def update_output_data(self):

        self._logger.info(self.name)
        
        # prevent chasing our tail
        if self._updating:
            self._logger.info("{} is updating!".format(self.name))
            return
        
        # Prepare all the outputs. This may deallocate previous bulk data.
        # self.prepare_outputs()
        
        # Propagate the update call - make sure everything we might rely on is up-to-date
        # Must call PropagateRequestedRegion before UpdateOutputData if multiple inputs since they
        # may lead back to the same data object.
        self._updating = True
        if len(self._inputs) == 1:
            self.get_primary_input().update_output_data()
        else:
            for input_ in self._inputs.values():
                input_.propagate_requested_region()
                input_.update_output_data()
        
        # start
        self.generate_data()
        # stop
        
        # Now we have to mark the data as up to date.
        for output in self._outputs.values():
            output.data_has_been_generated()
        
        self._updating = False

    ##############################################

    def generate_data(self):

        self._logger.info(self.name)