As a beginner, where to start in coding for driverless car operations?

Hi there,

Sorry to be asking this in advance, but can anyone point me as to how to start coding for driverless car operations (such as steering, object detection, etc.)?

We need to implement a system, as a requirement for our course. Our professor told us he does not expect any complex code. To be quite honest, I have a better understanding of the code AI generates than any other tutorial/code I can find. But if you can lead me to any elementary-level explanation, or if someone is kind enough to guide me through the coding process or developing a simple code, please let me know. I am honestly struggling with coding in general…

Sorry if my question is inappropriate.
Thank you for your kind understanding!

I think I’d start by defining a Car class embodying the state of a
car. Initially: position and velocity (direction and speed). Something
like:

 class Car:
     def __init__(self, *, px, py, dr, vx, vy):
         self.px = px    # position X ordinate in metres
         self.py = py    # position Y ordinate in metres
         self.dr = dr    # direction in radians
         self.vx = vx    # speed in metres/second
         self.vy = vy    # speed in metres/second

I’d give it a tick(t) method which updates its state for t seconds
of time elapsed: update the position according to the direction and
speed if t seconds took place.

Then I’d give it a method steer(ddr) to adjust the direction by ddr
radians (“delta dr”, the change in direction).

Then I’d give it an object_detected(objx, objy) method called when
some system detects an object. Compute whether the path of the car takes
it too near the object, and adjust the steering andor speed to avoid it
if necessary.

Using metres/seconds/radians makes the math easy.

I’d write presentation functions to present things in more human
conventional forms like kilometres per hour for speed or degrees for
direction, etc.

2 Likes

Did he say anything about what a) the system; b) the code needs to do?

Hi Cameron,

Thanks for your reply! Would it be helpful to import numpy or matplotlib.pylot or the like? As I see from other autonomous vehicle codes that they import those.

Tick is something I have never encountered before. Thanks for letting me know about it. I think that would help one functionality of the driverless car I want to include as well, which is displaying real time data.

You are correct, using metres, radians, and seconds makes it clearer and simpler. I will try to build on it based on what you’ve given me.

Thanks once more for your guidance! :slight_smile:

Hi Karl,

Thanks for taking time to read through my query.

Actually, the first part of the assignment was designing the system using various UML models. This for the object-oriented programming course I am currently taking.

Then this is the second part, which is implementing code that supports the autonomous vehicle operation. I chose three:

  1. car control (steering, acceleration, brake),
  2. environment perception (mapping and object detection using LiDAR), and
  3. vehicle to infrastructure communication (which would display real time data)

We then have to test the code, using assert, and I’m thinking of using unittest to do that.

Thank you!

Thanks for your reply! Would it be helpful to import numpy or
matplotlib.pylot or the like? As I see from other autonomous vehicle
codes that they import those.

That might just be habit on their part. I wouldn’t bother unless you
need to do some bulk computation.

Matplotlib is great for making plots. Not what you need.

Numpy is great for computation on large amounts of data, mostly through
its Series (like a list) and DataFrame (like spreadsheet with
columns, each of which is effectively a Series). It it useful for
anything with large “grids” of numbers because it can run bulk
computations on these columns at machine speed. Not what you need (yet).

Tick is something I have never encountered before. Thanks for letting me know about it.

It is just a function name. In my mind it represents “what happens when
a short period of time elaspes”, like the tick of a clock.

I think that would help one functionality of the driverless car I want
to include as well, which is displaying real time data.

Matplotlib might help you here, depending on what you really need. If
you had a chunk of data, like a lot of rows saving the Car state at
points in time. You might record that state inside the tick function,
if that’s how you end up doing things.

If you’re saving all that state, then you might usefully save it in
a numpy DataFrame with columns for each piece of state from your car
(its position, speed, direction etc) and using the time as the index.
And now you’re into an area where importing numpy might be of help.

Better still, a numpy DataFrame has methods for drawing plots
directly (they call out to matplotlib internally, by default). See the
DataFrame.plot method, if you end up with a DataFrame.

These are part of Pandas, not Numpy.

2 Likes

Um, indeed. Apologies.

(Pokes around his own code…) Yep, I’ve been using numpy.array etc in
a time series module. And also pandas.DataFrame in the same module :slight_smile:
You’d think I’d remember this by now.

Hi Cameron,

Thanks for your explanation. It’s nice to learn about these things from experts.
Honestly, my brain can’t comprehend all of this at once, but I am trying my best.

I tried writing my code, but just a very simple one. I tried implementing some of your earlier suggestions.

I only imported math, for the steering radius/angle.

