def __init__(self):
        AlgorithmPluginManager.register_plugins()

        # Algorithms
        self.algo_convert_img = ConvertImage("convert_image")
        self.algo_image_io = ImageIo("image_reader")
        self.algo_track_features = TrackFeatures("feature_tracker")

        # Other tool variables
        self.image_list_filepath = None
        self.mask_list_filepath = None
        self.invert_masks = False
        self.expect_multichannel_masks = False
        self.output_tracks_filepath = None
    def __init__(self):
        AlgorithmPluginManager.register_plugins()

        # Algorithms
        self.algo_convert_img = ConvertImage("convert_image")
        self.algo_image_io = ImageIo("image_reader")
        self.algo_track_features = TrackFeatures("feature_tracker")

        # Other tool variables
        self.image_list_filepath = None
        self.mask_list_filepath = None
        self.invert_masks = False
        self.expect_multichannel_masks = False
        self.output_tracks_filepath = None
    def test_image_load_save_diff(self):
        fd, tmp_filename = tempfile.mkstemp()

        c = ConfigBlock()
        c.set_value('iio:type', 'vxl')
        iio = ImageIo('iio')
        iio.set_config(c)

        nt.assert_true(osp.isfile(self.test_image_filepath),
                       "Couldn't find image file")
        ic_orig = iio.load(self.test_image_filepath)
        iio.save(ic_orig, tmp_filename)
        ic_test = iio.load(tmp_filename)

        nt.assert_equal(ic_orig.size(), ic_test.size())
        nt.assert_equal(ic_orig.width(), ic_test.width())
        nt.assert_equal(ic_orig.height(), ic_test.height())
        nt.assert_equal(ic_orig.depth(), ic_test.depth())

        os.remove(tmp_filename)
        os.close(fd)
    def test_image_load_save_diff(self):
        fd, tmp_filename = tempfile.mkstemp()

        c = ConfigBlock()
        c.set_value("iio:type", "vxl")
        iio = ImageIo("iio")
        iio.set_config(c)

        nt.assert_true(osp.isfile(self.test_image_filepath), "Couldn't find image file")
        ic_orig = iio.load(self.test_image_filepath)
        iio.save(ic_orig, tmp_filename)
        ic_test = iio.load(tmp_filename)

        nt.assert_equal(ic_orig.size(), ic_test.size())
        nt.assert_equal(ic_orig.width(), ic_test.width())
        nt.assert_equal(ic_orig.height(), ic_test.height())
        nt.assert_equal(ic_orig.depth(), ic_test.depth())

        os.remove(tmp_filename)
        os.close(fd)
