OK, the burrito was great! Let's talk about what you'd need to do to fix the 
BMP reader to do the trick of forcing gray images to 1 channel. I think it's 
actually pretty simple.

https://github.com/OpenImageIO/oiio/blob/master/src/bmp.imageio/bmpinput.cpp#L98

Here's where much of the magic needs to go. In this clause, which handles the 
case of 8-bit images, I think you'll want a change like this:

    case 8:
        m_padded_scanline_size = (m_spec.width + 3) & ~3;
        if (!read_color_table())
            return false;
        // m_allgray is a new BMPInput data member, should be initialized to 
false
        m_allgray = color_table_is_all_gray();  // <-- you'll need to implement 
this method
        if (m_allgray)
            m_spec.nchannels = 1;   // make it look like a 1-channel image
        break;


and then in read_native_scanline(), at 
https://github.com/OpenImageIO/oiio/blob/master/src/bmp.imageio/bmpinput.cpp#L188
 :

    if (m_dib_header.bpp == 8) {
        if (m_allgray) {
            // Keep it as 1-channel image because all colors are gray
            for (unsigned int i = 0; i < scanline_bytes; ++i) {
                mscanline[i] = m_colortable[fscanline[i]].r;
            }
        } else {
            // Expand palette image into 3-channel RGB (existing code)
            for (unsigned int i = 0, j = 0; j < scanline_bytes; ++i, j += 3) {
                mscanline[j]     = m_colortable[fscanline[i]].r;
                mscanline[j + 1] = m_colortable[fscanline[i]].g;
                mscanline[j + 2] = m_colortable[fscanline[i]].b;
            }
        }
    }



