hydrus/hydrus/client/ClientImageHandling.py

222 lines
6.8 KiB
Python
Raw Normal View History

2020-05-20 21:36:02 +00:00
from functools import reduce
2019-05-08 21:06:42 +00:00
import numpy
2015-11-18 22:44:07 +00:00
import numpy.core.multiarray # important this comes before cv!
2020-05-20 21:36:02 +00:00
2015-11-18 22:44:07 +00:00
import cv2
2020-05-20 21:36:02 +00:00
from hydrus.client import ClientConstants as CC
2020-04-22 21:00:35 +00:00
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusImageHandling
from hydrus.core import HydrusGlobals as HG
2015-11-18 22:44:07 +00:00
2016-09-21 19:54:04 +00:00
cv_interpolation_enum_lookup = {}
cv_interpolation_enum_lookup[ CC.ZOOM_NEAREST ] = cv2.INTER_NEAREST
cv_interpolation_enum_lookup[ CC.ZOOM_LINEAR ] = cv2.INTER_LINEAR
cv_interpolation_enum_lookup[ CC.ZOOM_AREA ] = cv2.INTER_AREA
cv_interpolation_enum_lookup[ CC.ZOOM_CUBIC ] = cv2.INTER_CUBIC
cv_interpolation_enum_lookup[ CC.ZOOM_LANCZOS4 ] = cv2.INTER_LANCZOS4
2017-11-08 22:07:12 +00:00
2019-05-15 20:35:00 +00:00
def DiscardBlankPerceptualHashes( phashes ):
phashes = { phash for phash in phashes if HydrusData.Get64BitHammingDistance( phash, CC.BLANK_PHASH ) > 4 }
return phashes
2019-05-08 21:06:42 +00:00
def GenerateNumPyImage( path, mime ):
2015-11-18 22:44:07 +00:00
2019-05-08 21:06:42 +00:00
force_pil = HG.client_controller.new_options.GetBoolean( 'load_images_with_pil' )
2015-11-18 22:44:07 +00:00
2019-05-08 21:06:42 +00:00
return HydrusImageHandling.GenerateNumPyImage( path, mime, force_pil = force_pil )
2015-11-18 22:44:07 +00:00
2017-08-23 21:34:25 +00:00
def GenerateShapePerceptualHashes( path, mime ):
2015-11-18 22:44:07 +00:00
2019-07-17 22:10:19 +00:00
if HG.phash_generation_report_mode:
HydrusData.ShowText( 'phash generation: loading image' )
2019-05-08 21:06:42 +00:00
numpy_image = GenerateNumPyImage( path, mime )
2015-11-18 22:44:07 +00:00
2019-07-17 22:10:19 +00:00
if HG.phash_generation_report_mode:
HydrusData.ShowText( 'phash generation: image shape: {}'.format( numpy_image.shape ) )
2015-11-18 22:44:07 +00:00
( y, x, depth ) = numpy_image.shape
if depth == 4:
2017-05-10 21:33:58 +00:00
# doing this on 10000x10000 pngs eats ram like mad
2019-05-08 21:06:42 +00:00
target_resolution = HydrusImageHandling.GetThumbnailResolution( ( x, y ), ( 1024, 1024 ) )
numpy_image = HydrusImageHandling.ResizeNumPyImage( numpy_image, target_resolution )
2017-05-10 21:33:58 +00:00
( y, x, depth ) = numpy_image.shape
2015-11-18 22:44:07 +00:00
# create weight and transform numpy_image to greyscale
numpy_alpha = numpy_image[ :, :, 3 ]
2016-08-24 18:36:56 +00:00
numpy_alpha_float = numpy_alpha / 255.0
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
numpy_image_bgr = numpy_image[ :, :, :3 ]
2015-11-18 22:44:07 +00:00
2016-11-16 20:21:43 +00:00
numpy_image_gray_bare = cv2.cvtColor( numpy_image_bgr, cv2.COLOR_RGB2GRAY )
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
# create a white greyscale canvas
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
white = numpy.ones( ( y, x ) ) * 255.0
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
# paste the grayscale image onto the white canvas using: pixel * alpha + white * ( 1 - alpha )
2015-11-18 22:44:07 +00:00
2016-08-24 18:36:56 +00:00
numpy_image_gray = numpy.uint8( ( numpy_image_gray_bare * numpy_alpha_float ) + ( white * ( numpy.ones( ( y, x ) ) - numpy_alpha_float ) ) )
2015-11-18 22:44:07 +00:00
else:
2016-11-16 20:21:43 +00:00
numpy_image_gray = cv2.cvtColor( numpy_image, cv2.COLOR_RGB2GRAY )
2015-11-18 22:44:07 +00:00
2019-07-17 22:10:19 +00:00
if HG.phash_generation_report_mode:
HydrusData.ShowText( 'phash generation: grey image shape: {}'.format( numpy_image_gray.shape ) )
2015-11-18 22:44:07 +00:00
numpy_image_tiny = cv2.resize( numpy_image_gray, ( 32, 32 ), interpolation = cv2.INTER_AREA )
2019-07-17 22:10:19 +00:00
if HG.phash_generation_report_mode:
HydrusData.ShowText( 'phash generation: tiny image shape: {}'.format( numpy_image_tiny.shape ) )
2015-11-18 22:44:07 +00:00
# convert to float and calc dct
numpy_image_tiny_float = numpy.float32( numpy_image_tiny )
2019-07-17 22:10:19 +00:00
if HG.phash_generation_report_mode:
HydrusData.ShowText( 'phash generation: tiny float image shape: {}'.format( numpy_image_tiny_float.shape ) )
HydrusData.ShowText( 'phash generation: generating dct' )
2015-11-18 22:44:07 +00:00
dct = cv2.dct( numpy_image_tiny_float )
# take top left 8x8 of dct
dct_88 = dct[:8,:8]
2017-01-18 22:52:39 +00:00
# get median of dct
# exclude [0,0], which represents flat colour
# this [0,0] exclusion is apparently important for mean, but maybe it ain't so important for median--w/e
2015-11-18 22:44:07 +00:00
2017-01-18 22:52:39 +00:00
# old mean code
# mask = numpy.ones( ( 8, 8 ) )
# mask[0,0] = 0
# average = numpy.average( dct_88, weights = mask )
2015-11-18 22:44:07 +00:00
2017-01-18 22:52:39 +00:00
median = numpy.median( dct_88.reshape( 64 )[1:] )
2015-11-18 22:44:07 +00:00
2019-07-17 22:10:19 +00:00
if HG.phash_generation_report_mode:
HydrusData.ShowText( 'phash generation: median: {}'.format( median ) )
2017-01-18 22:52:39 +00:00
# make a monochromatic, 64-bit hash of whether the entry is above or below the median
2015-11-18 22:44:07 +00:00
2017-01-18 22:52:39 +00:00
dct_88_boolean = dct_88 > median
2019-07-17 22:10:19 +00:00
if HG.phash_generation_report_mode:
HydrusData.ShowText( 'phash generation: collapsing bytes' )
2017-01-18 22:52:39 +00:00
# convert TTTFTFTF to 11101010 by repeatedly shifting answer and adding 0 or 1
2017-03-08 23:23:12 +00:00
# you can even go ( a << 1 ) + b and leave out the initial param on the reduce call as bools act like ints for this
2017-01-18 22:52:39 +00:00
# but let's not go crazy for another two nanoseconds
2017-03-08 23:23:12 +00:00
def collapse_bools_to_binary_uint( a, b ):
return ( a << 1 ) + int( b )
2015-11-18 22:44:07 +00:00
2019-01-09 22:59:03 +00:00
list_of_bytes = []
2015-11-18 22:44:07 +00:00
for i in range( 8 ):
2017-01-18 22:52:39 +00:00
'''
# old way of doing it, which compared value to median every time
2015-11-18 22:44:07 +00:00
byte = 0
for j in range( 8 ):
byte <<= 1 # shift byte one left
value = dct_88[i,j]
2017-01-18 22:52:39 +00:00
if value > median:
byte |= 1
2015-11-18 22:44:07 +00:00
2017-01-18 22:52:39 +00:00
'''
2019-01-09 22:59:03 +00:00
# this is a 0-255 int
2017-01-18 22:52:39 +00:00
byte = reduce( collapse_bools_to_binary_uint, dct_88_boolean[i], 0 )
2015-11-18 22:44:07 +00:00
2019-01-09 22:59:03 +00:00
list_of_bytes.append( byte )
2015-11-18 22:44:07 +00:00
2019-01-09 22:59:03 +00:00
phash = bytes( list_of_bytes ) # this works!
2016-11-30 20:24:17 +00:00
2019-07-17 22:10:19 +00:00
if HG.phash_generation_report_mode:
HydrusData.ShowText( 'phash generation: phash: {}'.format( phash.hex() ) )
2017-01-18 22:52:39 +00:00
# now discard the blank hash, which is 1000000... and not useful
phashes = set()
phashes.add( phash )
2019-05-08 21:06:42 +00:00
phashes = DiscardBlankPerceptualHashes( phashes )
2015-11-18 22:44:07 +00:00
2019-07-17 22:10:19 +00:00
if HG.phash_generation_report_mode:
HydrusData.ShowText( 'phash generation: final phashes: {}'.format( len( phashes ) ) )
2015-11-18 22:44:07 +00:00
# we good
2016-11-30 20:24:17 +00:00
return phashes
2016-09-21 19:54:04 +00:00
2019-05-08 21:06:42 +00:00
def ResizeNumPyImageForMediaViewer( mime, numpy_image, target_resolution ):
2016-09-21 19:54:04 +00:00
2019-04-03 22:45:57 +00:00
( target_width, target_height ) = target_resolution
2017-12-06 22:06:56 +00:00
new_options = HG.client_controller.new_options
2016-09-21 19:54:04 +00:00
( scale_up_quality, scale_down_quality ) = new_options.GetMediaZoomQuality( mime )
2021-05-12 20:49:20 +00:00
( image_height, image_width, depth ) = numpy_image.shape
2016-09-21 19:54:04 +00:00
2019-04-03 22:45:57 +00:00
if ( target_width, target_height ) == ( image_height, image_width ):
2016-09-21 19:54:04 +00:00
return numpy_image
else:
2021-05-12 20:49:20 +00:00
if target_width > image_width or target_height > image_height:
2016-09-21 19:54:04 +00:00
interpolation = cv_interpolation_enum_lookup[ scale_up_quality ]
else:
interpolation = cv_interpolation_enum_lookup[ scale_down_quality ]
2019-04-03 22:45:57 +00:00
return cv2.resize( numpy_image, ( target_width, target_height ), interpolation = interpolation )
2016-09-21 19:54:04 +00:00
2017-01-18 22:52:39 +00:00