I start by configuring the Python environment using reticulate, specifying a custom virtual environment and ensuring required packages (matplotlib and pillow) are installed. This setup step ensures Python and R work seamlessly together; granted ‘seamlessly’ might be over-reaching, but at least for this case, fairly commonly used modules, it was the case.
With this chunk executed, R is now ready to run Python code from the designated virtual environment with the necessary libraries installed.
Image Configuration and Setup using matplotlib & Pillow
To overcome issues with displaying Pillow images directly in Quarto, I used matplotlib.pyplot to render and show images in the document. This section defines utility functions for drawing images programmatically, including a basic black board, a chessboard, and an inversion-based pattern. Otherwise, you’d likely have to set figure parameters such as dimensions, axes, etc. for every image input, so I create the below function to reduce/eliminate this redundancy.
Show the code
from PIL import Image import matplotlib.pyplot as pltimport math# build a function to ease the plotting, reusable boilerplate basically with option to add additional argumentsdef draw_figure(width=512, height=512, draw_func=None, title='Custom Board', figsize=(4, 4), background='white', **kwargs): board = Image.new('RGB', (width, height), background) # apply function if providedif draw_func: draw_func(board, width, height, **kwargs) plt.figure(figsize=figsize) plt.imshow(board) plt.axis('off') plt.title(title) plt.show()def solid_unicolor_board(board, width, height):for x inrange(width):for y inrange(height): board.putpixel( (x, y), (0, 0, 0))# define chess board patterndef chess_pattern(board, width, height, square =64, **kwargs):for x inrange(width):for y inrange(height): cx = x // square # or you can use math.floor(x / square) if index doesn't jstart at 0 cy = y // square color = ((cx + cy) %2) *255 board.putpixel((x, y), (color, color, color)) # black and white# define an inversion def inversion_pattern(board, width, height, R =256, square =64, **kwargs):for x inrange(width):for y inrange(height): x0 = x - width /2+0.5# define a center for x and one for y with offsets by half a pixel as technically the center is at .5, .5 y0 = y - height /2+0.5 f = R**2/ (x0**2+ y0**2) # inverse factor or the radius squared divided by distance to center; addition to avoid zero division x1 = x0 * f y1 = y0 * f cx = math.floor(x1 / square) cy = math.floor(y1 / square) color = ((cx + cy) %2) *255# board.putpixel((x, y), (color, color, 0)) # black and yellow board.putpixel((x, y), (color, color, color)) # black and white # outputting results/imagesdraw_figure( draw_func = solid_unicolor_board, title ='Solid Black Board' )
Show the code
draw_figure( draw_func = chess_pattern, title ='Chess Board' )
The closer you get to the center, the more stretched and squished things become visually (that’s if you really zoom it). This also explains why some areas look pixelated - the image doesn’t have enough detail at this size to show everything smoothly so next resolution is improved.
Improving Output Resolution
To reduce the pixelation, especially towards the center of the image, I increase both the image dimensions and the square size; essentially increase resolution.
Show the code
draw_figure( draw_func = inversion_pattern, width =512*2, height =512*2, square =256*2, R =256*2, title ='Improved Resolution (x2)' )
Doubling the dimensions already leads to significant visual improvement. The lift in clarity is again specially visible towards the center/origin where distortion is most intense/visible.
Improving Output Resolution (by a factor of 10)
Increasing resolution tenfold didn’t slow down render time that much so I go for it and it does look like we win in the trade.
Show the code
draw_figure( draw_func = inversion_pattern, width =512*10, height =512*10, square =256*10, R =256*10, title ='Final Inversion - Improved Resolution (x10)' )
Overall, I thought it was interesting, aesthetically if nothing else, to see how with a few lines of code, fairly intricate and cool-looking images get created. Thanks for reading !
---title: | <div class="custom-title-block" style="text-align: center;"> <span style="color:#2c3e50; font-size: 1.4em; font-weight: bold;">Exploring Inversions using Python</span><br> <span style="font-size:.5em;">Karim K. Kardous</span><br> <a href="mailto:kardouskarim@gmail.com" style="margin: 0 6px; font-size: 0.9em;"> <i class="bi bi-envelope"></i> </a> <a href="https://github.com/kkardousk" style="margin: 0 6px; font-size: 0.9em;"> <i class="bi bi-github"></i> </a> </div>format: html: toc: true toc-depth: 4 toc-expand: true toc-title: 'Jump To' number-depth: 2 fig-format: retina fig-dpi: 300 code-link: true # requires both downlit and xml2 to be downloaded code-fold: true code-summary: '<i class="bi-code-slash"></i> Show the code' # code-overflow: wrap code-tools: toggle: true # adds "Show All / Hide All"; also allows for all code copy (at once as quarto doc) css: styles.css highlight-style: github-dark df-print: paged page-layout: article embed-resources: true smooth-scroll: true link-external-icon: false link-external-newwindow: true fontsize: 1.1em linestretch: 0 linespace: 0 html-math-method: katex linkcolor: '#D35400'execute: echo: true warning: false message: false info: false cache: false freeze: autoeditor: visual---## **Initial Setup**::: text-justifyI start by configuring the Python environment using `{reticulate}`, specifying a custom virtual environment and ensuring required packages (matplotlib and pillow) are installed. This setup step ensures Python and R work seamlessly together; granted 'seamlessly' might be over-reaching, but at least for this case, fairly commonly used modules, it was the case.:::```{r}#| echo: true#| message: falselibrary(reticulate)invisible(capture.output({Sys.setenv(RETICULATE_PYTHON ="py_venv/bin/python3.9") reticulate::use_python("py_venv/bin/python3.9", required =TRUE) }))invisible(capture.output({use_python("py_venv/bin/python3.9", required =TRUE)py_config()py_install(c('matplotlib', 'pillow'), envname ="py_venv", pip =TRUE) }))```::: text-justifyWith this chunk executed, R is now ready to run Python code from the designated virtual environment with the necessary libraries installed.:::## **Image Configuration and Setup using matplotlib & [Pillow](https://pypi.org/project/pillow/)**::: text-justifyTo overcome issues with displaying Pillow images directly in Quarto, I used matplotlib.pyplot to render and show images in the document. This section defines utility functions for drawing images programmatically, including a basic black board, a chessboard, and an inversion-based pattern. Otherwise, you'd likely have to set figure parameters such as dimensions, axes, etc. for every image input, so I create the below function to reduce/eliminate this redundancy.:::```{python}#| echo: true#| message: falsefrom PIL import Image import matplotlib.pyplot as pltimport math# build a function to ease the plotting, reusable boilerplate basically with option to add additional argumentsdef draw_figure(width=512, height=512, draw_func=None, title='Custom Board', figsize=(4, 4), background='white', **kwargs): board = Image.new('RGB', (width, height), background) # apply function if providedif draw_func: draw_func(board, width, height, **kwargs) plt.figure(figsize=figsize) plt.imshow(board) plt.axis('off') plt.title(title) plt.show()def solid_unicolor_board(board, width, height):for x inrange(width):for y inrange(height): board.putpixel( (x, y), (0, 0, 0))# define chess board patterndef chess_pattern(board, width, height, square =64, **kwargs):for x inrange(width):for y inrange(height): cx = x // square # or you can use math.floor(x / square) if index doesn't jstart at 0 cy = y // square color = ((cx + cy) %2) *255 board.putpixel((x, y), (color, color, color)) # black and white# define an inversion def inversion_pattern(board, width, height, R =256, square =64, **kwargs):for x inrange(width):for y inrange(height): x0 = x - width /2+0.5# define a center for x and one for y with offsets by half a pixel as technically the center is at .5, .5 y0 = y - height /2+0.5 f = R**2/ (x0**2+ y0**2) # inverse factor or the radius squared divided by distance to center; addition to avoid zero division x1 = x0 * f y1 = y0 * f cx = math.floor(x1 / square) cy = math.floor(y1 / square) color = ((cx + cy) %2) *255# board.putpixel((x, y), (color, color, 0)) # black and yellow board.putpixel((x, y), (color, color, color)) # black and white # outputting results/imagesdraw_figure( draw_func = solid_unicolor_board, title ='Solid Black Board' )draw_figure( draw_func = chess_pattern, title ='Chess Board' )draw_figure( draw_func = inversion_pattern, square =256, title ='Initial inversion Board' )```::: text-justifyThe closer you get to the center, the more stretched and squished things become visually (that's if you really zoom it). This also explains why some areas look pixelated - the image doesn’t have enough detail at this size to show everything smoothly so next resolution is improved.:::## **Improving Output Resolution**::: text-justifyTo reduce the pixelation, especially towards the center of the image, I increase both the image dimensions and the square size; essentially increase resolution.:::```{python}draw_figure( draw_func = inversion_pattern, width =512*2, height =512*2, square =256*2, R =256*2, title ='Improved Resolution (x2)' )```::: text-justifyDoubling the dimensions already leads to significant visual improvement. The lift in clarity is again specially visible towards the center/origin where distortion is most intense/visible.:::## **Improving Output Resolution (by a factor of 10)**Increasing resolution tenfold didn't slow down render time that much so I go for it and it does look like we win in the trade.```{python}draw_figure( draw_func = inversion_pattern, width =512*10, height =512*10, square =256*10, R =256*10, title ='Final Inversion - Improved Resolution (x10)' )```Overall, I thought it was interesting, aesthetically if nothing else, to see how with a few lines of code, fairly intricate and cool-looking images get created. Thanks for reading !