<p>Ok, after several hours of debugging, I finally got the answer and posting it here.
The issue was in image generation.</p>

<pre><code>class P3Image:
    def __init__(self, width,  height):
        self.width = width
        self.height = height

        self.img = [[None for _ in range(width)] for _ in range(height)]

    def set_pixel(self, x, y, color):
        self.img[y][x] = color
        print(self.img[19][173], y,x, color)


    def __rescale(self, color):
        ''' rescale a color range from 0-1 to 0-225 '''
        return Vec3(round(color.x*255), round(color.y*255) , round(color.z*255))

    def save(self, filename):
        with open(filename, 'w') as f:

            f.write(f'P3 {self.width} {self.height}\n255\n')
            for row in self.img:
                for color in row:
                    c = self.__rescale(color)
                    f.write(f'{c.x} {c.y} {c.z}  ')

                f.write('\n')
</code></pre>

CC BY-SA 4.0
2020-05-05T20:12:56.653Z
2020-05-05T20:12:56.653Z
anekix
<p>I am trying to implement a simple raytracer in python. <a href="https://www.gabrielgambetta.com/computer-graphics-from-scratch/basic-ray-tracing.html" rel="nofollow noreferrer">I am following this tutorial here</a> </p>

<p>here is my code, i have explained in comments the relevant parts .All i am getting is a blank image.      </p>

<p>Program</p>

<pre><code>import math
from collections import namedtuple


class P3Image:
    def __init__(self, width,  height):
        self.width = width
        self.height = height
        self.img = [[0]*width]*height

    def pixel(self, x, y, color):
        self.img[y][x] = color

    def __rescale(self, color):
        ''' rescale a color range from 0-1 to 0-225 '''
        return Vec3(round(color.x*255), round(color.y*255) , round(color.z*255))

    def save(self, filename):
        with open(filename, 'w') as f:
            f.write(f'P3 {self.width} {self.height}\n255\n')
            for row in self.img:
                for color in row:
                    color = self.__rescale(color)
                    f.write(f'{color.x} {color.y} {color.z}  ')
                f.write('\n')


class Vec3:
    def __init__(self, x, y, z):

        self.x = x
        self.y = y
        self.z = z

    def __str__(self):
        return f'Vec3({self.x},{self.y},{self.z})'

    def __sub__(self, other):
        return Vec3(self.x - other.x , self.y - other.y , self.z - other.z)

    def __add__(self, other):
        return Vec3(self.x +  other.x , self.y + other.y , self.z + other.z)

    def mag(self):
        return math.sqrt(self.x**2, self.y**2, self.z**2)

    def dot(self, other):
        if isinstance(other, Vec3):
            return self.x * other.x + self.y * other.y + self.z * other.z
        raise Exception('this should recieve an arfument of type Vec3') 

    def cross(self, other):
        if isinstance(other, Vec3):
            return Vec3( self.y* other.z - self.x* other.y, self.z* other.x - self.x* other.z , self.x * self.y - self.y * self.x)
        raise Exception('this should recieve an arfument of type Vec3') 


def trace_ray(O, D, t_min, t_max):
    closest_t = math.inf
    closest_sphere = None

    for sphere in scene['spheres']:
        t1, t2 = ray_sphere_intersect(O, D, sphere)

        if  (t_min &lt; t1 &lt; t_max) and t1 &lt; closest_t:
            closest_t  = t1
            closest_sphere = sphere

        if (t_min &lt; t2 &lt; t_max) and t2 &lt; closest_t:
            closest_t = t2

            closest_sphere = sphere

    if closest_sphere == None:
        return Vec3(1,1,1)
    else:
        return closest_sphere['color']



def dot(v1, v2):
    return v1.x* v2.x + v1.y * v2.y + v1.z * v2.z

def ray_sphere_intersect(O, D, sphere):
    C = sphere['center']
    r = sphere['radius']
    oc = O - C
    k1 = dot(D,D)
    k2 = 2*dot(oc, D)
    k3 = dot(oc, oc) - r*r

    discriminant = k2*k2 - 4*k1*k3

    if discriminant &lt; 0:
        return math.inf, math.inf

    t1 = (-k2 + math.sqrt(discriminant))/(2*k1)
    t2 = (-k2 - math.sqrt(discriminant))/(2*k1)
    return t1, t2


def canvas_to_viewport(x,y):
    return Vec3(x*Vw/Cw, y*Vh/Ch, d)


