def __init__(self, node_name):
        super(LaneFilterNode, self).__init__(node_name=node_name,
                                             node_type=NodeType.PERCEPTION)

        veh = os.getenv("VEHICLE_NAME")

        self._filter = rospy.get_param(
            '~lane_filter_histogram_kf_configuration', None)
        self._debug = rospy.get_param('~debug', False)
        self._predict_freq = rospy.get_param('~predict_frequency', 30.0)

        # Create the filter
        self.filter = LaneFilterHistogramKF(**self._filter)
        self.t_last_update = rospy.get_time()
        self.last_update_stamp = self.t_last_update

        self.filter.wheel_radius = rospy.get_param(
            f"/{veh}/kinematics_node/radius")
        self.filter.baseline = rospy.get_param(
            f"/{veh}/kinematics_node/baseline")

        # Subscribers
        self.sub_segment_list = rospy.Subscriber("~segment_list",
                                                 SegmentList,
                                                 self.cbProcessSegments,
                                                 queue_size=1)

        self.sub_encoder_left = rospy.Subscriber(
            "~left_wheel_encoder_node/tick",
            WheelEncoderStamped,
            self.cbProcessLeftEncoder,
            queue_size=1)

        self.sub_encoder_right = rospy.Subscriber(
            "~right_wheel_encoder_node/tick",
            WheelEncoderStamped,
            self.cbProcessRightEncoder,
            queue_size=1)

        self.sub_episode_start = rospy.Subscriber(f"episode_start",
                                                  BoolStamped,
                                                  self.cbEpisodeStart,
                                                  queue_size=1)

        # Publishers
        self.pub_lane_pose = rospy.Publisher(
            "~lane_pose",
            LanePose,
            queue_size=1,
            dt_topic_type=TopicType.PERCEPTION)

        self.pub_ml_img = rospy.Publisher("~measurement_likelihood_img",
                                          Image,
                                          queue_size=1,
                                          dt_topic_type=TopicType.DEBUG)

        self.pub_seglist_filtered = rospy.Publisher(
            "~seglist_filtered",
            SegmentList,
            queue_size=1,
            dt_topic_type=TopicType.DEBUG)

        self.right_encoder_ticks = 0
        self.left_encoder_ticks = 0
        self.right_encoder_ticks_delta = 0
        self.left_encoder_ticks_delta = 0
        # Set up a timer for prediction (if we got encoder data) since that data can come very quickly
        rospy.Timer(rospy.Duration(1 / self._predict_freq), self.cbPredict)

        self.bridge = CvBridge()
