TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10 icon indicating copy to clipboard operation
TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10 copied to clipboard

crop deteted objects from imagei and display it

Open ocularnets opened this issue 6 years ago • 24 comments

I want to store the detected objects from the image into separate folder. I have made changes in visualization_utils.py to crop the image section using normalized points but it is not cropping the same objects it has detected.

for i in range(0,len(bigger_bounding_box)):
    im_width, im_height = image.shape[0], image.shape[1]
    arr = numpy.array(image)
    box = bigger_bounding_box[i]
    xmin  = box[0]
    ymin  = box[1]
    xmax  = box[2]
    ymax  = box[3]
    (left, right, top, bottom) = (xmin * im_width, xmax * im_width, ymin * im_height, ymax * im_height)
    a,b,c,d = int(left) , int(right) , int(top) ,int(bottom)
    arr = arr[c:d,a:b]
    cv2.imwrite("C:/Users/tensor19/Desktop/images/QRCODE{}.jpg".format(count),arr)
    count =count+1

ocularnets avatar Feb 20 '19 11:02 ocularnets

Hopefully it will help you.

        height, width, channel = frame.shape
        ymin = int((boxes[0][0][0]*height))
        xmin = int((boxes[0][0][1]*width))
        ymax = int((boxes[0][0][2]*height))
        xmax = int((boxes[0][0][3]*width))
        Result = np.array(frame[ymin:ymax,xmin:xmax]) 

mudasar477 avatar Mar 28 '19 16:03 mudasar477

how use this code in object detection ?

cecepnrhya avatar Dec 14 '19 09:12 cecepnrhya

`(``` boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded})



(frame_height, frame_width) = image.shape[:2]

for i in range(len(np.squeeze(scores))):
            
	#print(np.squeeze(boxes)[i])
	ymin = int((np.squeeze(boxes)[i][0]*frame_height))
	xmin = int((np.squeeze(boxes)[i][1]*frame_width))
	ymax = int((np.squeeze(boxes)[i][2]*frame_height))
	xmax = int((np.squeeze(boxes)[i][3]*frame_width))
	cropped_img = image[ymax:ymin,xmax:xmin]
	cv2.imwrite('cropped image.jpg', cropped_img)`

mudasar477 avatar Dec 16 '19 06:12 mudasar477

`(boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded})

(frame_height, frame_width) = image.shape[:2]

for i in range(len(np.squeeze(scores))):