Aside from that, I didn’t include any. Since I don’t want to make it very complex, because I know my skill and knowledge is very limited at this point in time.

If you have the time (or anyone else reading this, actually), can you kindly comment on it and let me know how I can further improve on my code? Sorry in advance if it is written badly. Any suggestions are welcome.

import math

# System Implementation of the Driverless Car


class Car:

    # Front end where user login is authenticated before starting the system
    def __init__(self, user, password, model, year, identification):
        self.user = user  # username
        self.password = password  # password
        self.model = model  # car model
        self.year = year  # year the car was made
        self.identification = identification  # user id
        # Welcome message
        print(f"Welcome {self.user} with ID {self.identification}. You are using car {self.model} from {self.year}.")


# Operation 1: Car Control
class Control:
    speed = 0  # in kilometers per hr (km/ph)
    max_speed = 120  # maximum speed before automatic braking
    car_on = True  # if car is started
    steer_direction = math.radians  # angle of steering
    car_controls = ("start", "steer", "accelerate", "brake", "stop", "update")  # a list of car controls
    real_time_data = []  # updates about the system will be listed here

    def __init__(self, start, steer, accelerate, brake, update):
        self.start = start  # car starts once login is authenticated
        self.steer = steer  # cruise of the car
        self.accelerate = accelerate  # speed of the car
        self.brake = brake  # car halts
        self.update = update  # real time data is updated

    def start(self):
        if self.car_on:  # if true
            print(f"Car is starting.")
        else:  # if false
            print(f"Car is stopped.")

    def steer(self, steer_left, steer_right,):
        if math.radians(270) <= math.radians(90):  # steering angle towards the left
            assert isinstance(steer_left, object)
            print(f"Car is steering left.")
        elif math.radians(90) <= math.radians(270):  # steering angle towards the right
            assert isinstance(steer_right, object)
            print(f"Car is steering right.")

    def accelerate(self, speed):
        if speed >= 0:  # possible to speed up if speed is 0
            print(f"Accelerating.")  # car is speeding up

    def brake(self, speed):
        if speed >= 120:  # maximum speed has been reached, car has to break
            print(f"Braking.")  # car is slowing down

    def stop(self):
        if not self.car_on:  # user can control to stop as well
            print(f"Car is stopped.")  # car is not moving

    def update(self):  # displays real time data about the car
        self.car_controls = ()
        self.real_time_data = []
        print(f"The state of the car is {self.car_controls}. Real time data is {self.real_time_data}")


# Operation 2: Environment Perception
class Perception:
    present_location = []
    lane = True  # boolean value that states whether the car is driving within the lane
    avoid = ("other car", "humans", "animals", "trees", "plants")  # list of objects to avoid using LiDAR
    distance = 10  # in meters, maximum distance from object

    def __init__(self, location, lane_marking, avoid_object):
        self.location = location  # current location
        self.lane_marking = lane_marking  # lane marking on the road
        self.avoid_object = avoid_object  # objects to avoid to prevent collision

    def location(self):
        self.present_location = []  # tells the exact location of the car in real time using GS
        print(f"Present location is at [].")

    def lane_marking(self):  # checks whether the car is driving within the lane, using camera and LiDAR
        if self.lane:
            print(f"Get back within lane.")
        else:
            print(f"Continue driving.")

    def avoid_object(self, distance):  # detects objects to avoid using LiDAR
        self.avoid = ()
        if distance <= 10:  # distance of the object is too near, collision possible
            print(f"Object {self.avoid} detected. Avoid collision.")  # warning message to avoid collision


# Operation 3: Vehicle to Infrastructure Communication
class V2I:
    # list of traffic signs recorded in system using camera
    sign = ("stop", "u-turn", "no u-turn", "turn left", "turn right", "no right", "no left")
    # dictionary of traffic lights and what car motion they correspond to, using camera
    light = {
        "red": "stop",
        "yellow": "slow down",
        "green": "go"
    }

    def __init__(self, traffic_sign, traffic_light):
        self.traffic_sign = traffic_sign  # traffic signs
        self.traffic_light = traffic_light  # traffic lights

    def traffic_sign(self):
        self.sign = ()  # traffic sign is detected and interpreted by the camera and machine learning
        print(f"This traffic sign is telling you to {self.traffic_sign}")

    def traffic_light(self):
        self.light = {}  # traffic light is detected and interpreted by the camera and machine learning
        if self.light == "red":  # stop light
            print(f"Car should stop.")  # car stops
        elif self.light == "yellow":  # slow down
            print(f"Car should slow down.")  # car slows down
        elif self.light == "green":  # go sign
            print(f"Car can move.")  # car drives

