-
Notifications
You must be signed in to change notification settings - Fork 4
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
Fits thumbnails #91
base: master
Are you sure you want to change the base?
Fits thumbnails #91
Changes from all commits
eb45b5b
1d72cfd
21e5275
0b93144
0284e8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -228,18 +228,6 @@ def make_maskable_buffer(self, buf_height, buf_width): | |
|
||
return Image.from_array(arr) | ||
|
||
def try_as_pil(self): | ||
""" | ||
Attempt to convert this mode into a PIL image mode string. | ||
|
||
Returns | ||
------- | ||
A PIL image mode string, or None if there is no exact counterpart. | ||
""" | ||
if self == ImageMode.F16x3: | ||
return None | ||
return self.value | ||
|
||
|
||
def _wcs_to_parity_sign(wcs): | ||
h = wcs.to_header() | ||
|
@@ -857,6 +845,7 @@ def asarray(self): | |
def aspil(self): | ||
"""Obtain the image data as :class:`PIL.Image.Image`. | ||
|
||
|
||
Returns | ||
------- | ||
If the image was loaded as a PIL image, the underlying object will be | ||
|
@@ -868,12 +857,34 @@ def aspil(self): | |
""" | ||
if self._pil is not None: | ||
return self._pil | ||
if self.mode.try_as_pil() is None: | ||
if self.mode not in (ImageMode.RGB, ImageMode.RGBA): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I think that it's probably better to preserve the original semantics here. PIL does support non-RGB(A) image modes, as listed here, and I think the most sensible behavior for this function is for it to succeed if the data array can be expressed as one of those modes. Also, I forgot to mention this before, but as always, deleting the |
||
raise Exception( | ||
f"Toasty image with mode {self.mode} cannot be converted to PIL" | ||
) | ||
|
||
return pil_image.fromarray(self._array) | ||
|
||
def coerce_into_pil(self, pixel_cut_low, pixel_cut_high): | ||
"""Coerce the image data into a :class:`PIL.Image.Image` by converting | ||
the data into an ``uint8`` RGB(A) array. | ||
|
||
Parameters | ||
---------- | ||
pixel_cut_low : number | ||
An value used to stretch the pixel values to the new ``uint8`` | ||
range (0 - 255). | ||
pixel_cut_high : number | ||
An value used to stretch the pixel values to the new ``uint8`` | ||
range (0 - 255). | ||
""" | ||
array = np.copy(self._array) | ||
array[..., :3] = (array[..., :3] - pixel_cut_low) / ( | ||
pixel_cut_high - pixel_cut_low | ||
) * 255 + 0.5 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, now that I think about this part more, I think it will do the wrong thing in some important circumstances. First. we should always use But second, if Third, if the data array is two-dimensional — which is going to be the most common usage — the But fourth, I think we also need to think about whether this function should return either RGB or RGBA, or choose one. It it can return RGBA, we'd need to initialize the alpha channel appropriately, setting it to zero for NaNs and the like. But WWT thumbnails must be delivered as RGB JPEGs. So for the proximate use case, we need to drop down the alpha channel eventually. So, synthesizing all of that, I think that this function should actually explicitly be |
||
|
||
array = np.uint8(np.clip(array, 0, 255)) | ||
return pil_image.fromarray(array) | ||
|
||
@property | ||
def mode(self): | ||
return self._mode | ||
|
@@ -1167,9 +1178,7 @@ def is_completely_masked(self): | |
elif self.mode == ImageMode.RGBA: | ||
return np.all(i[..., 3] == 0) | ||
else: | ||
raise Exception( | ||
f"unhandled mode `{self.mode}` in is_completely_masked" | ||
) | ||
raise Exception(f"unhandled mode `{self.mode}` in is_completely_masked") | ||
|
||
def save( | ||
self, path_or_stream, format=None, mode=None, min_value=None, max_value=None | ||
|
@@ -1205,7 +1214,7 @@ def save( | |
if format in PIL_RGB_FORMATS and mode is None: | ||
mode = ImageMode.RGB | ||
if mode is not None: | ||
pil_image = pil_image.convert(mode.try_as_pil()) | ||
pil_image = pil_image.convert(mode.value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little wary of dropping the check here since there are ImageMode values that are not valid inputs to |
||
pil_image.save(path_or_stream, format=PIL_FORMATS[format]) | ||
elif format == "npy": | ||
np.save(path_or_stream, self.asarray()) | ||
|
@@ -1239,26 +1248,29 @@ def save( | |
overwrite=True, | ||
) | ||
|
||
def make_thumbnail_bitmap(self): | ||
def make_thumbnail_bitmap(self, pixel_cut_low=None, pixel_cut_high=None): | ||
"""Create a thumbnail bitmap from the image. | ||
|
||
Parameters | ||
---------- | ||
pixel_cut_low : number or ``None`` (the default) | ||
An optional value used to stretch the pixel values to the new range | ||
as defined by pixel_cut_low and pixel_cut_high. Only used if the | ||
image was not loaded as a PIL image, and must be used together with | ||
pixel_cut_high. | ||
pixel_cut_high : number or ``None`` (the default) | ||
An optional value used to stretch the pixel values to the new range | ||
as defined by pixel_cut_low and pixel_cut_high. Only used if the | ||
image was not loaded as a PIL image, and must be used together with | ||
pixel_cut_low. | ||
|
||
Returns | ||
------- | ||
An RGB :class:`PIL.Image.Image` representing a thumbnail of the input | ||
image. WWT thumbnails are 96 pixels wide and 45 pixels tall and should | ||
be saved in JPEG format. | ||
|
||
""" | ||
if self.mode in ( | ||
ImageMode.U8, | ||
ImageMode.I16, | ||
ImageMode.I32, | ||
ImageMode.F32, | ||
ImageMode.F64, | ||
ImageMode.F16x3, | ||
): | ||
raise Exception("cannot thumbnail-ify non-RGB Image") | ||
|
||
THUMB_SHAPE = (96, 45) | ||
THUMB_ASPECT = THUMB_SHAPE[0] / THUMB_SHAPE[1] | ||
|
||
|
@@ -1281,6 +1293,15 @@ def make_thumbnail_bitmap(self): | |
try: | ||
pil_image.MAX_IMAGE_PIXELS = None | ||
thumb = self.aspil().crop(crop_box) | ||
except: | ||
if pixel_cut_low is None or pixel_cut_high is None: | ||
raise Exception( | ||
( | ||
"Need both pixel_cut_low and pixel_cut_high parameters" | ||
"to be able to coerce Toasty image into PIL format." | ||
) | ||
) | ||
thumb = self.coerce_into_pil(pixel_cut_low, pixel_cut_high).crop(crop_box) | ||
finally: | ||
pil_image.MAX_IMAGE_PIXELS = old_max | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stylistically, I like to have blank lines both before and after indenting constructs such as
for
,if,
etc.