#print(np.squeeze(boxes)[i])
ymin = int((np.squeeze(boxes)[i][0]*frame_height))
xmin = int((np.squeeze(boxes)[i][1]*frame_width))
ymax = int((np.squeeze(boxes)[i][2]*frame_height))
xmax = int((np.squeeze(boxes)[i][3]*frame_width))
cropped_img = image[ymax:ymin,xmax:xmin]
cv2.imwrite('cropped image.jpg', cropped_img)`

where do I add this code to visualization_utils

cecepnrhya avatar Dec 16 '19 23:12 cecepnrhya

visualization_utils is used to draw bounding box and label the detected object, for your case if you want to crop the detected object then you don't need visualization part so just ignore those line of codes.

mudasar477 avatar Dec 17 '19 05:12 mudasar477

is used to draw bounding box and label the detected object, for your case if you want to crop the detected object then you don't need visualization part so just ignore those line of codes.

then the code to cut the detected object using what code

cecepnrhya avatar Dec 17 '19 06:12 cecepnrhya

after this line of code : (boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded})

put this to crop image:

(frame_height, frame_width) = image.shape[:2]

for i in range(len(np.squeeze(scores))):

	#print(np.squeeze(boxes)[i])
	ymin = int((np.squeeze(boxes)[i][0]*frame_height))
	xmin = int((np.squeeze(boxes)[i][1]*frame_width))
	ymax = int((np.squeeze(boxes)[i][2]*frame_height))
	xmax = int((np.squeeze(boxes)[i][3]*frame_width))
	cropped_img = image[ymax:ymin,xmax:xmin]
	cv2.imwrite('cropped image.jpg', cropped_img)

mudasar477 avatar Dec 17 '19 06:12 mudasar477

after this line of code : (boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded})

put this to crop image:

(frame_height, frame_width) = image.shape[:2]

for i in range(len(np.squeeze(scores))):

	#print(np.squeeze(boxes)[i])
	ymin = int((np.squeeze(boxes)[i][0]*frame_height))
	xmin = int((np.squeeze(boxes)[i][1]*frame_width))
	ymax = int((np.squeeze(boxes)[i][2]*frame_height))
	xmax = int((np.squeeze(boxes)[i][3]*frame_width))
	cropped_img = image[ymax:ymin,xmax:xmin]
	cv2.imwrite('cropped image.jpg', cropped_img)

success, but why the results image are empty

cecepnrhya avatar Dec 17 '19 06:12 cecepnrhya

after this line of code : (boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded}) put this to crop image: (frame_height, frame_width) = image.shape[:2] for i in range(len(np.squeeze(scores))):

	#print(np.squeeze(boxes)[i])
	ymin = int((np.squeeze(boxes)[i][0]*frame_height))
	xmin = int((np.squeeze(boxes)[i][1]*frame_width))
	ymax = int((np.squeeze(boxes)[i][2]*frame_height))
	xmax = int((np.squeeze(boxes)[i][3]*frame_width))
	cropped_img = image[ymax:ymin,xmax:xmin]
	cv2.imwrite('cropped image.jpg', cropped_img)

success, but why the results image are empty

which images? can you show me a snippet of your code and also the input image and cropped images?

mudasar477 avatar Dec 17 '19 06:12 mudasar477

after this line of code : (boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded}) put this to crop image: (frame_height, frame_width) = image.shape[:2] for i in range(len(np.squeeze(scores))):

	#print(np.squeeze(boxes)[i])
	ymin = int((np.squeeze(boxes)[i][0]*frame_height))
	xmin = int((np.squeeze(boxes)[i][1]*frame_width))
	ymax = int((np.squeeze(boxes)[i][2]*frame_height))
	xmax = int((np.squeeze(boxes)[i][3]*frame_width))
	cropped_img = image[ymax:ymin,xmax:xmin]
	cv2.imwrite('cropped image.jpg', cropped_img)

success, but why the results image are empty

which images? can you show me a snippet of your code and also the input image and cropped images?

` import os import cv2 import numpy as np import tensorflow as tf import sys from PIL import Image from matplotlib import pyplot as plt

This is needed since the notebook is stored in the object_detection folder.

sys.path.append("..")

Import utilites

from utils import label_map_util from utils import visualization_utils as vis_util

Name of the directory containing the object detection module we're using

MODEL_NAME = 'plate' IMAGE_NAME = 'test1.jpg'

Grab path to current working directory

CWD_PATH = os.getcwd()

Path to frozen detection graph .pb file, which contains the model that is used

for object detection.

PATH_TO_CKPT = os.path.join(CWD_PATH,MODEL_NAME,'frozen_inference_graph.pb')

Path to label map file

PATH_TO_LABELS = os.path.join(CWD_PATH,'training','labelmap.pbtxt')

Path to image

PATH_TO_IMAGE = os.path.join(CWD_PATH,IMAGE_NAME)

Number of classes the object detector can identify

NUM_CLASSES = 1

Load the label map.

Label maps map indices to category names, so that when our convolution

network predicts 5, we know that this corresponds to king.

Here we use internal utility functions, but anything that returns a

dictionary mapping integers to appropriate string labels would be fine

label_map = label_map_util.load_labelmap(PATH_TO_LABELS) categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=NUM_CLASSES, use_display_name=True) category_index = label_map_util.create_category_index(categories)

Load the Tensorflow model into memory.