def c_to_i(x, y):
    '''canvas to image'''

    xnew = xmin + (xmax - xmin)/Cw
    ynew = ymin + (ymax - ymin)/Ch
    return Vec3(xnew, ynew, d)



# define scene with one sphere 
scene = {
        'spheres':[
                {
                'center':Vec3(0,0 ,0),
                'radius': 0.5,
                'color':Vec3(1,0,0)
                },
            ]
    }


if __name__ == '__main__':

    WIDTH  = 320 
    HEIGHT = 200
    camera = Vec3(0, 0, -1)

    aspect_ratio = WIDTH/HEIGHT

    xmin = -1 
    xmax = 1  

    ymax = 1/aspect_ratio
    ymin = -1*ymax

    xstep = (xmax - xmin) / (WIDTH-1)
    ystep = (ymax - ymin) / (HEIGHT-1)

    image = P3Image(WIDTH, HEIGHT)

    for j in range(HEIGHT):
        y  = ymin + j*ystep
        for i in range(WIDTH): 
            x  = xmin + i*xstep
            ray = Vec3(x,y,0) - camera
            color = trace_ray(camera, ray, -1, math.inf)

            print(i,j,color)

            image.pixel(i, j, color)


    image.save('ii.ppm')
</code></pre>

<p><strong>NOTE:</strong> <em>I modified the main render loop to avoid negative index while generating image as suggested by @Oliver. but now even now i get a blank image. but i am sure the rays are hitting sphere properly as evident from the output of print statement in code.</em></p>

CC BY-SA 4.0
2020-05-05T01:28:52.587Z
The `print(x,y, color)` trace shows it is working for me. It's just the image writing code which fails. I suspect because of the negative indices.
Olivier
CC BY-SA 4.0
2020-05-05T06:36:20.047Z
@Olivier yes, i figured that by looking at the pattern of print(x,y,color),  is having a viewport necessary as i have seen some code directly doing `for x in range(Cw)` and `for y in range(Ch)` ,
anekix
CC BY-SA 4.0
2020-05-05T13:15:04.157Z
you could loop that way and redefine `canvas_to_viewport` accordingly. There's no single correct way to do this.
Olivier
CC BY-SA 4.0
2020-05-05T18:36:34.343Z
@Olivier I changed the main render loop to avoid the negative index while generating images but still the output is blank, I have updated the original code above.
anekix
<raytracing>
a very simple raytracer is giving a black output which is implemented in python
anekix
# a very simple raytracer is giving a black output which is implemented in python

(Asked by: username_0 on 2020-05-04)

