# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# # Lesson 2: Errors
#
# Seeing errors is an inevitable part of programming. There is essentially no level of expertise at which a programmer expects to write error-free code. Let's look at some common types of errors and what you should do about them:
#
# - SyntaxError
# - NameError
# - IndexError
#
# In this module, you will learn
#
# - How to read and interpret common Python error messages
# - How to debug your code

# For the sake of demonstration we will quickly set ourselves up with a "load this image" function. For now it will just give us a random image.

# +
import numpy as np

def load_image(filename):
    return np.random.rand(400, 600)


# -

# ### Syntax errors

# **Exercise** What is this code supposed to do?

# +
from skimage.io import imread

for i in range(50)
    image_data = load_image("image_{}".format(i))
    my_images.append(image_data)
# -

# This file is trying to load a series of numbered images into an array of images.
#
# _However_ running it gives you an error
#
# Python errors are given to you as a "stack trace". Let's first dissect a stack trace.
#
# **`File "<ipython-input-2-some_ID_number>", line 3`** tells you what file, and where in the file, the error came from. Because we're in a notebook, instead of a file we get a message that tells us we're in a notebook using IPython and that the error was in input cell #2. Specifically, line 3 of cell 2.
#
# **`  for i in range(50)`** is where Python conveniently reminds us what code was at line 3. Sometimes it shows us a bit of code before and after to give us context.
#
# **`^`** marks exactly _where_ in line 3 the problem was noticed. This can be tricky because a problem with a function call may not be noticed until the closing ')' at the very end of the function. Nonetheless, here it might be helpful.
#
# **`SyntaxError: invalid syntax`**. This tells us that our problem is a SyntaxError, which is one of a large hierarchy of errors python can provide us with.
#
# `SyntaxError`s happen when Python sees you violating the rules of the language - it's a problem with the literal code characters you have typed rather than what your code is trying to do conceptually. Therefore they are usually short errors with quick fixes.

# **Exercise** do you see how to fix _this_ error?

# +
from skimage.io import imread

for i in range(50):
    image_data = load_image("image_{}".format(i))
    my_images.append(image_data)
# -

# ### NameError 

# `NameError`s happen when you try to access a python variable or function that does not exist yet. To get a NameError, Python has to actually try to run your code, so NameError is a type of `RuntimeError`.

# **Exercise** Can you add in a line that fixes this code?

# +
from skimage.io import imread

my_images = []

for i in range(50):
    image_data = load_image("image_{}".format(i))
    my_images.append(image_data)
# -

# In this case "my_images" does not exist, so it cannot be appended to, thus the name error. Making an empty my_images array solves this problem.
#
# Now that our images are loaded, let's check to make sure each images's mean intensity changes by less than 10% compared with the next timage. This might be a good check to make sure no one bumped our microscope or turned on a light while we were taking images.

for i in range(50):
    intensity = my_images[i].mean()
    next_intensity = my_images[i+1].mean()
    if abs(intensity - next_intensity) > 0.10 * intensity:
        print("Notice: intensity jumped between images {} and {}".format(i, i+1))
        break

# ### IndexError

# `IndexError`s happen when you try to access data from a list-like object, such as a `numpy` array or image, but the location you requested does not exist. Like asking for index 10 for a list of 10 items.
#
# In image processing, this is often caused by switching your rows/columns or width/height. Say you have a 400x600 image and try to access a pixel at row 401-600.
#
# It is also generally common when looping through something by index. This is a good reason to use Python's `for item in collection` syntax rather than looping through indices!

# +
data = [1, 3, "cat", 0.4]

# This is less clear and prone to error
for index in range(4):
    item = data[index]
    print(item)

# Than this
for item in data:
    print(item)
# -

# One interesting thing about Python loop's current value, `i` in this case, is available outside the loop and does not reset until you run the loop again. This let's us quickly check what value `i` took on when the code crashed!

print(i)

# **Exercise** Now can you explain what happened here? Can you fix this code?

for i in range(49):
    intensity = my_images[i].mean()
    next_intensity = my_images[i+1].mean()
    if abs(intensity - next_intensity) > 0.10 * intensity:
        print("Notice: intensity jumped between images {} and {}".format(i, i+1))
        break

# ### Sufficiency and necessity of error messages

# **Exercise** What would have happened if instead of comparing image `i` to image `i+1` we had compared image `i-1` (previous image) to `i`?

for i in range(50):
    intensity = my_images[i-1].mean()
    next_intensity = my_images[i].mean()
    if abs(intensity - next_intensity) > 0.10 * intensity:
        print("Notice: intensity jumped between images {} and {}".format(i-1, i))
        break

# **Exercise** There was no error! Did this solve our problem?

# No, it did not. Consider the first pass through the loop. `i` is 0. `i-1` is -1. What is `my_images[-1]`?

my_images[-1]

# `my_images[-1]` doesn't give a name error, -1 refers to the _last_ item in the list. But we don't want to compare the first and last images, so although this runs it's not the right behavior.

# ### Long stack traces

# What can make the errors that we see in this course particularly daunting is that we use many libraries which use other libraries which in turn use more libraries, etc. This means that the peice of code that reports the error is often code we didn't write or didn't even know was being run, which can make errors feel unfair or unsolveable. But here's what's really happening.
#
# Imagine that you send me on an errand to buy groceries. You give me detailed instructions (a program) describing the steps to take. So I get in the car and start driving to the store. Half way there, I notice I'm out of gas. That's OK, I have my own program for dealing with that. "Buy gas at a station" is a bit of instructions you didn't know I had, nor did you anticipate me using, but it's being used now anyway. I pull into the gas station, get out of my car, pay for the gas, and try to start filling up. However, the gas cover locks from the inside. I don't know about this (new car), and send you a text trying to precisely describe the error: "Nozzle cannot pass through solid metal".
#
# So here you are, having sent me to buy groceries, and I tell you I can't because "nozzle cannot pass through solid metal". Python is frustrating in the same way: generally it tells you the _lowest-level problem_ when it fails. It's important to keep this in mind: the piece of code that reports an error is probably not the one that caused it!
#
# Stack traces report errors with the first call at the top. **This means that you should read a long stack trace by starting at the bottom and working up until you see a line of code that you wrote or a function you called.** This line is likely to be the line you have to change. Maybe you passed a string to a function when it needed an integer.
#
# Lines below code you wrote in the stack trace may contain hints about what's wrong. Maybe the lowest error at the bottom of the stack trace is "Cannot subtract string from int", which is a cluse that there was a string where an int should be.
#
# Higher lines tell you the context that the error happened in, i.e. what code ran before the error. Maybe you have a function that you use several times - you want to know _which_ usage of the function is giving you the error. If it's the 2nd time you use the function, then either the function _can_ work, but not in a particular context, or the function is being called in the 2nd location _before_ the first: the flow of your program is not what you expected.

StarcoderdataJupyterScriptsDedupFiltered