#! /usr/bin/python3 # Last edited on 2025-01-28 22:21:13 by stolfi from math import sin, cos, log, exp, pi, sqrt, floor import os import subprocess import sys err = sys.stderr def main(): show = False # Display images and wait user OK? mfoc = False # Generate multiple frames with different focus height? phot = True # Generate multiple images with different light sources? test = True # Generate a single frame and light, "L00/F00"? for obj in range(2): make_all(obj,mfoc,phot,show,test) # Test image. err.write("done\n") return 0 # ---------------------------------------------------------------------- def make_all(obj, mfoc, phot, show, test): # {obj} object: 0 for ball, 1 for pyramid. # {mfoc} true to make blurry images for multifocus stereo, with varied {zFoc}. # {phot} true to make images for photometric stereo, with varied light dirs. # {show} should display images and wait for user OK? # {test} true to make a single image to check the geometry etc. # # The main folder will be "out/povball-tx{TX}" or "out/povpyra-tx{TX}" # where {TX} says if the object is textured, "T" or "F". # # A mask shoing the object's outline be written to subfolder "MASK" # of the main folder. # # If {phot} is true there will be multiple light versions, in # subfolders "L00", "L01", etc of the main folder. In any case there # will be a subfolder "ALB" with the albedo map. # # If {mfoc} is true, images (other than mask) will be textured and the # blurred frames will be in sub-sub-folders "F00", "F01", etc of each # light sub-folder, including the "ALB" folder. In any case there will # be a sharp frame in frame sub-sub-folder "SHARP". light_dirs = ( ( +0.567, -0.554, +0.608 ), ( -0.191, -0.794, +0.576 ), ( -0.708, -0.226, +0.668 ), ( -0.563, +0.499, +0.658 ), ( +0.229, +0.689, +0.686 ), ( +0.733, +0.214, +0.645 ), # ( +0.472, -0.845, +0.251 ), ( -0.854, -0.484, +0.190 ), ( -0.982, +0.105, +0.155 ), ( -0.470, +0.836, +0.282 ), ( +0.890, +0.418, +0.185 ), ( +0.980, -0.128, +0.151 ), ) textured = mfoc # Surface is textured? brt = 1.00; # The 'brightness' parameter for pov-ray diffuse finish. zScene_min = 0.0; zScene_max = 2.0; txTag = "T" if textured else "F" objName = ('povball', 'povpyra')[obj] objDir = f"out/{objName}-tx{txTag}" subprocess.run(["rm", "-rfv", f"{objDir}"]) # Generate {nlit} light sources "L{NN}" plus the ambient version "AMB": nlit = len(light_dirs) if phot and not test else 1 # Generate {nfoc} blurred versions "F{}", plus the sharp version "SHARP": nfoc = int(floor((zFoc_max - zFoc_min)/zFoc_step + 0.5)) + 1 if mfoc and not test else 1 # Use this value to match the points of the Z-scale in the scene: zFoc_step = 0.20 # Define {Z} range of in-focus plane to fully cover the scene {Z}-range: zFoc_min = zScene_min - zFoc_step zFoc_max = zScene_max + zFoc_step # Define {Z} range of in-focus plane: err.write("!! nfoc = %d zFoc_step = %8.6f\n" % ( nfoc, zFoc_step ) ) for klit in range(nlit + 1): if klit == nlit: liv = (1,1,1) amb = 1.0 lightName = "ALB" # Albedo map - 100% ambient. else: liv = light_dirs[klit] amb = 0.0 lightName = f"L{klit:02d}" lightDir = f"{objDir}/{lightName}" images = [] # Generate the blurry images for this light. for kfoc in range(nfoc+1): if kfoc == nfoc: zFoc = 0 ape = 0 frameName = "SHARP" elif nfoc == 1: zFoc = (zFoc_min + zFoc_max)/2 ape = 0.25 frameName = f"F{kfoc:02d}" else: zFoc = zFoc_min + kfoc/(nfoc-1)*(zFoc_max - zFoc_min) ape = 0.25*nfoc/13 frameName = f"F{kfoc:02d}" frameDir = f"{lightDir}/{frameName}" make_frame(obj,frameDir, textured,liv,amb,brt, zFoc,ape, mask=False, show=False); images.append(f"{frameDir}/image.png") if show: subprocess.run( [ "display -title '%d' -filter Box -resize '400%' " + " ".join(images) ], shell=True, ) if obj == 0: compare_with_ref(objDir,brt) maskDir = f"{objDir}/MASK" make_frame(obj,maskDir, textured, liv=(1,1,1), amb=1.0, brt=1.0, zFoc=0, ape=0, mask=True, show=show); return # ---------------------------------------------------------------------- def make_frame(obj,frameDir, textured,liv,amb,brt, zFoc,ape,mask,show): # Makes one image in "{frameDir}/image.png" write_parms(obj,textured,liv,amb,brt,zFoc,ape,mask) subprocess.run(["mkdir", "-pv", f"{frameDir}"]) imageName = "image" imageFile = f"{frameDir}/{imageName}.png" err.write(f"creating '{imageFile}' ...\n") subprocess.run ( ["make", "small-square"] ) subprocess.run(["mv", "-vi", "main.png", f"{imageFile}"]) if show: subprocess.run( [ f"display -title '%d' -filter Box -resize '400%' {imageFile}" ], shell=True, ) def compare_with_ref(objDir, brt): # Compares the image "{objDir}/L00/F00/image.png" to "ref/image00.png", # adjusting for different gauge postions and dimensions and max brightness values. # Also extracts the brightness scales and plots their histograms. newImage_PNG = f"{objDir}/L00/F00/image.png" refImage_PNG = "ref/image00.png" err.write(f"=== comparing {newImage_PNG} {refImage_PNG} ===\n") err.write("converting images to PGM ...\n"); newImage_PGM = f"cmp/new-{brt:5.3f}.pgm" refImage_PGM = f"cmp/ref-{brt:5.3f}.pgm" subprocess.run( [ f"convert {newImage_PNG} -gamma 0.45455 -colorspace Gray PGM:- > {newImage_PGM}" ], shell = True) subprocess.run( [ f"convert {refImage_PNG} -colorspace Gray PGM:- > {refImage_PGM}" ], shell = True) subprocess.run( [ "display -title '%f' -filter box " + newImage_PGM + " " + refImage_PGM ], shell = True ) err.write("extracting the gauge images proper and normalizing brightness ...\n"); newGauge_PGM = f"cmp/new-{brt:5.3f}-gauge.pgm" refGauge_PGM = f"cmp/ref-{brt:5.3f}-gauge.pgm" subprocess.run( [ "pamcut -left 66 -top 66 -width 124 -height 124 " + newImage_PGM + " | pnmnorm -bvalue=0 -wvalue=203 > " + newGauge_PGM ], shell = True) subprocess.run( [ "pamcut -left 66 -top 66 -width 124 -height 124 " + refImage_PGM + " | pnmnorm -bvalue=0 -wvalue=204 > " + refGauge_PGM ], shell = True) subprocess.run( [ "display -title '%f' -filter box -resize '400%' " + newGauge_PGM + " " + refGauge_PGM ], shell = True ) err.write("extracting the brightness scale images ...\n"); newScale_PGM = f"cmp/new-{brt:5.3f}-scale.pgm" refScale_PGM = f"cmp/ref-{brt:5.3f}-scale.pgm" subprocess.run( [ "pamcut -left 44 -top 218 -width 166 -height 26 < " + newImage_PGM + " > " + newScale_PGM ], shell = True) subprocess.run( [ "pamcut -left 24 -top 192 -width 208 -height 44 < " + refImage_PGM + " > " + refScale_PGM ], shell = True) subprocess.run( [ "display -title '%f' -filter box -resize '400%' " + newScale_PGM + " " + refScale_PGM ], shell = True ) err.write("creating histograms of the scale images ...\n"); newScale_HIS = f"cmp/new-{brt:5.3f}-scale.his" refScale_HIS = f"cmp/ref-{brt:5.3f}-scale.his" subprocess.run( [ "pgmhist -machine " + newScale_PGM + " > " + newScale_HIS ], shell = True) subprocess.run( [ "pgmhist -machine " + refScale_PGM + " > " + refScale_HIS ], shell = True) err.write("plotting histograms of the scale images ...\n"); subprocess.run( [ "plot_hists.sh " + newScale_HIS + " " + refScale_HIS ], shell = True) err.write("computing the difference between gauge images ...\n"); difGauge_PGM = f"cmp/dif-{brt:5.3f}.pgm" subprocess.run( [ "pnmxarith -mix 1.000 -1.000 -scale 10.0 -offset 0.5 " + newGauge_PGM + " " + refGauge_PGM + " > " + difGauge_PGM ], shell = True ) subprocess.run( [ "display -title '%f' -filter box -resize '400%' " + difGauge_PGM + " " + "dif*.pgm" + " " + newGauge_PGM + " " + refGauge_PGM ], shell = True ) err.write("=== done comparing ===\n"); def write_parms(obj,textured,liv,amb,brt,zFoc,ape,mask): # The argument {obj} must be 0 for ball, 1 for pyramid. # The boolean {textured} says whether object is smooth or textured. # The argument {liv} must be the light direction. # No need to normalize. # The parameters {amb,brt} are the 'ambient' and 'brightness' of the obj's finish. # The parameter {zFoc} is the distance from the # scene center to the in-focus plane. {ape} is a # relative camera aperture that defines the amount of blur. err.write("!! zFoc = %+8.5f ape = %8.5f liv = ( %+6.3f, %+6.3f, %+6.3f )\n" % ((zFoc, ape,) + liv)); wr = open("parms.inc", "w"); wr.write("// generated by make_images.py\n"); wr.write("\n") wr.write("#declare obj = %d;\n" % obj); wr.write("\n") wr.write("#declare obj_textured = %d;\n" % int(textured)); wr.write("#declare obj_mask = %d;\n" % int(mask)); wr.write("\n") wr.write("#declare obj_clr_dark = < 0.200, 0.300, 0.400 >;\n") wr.write("#declare obj_clr_lite = < 0.400, 0.700, 0.200 >;\n") wr.write("#declare obj_amb = %6.4f;\n" % amb) wr.write("#declare obj_brt = %.4f;\n" % brt) wr.write("#declare obj_spc = 0.000;\n") wr.write("\n") wr.write("#declare obj_rad = 1.000;\n") wr.write("#declare obj_ctr = < 0, 0, obj_rad >;\n") wr.write("\n") wr.write("#declare ground_clr_dark = < 0.200, 0.200, 0.200 >;\n") wr.write("#declare ground_clr_lite = < 0.900, 0.500, 0.400 >;\n") wr.write("\n") wr.write("#declare light_dir = < %+6.3f, %+6.3f, %+6.3f >;\n" % liv) wr.write("#declare light_dist = 1000.0;\n") wr.write("#declare light_brt = 0.80;\n") wr.write("\n") wr.write("#declare cam_foc = %+7.4f;\n" % zFoc) wr.write("#declare cam_ape = %6.4f;\n" % ape) wr.close() return main()