class LaneFilterNode(DTROS):
    """ Generates an estimate of the lane pose.

    Creates a `lane_filter` to get estimates on `d` and `phi`, the lateral and heading deviation from the center of the lane.
    It gets the segments extracted by the line_detector as input and output the lane pose estimate.


    Args:
        node_name (:obj:`str`): a unique, descriptive name for the node that ROS will use

    Configuration:
        ~filter (:obj:`list`): A list of parameters for the lane pose estimation filter
        ~debug (:obj:`bool`): A parameter to enable/disable the publishing of debug topics and images

    Subscribers:
        ~segment_list (:obj:`SegmentList`): The detected line segments from the line detector
        ~car_cmd (:obj:`Twist2DStamped`): The car commands executed. Used for the predict step of the filter
        ~change_params (:obj:`String`): A topic to temporarily changes filter parameters for a finite time only
        ~switch (:obj:``BoolStamped): A topic to turn on and off the node. WARNING : to be replaced with a service call to the provided mother node switch service
        ~fsm_mode (:obj:`FSMState`): A topic to change the state of the node. WARNING : currently not implemented
        ~(left/right)_wheel_encoder_node/tick (:obj: `WheelEncoderStamped`): Information from the wheel encoders

    Publishers:
        ~lane_pose (:obj:`LanePose`): The computed lane pose estimate

    """
    def __init__(self, node_name):
        super(LaneFilterNode, self).__init__(node_name=node_name,
                                             node_type=NodeType.PERCEPTION)

        veh = os.getenv("VEHICLE_NAME")

        self._filter = rospy.get_param(
            '~lane_filter_histogram_kf_configuration', None)
        self._debug = rospy.get_param('~debug', False)
        self._predict_freq = rospy.get_param('~predict_frequency', 30.0)

        # Create the filter
        self.filter = LaneFilterHistogramKF(**self._filter)
        self.t_last_update = rospy.get_time()
        self.last_update_stamp = self.t_last_update

        self.filter.wheel_radius = rospy.get_param(
            f"/{veh}/kinematics_node/radius")
        self.filter.baseline = rospy.get_param(
            f"/{veh}/kinematics_node/baseline")

        # Subscribers
        self.sub_segment_list = rospy.Subscriber("~segment_list",
                                                 SegmentList,
                                                 self.cbProcessSegments,
                                                 queue_size=1)

        self.sub_encoder_left = rospy.Subscriber(
            "~left_wheel_encoder_node/tick",
            WheelEncoderStamped,
            self.cbProcessLeftEncoder,
            queue_size=1)

        self.sub_encoder_right = rospy.Subscriber(
            "~right_wheel_encoder_node/tick",
            WheelEncoderStamped,
            self.cbProcessRightEncoder,
            queue_size=1)

        self.sub_episode_start = rospy.Subscriber(f"episode_start",
                                                  BoolStamped,
                                                  self.cbEpisodeStart,
                                                  queue_size=1)

        # Publishers
        self.pub_lane_pose = rospy.Publisher(
            "~lane_pose",
            LanePose,
            queue_size=1,
            dt_topic_type=TopicType.PERCEPTION)

        self.pub_ml_img = rospy.Publisher("~measurement_likelihood_img",
                                          Image,
                                          queue_size=1,
                                          dt_topic_type=TopicType.DEBUG)

        self.pub_seglist_filtered = rospy.Publisher(
            "~seglist_filtered",
            SegmentList,
            queue_size=1,
            dt_topic_type=TopicType.DEBUG)

        self.right_encoder_ticks = 0
        self.left_encoder_ticks = 0
        self.right_encoder_ticks_delta = 0
        self.left_encoder_ticks_delta = 0
        # Set up a timer for prediction (if we got encoder data) since that data can come very quickly
        rospy.Timer(rospy.Duration(1 / self._predict_freq), self.cbPredict)

        self.bridge = CvBridge()

    def cbEpisodeStart(self, msg):
        rospy.loginfo("Lane Filter Resetting")
        if msg.data:
            self.filter.reset()

    def cbProcessLeftEncoder(self, left_encoder_msg):
        if not self.filter.initialized:
            self.filter.encoder_resolution = left_encoder_msg.resolution
            self.filter.initialized = True
        self.left_encoder_ticks_delta = left_encoder_msg.data - self.left_encoder_ticks

    def cbProcessRightEncoder(self, right_encoder_msg):
        if not self.filter.initialized:
            self.filter.encoder_resolution = right_encoder_msg.resolution
            self.filter.initialized = True
        self.right_encoder_ticks_delta = right_encoder_msg.data - self.right_encoder_ticks

    def cbPredict(self, event):
        current_time = rospy.get_time()
        dt = current_time - self.t_last_update
        self.t_last_update = current_time

        # first let's check if we moved at all, if not abort
        if self.right_encoder_ticks_delta == 0 and self.left_encoder_ticks_delta == 0:
            return

        self.filter.predict(dt, self.left_encoder_ticks_delta,
                            self.right_encoder_ticks_delta)
        self.left_encoder_ticks += self.left_encoder_ticks_delta
        self.right_encoder_ticks += self.right_encoder_ticks_delta
        self.left_encoder_ticks_delta = 0
        self.right_encoder_ticks_delta = 0

        self.publishEstimate()

    def cbProcessSegments(self, segment_list_msg):
        """Callback to process the segments

        Args:
            segment_list_msg (:obj:`SegmentList`): message containing list of processed segments

        """

        self.last_update_stamp = segment_list_msg.header.stamp

        # Get actual timestamp for latency measurement
        timestamp_before_processing = rospy.Time.now()

        # Step 2: update
        self.filter.update(segment_list_msg.segments)

        self.publishEstimate(segment_list_msg)

    def publishEstimate(self, segment_list_msg=None):

        belief = self.filter.getEstimate()

        # build lane pose message to send
        lanePose = LanePose()
        lanePose.header.stamp = self.last_update_stamp
        lanePose.d = belief['mean'][0]
        lanePose.phi = belief['mean'][1]
        lanePose.d_phi_covariance = [
            belief['covariance'][0][0], belief['covariance'][0][1],
            belief['covariance'][1][0], belief['covariance'][1][1]
        ]
        lanePose.in_lane = True
        lanePose.status = lanePose.NORMAL

        self.pub_lane_pose.publish(lanePose)
        if segment_list_msg is not None:
            self.debugOutput(segment_list_msg, lanePose.d, lanePose.phi)

    def debugOutput(self, segment_list_msg, d_max, phi_max):
        """Creates and publishes debug messages

        Args:
            segment_list_msg (:obj:`SegmentList`): message containing list of filtered segments
            d_max (:obj:`float`): best estimate for d
            phi_max (:obj:``float): best estimate for phi

        """
        if self._debug:

            # Get the segments that agree with the best estimate and publish them
            inlier_segments = self.filter.get_inlier_segments(
                segment_list_msg.segments, d_max, phi_max)
            inlier_segments_msg = SegmentList()
            inlier_segments_msg.header = segment_list_msg.header
            inlier_segments_msg.segments = inlier_segments

            self.pub_seglist_filtered.publish(inlier_segments_msg)

            # Create belief image and publish it
            ml = self.filter.generate_measurement_likelihood(
                segment_list_msg.segments)
            if ml is not None:
                ml_img = self.bridge.cv2_to_imgmsg(
                    np.array(255 * ml).astype("uint8"), "mono8")
                ml_img.header.stamp = segment_list_msg.header.stamp
                self.pub_ml_img.publish(ml_img)

    def cbMode(self, msg):
        return  # TODO adjust self.active

    def updateVelocity(self, twist_msg):
        self.currentVelocity = twist_msg

    def loginfo(self, s):
        rospy.loginfo('[%s] %s' % (self.node_name, s))