detection_graph = tf.Graph() with detection_graph.as_default(): od_graph_def = tf.GraphDef() with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid: serialized_graph = fid.read() od_graph_def.ParseFromString(serialized_graph) tf.import_graph_def(od_graph_def, name='')

sess = tf.Session(graph=detection_graph)

Define input and output tensors (i.e. data) for the object detection classifier

Input tensor is the image

image_tensor = detection_graph.get_tensor_by_name('image_tensor:0')

Output tensors are the detection boxes, scores, and classes

Each box represents a part of the image where a particular object was detected

detection_boxes = detection_graph.get_tensor_by_name('detection_boxes:0')

Each score represents level of confidence for each of the objects.

The score is shown on the result image, together with the class label.

detection_scores = detection_graph.get_tensor_by_name('detection_scores:0') detection_classes = detection_graph.get_tensor_by_name('detection_classes:0')

Number of objects detected

num_detections = detection_graph.get_tensor_by_name('num_detections:0')

Load image using OpenCV and

expand image dimensions to have shape: [1, None, None, 3]

i.e. a single-column array, where each item in the column has the pixel RGB value

image = cv2.imread(PATH_TO_IMAGE) image_expanded = np.expand_dims(image, axis=0)

Perform the actual detection by running the model with the image as input

(boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded})

(frame_height, frame_width) = image.shape[:2] for i in range(len(np.squeeze(scores))):

#print(np.squeeze(boxes)[i])
ymin = int((np.squeeze(boxes)[i][0]*frame_height))
xmin = int((np.squeeze(boxes)[i][1]*frame_width))
ymax = int((np.squeeze(boxes)[i][2]*frame_height))
xmax = int((np.squeeze(boxes)[i][3]*frame_width))
cropped_img = image[ymax:ymin,xmax:xmin]
cv2.imwrite('cropped image.jpeg', cropped_img)

Draw the results of the detqection (aka 'visulaize the results')

vis_util.visualize_boxes_and_labels_on_image_array( image, np.squeeze(boxes), np.squeeze(classes).astype(np.int32), np.squeeze(scores), category_index, use_normalized_coordinates=True, line_thickness=8, min_score_thresh=0.60)

All the results have been drawn on image. Now display the image.

cv2.imshow("0bject detecttor", image)

Press any key to close the image

cv2.waitKey(0)

Clean up

cv2.destroyAllWindows() `

cecepnrhya avatar Dec 17 '19 06:12 cecepnrhya

after this line of code : (boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded}) put this to crop image: (frame_height, frame_width) = image.shape[:2] for i in range(len(np.squeeze(scores))):

	#print(np.squeeze(boxes)[i])
	ymin = int((np.squeeze(boxes)[i][0]*frame_height))
	xmin = int((np.squeeze(boxes)[i][1]*frame_width))
	ymax = int((np.squeeze(boxes)[i][2]*frame_height))
	xmax = int((np.squeeze(boxes)[i][3]*frame_width))
	cropped_img = image[ymax:ymin,xmax:xmin]
	cv2.imwrite('cropped image.jpg', cropped_img)

success, but why the results image are empty

which images? can you show me a snippet of your code and also the input image and cropped images?

after this line of code : (boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded}) put this to crop image: (frame_height, frame_width) = image.shape[:2] for i in range(len(np.squeeze(scores))):

	#print(np.squeeze(boxes)[i])
	ymin = int((np.squeeze(boxes)[i][0]*frame_height))
	xmin = int((np.squeeze(boxes)[i][1]*frame_width))
	ymax = int((np.squeeze(boxes)[i][2]*frame_height))
	xmax = int((np.squeeze(boxes)[i][3]*frame_width))
	cropped_img = image[ymax:ymin,xmax:xmin]
	cv2.imwrite('cropped image.jpg', cropped_img)

success, but why the results image are empty

which images? can you show me a snippet of your code and also the input image and cropped images?

this image input result object detection and i crop plate number only contoh

cecepnrhya avatar Dec 17 '19 06:12 cecepnrhya

after this line of code : (boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded}) put this to crop image: (frame_height, frame_width) = image.shape[:2] for i in range(len(np.squeeze(scores))):

	#print(np.squeeze(boxes)[i])
	ymin = int((np.squeeze(boxes)[i][0]*frame_height))
	xmin = int((np.squeeze(boxes)[i][1]*frame_width))
	ymax = int((np.squeeze(boxes)[i][2]*frame_height))
	xmax = int((np.squeeze(boxes)[i][3]*frame_width))
	cropped_img = image[ymax:ymin,xmax:xmin]
	cv2.imwrite('cropped image.jpg', cropped_img)

success, but why the results image are empty

which images? can you show me a snippet of your code and also the input image and cropped images?

` import os import cv2 import numpy as np import tensorflow as tf import sys from PIL import Image from matplotlib import pyplot as plt