Thank you! :slight_smile:

Assorted comments inline below…

class Car:

    # Front end where user login is authenticated before starting the system
    def __init__(self, user, password, model, year, identification):
        self.user = user  # username
        self.password = password  # password
        self.model = model  # car model
        self.year = year  # year the car was made
        self.identification = identification  # user id
        # Welcome message
        print(f"Welcome {self.user} with ID {self.identification}. You are using car {self.model} from {self.year}.")

The print’s good for debug so you get some confirmation that the Car
has been initialised correctly. But normally __init__ is silent (as
are many functions - think of the noise!)

In real systems we try to never store passwords in the clear, or at all
really. Don’t change this now, but a real system will store not the
password but a salted hash of it - this is a string computed from the
original password which lets you confirm that a supplied password
matches it, but does not let you compute the password from the hash -
the hash is a one way lossy computation.

# Operation 1: Car Control
class Control:
    speed = 0  # in kilometers per hr (km/ph)
    max_speed = 120  # maximum speed before automatic braking
    car_on = True  # if car is started
    steer_direction = math.radians  # angle of steering
    car_controls = ("start", "steer", "accelerate", "brake", "stop", "update")  # a list of car controls
    real_time_data = []  # updates about the system will be listed here

I don’t think the above does what you want.

You’re defining a set of attribute for the class itself (Control), not
each instance.

(Classes are objects too, so you can set attributes on them too.)

Normally you’d be setting some of these up in the __init__ method as
well, eg:

 self.speed = 0  # in kilometers per hr (km/ph)
 self.car_on = True  # if car is started
 self.steer_direction = math.radians  # angle of steering
 self.real_time_data = []  # updates about the system will be listed here

in the __init__ method.

What we do set on the class itself is attributes defining some common
defaults or other class wide features of the class. So you might start
your class like this:

 class Control:
     MAX_SPEED = 120  # maximum speed before automatic braking
     CAR_CONTROLS = ("start", "steer", "accelerate", "brake", "stop", "update")  # a list of car controls

Think about what these attributes mean: are they attributes for the
class, which apply for every instance and are not changed, or are they
attributes of an instance? The former you’d define as above. The
latter get defined on self in the __init__ method.

You can still refer to the class attributes via self, for example
self.MAX_SPEED - as long as you haven’t set that attribute on self
it will be found on self’s class. This lets you write:

 if self.speed > self.MAX_SPEED:

Another odd item is this line:

 steer_direction = math.radians  # angle of steering

math.radians is a function accepting a value in degrees and returning
a value in radians. I’m not sure what you intended to mean here.

 def start(self):
     if self.car_on:  # if true
         print(f"Car is starting.")
     else:  # if false
         print(f"Car is stopped.")

What’s the purpose of this function? Is it to start the car, which I
would expect to affect the value of self.car_on, or to report the
current state of the car, which is what it seems to do? i.e. printing
out whether the car is on or not.

 def steer(self, steer_left, steer_right,):
     if math.radians(270) <= math.radians(90):  # steering angle towards the left

The condition in the if-statement is always false. 270 is not <= 90, and
the corresponding values in radians have the same relation. You at least
want the comparison in the other direction: >=. Or swap them so that
90 is on the left.

However, you’re comparing two constant values: their relation is always
fixed.

I suspect you actually want to examine the value of
self.steer_direction. You can write this test like this:

 if math.radians(90) <= self.steer_direction <= math.radians(270):

because Python has this nice chained comparison so you can compare
things in one go as above.

Note that this assumes self.steer_direction is always positive, like
the degrees range 0 through 360 - you want your radians direction to be
between 0 and 2 π. This means that if you change direction you need to
make sure that the result is not negative. So if you subtract from it,
if the value goes below 0 you need it to wrap around to 2 π.

At the least, put in an assertion at the start of this function:

 assert 0 <= self.steer_direction < 2 * math.pi

That will at least fail if your value gets out of range. If you don’t
know the above to be true, all the if-statements which follow can make
incorrect decisions. Thus the assertion.

 assert isinstance(steer_left, object)

The assertion above will never fail. All values in Python are objects
and thus instances of object. What do you intend this assertion to
test?

    def accelerate(self, speed):
        if speed >= 0:  # possible to speed up if speed is 0
            print(f"Accelerating.")  # car is speeding up

Is this really what you mean? Acceleration is change in speed. If you
intend to express whether you’re allowed to accelerate, wouldn’t you
test that you’re below the max speed, not >0 ?

    def brake(self, speed):
        if speed >= 120:  # maximum speed has been reached, car has to break
            print(f"Braking.")  # car is slowing down

