-
Notifications
You must be signed in to change notification settings - Fork 50
JPEG2000 codec support (attempt) #903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Just noticed that the |
ok, so I think the issue is the way xenium stores these in a multi-file pyramid structure - so this is liable to entail a bit more work, and I think the way I've been testing by dragging a particular image into Avivator won't be able to work as the browser wouldn't allow accessing these siblings like that. Maybe if the directory was dragged it'd be ok. |
I'm having a look at whether I can patch |
You should be able to pull the latest changes from main, where I recently changed the test framework. I think the hanging previously indicated test failures under the old framework |
Thanks @keller-mark, those tests are now passing (not including any new ones for these changes). I could do with checking on some other JP2K test data - the problems I have now seem to be to do with the way Xenium do multi-file TIFF, which is technically orthogonal to JP2K as I understand it, but it might be useful to combine those things in a PR since I think easy reading of Xenium is relevant to our common interests. When I've converted things to JP2K OME-TIFF with I guess maybe some of this discussion could be on image.sc or something. |
@xinaesthete If we're creating a |
I think Xenium data is important enough that it would be beneficial to have the support in Viv, especially if the decoding adapter code is non-trivial. Would it be possible to not activate the Decoder up-front, but instead either:
Ideally, then with tree-shaking, a consumer who does not need the JP2K support does not receive an increased bundle size. |
Edit: I understand now what you are saying. Then yes, I think testing with a single-file OME-TIFF that uses JP2K compression would be fine to validate that the Decoder works. Then the multi-file tiff issues can be addressed independently/orthogonally. |
Regarding multi-file tiff, I think that functionality is independent of the Decoder/decompression functionality. If there are issues with the Viv multi-file tiff support (or the issue that it is not being exposed via the Avivator demo), that could be its own PR. |
Yes - I don't disagree (although interesting to hear your phrasing on the state of I started out by just adding a similar codec handler in MDV, but then when it didn't quite go as easily as I hoped it was useful to test it on a simpler Avivator base, and as I say, it seems like loading xenium is in our common interest... but I started this PR more as a place to experiment with the implementation and hopefully get some more feedback on how it should work. Just noticed @keller-mark's comments as I was writing this - it seems like the xenium use of multi-tiff may apparently be somewhat standard, but the support viv has for this is designed for something different - and I agree that seems like a separate issue. I have slowly been starting to decipher this a bit better, but I think following through from the xenium spec I linked above is the best reference I have currently. I haven't thought a huge amount about the design or tested whether it is properly doing so, but the decoder potentially only lazily imports any heavy dependencies when it is actually invoked. Still - I don't know if it definitely wants to be there adding extra bloat to viv in general. Hope that's somewhat coherent - only intended to unplug my laptop after a couple of work social beers... |
ok, so now I'm using The viv rendering has an issue with byte-order or something getting mangled, so the results look very glitchy. I'll see if I can try a few more variations of precision. The image I've been testing with is |
It looks as though the decoder has a block for when // Convert from int32 to native size
int comp_num;
for (int y = 0; y < sizeAtDecompositionLevel.height; y++)
{
size_t lineStartPixel = y * sizeAtDecompositionLevel.width;
size_t lineStart = lineStartPixel * frameInfo_.componentCount * bytesPerPixel;
if(frameInfo_.componentCount == 1) {
int* pIn = (int*)&(image->comps[0].data[y * sizeAtDecompositionLevel.width]);
if(frameInfo_.bitsPerSample <= 8) {
unsigned char* pOut = (unsigned char*)&decoded_[lineStart];
for (size_t x = 0; x < sizeAtDecompositionLevel.width; x++) {
int val = pIn[x];;
pOut[x] = std::max(0, std::min(val, UCHAR_MAX));
}
} else {
if(frameInfo_.isSigned) {
short* pOut = (short*)&decoded_[lineStart];
for (size_t x = 0; x < sizeAtDecompositionLevel.width; x++) {
int val = pIn[x];;
pOut[x] = std::max(SHRT_MIN, std::min(val, SHRT_MAX));
}
} else {
unsigned short* pOut = (unsigned short*)&decoded_[lineStart];
for (size_t x = 0; x < sizeAtDecompositionLevel.width; x++) {
int val = pIn[x];;
pOut[x] = std::max(0, std::min(val, USHRT_MAX));
}
}
}
} else {
if(frameInfo_.bitsPerSample <= 8) {
uint8_t* pOut = &decoded_[lineStart];
for (size_t x = 0; x < sizeAtDecompositionLevel.width; x++) {
pOut[x*3+0] = image->comps[0].data[lineStartPixel + x];
pOut[x*3+1] = image->comps[1].data[lineStartPixel + x];
pOut[x*3+2] = image->comps[2].data[lineStartPixel + x];
}
} /*else {
// This should work but has not been tested yet
if(frameInfo.isSigned) {
short* pOut = (short*)&decoded_[lineStart] + c;
for (size_t x = 0; x < sizeAtDecompositionLevel.width; x++) {
int val = line->i32[x];
pOut[x * frameInfo.componentCount] = std::max(SHRT_MIN, std::min(val, SHRT_MAX));
}
} else {
unsigned short* pOut = (unsigned short*)&decoded_[lineStart] + c;
for (size_t x = 0; x < sizeAtDecompositionLevel.width; x++) {
int val = line->i32[x];
pOut[x * frameInfo.componentCount] = std::max(0, std::min(val, USHRT_MAX));
}
}
}*/
}
} So I'm going to try a build which includes that, fingers crossed this may help. edit: I get build errors when I uncomment that block, will investigate further / raise an issue upstream. edit2: |
ok, so it needs more testing with a few different dtypes but the main issue I was seeing with |
…compression codes for JP2K.
I'd like to be able to process float32 data as well - but this seems not to be implemented in OpenJpeg. https://github.com/jasper-software/jasper/ may be an alternative - the releases include WebAssembly editions, but target WASI & I think it'll need a bit of a tweak to work. I also noticed a pure-js |
Hi @xinaesthete,
|
Unfortunately in the current form I probably wouldn't merge it (I did merge equivalent changes in MDV, but with the intention of future revisions). It'd probably be worth checking out and looking at a few test images. I'm on vacation until September, but thought I'd respond. I don't think we necessarily need to aim for supporting It seems like it should be simple to read the first bytes of the file to determine which order to use, but I'm not sure how to do that in the context of a geotiff decoder. I looked at the lzw decoder implementation and it seems to have a bit-order flag hard coded, but I also believe I've tried lzw images with both orders and it is correct either way. So I'm not sure if some byte switching is happening elsewhere or... I'm not sure what the issue is. Two other questions
I don't think there are any other flags than setting the compression type and quality. Certainly couldn't see a way to specify endianness. I also raised another issue there that the quality seems to flatten out at a relatively low level, but it does get remarkably good image quality at pretty massive reductions in size. Quite a remarkable thing to behold, so I would recommend giving it a try, also maybe if you can find that issue and let them know if you do or don't reproduce that'd be good.
Yes, I agree, I would prefer it to be an npm package... I was in a bit of a rush before my holiday. Currently this messy state is what is making biome complain in CI for this PR, which having it installed from npm should fix. May be worth reaching out to Chris The state is that I made a few changes to update some dependencies etc so I could get it to build on arm64 Mac, with emitted TypeScript and configured as ESM. I also enabled a SIMD flag which was commented in a build script somewhere as not making a difference at the time. I don't think it makes a big difference, but it seemed maybe somewhat better. Aside from that, I made a somewhat dubious change to the byte order handling, but as commented this part is not really satisfactorily resolved as yet. Also notable that it doesn't change their test results either way iirc, so that'd be another thing to look at. Another comment while I'm here: I worked a bit on some hobby GIS stuff a few years ago with a version of openjpegjs that handles HTJ2K variant of the codec... It'd be potentially good if that was well supported by ome etc at some point. Faster encoding/decoding. Way out of scope for this PR of course, but worth bearing in mind for future evolution of tools. I'd like to be using that. |
Attempting to implement #774
Background
I've been trying to implement this and I'm running into some issues.
As suggested, I've registered a codec for
34712
with https://www.npmjs.com/package/@cornerstonejs/codec-openjpeg and it's been able to correctly process one sample I passed (which was very small, with only one channel & a few Z slices, fwiw), but not others.I'm trying to get Xenium images to render (from here, for example - I should track down where my smaller successful test image was from), but would also like to use JP2K more generally. This could be via support added to Viv, or by an app using it by registering the decoder itself - but it's useful for experimentation to have a version running in Avivator.
I've also been experimenting with using a local build of
openjpegjs
- and may also consider publishing a slightly more ergonomic/typed version as an npm module, but as of now I am not getting any different results in terms of actually properly parsing and rendering the data. I don't think the issue is within the codec, but wouldn't rule it out.One thing I would note as well is that I have found JP2K images I have converted with bftools don't render correctly in QuPath either - I would appreciate some good reference parameters if such are known.
The issue I currently have when trying xenium images is that
geotiff.js
throws an errorI've started debugging this - the part of Viv which calls it is in
packages/loaders/src/tiff/lib/indexers.ts
:Checklist