> On Apr 5, 2021, at 7:18 PM, Larry Gritz <l...@larrygritz.com> wrote:
> 
> I think the TBB allocator is pretty good, though I believe its main goal is 
> to be scalable when many threads are asking from memory at once, which may be 
> at the cost of total memory footprint size.
> 
> We've liked jemalloc quite a bit, for a good compromise between speed and 
> memory size. That may be worth trying.
> 
> When you read the image, you should try this:
> 
>     ImageBuf buf(filename);
>     buf.read(0 /*submage*/, 0 /*miplevel*/,
>             0 /*chbegin*/, 1 /*chend*/,
>              false /*force*/);
> 
> Using the ImageBuf::read that lets you specifiy a channel range will at least 
> mean that the ImageBuf, and any subsequent calculation you do on it, will be 
> 1 channel, even if the underlying cache is 3 channels.
> 
> If you want to get tricky, we can keep the cache size down, too, with this 
> strategy:
> 
>     // Ask the cache for the spec of the file, but doctor to 1 channel
>     ImageSpec spec;
>     imagecache->get_imagespec(filename, spec);
>     spec.nchannels = 1;
>     // Allocate an ImageBuf with local memory exactly the right size, and 
> don't
>     // waste time zeroing out the pixels because we're about to write over 
> them.
>     ImageBuf buf(spec, ImageBuf::InitializePixels::Yes);
>     // Ask the image cache to copy the right channels
>     imagecache->get_pixels(filename /*or handle*/, 0 /*subimage*/, 0 
> /*miplevel*/,
>                            0, width, 0, height, 0, 1,
>                         0 /*chbegin*/, 1 /*chend*/,
>                            TypeUInt8 /*format*/,
>                            buf.localpixels(),  // <--- read into the buf's 
> memory
>                            AutoStride, AutoStride, AutoStride,
>                         0 /*cache_chbegin*/, 1 /*cache_chend*/);
> 
> Those last params to get_pixels tell the ImageCache that you only want to 
> cache the first channel of the file you're reading from!
> 
> So now in this iteration, we are doing image processing on one channels 
> (because of the ImageBuf), and we are caching only one channel (because of 
> those cache_chbegin/cache_chend args to get_pixels), but the I/O itself is 
> still kind of expanding the palette scanlines out to 3 channels before 
> copying and storing only one channel into the cache. So the cache *size* is 
> as good as it can be, and so is the I/O, and so is the image processing... 
> but there's a bit of useless copying going on. But maybe that's enough?
> 
> The only improvement to make on top of that is to fix bmpinput.cpp itself as 
> I described. I'll explain that in the next email, because my burrito delivery 
> just arrived.
> 
> 
> 
>> On Apr 5, 2021, at 6:49 PM, Phil Miller <philip.mil...@sri.com 
>> <mailto:philip.mil...@sri.com>> wrote:
>> 
>> Thanks! I greatly appreciate your replies.
>>  
>> I will put together a small scale test and try things out. So, what is the 
>> right/easy/fast way to convert the 3-channel RGB image that is loaded from 
>> the bmp file into a single channel image? (I want to stay with the 
>> underlying 8-bit per pixel format.) 
>>  
>> As for my use case, yes, my usage will be such that when I return to an 
>> image it will be  soon after it is loaded into the cache; and should almost 
>> always be before I will I have filled the cache with so many images it will 
>> be flushed from the cache. 
>>  
>> Regarding memory fragmentation, I have had bad experiences in the past with 
>> fragmentation, but my experience could be dated and no longer relevant for 
>> the current state of compilers/operating systems (with my primary platform 
>> being 64-bit windows 10 with Visual Studio 2019). Also, I am using the [TBB 
>> Scalable Memory 
>> Allocator](https://www.threadingbuildingblocks.org/docs/help/index.htm#tbb_userguide/Scalable_Memory_Allocator.html
>>  
>> <https://www.threadingbuildingblocks.org/docs/help/index.htm#tbb_userguide/Scalable_Memory_Allocator.html>)
>>  as a drop-in custom allocator injected at the link stage. But, I would 
>> appreciate hearing about other allocators that you would suggest. With my 
>> current tests, I am seeing a few hundred thousand page faults per second, 
>> and this concerns me as my full-up application will need to run for 10-30 
>> days.
>>  
>> Finally, I would be willing to modify the oiio bmp reader to support a 
>> single channel bmp file. I believe the common interpretation is to have a 
>> color palette with an identity lookup table (256 entries with values 0-255 
>> mapped to 0-255) is not really a hack – just not your normal 24-bit color 
>> bmp. If you point me in the right direction, I will try to make the change.
>>  
>> From: Oiio-dev <oiio-dev-boun...@lists.openimageio.org 
>> <mailto:oiio-dev-boun...@lists.openimageio.org>> On Behalf Of Larry Gritz
>> Sent: Monday, April 5, 2021 9:12 PM
>> To: OpenImageIO dev list <oiio-dev@lists.openimageio.org 
>> <mailto:oiio-dev@lists.openimageio.org>>
>> Subject: Re: [Oiio-dev] [EXTERNAL] Re: Reading single channel bmp
>>  
>> ImageCache may still be the right solution.
>>  
>> How frequently will you reuse the images, and how big is your cache compared 
>> to the image size?
>>  
>> Let's say that the total cache size you are willing to allocate (with 
>> ImageCache::attribute("max_memory_MB")) can hold N images. And let's also 
>> say that the access pattern is such that after using image A, you will tend 
>> to need to access n different images on average before returning to need A 
>> again.
>>  
>> If n >> N almost always, then the cache is probably not doing you any good, 
>> and you're paying for the overhead of IC without benefit.
>>  
>> But if with some frequency you return to image A soon enough that you have 
>> touched < n other images in the mean time, so that A is still in the cache, 
>> then you win. It doesn't need to happen all the time. But it's likely that 
>> reading an image from disk is MUCH more expensive than any of the processing 
>> you're doing on each image, so removing the need for even a small percentage 
>> of redundant reads is probably well worth it.
>>  
>> I think you are worrying needlessly about memory fragmentation. Modern 
>> memory allocators are pretty smart about this. You'll only get fragmentation 
>> if you have an alloc/free pattern where you ask for something big, return 
>> it, ask for a bunch of medium size things that get allocated from the chunk 
>> that was the original allocation, then ask for something big again and need 
>> new memory, and so on.
>>  
>> If you ask for something big, free it, then ask for another thing of the 
>> same size, then free it, etc. -- memory allocators are really good about 
>> that. Also, they tend to have different arenas for different size ranges, so 
>> your small allocations will never subdivide your very big allocations. If 
>> your default system allocator is being a pig, there are many other choices 
>> you can try that are meant to optimize exactly this kind of thing. Using a 
>> custom memory allocator  (I can give you some suggestions) that simply 
>> replaces malloc/free at the link stage without needing any changes to source 
>> code at all, is a much easier solution than tying yourself in knots worrying 
>> about things on the app side.
>>  
>> I'm also wondering if that n and N relationship we mentioned earlier is such 
>> that worrying about the grayscale trick makes a lot of difference. If it's 
>> right on the cusp, then using 1/3 the cache space could be very helpful, but 
>> if the access pattern is less sensitive, it may be not worth sweating.
>>  
>> I would try a small scale test of this without thinking about it too hard, 
>> and look at the ImageCache stats output to see how much redundant I/O is 
>> happening (compare bytes of I/O read total, versus bytes in all the unique 
>> images read), and also how many "tiles" are ever allocated versus how many 
>> unique images. If they aren't too out of whack, then just roll with it, 
>> you're already pretty efficient.
>>  
>> If you *really* think you're right on the edge of having this work well and 
>> are being thwarted only by the grayscale vs RGB thing, I can take a look at 
>> it for you and see if it's quick to implement what I suggested.
>>  
>>                 -- lg
>>  
>> 
>> 
>> On Apr 5, 2021, at 5:46 PM, Phil Miller <philip.mil...@sri.com 
>> <mailto:philip.mil...@sri.com>> wrote:
>>  
>> Thank you for your reply.
>>  
>> My access pattern is a combination of the ones you describe.
>>  
>> I will access all pixels in an image, but I may re-read an image multiple 
>> times before I am finished with it. So I thought the ImageCache would be an 
>> easy way to avoid the cost of the redundant reads. Granted this is overkill, 
>> since I could make my own, separate, whole-image, cache, but I would rather 
>> re-use the proven oiio ImageCache than debug my own. Plus, in the future, I 
>> may have a finer-grained use case where I am using tiled images or want to 
>> access different mipmap levels.
>>  
>> But, I will need to process all of the images, so there is no way the image 
>> cache can be big enough to hold all of them. What I would like to do is to 
>> have the ImageCache re-use the memory from an expired image rather than free 
>> the old one and alloc a new one. The cost I am concerned about is not the 
>> computation or time cost of the alloc/free cycle, but the memory 
>> fragmentation that may occur. Since I already have a 15 MB block of memory 
>> for an expired image, I would like to re-use that rather than free it and 
>> alloc a new one (that is the exact same size as the one I just freed). This 
>> way, the image reading part of the processing will (eventually) allocate 10 
>> GB of memory and no more memory allocation will be needed for reading the 
>> rest of the 1 TB of images that I have to process.
>>  
>>  
>> From: Oiio-dev <oiio-dev-boun...@lists.openimageio.org 
>> <mailto:oiio-dev-boun...@lists.openimageio.org>> On Behalf Of Larry Gritz
>> Sent: Monday, April 5, 2021 8:09 PM
>> To: OpenImageIO dev list <oiio-dev@lists.openimageio.org 
>> <mailto:oiio-dev@lists.openimageio.org>>
>> Subject: Re: [Oiio-dev] [EXTERNAL] Re: Reading single channel bmp
>>  
>> Whether ImageCache is a good idea depends on what the access pattern is.
>>  
>> The case that ImageCache is most helpful is for when your access pattern is 
>> visiting and revisiting the same image (out of many many images), and each 
>> time you need to visit the image, you only need to access a few of the 
>> pixels, more or less coherently, and then the next time you need pixels from 
>> that image, it's either likely to be in a short time and needing nearby 
>> pixels, or not for a long time until you need a far-away part of that image. 
>> Also, this whole scheme works best when the images are *tiled*, which BMPs 
>> are not. When the usage patterns align, ImageCache can be used, for example, 
>> for an offline renderer to access 2TB of texture data per frame, spread 
>> across 20,000 texture files, all without using more than a few GB in memory 
>> at any one time (and with maybe only 20% redundant disk reads).
>>  
>> If you are reading one image at a time, need all the pixels in the image 
>> before moving on to the next image, and then are done with that first  
>> image, then you should not be using ImageCache at all.
>>  
>> You can still use an ImageBuf if you want, bypassing the ImageCache, by 
>> making sure that when you call ImageBuf::read(), you pass the optional 
>> parameter force=true. But when you free one ImageBuf and make the next one, 
>> it will free and then re-allocate the memory. I assure you, although this 
>> constant alloc/free cycle seems wasteful, it will be a tiny fraction of the 
>> cost of the actual disk I/O and the compute necessary to decompress the 
>> pixel data and you will barely notice it.
>>  
>> Another route you could take -- again, assuming that all these images are 
>> the same size and you only need one in memory at a time -- is to not use 
>> ImageBuf either. Just allocate your own buffer of the right size, use 
>> ImageInput::read_image to get the pixels into it, process it, and then read 
>> the next image into the same memory buffer without ever freeing it.
>>  
>> ImageInput, ImageBuf, and ImageCache are different levels of abstraction, 
>> intended for different use cases.
>>  
>>  
>>  
>> On Apr 5, 2021, at 4:55 PM, Phil Miller <philip.mil...@sri.com 
>> <mailto:philip.mil...@sri.com>> wrote:
>>  
>> Well, our application is a little newer than horse-drawn wagons, and we do 
>> use bmp files from time to time. This particular usage is for an instrument 
>> that is outputting sensor data at scan locations using bmp images. Bmp 
>> images tend to be simple, easy to use, well supported on both windows and 
>> linux, and (still 😊) popular for certain applications. For my immediate use 
>> case, I am not in control of the format and am just given the images that we 
>> need to process. I have been able to use opencv to load the bmp images, but 
>> the opencv reader is doing a memory allocation for each image that I read. 
>> Consequently, I am churning through way too many memory allocations, as 
>> opencv is doing an alloc, read, and free for each image that I read. What I 
>> am trying to do is have an image cache that resuses images and avoids 
>> unnecessary allocations. So I was looking to see if oiio would be better.  
>>  
>> Since oiio considers bmp files to be an obsolete format (and I concur that 
>> there are many better image formats than bmp), let me ask a different 
>> question:
>> If I choose to store my images (and their pyramids/mipmaps) in a tif file, 
>> can I use oiio and its ImageCache to re-use a pre-existing memory block to 
>> read the pixel values into? In particular, suppose I am willing to use a 10 
>> GB image cache. After the cache has filled up and it needs to free an image 
>> before reading another one, will the cache reuse the memory block or free 
>> the existing one and allocate a new one? Does it matter, that is will it 
>> help, if all of my images are the same size? If I can get oiio to reduce the 
>> memory allocations, then I can write a pre-processing application to use 
>> opencv to convert the single channel bmp files to mipmapped tif files and 
>> then write the real image processing app using oiio.
>>  
>> Thanks for your time!
>>  
>> From: Larry Gritz <l...@larrygritz.com <mailto:l...@larrygritz.com>> 
>> Sent: Monday, April 5, 2021 6:53 PM
>> To: OpenImageIO dev list <oiio-dev@lists.openimageio.org 
>> <mailto:oiio-dev@lists.openimageio.org>>
>> Cc: Phil Miller <philip.mil...@sri.com <mailto:philip.mil...@sri.com>>
>> Subject: Re: [Oiio-dev] [EXTERNAL] Re: Reading single channel bmp
>>  
>> Yeah, sorry about that. I fixated so quickly on "is the texture 1->3 channel 
>> conversion somehow going awry" that I failed to notice that you said BMP, 
>> which is an unusual enough file format that there could be something 
>> particular to its reader.
>>  
>> And indeed, that is the case.
>>  
>> There's not really such a thing as a BMP "grayscale" image.
>>  
>> As I understand it, BMP files are either RGB[A] full color per pixel (24 or 
>> 32 bits), or 16 bits per pixel (where those 16 bits encode R, G, and B), or 
>> else are "palette" images. For palette images, the number of bits per pixel 
>> indicates the number of palette entries, e.g., 8 bits means 256 entries in 
>> the color palette, 4 bits means only 16 colors in the palette. But either 
>> way, the palette itself consists of entries of what they call an "RGBQUAD", 
>> so these palette images are reported by OIIO to be RGB images (OIIO hides 
>> the palette itself from you so that apps don't need to worry about it and 
>> can just ask for pixel colors).
>>  
>> You could make an 8-bit per pixel BMP appear to encode a grayscale image by 
>> making it a palette image in which all the palette entries coincidentally 
>> have their RGB triplets all have equal values. But that is just trickery -- 
>> it's still a color image, just a very, very gray-looking one because of the 
>> special choice of which colors are in the palette.
>>  
>> I could imagine some surgery of the BMP reader that would make it operate as 
>> follows: The palette is examined for whether every palette entry has the 
>> property that R==G==B, and if so, it could report the file as being a true 
>> 1-channel image and the palette conversion for pixels can just be result = 
>> palette[pixelval][0]. If any of the palette entries have R,G,B not all 
>> equal, then it reports back as if it were a RGB 3-channel image as before. 
>> This is kind of a hack -- it's taking that the BMP space says is a RGB image 
>> and lying to report it back as at 1-channel grayscale, just because "all the 
>> RGB colors in that image look gray enough to me". Still, it's a neat idea, 
>> and I would welcome a patch from anybody who would make this change, but I 
>> don't have time at the moment to do it myself.
>>  
>> I always thought that BMP was primarily for low-res old Windows images such 
>> as file icons and such, and other stuff from back when images were assembled 
>> by horse-drawn wagons. I'm a little surprised to hear that anybody is 
>> dealing with a MILLION bmp images at 4k resolution. Can I ask what is the 
>> application of this and whether it might be better to use a different file 
>> format? Maybe one that not only has a proper grayscale, but also has better 
>> compression methods than are supported by BMP?
>>  
>>                 -- lg
>>  
>> 
>> 
>> 
>> 
>> On Apr 5, 2021, at 3:11 PM, Larry Gritz <l...@larrygritz.com 
>> <mailto:l...@larrygritz.com>> wrote:
>>  
>> Oh, bmp specifically, I get it. Hang on, let me look at that case.
>> 
>> 
>> 
>> 
>> 
>> On April 5, 2021 2:53:56 p.m. PDT, Phil Miller <philip.mil...@sri.com 
>> <mailto:philip.mil...@sri.com>> wrote:
>> No, when I was debugging, the spec comes out with nchannels=3 instead of 1. 
>> Let me extract some sample reader code that I can share and I will get back 
>> to you.
>>  
>> From: Oiio-dev <oiio-dev-boun...@lists.openimageio.org 
>> <mailto:oiio-dev-boun...@lists.openimageio.org>> On Behalf Of Larry Gritz
>> Sent: Monday, April 5, 2021 2:58 PM
>> To: OpenImageIO dev list <oiio-dev@lists.openimageio.org 
>> <mailto:oiio-dev@lists.openimageio.org>>
>> Subject: [EXTERNAL] Re: [Oiio-dev] Reading single channel bmp
>>  
>> There is logic in the TextureSystem so that individual texture lookups will 
>> expand a 1-channel greyscale image to all 3 channels of a 3-channel lookup 
>> (versus using the "fill" color for the second two channels).
>>  
>> Even when using that TextureSystem option, it's only about what data value 
>> it copies into a user result for that individual texture lookup. It doesn't 
>> actually duplicate any image data in memory in the cache.
>>  
>> But there is nothing in ImageCache or ImageBuf, as far as I can tell, that 
>> does anything like that. An ImageBuf that references a 1-channel file should 
>> just report as 1 channel.
>>  
>> Are you doing something that indicates that it's actually replicating the 
>> data? Or are you just worried, based on this option of the texture system, 
>> that something weird might end up happening for an ordinary ImageBuf?
>>  
>> 
>> 
>> 
>> 
>> 
>> On Apr 5, 2021, at 11:30 AM, Phil Miller <philip.mil...@sri.com 
>> <mailto:philip.mil...@sri.com>> wrote:
>>  
>> I am having trouble using an ImageBuf with an ImageCache to read a single 
>> channel (grayscale/monochrome) bmp image as a single channel image. The spec 
>> in the internal oiio logic seems to always expand to 3 channels. I can use 
>> the force flag when I read it, but then that bypasses the cache. The images 
>> are fairly large (4k x 3k) and I need to read a million of them, so the 
>> difference between one and three channels is significant.
>>  
>> I appreciate any help and guidance.
>>  
>> Sincerely,
>> Phil
>> _______________________________________________
>> Oiio-dev mailing list
>> Oiio-dev@lists.openimageio.org <mailto:Oiio-dev@lists.openimageio.org>
>> http://lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org 
>> <https://urldefense.us/v3/__http:/lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org__;!!Nv3xtKNH_4uope0!xOYMMZQ_G451K0HfPRe1lqeW_Z4EG0Oep39UNfj0cKUGt0-jxrGFh-9CA29gUrlK$>
>>  
>> --
>> Larry Gritz
>> l...@larrygritz.com <mailto:l...@larrygritz.com>
>>  
>>  
>> 
>>  
>> 
>> -- 
>> Larry Gritz
>> l...@larrygritz.com <mailto:l...@larrygritz.com>
>> _______________________________________________
>> Oiio-dev mailing list
>> Oiio-dev@lists.openimageio.org <mailto:Oiio-dev@lists.openimageio.org>
>> http://lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org 
>> <https://urldefense.us/v3/__http:/lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org__;!!Nv3xtKNH_4uope0!0-H9hVMU1slRpUi7NepKlIBTy-jWTsIhbzrnS5Bn8mOuMUAKBqCUcUgc2CfVaPes$>
>>  
>> --
>> Larry Gritz
>> l...@larrygritz.com <mailto:l...@larrygritz.com>
>>  
>>  
>> 
>>  
>> _______________________________________________
>> Oiio-dev mailing list
>> Oiio-dev@lists.openimageio.org <mailto:Oiio-dev@lists.openimageio.org>
>> http://lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org 
>> <https://urldefense.us/v3/__http:/lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org__;!!Nv3xtKNH_4uope0!25_cHMQAGvgtRdtA8qrfhZei8-l42yZqB-KFY0uOwXbs-jaYYh-a74e_BAcev3tG$>
>>  
>> --
>> Larry Gritz
>> l...@larrygritz.com <mailto:l...@larrygritz.com>
>>  
>>  
>> 
>>  
>> _______________________________________________
>> Oiio-dev mailing list
>> Oiio-dev@lists.openimageio.org <mailto:Oiio-dev@lists.openimageio.org>
>> http://lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org 
>> <https://urldefense.us/v3/__http:/lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org__;!!Nv3xtKNH_4uope0!yNF9b4TAIGIMwps14vhz3fxBSAupg4kFzxuetkePBk2nlcH6IUwlXrrZL46wmwI-$>
>>  
>> --
>> Larry Gritz
>> l...@larrygritz.com <mailto:l...@larrygritz.com>
>>  
>>  
>> 
>>  
>> _______________________________________________
>> Oiio-dev mailing list
>> Oiio-dev@lists.openimageio.org <mailto:Oiio-dev@lists.openimageio.org>
>> http://lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org 
>> <http://lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org>
> --
> Larry Gritz
> l...@larrygritz.com <mailto:l...@larrygritz.com>
> 
> 
> 
> 
> _______________________________________________
> Oiio-dev mailing list
> Oiio-dev@lists.openimageio.org
> http://lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org

--
Larry Gritz
l...@larrygritz.com




_______________________________________________
Oiio-dev mailing list
Oiio-dev@lists.openimageio.org
http://lists.openimageio.org/listinfo.cgi/oiio-dev-openimageio.org

Reply via email to