PyVista error trying to cast a shadow onto a gridded plane - vtk

PyVista is great, and let's me do lots of cool things very conveniently. I would like to cast the shadow of a 3D object onto a plane with grid lines. Here's what I would like, minus the shadows, shadows=False, gridlines=True
I can enable shadows without the grid lines: gridlines=False, shadows=True
but when I try to do both gridlines=True, shadows=True I get a massive error trace as per below and a result just like above, shadows but no grid lines.
QUESTION: is what I am trying to possible? Am I misunderstanding grids and shadows, have I coded it wrong, or is this a bug. My bigger program, with more graphical entities will segfault. I am running PyVista 0.29.0, Python 3.8.5 on MacOS 10.15.7. Similar problems arise if I try to cast the shadow on to a StructuredGrid.
Below is my minimal code example
import pyvista as pv
import vtk
import numpy as np
# change these values
gridlines = True
shadows = False
plotter = pv.Plotter(polygon_smoothing=True, window_size=(2000,2000))
plotter.disable_parallel_projection()
sphere = pv.Sphere(radius=0.3, center=(0,0,1))
plotter.add_mesh(sphere, ambient=0.2, diffuse=0.5, specular=0.8, specular_power=30,
smooth_shading=True, color='dodgerblue')
# add the grid
grid = pv.Plane(i_size=5, j_size=5)
plotter.add_mesh(grid, show_edges=gridlines, ambient=0, diffuse=0.5, specular=0.8, color='red', edge_color='white')
if shadows:
# do the shadows
shadows = vtk.vtkShadowMapPass()
seq = vtk.vtkSequencePass()
passes = vtk.vtkRenderPassCollection()
passes.AddItem(shadows.GetShadowMapBakerPass())
passes.AddItem(shadows)
seq.SetPasses(passes)
# Tell the renderer to use our render pass pipeline
cameraP = vtk.vtkCameraPass()
cameraP.SetDelegatePass(seq)
plotter.renderer.SetPass(cameraP)
plotter.set_background('white')
plotter.show()
Error trace:
(dev) [233 ~...code/RVC3-python/tools] % /Users/corkep/opt/miniconda3/envs/dev/bin/python /Users/corkep/Dropbox/code/RVC3-python/chapter8/pvbug.py
2021-03-11 19:57:38.908 ( 0.621s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b5f90): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:38.908 ( 0.622s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b6840): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:38.908 ( 0.622s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b7120): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:38.908 ( 0.622s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b7a00): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:38.908 ( 0.622s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b82e0): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:38.990 ( 0.704s) [ 84DF2C] vtkShaderProgram.cxx:452 ERR| vtkShaderProgram (0x7fee29ba49b0): 1: #version 150
2: #ifdef GL_ES
3: #ifdef GL_FRAGMENT_PRECISION_HIGH
4: precision highp float;
5: precision highp sampler2D;
6: precision highp sampler3D;
7: #else
8: precision mediump float;
9: precision mediump sampler2D;
10: precision mediump sampler3D;
11: #endif
12: #define texelFetchBuffer texelFetch
13: #define texture1D texture
14: #define texture2D texture
15: #define texture3D texture
16: #else // GL_ES
17: #define highp
18: #define mediump
19: #define lowp
20: #if __VERSION__ == 150
21: #define texelFetchBuffer texelFetch
22: #define texture1D texture
23: #define texture2D texture
24: #define texture3D texture
25: #endif
26: #endif // GL_ES
27: #define varying in
28:
29:
30: /*=========================================================================
31:
32: Program: Visualization Toolkit
33: Module: vtkPolyDataFS.glsl
34:
35: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
36: All rights reserved.
37: See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
38:
39: This software is distributed WITHOUT ANY WARRANTY; without even
40: the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
41: PURPOSE. See the above copyright notice for more information.
42:
43: =========================================================================*/
44: // Template for the polydata mappers fragment shader
45:
46: uniform int PrimitiveIDOffset;
47:
48:
49:
50: // VC position of this fragment
51: //VTK::PositionVC::Dec
52:
53: // Camera prop
54: uniform int cameraParallel;
55:
56:
57: // optional color passed in from the vertex shader, vertexColor
58: uniform float ambientIntensity; // the material ambient
59: uniform float diffuseIntensity; // the material diffuse
60: uniform float opacityUniform; // the fragment opacity
61: uniform vec3 ambientColorUniform; // ambient color
62: uniform vec3 diffuseColorUniform; // diffuse color
63:
64:
65: // optional surface normal declaration
66: //VTK::Normal::Dec
67:
68: // extra lighting parameters
69: uniform vec3 lightColor0;
70: uniform vec3 lightDirectionVC0; // normalized
71: uniform vec3 lightColor1;
72: uniform vec3 lightDirectionVC1; // normalized
73: uniform vec3 lightColor2;
74: uniform vec3 lightDirectionVC2; // normalized
75: uniform vec3 lightColor3;
76: uniform vec3 lightDirectionVC3; // normalized
77: uniform vec3 lightColor4;
78: uniform vec3 lightDirectionVC4; // normalized
79:
80: uniform float depthC;
81: vec2 calcShadow(in vec4 vert,
82: in sampler2D shadowMap,
83: in mat4 shadowTransform,
84: in float attenuation,
85: in int shadowParallel,
86: in float sNearZ, in float sFarZ)
87: {
88: vec4 shadowCoord = shadowTransform*vert;
89: float expFactor = 8.0;
90: float thickness = 0.0;
91: if(shadowCoord.w > 0.0)
92: {
93: vec2 projected = shadowCoord.xy/shadowCoord.w;
94: if(projected.x >= 0.0 && projected.x <= 1.0
95: && projected.y >= 0.0 && projected.y <= 1.0)
96: {
97: float ldepth = shadowCoord.z;
98: if (shadowParallel == 0) { ldepth = (shadowCoord.w - sNearZ)/(sFarZ - sNearZ); }
99: float depthCExpActual = exp(- depthC*ldepth);
100: float depthCExpBlured = texture2D(shadowMap,projected).r;
101: expFactor = depthCExpBlured * depthCExpActual;
102: float depth = log(depthCExpBlured)/depthC;
103: thickness = clamp(ldepth - depth, 0.0, 1.0)*(sFarZ - sNearZ);
104: if (expFactor > 1.0) { expFactor = 1.0; }
105: }
106: }
107: return vec2(1.0 - attenuation + attenuation*expFactor, thickness);
108: }
109: uniform int shadowParallel0;
110: uniform float shadowNearZ0;
111: uniform float shadowFarZ0;
112: uniform float shadowAttenuation0;
113: uniform sampler2D shadowMap0;
114: uniform mat4 shadowTransform0;
115: uniform int shadowParallel1;
116: uniform float shadowNearZ1;
117: uniform float shadowFarZ1;
118: uniform float shadowAttenuation1;
119: uniform sampler2D shadowMap1;
120: uniform mat4 shadowTransform1;
121: uniform int shadowParallel2;
122: uniform float shadowNearZ2;
123: uniform float shadowFarZ2;
124: uniform float shadowAttenuation2;
125: uniform sampler2D shadowMap2;
126: uniform mat4 shadowTransform2;
127: uniform int shadowParallel3;
128: uniform float shadowNearZ3;
129: uniform float shadowFarZ3;
130: uniform float shadowAttenuation3;
131: uniform sampler2D shadowMap3;
132: uniform mat4 shadowTransform3;
133:
134:
135: // Texture maps
136: //VTK::TMap::Dec
137:
138: // Texture coordinates
139: //VTK::TCoord::Dec
140:
141: // picking support
142: //VTK::Picking::Dec
143:
144: // Depth Peeling Support
145: //VTK::DepthPeeling::Dec
146:
147: // clipping plane vars
148: //VTK::Clip::Dec
149:
150: // the output of this shader
151: out vec4 fragOutput0;
152:
153:
154: // Apple Bug
155: //VTK::PrimID::Dec
156:
157: // handle coincident offsets
158: uniform float cCValue;
159: uniform float cSValue;
160: uniform float cDValue;
161:
162: // Value raster
163: //VTK::ValuePass::Dec
164:
165: void main()
166: {
167: // VC position of this fragment. This should not branch/return/discard.
168: //VTK::PositionVC::Impl
169:
170: // Place any calls that require uniform flow (e.g. dFdx) here.
171: //VTK::UniformFlow::Impl
172:
173: // Set gl_FragDepth here (gl_FragCoord.z by default)
174: float Zdc = gl_FragCoord.z*2.0 - 1.0;
175: float Z2 = -1.0*cDValue/(Zdc + cCValue) + cSValue;
176: float Zdc2 = -1.0*cCValue - cDValue/Z2;
177: gl_FragDepth = Zdc2*0.5 + 0.5;
178:
179:
180: // Early depth peeling abort:
181: //VTK::DepthPeeling::PreColor
182:
183: // Apple Bug
184: //VTK::PrimID::Impl
185:
186: //VTK::Clip::Impl
187:
188: //VTK::ValuePass::Impl
189:
190: vec3 ambientColor = ambientIntensity * ambientColorUniform;
191: vec3 diffuseColor = diffuseIntensity * diffuseColorUniform;
192: float opacity = opacityUniform;
193:
194:
195: // Generate the normal if we are not passed in one
196: //VTK::Normal::Impl
197:
198: vec2 factor0 = vec2(1.0);
199: vec2 factor1 = calcShadow(vertexVC, shadowMap0, shadowTransform0, shadowAttenuation0, shadowParallel0, shadowNearZ0, shadowFarZ0);
200: vec2 factor2 = calcShadow(vertexVC, shadowMap1, shadowTransform1, shadowAttenuation1, shadowParallel1, shadowNearZ1, shadowFarZ1);
201: vec2 factor3 = calcShadow(vertexVC, shadowMap2, shadowTransform2, shadowAttenuation2, shadowParallel2, shadowNearZ2, shadowFarZ2);
202: vec2 factor4 = calcShadow(vertexVC, shadowMap3, shadowTransform3, shadowAttenuation3, shadowParallel3, shadowNearZ3, shadowFarZ3);
203: fragOutput0 = vec4(ambientColor + diffuseColor, opacity);
204: //VTK::Light::Impl
205:
206:
207:
208: //VTK::TCoord::Impl
209:
210: if (fragOutput0.a <= 0.0)
211: {
212: discard;
213: }
214:
215: //VTK::DepthPeeling::Impl
216:
217: //VTK::Picking::Impl
218:
219: // handle coincident offsets
220: //VTK::Coincident::Impl
221: }
ERROR:root:1: #version 150
2021-03-11 19:57:38.991 ( 0.705s) [ 84DF2C] vtkShaderProgram.cxx:453 ERR| vtkShaderProgram (0x7fee29ba49b0): ERROR: 0:199: Use of undeclared identifier 'vertexVC'
ERROR: 0:200: Use of undeclared identifier 'vertexVC'
ERROR: 0:201: Use of undeclared identifier 'vertexVC'
ERROR: 0:202: Use of undeclared identifier 'vertexVC'
ERROR:root:ERROR: 0:199: Use of undeclared identifier 'vertexVC'
2021-03-11 19:57:39.023 ( 0.737s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b5f90): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.023 ( 0.737s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b6840): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.023 ( 0.737s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b7120): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.023 ( 0.737s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b7a00): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.023 ( 0.737s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b82e0): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.024 ( 0.738s) [ 84DF2C] vtkShaderProgram.cxx:437 ERR| vtkShaderProgram (0x7fee29ba49b0): 1: #version 150
2: #ifndef GL_ES
3: #define highp
4: #define mediump
5: #define lowp
6: #endif // GL_ES
7: #define attribute in
8: #define varying out
9:
10:
11: /*=========================================================================
12:
13: Program: Visualization Toolkit
14: Module: vtkPolyDataVS.glsl
15:
16: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
17: All rights reserved.
18: See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
19:
20: This software is distributed WITHOUT ANY WARRANTY; without even
21: the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
22: PURPOSE. See the above copyright notice for more information.
23:
24: =========================================================================*/
25:
26: in vec4 vertexMC;
27:
28:
29:
30: // frag position in VC
31: //VTK::PositionVC::Dec
32:
33: // optional normal declaration
34: //VTK::Normal::Dec
35:
36: // extra lighting parameters
37: //VTK::Light::Dec
38:
39: // Texture coordinates
40: //VTK::TCoord::Dec
41:
42: // material property values
43: //VTK::Color::Dec
44:
45: // clipping plane vars
46: //VTK::Clip::Dec
47:
48: // camera and actor matrix values
49: uniform mat4 MCDCMatrix;
50:
51: // Apple Bug
52: //VTK::PrimID::Dec
53:
54: // Value raster
55: //VTK::ValuePass::Dec
56:
57: // picking support
58: //VTK::Picking::Dec
59:
60: void main()
61: {
62: //VTK::Color::Impl
63:
64: //VTK::Normal::Impl
65:
66: //VTK::TCoord::Impl
67:
68: //VTK::Clip::Impl
69:
70: //VTK::PrimID::Impl
71:
72: gl_Position = MCDCMatrix * vertexMC;
73:
74:
75: //VTK::ValuePass::Impl
76:
77: //VTK::Light::Impl
78:
79: //VTK::Picking::Impl
80: }
ERROR:root:1: #version 150
2021-03-11 19:57:39.025 ( 0.739s) [ 84DF2C] vtkShaderProgram.cxx:438 ERR| vtkShaderProgram (0x7fee29ba49b0):
WARNING:root:ERROR: In /Users/tjcorona/Development/vtk/source/Rendering/OpenGL2/vtkShaderProgram.cxx, line 438
vtkShaderProgram (0x7fee29ba49b0):
2021-03-11 19:57:39.292 ( 1.006s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b5f90): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.292 ( 1.006s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b6840): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.292 ( 1.006s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b7120): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.292 ( 1.006s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b7a00): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.292 ( 1.006s) [ 84DF2C] vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b82e0): not a spotlight.
ERROR:root:not a spotlight.
2021-03-11 19:57:39.293 ( 1.007s) [ 84DF2C] vtkShaderProgram.cxx:437 ERR| vtkShaderProgram (0x7fee29ba49b0): 1: #version 150
2: #ifndef GL_ES
3: #define highp
4: #define mediump
5: #define lowp
6: #endif // GL_ES
7: #define attribute in
8: #define varying out
9:
10:
11: /*=========================================================================
12:
13: Program: Visualization Toolkit
14: Module: vtkPolyDataVS.glsl
15:
16: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
17: All rights reserved.
18: See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
19:
20: This software is distributed WITHOUT ANY WARRANTY; without even
21: the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
22: PURPOSE. See the above copyright notice for more information.
23:
24: =========================================================================*/
25:
26: in vec4 vertexMC;
27:
28:
29:
30: // frag position in VC
31: //VTK::PositionVC::Dec
32:
33: // optional normal declaration
34: //VTK::Normal::Dec
35:
36: // extra lighting parameters
37: //VTK::Light::Dec
38:
39: // Texture coordinates
40: //VTK::TCoord::Dec
41:
42: // material property values
43: //VTK::Color::Dec
44:
45: // clipping plane vars
46: //VTK::Clip::Dec
47:
48: // camera and actor matrix values
49: uniform mat4 MCDCMatrix;
50:
51: // Apple Bug
52: //VTK::PrimID::Dec
53:
54: // Value raster
55: //VTK::ValuePass::Dec
56:
57: // picking support
58: //VTK::Picking::Dec
59:
60: void main()
61: {
62: //VTK::Color::Impl
63:
64: //VTK::Normal::Impl
65:
66: //VTK::TCoord::Impl
67:
68: //VTK::Clip::Impl
69:
70: //VTK::PrimID::Impl
71:
72: gl_Position = MCDCMatrix * vertexMC;
73:
74:
75: //VTK::ValuePass::Impl
76:
77: //VTK::Light::Impl
78:
79: //VTK::Picking::Impl
80: }
ERROR:root:1: #version 150
2021-03-11 19:57:39.294 ( 1.008s) [ 84DF2C] vtkShaderProgram.cxx:438 ERR| vtkShaderProgram (0x7fee29ba49b0):
WARNING:root:ERROR: In /Users/tjcorona/Development/vtk/source/Rendering/OpenGL2/vtkShaderProgram.cxx, line 438
vtkShaderProgram (0x7fee29ba49b0):