This is needed since the notebook is stored in the object_detection folder.

sys.path.append("..")

Import utilites

from utils import label_map_util from utils import visualization_utils as vis_util

Name of the directory containing the object detection module we're using

MODEL_NAME = 'plate' IMAGE_NAME = 'test1.jpg'

Grab path to current working directory

CWD_PATH = os.getcwd()

Path to frozen detection graph .pb file, which contains the model that is used

for object detection.

PATH_TO_CKPT = os.path.join(CWD_PATH,MODEL_NAME,'frozen_inference_graph.pb')

Path to label map file

PATH_TO_LABELS = os.path.join(CWD_PATH,'training','labelmap.pbtxt')

Path to image

PATH_TO_IMAGE = os.path.join(CWD_PATH,IMAGE_NAME)

Number of classes the object detector can identify

NUM_CLASSES = 1

Load the label map.

Label maps map indices to category names, so that when our convolution

network predicts 5, we know that this corresponds to king.

Here we use internal utility functions, but anything that returns a

dictionary mapping integers to appropriate string labels would be fine

label_map = label_map_util.load_labelmap(PATH_TO_LABELS) categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=NUM_CLASSES, use_display_name=True) category_index = label_map_util.create_category_index(categories)

Load the Tensorflow model into memory.

detection_graph = tf.Graph() with detection_graph.as_default(): od_graph_def = tf.GraphDef() with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid: serialized_graph = fid.read() od_graph_def.ParseFromString(serialized_graph) tf.import_graph_def(od_graph_def, name='')

sess = tf.Session(graph=detection_graph)

Define input and output tensors (i.e. data) for the object detection classifier

Input tensor is the image

image_tensor = detection_graph.get_tensor_by_name('image_tensor:0')

Output tensors are the detection boxes, scores, and classes

Each box represents a part of the image where a particular object was detected

detection_boxes = detection_graph.get_tensor_by_name('detection_boxes:0')

Each score represents level of confidence for each of the objects.

The score is shown on the result image, together with the class label.

detection_scores = detection_graph.get_tensor_by_name('detection_scores:0') detection_classes = detection_graph.get_tensor_by_name('detection_classes:0')

Number of objects detected

num_detections = detection_graph.get_tensor_by_name('num_detections:0')

Load image using OpenCV and

expand image dimensions to have shape: [1, None, None, 3]

i.e. a single-column array, where each item in the column has the pixel RGB value

image = cv2.imread(PATH_TO_IMAGE) image_expanded = np.expand_dims(image, axis=0)

Perform the actual detection by running the model with the image as input

(boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded})

(frame_height, frame_width) = image.shape[:2] for i in range(len(np.squeeze(scores))):

#print(np.squeeze(boxes)[i])
ymin = int((np.squeeze(boxes)[i][0]*frame_height))
xmin = int((np.squeeze(boxes)[i][1]*frame_width))
ymax = int((np.squeeze(boxes)[i][2]*frame_height))
xmax = int((np.squeeze(boxes)[i][3]*frame_width))
cropped_img = image[ymax:ymin,xmax:xmin]
cv2.imwrite('cropped image.jpeg', cropped_img)

