Skip to content

Commit

Permalink
Fixed pylint issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Iván de Paz committed Oct 8, 2024
1 parent 4205275 commit 724752a
Show file tree
Hide file tree
Showing 21 changed files with 973 additions and 231 deletions.
650 changes: 650 additions & 0 deletions .pylintrc

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions mtcnn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@

from mtcnn.mtcnn import MTCNN

__all__ = ["MTCNN"]
1 change: 1 addition & 0 deletions mtcnn/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
# SOFTWARE.

__version__ = "1.0.0"

50 changes: 30 additions & 20 deletions mtcnn/mtcnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class MTCNN:
Default is "CPU:0".
"""

def __init__(self, stages="face_and_landmarks_detection", device="CPU:0"):
if isinstance(stages, str):
if stages not in COMMON_STAGES:
Expand All @@ -59,12 +59,12 @@ def __init__(self, stages="face_and_landmarks_detection", device="CPU:0"):
# Instantiate stages if necessary (can pass already instantiated stages too)
self._stages = [stage() if isinstance(stage, type) else stage for stage in stages]
self._device = device

@property
def device(self):
"""Returns the device where the algorithm is executed"""
return self._device

@property
def stages(self):
"""Returns the list of pipeline stages."""
Expand All @@ -84,15 +84,19 @@ def get_stage(self, stage_id=None, stage_name=None):
for stage in self._stages:
if stage.id == stage_id or stage.name == stage_name:
return stage

return None

def predict(self, image, fit_to_image=True, limit_boundaries_landmarks=False, box_format="xywh", output_type="json", postprocess=True, **kwargs):
def predict(self, image, fit_to_image=True, limit_boundaries_landmarks=False, box_format="xywh", output_type="json", postprocess=True,
**kwargs):
"""
Alias for detect_faces().
"""
return self.detect_faces(image, fit_to_image=fit_to_image, limit_boundaries_landmarks=limit_boundaries_landmarks, box_format=box_format, output_type=output_type, postprocess=postprocess, **kwargs)

def detect_faces(self, image, fit_to_image=True, limit_boundaries_landmarks=False, box_format="xywh", output_type="json", postprocess=True, batch_stack_justification="center", **kwargs):
return self.detect_faces(image, fit_to_image=fit_to_image, limit_boundaries_landmarks=limit_boundaries_landmarks,
box_format=box_format, output_type=output_type, postprocess=postprocess, **kwargs)

def detect_faces(self, image, fit_to_image=True, limit_boundaries_landmarks=False, box_format="xywh", output_type="json",
postprocess=True, batch_stack_justification="center", **kwargs):
"""
Runs face detection on a single image or batch of images through the configured stages.
Expand All @@ -101,10 +105,13 @@ def detect_faces(self, image, fit_to_image=True, limit_boundaries_landmarks=Fals
It can be a file path, a tensor, or raw bytes.
fit_to_image (bool, optional): Whether to fit bounding boxes and landmarks within image boundaries. Default is True.
limit_boundaries_landmarks (bool, optional): Whether to ensure landmarks stay within image boundaries. Default is False.
box_format (str, optional): The format of the bounding box. Can be "xywh" for [X1, Y1, width, height] or "xyxy" for [X1, Y1, X2, Y2]. Default is "xywh".
box_format (str, optional): The format of the bounding box. Can be "xywh" for [X1, Y1, width, height] or "xyxy" for [X1, Y1, X2, Y2].
Default is "xywh".
output_type (str, optional): The output format. Can be "json" for dictionary output or "numpy" for numpy array output. Default is "json".
postprocess (bool, optional): Flag to enable postprocessing. The postprocessing includes functionality affected by `fit_to_image`, `limit_boundaries_landmarks` and removing padding effects caused by batching images with different shapes.
batch_stack_justification (str, optional): The justification of the smaller images w.r.t. the largest images when stacking in batch processing, which requires padding smaller images to the size of the biggest one.
postprocess (bool, optional): Flag to enable postprocessing. The postprocessing includes functionality affected by `fit_to_image`,
`limit_boundaries_landmarks` and removing padding effects caused by batching images with different shapes.
batch_stack_justification (str, optional): The justification of the smaller images w.r.t. the largest images when
stacking in batch processing, which requires padding smaller images to the size of the biggest one.
**kwargs: Additional parameters passed to the stages. The following parameters are used:
- **StagePNet**:
Expand All @@ -124,11 +131,13 @@ def detect_faces(self, image, fit_to_image=True, limit_boundaries_landmarks=Fals
- nms_onet (float, optional): IoU threshold for Non-Maximum Suppression in ONet. Default is 0.7.
Returns:
list or list of lists: A list of detected faces (in case a single image) or a list of lists of detected faces (one per image in the batch). If the stages are `face_and_landmarks_detection`, the output will have the detected faces and landmarks in JSON format. In case of `face_detection_only`, only the bounding boxes will be provided in JSON format.
list or list of lists: A list of detected faces (in case a single image) or a list of lists of detected faces (one per image in the batch).
If the stages are `face_and_landmarks_detection`, the output will have the detected faces and landmarks in JSON format.
In case of `face_detection_only`, only the bounding boxes will be provided in JSON format.
"""
return_tensor = output_type == "numpy"
as_width_height = box_format == "xywh"

is_batch = isinstance(image, list)
images = image if is_batch else [image]

Expand All @@ -139,33 +148,33 @@ def detect_faces(self, image, fit_to_image=True, limit_boundaries_landmarks=Fals
images_normalized, images_oshapes, pad_param = standarize_batch(images_raw,
justification=batch_stack_justification,
normalize=True)

bboxes_batch = None

# Process images through each stage (PNet, RNet, ONet)
for stage in self.stages:
bboxes_batch = stage(bboxes_batch=bboxes_batch, images_normalized=images_normalized, images_oshapes=images_oshapes, **kwargs)

except tf.errors.InvalidArgumentError: # No faces found
bboxes_batch = np.empty((0, 16))
pad_param = None

if postprocess and pad_param is not None:
# Adjust bounding boxes and landmarks to account for padding offsets
bboxes_batch = fix_bboxes_offsets(bboxes_batch, pad_param)

# Optionally, limit the bounding boxes and landmarks to stay within image boundaries
if fit_to_image:
bboxes_batch = limit_bboxes(bboxes_batch, images_shapes=images_oshapes, limit_landmarks=limit_boundaries_landmarks)

# Convert bounding boxes and landmarks to JSON format if required
if return_tensor:
result = bboxes_batch

if as_width_height:
result[:, 3] = result[:, 3] - result[:, 1]
result[:, 4] = result[:, 4] - result[:, 2]

else:
result = to_json(bboxes_batch,
images_count=len(images),
Expand All @@ -174,3 +183,4 @@ def detect_faces(self, image, fit_to_image=True, limit_boundaries_landmarks=Fals
result = result[0] if (not is_batch and len(result) > 0) else result

return result

3 changes: 0 additions & 3 deletions mtcnn/network/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,3 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from mtcnn.network.pnet import PNet
from mtcnn.network.rnet import RNet
from mtcnn.network.onet import ONet
48 changes: 25 additions & 23 deletions mtcnn/network/onet.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# pylint: disable=duplicate-code

import tensorflow as tf

L = tf.keras.layers
Expand All @@ -37,33 +39,33 @@ class ONet(tf.keras.Model):
"""
def __init__(self, **kwargs):
super(ONet, self).__init__(**kwargs)

# Defining the layers according to the provided architecture
self.conv1 = L.Conv2D(32, kernel_size=(3, 3), strides=(1, 1), padding="valid", activation="linear", name="conv1")
self.prelu1 = L.PReLU(shared_axes=[1, 2], name="prelu1")
self.maxpool1 = L.MaxPooling2D(pool_size=(3, 3), strides=(2, 2), padding="same", name="maxpooling1")

self.conv2 = L.Conv2D(64, kernel_size=(3, 3), strides=(1, 1), padding="valid", activation="linear", name="conv2")
self.prelu2 = L.PReLU(shared_axes=[1, 2], name="prelu2")
self.maxpool2 = L.MaxPooling2D(pool_size=(3, 3), strides=(2, 2), padding="valid", name="maxpooling2")

self.conv3 = L.Conv2D(64, kernel_size=(3, 3), strides=(1, 1), padding="valid", activation="linear", name="conv3")
self.prelu3 = L.PReLU(shared_axes=[1, 2], name="prelu3")
self.maxpool3 = L.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding="same", name="maxpooling3")

self.conv4 = L.Conv2D(128, kernel_size=(2, 2), strides=(1, 1), padding="valid", activation="linear", name="conv4")
self.prelu4 = L.PReLU(shared_axes=[1, 2], name="prelu4")

self.permute = L.Permute((2, 1, 3), name="permute")
self.flatten = L.Flatten(name="flatten4")

self.fc5 = L.Dense(256, activation="linear", name="fc5")
self.prelu5 = L.PReLU(name="prelu5")

self.fc6_1 = L.Dense(4, activation="linear", name="fc6-1") # Bounding box regression
self.fc6_2 = L.Dense(10, activation="linear", name="fc6-2") # Landmark regression (5 landmarks, 10 points total)
self.fc6_3 = L.Dense(2, activation="softmax", name="fc6-3") # Classification (face or not)

def build(self, input_shape=(None, 48, 48, 3)):
"""
Build the network by defining the input and manually creating each layer step by step, computing output shapes.
Expand All @@ -76,80 +78,80 @@ def build(self, input_shape=(None, 48, 48, 3)):
output_shape = self.prelu1.compute_output_shape(output_shape)
self.maxpool1.build(output_shape)
output_shape = self.maxpool1.compute_output_shape(output_shape)

# Build conv2 block
self.conv2.build(output_shape)
output_shape = self.conv2.compute_output_shape(output_shape)
self.prelu2.build(output_shape)
output_shape = self.prelu2.compute_output_shape(output_shape)
self.maxpool2.build(output_shape)
output_shape = self.maxpool2.compute_output_shape(output_shape)

# Build conv3 block
self.conv3.build(output_shape)
output_shape = self.conv3.compute_output_shape(output_shape)
self.prelu3.build(output_shape)
output_shape = self.prelu3.compute_output_shape(output_shape)
self.maxpool3.build(output_shape)
output_shape = self.maxpool3.compute_output_shape(output_shape)

# Build conv4 block
self.conv4.build(output_shape)
output_shape = self.conv4.compute_output_shape(output_shape)
self.prelu4.build(output_shape)
output_shape = self.prelu4.compute_output_shape(output_shape)

# Permute and flatten
self.permute.build(output_shape)
output_shape = self.permute.compute_output_shape(output_shape)
self.flatten.build(output_shape)
output_shape = self.flatten.compute_output_shape(output_shape)

# Fully connected layers
self.fc5.build(output_shape)
output_shape = self.fc5.compute_output_shape(output_shape)
self.prelu5.build(output_shape)
output_shape = self.prelu5.compute_output_shape(output_shape)

# Outputs (classification, bounding box regression, and landmark regression)
self.fc6_1.build(output_shape)
self.fc6_2.build(output_shape)
self.fc6_3.build(output_shape)

# Call the super build to finalize the model building
super(ONet, self).build(input_shape)

def call(self, inputs):
def call(self, inputs, *args, **kwargs):
x = inputs

# First conv block
x = self.conv1(x)
x = self.prelu1(x)
x = self.maxpool1(x)

# Second conv block
x = self.conv2(x)
x = self.prelu2(x)
x = self.maxpool2(x)

# Third conv block
x = self.conv3(x)
x = self.prelu3(x)
x = self.maxpool3(x)

# Fourth conv block
x = self.conv4(x)
x = self.prelu4(x)

# Permute, flatten, and fully connected layers
x = self.permute(x)
x = self.flatten(x)
x = self.fc5(x)
x = self.prelu5(x)

# Outputs
bbox_reg = self.fc6_1(x) # Regression of bounding boxes
landmarks = self.fc6_2(x) # Regression of facial landmarks
bbox_class = self.fc6_3(x) # Classification (face or not)

return [bbox_reg, landmarks, bbox_class]
22 changes: 12 additions & 10 deletions mtcnn/network/pnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# pylint: disable=duplicate-code

import tensorflow as tf


Expand All @@ -37,7 +39,7 @@ class PNet(tf.keras.Model):
"""
def __init__(self, **kwargs):
super(PNet, self).__init__(**kwargs)

# Definir las capas
self.conv1 = L.Conv2D(10, kernel_size=(3,3), strides=(1,1), padding="valid", activation="linear", name="conv1")
self.prelu1 = L.PReLU(shared_axes=[1, 2], name="prelu1")
Expand All @@ -52,31 +54,31 @@ def __init__(self, **kwargs):
def build(self, input_shape=(None, None, None, 3)):
self.conv1.build(input_shape)
output_shape = self.conv1.compute_output_shape(input_shape)

self.prelu1.build(output_shape)
output_shape = self.prelu1.compute_output_shape(output_shape)

self.maxpool1.build(output_shape)
output_shape = self.maxpool1.compute_output_shape(output_shape)

self.conv2.build(output_shape)
output_shape = self.conv2.compute_output_shape(output_shape)

self.prelu2.build(output_shape)
output_shape = self.prelu2.compute_output_shape(output_shape)

self.conv3.build(output_shape)
output_shape = self.conv3.compute_output_shape(output_shape)

self.prelu3.build(output_shape)
output_shape = self.prelu3.compute_output_shape(output_shape)

self.conv4_1.build(output_shape)
self.conv4_2.build(output_shape)

super(PNet, self).build(input_shape)
def call(self, inputs):

def call(self, inputs, *args, **kwargs):
x = inputs

# First conv block
Expand Down
Loading

0 comments on commit 724752a

Please sign in to comment.