Shadowing is currently not implemented in PyVista, and I don't know how reliable it is in vtk (or how to make it work reliably).
Quoting from this comment:
As for the shadows, it works 100% of the time 60% of the time. We should add as an option and then users can play around with it. I'm sure at some point real time ray tracing is going to be a hardware feature even down to integrated GPUs, so it's a matter of time until this becomes a well tested/supported feature.
For instance on my laptop with an integrated GPU shadow rendering is a mess.
With window size (1000, 1000):
With window size (2000, 1000):
In any case I can more or less reproduce the errors you're seeing. The first error comes partly from PyVista:
vtkLightActor.cxx:285 ERR| vtkLightActor (0x7fee299b5f90): not a spotlight.
ERROR:root:not a spotlight.
This is because PyVista Light objects have a vtkLightActor attached to them, but these should be hidden (i.e. never rendered) unless the light is a spotlight (the only case where a vtkLightActor makes sense). As I noted on the aforementioned issue the error shouldn't arise and it makes little sense that shadowing affects this. If this ended up staying a problem we could try mitigating it in PyVista, but it would be a lot of complexity for questionable gain.
The second error from the shader is very much VTK and I don't know what exactly it's about. Considering how this is not a supported feature of PyVista and you're using vtk directly, it probably makes more sense to raise the issue with vtk. (Although I don't know if there might be something we could put in on the PyVista side to make it work more reliably.)
For what it's worth you might be able to swap your if shadows block with just this:
plotter.renderer.SetUseShadows(shadows)
At least I see the same behaviour if I use that. So in cases where your code works for you, it might be a simpler alternative.