I am trying to implement a simple raytracer in python. [I am following this tutorial here](https://www.gabrielgambetta.com/computer-graphics-from-scratch/basic-ray-tracing.html)

here is my code, i have explained in comments the relevant parts .All i am getting is a blank image.

Program

```
import math
from collections import namedtuple

class P3Image:
    def __init__(self, width,  height):
        self.width = width
        self.height = height
        self.img = [[0]*width]*height

    def pixel(self, x, y, color):
        self.img[y][x] = color

    def __rescale(self, color):
        ''' rescale a color range from 0-1 to 0-225 '''
        return Vec3(round(color.x*255), round(color.y*255) , round(color.z*255))

    def save(self, filename):
        with open(filename, 'w') as f:
            f.write(f'P3 {self.width} {self.height}\n255\n')
            for row in self.img:
                for color in row:
                    color = self.__rescale(color)
                    f.write(f'{color.x} {color.y} {color.z}  ')
                f.write('\n')

class Vec3:
    def __init__(self, x, y, z):

        self.x = x
        self.y = y
        self.z = z

    def __str__(self):
        return f'Vec3({self.x},{self.y},{self.z})'

    def __sub__(self, other):
        return Vec3(self.x - other.x , self.y - other.y , self.z - other.z)

    def __add__(self, other):
        return Vec3(self.x +  other.x , self.y + other.y , self.z + other.z)

    def mag(self):
        return math.sqrt(self.x**2, self.y**2, self.z**2)

    def dot(self, other):
        if isinstance(other, Vec3):
            return self.x * other.x + self.y * other.y + self.z * other.z
        raise Exception('this should recieve an arfument of type Vec3') 

    def cross(self, other):
        if isinstance(other, Vec3):
            return Vec3( self.y* other.z - self.x* other.y, self.z* other.x - self.x* other.z , self.x * self.y - self.y * self.x)
        raise Exception('this should recieve an arfument of type Vec3') 

def trace_ray(O, D, t_min, t_max):
    closest_t = math.inf
    closest_sphere = None

    for sphere in scene['spheres']:
        t1, t2 = ray_sphere_intersect(O, D, sphere)

        if  (t_min < t1 < t_max) and t1 < closest_t:
            closest_t  = t1
            closest_sphere = sphere

        if (t_min < t2 < t_max) and t2 < closest_t:
            closest_t = t2

            closest_sphere = sphere

    if closest_sphere == None:
        return Vec3(1,1,1)
    else:
        return closest_sphere['color']

def dot(v1, v2):
    return v1.x* v2.x + v1.y * v2.y + v1.z * v2.z

def ray_sphere_intersect(O, D, sphere):
    C = sphere['center']
    r = sphere['radius']
    oc = O - C
    k1 = dot(D,D)
    k2 = 2*dot(oc, D)
    k3 = dot(oc, oc) - r*r

    discriminant = k2*k2 - 4*k1*k3

    if discriminant < 0:
        return math.inf, math.inf

    t1 = (-k2 + math.sqrt(discriminant))/(2*k1)
    t2 = (-k2 - math.sqrt(discriminant))/(2*k1)
    return t1, t2

def canvas_to_viewport(x,y):
    return Vec3(x*Vw/Cw, y*Vh/Ch, d)

def c_to_i(x, y):
    '''canvas to image'''

    xnew = xmin + (xmax - xmin)/Cw
    ynew = ymin + (ymax - ymin)/Ch
    return Vec3(xnew, ynew, d)

# define scene with one sphere 
scene = {
        'spheres':[
                {
                'center':Vec3(0,0 ,0),
                'radius': 0.5,
                'color':Vec3(1,0,0)
                },
            ]
    }

if __name__ == '__main__':

    WIDTH  = 320 
    HEIGHT = 200
    camera = Vec3(0, 0, -1)

    aspect_ratio = WIDTH/HEIGHT

    xmin = -1 
    xmax = 1  

    ymax = 1/aspect_ratio
    ymin = -1*ymax

    xstep = (xmax - xmin) / (WIDTH-1)
    ystep = (ymax - ymin) / (HEIGHT-1)

    image = P3Image(WIDTH, HEIGHT)

    for j in range(HEIGHT):
        y  = ymin + j*ystep
        for i in range(WIDTH): 
            x  = xmin + i*xstep
            ray = Vec3(x,y,0) - camera
            color = trace_ray(camera, ray, -1, math.inf)

            print(i,j,color)

            image.pixel(i, j, color)

    image.save('ii.ppm')

```

**NOTE:** *I modified the main render loop to avoid negative index while generating image as suggested by @Oliver. but now even now i get a blank image. but i am sure the rays are hitting sphere properly as evident from the output of print statement in code.*

**Tags:** raytracing

> **Comments:**
> **username_1**: The `print(x,y, color)` trace shows it is working for me. It's just the image writing code which fails. I suspect because of the negative indices.  
> **username_0**: @username_1 yes, i figured that by looking at the pattern of print(x,y,color),  is having a viewport necessary as i have seen some code directly doing `for x in range(Cw)` and `for y in range(Ch)` ,  
> **username_1**: you could loop that way and redefine `canvas_to_viewport` accordingly. There's no single correct way to do this.  
> **username_0**: @username_1 I changed the main render loop to avoid the negative index while generating images but still the output is blank, I have updated the original code above.  

---

## 1 answers

### Answer by username_0 on 2020-05-05. Score: 1

Ok, after several hours of debugging, I finally got the answer and posting it here.
The issue was in image generation.

```
class P3Image:
    def __init__(self, width,  height):
        self.width = width
        self.height = height

        self.img = [[None for _ in range(width)] for _ in range(height)]

    def set_pixel(self, x, y, color):
        self.img[y][x] = color
        print(self.img[19][173], y,x, color)

    def __rescale(self, color):
        ''' rescale a color range from 0-1 to 0-225 '''
        return Vec3(round(color.x*255), round(color.y*255) , round(color.z*255))

    def save(self, filename):
        with open(filename, 'w') as f:

            f.write(f'P3 {self.width} {self.height}\n255\n')
            for row in self.img:
                for color in row:
                    c = self.__rescale(color)
                    f.write(f'{c.x} {c.y} {c.z}  ')

                f.write('\n')

```


---

