{"id":17222743,"url":"https://github.com/kfjahnke/envutil","last_synced_at":"2025-10-04T06:35:43.279Z","repository":{"id":234660941,"uuid":"789326581","full_name":"kfjahnke/envutil","owner":"kfjahnke","description":"utility to convert between lat/lon and cubemap environment maps, and to extract single images and image series in several projections and arbitrary orientation","archived":false,"fork":false,"pushed_at":"2025-05-25T10:46:44.000Z","size":2800,"stargazers_count":0,"open_issues_count":6,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-25T11:34:27.152Z","etag":null,"topics":["cubemap","environment-lookup","image-processing","image-pyramid","latlon-environment","reprojection","scalable-filter","simd","spherical-panorama","twining"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kfjahnke.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-04-20T08:44:04.000Z","updated_at":"2025-05-25T10:46:48.000Z","dependencies_parsed_at":"2025-05-14T15:47:51.270Z","dependency_job_id":null,"html_url":"https://github.com/kfjahnke/envutil","commit_stats":null,"previous_names":["kfjahnke/envutil"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/kfjahnke/envutil","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfjahnke%2Fenvutil","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfjahnke%2Fenvutil/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfjahnke%2Fenvutil/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfjahnke%2Fenvutil/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kfjahnke","download_url":"https://codeload.github.com/kfjahnke/envutil/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfjahnke%2Fenvutil/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278277839,"owners_count":25960428,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-04T02:00:05.491Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cubemap","environment-lookup","image-processing","image-pyramid","latlon-environment","reprojection","scalable-filter","simd","spherical-panorama","twining"],"created_at":"2024-10-15T04:06:14.828Z","updated_at":"2025-10-04T06:35:43.270Z","avatar_url":"https://github.com/kfjahnke.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# envutil - a utility program to process oriented images and environments\n\nThis is a stand-alone repository for envutil, which started out as a demo\nprogram for my library [zimt](https://github.com/kfjahnke/zimt).\nThe program has grown beyond the limits of what I think is sensible for a\ndemo program, and I also think it's a useful tool.\n\nThis program takes a 2:1 lat/lon environment, a 1:6 cubemap image or a set\nof several 'facet' images as input and produces output in the specified\norientation, projection, field of view and extent. For CL arguments, try\n'envutil --help'. Simple synoptic renditions of several facet images can\nbe achieved using the '--facet' command line arguments. For more complex\nscenarios, envutil uses a subset of PanoTools 'PTO' format. envutil's\nimplementation of PanoTools features is growing, currently the focus is\non getting the geometry right, and all PanoTools geometric transformations\nalong with some source/target projections are implemented. Colour and\nbrightness manipulations are currently being added, it's recommended to\nwork with *scene-referred linear RGB image data*, preferably from image\nfiles using such formats by default, like openEXR. envutil uses the Acacdemy\nSoftware Foundation's library 'OpenImageIO' (OIIO in short) to access image\ndata, which provides access to a large number of image formats, both for\ninput and output. This widens the spectrum of material for processing\nwith PanoTools methods, one notable option is to read directly from RAW\ncamera image formats like CR2, using OpenImageIO's libraw plugin.\nOIIO does in turn use OpenColorIO (OCIO) in short, which is another\nproject under the umbrella of the Academy Software Foundation. This\nproject provides sophisticated colour management, which envutil can\nleverage via OIIO's OCIO interface.\nenvutil has limited stitching capabilities, roughly what hugin's helper\nprogram 'nona' would offer, but not using PanoTools-specific EMoR\nprocessing, PT interpolators or specific formats like multilayer TIFF,\nand offering only a limited set of projections.\nThe images are put together in a way which resembles a spherical voronoi\ndiagram, there is no seam optimization or feathering. envutil can also\nhdr-merge exposure brackets, and since it can process RAW camera images,\nthis is an interesting new route to HDR images directly from the RAW\ndata without manifesting intermediate images.\n\nPanorama photographers may not be familiar with the term 'lat/lon\nenvironment' - to them, this is a 'full spherical panorama' or\n'full equirect'. The difference is merely terminology, both are the same.\nCubemaps are rarely used in panorama photography, but I aim at reviving\nthem by building a code base to process them efficiently, honing the\nstandard, and integrating processing of single-image cubemap format\n(as it is used by envutil) into [lux](https://bitbucket.org/kfj/pv), my\nimage and panorama viewer. I have added a type of cubemap which uses\nan additional in-plane retransformation on the six cube face images\nwhich should offer better resolution for a given pixel count, see notes\nabout the 'biatan6' format - I think this is an interesting new development\nand offers a full 360X180 degree environment format with good storage\nefficiency while processing is still quite fast. I'd welcome inspection\nof the format's properties by others from the image processing community!\n\nThe single-image cubemap format I process in envutil leans on the\nopenEXR standard as far as the order and orientation of the cube\nface images is concerned: the images are expected stacked vertically (in\nthis sequence: left, right, top, bottom, front, back; top and bottom align\nwith the 'back' image), but the precise geometry of the images may differ\nfrom openEXR's standard - I measure cube face image field of view\n'edge-to-edge', meaning that I consider pixels small square areas and\nX degrees field of view extend over just as many of these small areas as\nthe image is wide and high. A different notion is 'center-to-center'\nmeasuring, where the X degrees refer to the area enclosed between the\nmarginal pixels seen as points in space (the convex hull).\nenvutil can also process cubemaps with cube face\nimages with larger field of view than ninety degrees (pass --fov). \nCubemaps can also be passed as six single cube face images. The images\nhave to follow a naming scheme which the caller passes via a format\nstring. You pass the format string as input, and it's used to generate six\nfilenames, where the format string is expanded with \"left\", \"right\"...\nfor the six cube faces. All six image file names generated in this fashion\nmust resolve to square images of equal size and field of view.\n\nThe output projection can be one of \"spherical\", \"cylindrical\",\n\"rectilinear\", \"stereographic\", \"fisheye\", \"cubemap\". or 'biatan6'.\nThe geometrical extent of the output is set up most conveniently by\npassing --hfov, the horizontal field of view of the output.\nThe x0, x1, y0, and y1 parameters allow passing specific extent values\n(in model space units), which should rarely be necessary. To specify the\norientation of the 'virtual camera', pass Euler angles yaw, pitch and\nroll - they default to zero: a view 'straight ahead' to the point corresponding to the center of the environment image with no camera\nroll. The size of the output is given by --width and --height. You\nmust pass an output filename with --output; --input specifies the\nenvironment image. Note that you can use 'envutil' to reproject\nenvironment images, e.g convert a cubemap to a lat/lon image and the\nreverse. To get a full 360 degree lat/lon image from a cubemap, you\nmust pass --hfov 360 and --projection spherical, and for the reverse\noperation, pass --hfov 90 and --projection cubemap. Export of a cubemap\nas six separate images can be achieved by passing a format string -\nthe same mechanism as for input is used.\n\nenvutil can 'mount' images in the supported six projections as\ninput (use --facet ...) - currently, such images have to be cropped\nsymmetrically, meaning that the optical axis is taken to pass through\nthe image center. For facet input, you must specify the horizontal\nfield of view and the projection together with the image filename,\nand additionally three Euler angles (yaw, pitch, roll) defining the\norientation of the facet. If the view 'looks at' areas not covered\nby the facet image, it's painted black or transparent black for\nimages with alpha channel. If several facets might provide content\nfor a given viewing ray, the facet whose central ray is next to the\ngiven viewing ray 'wins', producing a result which is a spherical\nvoronoi diagram. If images with different resolution are combined,\nimages with higher resolution are given precedence. Where facets have\ntransparency, content which would be obscured by an opaque facet will\nshine through even if it does not qualify as 'winner'. More complex\nfacet image specifications can be achieved by using a PTO script -\nthere, you can specify additional features like lens distortion\ncorrection, translation, lens shift and shear - typically you'll have\nthe PTO file generated by software like hugin, and the PTO dialect\nenvutil understands is the same which hugin uses, so other PT-based\nsoftware may not be compatible. Envutil uses a few extensions to PTO\nformat, notably several 'clauses' occuring in i-lines and p-lines.\nThis may also break compatibility - PTO files with these extensions\nmay not parse in other PTO-processing applications.\n\nYou can choose two different interpolation methods. The default\nis to use 'twining' - oversampling with subsequent weighted pixel\nbinning. The default with this method is to use a simple box filter\non a signal which is interpolated with b-spline interpolation; the\nnumber of taps and the footprint of the pick-up are set automatically.\nAdditional parameters can change the footprint and the amount of\noversampling and add gaussian weights to the filter parameters.\nTwining is quite fast (if the number of filter taps isn't\nvery large); when down-scaling, the parameter 'twine' should be at\nleast the same as the scaling factor to avoid aliasing. When upscaling,\nlarger twining values will slighly soften the output and suppress the\nstar-shaped artifacts typical for bilinear interpolation. Twining is\nnew and this is a first approach. The method is intrinsically very\nflexible (it's based on a generalization of convolution). You can\nuse arbitrarily shaped twining filters via a simple text-based\nspecification if you want to implement filters beyond the ordinary.\nYou can switch twining off by passing --twine 0. With twining\noff, the 'ground-truth' interpolator is used directly. The b-spline\nused as 'ground truth' can be parameterized with the --degree and\n--prefilter arguments, see there. Note that low-degree b-splines are\nbetter known by their generic names: a zero-degree b-spline is known\nas 'nearest-neighbour interpolation', and a degree-one b-spline is\nknown as 'bilinear interpolation'. A very commonly used type of b-spline\nis the degree-three spline, which is commonly known as a 'cubic b-spline'.\nNote that prefiltering is essential for splines with degrees of two and\nmore to produce a spline satisfying the interpolation criterion (the\nspline passes through the knot points). Omitting the prefiltering will\nproduce slightly blurred output.\nFind out mor eabout parameterization of b-splines [here](#interpolation-options) and about twining [here](#twining-specific-options)!\n\nThe program uses [zimt](https://github.com/kfjahnke/zimt) as it's 'strip-mining' and SIMD back-end, and\nsets up the pixel pipelines using zimt's functional composition tools.\nThis allows for terse programming, and the use of the functional\nparadigm allows for many features to be freely combined - a property\nwhich is sometimes called 'orthogonality'. Initially I employed\nOIIO's 'texture' and 'environment' functions as an alternative to\nenvutil's own interpolation methods, but I ended up with convoluted\ncode which did not produce better results, so I am currently using\nOIIO only for image input and output.\n\nCurrently, single-ISA builds are set up to produce binary for specific\nCPUs - pass the ISA-specific flags to cmake with \"ISA_SPECIFIC_ARGS\".\nmulti-ISA builds (the default) will produce binary for *all* ISAs which\nmay occur in the given CPU family and dispatch to the variant which\nis best suited to the CPU detected at run-time. multi-ISA builds\nrquire highway.\n\nI strongly suggest you install highway on your system - the build\nwill detect and use it to good effect. This is a build-time dependency\nonly. Next-best (when using i86 CPUs up to AVX2) is Vc, the fall-back\nis to use std::simd, and even that can be turned off if you want to\nrely on autovectorization; zimt structures the processing so that it's\nautovectorization-friendly and performance is still quite good that way.\nUsing optimization is essential.\n\nWith version 0.1.2 I have changed the code to cooperate with highway's\nforeach_target mechanism, which is now the default (but requires highway).\nThe resulting binary will contain specialized machine code for each ISA\nwhich is common on a given CPU line (e.g. SSE*, SSSE3, AVX2 and AVX3 on\nx86 CPUs) and dispatch internally to the best ISA which the current CPU\ncan process. This gives the resulting binary a much wider 'feeding\nspectrum' - it should run on any x86 CPU with near-optimal ISA - for\nx86 highway produces machine code up to AVX512 - and never produce\nillegal instruction errors, because the CPU detection makes sure no\n'too good' ISA instructions will be executed. If you prefer single-ISA\nbuilds - e.g. if you're producing a binary only for a specific machine\nor if you're modifying the code (recompilation is much quicker with\nonly a single ISA) - pass -DMULTI_SIMD_ISA=OFF to cmake. multi-ISA\nbuilds require highway, but they can also be used with zimt's otherg'\nback-ends. Pass -DUSE_GOADING=ON to make the build ignore explicit SIMD\nlibraries (highway, Vc or std::simd) which would otherwise be used in\nthis order of preference.\n\nThe only mandatory dependency is [OpenImageIO](https://github.com/AcademySoftwareFoundation/OpenImageIO) - OIIO in short. Using highway\nfor SIMD is highly recommended, and some other needed libraries may not\nbe readily available, even though they should come with OpenImageIO.\nNote, though, that OIIO is a very large dependency, pulling in many\nmore libraries to implement it's huge body of functionality.\n\nIt's recommended to build with clang++\n\nTo build, try this:\n\n    mkdir build\n    cd build\n    cmake [options] ..\n    make\n\nrecommended options for the build:\n\n    -DCMAKE_CXX_COMPILER=clang++\n    -DCMAKE_C_COMPILER=clang\n    -DCPACK_GENERATOR=DEB\n\nthe last option is only useful if you want to build debian packages with\n'make package'. Compilation with g++/gcc may or may not work - I don't check\nregularly.\n\nBuilding with highway is highly recommended. I tried the highway coming with\nthe package manager on debian12, but that did not work - building highway\nfrom source is easy, though. Another hickough I experienced on debian12 was\nthe default clang++, which is only v14. That did not work either - I installed\nclang-19, and that did the trick. Imath may also not be available, and\nOpenImageIO did not work without also installing it's CL tools. With all\ndependencies met, the build went without errors or warnings. I could also\nbuild a debian package (I did install the debhelper package), and at times\nI offer debian packages for download form the Downloads section of the lux\nproject.\n\nWith version 0.1.1, on top of building on debian12,  I have also managed to\nbuild envutil on on an intel mac running macOS 12.7.5 (using macPorts for the\ndependencies) and on windows 10/11 using mingw64.\n    \n'make' should produce a binary named 'envutil' or 'envutil.exe''.\n\nenvutil --help gives a summary of command line options:\n\n    --help                      Print help message\n      -v                          Verbose output\n    mandatory options:\n      --output OUTPUT             output file name (mandatory)\n    important options which have defaults:\n      --projection PRJ            projection used for the output image(s) (default: rectilinear)\n      --hfov ANGLE                horiziontal field of view of the output (default: 90)\n      --width EXTENT              width of the output (default: 1024)\n      --height EXTENT             height of the output (default: same as width)\n      --support_min EXTENT        minimal additional support around the cube face proper\n      --tile_size EXTENT          tile size for the internal representation image\n      --synopsis MODE             mode of composing several images (panorama or hdr_merge)\n      --working_colour_space CSP  colour space used for internal processing (default scene_linear)\n    additional parameters for single-image output:\n      --output_colour_space CSP   colour space used for output (default scene_linear)\n      --single FACET              render an image like facet FACET\n      --split FORMAT_STRING       create a 'single' image for all facets in a PTO\n      --yaw ANGLE                 yaw of the virtual camera\n      --pitch ANGLE               pitch of the virtual camera\n      --roll ANGLE                roll of the virtual camera\n      --x0 EXTENT                 low end of the horizontal range\n      --x1 EXTENT                 high end of the horizontal range\n      --y0 EXTENT                 low end of the vertical range\n      --y1 EXTENT                 high end of the vertical range\n    interpolation options:\n      --prefilter DEG             prefilter degree (\u003e= 0) for b-spline-based interpolations\n      --degree DEG                degree of the spline (\u003e= 0) for b-spline-based interpolations\n    parameters for twining (--twine 0 switches twining off)\n      --twine TWINE               use twine*twine oversampling - omit this arg for automatic twining\n      --twf_file TWF_FILE         read twining filter kernel from TWF_FILE (switches twining on)\n      --twine_normalize           normalize twining filter weights gleaned from a file\n      --twine_precise             project twining basis vectors to tangent plane\n      --twine_width WIDTH         widen the pick-up area of the twining filter\n      --twine_density DENSITY     increase tap count of an 'automatic' twining filter\n      --twine_sigma SIGMA         use a truncated gaussian for the twining filter (default: don't)\n      --twine_threshold THR       discard twining filter taps below this threshold\n    parameters for mounted (facet) image input:\n      --photo IMAGE               load photographic image, interpreting metadata\n      --facet IMAGE PROJECTION HFOV YAW PITCH ROLL\n                                  load oriented non-environment source image\n      --oiio OPTION               pass option to configure OIIO plugin (may be used repeatedly)\n      --input_colour_space CSP    default colour space for input images (default: none)\n      --pto PTOFILE               panotools script in hugin PTO dialect (optional)\n      --pto_line LINE             add (trailing) line of PTO code\n      --solo FACET_INDEX          show only this facet (indexes starting from zero)\n      --mask_for FACET_INDEX      paint this facet white, all others black\n      --nchannels CHANNELS        produce output with CHANNELS channels (1-4)\n\nThere is an option to switch envutil into 'streaming mode'. This is done\nby suffixing the command line with a single '-' (minus) sign. The result\nis that envutil will read more command line parameters from standard input\nuntil it encounters a line feed. The combined set of parameters is then used\nto produce output. Next, envutil tries to read another set of parameters\nfrom standard input, which replace the set read from standard input\npreviously. Then, another rendering job is launched. This continues until\nthere is an EOF on standard input. Using this facility is helpful in reducing\nprocessing and I/O, because source images which are read once will persist\nin memory for another cycle, allowing successive rendering jobs to 'pick up'\nimages from the previous cycle.\n\nenvutil can now leverage some of OCIO's colour space management capabilities\nvia OIIO's interface to OCIO. I've kept it simple and only invoke OIIO's\n'colorconvert' function (which is in [this](https://openimageio.readthedocs.io/en/v2.5.8.0/imagebufalgo.html#color-space-conversion) section) twice:\nOnce on reading image files, and once on writing them. When reading, the\ndefault is to convert incoming image data to OIIO's notion of a scene-referred\nlinear colour space - I think this is ACEScg. On writing, the scene-linear\ndata are, by default, written as they are unless output is to JPEG, which\nwill result in mandatory conversion to sRGB. There are three parameters which\nyou can use to alter processing: --input_colour_space, --working_colour_space\nand --output_colour_space. Note the british english spelling (colour, not\ncolor). Without an OCIO config file, only simple values like 'scene_linear'\nand 'sRGB' are accepted, but with an OCIO config available, all colour space\nnames provided by the config can be used as arguments and should be passed\nthrough to OCIO. As far as I know, newer versions of OIIO/OCIO also contain\na set of hard-coded colour space names which may be recognized without\nspecifying an OCIO config file via the OCIO environment variable. Your mileage\nwith these three parameters may vary, but with the defaults you should be able\nto get decent results with most image files you encounter, because sRGB and\nscene linear are very common. When processing RAW images, you can 'tell' the\nlibraw plugin to emit ACES data, which OIIO recognizes. This is probably better\nthan having libraw produce sRGB, which is the default behaviour. You can\npass configuration parameters to OIIO using the --oiio command line argument,\nwhich can be passed repeatedly, so to tell the libraw plugin to emit ACES,\nyou'd pass '--oiio raw:ColorSpace=ACES'.\nOIIO will 'know' the colour space of input images if it can figure it out,\nso you only need to specify 'input_colour_space' if OIIO fails to detect the\nnature of the incoming material - this is not always evident.\n'working_colour_space' is probably best left alone, but since this option can\nbe used to change the colour space used internally for image processing, it may\nbe interesting to experiment with it. Do pick a linear colour space, though,\notherwise processing is not mathematically correct even if it may look 'okay'.\n'output_colour_space' is the colour space which internal scene_linear data are\nconverted to before writing them to the output image file(s). Best is to write\nto a format like openEXR which can (and usually does) store linear data, but\nother formats will work just as well, with the usual constraints (like, losing\ncontent outside the format's dynamic range). Since envutil's default is to\nemit scene-linear data, you may need the output_colour_space argument for\ntarget formats other than JPEG which is sRGB only.\n\n# envutil Command Line Options\n\nThe options are given with a headline made from the argument parser's help\ntext. The capitalized word following the parameter is a placeholder for the\nactual value you pass.\n\n# colour space options\n\nWhich values you can pass to these options depends on the version of\nOIIO/OCIO which envutil is built with, and the use (or lack thereof)\nof an OCIO config file. Without an active OCIO config file, the number\nof possible values is limited to what's 'hard-coded' into OCIO, which\nin turn depends on the OCIO version. This is a bit of a moving target.\nWith an active OCIO config, any number of named colour spaces can be\nadded by the site, so it's up to the local setup what can be used and\nwhat can't - OIIO simply passes the colour space names through to it's\nOCIO interface.\n\n## --input_colour_space CSP\n##     default colour space for input images (default: unset)\n\nIf you pass a value here, it will tell OIIO to consider input as being\nin this colour space. This may or may not be correct for the incoming\ndata - this parameter takes precedence, whereas the default tells OIIO\nto glean the value if it can.\n\n## --working_colour_space CSP\n##     colour space used for internal processing (default scene_linear)\n\nAs said above, this is probably best left alone. If you need to use\nsomething other than the default, use a linear colour space like ACEScg.\n\n## --output_colour_space CSP \n##     colour space used for output (default scene_linear)\n\nThis prescribes the colour space used for output, unless output is to\nJPEG, where sRGB is enforced. The default, scene_linear, is best used with\nformats which are commonly used for the task and can handle HDR data, like\nopenEXR.\n\n#  parameters for mounted (facet) image input:\n\nenvutil uses the 'facet' option or a PTO file to introduce one or more\nsource images. Both options can occur together, and you can pass several\n--facet options, but there can currently be at most one PTO file. If\nboth --facet and --pto are used, the numbering starts with the facets from\nthe PTO file, even if some --facet options precede the --pto option.\n\n## --facet IMAGE PROJECTION HFOV YAW PITCH ROLL\n##     load oriented non-environment source image\n\nenvutil will 'mount' images in various projections and hfov which may only\ncover a part of the full 360X180 degree environment. All projections are\nsupported. hfov is in degrees - for cubemaps and their 'biatan6' variant\npass 90 (or whatever hfov your cube face images have) even though the whole\ncubemap does of course cover 360X180 degrees fov. The three values must\nbe followed by the facet's orientation, given as three 'Euler angles' (yaw,\npitch, roll). If you want the facet to be mounted 'straight ahead', just pass\n0 0 0. All six values (image filename, projection, hfov, yaw, pitch, roll)\nmust be passed after --facet, separated by space.\n\nYou may pass more than one facet. How several images are put together is\nset with the --synopsis argument, see there. Here, I describe the 'panorama'\nsetting:\n\nWhere several facets provide visible content for a given viewing ray,\nenvutil gives preference to one of them, following this scheme:\nFor every candidate, the normalized viewing ray's z (forward) component\n*in the facet's coordinate system* is isolated. To this, the reciprocal\n'step' value is added - see just below for an explanation. The facet\nwhere the sum comes out largest 'wins' the contest - it's content is\nassigned to the viewing ray.\n\nThe 'step' value is a measure of the change in a viewing ray's angle\n(measured in radians) when moving one pixel to the right in the image\ncenter. Images with high resolution per degree of hfov have small step\nvalues, and vice versa - so the reciprocal step value is higher for\nimages with higer resolution, and adding this datum to the z value\nwill give preference to images with higher resolution. The effect is\nthat higher-res content is placed 'in front of' lower-res content,\nwhich is usually desirable. This scheme for prioritization can produce\nunexpected results, though - lower-res content can simply disappear\nbehind higher-res facets. Keep this in mind if you can't see some of\nyour input in the output - you can pass --solo for the facet in question\nto make sure that it is in the viewing area at all.\n\nThe overall result - especially when all images have the same resolution -\nresembles a voronoi diagram. Facets with transparency let other facets\nshine through even if they don't 'win the contest'. You can mix facets\nwith and without transparency: all facets are 'pulled up' to the highest\nchannel count, but it's probably better if all input facets have the\nsame channel count and transparency quality. If a facet has transparency\nand is situated 'in front' of other facets, the facet(s) behind it will\nshine through. So, to reiterate: higher resolution content is given\npriority over lower resolution content, and if facets with equal resolution\ncollide, the pixels from the facet whose center is closest-by are given\npriority. The latter is the same criterion as 'highest z component'.\n\nWhy can the viewing ray's z component be used as 'quality' criterion?\nBecause the 'steppers' which feed rays into the pixel pipeline produce\nrays in each facet's 'native' coordinate system - so any rotations due\nto the facet's own orientation and the virtual camera's orientation are\nhandled by the stepper. This facilitates handling at the receiving end,\nand for multi-facet operation, the rays are also normalized. With normalized\nrays as input, comparing their z component is enough to figure out the\nvoronoi criterion: The maximum z value is 1.0, a normalized ray straight\nahead, which corresponds with the facet image's center. the smaller the\nz value, the farther away the ray is from the center, down to -1, which\nis a ray 'straight back'.\n\nIf you use multi-facet input with simple interpolation (--twine 0), you may\nnotice ungainly staircase artifacts where facets collide, and also where\nthe facets border on 'empty space'. This is due to the way facets are\nprioritized: only one facet can 'win the contest', and there is currently\nno implementation of feathering. If you use twining, the effect is mitigated,\nthe facets are blended to a certain degree, and tilted edges are faded into\nblack. The larger the twining kernel is, the better the effect. When\nautomatic twining is used, the twining kernel is calculated to suit all\nfacets - if some facets have very high resolution, this may result in a\nlarge twining kernel to avoid aliasing even for the parts of the target\nimage which show low-res content, bringing computation load up even if\nmost of the target image may come from lower resolution content. So the\nchoice of the automatic twining kernel is conservative but may be slow to\ncompute. With a twining kernel of standard size, horizontal and vertical\ncollision lines will be hard discontinuities. This is correct - the blending\ndue to twining only affects tilted collision lines, unless the twining kernel\nis widened, which introduces overall blur as well.\n\n## --pto PTO-FILE       panotools script in hugin PTO dialect (optional)\n\nenvutil can process a growing subset of the PTO standard. Currently, the\ni-lines in a PTO file are scanned for file name, projection, hfov, yaw,\npitch, roll, translation, shear and lens correction parameters. For an\nexplanation of PTO lens correction parameters, see this [Wiki Page](https://wiki.panotools.org/Lens_correction_model). The p-line is also processed,\nand k-lines (specifying masks) are partly understood (exclude masks for\nsingle images only). I have added a bit of tentative code looking at\ncontrol points, but this isn't ready as a feature yet.\n\nOther lines in the PTO file are currently ignored. Images from PTO files\nprecede the set of facets given with --facet - if no --facet parameters\nare present, only those given in the PTO file are used. I have opted to\nrestrict the facet parameters accessible with the --facet option to the\nset given above to avoid an overly large parameter signature - in favour\nof using PTO format for more complex facet parameterization. Note that\nenvutil's processing of projections in PTO format is limited to rectilinear,\nspherical, cylindrical, fisheye, and stereographic. Also note that there is\ncurrently no image blending (no image splining with the Burt\u0026Adelson image\nsplining algorithm as I use it in lux) - the facets will have 'hard' edges.\nThe facet prioritization is also fixed to a simple voronoi-diagram-like\nmode, more complex schemes like lux' shallow cone/steep pyramid method are\nnot yet available in envutil. Image vignetting is not touched either, and\nfor brightness envutil just looks at the Eev values and brightens/darkens\naccordingly, which is only correct for linear RGB input. Stacks aren't yet supported, but exposure brackets can be HDR-merged (see --synopsis)\n\nenvutil parses PTO format 'leniently' - you need to pass an image file\nname, projection and hfov in the i-lines, but other parameters may or\nmay not be present; if they are not given envutil sets them to zero or\nsome other sensible default. If the i-line contains fields which envutil\ndoes not process, they are simply ignored. envutil also recognizes some\nextensions to PTO format to help with cropped image input (cropped TIFF\nis currently not recognized) - see the section for the '--split' argument.\n\n## --pto_line LINE          add (trailing) line of PTO code\n\nPTO format is good to specify things like source images. At times\nediting or producing a given PTO file is laborious, and if all that's\nneeded can be expressed in one or a few lines of PTO code on the\ncommand line this is the option to use. You can pass as many PTO\nlines as you want by passing this argument repeatedly. The effect is\nprecisely the same as if you added the PTO lines to the PTO script\npassed with --pto - and if you did not pass a PTO script, only the\nlines given with pto_line option(s) are used. Internally, pto_line\narguments are simply collected and passed to the PTO parser after\na PTO file, if there is one, so there is no special magic here.\nAny 'facet' arguments are processed after the 'pto' and 'pto_line'\narguments - this is relevant if you need to refer to source images\nby number. See the example given in the text for the --single\noption for an example!\n\n## --oiio PLUGIN:ATTRIBUTE{@TYPEDESC}=VALUE\n\nOIIO plugins can take a whole range of extra configuration\narguments which instruct the plugins to behave in a certain way.\nenvutil doesn't deal with all these arguments separately but uses\nOIIO infrastructure code to make them 'palatable' to OIIO.\nenvutil can accept such parameters and pass them on to OIIO.\nTo pass OIIO configuration parameters, pass one or several '--oiio'\nparameters. The OPTION part is usually of the form\n\n    \u003cplugin\u003e:\u003cattribute\u003e=\u003cvalue\u003e\n\nso you'd pass this to envutil:\n\n    --oiio raw:ColorSpace=sRGB\n\nThis would instruct the libraw plugin to produce pixels in sRGB\nwhen loading raw images. Internally, the datum is treated as a mere\nkey/value pair; typos do not necessarily trigger an error but may\nresult in the desired option not being set, so check carefully.\nSome options require additional information about the data type\nof the value - especially when it's a datum consisting of several\nvalues (e.g. an ROI specification). OIIO can accept type information,\nand envutil has special syntax to pass it to a plugin: an OIIO\ntypestring is suffixed to the key, separated by an '@' sign,\nlike --oiio key@typestr=\"val val ...\" note the quotes: if there are\nseveral values, they have to be separated by space or tab, so the\nargument is quoted to 'hold it together'. An example for\nmulti-value parameters is the libraw plugin's parameter for the\ncorrection of chromatic aberration, raw:aber. It's used like this:\n\n    --oiio raw:aber@float[2]=\"1.001 1.001\"\n\nSo, to 'disentagle' this argument: we're telling OIIO (--oiio) to pass\nthe two-float 'aber' argument (aber@float[2]) to it's libraw plugin\n(raw:), namely the two floats in the quoted string (\"1.001 1.001\").\n\nPlease consult the [OIIO documentation on class TypeDesc](https://openimageio.readthedocs.io/en/stable/imageioapi.html#data-type-descriptions-typedesc)\nabout possible values for data types - most of the time, you can get by without passing a type, and the common types are simple lower-case strings\nlike 'int'. Note again that there is no check on the values you pass. If\nyou don't get the expected behaviour, check for typos - OIIO will accept\nany arguments: if you pass a key which is not recognized, this will\nsimply have no effect.\n\n## --solo FACET_INDEX       show only this facet (indexes starting from zero)\n\nThis is mostly useful when processing PTO files. envutil ignores all\nfacets but the specified one - processing is as if they did not exist\nat all, even if they would otherwise occlude the 'solo' facet.\n\n## --single FACET           render an image like facet FACET\n\nThis option sounds similar to the previous one, but it affects the\noutput rather than the input: the output will be rendered with the same\nprojection, width, height, hfov etc. as the facet with the given number.\nIf facet FACET is oriented, the virtual camera will be oriented in the\nsame way. If no other facets 'get in the way', the output should recreate\nfacet FACET - possibly with small differences due to processing. This\ndoesn't sound very interesting, but there is one good use I'd like to\npoint out: let's say you have a PTO file and the result from stitching\nthat PTO. Now you may want to re-create one of the source images. You\ncan do that by combining --single and --solo, like this: pass the PTO\nfile with --pto, then add a 'free' facet with the stitched image via\na --facet argument. If the PTO file contains x facets, the 'free' facet\nhas facet index x (numbering starts with zero!), now add --solo x to your\ncommand line, specifying that only content from facet x (the stitched\nimage) should be taken. Finally add --single y, where y is the facet from\nthe PTO file you'd like to recreate. The rendering will now produce an\nimage with the metrics of facet y (from the PTO file) filled with the\ncontent from the stitched image (passed as 'free' facet). The special\nfeature here is that the 'single' image will be rendered with *inverse\ntranslation and lens correction*, so the 'recreation' of the single\nimage is as faithful as possible. This feature can be used to produce\na set of synthetic source images from an already-stitched panorama and\nthen stitch the synthetic images with the same PTO parameters, which may\nbe helpful when testing panorama-related software. To give an example of\nthe procedure, suppose you have a PTO 'pano.pto' with three source images\nand the stitched output 'pano.tif', let's say it's a full spherical.\nTo recreate the second source image (so, number 1) from pano.tif:\n\n    envutil --pto pano.pto \\\n            --facet pano.tif spherical 360 0 0 0 \\\n            --solo 3 --single 1 --output image1.tif\n\nAt times you want to add further specifications to the 'solo' facet,\ne.g. lens correction parameters or translation parameters. envutil\ndoes not provide command line arguments for these specific values,\nbut you can add trailing lines of PTO code with one or several\n--pto_line arguments. Here's an example specifying a solo facet with\na specific Eev value:\n\n    envutil --pto pano.pto \\\n            --pto_line 'i f4 v360 n\"pano.tif\" Eev13.5' \\\n            --solo 3 --single 1 --output image1.tif\n\nApart from the Eev parameter this is just the same as the previous\ninvocation, but the syntax is plain PTO: you add an 'i' line\nspecifying an additional source image 'pano.tif' in spherical\nprojection (f4) and 360 degrees fov (v360). Note that it's enough\nto specify only the parameters you need.\n\nThis feature is also handy for shell scripts or other scenarios where\nyou want to use envutil as a helper program. Please read on to the next\nsection, where I introduce parameterization for cropped image input;\nthis can be used for 'single' jobs as well.\n\n## --split FORMAT_STRING  create a 'single' facet for all facets in a PTO\n\nThis argument is for convenience - you might produce the same set of\noutput images with 'single' jobs (see above) for each of the source\nfacets. Here, you pass a format string which contains a placeholder for\nan integer (use something like %02d) - this is replaced with each\nfacet number in turn, and a 'single' job for that facet is run,\nproducing the corresponding 're-created' facet image. As explained\nfor 'single' jobs, you can do this with 'solo' set to a facet which\nyou want to yield content exclusively. For 'split' jobs, this is\ntypically an already-stitched panorama image, and because you do\nnot normally want to have this image re-created, (you have it already)\n'split' jobs skip over the solo facet. Without a 'solo' argument,\ncontent for the 're-created' images is taken from all source facets,\nlike in an ordinary stitch with envutil - as of this writing, this is\ngeometrically correct, but the images are not blended. Here's the\nexample above as a 'split' job *with* a solo argument:\n\n    envutil --pto pano.pto \\\n            --pto_line 'i f4 v360 n\"pano.tif\"' \\\n            --solo 3 --split img_%02d.tif\n\nThis would produce images img_00.tif, img_01.tif and img_03.tif,\nwhich should be geometrically identical to the three source facets\ngiven in 'pano.pto', provided that 'pano.tif' was stitched from that\nPTO script - but due to the stitched intermediate, the new files\nwill show the stitched image's content - so if the stitch is with\nproper blending, you'll not see any seams, and of course you won't\nsee any content which was masked out or not included into pano.tif\nduring the stitch. Re-stitching the 're-created' images with the\noriginal PTO (replacing filenames in the i-lines) should recreate\npano.tif - minus small differences due to processing, e.g. from\ninterpolation or due to excession of the dynamic range. There is\none stumbling stone in this process: output cropping. PT format\nallows output cropping via an 'S' clause in the p-line. Obviously,\nthe output resulting from stitching such a PTO file will need\nspecial treatment - and so do other facet images which have been\ncropped from a (possibly virtual) larger image file. envutil can\nprocess cropped images, but the metrics of the cropping window must\nbe given explicitly - currently, 'cropped TIFF format' is not\nrecognized. envutil uses an extension to PTO format: a 'W' clause.\nThis is using the same syntax as the 'S' clause in the p-line, so\nit's W\u003cX0\u003e,\u003cY0\u003e,\u003cX1\u003e,\u003cY1\u003e where \u003cX0\u003e stands for the start pixel\nnumber for the window, \u003cX1\u003e for one past it's end, and the Y values\nsimilarly for the vertical. If a 'W' clause is present, an i-line\nalso needs 'w' and 'h' clauses, which are taken to encode the width\nand height of the *uncropped* image, which can't be figured out\notherwise. With a 'W' clause present, the size of the window given\nby the 'W' clause must match the size of the image data in the image\nfile given in the i-line's 'n' clause. Let's assume the file 'pano.tif'\nfrom the example above had been made with this p-line 'S' clause:\n\n    S20,10,2000,1500\n\nAnd assume the uncropped size given in pano.pto is 4000X2000. The\nimage file 'pano.tif' must be size 1980X1490 (the size of the cropping\nwindow), and it's a cut-out from a (virtual) full spherical image. To\nrun a 'split' job using 'pano.tif', you'd invoke envutil like this:\n\n    envutil --pto pano.pto \\\n            --pto_line 'i f4 v360 n\"pano.tif\" W20,10,2000,1500 w4000 h2000' \\\n            --solo 4 --split img_%02d.tif\n\nAs before, the extra image file which is submitted to splitting is\nintroduced as an extra facet. The extra facet is used as sole input.\nBut the i-line used to introduce the extra facet now has the information\nneeded to take into account the fact that it's a cropped image. Please\ndon't confuse the 'W' clause in an i-line with an 'S' clause, which may\nalso be present - this has quite a different meaning in an i-line and\nspecifies lens cropping - discarding unwanted marginal parts of the image\nwhich don't contain useful content.\n\nMost of the time, when you want to 'unstitch' a panorama, you'll want to\nwork with just what the PTO file's p-line prescribes - you've seen in the\nprevious example how the w,h, and S-clauses 'reappeared' in the pto_line\nargumment. Because this is a common requirement, there is a shortcut with\nyet another envutil extension to PT format. To get the same effect as\nabove, use:\n\n    envutil --pto pano.pto \\\n            --pto_line 'i Pano\"pano.tif\"' \\\n            --split img_%02d.tif\n\nThe metrics of the extra facet are now taken *from the p-line*, and the\n'solo' argument is set automatically. Output is the same. If the p-line\nin the PTO file does not have an S-clause, that's okay - the image is\ntaken as uncropped, with width and height as found in the image file\n(this takes precedence over 'w' and 'h' clauses in the p-line). So the\n'Pano' clause in the pto_line argument can make your life easier. Note\nthat the two envutil extensions to PTO format which I have described -\nnamely the 'W' clause and the 'Pano' clause - can be used inside of\nPTO files just as well, but this may make the PTO file invalid for\nother programs using PT format, so 'slotting in' the extra facet with\na pto_line parameter is less intrusive.\n\n## --mask_for FACET_INDEX   paint this facet white, all others black\n\nThe caption is slightly simplified, so here's the whole story: processing\nreplaces all colour or greyscale channels with 1 (full intensity) in\nthe indicated facet - or, if an alpha channel is present, with the value\nof the alpha channel, so that the pixel, interpreted as associated alpha,\nis like a full-intensity pixel with the given transparency. Other facets\nare treated in the same way, but receive zero intensity while retaining\nan alpha value, if present. Further processing is the same.\nBecause all colour channels receive the same intensity value, the global\nchannel count can be reduced to one (if no alpha is present) or two (for\nfacets with alpha channel) - by passing --nchannels. single-channel masks\ncan even be made without loading any images, because there is no alpha\nchannel to consider. Masks with alpha channel may have (semi-)transparent\nareas where none of the facets provides full opacity. Areas not covered\nby any facets will also come out black or transparent black.\n\n## --nchannels CHANNELS     produce output with CHANNELS channels (1-4)\n\nThis option sets the global channel count for all facets before they\nare combined into the target image. The facets are forced to this common\nchannel count, processig facet data with alpha channel as assocotaed alpha.\nThis is a handy way to reduce the channel count for masking jobs (as stated\nin the previous chapter) or to produce greyscale imagery. Reduction of RGB\ninformation to greyscale is by averaging. If this option is not present,\nfacets may still be 'pulled up' to a different channel count before they are\nsubmitted to prioritization. Here's the rule: all facets are pulled up to\nthe channel count of the facet with the highest channel count. If any facet\nhas an alpha channel and the maximal channel count would not contain an\nalpha channel, this is added - this can happen if there are two-channel\nfacets (greyscale+alpha) and RGB facets but no RGBA facets - the resulting\nglobal chanel count will be set to four for RGBA.\n\n# Parameters for Single-Image Output\n\n## --output OUTPUT       output file name (mandatory)\n\nThe output will be stored under this name. If you are generating cubemaps\n(e.g. --projection cubemap) you may pass a format string, just as for input.\nYou'll get six separate cube face images instead of the single-image cubemap.\nThis also works with --projection biatan6 - you'll get six cube face images\nwith the 'biatan6' reprojection applied.\n\n## --projection PRJ  target projection\n\nPass one of the supported output projections: \"spherical\", \"cylindrical\",\n\"rectilinear\", \"stereographic\", \"fisheye\", \"cubemap\" or \"biatan6\". The\ndefault is \"rectilinear\". \"biatan6\" is a recent addition: it's a cubemap\nwith an additional in-plane transformation to sample the sphere more\nevenly than can be done with rectilinear cube faces:\nOn top of the default, \"cubemap\", which does not use an in-plane transformation\n(the cube faces are in rectilinear transformation and used just so), envutil\nnow supports \"biatan6\" in-plane transformation. This is a transformation which,\nwhen used to create a cubemap, compresses the content\ntowards the edges of the cube faces (where it is normally unduly stretched\nbecause of the rectilinear projection) and a widens it in the center, where\nthere is now more space due to the compression near the edges. Where the\nsample steps along a horizon line (central horizontal of one of the surrounding\nfour cube faces) correspond with rays whose angular difference decreases\ntowards the edges with rectilinear projection, with this in-plane \ntransformation the corresponding rays are all separated by the same angular\nstep. There is still a distortion which can't be helped (we're modelling\na curved 2D manifold on a plane), but it amounts to a maximum of 4/pi in\nthe center of a vertical near a horizontal edge (or vice versa) due to the\nscaling factor used both ways. Why 'biatan6'? because it uses the arcus tangens\n(atan) in both the vertical and horizontal of all six cube faces. The tangens\nand it's inverse, the arcus tangens, translate between an angle and the length\nof a tangent - the latter is what's used by the rectlinear projection, which\nprojects to the tangent plane, and is the default for cube faces. Using\nequal angular steps is closer to an even sampling of the sphere. The in-plane\ntransformation functions are used on the in-plane coordinates of the cube faces,\nwhich, in envutil, range from -1 to 1 in 'model space' (the cube is modelled\nto have unit center-to-plane distance, so as to just enclose a unit sphere).\nThere is a pair of them, which are inverses of each other, meaning that\napplying them to a 2D coordinate one after the other will reproduce the\ninitial value. These are the two functions:\n\n    float ( 4.0 / M_PI ) * atan ( in_face ) ;\n\n    tan ( in_face * float ( M_PI / 4.0 ) ) ;\n\nUsing transcendental functions (tan, atan) is costly in terms of CPU\ncycles, but both functions have SIMD implementations which make the cost\nacceptable, because they can still process several values at once. An\nalternative would be to use a pair of two functions roughly modelling\nthe two curves above, but with the same property of being inverses of\neach other. I leave this option for further development - another idea\nwould be to introduce the in-plane transformation as a functional paramter,\nso that user code can 'slot in' such a transformation.\nThe advantage of the 'biatan6' transformation is that it transforms each\n2X2 square to another 2X2 square - if one were to use e.g. spherical\nprojection, there would be redundant parts in several images. So\nwith biatan6 transformation, each point in the cubemap has precisely one\ncorrespondence on the sphere, just as with rectilinear projection. \n\n## --hfov ANGLE      horiziontal field of view of the output (in degrees)\n\nThe default here is ninety degrees, which all projections can handle.\nSpherical, cylindrical and fisheye output will automatically periodize\nthe image if the hfov exceeds 360 degrees. Rectilinear images can't handle\nmore than 180 degrees hfov, and at this hfov, they won't produce usable\noutput. Stereographic images can theoretically accomodate 360 degrees fov,\nbut just as rectilinear images aren't usable near their limit of 180 degrees,\nthey aren't usable when their limit is approached: most of the content\nbecomes concentrated in the center and around that a lot of space is wasted\non a bit of content which is stretched extremely in radial direction. For\ncubemaps, you should specify ninety degrees hfov, but you can produce\ncubemaps with different hfov - they just won't conform to any standards\nand won't be usable with other software unless that software offers\nsuitable options. envutil supports wider-angle cubemaps - just pass the\ncorrect hfov to facets with cubemaps. Note that some cubemaps you may\nget hold of use a slightly different notion of a square image: envutil\nmeasures field of view 'edge-to-edge', meaning that each pixel is taken\nto be a small square, and the fov is measured from the leftmost pixel's\nleft margin to the rightmost pixel's right margin. Some cubemaps measure\nthe field of view from the center of the leftmost pixel to the center of\nthe rightmost one, which I call 'center-to-center or 'ctc' for short.\nSuch cubemaps have margins which repeat on other facets, so they waste\nsome space, but they are easier to handle mathematically. envutil does it\n'the hard way' and uses edge-to-edge semantics. If you encounter a cubemap\nwith center-to-center semantics and want to process it with envutil, you\nneed to modify the hfov value like this:\n\n    fov' = 2 * atan ( tan ( fov / 2 ) * ( width + 1 ) / width )\n\nThe resulting value, fov', is what you pass to envutil - it's slightly\nlarger than the 'ctc' value, because it's now measured edge-to-edge.\n\n## --width EXTENT    width of the output\n\nin pixel units. For lat/lon environment images, this should be precisely\ntwice the height, so this value should be an even number. I recommend that\nyou pick a multiple of a small-ish power of two (e.g. 64) to make it easier\nfor software wanting to mip-map the data. When producing cubemaps from\nfull sphericals, I recommend using a width which is ca. 1/pi times the width\nof the input. For the reverse operation, just use four times the cubemap's\nwidth. These factors preserve the resolution. cubemaps in biatan6 projection\nshould preserve resolution with slightly smaller width - try and use 1/4 of\nthe full spherical's width.\n\n## --height EXTENT   height of the output\n\nin pixel units. For cubemaps, this is automatically set to six times the width.\nFor spherical output, if height is not passed, it is set to half the width,\nincreasing 'width' to the next even value. For other projections, if you don't\npass 'height', the default is to use the same as the width, so to render a\nsquare image.\n\n## --synopsis MODE  mode of composing several images (panorama or hdr_merge)\n\nWhen there are several facets, envutil can produce a synoptic view in\ndifferent ways. Currently, two modes are available: producing a simple\npanorama (no blending at the seams) or merging several exposures from an\nexposure bracket into an HDR image. The rendition is, in both cases,\npixel-based - pyramid-based methods like the Burt\u0026Adelson image splining\nalgorithm which I provide in lux are not yet available. If the 'facet'\nimages fit very well, the lack of blending may be acceptable - at any rate\nit provides a good idea of how the images fit together and allows for\nreasonably quick inspection of PTO files as long as the project isn't too\ncomplex, which will require lots of memory. Note that using input of greatly\nvarying resolution may result in very long processing times, because the\ntwining is set up with regards to the highest-res image - if you need quick\nresults, switch twining off (--twine 0) and live with aliasing. envutil's\n'understanding' of PTO format is quite good - it can even process translation\nand lens correction parameters, and there is an interesting option to\n'unstitch' a panorama into partial images with the geometry of the original\ninput but the content from the panorama. Please refer to the '--single'\nand '--split' parameters. The spatial composition mode can also be used to\nproduce binary masks.\nHDR blending can be achieved quite successfully on a per-pixel basis.\nInternally, the images are dimmed/brightened to a common brightness, but\nwhich content is picked from each facet is determined by looking at the\n'well-exposedness' criterion: if pixels are near the middle of the image's\nintensity range, they are considered well-exposed. envutil excludes all\npixels which are overexposed, even if only in one channel (grey projection\nuses the maximum of all three channels). Since all imput is in the form of\na 'facet' (single-image input is simply facet number zero), HDR-merging\nbrackets goes well with passing --single X where X is the number of the\nfacet whose geometry, brightness and shape are used for output. For exposure\nbrackets from my Canon cameras I often use --single 0, because the number\nzero exposure is the middle exposure on these cameras. HDR output is best\nstored to formats which can handle an extended dynamic range, like openEXR.\nEven LDR output will benefit from HDR-fused content, though, because the\nbrighter exposures will provide less noisy data for the darker parts of\nthe scene - only the content which is to bright for an LDR image's dynamic\nrange will be lost.\n\n# Additional Parameters for Cubemaps\n\n## --support_min EXTENT  minimal additional support around the cube face proper\n\nThis is a technical value, probably best to leave it at it's default of 8.\nwhen a cubemap is converted to it's internal representation, the ninety degree\n'cube face proper' is surrounded with a frame of additional 'support' pixels\nwhich help interpolation. You can specify here how wide this frame should be\nat the least.\n\n## --tile_width EXTENT   tile width for the internal representation image\n\nAlso best left at the default of 64.\nThis value takes care of widening the support frame further - if necessary - to\nmake the size of the square images in the internal representation a multiple\nof this size. This does not magnify the image but adds pixels reprojected from\nother cube faces, so it doesn't affect image quality. Using a small-ish power\nof two here is especially useful when using OIIO for lookup, to help it\nmip-map the texture generated from the internal representation - OIIO can't\nnatively process cubemap environments, so envutil generates a texture file\nand feeds that to OIIO's texture system for the look-up.\n\n### A Side Note on lat/lon Images\n\nenvutil's assumption about lat/lon images is that they follow edge-to-edge\nsemantics for the horizontal and the vertical: the image lines at\nthe very top and bottom of the image represent a (very small) circle around the\npole with half a pixel width radius, opposed to some lat/lon formats which use\n'center-to-center' semantics and repeat a single value (namely that for the pole)\nfor all pixels in the top, and another for the bottom row. From this it shoud be\nclear why envutil expects lat/lon images to have precisely 2:1 aspect ratio.\nEnvutil honours the peculiarities of the spherical (a.k.a. equirectangular)\nprojection and interpolations near the poles will 'look at' pixels which\nare nearby on the spherical surface, even if they are on opposite sides of the\npole, so you can e.g. safely extract nadir caps.\n\n## --yaw ANGLE       yaw of the virtual camera (in degrees)\n## --pitch ANGLE     pitch of the virtual camera (in degrees)\n## --roll ANGLE      roll of the virtual camera (in degrees)\n\nThese three angles are applied to the 'virtual camera' taking the view.\nThey default to zero. It's okay to pass none or just one or two. yaw is\ntaken as moving the camera to the right, pitch is taken as upward movement,\nand roll as a clockwise rotation. Note that the orientation of the *virtual\ncamera* is modified; when looking at the resulting images, objects seen on\nthem seem to move the opposite way. Negative values have the opposite effect.\nPanorama photographers: to extract nadir patches, pass --pitch -90\nThese angles are known as the 'Euler Angles' and are easy to understand, as\nopposed to the quaternions and rotation matrices which envutil uses internally\nto represent rotations.\n\n## --x0 EXTENT       low end of the horizontal range\n## --x1 EXTENT       high end of the horizontal range\n## --y0 EXTENT       low end of the vertical range\n## --y1 EXTENT       high end of the vertical range\n\nThese are special values which can be used to specify the extent, in model\nspace units, of the output. This requires some understanding of the inner\nworkings of this program - if you use -v, the verbose output will tell you\nfor each rendering which extent values are generated from a field of view\nparameter, given a specific projection. This can help you figure out specific\nvalues you may want to pass, e.g. to produce anisotropic output or cropped\nimages.\n\n\u003cdiv id=\"interpolation-options\"/\u003e\u003c/div\u003e\n\n# Interpolation Options\n\nenvutil will use 'twining' with automatic settings as it's default\ninterpolation method. You can explicitly disable twining by passing\n--twine 0 - this results in 'straight' b-spline interpolation directly\nfrom the source image data:\n\n    use b-spline interpolation directly on the source image(s). This\n    is the fastest option, and unless there is a significant scale\n    change involved, the output should be 'good enough' for most still\n    image renditions. This is the default, with a spline degree of 1,\n    a.k.a. bilinear interpolation. Other spline degrees can be chosen\n    by passing --degree D, where D is the degree of the spline. Per\n    default, splines with degree \u003e 1 are 'prefiltered'. You may pass\n    --prefilter D to apply a prefilter for a different degree than the\n    one used for evaluation. This can be used e.g. to blur the output\n    (use a smaller value for the prefilter degree than for the spline\n    degree). Note that b-splines may 'overshoot', unless you omit\n    the prefilter. This depends on the signal - if it's sufficiently\n    band-limited (nothing above half Nyquist frequency), the spline\n    won't overshoot. Using a degree-2 b-spline without prefilter\n    (--degree 2 --prefilter 0) introduces only slight blur and won't\n    overshoot - it's often a good compromise and also avoids the\n    star-shaped artifacts typical of degree-1 b-splines when the\n    signal is magnified a lot.\n\nIf you don't pass --twine 0, or pass a value other than zero, envutil uses\ntwining to avoid aliasing and star-shaped artifacts of bilinear interpolation:\n\n    use 'twining' - this is a method which first super-samples and then\n    combines several pixels to one output pixel ('binning'). This is my\n    own invention. It's quite fast and produces good quality output.\n    This method should see community review to compare it with other\n    methods. The 'twining' interpolator is 'grafted' onto the 'substrate'\n    interpolator - that is the b-spline from the source image. So if you\n    pass --degree 3, the 'substrate' of the twining operator will be a\n    cubic b-spline, rather than a degree-1 b-spline (a.k.a bilinear\n    interpolation) which is the default.\n\nIn general, producing visible output is often a two-step process. The first\nstep is to provide some sort of 'ground truth' - an internal representation\nof the image data which will provide a specific value for a specific pick-up\nlocation. This step tends to aim for speed and precision, without taking\ninto account considerations like aliasing or artifacts introduced by the\ninterpolation. The signal which is provided by the first step is usually\ncontinuous due to interpolation. Sometimes, the first stage will not use\ntrue interpolation, meaning that the value of the first-stage signal is\nnot necessarily equal to the image data at discrete coordinates. If so,\nthe signal is typically blurred - e.g. by using a b-spline kernel on the\nraw data without adequate prefiltering.\n\nThe second step - if present - operates on the first-stage signal. This step\nis often added to avoid problems from using the first-stage signal directly.\nThe most important effect which the second stage tries to produce is\nanti-aliasing. If the output is a scaled-down version of the input, direct\ninterpolation at the pick-up points will produce aliasing where the input\nsignal has high-frequency content. A typical strategy to avoid this is to\nconsider a section of the first-stage signal corresponding to the 'footprint'\nof each target pixel and form a weighted sum over source pixels in that area.\nThe precise way of how this is done varies - OIIO can use an elliptic shape\nplaced over the first-stage signal and produces an average over this area,\nwhereas 'twining' gathers several point-samples in this area and forms a\nweighted sum. Both methods produce similar results.\n\nA third strategy adds scaled-down versions of the image data, which are then\nused to provide 'ground truth' for rendering jobs which would down-scale\nfrom the original resolution. The archetypal construct for this strategy is\nan 'image pyramid' - a set of images where each has half the resolution\nof the one 'below' it. OIIO can use this strategy (look for mip-mapping),\nand it's advantage is that pickup kernels can remain relatively small,\nbecause there are fewer pixels to form a sum over to avoid aliasing.\nThe rendering schemes in envutil do not currently use image pyramids;\ntwining deals with the aliasing problem by picking adequately large\nkernels directly on the first-stage signal. It would be feasible, though,\nto add pyramid schemes. One problem with image pyramids is the fact that\nthey have to be produced at all: they take up memory and generating them\ncosts computational resources. envutil often does 'one-shot' jobs, and\nrather than producing an image pyramid with *all* resolutions, producing\noutput with just one resolution - even if that requires a large kernel -\nis usually more efficient. If an image pyramid is present, it's an option\nto mix 'ground truth' data from two adjacent pyramid levels for output\nwhose resolution is between that of the two pyramid levels (look e.g.\nfor 'trilinear interpolation'). twining, which uses a scalable filter,\ncan adapt the filter size to the change in resolution, so it doesn't use\nthis method. Image pyramids are useful when scaled-down content can be\nreused often (e.g. in animated sequences in lux where 60 fps are needed\nand rendering must be as fast as possible).\n\n##  --degree DEG      degree of the b-spline (\u003e= 0)\n##  --prefilter DEG   prefilter degree (\u003e= 0) for the b-spline\n\nAll rendering in envutil use b-splines as the 'ground truth' substrate.\nIf you don't pass --degree or --prefilter, a degree-1 b-spline is used - this\nis also known as 'bilinear interpolation' and already 'quite good'. But higher\nspline degrees can produce even better output, especially if the view magnifies\nthe source image, and the star-shaped artifacts of the bilinear interpolation\nbecome visible. If you only pass 'degree', the spline will be set up as an\ninterpolating spline, meaning it will yield precisely the same pixel values\nas the input when evaluated at discrete coordinates. This requires prefiltering\nof the spline coefficients with a prefilter of the same degree as the spline,\nwhich is done by default. All rendering arithmetic in envutil is done in\nsingle-precision float, so you can't use very high spline degrees, which is\nfutile anyway. If you go up to degrees in the twenties, the dynamic range of\nsingle precision is exceeded and you'll first get artifacts in the output,\nthen, with even higher degrees, errors which render the output unusable.\nFor the purpose at hand, 'ground truth' with bilinear interpolation is\nusually perfectly good enough. If you don't use twining (--twine 0) and\nyour view is magnifying, pick a small degree like two or three.\n\nIf you pass a different prefilter degree, the coefficients are prefiltered\n*as if* the spline degree were so, whereas the evaluation is done with the\ngiven degree. You can use this to produce smoother output (lower prefilter\ndegree than spline degree) or to sharpen it (higher prefilter degree than\nspline degree). A disadvantage of interpolating splines is that they will\nproduce ringing artifacts if the input signal isn't band-limited to half the\nNyquist frequency. With raw images straight from the camera this is usually\nthe case, but processed or generated images often have high frequency content\nwhich will result in these artifacts, which become annoyingly visible at high\nmagnifications. One way to deal with this problem is to accept a certain amount\nof smoothing by omitting the prefilter, e.g. passing --prefilter 0 and --degree\ngreater than one. With a spline degree of two and no prefiltering, there is\nmild suppression of high frequencies, but there are no ringing artifacts, and\nthis is often a good compromise. bilinear interpolation also does not suffer\nfrom ringing artifacts - there, the drawback is the 'star-shaped artifacts'\nin magnifying views. Using prefilter degrees higher than the spline degree\nmay result in unwanted artifacts and make the output unusable.\n\nIf you need very high quality processing, keep in mind that only splines\nwhich are band-limited to *half the Nyquist frequency* are stable under\nrepeated re-sampling with shifted/rotated grids. To obtain such a spline,\none easy route is to create an image with twice the size of the input,\nusing a b-spline (e.g. a cubic one) with no twining. This double-sized\nimage will be appropriately band-limited. Now you can do the processing,\nagain working with a b-spline, and taking care not to violate the sampling\ntheorem - as long as your process doesn't sample the 'doubled' spline\nwith sampling locations spread out farther than the distance between two\nadjacent knot points, the 'stability' of the doubled spline to resampling\nwill preserve the signal, so that degradation only occurs due to quantization\nerrors. If you produce a final output, you may need to revert to the input\nimage's resolution, and that's when you can use a twining filter to affect\nthe down-scaling. Of course you can save yourself the 'spline doubling' if\nyou know beforehand that your input is adequately band-limited. If your\nprocessing uses intermediate images, keep in mind that quantization is an\nissue. The least you can do to preserve quality is to use 16 bit samples,\nbut using single-precision float is better - e.g. in TIFF format. Only use\nlossless compression if at all.\n\n\u003cdiv id=\"twining-specific-options\"/\u003e\u003c/div\u003e\n\n# Twining-specific options\n\nThese options control the 'twining' filter, which is active by default (switch\nit off by passing --twine 0 explicitly). With twining, envutil uses a two-step\napproach to rendering. The first step establishes, for any given (continuous)\ncoordinate, a 'ground truth' value. This is done with b-spline interpolation,\nwhich can be parameterized: starting with simple nearest-neighbour lookup\n(a zero-degree spline), the next step up is a degree-one b-spline, also known\nas 'bilinear interpolation'. This is aleady 'quite good' and the default.\nAbove degree-one splines come b-splines of higher degrees, where choices\nabove three are rarely sensible. To find out more about parameterization\nof b-splines, refer to te chapters for --spline_degree and --prefilter.\n\nThe second step - the actual 'twining' - looks up several closely spaced\ncontinuous coordinates with the 'ground truth' interpolator and combines\nthem in a weighted sum. This process is inherently very flexible: the\n'sub-pick-up' locations can be chosen arbitrarily, rather than having to\nfollow a rigid discrete grid, which is the case for convolution-based lookup.\nThe default twining process in envutil spaces the sub-pick-up locations\nevenly over the area which a target pixel would cover in the source image,\nif it were projected onto it. Subsequently, the results from the sub-pick-ups\nare averaged. The result is the same as supersampling the 'ground truth'\nsignal and subsequently combining groups of neighbouring pixels into\n'bins' corresponding with the target pixels - or, to put it differently,\nthe result is the same as first rendering a large image with the ground-truth\ninterpolator, and then scaling it down to the desired output size. If the\nsub-pick-up locations are closer to each other than the knot points of the\nground truth spline, aliasing won't occur, and interpolation-related\nartifacts are mitigated (e.g. overshoot due to insufficient band-limitation\nor the star-shaped artifacts of bilinear interpolation).\n\nThe operation of the twining filter differs conceptually from OIIO's pick-up\nwith derivatives: OIIO's filter (as I understand it) looks at the difference\nof the 2D coordinates into the source texture and produces a filter with a\nfootprint proportional to that. twining instead inspects the difference in\n3D coordinates and places it's sub-pickups to coincide with rays which are\nproduced by processing this difference. This is a subtle difference, but it\nhas an important consequence: OIIO's pick-up will encompass a sufficiently\nlarge area in the source texture (or one of it's mip levels) to generate an\noutput pixel, so even if this area is very large, the pick-up will be\nadequately filtered. twining will spread out it's sub-pickups according to\nthe number of kernel coefficients and kernel size, but the number of kernel\ncoefficients does not vary with the pick-up location, only the area over\nwhich the sub-pickups are spread will vary. So to filter correctly with a\ntwining filter (obeying the sampling theorem), the twining filter needs to\nhave sufficiently many coefficients spread 'quite evenly' over the pickup\narea to avoid sampling at lower rates than the source texture's sampling\nrate. While OIIO's filter forms a weighted sum over pixels in one of the\ntexture's mip levels, twining relies on a b-spline interpolation of the\noriginally-sized texture as it's substrate. If the sub-pickup locations are\n'too close' to each other, the shortcomings of a bilinear interpolation\nmay 'shine through' if the transformation magnifies the image (you'll see\nthe typical star-shaped artifacts). If the sub-pick-ups are 'too far apart',\nyou may notice aliasing - of course depending on the input's spectrum as well.\n\nThe big advantage of twining is that it does not interact directly with the\nsource image data (all interaction is via the 'ground truth' spline signal)\nand it's operation happens in 'ray space'. The relieves the process from having\nto make assupmptions about the source image data (like, it's availablility\nover a given area or a specific geometrical structure) - the only thing which\nis taken for granted is the fact that the ground truth signal is available\nand precise, and the twining filter is set up so that it is adequate for the\nground truth signal at hand so that it will stay within the boundaries set by\nthe sampling theorem.\n\nKeeping this in mind, if you want to set up a twining filter yourself, either\nby passing twining-related parameters setting the number and 'spread' of\nthe filter coefficients or by passing a file with filter coefficients, you\nmust be careful to operate within these constraints - using automatic\ntwining, on the other hand will figure out a parameter set which should \nkeep the filter within the constraints, so you neither get aliasing for\ndown-scaling views nor bilinear artifacts in up-scaling views.\nWhen creating a twining filter externally, be generous: processing is fast\neven with many sub-pickups, so using a larger number than strictly necessary\nwon't do much harm, just take a little longer. If you produce a magnifying\nview and can use a b-spline with degree two or more, twining is futile,\nbecause the b-spline interpolation already produces a near-optimal result.\nIf there are several facets, automatic twining will configure the filter\nso that even the most scaled-down content shows no aliasing. Using twining\nwith large kernels can take significant processing time, but the resulting\nimages should be of a very good quality, so I think the process should be\nsuitable to e.g. calculate image pyramids for pyramid-based schemes (like\nmip-mapping) which, in turn, are better-suited for fast rendering over a\nlarge range of scales (like in lux).\n\n## --twf_file TWF_FILE   read twining filter from a file\n\nPassing a twf file with this option reads the twining filter from this file,\nand ignores most other twining-related options. The file is a simple text file\nwith three float values per line, let's call them x, y and w. This option\nswitches twining on unconditionally.\n\nThe x and y values are offsets from the pick-up location and w is a weight.\nThe x and y values are offsets in the horizontal/vertical of the target\nimage (!): an offset of (1,0) will pick up from the same location as the\nunmodified source coordinate of the target pixel one step to the right.\nHow far away - in source texture coordinates - this is, depends on the\nrelative geometry - projection and size of the source and target image.\n\ntwining is based on an approximation of the derivatives of the coordinate\ntransformation at the locus of interpolation: if you calculate the pick-up\ncoordinate for a pixel one step to the right or one step down, respectively,\nyou can form the differences to the actual pick-up coordinate and obtainin\ntwo vectors xv and yv which approximate the derivative. x and y are used as\nmultipliers for these vectors, resulting in offsets which are added to the\nactual pick-up coordinate for each set of x, y and w. The offsetted coordinate\nis used to obtain a pixel value, this value is multiplied with the weight, w,\nand all such products are summed up to form the final result. The vectors xv\nand yv are 3D vectors and represent the difference of two 3D rays: the ray to\nthe central pickup location and a ray to another sub-pickup location in it's\nvicinity.\n\nA twining filter read from a file will apply the given weights as they are,\nthere is no automatic normalization - pass 'twine_normalize' to have the\nfilter values normalized after they have been read. Beware: using a filter\nwith gain greater than 1 may produce invalid output! The only other parameter\naffecting a twining filter read from a file is 'twine_width': if it is not\n1.0 (the default), it will be applied as a multiplicative factor to the 'x'\nand 'y' values, modifying the filter's footprint.\n\nWith an externally-generated twining filter, there is no fixed geometry,\nand any kind of non-recursive filter can be realized. envutil applies this\nfilter to 3D ray coordinates rather than to the 2D coordinates which occur\nafter projecting the 3D ray coordinate to a source image coordinate, so\nthe effect is similar to what you get from OIIO's 'environment' lookup\nwith the elliptic filter.\n\nBecause the two vectors, xv and yv, are recalculated for every contributing\ncoordinate, the filer adapts to the location, and handles varying local\ngeometry within it's capability: the sampling theorem still has to be\nobeyed insofar as generated sub-pickups in a cluster mustn't spread out\ntoo far and when they are too close, shortcomings of the underlying\ninterpolation may show. And there have to be sufficiently many\nsub-pickups. Automatic twining produces a 'reasonable' filter which\ntries to address all these issues - play with automatic twining and\nvarious magnifications to see what envutil comes up with (pass -v\nto see the filter coefficients).\n\n## --twine_normalize   normalize twining filter weights gleaned from a file\n\nIf you pass a file with twining kernel values, the default is to use\nthem as they are: you are free to apply any possible twining filter,\nincluding, e.g. differentiators which may yield negative output. Most of\nthe time, though, your kernel's weights will all be positive and the\nfilter will have unit gain, meaning that all weights add up to one.\nAt times it's easier to just calculate filter weights without making\nsure that the sum of the weights is one. Pass --twine_normalize to let\nenvutil do the normalization for you.\n\n## --twine TWINE         use twine*twine oversampling and box filter\n\nThe effect of 'twining' is the same as oversampling and subsequent application\nof a box filter. The filter is sized so that the oversampling is uniform over\nthe data, but the direct result of the oversampling is never saved - all\nsamples falling into a common output pixel are pooled and only the average\nis stored. This keeps the pipeline 'afloat' in SIMD registers, which is fast\n(as is the arithmetic) - especially when highway or Vc are used, which\nincrease SIMD performance particularly well.\n\nPass --twine 0 explicitly to switch twining off.\n\nIf you don't deactivate twining, envutil will set up twining parameters\nautomatically, so that they fit the relation of input and output. If the\noutput magnifies (in it's center), the twine width will be widened to\navoid star-shaped artifacts in the output. Otherwise, the twine factor\nwill be raised to avoid aliasing. You can see the values which envutil\ncalculates if you pass -v. If the effect isn't to your liking, you can\ntake the automatically calculated values as a starting point. There are\nseveral optional parameters to change the twining filter and override\nautomatic parameterization:\n\n## --twine_width TWINE_WIDTH  alter the size of the pick-up area of the twining filter\n\nA second parameter affecting 'twining'. If the source image has smaller\nresolution than the target image, the output reflects the interpolator's\nshortcomings, so with e.g. bilinear interpolation and large scale change\n(magnification) the output may show star-shaped and staircase artifacts.\nA 'standard' twine with twine_width 1.0 will pool look-ups which all\ncorrespond to target locations inside the *target* pixel's boundaries.\nFor magnifying views, this becomes ever more pointless with increasing\nmagnification - the look-up locations will all fall into a small area\nof the source image - so, they'll be very much one like the other, and\npooling several of them is futile. So for magnifying views, you want to\nwiden the area in which look-ups are done to an area which is in the\nsame order of magnitude as a *source* image pixel. To get this effect,\ntry and pass a twine_width up to the magnitude of the scale change,\nor rely on automatic twining, which calculates a good value for you.\nOn the other hand, passing a twine_width smaller than 1.0 will make the\nkernel proportionally smaller, producing 'sharper' output, at the cost of\npotentially producing more aliasing.\n\ntwine_width is the only one of the twining-related options which also\naffect a twining filter read from a file - apart from --twf_file itself,\nobviously. The twine_width is applied as a multiplicative factor to the\nfirst two parameters of each coefficient, so the default of 1.0 results\nin the filter geometry being unchanged.\n\nInput with low resolution is often insufficiently band-limited which will\nresult in artifacts in the output or become very blurred when you try to\ncounteract the artifacts with excessive blurring. There's little to be\ngained from scaling up anyway - the lost detail can't be regained.\nScaling up with a b-spline of degree two or higher is a good idea, but\nto see why this is so, a bit of explanation is needed. Most image viewers\nproduce up-scaled views by rendering the source image's pixels as small\nrectangles, producing a 'pixelated' view. A b-spline rendition is smooth,\nthough. This leads to the common misconception that the b-spline is\nsomehow 'blurred' compared to the 'pixelated' rendition. If the b-spline\nis made from properly prefiltered coefficients, there is no blurring:\nthe smoothness is just so much as to adequately represent the uncertainty\ncreated by sampling the ambient information. The 'pixelated' look shows\nyou the size of the pixels, but a lot of it's visual quality consists\nof artifacts: namely the distinct rectangular shapes of the 'pixels'\nwith discontinuities where one pixel borders on the next one. These\nartifacts are often the most prominent part of a 'pixelated' image,\nand users are often surprised that the seemingly 'blurred' image from\na b-spline rendition is easier to decipher than the 'pixelated' one.\nIn fact, if 'pixelation' is used to make faces unrecognizable and the\nsize of the 'pixels' is not chosen large enough, *smoothing* the\n'pixelated' image may reveal a recognizable face, because the information\nis no longer 'spoiled' by the high-frequency artifacts resulting from\nthe artifical rectangles in the pixelated area. Only if prefiltering\nis omitted blurring does indeed happen. envutil decouples the prefilter\nand spline degree, so you can observe the effects of picking each of\nthem individually.\n\nIf you don't get good results with twining and adequate twine_width,\nyou may be better off with one of the better OIIO interpolators, e.g.\nbicubic. If the signal is sufficiently band-limited, using a b-spline with\na degree of two or higher is a good idea: there are no more star-shaped\nartifacts, and for magnifying views, evaluating the spline yields a good\nresult without further processing. If the signal is not band-limited, but\nyou can live with slight blurring, a degree-2 b-spline with no prefiltering\n(--prefilter 0) is often a good compromise, especially if the resolution\nof the input is 'excessive', like the sensor data from very small sensors\nand very high resolution - and this very high resolution is not actually\nexploited (or usable) to provide fine detail. AFAICT, OIIO's spline-based\ninterpolators (e.g. bicubic) don't use prefiltering and therefore produce\nnoticeable blurring. Try a b-spline with proper prefiltering and no twining\n(--twine 0 -degree 3) to see the difference - you'll see no blurring, but if\nthe view magnifies and the signal isn't band-limited, you may see ringing\nartifacts.\n\n## --twine_sigma TWINE_SIGMA  use a truncated gaussian for the twining filter (default: don't)\n\nIf you don't pass --twine_sigma, envutil will use a simple box filter to\ncombine the result of supersampling into single output pixels values. If\nyou pass twine_sigma, the kernel will be derived from a gaussian with a sigma\nequivalent to twine_sigma times the half kernel width. This gives more weight\nto subsamples near the center of the pick-up.  If you pass -v, the filter\nis echoed to std::cout for inspection.\n\nYou can combine this parameter with automatic twining - the twine factor and\nthe twine width will be calculated automatically, then the gaussian is applied\nto the initially equal-weighted box filter. Keep in mind that applying gaussian\nweights will 'narrow' the filter, so you may need to pass a larger twine_width\nto counteract that effect. This may be a bit counterintuitive, because gaussians\nare commonly associated with blurring - but a gaussian kernel produces 'sharper'\noutput than a box filter. Also consider the next parameter which eliminates\nweights below a given threshold to save CPU time.\n\n## --twine_threshold TWINE_THRESHOLD  discard twining filter taps below this threshold\n\nIf you pass twine_sigma, marginal twining kernel values may become quite small\nand using them as filter taps makes no sense. Pass a threshold here to suppress\nkernel values below the threshold. This is mainly to reduce processing time.\nUse -v to display the kernel and see which kernel values 'survive' the thresholding.\nThis parameter makes no sense without --twine_sigma (see above): if all weights\nare equal, they'd either all be above or below the threshold.\n\nYou can combine this parameter with automatic twining - the twine factor and\nthe twine width will be calculated automatically, then the twine_sigma is applied,\nand finally the thresholding eliminates small weights.\n\nAfter thresholding, the weights are 'normalized' to produce a filter with 'unit\ngain' - you can see that all the weights add up to 1.0 precisely. Without the\nnormlization, just eliminating the sub-threshold taps would darken the output.\n\n## --twine_precise      project twining basis vectors to tangent plane\n\nThis parameter affects all twining filters, but it's effect is rarely\nnoticeable. To explain what this parameter does, a bit of explanation\nis needed. When envutil does a lookup from an environment, it starts out\nwith the target coordinate - the discrete coordinate where an output\npixel will appear. Next, it figures out a 3D 'ray' coordinate which\nencodes the direction where the lookup should 'look for' content. This\nobviously depends on the target projection and the orientation of the\nvirtual camera. With 'simple' interpolation - like the bilinear interpolation\nyou get by default with --twine 0, the next step is to find a 2D\ncoordinate into the source image which has the content corresponding\nto the given ray and interpolate a pixel value from the source image\nat that coordinate.\n\nThe more sophisticated lookup methods in envutil use 'derivatives':\nthey don't merely look at the single lookup point, but take into\naccount what happens when the lookup progresses to the next neighbours\nof the target point, both in the horizontal and the vertical: the\nneighbours have different corresponding rays and therefore different\ncorresponding 2D source image coordinates. A simple way to obtain the\nderivative is to subtract the current pick-up coordinate from those\ncorresponding to it's neighbours, yielding two 2D differences which\nencode an approximation of the derivative. But there is also a\ndifferent way: the difference can be formed between the 3D ray\ncoordinates, and this yields two 3D differences. These differences\nare (usually small) vectors from one point on the sphere to another.\nenvutil's twining filter uses these two vectors to place the pick-up\ncoordinates for each kernel coefficient: it multiplies the first one\nwith the 'x' value of the kernel coefficient, the second one with the\n'y' value', and adds the result to the current pickup ray coordinate,\nyielding a slightly altered ray which is used as sub-pick-up. But there\nis a problem here: The two small difference vectors are not perpendicular\nto the current pick-up ray, because they are not on it's tangent plane,\nbut instead connect two points on a spherical surface. If the difference\nis small, they are still almost parallel to the tangent plane and this\ndistinction is irrelevant, but to be very precise, and for larger\ndifferences, it's better to project the difference vectors onto the\ntangent plane and use the resulting vectors to calculate the sub-pick-up\nrays. Without this projection to the tangent plane, the pattern of\n3D ray coordinates is tilted slightly off the tangent plane, and a\nsub-pick-up with negative kernel x and y values is not at precisely\nthe same distance from the current pick-up location as one with positive\nx and y of equal magnitude. --twine_precise takes care of this problem\nand does the projection to the tangent plane. The calculation needs to\nbe done once per target pixel, so with increasing kernel size it does\nnot require additional computations. Most of the time the change which\nresults from this parameter is so minimal that it's not worth the extra\neffort, that's why it's not the default.\n\nSo what's the point of calculating the 'derivative' as a 3D vector, vs.\na 2D difference in source image coordinates? Suppose you have a sub-pick\nwhose ray is substantially different from the current pick-up location.\nWhat if it's projection to the source image plane lands outside the source\nimage, or on the opposite edge (which can happen e.g. with full\nspherical source images)? To deal with this situation, you'd need to add\ncode which recognizes and mends this problem, and you need code for each\ntype of projection to handle such issues properly. Because this is all\ninner-loop code and introduces conditionals, it's detrimental to performance,\nwhere you want conditional-free stencil code which is the same for each\ncoordinate. Calculating the 'derivatives' in 3D avoids this problem:\nonly the 3D ray coordinates of each sub-pick-up are projected to source\nimage coordinates and the current pick-up coordinate is never 'taken\nall the way' to the source image - the result pixel is a weighted sum\nof the sub-pick-ups. The sub-pick-ups either yield a pixel value or they\ndon't, the 2D difference never occurs and therefore doesn't produce any\nproblems. So we trade a few more cycles (for working in 3D) for a more\northogonal and branch-free construct, which suits SIMD processing and the\nfunctional paradigm, and performance is acceptable. With this setup, adding\nnew projections becomes easier - it's especially useful for source data\ncoming as several discrete images, like in a cubemap, where lookup schemes\nwhich have to encompass a 'piece' of source image run into trouble when\n'looking' at the edges of the cube and have to combine data from several\ncube faces. With twining this is not an issue.\n\n## --twine_density DENSITY increase tap count of an 'automatic' twining filter\n\n'automatic' twining uses a number of twining kernel coefficients which\nis deemed sufficient to produce an artifact-free result (or to keep the\nunavoidable artifacts so small that they won't be noticeable). You may\nwant to be 'more generous' and increase the number of kernel coefficients,\nand twine_density does that: if your twining kernel is generated\nautomatically, it multiplies the 'twine' value with this factor, rounds,\nand assigns the result to 'twine'.\n\n# Additional Technical Notes\n\nOne problem with cubemaps is that they are normally stored as concatenations of\nsix square images with precisely ninety degrees fov (field of view). This makes\naccess to pixels near the edges tricky - if the interpolator used for the purpose\nneeds support, marginal pixels don't have it all around, and it has to be gleaned\nfrom adjoining cube faces, reprojecting content to provide the support. envutil\nuses an internal representation (IR for short) of the cubemap which holds six\nsquare images with a fov slightly larger than ninety degrees, where the part\nexceeding the 'ninety degrees proper' cubeface image is augmented with pixels\nreprojected from adjoining cube faces. With this additional 'frame', interpolators\nneeding support can operate without need for special-casing access to marginal\npixels. The formation and use of the IR image is transparent, it's used automatically.\nThere are two parameters which influence the size of the 'support margin', namely\n--support_min and --tile_size - usually it's best to leave them at their defaults.\n\nTo access pixels in lat/lon environment maps which are marginal, envutil exploits the\ninherent periodicity of the lat/lon image - simple periodicity in the horizontal and\n'over-the-pole' periodicity in the vertical (that's reflecting in the vertical plus\nan offset of half the image's width in the horizontal). One can in fact generate an\nimage from a full spherical which is periodic vertically by cutting of the right\nhalf, rotating it by 180 degrees and pasting it to the bottom of the first half.\nlux does just that for it's 'spherical filter', so that the image pyramids used\ninternally can be created with geometrically correct down-scaling. envutil does\nnot use mip-mapping or other pyramid-like code itself (even though OIIO's texture\nsystem code does so), so this is merely a technical hint. If you use b-spline\ninterpolation with full-spherical source images, a specialized prefilter function\nis used which is correctly set up to handle both dimensions as periodic signals,\nproducing artifact-free evaluation near the poles.\n\nThe initial implementation of envutil offered use of OIIO's 'environment' and\n'texture' functions (passing --itp -1) - but the code ended up convoluted and\nperformance wasn't very good. So this branch ('streamline') is now coded to\nuse direct interpolation and twining only, and I think I'll merge that to main\nand not use OIIO's interpolation facilities - at least for the time being. \n\nAs an alternative to the antialiasing and interpolation provided by OIIO, envutil\noffers processing with b-spline interpolation and it's own antialiasing filter, using\na method which I call 'twining'. Both for OIIO-based lookup and for twining, the\nbeginning of the pixel pipeline receives not just one 3D directional 'ray' coordinate\npointing 'into' the environment, but three: the second and third ray are calculated\nto coincide with a target coordinate one discrete step toward the right or downwards,\nrespectively, in the *target* image. The pixel pipeline which receives these sets\nof three ray coordinates can glean the difference between the first ray and the\ntwo other two rays and adapt the lookup based on this additional information. For\nlookup, simple differencing produces an approximation of the derivatives, or,\nto interpret the difference differently, a pair of 3D vectors which can be used\nto construct a plane and place several lookup points on that plane, to combine\ntheir results to form the output as a weighted sum.\n\nFrom visual inspection of the results, envutil's use of OIIO's lookup produced\nquite soft images. OIIO's lookup is - with the settings envutil uses - conservative\nand makes an effort to avoid aliasing, erring on the side of caution. Of course I\ncan't rule out that my use of OIIO's lookup is not coded correctly, but I'm quite\nconfident that I've got it right. As of this writing, OIIO's lookup offers\nfunctions with SIMD parameter set, processing batches of coordinates. Internally,\nthough, the coordinates are processed one after the other (albeit with use of\nvertical SIMDization for the single-coordinate lookups). This is one of the reasons\nwhy this process isn't very fast. OIIO's method of inspecting the derivatives has\nthe advantage of being perfectly general, and the lookup can decide for each pixel\nwhether it needs to be interpolated or antialiased - and how much.\n\nIn contrast, using bilinear interpolation is very fast, but it's not adequate\nfor all situations - especially not if there are large differences in\nresolution between input and output. If the scale of the output is much smaller\nthan the input's, the use of 'twining' should provide adequate antialiasing.\nWhen scaling up, bilinear interpolation only produces visible artifacts if the\nscale change is very large (the typical 'star-shaped artifacts'), which makes\nlittle sense, because there's no gain to be had from scaling up by large\nfactors - the content won't improve. Up-scaling with OIIO's lookup uses bicubic\ninterpolation, which may be preferable. Ultimately it's up to the user to find\na suitable process by inspecting the output. envutil's 'twining' can cover a\ncertain range of input/output relations with a given parameter set, but for\nextreme distortions, it may be suboptimal. This should be analyzed in depth,\nfor now, it's just a promising new method which seems to work 'well enough'.\nWhen using b-splines 'better' than degree-1 (a.k.a. bilinear interpolation),\nupscaling should be artifact-free - apart from possible ringing artifacts if\nthe signal isn't band-limited to half the Nyquist frequency. The ringing\nartifacts can be avoided by omitting the prefilter, which will produce a\nsmoothed and bounded signal, but remove some high frequency content (if there\nis any).\n\nI think that twining offers a good compromise beween speed and quality. I'm sure\nI'm not the first one to think of this method, but I haven't done research to\nsee if I can find similar code 'out there'. What I am sure of is that my\nimplementation is fast due to the use of multithreaded horizontal SIMD code\nthrough the entire processing chain, so I think it's an attractive offer. I'd\nwelcome external evaluation of the results and a discussion of the methods;\nplease don't hesitate to open issues on the issue tracker if you'd like to\ndiscuss or report back! In my tests, images generated with twining seemed to\ncome out somewhat 'crisper' than renditions with OIIO's default mode, so they\nmay be preferable for photographic work, whereas OIIO's renditions make extra\nsure there is no aliasing, which is good for video output.\n\nThere seems to be ambiguity of what constitutes a 'correct' cube face image with\nninety degrees field of view. In envutil, I code so that each pixel is taken to\nrepresent a small square section of the image with constant colour. So the\n'ninety degrees proper' extends form the leftmost pixel's left margin to the\nrightmost pixel's right margin. Some cubemap formats provide images where the\ncenters of the marginal pixels coincide with the virtual cube's edges, repeating\neach edge in the image it joins up with in the cube. If you process such cubemaps\nwith envutil, pass --ctc 1 (which stands for center-to-center). Otherwise, there\nwill be subtle errors along the cube face edges which can easily go unnoticed.\nMake sure you figure out which 'flavour' your cubemaps are.\n\n# Style\n\nI use a coding style which avoids forward declarations, so at times it's\nbetter to read the code from bottom to top to follow the flow of control.\nMy code formatting is quite 'old-school', with white-space-separated\ntokens and curly braces in separate lines - and generally a lot of white\nspace. I think this style makes the code easier to grasp and also helps\nwith reading it.\n\nThe code itself is 'optimistic' - I don't make efforts to prevent errors\nfrom happening - if they happen, most of the time an exception will terminate\nthe program. I tend not to analyze parameters and trust the user to pass\nsensible ones.\n\nThe code is complex - it's template metacode, and control flow may be hard\nto grasp, due to the use of functional programming. Functional components\nfollow the pattern I have established in vspline and zimt: the functors\nhave an 'eval' method which takes a const reference for input and writes\noutput to another reference. The eval method itself is void. Usually\nI code it as a template to avoid a verbose signature - the types are\nimplicit from the functor's template arguments, and oftentimes the template\ncan be used for scalar and SIMDized arguments alike.\n\n# An Aside - Image Pyramids Reloaded\n\nImage pyramids are a popular method of providing multi-resolution\nrepresentations of images. The traditional method to generate an image\npyramid is to apply a low-pass filter to the image, then decimate it\nto obtain the next level. This level is again low-pass-filtered and\ndecimated, and this is repeated until only one or a few pixels are left.\n\nWith a scalable filter (like twining) there is an alternative approach,\nwhich I find promising and which has the potential to produce better\npyramids: You start out with a twining kernel with many coefficients\n(say, 16X16) and directly produce the first few pyramid levels by\nproducing a scaled-down output from the original (level-0) image,\nhalving the size with each rendition and at the same time doubling the\nkernel width (by passing a doubled 'twine_width' parameter). Depending\non the kernel, you may want to start with a twine_width less than one\nfor the first rendition and start doubling from that - e.g .125, .25 ...\nFinally, you reach a point where another doubling of the kernel width\nwould violate the sampling theorem (kernel coefficients further apart\nthan the source image's sampling step), and *only then* you start using\nthe next level of the pyramid as input, now keeping twine_width constant\nfor successive renditions, rather than carrying on with the doubling\nof the kernel width. By using a large kernel on a level ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkfjahnke%2Fenvutil","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkfjahnke%2Fenvutil","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkfjahnke%2Fenvutil/lists"}