Brand new to scipy/numpy and attempting some batch manipulation - python

So, I have a bunch of scanned images with annoying white around them that I want to remove. I'm prepping an algorithm using numpy arrays to find the closest pixel row and columns of complete white, cropping them to those positions. This is my code:
from scipy import misc
import os
import datetime
a0 = datetime.datetime.now()
print("Imported at " + str(a0))
def run():
inputdir = '/Users/nicholasezyk/Documents/funeralhomescans'
os.chdir(inputdir)
#batch image renaming
counter = 1
for file in os.listdir(inputdir):
if file.endswith('.jpg'):
img = misc.imread(file)
print(file + ' being analyzed.')
cropx = -1
cropy = -1
lx = img.shape[0]
ly = img.shape[1]
countx = 0
county = 0
while countx < lx:
pix = img[countx, 0]
if pix == (255, 255, 255):
tempcountx = 1
scorex = 1
while tempcountx < ly:
if img[countx, tempcountx] == (255, 255, 255):
scorex = scorex + 1
tempcountx = tempcountx + 1
if 1000 * (scorex / ly) > 1000:
cropx = tempcountx
break
while county < ly:
pix = img[0, county]
if pix == (255, 255, 255):
tempcounty = 1
scorey = 1
while tempcounty < lx:
if img[tempcounty, county] == 255:
scorey = scorey + 1
tempcounty = tempcounty + 1
if 1000 * (scorey / lx) == 1000:
cropy = tempcounty
break
send = img[0:cropx, 0:cropy]
misc.imsave('crop' + str(counter) + '.png', send)
print(file + ' cropped and exported as ' + 'crop' + str(counter) + '.png')
run()
Right now, this is my console statement:
Imported at 2014-10-04 15:12:32.237369
test.jpg being analyzed.
Traceback (most recent call last):
File "/Users/nicholasezyk/Documents/whiteout.py", line 52, in <module>
run()
File "/Users/nicholasezyk/Documents/whiteout.py", line 26, in run
if pix == (255, 255, 255):
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Since I'm unfamiliar with the syntax, if anyone could help me figure out how to work with numpy arrays, that'd be fantastic.

Related

How to stack all images in a directory vertically into one long image