class TrackFeaturesTool (object):

    @property
    def log(self):
        return logging.getLogger("TrackFeaturesTool")

    def __init__(self):
        AlgorithmPluginManager.register_plugins()

        # Algorithms
        self.algo_convert_img = ConvertImage("convert_image")
        self.algo_image_io = ImageIo("image_reader")
        self.algo_track_features = TrackFeatures("feature_tracker")

        # Other tool variables
        self.image_list_filepath = None
        self.mask_list_filepath = None
        self.invert_masks = False
        self.expect_multichannel_masks = False
        self.output_tracks_filepath = None

    def get_configuration(self):
        """
        :return: the current tool configuration
        :rtype: ConfigBlock
        """
        c = ConfigBlock("track_features")
        c.set_value("image_list_file",
                    self.image_list_filepath or "",
                    "Path to an input file containing new-line separated paths "
                    "to sequential image files")
        c.set_value("mask_list_file",
                    self.mask_list_filepath or "",
                    "Optional path to an input file containing new-line "
                    "separated paths to mask images. This list should be "
                    "parallel in association to files specified in "
                    "``image_list_file``. Mask image must be the same size as "
                    "the image they are associated with.\n"
                    "\n"
                    "Leave this blank if no image masking is desired.")
        c.set_value("invert_masks",
                    str(self.invert_masks).lower(),
                    "If true, all mask images will be inverted after loading. "
                    "This is useful if mask images read in use positive "
                    "values to indicated masked areas instead of non-masked "
                    "areas.")
        c.set_value("expect_multichannel_masks",
                    str(self.expect_multichannel_masks).lower(),
                    "A majority of the time, mask images are a single channel, "
                    "however it is feasibly possible that certain "
                    "implementations may use multi-channel masks. If this is "
                    "true we will expect multiple-channel mask images, "
                    "warning when a single-channel mask is provided. If this "
                    "is false we error upon seeing a multi-channel mask "
                    "image.")
        c.set_value("output_tracks_file",
                    self.output_tracks_filepath or "",
                    "Path to a file to write output tracks to. If this "
                    "file exists, it will be overwritten.")
        # Required algorithm does not have an implemented interface yet
        # c.set_value("output_homography_file",
        #             self.output_homography_filepath or "",
        #             "Optional path to a file to write source-to-reference "
        #             "homographies for each frame. Leave blank to disable this "
        #             "output. The output_homography_generator algorithm type "
        #             "only needs to be set if this is set.")

        self.algo_convert_img.get_config(c)
        self.algo_image_io.get_config(c)
        self.algo_track_features.get_config(c)

        return c

    def set_configuration(self, config):
        """
        Set the tool configuration
        :param config: The configuration to set
        :type config: ConfigBlock
        """
        self.algo_convert_img.set_config(config)
        self.algo_image_io.set_config(config)
        self.algo_track_features.set_config(config)

        abspath = lambda p: os.path.abspath(os.path.expanduser(p))

        self.image_list_filepath = config.get_value('image_list_file',
                                                    self.image_list_filepath)
        if self.image_list_filepath:
            self.image_list_filepath = abspath(self.image_list_filepath)
        self.mask_list_filepath = config.get_value('mask_list_file',
                                                   self.mask_list_filepath)
        if self.mask_list_filepath:
            self.mask_list_filepath = abspath(self.mask_list_filepath)
        self.invert_masks = config.get_value_bool('invert_masks', self.invert_masks)
        self.expect_multichannel_masks = \
            config.get_value_bool('expect_multichannel_masks',
                            self.expect_multichannel_masks)
        self.output_tracks_filepath = \
            config.get_value('output_tracks_file', self.output_tracks_filepath)
        if self.output_tracks_filepath:
            self.output_tracks_filepath = abspath(self.output_tracks_filepath)

    def validate_configuration(self):
        """
        Check the current configuration for validity
        :return: True if valid, false if not.
        :type: bool
        """
        if not self.image_list_filepath:
            self.log.error("No image list file provided")
            return False
        if not os.path.isfile(self.image_list_filepath):
            self.log.error("Path given for image list does not refer to a file")
            return False
        if not self.output_tracks_filepath:
            self.log.error("No output tracks file privided")
            return False
        if self.mask_list_filepath and not os.path.isfile(self.mask_list_filepath):
            self.log.error("Specified a mask list filepath, but path did not "
                           "refer to an existing file.")
            return False

        return (
            # Algorithms should check out valid
            self.algo_convert_img.check_config(self.algo_convert_img.get_config())
            and self.algo_image_io.check_config(self.algo_image_io.get_config())
            and self.algo_track_features.check_config(self.algo_track_features.get_config())
        )

    def track_images(self):
        # Create list of input filepaths
        self.log.info("Reading input image list file...")
        with open(self.image_list_filepath, 'r') as image_list_file:
            input_filepaths = \
                [line.strip() for line in image_list_file.readlines()]

        # Create a list of mask images if we were given a list file
        mask_filepaths = None
        if self.mask_list_filepath:
            self.log.info("Reading mask image list file...")
            with open(self.mask_list_filepath) as mask_list_file:
                mask_filepaths = \
                    [line.strip() for line in mask_list_file.readlines()]

            if len(input_filepaths) != len(mask_filepaths):
                self.log.error("Input and Mask image lists were not congruent "
                               "in size.")
                return False

        # Check that the output tracks file is open-able and that the containing
        # directory exists.
        if not os.path.isdir(os.path.dirname(self.output_tracks_filepath)):
            self.log.info("Creating containing directory for output tracks "
                          "file: %s",
                          os.path.dirname(self.output_tracks_filepath))
            os.mkdir(os.path.dirname(self.output_tracks_filepath))
        self.log.info("Testing that output tracks file is open-able...")
        test_f = open(self.output_tracks_filepath, 'w')
        test_f.close()

        tracks = TrackSet()
        for frame_num in xrange(len(input_filepaths)):
            input_img = self.algo_convert_img.convert(
                self.algo_image_io.load(input_filepaths[frame_num])
            )
            mask_img = None
            if mask_filepaths:
                mask_img = self.algo_convert_img.convert(
                    self.algo_image_io.load(mask_filepaths[frame_num])
                )
            self.log.info("Processing frame %d...", frame_num)
            tracks = self.algo_track_features.track(tracks, frame_num,
                                                    input_img, mask_img)

        self.log.info("Frame processing complete, writing out track set...")
        tracks.write_tracks_file(self.output_tracks_filepath)