Related

Linux Kernel 5.10 verifier rejects eBPF XDP program that is fine for kernel 5.13

I am writing some eBPF programs in Rust using redBPF and I've encountered some issue with the verifier that only appears on some kernels.
This is a minimal reproducer XDP probe that shows the issue:
#[xdp]
unsafe fn xdp_test(ctx: XdpContext) -> XdpResult {
let data = ctx.data()?;
let start = ctx.data_start();
let off = data.offset();
let end = ctx.data_end();
/* Ensuring an upper bound for off doesn't make any difference
if off > 50 {
return XdpResult::Err(OutOfBounds);
}
*/
let mut address = start + off;
for i in 0..500 {
address = start + off + i;
if address <= start || address >= end {
break;
}
// This line (packet access) fails on kernel 5.10, but works fine on 5.13
let byte = *(address as *const u8);
// Just so the packet read above doesn't get optimized away
printk!("%u", byte as u32);
}
Ok(XdpAction::Pass)
}
Compiling this into eBPF bytecode and loading it into an Ubuntu 5.13 kernel (5.13.0-48-generic #54~20.04.1-Ubuntu SMP Thu Jun 2 23:37:17 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux) works without issues. The verifier accepts the program.
However, trying to load the exact same bytecode into an Android emulator kernel 5.10.66-android12-9-00041-gfa9c9074531e-ab7914766 #1 SMP PREEMPT Fri Nov 12 11:36:25 UTC 2021 x86_64 the verifier rejects the program. This is the full error log from the verifier:
ret=-1 os error=Permission denied (os error 13): 0: (61) r6 = *(u32 *)(r1 +4)
1: (61) r7 = *(u32 *)(r1 +0)
2: (bf) r1 = r7
3: (07) r1 += 14
4: (2d) if r1 > r6 goto pc+43
R1_w=pkt(id=0,off=14,r=14,imm=0) R6_w=pkt_end(id=0,off=0,imm=0) R7_w=pkt(id=0,off=0,r=14,imm=0) R10=fp0
5: (71) r2 = *(u8 *)(r7 +13)
6: (67) r2 <<= 8
7: (71) r3 = *(u8 *)(r7 +12)
8: (4f) r2 |= r3
9: (55) if r2 != 0x8 goto pc+38
R1_w=pkt(id=0,off=14,r=14,imm=0) R2_w=inv8 R3_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R6_w=pkt_end(id=0,off=0,imm=0) R7_w=pkt(id=0,off=0,r=14,imm=0) R10=fp0
10: (bf) r2 = r7
11: (07) r2 += 34
12: (2d) if r2 > r6 goto pc+35
R1=pkt(id=0,off=14,r=34,imm=0) R2=pkt(id=0,off=34,r=34,imm=0) R3=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R10=fp0
13: (71) r3 = *(u8 *)(r1 +0)
14: (67) r3 <<= 2
15: (57) r3 &= 60
16: (71) r2 = *(u8 *)(r1 +9)
17: (0f) r1 += r3
last_idx 17 first_idx 12
regs=8 stack=0 before 16: (71) r2 = *(u8 *)(r1 +9)
regs=8 stack=0 before 15: (57) r3 &= 60
regs=8 stack=0 before 14: (67) r3 <<= 2
regs=8 stack=0 before 13: (71) r3 = *(u8 *)(r1 +0)
18: (15) if r2 == 0x11 goto pc+31
R1_w=pkt(id=1,off=14,r=0,umax_value=60,var_off=(0x0; 0x3c)) R2_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R3_w=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R10=fp0
19: (55) if r2 != 0x6 goto pc+28
R1_w=pkt(id=1,off=14,r=0,umax_value=60,var_off=(0x0; 0x3c)) R2_w=inv6 R3_w=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R10=fp0
20: (2d) if r7 > r1 goto pc+27
R1=pkt(id=1,off=14,r=0,umax_value=60,var_off=(0x0; 0x3c)) R2=inv6 R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R10=fp0
21: (bf) r2 = r1
22: (07) r2 += 20
23: (2d) if r2 > r6 goto pc+24
R1=pkt(id=1,off=14,r=34,umax_value=60,var_off=(0x0; 0x3c)) R2_w=pkt(id=1,off=34,r=34,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R10=fp0
24: (71) r2 = *(u8 *)(r1 +12)
25: (77) r2 >>= 2
26: (57) r2 &= 60
27: (0f) r1 += r2
last_idx 27 first_idx 20
regs=4 stack=0 before 26: (57) r2 &= 60
regs=4 stack=0 before 25: (77) r2 >>= 2
regs=4 stack=0 before 24: (71) r2 = *(u8 *)(r1 +12)
28: (2d) if r7 > r1 goto pc+19
R1=pkt(id=2,off=14,r=0,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R10=fp0
29: (bf) r8 = r1
30: (3d) if r1 >= r6 goto pc+17
R1=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8_w=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R10=fp0
31: (bf) r1 = r8
32: (1f) r1 -= r7
33: (25) if r1 > 0x32 goto pc+14
R1_w=inv(id=0,umax_value=50,var_off=(0x0; 0xffffffff)) R2=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8_w=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R10=fp0
34: (b7) r9 = 0
35: (bf) r1 = r8
36: (0f) r1 += r9
last_idx 36 first_idx 28
regs=200 stack=0 before 35: (bf) r1 = r8
regs=200 stack=0 before 34: (b7) r9 = 0
37: (3d) if r7 >= r1 goto pc+10
R1=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R9=invP0 R10=fp0
38: (3d) if r1 >= r6 goto pc+9
R1=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R9=invP0 R10=fp0
39: (71) r3 = *(u8 *)(r1 +0)
invalid access to packet, off=14 size=1, R1(id=2,off=14,r=13)
R1 offset is outside of the packet
processed 40 insns (limit 1000000) max_states_per_insn 0 total_states 4 peak_states 4 mark_read 2
As I understand the issue, the verifier of the 5.10 kernel rejects the dereferencing of the packet pointer, claiming that we don't have validated that it is always within bounds (reading at offset 14 while r is 13). We do in fact check this just above.
Interestingly, if I oversize the bounds check above to something like this, both the 5.10 and 5.13 kernel verifiers accept the program:
[snip]
for i in 0..500 {
address = start + off + i;
// Checking 2 bytes ahead makes 5.10 verifier happy
if address <= start || (address + 2) >= end {
break;
}
// Works on both 5.10 and 5.13
let byte = *(address as *const u8);
// Just so the packet read above doesn't get optimized away
printk!("%u", byte as u32);
}
Ok(XdpAction::Pass)
}
But the above is not what I want, because this causes the bounded loop to abort too early - I want the loop to run fully, if the packet is large enough. I have tried the usual tricks I do when I run into verifier issues, but so far to no avail. I don't quite understand why the 5.10 verifier is unhappy with the first example. Usually this is related to some unbounded registers, but as far as I can see all bounds should be satisfied.
I have tried looking at a diff of the kernel verifier between the two versions, but couldn't see any obvious change that causes this.
TL;DR. You are missing bug fix 2fa7d94afc1a for the BPF verifier. It was backported to the v5.13 kernel you are using as commit e7a61f15beea, but not to the v5.10 kernel.
You might want to try a newer Android kernel if possible, or to ask them to carry the bugfix if they don't on v5.10.
Verifier Error Explanation
I removed parts of the output that were irrelevant here.
R1=pkt(id=2,off=14,r=0,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R10=fp0
29: (bf) r8 = r1
30: (3d) if r1 >= r6 goto pc+17
R1=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8_w=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R10=fp0
[...]
34: (b7) r9 = 0
35: (bf) r1 = r8
36: (0f) r1 += r9
37: (3d) if r7 >= r1 goto pc+10
R1=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R9=invP0 R10=fp0
38: (3d) if r1 >= r6 goto pc+9
R1=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R2=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R3=invP(id=0,umax_value=60,var_off=(0x0; 0x3c)) R6=pkt_end(id=0,off=0,imm=0) R7=pkt(id=0,off=0,r=34,imm=0) R8=pkt(id=2,off=14,r=13,umax_value=120,var_off=(0x0; 0x7c),s32_max_value=124,u32_max_value=124) R9=invP0 R10=fp0
39: (71) r3 = *(u8 *)(r1 +0)
invalid access to packet, off=14 size=1, R1(id=2,off=14,r=13)
R1 offset is outside of the packet
The verifier tells us that we are trying to access the packet at offset 14 (off=14) with an access size of 1 byte when the packet is only known to be at least 13 bytes long (r=13). The known packet length here is incorrect somehow because you checked that address >= end.
Going up, we can check where that packet range (r=13) is coming from. It is assigned from R8 at instruction 35, itself assigned from R1 at instruction 29.
At instruction 30, we find the address >= end check in its bytecode form. We see that both R1 and R8's ranges are updated from r=0 to r=13 after this check. That is however incorrect and should be updated to off+1, hence r=15.
Checking the Verifier Sources
In the verifier, this update of the range is implemented in find_good_pkt_pointers. The update logic looks fine and shouldn't cause this issue.
But if we git blame those lines, we can see they were changed in commit 2fa7d94afc1a. The commit describes the off-by-two error you are hitting:
This commit fixes the off-by-two error by adjusting new_range in the
right direction and fixes the tests by changing the range into the
one that should actually fail.
This commit was released upstream in v5.16. Checking the Ubuntu sources for Ubuntu-hwe-5.13-5.13.0-48.54_20.04.1, we find this same commit backported as e7a61f15beea, which explains why it works on your v5.13 kernel.

Can cast pandas Series to `int64` but not to `Int64`

I am stuck with a weird type conversion issue..
I have a pandas DataFrame pp with a column Value. All values are 'int', of type float. It is possible to convert them all to int64. But I get an error when attempting to conver to nullable Int64. Help!
Some details: pp['Value'] is of dtype float and may contain NaN. For my specific case, all values are integer values (see my debugging attempts below). I want them to become Int64. This has worked for months with thousands of series, but now suddenly 1 series seems to fail - I cannot find any non-int value that would explain this.
What I am trying: Convert the below DataSeries to Int64:
print(f"pp['Value']:\n{pp['Value']}")
pp['Value']:
0 3500000.0
1 600000.0
2 400000.0
3 8300000.0
4 5700000.0
5 4400000.0
6 3000000.0
7 2700000.0
8 2000000.0
9 800000.0
10 300000.0
11 300000.0
12 5300000.0
13 2500000.0
14 11000000.0
15 1000000.0
16 18000000.0
17 6250000.0
18 5000000.0
19 4400000.0
20 4200000.0
21 2000000.0
22 1750000.0
23 900000.0
24 4000000.0
25 800000.0
26 9250000.0
27 5200000.0
28 600000.0
29 5700000.0
30 13500000.0
31 10000000.0
32 3300000.0
33 3200000.0
34 2000000.0
35 750000.0
Name: Value, dtype: float64
For some reason, pp['Value'].astype('Int64') raises the error TypeError: cannot safely cast non-equivalent float64 to int64
I debugged 2 alternative approaches, with both work..
A: Convert the series to 'int64' - works like a charm (the numbers
really all are integers):
pp['Value'] = pp['Value'].astype('int64')
print(f"pp['Value']:\n{pp['Value']}")
pp['Value']:
0 3500000
1 600000
2 400000
3 8300000
4 5700000
5 4400000
6 3000000
7 2700000
8 2000000
9 800000
10 300000
11 300000
12 5300000
13 2500000
14 11000000
15 1000000
16 18000000
17 6250000
18 5000000
19 4400000
20 4200000
21 2000000
22 1750000
23 900000
24 4000000
25 800000
26 9250000
27 5200000
28 600000
29 5700000
30 13500000
31 10000000
32 3300000
33 3200000
34 2000000
35 750000
Name: Value, dtype: int64
B: Converted each element individually, and checked whether any single value does have some weird floating-point arithmetic issue.. not the case either. All values can be converted..
for idx, row in pp.iterrows():
print(f"{idx}: value = {row['Value']}, residual vs. int: {row['Value']%row['Value']}, int value: {int(row['Value'])}")
0: value = 3500000.0, residual vs. int: 0.0, int value: 3500000
1: value = 600000.0, residual vs. int: 0.0, int value: 600000
2: value = 400000.0, residual vs. int: 0.0, int value: 400000
3: value = 8300000.000000001, residual vs. int: 0.0, int value: 8300000
4: value = 5700000.0, residual vs. int: 0.0, int value: 5700000
5: value = 4400000.0, residual vs. int: 0.0, int value: 4400000
6: value = 3000000.0, residual vs. int: 0.0, int value: 3000000
7: value = 2700000.0, residual vs. int: 0.0, int value: 2700000
8: value = 2000000.0, residual vs. int: 0.0, int value: 2000000
9: value = 800000.0, residual vs. int: 0.0, int value: 800000
10: value = 300000.0, residual vs. int: 0.0, int value: 300000
11: value = 300000.0, residual vs. int: 0.0, int value: 300000
12: value = 5300000.0, residual vs. int: 0.0, int value: 5300000
13: value = 2500000.0, residual vs. int: 0.0, int value: 2500000
14: value = 11000000.0, residual vs. int: 0.0, int value: 11000000
15: value = 1000000.0, residual vs. int: 0.0, int value: 1000000
16: value = 18000000.0, residual vs. int: 0.0, int value: 18000000
17: value = 6250000.0, residual vs. int: 0.0, int value: 6250000
18: value = 5000000.0, residual vs. int: 0.0, int value: 5000000
19: value = 4400000.0, residual vs. int: 0.0, int value: 4400000
20: value = 4200000.0, residual vs. int: 0.0, int value: 4200000
21: value = 2000000.0, residual vs. int: 0.0, int value: 2000000
22: value = 1750000.0, residual vs. int: 0.0, int value: 1750000
23: value = 900000.0, residual vs. int: 0.0, int value: 900000
24: value = 4000000.0, residual vs. int: 0.0, int value: 4000000
25: value = 800000.0, residual vs. int: 0.0, int value: 800000
26: value = 9250000.0, residual vs. int: 0.0, int value: 9250000
27: value = 5200000.0, residual vs. int: 0.0, int value: 5200000
28: value = 600000.0, residual vs. int: 0.0, int value: 600000
29: value = 5700000.0, residual vs. int: 0.0, int value: 5700000
30: value = 13500000.0, residual vs. int: 0.0, int value: 13500000
31: value = 10000000.0, residual vs. int: 0.0, int value: 10000000
32: value = 3300000.0, residual vs. int: 0.0, int value: 3300000
33: value = 3200000.0, residual vs. int: 0.0, int value: 3200000
34: value = 2000000.0, residual vs. int: 0.0, int value: 2000000
35: value = 750000.0, residual vs. int: 0.0, int value: 750000
I am lost... All the values are int. I can convert all values to int. I can convert the whole Series to int64. But when converting to Int64, I get an error. Why? What is wrong here?
Edit note:
pp['Value'] = pp['Value'].round().astype('Int64')
solves the problem.. But I would love to understand why. As you can see above, the set is guaranteed to only contain integers; each value is an 'int' down to machine accuracy.. Why on earth would the 'non-safe conversion' error be raised?
As Jason suggested in his comment, your edit solves the problem because rounding changes 8300000.000000001 to 8300000.0.
This is important as it means that after the type conversion the two values are still equal, and so they meet the "safe" casting rule for numpy conversions. When converting to 'Int64' pandas use the numpy.ndarray.astype function which applies this rule. The details on "safe" casting can be found here.
As far as I am aware, there is no way to request that pandas uses the numpy function with a different type of casting, so rounding the values first is the solution to your problem.

Trying to simulate JK-FF with gate level code

I've been trying to simulate a JK-FF with gate level code, but it's not working. Any help is appreciated.
Circuit code:
module circuit1_3_c(j,k,r,cp,q,q1);
input j,k,r,cp;
output q,q1;
wire t1,t2,t3,t4;
nand(t1,t2,q);
nand(t2,t1,j,cp,t3);
nand(t3,cp,k,t4);
nand(t4,t3,t1);
nand(q,q1,t2);
nand(q1,q,t3,r);
endmodule
Testbench code:
module circuit1_3_ctest;
parameter STEP=10;
parameter HALF_STEP=5;
reg j,k,r,cp;
wire q,q1;
circuit1_3_c circ(j,k,r,cp,q,q1);
initial begin
$dumpfile("circuit1_3_c.vcd");
$dumpvars(0,circuit1_3_ctest);
$monitor("\%t: J=%b, K=%b, R=%b, Cp=%b, Q=%b, Qbar=%b", $time, j,k,r,cp,q,q1);
r<=1'b1;
cp<=1'b0;
j<=1'b0; k<=1'b0; r<=1'b1;
#STEP;
j<=1'b0; k<=1'b1; r<=1'b1;
#STEP;
j<=1'b1; k<=1'b0; r<=1'b1;
#STEP;
j<=1'b1; k<=1'b1; r<=1'b1;
#STEP;
j<=1'b0; k<=1'b0; r<=1'b1;
#STEP;
j<=1'b0; k<=1'b1; r<=1'b1;
#STEP;
j<=1'b1; k<=1'b0; r<=1'b1;
#STEP;
j<=1'b1; k<=1'b1; r<=1'b1;
#HALF_STEP
$finish;
end
always #HALF_STEP cp=~cp;
endmodule
Output. As you can see, q and q1 are always unknown (x):
[1
You need to reset your logic properly. One way is to drive r as 0 at time 0.
Change:
r<=1'b1;
cp<=1'b0;
j<=1'b0; k<=1'b0; r<=1'b1;
to:
cp<=1'b0;
j<=1'b0; k<=1'b0; r<=1'b0;
Output:
0: J=0, K=0, R=0, Cp=0, Q=0, Qbar=1
5: J=0, K=0, R=0, Cp=1, Q=0, Qbar=1
10: J=0, K=1, R=1, Cp=0, Q=0, Qbar=1
15: J=0, K=1, R=1, Cp=1, Q=0, Qbar=1
20: J=1, K=0, R=1, Cp=0, Q=0, Qbar=1
25: J=1, K=0, R=1, Cp=1, Q=1, Qbar=0
30: J=1, K=1, R=1, Cp=0, Q=1, Qbar=0
35: J=1, K=1, R=1, Cp=1, Q=0, Qbar=1
40: J=0, K=0, R=1, Cp=0, Q=0, Qbar=1
45: J=0, K=0, R=1, Cp=1, Q=0, Qbar=1
50: J=0, K=1, R=1, Cp=0, Q=0, Qbar=1
55: J=0, K=1, R=1, Cp=1, Q=0, Qbar=1
60: J=1, K=0, R=1, Cp=0, Q=0, Qbar=1
65: J=1, K=0, R=1, Cp=1, Q=1, Qbar=0
70: J=1, K=1, R=1, Cp=0, Q=1, Qbar=0

Sass Map and For Loop Not Compiling

I'm having some issues trying to get my Sass #for loop to work with a map of spacer value variables I have set.
Usually the below code will work, not sure if it's my unrested mind or if there is actually something I have done incorrectly.
Here's my Sass map:
$spacer: 1rem;
$spacers:(
0: 0,
1: ($spacer * .25),
2: ($spacer * .5),
3: $spacer,
4: ($spacer * 1.5),
5: ($spacer * 3),
6: ($spacer * 4)
);
Here's the for loop I'm trying to get to work:
#for $i from 0 through 6 {
.padd-top-#{$i} {
padding-top: map-get($spacers, #{$i});
}
}
I have tried turning the compiler off and on again as sometimes this can cause problems.
You are working with numbers, don't use interpolation syntax: #{ }
$spacer: 1rem;
$spacers:(
0: 0,
1: $spacer * .25,
2: $spacer * .5,
3: $spacer,
4: $spacer * 1.5,
5: $spacer * 3,
6: $spacer * 4
);
#for $i from 0 through 6 {
.padd-top-#{$i} {
padding-top: map-get($spacers, $i);
}
}

How to reduce the palette of PNG image in Python/Pillow to the colors being really used?

After processing a previously optimized indexed color PNG image with transparency (see here for some background, since this question refers to the same image file), using the following code, the PLTE chunk seems to be expanded with more colors than those effectively being used.
Mu current code:
#!/usr/bin/env/python3
import os
from PIL import Image
source_file = os.path.expanduser("~/Desktop/prob.png")
dest_file = os.path.expanduser("~/Desktop/processed_img.png")
img = Image.open(source_file)
# Convert all colors in the palette to grayscale and save the new palette
pal = img.getpalette()
for i in range(len(pal) // 3):
# Using ITU-R 601-2 luma transform
g = (pal[3*i] * 299 + pal[3*i+1] * 587 + pal[3*i+2] * 114) // 1000
pal[3*i: 3*i+3] = [g, g, g]
img.putpalette(pal)
try:
img.save(dest_file, optimize=True, format="PNG")
except IOError:
ImageFile.MAXBLOCK = img.size[0] * img.size[1]
img.save(dest_file, optimize=True, format="PNG")
Using pngcheck I get a small 16 colors palette for the original file:
$ pngcheck -tc7pv ~/Desktop/prob.png
File: /Users/victor/Desktop/prob.png (12562 bytes)
chunk IHDR at offset 0x0000c, length 13
825 x 825 image, 8-bit palette, non-interlaced
chunk PLTE at offset 0x00025, length 48: 16 palette entries
0: ( 0, 0, 0) = (0x00,0x00,0x00)
1: (230,230,230) = (0xe6,0xe6,0xe6)
2: (215,215,215) = (0xd7,0xd7,0xd7)
3: (199,199,199) = (0xc7,0xc7,0xc7)
4: (175,175,175) = (0xaf,0xaf,0xaf)
5: (143,143,143) = (0x8f,0x8f,0x8f)
6: (111,111,111) = (0x6f,0x6f,0x6f)
7: ( 79, 79, 79) = (0x4f,0x4f,0x4f)
8: ( 22, 22, 22) = (0x16,0x16,0x16)
9: ( 0, 0, 0) = (0x00,0x00,0x00)
10: ( 47, 47, 47) = (0x2f,0x2f,0x2f)
11: (254,254,254) = (0xfe,0xfe,0xfe)
12: (115, 89, 0) = (0x73,0x59,0x00)
13: (225,176, 0) = (0xe1,0xb0,0x00)
14: (255,211, 0) = (0xff,0xd3,0x00)
15: (254,204, 0) = (0xfe,0xcc,0x00)
chunk tRNS at offset 0x00061, length 1: 1 transparency entry
0: 0 = 0x00
chunk IDAT at offset 0x0006e, length 12432
zlib: deflated, 32K window, maximum compression
chunk IEND at offset 0x0310a, length 0
No errors detected in /Users/victor/Desktop/prob.png (5 chunks, 98.2% compression).
Then, after processing the image using the code sample above, pngcheck displays a much bigger PLTE chunk, filled with lots of (probably unused) color values:
$ pngcheck -tc7pv ~/Desktop/processed_img.png
File: /Users/victor/Desktop/processed_img.png (14680 bytes)
chunk IHDR at offset 0x0000c, length 13
825 x 825 image, 8-bit palette, non-interlaced
chunk PLTE at offset 0x00025, length 768: 256 palette entries
0: ( 0, 0, 0) = (0x00,0x00,0x00)
1: (230,230,230) = (0xe6,0xe6,0xe6)
2: (215,215,215) = (0xd7,0xd7,0xd7)
3: (199,199,199) = (0xc7,0xc7,0xc7)
4: (175,175,175) = (0xaf,0xaf,0xaf)
5: (143,143,143) = (0x8f,0x8f,0x8f)
6: (111,111,111) = (0x6f,0x6f,0x6f)
7: ( 79, 79, 79) = (0x4f,0x4f,0x4f)
8: ( 22, 22, 22) = (0x16,0x16,0x16)
9: ( 0, 0, 0) = (0x00,0x00,0x00)
10: ( 47, 47, 47) = (0x2f,0x2f,0x2f)
11: (254,254,254) = (0xfe,0xfe,0xfe)
12: ( 86, 86, 86) = (0x56,0x56,0x56)
13: (170,170,170) = (0xaa,0xaa,0xaa)
14: (200,200,200) = (0xc8,0xc8,0xc8)
15: (195,195,195) = (0xc3,0xc3,0xc3)
16: ( 16, 16, 16) = (0x10,0x10,0x10)
17: ( 17, 17, 17) = (0x11,0x11,0x11)
18: ( 18, 18, 18) = (0x12,0x12,0x12)
19: ( 19, 19, 19) = (0x13,0x13,0x13)
20: ( 20, 20, 20) = (0x14,0x14,0x14)
(...) --- and it goes on listing all values up to 255:
254: (254,254,254) = (0xfe,0xfe,0xfe)
255: (255,255,255) = (0xff,0xff,0xff)
chunk tRNS at offset 0x00331, length 1: 1 transparency entry
0: 0 = 0x00
chunk IDAT at offset 0x0033e, length 13830
zlib: deflated, 32K window, maximum compression
chunk IEND at offset 0x03950, length 0
No errors detected in /Users/victor/Desktop/processed_img.png (5 chunks, 97.8% compression).
Is this behavior normal on Pillow? Is there any way to save a shorter PLTE chunk, similar to the original file (I am trying to optimize for smaller file sizes)?
If Pillow can't do it, is there any other simple way to do it? Preferably using pure Python, but it could also be with numpy or some additional pure Python package, like PyPNG or PurePNG, if that helps.
I ran your code on the bee png with Pillow 9.2.0 and the output png had the same number colors in its palette as the original. Additionally, Image.convert() has the args palette and colors that can be used to control the output colors. For example, to keep the same number of colors as the input image:
from PIL import Image
img = Image.open('bee.png')
num_pixels = img.size[0]*img.size[1]
num_colors = len(img.getcolors(num_pixels))
# Max palette size = 256
if num_colors > 256:
num_colors = 256
img = img.convert(mode='P', palette=1, colors=num_colors)
img.save('bee2.png', optimize=True, format="PNG")
Alternatively, if you're interested in reducing/managing image size I'd recommend compressing the image using the quality arg in Image.save() or utilizing another tool such as ExifTool to eliminate unnecessary metadata (These will likely have a limited effect on the bee png).
For more info on image modes in Pillow see Mark Setchell's answer: What is the difference between images in 'P' and 'L' mode in PIL?
Answers discussing file size reduction with Pillow: How to reduce the image file size using PIL

Resources