{"id":17113068,"url":"https://github.com/emmt/scientificcameras.jl","last_synced_at":"2025-04-09T20:33:47.299Z","repository":{"id":71510245,"uuid":"106105596","full_name":"emmt/ScientificCameras.jl","owner":"emmt","description":"A Julia infrastructure for scientific cameras","archived":false,"fork":false,"pushed_at":"2021-07-05T21:33:14.000Z","size":66,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-23T22:34:49.580Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emmt.png","metadata":{"files":{"readme":"README.md","changelog":"NEWS.md","contributing":null,"funding":null,"license":"LICENSE.md","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}},"created_at":"2017-10-07T14:49:47.000Z","updated_at":"2023-09-21T07:49:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"355984eb-7707-4c49-a57a-9b3cdabe5bb5","html_url":"https://github.com/emmt/ScientificCameras.jl","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FScientificCameras.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FScientificCameras.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FScientificCameras.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FScientificCameras.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emmt","download_url":"https://codeload.github.com/emmt/ScientificCameras.jl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248107871,"owners_count":21049024,"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","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":[],"created_at":"2024-10-14T17:02:46.111Z","updated_at":"2025-04-09T20:33:47.278Z","avatar_url":"https://github.com/emmt.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A Julia infrastructure for scientific cameras\n\nThe `ScientificCameras` package provides an infrastructure to interface\nscientific cameras with [`Julia`](http://julialang.org/).  This infrastructure\nis an attempt to unify the use of interfaced cameras in Julia and is used by\n[`Phoenix.jl`](https://github.com/emmt/Phoenix.jl) and\n[`AndorCameras.jl`](https://github.com/emmt/AndorCameras.jl) packages.\n\n\n## Table of contents\n\n* [Typical usage (for end-users).](#typical-usage)\n* [Implementing a concrete interface (for developers).](#implementing-a-concrete-interface)\n* [Installation of the package.](#installation)\n\n\n## Typical usage\n\nTo explain the usage of the methods provided by `ScientificCameras`, it is best\nto present typical examples of the different stages involved in acquisition of\nimages with a camera.\n\n\n### Connection and configuration\n\nYou must first import the methods from the general module (with pixel formats)\nand a specific camera model, say `SomeCameraModel`, from a specific frame\ngrabber module, say `SomeFrameGrabber`:\n\n```julia\nusing ScientificCameras                  # import all methods of main module\nusing ScientificCameras.PixelFormats     # import all pixel formats\nimport SomeFrameGrabber: SomeCameraModel # import a specific camera model\n```\n\nSecond, you create an instance of the camera and connect it to the hardware by:\n\n```julia\ncam = open(SomeCameraModel)\n```\n\nThird, you configure the camera:\n\n```julia\nresetroi!(cam) # use full sensor area and no sub-sampling\nfullwidth, fullheight = getfullsize(cam) # get the full sensor size\nsetspeed!(cam, 100, 0.005) # 100 frames per second, 5ms of exposure\nsetgain!(cam, 1.0) # set the gain of the analog to digital conversion\nsetbias!(cam, 0.0) # set the bias of the analog to digital conversion\nsetgamma!(cam, 1.0) # set the gamma correction of the analog to digital conversion\nsetpixelformat!(cam, Monochrome{8}) # set the pixel format to monochrome 8 bits\n```\n\nNote that you may choose different settings (for instance, a smaller ROI) and\nthat not all these settings may be available for the considered camera.  To\nfigure out the current settings, you can use `get*` methods.  For instance,\n`getspeed(cam)` yields the current number of frames per second and exposure\ntime.  In general, the `set*!(cam, ...)` methods may be only able to\napproximately set the requested value(s) (*e.g.* because of rounding, of\nhardware limitations, *etc.*), it is therefore a good practice to check actual\nvalues by calling the corresponding `get*(cam)` methods.  However, when a given\nsetting is not implemented or when the settings are grossly wrong, the\n`set*!(cam, ...)` methods shall throw a specific exception such as\n`ScientificCameras.NotImplementedException` for unimplemented features (so that\nyou can specifically catch it).\n\n\n### Region of interest\n\nThe region of interest (ROI) is defined by 6 values:\n\n- `xsub`, `ysub` the horizontal and vertical dimensions of the macro-pixels\n  in pixels;\n\n- `xoff`, `yoff` the horizontal and vertical offsets of the ROI in pixels\n  relative to the sensor area;\n\n- `width` and `height` the horizontal and vertical dimensions of the ROI in\n  macro-pixels.\n\nDepending on the camera model, *macro-pixels* can be larger pixels made of\n`xsub` by `yxsub` sensor pixels (known as *binning*) or single sensor pixels\ntaken every `xsub` by `yxsub` sensor pixels (known as *subsampling*).  Some\nhardware may impose restrictions such as `xoff` and `yoff` being multiple of\n`xsub` and `yxsub` respectively.  These kind of restrictions cannot be\ncompensated by the software.\n\nIn the following example, we first get the current ROI settings, then modify it\nto have no subsampling/rebinning and a centered ROI of half the sensor\ndimensions and finally apply it:\n\n```julia\nroi = getroi(cam) # retrieve current ROI\nroi.xsub = 1\nroi.ysub = 1\nroi.xoff = div(getfullwidth(cam),4)\nroi.yoff = div(getfullheight(cam),4)\nroi.width = div(getfullwidth(cam),2)\nroi.height = div(getfullheight(cam),2)\nsetroi!(cam, roi) # apply the new settings\n```\n\nThe same result is obtained with:\n\n```julia\nfullwidth, fullheight = getfullsize(cam)\nsetroi!(cam, 1, 1, div(fullwidth(cam),4), div(fullheight,4),\n        div(fullwidth(cam),4), div(fullheight,4))\n```\n\n\n### Reading a given number of images\n\nAssuming you have a connected and configured camera instance, say `cam`, it is\ntime to read images.  Reading a given number of images amounts to calling\n`read`.  For instance, to read a single image:\n\n```julia\nimg = read(cam, UInt8)\n```\n\nyields a 2D Julia array whose element type is `UInt8` (see below for more\ndetails).  Reading a given number of images is done by something like:\n\n```julia\nimgs = read(cam, UInt8, 10)\n```\n\nwhich reads 10 images of element type `UInt8` and return them as a vector of\nimages.  Each image is a Julia array whose dimensions are those of the chosen\nregion of interest (ROI).  It is however possible that the first dimension (the\n*width*) be different to that of the ROI to accommodate for different sizes for\nthe pixel format used by the camera and the chosen element type.\n\nIn the `read` call, the element type of the result is optional.  If omitted, it\nis given by `getcapturebitstype(cam)`.\n\n\n### Continuous acquisition\n\nAnother way to acquire images is to process them as they arrive.  Assuming you\nhave connected and configured your camera, continuous acquisition is done by a\nloop like:\n\n```julia\nstart(cam, UInt16, 4) # start continuous acquisition with 4 cyclic buffers\nfor num in 1:100\n    img, ticks = wait(cam, Inf) # wait for next frame (waiting forever)\n    ... # process the captured image `img`\n    release(cam) # image buffer is again available for acquisition\nend\nabort(cam) # abort acquisition and exit the loop\n```\n\nThe `start` method iniates continuous acquisition, its arguments are the\nelement type of the captured images (optional as for the `read` method) and the\nnumber of capture buffers to use.  The `wait` method waits for the next frame\nfrom the specified camera but not longer than a given number of seconds, it\nreturns the next image and its timestamp (in seconds).  After processing of the\ncaptured image, the `release` method should be called to reuse the associated\nressources for subsequant acquisitions.  The `stop` (or `abort`) method must be\ncalled to terminate continuous acquisition (the former stops acquisition after\ncompletion of the current image while the latter stops acquisition\nimmediately).\n\nFor real-time applications, it is important to avoid that new ressources be\nallocated in the acquisition loop.  This explains the structure of the\ncontinuous acquisition and processing loop above: ressources are allocated\nbefore entering the loop (in particular by the `start` method), they are\nrecycled by the `release` method inside the loop and are eventually freed at\nthe end of the loop.  For the same reasons, it must not be assumed (unless\nexplicitly stated by the documentation related to a given camera / frame\ngrabber) that the captured image returned by the `wait` method is a new array:\nto avoid resources allocation, the same image (or a limited number of images)\nmay be recycled by the acquisition loop.\n\n\n### Closing the camera\n\nWhen the camera is no longer needed, you may close it to disconnect it from the\nhardware and release related resources.  This is as simple as:\n\n```julia\nclose(cam)\n```\n\nIn practice, this is even more simpler as you can avoid calling the `close`\nmethod.  Indeed any serious concrete implementations should take care of\nreleasing resources when the camera instance is no longer referenced and\neventually finalized by Julia's garbage collector.  It may be necessary to\nclose the camera to disconnect it from the hardware so that it is immediately\navailable for some other purposes.\n\n\n### Simple processing of a sequence of images\n\nThe `ScientificCameras` package provides a simple method to process a sequence\nof images:\n\n```julia\nprocessimages(cam, num, proc, state;\n              skip=0, timeout=sec, truncate=false) -\u003e state, cnt\n```\n\nprocesses `num` images from camera `cam` by calling the function `proc` as\nfollows:\n\n```julia\nstate = proc(state, img, ticks, cnt)\n```\n\nto process each image, here `img`, and update `state` (`ticks` is the timestamp\nin seconds of the captured image `img` and `cnt` is the current image number,\nstarting at 1 for the first one).\n\nThe final state and the actual number of processed images are returned (the\nlatter can be smaller than `num` if a timeout occured and keyword `truncate` is\n`true`.\n\nThe keywords `skip` (default 0), `timeout` and `truncate` (default `false`) may\nbe used and have the same meaning as for the `read` method.\n\nFor instance:\n\n```julia\nroi = getroi(cam)\ndims = (roi.width, roi.height)\nsum, cnt = processimages(cam, num, (sum, img, args...) -\u003e (sum .+= img; sum),\n                         zeros(dims))\n```\n\nyields `sum` the sum of a number of images and `cnt` the actual number of\nimages (which can onky be `num` in this example).\n\nThe methods `mean` and `stat` are extended to compute the sample mean of sample\nmeand and standard deviation of a sequence of images:\n\n```julia\navg = mean(cam, num)\navg, rms, cnt = stat(cam, num)\n```\n\n\n### Pixel formats\n\n`ScientificCameras.PixelFormat{N}` is the super-type of the various pixel\nformats and is parameterized by `N` the number of bits per pixel.  In order to\navoid prefixing pixel formats by ``ScientificCameras.`, you may add:\n\n```julia\nusing ScientificCameras.PixelFormats\n```\n\nto your code, as `using ScientificCameras` only imports public methods defined\nby the package (no types).  In what follows, it is assumed that\n`ScientificCameras.PixelFormats` has been imported with `using` as shown above.\n\nActual pixel formats are concrete sub-types of `PixelFormat{N}`.  The type\nhierarchy is:\n\n```\nPixelFormat{N} (abstract)\n |- Monochrome{N} (concrete)\n `- ColorFormat{N} (abstract)\n     |- RGB{N} (concrete)\n     |- BGR{N} (concrete)\n     |- XRGB{N} (concrete)\n     |- XBGR{N} (concrete)\n     |- RGBX{N} (concrete)\n     |- BGRX{N} (concrete)\n     |- BayerFormat{N} (abstract)\n     |   |- BayerRGGB{N} (concrete)\n     |   |- BayerGRBG{N} (concrete)\n     |   |- BayerBGGR{N} (concrete)\n     |   `- BayerBGGR{N} (concrete)\n     `- YUV422 (concrete)\n   (etc.)\n```\n\nNote that *concrete* types (the leaves of the above tree) are all singletons.\nThis system forbids to have concrete definitions which provide an equivalent\nJulia *bits*, that is *plain data*, type.  This is not really an isssue since\npixel formats are just meant to describe the pixel format used by a camera, not\nto provide Julia equivalent bits types.  To get the equivalent bits type (when\nit exists), call:\n\n```julia\nequivalentbitstype(format)\n```\n\nwhich returns `Void` when there is no possible exact equivalence.\n\n\n### Choosing the pixel format\n\nThere are two pixel formats: one corresponding to the data sent by the camera\nand the other corresponding to the pixels in the captured images.  To retrieve\nthe pixel format of the camera, just do:\n\n```julia\nfmt = getpixelformat(cam)\n```\n\nTo change the pixel format of the camera `cam` to `fmt`, do:\n\n```julia\nsetpixelformat!(cam, fmt)\n```\n\nFor instance:\n\n```julia\nusing ScientificCameras.PixelFormats\nsetpixelformat!(cam, Monochrome{10})\n```\n\nto select monochrome pixels encoded on 10 bits.  Not all hardware support\ndifferent pixel formats.\n\nBecause not all pixel formats are exactly representable by a Julia bits type,\nthe type of the elements of the Julia arrays used as image buffers has also to\nbe taken into account.  When capturing images, the type, say `T`, of the\nelements of the Julia arrays used as image buffers may be specified as follows:\n\n```julia\nimgs = read(cam, T, n)\n```\n\nfor sequential acquisition of `n` images, or:\n\n```julia\nstart(cam, T, n)\n```\n\nfor continuous acquisition using `n` image buffers and capturing images as\nregular Julia 2D array whose element type is `T`.\n\nIf the type `T` of the array elements is not specified, the method\n`getcapturebitstype(cam)` is used to find an equivalence Julia bits type.  If\nthere are no equivalent bits types, `getcapturebitstype(cam)` yields `UInt8`\n(*i.e.* image buffers are stored as 2D byte arrays in Julia whose first\ndimension is set so as to store the binary data of a single line of the\ncaptured image with possible padding, and whose second dimension is the number\nof lines in the captured image).\n\nTo avoid unpacking pixel values, it is advisable to choose a Julia bits type\nfor the captured images which is close or, better, exactly equivalent to the\ncamera pixel format.\n\nWhen reading an image or a sequence of images, `read` accepts a number of\nkeywords:\n\n* Use keyword `skip` to specify a number of images to skip.\n\n* Use keyword `timeout` to specify the maximum amount of time (in seconds) to\n  wait for the acquisition of each image.  If acquisition of any image takes\n  longer than this time, a `ScientificCameras.TimeoutError` is thrown unless\n  keyword `truncate` is `true` (see below).   The default timeout depends on the\n  exposure time and acquisition frame rate (see [`defaulttimeout`](@ref)).\n\n* When reading a sequence of images, keyword `truncate` may be set `true` to\n  print a warning and return a truncated sequence instead of throwing an\n  exception in case of timeout.\n\n* Keyword `quiet` can be set `true` to suppress the printing of warning\n  messages (see above).\n\n\n\n## Implementing a concrete interface\n\nExamples of concrete implementations of the interface are given by the\n[`Phoenix.jl`](https://github.com/emmt/Phoenix.jl) and\n[`AndorCameras.jl`](https://github.com/emmt/AndorCameras.jl) packages.\n\nThe `ScientificCameras` package mostly provides an infrastructure for concrete\ninterfaces to cameras.  To be callable (without throwing a\n`ScientificCameras.NotImplementedException`), most methods must be extended for\ntypes of camera derived from the abstract `ScientificCameras.ScientificCamera`\ntype.  The `ScientificCameras` package however handles the many different\npossible signatures of these methods and takes care of properly converting the\narguments, so that it is generally sufficient to extend a single version of\neach method.\n\nAssuming `Camera` is such a type (*i.e.*, `Camera \u003c: ScientificCamera`),\ntypically:\n\n```julia\n# Import all methods such that they can be extended and some types\n# (only methods are exported by ScientificCameras).\nimportall ScientificCameras\nimport ScientificCameras: ScientificCamera, ROI\nusing ScientificCameras.PixelFormats\n\n# Re-export the public interface of the ScientificCameras module.\nScientificCameras.@exportpublicinterface\n\nfunction open(::Type{Camera}, args...; kwds...)\n    cam = ... # create camera instance\n    ...       # setup camera and open connection\n    return cam\nend\n\nfunction close(::Type{Camera}; kwds...)\n    ... # release resources\nend\n\ngetfullwidth(cam::Camera) = cam.fullwidth\ngetfullheight(cam::Camera) = cam.fullheight\n\ngetroi(cam::Camera) =\n    (cam.xoff, cam.yoff, cam.width, cam.height)\n\nfunction setroi!(cam::Camera, roi::ROI)\n    checkroi(cam, roi)\n    ...\n    return getroi(cam)\nend\n```\n\nA complete interface would extend the following methods:\n\n- `open` for creating an instance of the camera connected to the hardware.\n- `close` for disconnecting a camera from the hardware.\n- `start`, `wait`, `release`, `stop` and `abort` for continuous acquisition.\n- `getfullwidth`, `getfullheight` for getting the full size of the sensor.\n- `getroi` and `setroi!` for the region of interest.\n- `getpixelformat`, `setpixelformat!`, `supportedpixelformats` and\n  `getcapturebitstype` for the pixel format.\n- `getspeed`, `setspeed!` and `checkspeed` for the frame rate and exposure\n  time.\n- `getgain` and `setgain!` for the gain of the analog to digital conversion.\n- `getbias` and `setbias!` for the bias of the analog to digital conversion.\n- `getgamma` and `setgamma!` for the gamma correction of the analog to digital\n  conversion.\n\nDefault implementations are provided by `ScientificCameras` for the following\nmethods:\n- `read` for reading a given number of images.\n\n## Installation\n\nThe easiest way to install `ScientificCameras` is via the Julia registry\n[`EmmtRegistry`](https://github.com/emmt/EmmtRegistry):\n\n```julia\nusing Pkg\npkg\"registry add https://github.com/emmt/EmmtRegistry\"\npkg\"add ScientificCameras\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femmt%2Fscientificcameras.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femmt%2Fscientificcameras.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femmt%2Fscientificcameras.jl/lists"}