class TrackFeaturesTool(object):
    @property
    def log(self):
        return logging.getLogger("TrackFeaturesTool")

    def __init__(self):
        AlgorithmPluginManager.register_plugins()

        # Algorithms
        self.algo_convert_img = ConvertImage("convert_image")
        self.algo_image_io = ImageIo("image_reader")
        self.algo_track_features = TrackFeatures("feature_tracker")

        # Other tool variables
        self.image_list_filepath = None
        self.mask_list_filepath = None
        self.invert_masks = False
        self.expect_multichannel_masks = False
        self.output_tracks_filepath = None

    def get_configuration(self):
        """
        :return: the current tool configuration
        :rtype: ConfigBlock
        """
        c = ConfigBlock("track_features")
        c.set_value(
            "image_list_file", self.image_list_filepath or "",
            "Path to an input file containing new-line separated paths "
            "to sequential image files")
        c.set_value(
            "mask_list_file", self.mask_list_filepath or "",
            "Optional path to an input file containing new-line "
            "separated paths to mask images. This list should be "
            "parallel in association to files specified in "
            "``image_list_file``. Mask image must be the same size as "
            "the image they are associated with.\n"
            "\n"
            "Leave this blank if no image masking is desired.")
        c.set_value(
            "invert_masks",
            str(self.invert_masks).lower(),
            "If true, all mask images will be inverted after loading. "
            "This is useful if mask images read in use positive "
            "values to indicated masked areas instead of non-masked "
            "areas.")
        c.set_value(
            "expect_multichannel_masks",
            str(self.expect_multichannel_masks).lower(),
            "A majority of the time, mask images are a single channel, "
            "however it is feasibly possible that certain "
            "implementations may use multi-channel masks. If this is "
            "true we will expect multiple-channel mask images, "
            "warning when a single-channel mask is provided. If this "
            "is false we error upon seeing a multi-channel mask "
            "image.")
        c.set_value(
            "output_tracks_file", self.output_tracks_filepath or "",
            "Path to a file to write output tracks to. If this "
            "file exists, it will be overwritten.")
        # Required algorithm does not have an implemented interface yet
        # c.set_value("output_homography_file",
        #             self.output_homography_filepath or "",
        #             "Optional path to a file to write source-to-reference "
        #             "homographies for each frame. Leave blank to disable this "
        #             "output. The output_homography_generator algorithm type "
        #             "only needs to be set if this is set.")

        self.algo_convert_img.get_config(c)
        self.algo_image_io.get_config(c)
        self.algo_track_features.get_config(c)

        return c

    def set_configuration(self, config):
        """
        Set the tool configuration
        :param config: The configuration to set
        :type config: ConfigBlock
        """
        self.algo_convert_img.set_config(config)
        self.algo_image_io.set_config(config)
        self.algo_track_features.set_config(config)

        abspath = lambda p: os.path.abspath(os.path.expanduser(p))

        self.image_list_filepath = config.get_value('image_list_file',
                                                    self.image_list_filepath)
        if self.image_list_filepath:
            self.image_list_filepath = abspath(self.image_list_filepath)
        self.mask_list_filepath = config.get_value('mask_list_file',
                                                   self.mask_list_filepath)
        if self.mask_list_filepath:
            self.mask_list_filepath = abspath(self.mask_list_filepath)
        self.invert_masks = config.get_value_bool('invert_masks',
                                                  self.invert_masks)
        self.expect_multichannel_masks = \
            config.get_value_bool('expect_multichannel_masks',
                            self.expect_multichannel_masks)
        self.output_tracks_filepath = \
            config.get_value('output_tracks_file', self.output_tracks_filepath)
        if self.output_tracks_filepath:
            self.output_tracks_filepath = abspath(self.output_tracks_filepath)

    def validate_configuration(self):
        """
        Check the current configuration for validity
        :return: True if valid, false if not.
        :type: bool
        """
        if not self.image_list_filepath:
            self.log.error("No image list file provided")
            return False
        if not os.path.isfile(self.image_list_filepath):
            self.log.error(
                "Path given for image list does not refer to a file")
            return False
        if not self.output_tracks_filepath:
            self.log.error("No output tracks file privided")
            return False
        if self.mask_list_filepath and not os.path.isfile(
                self.mask_list_filepath):
            self.log.error("Specified a mask list filepath, but path did not "
                           "refer to an existing file.")
            return False

        return (
            # Algorithms should check out valid
            self.algo_convert_img.check_config(
                self.algo_convert_img.get_config()) and
            self.algo_image_io.check_config(self.algo_image_io.get_config())
            and self.algo_track_features.check_config(
                self.algo_track_features.get_config()))

    def track_images(self):
        # Create list of input filepaths
        self.log.info("Reading input image list file...")
        with open(self.image_list_filepath, 'r') as image_list_file:
            input_filepaths = \
                [line.strip() for line in image_list_file.readlines()]

        # Create a list of mask images if we were given a list file
        mask_filepaths = None
        if self.mask_list_filepath:
            self.log.info("Reading mask image list file...")
            with open(self.mask_list_filepath) as mask_list_file:
                mask_filepaths = \
                    [line.strip() for line in mask_list_file.readlines()]

            if len(input_filepaths) != len(mask_filepaths):
                self.log.error("Input and Mask image lists were not congruent "
                               "in size.")
                return False

        # Check that the output tracks file is open-able and that the containing
        # directory exists.
        if not os.path.isdir(os.path.dirname(self.output_tracks_filepath)):
            self.log.info(
                "Creating containing directory for output tracks "
                "file: %s", os.path.dirname(self.output_tracks_filepath))
            os.mkdir(os.path.dirname(self.output_tracks_filepath))
        self.log.info("Testing that output tracks file is open-able...")
        test_f = open(self.output_tracks_filepath, 'w')
        test_f.close()

        tracks = TrackSet()
        for frame_num in xrange(len(input_filepaths)):
            input_img = self.algo_convert_img.convert(
                self.algo_image_io.load(input_filepaths[frame_num]))
            mask_img = None
            if mask_filepaths:
                mask_img = self.algo_convert_img.convert(
                    self.algo_image_io.load(mask_filepaths[frame_num]))
            self.log.info("Processing frame %d...", frame_num)
            tracks = self.algo_track_features.track(tracks, frame_num,
                                                    input_img, mask_img)

        self.log.info("Frame processing complete, writing out track set...")
        tracks.write_tracks_file(self.output_tracks_filepath)