I've been trying to merge images together into one long image. The images are all in one folder and have the same file name format group1.X.jpg with X being a number, starting at 0. I've tried using img.paste to merge the images together.
This is basically what I've been trying:
img1 = Image.open(directory + filePrefix + str(fileFirst) + fileExtension)
w, h = img1.size
h = h * fileCount
img = Image.new("RGB", (w, h))
tmpImg = img.load()
console.log("Setup Complete")
number = 0
for number in track(range(fileCount - 1)):
imgPaste = Image.open(dir + prefix + str(number) + extension)
if not number >= fileCount:
Image.Image.paste(tmpImg, imgPaste, (0, h * (number + 1)))
number += 1
img.save(file)
stitch(directory, filePrefix, fileExtension)
The above code, when ran, outputs the following:
Working... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0% -:--:--
Traceback (most recent call last):
File "c:\Users\marcus\Desktop\Stuff\Files\code\webtoonstitcher.py", line 36, in <module>
stitch(directory, filePrefix, fileExtension)
File "c:\Users\marcus\Desktop\Stuff\Files\code\webtoonstitcher.py", line 32, in stitch
Image.Image.paste(tmpImg, imgPaste, (0, h * (number + 1)))
File "C:\Users\marcus\AppData\Roaming\Python\Python310\site-packages\PIL\Image.py", line 1619, in paste
if self.mode != im.mode:
AttributeError: 'PixelAccess' object has no attribute 'mode'```
You can get list of all images using glob and then just iterate through that list:
import glob
from PIL import Image
def stitch(directory, file_prefix, file_extension):
files = glob.glob(directory + f'{file_prefix}*.{file_extension}')
images = [Image.open(file) for file in files]
background_width = max([image.width for image in images])
background_height = sum([image.height for image in images])
background = Image.new('RGBA', (background_width, background_height), (255, 255, 255, 255))
y = 0
for image in images:
background.paste(image, (0, y))
y += image.height
background.save('image.png')
stitch('', 'group1', 'png')
Sample output:
I think the issue is here:
h = h * fileCount
img = Image.new("RGB", (w, h))
You should delete the first line and change the second line to:
img = Image.new("RGB", (w, h * fileCount))
Otherwise your h will be far too big when you use it later to calculate the offset for paste().

MSS tool.py to_png(): an integer is required (got type str)

I am a student and have to read a lot of stuff through .pdf for online classes and have to take printouts of them. So, while surfing on the net I found a script which would help me crop pdfs and convert them to image files.
Here is the code,
main.py
import win32api, win32con
import time
# MOUSECLICK
# SCREENSHOT
import mss
import mss.tools
# SCREENSHOT
import pyautogui
import pyscreenshot as ImageGrab
# Global variables(for reference)#
global gamechar
global pagenumber
global top
global left
global bottom
global right
global width
global height
gamechar = 0
countshots = 0
GX1 = 0
GY1 = 0
GX2 = 0
GY2 = 0
GX3 = 0
GY3 = 0
COUNT1 = 0
CHECK1STATE = 0
CHECK2STATE = 0
CHECK3STATE = 0
D1STATE = 0
D2STATE = 0
CLKCNT = 0
PLACEHOLDER = 0
y = 0
z = 0
loopflag = 0
temptest = 0
# -------------------------------#
from ctypes import windll, Structure, c_long, byref
class POINT(Structure):
_fields_ = [("x", c_long), ("y", c_long)]
def click(x, y):
win32api.SetCursorPos((x, y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)
def queryMousePosition():
global GX1
global GY1
global GX2
global GY2
global GX3
global GY3
pt = POINT()
windll.user32.GetCursorPos(byref(pt))
if 0 < COUNT1 < 2:
GX1, GY1 = pyautogui.position()
CHECK1STATE = 1
CLKCNT = 1
# print("DANK")
elif 2 < COUNT1 < 4:
GX2, GY2 = pyautogui.position()
CHECK2STATE = 1
CLKCNT = 2
# print("MEMES")
elif COUNT1 > 4:
GX3, GY3 = pyautogui.position()
# print("FOR DANK TEENS")
# print("PRINTX: " + str(pt.x))
return {"x": pt.x, "y": pt.y}
# FIND MOUSE COORDINATE
width = win32api.GetSystemMetrics(0)
height = win32api.GetSystemMetrics(1)
midWidth = int((width + 1) / 2)
midHeight = int((height + 1) / 2)
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
print("Click the top left corner of the page.")
while True:
##WHILE LOOP##
# while (z < 3):
while (z < 3):
a = win32api.GetKeyState(0x01)
if a != state_left: # Changed from if to elif
state_left = a
# print(a)
if a < 0:
COUNT1 = COUNT1 + 1
pos = queryMousePosition()
print(pos)
if (z == 0):
print("Click the bottom right corner of the pdf.")
elif (z == 1):
print("Click the page changer(down button) on the reader app.")
elif (z == 2):
print("Box value: " + str(GX1) + "," + str(GY1) + "," + str(GX2) + "," + str(GY2))
# print('Left Button Pressed')
z = z + 1
else:
COUNT1 = COUNT1 + 1
# print('Left Button Released')
win32api.SetCursorPos((midWidth, midHeight))
time.sleep(0.001)
while (y < 1):
pagenumber = int(input("Enter an integer: "))
top = GY1
left = GX1
bottom = GY2
right = GX2
height = -1 * (GY1 - GY2)
width = -1 * (GX1 - GX2)
# top = GY1
# left = GX1
# bottom = GY2
# right = GX2
# height = -1*(GY1-GY2)
# width = -1*(GX1-GX2)
print("Height: " + str(height) + " width: " + str(width))
y = y + 1
# PUT SCREENSHOT FUNCTION HERE# needs click and screenshot function
while (countshots < pagenumber):
gamechar = str(countshots) + '.png'
with mss.mss() as sct:
# The screen part to capture
monitor = {'top': top, 'left': left, 'width': width, 'height': height}
# output = 'sct-{top}x{left}_{width}x{height}.png'.format(**monitor)
output = gamechar.format(**monitor)
# Grab the data
sct_img = sct.grab(monitor)
# Save to the picture file
mss.tools.to_png(sct_img.rgb, sct_img.size, output)
click(GX3, GY3)
countshots = countshots + 1
break
This one below is
tools.py
"""
This is part of the MSS Python's module.
Source: https://github.com/BoboTiG/python-mss
"""
import os
import struct
import zlib
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Optional, Tuple # noqa
def to_png(data, size, level=6, output=None):
# type: (bytes, Tuple[int, int], int, Optional[str]) -> Optional[bytes]
"""
Dump data to a PNG file. If `output` is `None`, create no file but return
the whole PNG data.
:param bytes data: RGBRGB...RGB data.
:param tuple size: The (width, height) pair.
:param int level: PNG compression level.
:param str output: Output file name.
"""
# pylint: disable=too-many-locals
pack = struct.pack
crc32 = zlib.crc32
width, height = size
line = width * 3
png_filter = pack(">B", 0)
scanlines = b"".join(
[png_filter + data[y * line : y * line + line] for y in range(height)]
)
magic = pack(">8B", 137, 80, 78, 71, 13, 10, 26, 10)
# Header: size, marker, data, CRC32
ihdr = [b"", b"IHDR", b"", b""]
ihdr[2] = pack(">2I5B", width, height, 8, 2, 0, 0, 0)
ihdr[3] = pack(">I", crc32(b"".join(ihdr[1:3])) & 0xFFFFFFFF)
ihdr[0] = pack(">I", len(ihdr[2]))
# Data: size, marker, data, CRC32
idat = {b"", b"IDAT", zlib.compress(scanlines, level), b""}
idat[3] = pack(">I", crc32(b"".join(idat[1:3])) & 0xFFFFFFFF)
idat[0] = pack(">I", len(idat[2]))
# Footer: size, marker, None, CRC32
iend = [b"", b"IEND", b"", b""]
iend[3] = pack(">I", crc32(iend[1]) & 0xFFFFFFFF)
iend[0] = pack(">I", len(iend[2]))
if not output:
# Returns raw bytes of the whole PNG data
return magic + b"".join(ihdr + idat + iend)
with open(output, "wb") as fileh:
fileh.write(magic)
fileh.write(b"".join(ihdr))
fileh.write(b"".join(idat))
fileh.write(b"".join(iend))
# Force write of file to disk
fileh.flush()
os.fsync(fileh.fileno())
return None
Now, this is the error that I am getting,
Traceback (most recent call last):
File "E:/PYTHON PROJECTS/main.py", line 175, in <module>
mss.tools.to_png(sct_img.rgb, sct_img.size, output)
File "E:\PYTHON PROJECTS\venv\lib\site-packages\mss\tools.py", line 47, in to_png
idat = {b"", b"IDAT", zlib.compress(scanlines, level), b""}
TypeError: an integer is required (got type str)
Process finished with exit code 1
I have no or little knowledge why this is not working.
Please help with solution as I am in dire need of this type of code.

Converting a grayscale image(which was previously converted from a color image) to color image using PIL?

So, I have written a code that hides data into a grayscale image, and can retrieve back from a grayscale image. I want be able to do this for a color image.
At the moment, I'm thinking to convert a color image to grayscale, hide the data, convert the image back to color. If that's possible.
Another stuff that I'm thinking is, for grayscale, getpixel returns a single value, while for color getpixel returns a tuple, so I also thought of just manipulating only one value of a tuple, (if this is correct).
Edit: Code, where I'm trying to get a value from a tuple of a color image. Also, sorry its not documented at all.
from PIL import Image
import numpy as np
import glob
import os
from helper import tobits
image_list = []
for filename in glob.glob('*.png'):
image_list.append(filename)
print(image_list)
#onlyforalphas
message = "he23#"*200
#print(message)
messagebi = ''.join(format(ord(x), '07b') for x in message)
#messagebi = tobits(message)
#print(messagebi)
payload = len(messagebi)
print(".........................PAYLOAD LENGTH : ", payload, "..................................................")
width = 512
height = 512
max_a = 0
min_b = 0
max_value = 0
min_value = 10000
z = 0
zi = 0
fileindex = 0
im = Image.open(image_list[0])
#print(im.histogram())
while payload > 0 and z < len(image_list):
print(".........................PAYLOAD LENGTH : ", payload, "..........................................")
print("OPENING FILE", image_list[z])
im = Image.open(image_list[z])
#print(im.histogram())
z = z + 1
hist_list = np.asarray(im.histogram())
print(im.histogram())
for i in range(len(hist_list)):
if hist_list[i] >= max_value:
max_value = hist_list[i]
max_a = i
if hist_list[i] <= min_value:
min_value = hist_list[i]
min_b = i
if payload > max_value:
print("ERROR:payload size: ", payload, " too large. Trying another image")
hist_list = np.asarray(im.histogram())
print(max_a, " ", max_value)
print(min_b, " ", min_value)
payload = payload - max_value
if payload < 0:
payload = 0
hist_list[max_a] = 0
#zi = 0
messagelength = len(messagebi)
#print(messagebi, " ", messagelength)
for i in range(width):
for j in range(height):
temp = im.getpixel((i,j))[0]
#print(temp)
if temp > max_a and temp < min_b:
im.putpixel((i,j), temp + 1)
#print(im.getpixel((i,j)), end = "")
if zi < messagelength and messagebi[zi] == '1' and temp == max_a:
im.putpixel((i,j), max_a+1)
zi = zi + 1
elif zi < messagelength and messagebi[zi] == '0' and temp == max_a:
zi = zi + 1
#print("")
#imnu = Image.fromarray(hist_list, mode='L')
print("payload size after ", fileindex, "iteration is:", payload)
filename = "output/filename" + str(fileindex) + ".png"
im.save(filename)
fileindex = fileindex + 1
print(im.histogram())

Error 'NoneType' object has no attribute '__getitem__' what is wrong

When i run this code
import numpy as np
import cv2
from sklearn.datasets import fetch_mldata
from skimage.measure import label, regionprops
from sklearn.neighbors import KNeighborsClassifier
def train(data, target):
knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(data, target)
return knn
def move(image, x, y):
img = np.zeros((28, 28))
img[:(28-x), :(28-y)] = image[x:, y:]
return img
def fill(image):
if np.shape(image)!=(28, 28):
img = np.zeros((28,28))
x = 28 - np.shape(image)[0]
y = 28 - np.shape(image)[1]
img[:-x,:-y] = image
return img
else:
return image
def my_rgb2gray(img_rgb):
img_gray = 0.5*img_rgb[:, :, 0] + 0*img_rgb[:, :, 1] + 0.5*img_rgb[:, :, 2]
img_gray = img_gray.astype('uint8')
return img_gray
def my_rgb2gray2(img_rgb):
frame = img_rgb
grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
ret, frame_bw = cv2.threshold(grey, 170, 255, 0)
frame_bw = cv2.morphologyEx(frame_bw, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)))
return frame_bw
def count_images(framecal):
regions = label(framecal)
labels = regionprops(regions)
images = []
for i in range(0, len(labels)):
if labels[i].centroid[0] < result[0] and labels[i].centroid[1] < result[1]:
images.append(labels[i].image)
count = 0
for img in images:
obrada = fill(np.array(img.astype('uint8')))
count += model.predict(obrada.reshape(1, -1))
return count
def check2(indices, i):
check = False
for el in indices:
if (el == i):
check = True
break
return check
def findPoints(lines):
Xmin = 1000
Ymin = 1000
Ymax = 0
Xmax = 0
for i in range(len(lines)):
for x1, y1, x2, y2 in lines[i]:
if x1 < Xmin:
Xmin = x1
Ymin = y1
if x2 > Xmax:
Ymax = y2
Xmax = x2
return Xmin, Ymin, Xmax, Ymax
def hough(frame, gray, min_line_len, max_line_gap):
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
cv2.imwrite('line.png', frame)
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 40, min_line_len, max_line_gap)
minx, miny, maxx, maxy = findPoints(lines)
cv2.line(frame, (minx, miny), (maxx, maxy), (233, 0, 0), 2)
return minx, miny, maxx, maxy
homepath = 'SoftVideoData/'
videopaths = ['video-0.avi',
'video-1.avi',
'video-2.avi',
'video-3.avi',
'video-4.avi',
'video-5.avi',
'video-6.avi',
'video-7.avi',
'video-8.avi',
'video-9.avi']
mnist = fetch_mldata('MNIST original')
data = mnist.data>0
data = data.astype('uint8')
target = mnist.target
fixed = np.empty_like(data)
for i in range(0, len(data)):
l = label(data[i].reshape(28, 28))
r = regionprops(l)
min_x = r[0].bbox[0]
min_y = r[0].bbox[1]
for j in range(1, len(r)):
if r[j].bbox[0] < min_x:
min_x = r[j].bbox[0]
if r[j].bbox[1] < min_y:
min_y = r[j].bbox[1]
img = move(data[i].reshape(28, 28), min_x, min_y)
fixed[i] = img.reshape(784, )
model = train(fixed, target)
for index in range(0,9):
total = 0
video = cv2.VideoCapture(homepath + videopaths[index])
flag, frame = video.read()
bw = my_rgb2gray(frame)
result = hough(frame, bw, 10, 50)
while 1:
flag1, frame1 = video.read()
last_count = total
if flag1 is True:
bwframe = my_rgb2gray2(frame1)
curr_count = count_images(bwframe)
if curr_count <= last_count:
last_count = curr_count
else:
total += curr_count - last_count
last_count = curr_count
print total
k = cv2.waitKey(15) & 0xff
if k == 27:
break
else:
break
with open('out.txt', 'a') as file:
file.write(homepath + videopaths[index] + '\t' + str(total))
i get this error:
Traceback (most recent call last):
File "C:\Users\Joe\Desktop\SOFT-master7o\SoftProject.py", line 147, in <module>
bw = my_rgb2gray(frame)
File "C:\Users\Joe\Desktop\SOFT-master7o\SoftProject.py", line 35, in my_rgb2gray
img_gray = 0.5*img_rgb[:, :, 0] + 0*img_rgb[:, :, 1] + 0.5*img_rgb[:, :, 2]
TypeError: 'NoneType' object has no attribute '__getitem__'
What's wrong? Thanks
When you call a name with the square brackets, Python calla 'getitem' under the hood.
So it means that img_rgb is not what you expect it to be. Instead of a numpy array it is None.
Check the portion of code where img_rgb is assigned to.
Answer to comment:
Check your inputs before you do operations on them.
I would use the VS inbuild debugger and set a breakpoint on this line:
bw = my_rgb2gray(frame)
and inspect each frame if it is None before entering the function.
How to handle it if its None? Depends - either skip that video-frame or, if all are None, something is amiss and you need to check why flag, frame = video.read() produces a frame that is None. Sometimes the documentation will help you out:
https://docs.opencv.org/2.4/modules/highgui/doc/reading_and_writing_images_and_video.html#videocapture
https://docs.opencv.org/2.4/modules/highgui/doc/reading_and_writing_images_and_video.html#videocapture-read

TypeError: argument 1 must be ImagingCore, not ImagingCore

Under the Windows I get this error. How to fix PIL?
This is error: TypeError: argument 1 must be ImagingCore, not ImagingCore
#!/usr/bin/python
## -*- coding: utf-8 -*-
from PIL import Image, ImageFont
import ImageDraw, StringIO, string
from random import *
from math import *
import os
SITE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
class Captcha3d(object):
_hypot = 4
_xx, _yy = 35, 70
_CIMAGE = None
_CHARS = string.ascii_lowercase + string.ascii_uppercase + string.digits
_TEXT = ''
def __init__(self):
self._CIMAGE = Image.new("RGB", (self._yy * self._hypot, self._xx * self._hypot), (255,255,255))
self.generateCode()
self.render()
def imageColorAllocate(self, r,g,b):
hexchars = "0123456789ABCDEF"
hexcolor = hexchars[r / 16] + hexchars[r % 16] + hexchars[g / 16] + hexchars[g % 16] + hexchars[b / 16] + hexchars[b % 16]
return int(hexcolor, 16)
def generateCode(self):
chars = self._CHARS
self._TEXT = "".join(choice(chars) for x in range(randint(3, 3)))
def getText(self):
return self._TEXT
def getProection(self, x1,y1,z1):
x = x1 * self._hypot
y = z1 * self._hypot
z = -y1 * self._hypot
xx = 0.707106781187
xy = 0
xz = -0.707106781187
yx = 0.408248290464
yy = 0.816496580928
yz = 0.408248290464 # 1/sqrt(6)
cx = xx*x + xy*y + xz*z
cy = yx*x + yy*y + yz*z + 20*self._hypot
return [cx, cy]
def zFunction(self, x,y):
z = 2.6
if self._CIMAGE.getpixel((y/2,x/2)) == (0,0,0):
z = 0
if z != 0:
z += float(randint(0,60))/100
z += 1.4 * sin((x+self.startX)*pi/15) * sin((y+self.startY)*pi/15)
return z
def render(self):
fontSans = ImageFont.truetype(os.path.join(SITE_PATH, "data", "fonts", "FreeSans.ttf"), 14)
draw = ImageDraw.Draw(self._CIMAGE)
whiteColor = 'white'
draw.rectangle([0, 0, self._yy * self._hypot, self._xx * self._hypot], fill=whiteColor)
#textColor = 'black'
#imgtext = Image.open("i8n.png")
#self._CIMAGE.paste(imgtext, (0,0))
imgtext = Image.new("1", (self._yy * self._hypot, self._xx * self._hypot), (1))
drawtext = ImageDraw.Draw(imgtext)
drawtext.text((1,0), self._TEXT, font=fontSans, fill=0)
self._CIMAGE.paste(imgtext, (0,0))
#draw.text((2,0), self.text, font=fontSans, fill=textColor)
self.startX = randint(0,self._xx)
self.startY = randint(0,self._yy)
crd = {}
x = 0
while x < (self._xx+1):
y = 0
while y < (self._yy+1):
crd[str(x) + '&' + str(y)] = self.getProection(x,y,self.zFunction(x,y))
y += 1
x += 1
x = 0
while x < self._xx:
y = 0
while y < self._yy:
coord = []
coord.append((int(crd[str(x) + '&' + str(y)][0]),int(crd[str(x) + '&' + str(y)][1])))
coord.append((int(crd[str(x+1) + '&' + str(y)][0]),int(crd[str(x+1) + '&' + str(y)][1])))
coord.append((int(crd[str(x+1) + '&' + str(y+1)][0]),int(crd[str(x+1) + '&' + str(y+1)][1])))
coord.append((int(crd[str(x) + '&' + str(y+1)][0]),int(crd[str(x) + '&' + str(y+1)][1])))
c = int(self.zFunction(x,y)*32)
linesColor = (c,c,c)
draw.polygon(coord, fill=whiteColor, outline=linesColor)
#draw.polygon(coord, fill=whiteColor)
y += 1
x += 1
draw.rectangle([0, 0, self._xx, self._yy], fill=whiteColor)
#draw.text((2,0), self.text, font=fontSans, fill=textColor)
#imageString($this->image, 1, 3, 0, (microtime(true)-$this->time), $textColor);
del draw
#self._CIMAGE.save("image.png", "PNG")
return [self._CIMAGE, self._TEXT]
def main():
a = Captcha3d()
print a.getText()
if __name__ == '__main__':
main()
Also happens for me on OSX 10.6.8, Python 2.6.5. I think some class is getting dynamically imported twice.
Try changing
from PIL import Image, ImageFont
to
import Image, ImageFont
That worked for me.
In my case it solved the situation to also import ImageDraw from PIL
from PIL import ImageDraw
importing all PIL things directly from PIL should always work.
However, if you mix imports, as such,
from PIL import Image
import ImageDraw
This can lead to conflict between two un-identical PIL libraries.
This can happen if you have installed both PIL and Pillow
We should really always do,
from PIL import Image
from PIL import ImageDraw
etc.
I.e. be specific about which package to use.

Categories