Draw the results of the detqection (aka 'visulaize the results')

vis_util.visualize_boxes_and_labels_on_image_array( image, np.squeeze(boxes), np.squeeze(classes).astype(np.int32), np.squeeze(scores), category_index, use_normalized_coordinates=True, line_thickness=8, min_score_thresh=0.60)

All the results have been drawn on image. Now display the image.

cv2.imshow("0bject detecttor", image)

Press any key to close the image

cv2.waitKey(0)

Clean up

cv2.destroyAllWindows() `

this result crop but empty, plis help me image

cecepnrhya avatar Dec 17 '19 06:12 cecepnrhya

this line cv2.imwrite('cropped image.jpg', cropped_img) overwrite the images bcz it is in for loop you need to handle this string cropped image.jpg to make it unique everytime.

mudasar477 avatar Dec 17 '19 06:12 mudasar477

.

so I have to change the code like what. can you show

cecepnrhya avatar Dec 17 '19 07:12 cecepnrhya

.

so I have to change the code like what. can you show

no my friend you have to do this on your own its simple string concatenation

mudasar477 avatar Dec 17 '19 07:12 mudasar477

this line cv2.imwrite('cropped image.jpg', cropped_img) overwrite the images bcz it is in for loop you need to handle this string cropped image.jpg to make it unique everytime.

I am facing the same issue, for every detected object I created a different image file using a simple counter but still, all the detected images are blank

Nishant98 avatar Dec 17 '19 17:12 Nishant98

.

so I have to change the code like what. can you show

Check this out!

https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10/issues/69#issue-332751488

Nishant98 avatar Dec 17 '19 17:12 Nishant98

after this line of code : (boxes, scores, classes, num) = sess.run( [detection_boxes, detection_scores, detection_classes, num_detections], feed_dict={image_tensor: image_expanded}) put this to crop image: (frame_height, frame_width) = image.shape[:2] for i in range(len(np.squeeze(scores))):

	#print(np.squeeze(boxes)[i])
	ymin = int((np.squeeze(boxes)[i][0]*frame_height))
	xmin = int((np.squeeze(boxes)[i][1]*frame_width))
	ymax = int((np.squeeze(boxes)[i][2]*frame_height))
	xmax = int((np.squeeze(boxes)[i][3]*frame_width))
	cropped_img = image[ymax:ymin,xmax:xmin]
	cv2.imwrite('cropped image.jpg', cropped_img)

success, but why the results image are empty

Hi, came across this and find this thread helpful, thanks! Manage to find why it is empty though, the min and max should be reversed: cropped_img = image[ymin:ymax,xmin:xmax] It works for me!

junyuan98 avatar May 24 '20 13:05 junyuan98

Hello, but this is not cropping the image properly. I am running this code in loop but only for the first time it is fetching correct and rest I think random.

ghost avatar Jun 01 '20 15:06 ghost

#code to get the pop camera

import cv2 from datetime import date import datetime import time

start_time = datetime.datetime.now() num_frames = 0 count=0

cap = cv2.VideoCapture(0) with detection_graph.as_default(): with tf.Session(graph=detection_graph) as sess: while True: ret, image_np = cap.read() # Expand dimensions since the model expects images to have shape: [1, None, None, 3] image_np_expanded = np.expand_dims(image_np, axis=0) image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') # Each box represents a part of the image where a particular object was detected. boxes = detection_graph.get_tensor_by_name('detection_boxes:0')

  # Each score represent how level of confidence for each of the objects.
  # Score is shown on the result image, together with the class label.
  scores = detection_graph.get_tensor_by_name('detection_scores:0')
  classes = detection_graph.get_tensor_by_name('detection_classes:0')
  
  num_detections = detection_graph.get_tensor_by_name('num_detections:0')
  
  # Actual detection.
  (boxes, scores, classes, num_detections) = sess.run([boxes, scores, classes, num_detections],feed_dict={image_tensor: image_np_expanded})
  
# Visualization of the results of a detection.
  a = vis_util.visualize_boxes_and_labels_on_image_array(
      image_np,
      np.squeeze(boxes),
      np.squeeze(classes).astype(np.int32),
      np.squeeze(scores),
      category_index,
      use_normalized_coordinates=True,
      line_thickness=8)
  
  
  (frame_height, frame_width) = image_np.shape[:2]
  #print("Height", frame_height)
  #print("Width", frame_width)
  count = 0
  for i in range(len(np.squeeze(scores))):
        count = count+1
        #print(np.squeeze(boxes)[i])
         
        ymin = int((np.squeeze(boxes)[i][0]*frame_height))
        xmin = int((np.squeeze(boxes)[i][1]*frame_width))
        ymax = int((np.squeeze(boxes)[i][2]*frame_height))
        xmax = int((np.squeeze(boxes)[i][3]*frame_width))
        print(ymin, xmin, ymax, xmax)
        cropped_img = image_np[ymin:ymax,xmin:xmax]
        print(cropped_img.shape)
        img = "frame%d.jpg"% count
        cv2.imwrite(img, cropped_img)
        #cv2.waitKey()
 
  num_frames += 1
  elapsed_time = (datetime.datetime.now() -start_time).total_seconds()
  fps = num_frames / elapsed_time


  # Display FPS on frame
  draw_text_on_image("FPS : " + str("{0:.2f}".format(fps)), image_np)
  NumberOfFrames("Number of Frames : " + str(num_frames), image_np)
  
  #img1 = "frame0%d.jpg"% count
  #cv2.imwrite(img1, crop_img)
  
  cv2.imshow('Detection', image_np)
  if cv2.waitKey(25) & 0xFF == ord('q'):
    cv2.destroyAllWindows()
    break

cap.release()

ghost avatar Jun 01 '20 15:06 ghost

sorry Guys, I have found the solution. I have moved out of loop. Its working as expected

ymin = int((np.squeeze(boxes)[0][0] * frame_height)) xmin = int((np.squeeze(boxes)[0][1] * frame_width)) ymax = int((np.squeeze(boxes)[0][2] * frame_height)) xmax = int((np.squeeze(boxes)[0][3] * frame_width))

ghost avatar Jun 01 '20 16:06 ghost

sorry Guys, I have found the solution. I have moved out of loop. Its working as expected

ymin = int((np.squeeze(boxes)[0][0] * frame_height)) xmin = int((np.squeeze(boxes)[0][1] * frame_width)) ymax = int((np.squeeze(boxes)[0][2] * frame_height)) xmax = int((np.squeeze(boxes)[0][3] * frame_width))

Hey. This code worked for me, but it could only save 100 images. After 100 images, the new images overwrite the previous ones. Is this happening because the for loop is the length of scores and the max score is 100 ? How do i fix it ? Please help. @vivek86-ai @mudasar477

devimonica avatar Jun 17 '20 12:06 devimonica

@mudasar477 Hello.. My object detection detect 3 boxes When i use your code to crop them :

(frame_height, frame_width) = image.shape[:2]

for i in range(len(np.squeeze(scores))):
#print(np.squeeze(boxes)[i])
ymin = int((np.squeeze(boxes)[i][0]*frame_height))
xmin = int((np.squeeze(boxes)[i][1]*frame_width))
ymax = int((np.squeeze(boxes)[i][2]*frame_height))
xmax = int((np.squeeze(boxes)[i][3]*frame_width))
cropped_img = image[ymax:ymin,xmax:xmin]
cv2.imwrite('cropped image.jpg', cropped_img)`

images be repeated so many times (about 50 image of same 3 boxes) i need just to save those 3 boxes picture.. any kind of solution to this?

3b0000d avatar Feb 25 '21 14:02 3b0000d

what is sess..?

ahmedsolimanpts avatar Feb 02 '22 18:02 ahmedsolimanpts