|
| 1 | +import base64 |
1 | 2 | import os |
| 3 | +import uuid |
2 | 4 | from dataclasses import dataclass, field |
3 | | -from io import BytesIO |
4 | 5 | from pathlib import Path |
5 | 6 | from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Union |
6 | 7 |
|
|
18 | 19 |
|
19 | 20 | from .features import FeatureType |
20 | 21 |
|
| 22 | +if config.NIBABEL_AVAILABLE: |
| 23 | + import nibabel as nib |
| 24 | + |
| 25 | + class Nifti1ImageWrapper(nib.nifti1.Nifti1Image): |
| 26 | + """ |
| 27 | + A wrapper around nibabel's Nifti1Image to customize its representation. |
| 28 | + """ |
| 29 | + |
| 30 | + def __init__(self, nifti_image: nib.nifti1.Nifti1Image): |
| 31 | + super().__init__( |
| 32 | + dataobj=nifti_image.get_fdata(), |
| 33 | + affine=nifti_image.affine, |
| 34 | + header=nifti_image.header, |
| 35 | + extra=nifti_image.extra, |
| 36 | + file_map=nifti_image.file_map, |
| 37 | + dtype=nifti_image.get_data_dtype(), |
| 38 | + ) |
| 39 | + self.nifti_image = nifti_image |
| 40 | + |
| 41 | + def _repr_html_(self): |
| 42 | + bytes_ = self.nifti_image.to_bytes() |
| 43 | + b64 = base64.b64encode(bytes_).decode("utf-8") |
| 44 | + |
| 45 | + self.nifti_data_url = f"data:application/octet-stream;base64,{b64}" |
| 46 | + viewer_id = f"papaya-{uuid.uuid4().hex[:8]}" |
| 47 | + |
| 48 | + html = f""" |
| 49 | + <div id="{viewer_id}" style="width: 100%; height: 800px;"></div> |
| 50 | + <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/[email protected]/release/current/standard/papaya.css" /> |
| 51 | + <script src="https://cdn.jsdelivr.net/npm/[email protected]/release/current/standard/papaya.js"></script> |
| 52 | + <script type="text/javascript"> |
| 53 | + (function() {{ |
| 54 | + // Wait for Papaya to load |
| 55 | + function initPapaya() {{ |
| 56 | + if (typeof papaya === 'undefined' || typeof papaya.Container === 'undefined') {{ |
| 57 | + setTimeout(initPapaya, 100); |
| 58 | + return; |
| 59 | + }} |
| 60 | +
|
| 61 | + // Papaya loaded - manually initialize |
| 62 | + var params = {{}}; |
| 63 | + params["images"] = ["{self.nifti_data_url}"]; |
| 64 | + params["kioskMode"] = false; |
| 65 | + params["showControls"] = true; |
| 66 | +
|
| 67 | + // Manual initialization |
| 68 | + papaya.Container.startPapaya(); |
| 69 | + papaya.Container.addViewer("{viewer_id}", params); |
| 70 | + }} |
| 71 | +
|
| 72 | + initPapaya(); |
| 73 | + }})(); |
| 74 | + </script> |
| 75 | + """ |
| 76 | + return html |
| 77 | + |
21 | 78 |
|
22 | 79 | @dataclass |
23 | 80 | class Nifti: |
@@ -106,7 +163,7 @@ def encode_example(self, value: Union[str, bytes, bytearray, dict, "nib.Nifti1Im |
106 | 163 | f"A nifti sample should be a string, bytes, Path, nibabel image, or dict, but got {type(value)}." |
107 | 164 | ) |
108 | 165 |
|
109 | | - def decode_example(self, value: dict, token_per_repo_id=None) -> "nib.nifti1.Nifti1Image": |
| 166 | + def decode_example(self, value: dict, token_per_repo_id=None) -> "Nifti1ImageWrapper": |
110 | 167 | """Decode example NIfTI file into nibabel image object. |
111 | 168 |
|
112 | 169 | Args: |
@@ -165,11 +222,9 @@ def decode_example(self, value: dict, token_per_repo_id=None) -> "nib.nifti1.Nif |
165 | 222 | ): # gzip magic number, see https://stackoverflow.com/a/76055284/9534390 or "Magic number" on https://en.wikipedia.org/wiki/Gzip |
166 | 223 | bytes_ = gzip.decompress(bytes_) |
167 | 224 |
|
168 | | - bio = BytesIO(bytes_) |
169 | | - fh = nib.FileHolder(fileobj=bio) |
170 | | - nifti = nib.Nifti1Image.from_file_map({"header": fh, "image": fh}) |
| 225 | + nifti = nib.Nifti1Image.from_bytes(bytes_) |
171 | 226 |
|
172 | | - return nifti |
| 227 | + return Nifti1ImageWrapper(nifti) |
173 | 228 |
|
174 | 229 | def embed_storage(self, storage: pa.StructArray, token_per_repo_id=None) -> pa.StructArray: |
175 | 230 | """Embed NifTI files into the Arrow array. |
|
0 commit comments