Reading bitarray from disk has ordering flipped in groups of 8, how to fix?

On Julia 1.4.0-rc2 (I don’t think it’s a new behavior), I see the following:

versus the expected:

I’m reading in the image as follows:

io = open("bilevel_example.tif")
seek(io, 8)
data = BitArray(undef, 40160*8)

read!(io, data);

It appears that the bits are vertically flipped every 8 bits. The file was written and read on a little-endian machine. Is there any way to change this?

Here’s the file in question: Box

I believe this is coming from the least significant bit being in the leftmost position of each byte so I need to do a bit swap operation, which AFAIK there isn’t a dedicated Julia function for? (bswap swaps bytes)

Maybe you can copy parts of this code if you aren’t using master: add `bitreverse` function by JeffBezanson · Pull Request #34791 · JuliaLang/julia · GitHub

3 Likes

Not really familiar with .tif, but have you tried using Images.jl to read your image? They’re using FileIO.jl in the background which has explicit support for .tif files, maybe they do the flipping already?

Otherwise, maybe Base.ltoh can help you.

1 Like

I’m actually in the process of writing a pure-Julia TIFF reader/writer, which generalizes an earlier package that I created. See GitHub - tlnagy/TiffImages.jl: Pure-Julia TIFF I/O with a focus on correctness 🧐 and GitHub - tlnagy/OMETIFF.jl: I/O operations for OME-TIFF files in Julia

1 Like

This looks exactly like what I was looking for. I’m targeting 1.4, but I can copy that code into the package until it lands in stable.

Thanks @laborg, that code basically did what I want, I just had get rid of the bswap at the end since the byte order is correct (both little-endian).

const _mask1_uint128 = (UInt128(0x5555555555555555) << 64) | UInt128(0x5555555555555555)
const _mask2_uint128 = (UInt128(0x3333333333333333) << 64) | UInt128(0x3333333333333333)
const _mask4_uint128 = (UInt128(0x0f0f0f0f0f0f0f0f) << 64) | UInt128(0x0f0f0f0f0f0f0f0f)

function reversebits!(x::BitArray)
    n = length(x.chunks)
    for i in 1:n
        x.chunks[i] = reversebits(x.chunks[i])
    end
end

function reversebits(x::Base.BitInteger)
    z = unsigned(x)
    mask1 = _mask1_uint128 % typeof(z)
    mask2 = _mask2_uint128 % typeof(z)
    mask4 = _mask4_uint128 % typeof(z)
    z = ((z & mask1) << 1) | ((z >> 1) & mask1)
    z = ((z & mask2) << 2) | ((z >> 2) & mask2)
    z = ((z & mask4) << 4) | ((z >> 4) & mask4)
    return z                            # <----- got rid of the bswap here
end

seek(tf.io.io, 8)
data = BitArray(undef, 40160*8)

read!(tf.io.io, data);
reversebits!(data)

Gray.(reshape(data, 640, :)')