diff --git a/gui/KCC.ui b/gui/KCC.ui
index c7ed2017..c6bab3c2 100644
--- a/gui/KCC.ui
+++ b/gui/KCC.ui
@@ -6,7 +6,7 @@
0
0
- 450
+ 481
400
@@ -242,7 +242,7 @@
5
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -277,13 +277,13 @@
-
- 200
+ 300
1
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -489,13 +489,13 @@
QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}
- QAbstractItemView::NoSelection
+ QAbstractItemView::SelectionMode::NoSelection
- QAbstractItemView::ScrollPerPixel
+ QAbstractItemView::ScrollMode::ScrollPerPixel
- QAbstractItemView::ScrollPerPixel
+ QAbstractItemView::ScrollMode::ScrollPerPixel
@@ -516,7 +516,7 @@
false
- Qt::AlignJustify|Qt::AlignVCenter
+ Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter
diff --git a/kindlecomicconverter/KCC_rc.py b/kindlecomicconverter/KCC_rc.py
index f7b740d6..cf6e015a 100644
--- a/kindlecomicconverter/KCC_rc.py
+++ b/kindlecomicconverter/KCC_rc.py
@@ -1,6 +1,6 @@
# Resource object code (Python 3)
# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.5.2
+# Created by: The Resource Compiler for Qt version 6.6.3
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore
@@ -11476,49 +11476,49 @@
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x07\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\xac\x00\x00\x00\x00\x00\x01\x00\x02&\xd7\
-\x00\x00\x01\x88;p\xbcB\
+\x00\x00\x01\x90(\xef\xc4\x03\
\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x02{q\
-\x00\x00\x01\x88;p\xbcB\
+\x00\x00\x01\x90(\xef\xc4\x00\
\x00\x00\x01\xd6\x00\x00\x00\x00\x00\x01\x00\x02Qv\
-\x00\x00\x01\x88;p\xbcB\
+\x00\x00\x01\x90(\xef\xc3\xff\
\x00\x00\x01\xc2\x00\x00\x00\x00\x00\x01\x00\x02F\x13\
-\x00\x00\x01\x89\x89D9.\
+\x00\x00\x01\x90(\xef\xc4\x01\
\x00\x00\x00X\x00\x02\x00\x00\x00\x03\x00\x00\x00\x0c\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x01(\x97\
-\x00\x00\x01\x88;p\xbcB\
+\x00\x00\x01\x90(\xef\xc4\x03\
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x01\x1d\x90\
-\x00\x00\x01\x88;p\xbcB\
+\x00\x00\x01\x90(\xef\xc4\x02\
\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x011\xef\
-\x00\x00\x01\x88;p\xbcB\
+\x00\x00\x01\x90(\xef\xc4\x04\
\x00\x00\x00X\x00\x02\x00\x00\x00\x03\x00\x00\x00\x10\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x02.\x00\x00\x00\x00\x00\x01\x00\x02\xad\xbd\
-\x00\x00\x01\x88;p\xbcJ\
+\x00\x00\x01\x90(\xef\xc4!\
\x00\x00\x02\x00\x00\x00\x00\x00\x00\x01\x00\x02\x97\xc0\
-\x00\x00\x01\x88;p\xbcI\
+\x00\x00\x01\x90(\xef\xc4\x1d\
\x00\x00\x02\x16\x00\x00\x00\x00\x00\x01\x00\x02\xa1\x1d\
-\x00\x00\x01\x88;p\xbcI\
+\x00\x00\x01\x90(\xef\xc4\x19\
\x00\x00\x00X\x00\x02\x00\x00\x00\x07\x00\x00\x00\x14\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01\x08\x00\x00\x00\x00\x00\x01\x00\x01H\x9b\
-\x00\x00\x01\x88;p\xbcJ\
+\x00\x00\x01\x90(\xef\xc4\x22\
\x00\x00\x01\x1e\x00\x00\x00\x00\x00\x01\x00\x01qC\
-\x00\x00\x01\x88;p\xbcI\
+\x00\x00\x01\x90(\xef\xc4\x1c\
\x00\x00\x01\x80\x00\x00\x00\x00\x00\x01\x00\x01\xca\x17\
-\x00\x00\x01\x88;p\xbcI\
+\x00\x00\x01\x90(\xef\xc4\x1e\
\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x01\x84\xd0\
-\x00\x00\x01\x88;p\xbcH\
+\x00\x00\x01\x90(\xef\xc4\x18\
\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x01\x00\x01D<\
-\x00\x00\x01\x88;p\xbcF\
+\x00\x00\x01\x90(\xef\xc4\x0e\
\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x017\xd3\
-\x00\x00\x01\x88;p\xbcH\
+\x00\x00\x01\x90(\xef\xc4\x17\
\x00\x00\x01@\x00\x00\x00\x00\x00\x01\x00\x01z\x9a\
-\x00\x00\x01\x88;p\xbcH\
+\x00\x00\x01\x90(\xef\xc4\x18\
\x00\x00\x00X\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1c\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00h\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x88;p\xbcH\
+\x00\x00\x01\x90(\xef\xc4\x16\
"
def qInitResources():
diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py
index 4c8cf3cb..4800d0b8 100644
--- a/kindlecomicconverter/KCC_ui.py
+++ b/kindlecomicconverter/KCC_ui.py
@@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'KCC.ui'
##
-## Created by: Qt User Interface Compiler version 6.5.2
+## Created by: Qt User Interface Compiler version 6.6.3
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -26,7 +26,7 @@ class Ui_mainWindow(object):
def setupUi(self, mainWindow):
if not mainWindow.objectName():
mainWindow.setObjectName(u"mainWindow")
- mainWindow.resize(450, 400)
+ mainWindow.resize(481, 400)
icon = QIcon()
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Normal, QIcon.Off)
mainWindow.setWindowIcon(icon)
@@ -139,7 +139,7 @@ def setupUi(self, mainWindow):
self.gammaSlider.setObjectName(u"gammaSlider")
self.gammaSlider.setMaximum(250)
self.gammaSlider.setSingleStep(5)
- self.gammaSlider.setOrientation(Qt.Horizontal)
+ self.gammaSlider.setOrientation(Qt.Orientation.Horizontal)
self.horizontalLayout_2.addWidget(self.gammaSlider)
@@ -159,9 +159,9 @@ def setupUi(self, mainWindow):
self.croppingPowerSlider = QSlider(self.croppingWidget)
self.croppingPowerSlider.setObjectName(u"croppingPowerSlider")
- self.croppingPowerSlider.setMaximum(200)
+ self.croppingPowerSlider.setMaximum(300)
self.croppingPowerSlider.setSingleStep(1)
- self.croppingPowerSlider.setOrientation(Qt.Horizontal)
+ self.croppingPowerSlider.setOrientation(Qt.Orientation.Horizontal)
self.horizontalLayout_3.addWidget(self.croppingPowerSlider)
@@ -170,7 +170,7 @@ def setupUi(self, mainWindow):
self.buttonWidget = QWidget(self.centralWidget)
self.buttonWidget.setObjectName(u"buttonWidget")
- sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
@@ -267,9 +267,9 @@ def setupUi(self, mainWindow):
self.jobList = QListWidget(self.centralWidget)
self.jobList.setObjectName(u"jobList")
self.jobList.setStyleSheet(u"QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}")
- self.jobList.setSelectionMode(QAbstractItemView.NoSelection)
- self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
- self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
+ self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
+ self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
+ self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
@@ -278,7 +278,7 @@ def setupUi(self, mainWindow):
self.progressBar.setMinimumSize(QSize(0, 30))
self.progressBar.setFont(font)
self.progressBar.setVisible(False)
- self.progressBar.setAlignment(Qt.AlignJustify|Qt.AlignVCenter)
+ self.progressBar.setAlignment(Qt.AlignmentFlag.AlignJustify|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
@@ -290,7 +290,7 @@ def setupUi(self, mainWindow):
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
self.hLabel = QLabel(self.customWidget)
self.hLabel.setObjectName(u"hLabel")
- sizePolicy1 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
diff --git a/kindlecomicconverter/KCC_ui_editor.py b/kindlecomicconverter/KCC_ui_editor.py
index 6729adcb..051b92b6 100644
--- a/kindlecomicconverter/KCC_ui_editor.py
+++ b/kindlecomicconverter/KCC_ui_editor.py
@@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'MetaEditor.ui'
##
-## Created by: Qt User Interface Compiler version 6.5.2
+## Created by: Qt User Interface Compiler version 6.6.3
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -117,7 +117,7 @@ def setupUi(self, editorDialog):
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.statusLabel = QLabel(self.optionWidget)
self.statusLabel.setObjectName(u"statusLabel")
- sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.statusLabel.sizePolicy().hasHeightForWidth())
diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py
index ce826353..41469ff7 100755
--- a/kindlecomicconverter/image.py
+++ b/kindlecomicconverter/image.py
@@ -23,6 +23,7 @@
import mozjpeg_lossless_optimization
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
from .shared import md5Checksum
+from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
AUTO_CROP_THRESHOLD = 0.015
@@ -342,20 +343,6 @@ def resize_method(self):
else:
return Image.Resampling.LANCZOS
- def getBoundingBox(self, tmptmg):
- min_margin = [int(0.005 * i + 0.5) for i in tmptmg.size]
- max_margin = [int(0.1 * i + 0.5) for i in tmptmg.size]
- bbox = tmptmg.getbbox()
- bbox = (
- max(0, min(max_margin[0], bbox[0] - min_margin[0])),
- max(0, min(max_margin[1], bbox[1] - min_margin[1])),
- min(tmptmg.size[0],
- max(tmptmg.size[0] - max_margin[0], bbox[2] + min_margin[0])),
- min(tmptmg.size[1],
- max(tmptmg.size[1] - max_margin[1], bbox[3] + min_margin[1])),
- )
- return bbox
-
def maybeCrop(self, box, minimum):
box_area = (box[2] - box[0]) * (box[3] - box[1])
image_area = self.image.size[0] * self.image.size[1]
@@ -363,26 +350,16 @@ def maybeCrop(self, box, minimum):
self.image = self.image.crop(box)
def cropPageNumber(self, power, minimum):
- if self.fill != 'white':
- tmptmg = self.image.convert(mode='L')
- else:
- tmptmg = ImageOps.invert(self.image.convert(mode='L'))
- tmptmg = tmptmg.point(lambda x: x and 255)
- tmptmg = tmptmg.filter(ImageFilter.MinFilter(size=3))
- tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=5))
- tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
- if tmptmg.getbbox():
- self.maybeCrop(tmptmg.getbbox(), minimum)
+ bbox = get_bbox_crop_margin_page_number(self.image, power, self.fill)
+
+ if bbox:
+ self.maybeCrop(bbox, minimum)
def cropMargin(self, power, minimum):
- if self.fill != 'white':
- tmptmg = self.image.convert(mode='L')
- else:
- tmptmg = ImageOps.invert(self.image.convert(mode='L'))
- tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=3))
- tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
- if tmptmg.getbbox():
- self.maybeCrop(self.getBoundingBox(tmptmg), minimum)
+ bbox = get_bbox_crop_margin(self.image, power, self.fill)
+
+ if bbox:
+ self.maybeCrop(bbox, minimum)
class Cover:
diff --git a/kindlecomicconverter/page_number_crop_alg.py b/kindlecomicconverter/page_number_crop_alg.py
new file mode 100644
index 00000000..fd95100a
--- /dev/null
+++ b/kindlecomicconverter/page_number_crop_alg.py
@@ -0,0 +1,215 @@
+from PIL import ImageOps, ImageFilter
+import numpy as np
+
+'''
+Some assupmptions on the page number sizes
+We assume that the size of the number (including all digits) is between
+'min_shape_size_tolerated_size' and 'max_shape_size_tolerated_size' relative to the image size.
+We assume the distance between the digit is no more than 'max_dist_size' (x,y), and no more than 3 digits.
+'''
+max_shape_size_tolerated_size = (0.015*3, 0.02) # percent
+min_shape_size_tolerated_size = (0.003, 0.006) # percent
+window_h_size = max_shape_size_tolerated_size[1]*1.25 # percent
+max_dist_size = (0.01, 0.002) # percent
+
+
+'''
+E-reader screen real-estate is an important resource.
+More available screensize means more details can be better seen, especially text.
+Text is one of the most important elements that need to be clearly readable on e-readers,
+which mostly are smaller devices where the need to zoom is unwanted.
+
+By cropping the page number on the bottom of the page, 2%-5% of the page height can be regained
+that allows us to upscale the image even more.
+- Most of the times the screen height is the limiting factor in upscaling, rather than its width.
+
+ Parameters:
+ img (PIL image): A PIL image.
+ power (float): The power to 'chop' through pixels matching the background. Values in range[0,3].
+ background_color (string): 'white' for white background, anything else for black.
+ Returns:
+ bbox (4-tuple, left|top|right|bot): The tightest bounding box calculated after trying to remove the bottom page number. Returns None if couldnt find anything satisfactory
+'''
+def get_bbox_crop_margin_page_number(img, power=1, background_color='white'):
+ if img.mode != 'L':
+ img = ImageOps.grayscale(img)
+
+ if background_color != 'white':
+ img = ImageOps.invert(img)
+
+ '''
+ Autocontrast: due to some threshold values, it's important that the blacks will be blacks and white will be whites.
+ Box/MeanFilter: Allows us to reduce noise like bad a page scan or compression artifacts.
+ Note: MedianFilter works better in my experience, but takes 2x-3x longer to perform.
+ '''
+ img = ImageOps.autocontrast(img, 1).filter(ImageFilter.BoxBlur(1))
+
+ '''
+ The 'power' parameters determines the threshold. The higher the power, the more "force" it can crop through black pixels (in case of white background)
+ and the lower the power, more sensitive to black pixels.
+ '''
+ threshold = threshold_from_power(power)
+ bw_img = img.point(lambda p: 255 if p <= threshold else 0)
+ bw_bbox = bw_img.getbbox()
+
+ if not bw_bbox: # bbox cannot be found in case that the entire resulted image is black.
+ return None
+
+ left, top_y_pos, right, bot_y_pos = bw_bbox
+
+ '''
+ We inspect the lower bottom part of the image where we suspect might be a page number.
+ We assume that page number consist of 1 to 3 digits and the total min and max size of the number
+ is between 'min_shape_size_tolerated_size' and 'max_shape_size_tolerated_size'.
+ '''
+ window_h = int(img.size[1] * window_h_size)
+ img_part = img.crop((left,bot_y_pos-window_h, right, bot_y_pos))
+
+ '''
+ We detect related-pixels by proximity, with max distance defined in 'max_dist_size'.
+ Related pixels (in x axis) for each image-row are then merged to boxes with adjacent rows (in y axis)
+ to form bounding boxes of the detected objects (which one of them could be the page number).
+ '''
+ img_part_mat = np.array(img_part)
+ window_groups = []
+ for i in range(img_part.size[1]):
+ row_groups = [(g[0], g[1], i, i) for g in group_pixels(img_part_mat[i], img.size[0]*max_dist_size[0], threshold)]
+ window_groups.extend(row_groups)
+
+ window_groups = np.array(window_groups)
+
+ boxes = merge_boxes(window_groups, (img.size[0]*max_dist_size[0], img.size[1]*max_dist_size[1]))
+ '''
+ We assume that the lowest part of the image that has black pixels on is the page number.
+ In case that there are more than one detected object in the loewst part, we assume that one of them is probably
+ manga-content and shouldn't be cropped.
+ '''
+ # filter all small objects
+ boxes = list(filter(lambda box: box[1]-box[0] >= img.size[0]*min_shape_size_tolerated_size[0]
+ and box[3]-box[2] >= img.size[1]*min_shape_size_tolerated_size[1], boxes))
+ lowest_boxes = list(filter(lambda box: box[3] == window_h-1, boxes))
+
+ min_y_of_lowest_boxes = 0
+ if len(lowest_boxes) > 0:
+ min_y_of_lowest_boxes = np.min(np.array(lowest_boxes)[:,2])
+
+ boxes_in_same_y_range = list(filter(lambda box: box[3] >= min_y_of_lowest_boxes, boxes))
+
+ max_shape_size_tolerated = (img.size[0] * max_shape_size_tolerated_size[0],
+ max(img.size[1] *max_shape_size_tolerated_size[1], 3))
+
+ should_force_crop = (
+ len(boxes_in_same_y_range) == 1
+ and (boxes_in_same_y_range[0][1] - boxes_in_same_y_range[0][0] <= max_shape_size_tolerated[0])
+ and (boxes_in_same_y_range[0][3] - boxes_in_same_y_range[0][2] <= max_shape_size_tolerated[1])
+ )
+
+ cropped_bbox = (0, 0, img.size[0], img.size[1])
+ if should_force_crop:
+ cropped_bbox = (0, 0, img.size[0], bot_y_pos-(window_h-boxes_in_same_y_range[0][2]+1))
+
+ cropped_bbox = bw_img.crop(cropped_bbox).getbbox()
+
+ return cropped_bbox
+
+
+'''
+ Parameters:
+ img (PIL image): A PIL image.
+ power (float): The power to 'chop' through pixels matching the background. Values in range[0,3].
+ background_color (string): 'white' for white background, anything else for black.
+ Returns:
+ bbox (4-tuple, left|top|right|bot): The tightest bounding box calculated after trying to remove the bottom page number. Returns None if couldnt find anything satisfactory
+'''
+def get_bbox_crop_margin(img, power=1, background_color='white'):
+ if img.mode != 'L':
+ img = ImageOps.grayscale(img)
+
+ if background_color != 'white':
+ img = ImageOps.invert(img)
+
+ '''
+ Autocontrast: due to some threshold values, it's important that the blacks will be blacks and white will be whites.
+ Box/MeanFilter: Allows us to reduce noise like bad a page scan or compression artifacts.
+ Note: MedianFilter works better in my experience, but takes 2x-3x longer to perform.
+ '''
+ img = ImageOps.autocontrast(img, 1).filter(ImageFilter.BoxBlur(1))
+
+ '''
+ The 'power' parameters determines the threshold. The higher the power, the more "force" it can crop through black pixels (in case of white background)
+ and the lower the power, more sensitive to black pixels.
+ '''
+ threshold = threshold_from_power(power)
+ bw_img = img.point(lambda p: 255 if p <= threshold else 0)
+
+ return bw_img.getbbox()
+
+
+'''
+Groups close pixels together (x axis)
+'''
+def group_pixels(row, max_dist_tolerated, threshold):
+ groups = []
+ idx = np.where(row <= threshold)[0]
+
+ group_start = -1
+ group_end = 0
+ for i in range(len(idx)):
+ dist = idx[i] - group_end
+ if group_start == -1:
+ group_start = idx[i]
+ group_end = idx[i]
+ elif dist <= max_dist_tolerated:
+ group_end = idx[i]
+ else:
+ groups.append((group_start, group_end))
+ group_start = -1
+ group_end = -1
+
+ if group_start != -1:
+ groups.append((group_start, group_end))
+
+ return groups
+
+
+def box_intersect(box1, box2, max_dist):
+ return not (box2[0]-max_dist[0] > box1[1]
+ or box2[1]+max_dist[0] < box1[0]
+ or box2[2]-max_dist[1] > box1[3]
+ or box2[3]+max_dist[1] < box1[2])
+
+'''
+Merge close bounding boxes (left,right, top,bot) (x axis) with distance threshold defined in
+'max_dist_tolerated'. Boxes with less 'max_dist_tolerated' distance (Chebyshev distance).
+'''
+def merge_boxes(boxes, max_dist_tolerated):
+ j = 0
+ while j < len(boxes)-1:
+ g1 = boxes[j]
+ intersecting_boxes = []
+ other_boxes = []
+ for i in range(j+1,len(boxes)):
+ g2 = boxes[i]
+ if box_intersect(g1,g2, max_dist_tolerated):
+ intersecting_boxes.append(g2)
+ else:
+ other_boxes.append(g2)
+
+ if len(intersecting_boxes) > 0:
+ intersecting_boxes = np.array([g1, *intersecting_boxes])
+ merged_box = np.array([
+ np.min(intersecting_boxes[:,0]),
+ np.max(intersecting_boxes[:,1]),
+ np.min(intersecting_boxes[:,2]),
+ np.max(intersecting_boxes[:,3])
+ ])
+ other_boxes.append(merged_box)
+ boxes = np.concatenate([boxes[:j], other_boxes])
+ j = 0
+ else:
+ j += 1
+ return boxes
+
+
+def threshold_from_power(power):
+ return 240-(power*64)
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index ea3a7bf0..bc647c10 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,3 +8,4 @@ packaging>=23.2
mozjpeg-lossless-optimization>=1.1.2
natsort[fast]>=8.4.0
distro>=1.8.0
+numpy>=1.22.4,<2.0.0
diff --git a/setup.py b/setup.py
index fe074add..885b4146 100644
--- a/setup.py
+++ b/setup.py
@@ -83,6 +83,7 @@ def run(self):
'mozjpeg-lossless-optimization>=1.1.2',
'natsort[fast]>=8.4.0',
'distro',
+ 'numpy>=1.22.4,<2.0.0'
],
classifiers=[],
zip_safe=False,