Ok, here you’re saying “too fast”. The 120 is what we cal a “magic
number”: a plain value in the code whose meaning isn’t explained.

At the start of the class you defined max_speed to be this value. You
should use it here:

 if speed >= self.max_speed:

This makes the meaning clear, and has the added benefit that you could
change the value at the top of the class and the approriate thing would
still happen here.

 def update(self):  # displays real time data about the car
     self.car_controls = ()
     self.real_time_data = []
     print(f"The state of the car is {self.car_controls}. Real time data is {self.real_time_data}")

This actually sets the attributes on self. Did you mean to do that? It
looks like this function should just contain the print() call.

 # Operation 2: Environment Perception
 class Perception:
     present_location = []
     lane = True  # boolean value that states whether the car is driving within the lane
     avoid = ("other car", "humans", "animals", "trees", "plants")  # list of objects to avoid using LiDAR
     distance = 10  # in meters, maximum distance from object

Again: which is these attributes are for the class, and which for the
instance? The instance attributes should be set on self in the
__init__ method.

Finally:

 def traffic_light(self):
     self.light = {}  # traffic light is detected and interpreted by the camera and machine learning
     if self.light == "red":  # stop light
         print(f"Car should stop.")  # car stops
     elif self.light == "yellow":  # slow down
         print(f"Car should slow down.")  # car slows down
     elif self.light == "green":  # go sign
         print(f"Car can move.")  # car drives

what I have a chain of if/elif/elif/… I like to put a final
clause like this:

 else:
     raise RuntimeError(f'unhandled value for self.light: {self.light!r}')

This arranges to raise an exception is you’ve missed something in the
earlier ifs. Shouldn’t happen, but if it does you want to know rather
than have your function silently do nothing when it should have done
something.

1 Like

I’ll only comment on a little part of the code.

First, it’s good to have a separate class that models the (perception of the) environment.
But the way the code is organized can be improved. The current code has a hotch-potch of class variables (like present_location, lane) and instance variables (location, lane_marking). This makes the code very unclear - and you will have huge problems later in changing or debugging the code – especially because the meanings of those variables partially overlap. So, I would advise to either use a dataclass or get rid of all class variables and put the ones you actually want/need inside the __init__ function.

You may also benefit from having a quick look at how Agent and Environment are modeled in Reinforcement Learning. See for instance how this is done in Python code in Gymnasium.

1 Like

Hi Cameron!

Thanks so much for all your comments! I really appreciate you explaining why my code was wrong, and for telling how I can further improve on it.

I will try to redo the code, and implement whatever changes you recommended. Hopefully I can do it!

Alright, this is noted. Thanks for the information.

Okay, I will try to apply this to the other classes as well.

I just wanted to command the car to inform the direction of steering, based on the angle.

The purpose is to report the current state of the car. Should I include this in a separate function, such as update then?

You are correct, that is what I want to do. I have never applied the chained comparison, though I did read on it before. Thanks for the reminder.

   def accelerate(self, speed):
        if speed >= 0:  # possible to speed up if speed is 0
            print(f"Accelerating.")  # car is speeding up

Is this really what you mean? Acceleration is change in speed. If you
intend to express whether you’re allowed to accelerate, wouldn’t you
test that you’re below the max speed, not >0 ?

Yes, I wanted the system to accelerate if its speed is between 0-120.

It is indeed something I never use, so I will try to incorporate this more in order to describe things clearer.

It is incomplete, I should keep in mind to end them with an else always.

Thank you so much, I will try my best to fix the code based on your comments, Cameron!

I have an additional question, is it possible to test this code with assert and unittests?

Cheers.

Hi Hans,

You are right, looking at them now, they are unclear. I should fix that, cause we are required to test the code using assert and unittest. And it might be a problem for me. seeing as I am already struggling as is now. But I will keep your comment in mind, as I will fix the code based on everyone’s suggestions here. Thank you, all!

And, thanks for your comment, and the links you sent are helpful. Going through them now!

I have never encountered data classes before. I’ll try using them in the code.

Thank you!

I will try to redo the code, and implement whatever changes you
recommended. Hopefully I can do it!

Most changes should be doable.

Okay, I will try to apply this to the other classes as well.

There’s a style feature here, too. Python doesn’t have “constants” like
some languages, but we share a convention that things we would treat as
constants we given UPPERCASE names. This doesn’t change any behaviour,
it just reminds us what kind of value we’re looking at.

I just wanted to command the car to inform the direction of steering, based on the angle.

Right. Normally an attribute is just like a variable - it’s a named
thing referring to a value. So you’d use it to store the sterring
direction. Having it command the car can be done (there’s a thing
called a property which can have side effects, but put that off for
now).

There are two ways to look at “commanding” the car:

  • just store the steering angle, in which case you’re arguably
    “informing” the car of the sterring direction, and for it to have an
    effect something has to pay it some attention, such as the tick()
    method
  • have a method to set the angle, and the method could do other
    things; in a sense that would be a command to do something. If
    you’re just changing the steering angle I don’t see much use for doing
    additional things

It just occurred to me: is the steering angle the car’s current
direction, or the angle on the steering wheel, causing change in the
current direction over time?

The purpose is to report the current state of the car. Should I include this in a separate function, such as update then?

I’d just give it a different name, like show_start(). Methods and
functions do things (even if it’s just printing) so they tend to have
verbs for names.

So a method named start() I would expect to actually start the car.
Try to have methods named after what they do.

  def accelerate(self, speed):
       if speed >= 0:  # possible to speed up if speed is 0
           print(f"Accelerating.")  # car is speeding up

Is this really what you mean? Acceleration is change in speed. If you
intend to express whether you’re allowed to accelerate, wouldn’t you
test that you’re below the max speed, not >0 ?

Yes, I wanted the system to accelerate if its speed is between 0-120.

Then you probably want to check both ends of the range, eg:

 if 0 <= speed < self.max_speed:

I have an additional question, is it possible to test this code with
assert and unittests?

Definitely.

You’re already putting in assertions. Do more. There’s a paradigm called
“design by contract”, which involves specifying what functions may be
given, and what they achieve. If you know these things you can reason
about whether your final outcome is achieved by the functions. The flip
side is that you can embed these constraints in the code as assertions.

Example:

 def square_root(x):
     assert x >= 0, f'negative x not supported (x={x})'
     result = math.sqrt(x)
     assert result * result == x, f'result:{result} is not the square root of x:{x}'
     return result

This function’s contract would say that it accepts nonnegative values
and returns their square root.

At the beginning of the function you can test the requirements for x
and at the end of the function you can test that the result you
computed satisfies the contract.

You can do this with most functions.

Aside: note that with float values the above assert isn’t reliable.
floats are effectively fractions, so a square root is really the
closest such fraction to the real square root and the reverse
multiplication will thus be slightly off too. We test floating point
things like this with an “almost equal” test.
The unittest module has an assertAlmostEqual method for just this
kind of thing:

Note that these are run-time tests - they check that you’re using the
function correctly, and that the results were good when called.

They’re not unit tests.

Unit tests call functions with a set of test cases you define, planned
to exercise that function in important ways and to ensure that it
behaves as you expect. They’re designed to test your functions in
isolation and small combinations without running your programme as a
whole.

There’s a variety of unit test frameworks available, but standard Python
ships with a module called unittest which is where you should start:

A file containing unit tests using this module looks like this:

 import unittest

 class TestSomething(unittest.TestCase):

     def setUp(self):
         ...

     def test_this(self):
         ...

     def test_that(self):
         ...

You can have a few TestCase classes (for example, one for Cars, one
for each other class), and each class can have many test* methods, one
for each unit test. These are normally quite small.

The setUp() method (optional) sets things on self important to all
tests. For simple things you might not need this. For a test case
covering a bunch of related things it would do some common setup. For
you, maybe it makes a Car (or one of your other classes):

 def setUp(self):
     self.car = Car(......)

Now you can use self.car as a ready made Car in each test without
repeating yourself in each test method. It gets called for each test
method, so you have a pristine new Car for every test.

Inside a test method you would do things with the car and make various
assertions. Instead of just using the assert statement, you’d use
various assert* methods defined in the module. Example (totally made
up):

 def test_speed(self):
     # assuming the Car is set up to move in the X direction
     self.car.speed = 10
     px0 = self.car.px
     self.car.tick(2)
     # check that the car move the expected distance
     self.assertEqual(self.car.px, px0 + 2 * 10)

If you’re going to run the file directly:

 python3 test_file.py

you’ll also want the standard boilerplate at the bottom:

 if __name__ == '__main__':
     unittest.main(__name__, None, argv)

which will run the tests if you invoke the file directly. There are
tools like pytest which will scan your code looking for tests and run
them for you.

Anyway, the point of unit tests is to test functions and classes in
isolation, outside running your programme as a whole.

They’ve got two primary purposes:

  • test that the function behaves according to your expectations for
    various important values
  • test that the functions continue to pass the tests after you’ve
    changed them! So useful when you modify your programme!

Remember that testing helps you find bugs. It doesn’t prove that you
have no bugs.