Modify and Compare 2 images in Java - java

I have got an assignment where I need to validate images. I have got 2 sets of folders one which is actual and other folder contain expected images. These images are of some brands/Companies.
Upon initial investigation, I found that images of each brand have different dimension but are of same format i.e png
What I have done so far:- Upon googling I found the below code which compares 2 images. I ran this code for one of the brand and ofcourse the result was false. Then I modify one of the image such that both the images have same dimension.. even then i got the same result.
public void testImage() throws InterruptedException{
String file1="D:\\image\\bliss_url_2.png";
String file2="D:\\bliss.png";
Image image1 = Toolkit.getDefaultToolkit().getImage(file1);
Image image2 = Toolkit.getDefaultToolkit().getImage(file2);
PixelGrabber grab1 =new PixelGrabber(image1, 0, 0, -1, -1, true);
PixelGrabber grab2 =new PixelGrabber(image2, 0, 0, -1, -1, true);
int[] data1 = null;
if (grab1.grabPixels()) {
int width = grab1.getWidth();
int height = grab1.getHeight();
System.out.println("Initial width and height of Image 1:: "+width + ">>"+ height);
grab2.setDimensions(250, 100);
System.out.println("width and height of Image 1:: "+width + ">>"+ height);
data1 = new int[width * height];
data1 = (int[]) grab1.getPixels();
System.out.println("Image 1:: "+ data1);
}
int[] data2 = null;
if (grab2.grabPixels()) {
int width = grab2.getWidth();
int height = grab2.getHeight();
System.out.println("width and height of Image 2:: "+width + ">>"+ height);
data2 = new int[width * height];
data2 = (int[]) grab2.getPixels();
System.out.println("Image 2:: "+ data2.toString());
}
System.out.println("Pixels equal: " + java.util.Arrays.equals(data1, data2));
}
I just want to verify if the content of images are same i.e images belong to same brand ,if not then what are the differences
Please help me what should I do to do valid comparison.

Maybe you should not use some external library assuming it should be your own work. In this point of view, a way to compare images is to get the average color of the same portion of both images. If results are equals (or very similar due to compression errors etc)
Lets say we have two images
image 1 is 4 pixel. (to simplify each pixel is represented with a number but should be RGB)
1 2
3 4
[ (1+2+3+4) / 4 = 2.5 ]
image 2 is twice bigger
1 1 2 2
1 1 2 2
3 3 4 4
3 3 4 4
[ ((4*1)+(4*2)+(4*3)+(4*4)) / 16 = 2.5]
The average pixel value (color) is 2.5 in both images.
(with real pixel colors, compare the RGB colors separately. The three should be equal or very close)
That's the idea. Now, you should make this caumputing for each pixel of the smallest image and the corresponding pixels of the bigest one (according to the scale difference of both images)
Hope you'll find out a good solution !

Method setDimensions doesn't scale the image. Moreover, you shouldn't call it directly (see its java-doc). PixelGrabber is just a grabber to grab a subset of the pixels in an image. To scale the image use Image.getScaledInstance() http://docs.oracle.com/javase/7/docs/api/java/awt/Image.html#getScaledInstance(int,%20int,%20int) for instance
Even if you have got 2 images with the same size after scaling, you still cannot compare them pixel by pixel, since any algorithm of scaling is lossy by its nature. That means the only thing you can do is to check "similarity" of the images. I'd suggest to take a look at a great image processing library OpenCV which has a wrapper for Java:
Simple and fast method to compare images for similarity
http://docs.opencv.org/doc/tutorials/introduction/desktop_java/java_dev_intro.html

Related

How to scale PNG image until reaches the target file size

As the title says I'm trying to resize a PNG image in order to reach a target file size (in terms of MegaBytes).
I searched a lot on SO and over the web, found a lot of codes but all of them are not taking in consideration the final file size.
I've arranged some code but trying to optimize performance.
Example:
Source image dimensions = 30 MB
Target file output size = 5 MB
Current flow:
1 - Load the PNG image as BufferedImage
2 - Recursively use Scalr.resize(...) in order to resize the image
2.1 - For each step use ImageIO.write to store the compressed PNG on a temporary file
2.2 - Check the size using File.length, if size on disk is > 5 MB return to step 2
3 - Save the image using ImageIO.write(...)
This method works, fine-tuning some parameters (such as scale factor) I'm able to accomplish the task.
I'm trying to understand if I can improve all by calculating/guessing the final file size without storing the image on a temporary file.
There is a byte[] obj stored into the BufferedImage obj that I can get using BufferedImage.getData().getDataBuffer() that represents the content of the image but obviously the size of this array is 2x o 3x bigger than the final size of the file because of the PNG compression algo.
I've tried some formula in order to calculate the value, something like:
w * h * bitDepth * 8 / 1024 / 1024 but I'm sure that I'm loosing a lot of data and the accounts do not add up!
At the moment I'm using mainly this code:
static void resize(BufferedImage image, String outPath, int scalingFactor) throws Exception {
image = Scalr.resize(image, image.getWidth() - scalingFactor);
// image.getData().getDataBuffer() - the byteArray containing image
File tempFile = File.createTempFile("" + System.currentTimeMillis(), ".png");
ImageIO.write(image, "png", tempFile);
System.out.println("Calculated size in bytes is: " + tempFile.length() + " - factor: " + scalingFactor);
// MAX_SIZE defined in bytes
if (tempFile.length() > MAX_SIZE) {
// recursion starts here
resize(image, outPath, chooseFactor(tempFile, 4));
} else {
// break the recursive cycle
ImageIO.write(image, "png", new File(outPath));
}
}
static int chooseFactor(File image, int scale) {
// MEGABYTE is 1024*1024
double mbSize = (double) image.length() / MEGABYTE;
return (int) ((mbSize / scale) * 100);
}
There is a way to calculate/guess the final file size starting from BufferedImage object?
Please tell me if I have made myself clear or can I give additional information.
Also recommend a more appropriate title for the question if you think it is not explanatory enough.
Thanks.
Any monotone function along image width/height can be used to perform a binary search.
This approach will work well for many changes that may be needed
(changing from PNG to JPG, adding compression, changing optimization
targets) versus an ad-hoc solution such as directly predicting the
size of a PNG (which, for example, could simply change depending on
what library is installed on your production servers or on the client
that your application uses).
The stored bytes is expected to be monotone (anyway my implementation is safe [but not optimal] under no monotonic functions).
This function perform a binary search to the lower domain (e.g. not upscale the image) using any function:
static BufferedImage downScaleSearch(BufferedImage source, Function<BufferedImage, Boolean> downScale) {
int initialSize = Math.max(source.getWidth(), source.getHeight());
int a = 1;
int b = initialSize;
BufferedImage image = source;
while(true) {
int c = (a + b) / 2 - 1;
// fix point
if(c <= a)
return image;
BufferedImage scaled = Scalr.resize(source, c);
if(downScale.apply(scaled)) {
b = c;
} else {
// the last candidate will be the not greater than limit
image = scaled;
a = c;
}
}
}
if we are interested in the final PNG file size, the search function will be the PNG size:
static final Path output = Paths.get("/tmp/downscaled.png");
static long persistAndReturnSize(BufferedImage image) {
if(ImageIO.write(image, "png", output.toFile()))
return Files.size(output);
throw new RuntimeException("Cannot write PNG file!");
}
(you could persist to memory instead filesystem).
now, we can generate images with size no more than any fixed value
public static void main(String... args) throws IOException {
BufferedImage image = ImageIO.read(Paths.get("/home/josejuan/tmp/test.png").toFile());
for(long sz: asList(10_000, 30_000, 80_000, 150_000)) {
final long MAX_SIZE = sz;
BufferedImage bestFit = downScaleSearch(image, i -> persistAndReturnSize(i) >= MAX_SIZE);
ImageIO.write(bestFit, "png", output.toFile());
System.out.println("Size: " + sz + " >= " + Files.size(output));
}
}
with output
Size: 10000 >= 9794
Size: 30000 >= 29518
Size: 80000 >= 79050
Size: 150000 >= 143277
NOTE: if you do not use compression or you admit an approximation, probably you can replace the persistAndReturnSize function by an estimator without persist the image.
NOTE: our search space is size = 1, 2, ... but you could perform a similar search using more parameters like compression level, pixel color space, ... (although, probably, your domain then will be not monotone and you should use https://en.wikipedia.org/wiki/Gradient_descent or similar).

Getting HSV values of pixels in image OpenCV

I am working on a Rubik's side scanner to determine what state the cube is in. I am quite new to computer vision and using it so it has been a little bit of a challenge. What I have done so far is that I use a video capture and at certain frames capture that frame and save it for image processing. Here is what it looks like.
When the photo is taken the cube is in the same position each time so I don't have to worry about locating the stickers.
What I am having trouble doing is getting a small range of pixels in each square to determine its HSV.
I know the ranges of HSV are roughly
Red = Hue(0...9) AND Hue(151..180)
Orange = Hue(10...15)
Yellow = Hue(16..45)
Green = Hue(46..100)
Blue = Hue(101..150)
White = Saturation(0..20) AND Value(230..255)
So after I have captured the image I then load it and split the HSV values of the image but don't know how to get the certain pixel coordinates of the image. How do I do so?
BufferedImage getOneFrame() {
currFrame++;
//At the 90th frame I capture that frame and save that frame
if (currFrame == 120) {
cap.read(mat2Img.mat);
mat2Img.getImage(mat2Img.mat);
Imgcodecs.imwrite("firstImage.png", mat2Img.mat);
}
cap.read(mat2Img.mat);
return mat2Img.getImage(mat2Img.mat);
}
public void splitChannels() {
IplImage firstShot = cvLoadImage("firstImage.png");
//I split the channels so that I can determine the value of the pixel range
IplImage hsv = IplImage.create( firstShot.width(), firstShot.height(), firstShot.depth(), firstShot.nChannels());
IplImage hue = IplImage.create( firstShot.width(), firstShot.height(), firstShot.depth(), CV_8UC1 );
IplImage sat = IplImage.create( firstShot.width(), firstShot.height(), firstShot.depth(), CV_8UC1 );
IplImage val = IplImage.create( firstShot.width(), firstShot.height(), firstShot.depth(), CV_8UC1 );
cvSplit( hsv, hue, sat, val, null );
//How do I get a small range of pixels of my images to determine get their HSV?
}
If I understand your question right, you know the coordinates of all areas that interest you. Save the information about each area into cvRect objects.
You can traverse the rectangle area by looping. Make a double loop. In outer loop start at rect.y and stop before rect.y + rect.height. In inner loop, do a similar thing in x direction. Inside the loop, use CV_IMAGE_ELEM macro to access individual pixel values and compute whatever you need.
One advice though: There are several advantages to using Mat instead of IplImage when working with OpenCV. I recommend that you start using 'Mat', unless you have some special reasons to do so, of course. Click to see the documentation and take a look at one of constructors that takes one Mat and one Rect as parameters. This constructor is your good friend - you can create a new Mat object (without copying any data) which will only contain the area inside the rectangle.

Faster method to rotate an image in Python or Java

I have a quick question if someone of you can help me with this kind of information :).
What is the faster method to rotate an image with 90 degree(or multiples of 90 degree) if we speak about the execution speed and memory management.
I've search a lot with Google and I've found the faster method to do this is OpenCV in both languages Python or Java(and anothors languages).
It's true? Do you know and other method to rotate an image faster then 90 degree?
Thanks a lot for
JPEG images can be rotated without re-compressing the image data.
For a Python project, see jpegtran-cffi.
You probably can't get faster than that if you want to apply the rotation.
Another possibility is to edit the EXIF orientation of a JPEG image. It basically tells the viewer application on how to rotate the image. This is just changing a single value, however not all readers/viewers will support the orientation flag.
I had a more general question last week, how can I rotate an Image by any angel as fast as possible, and I ended up comparing different libraries which offered the rotation function in this article I wrote.
The quick answer is OpenCV, a more elaborate answer is written in the article:
I am going to focus on three most used libraries for image editing in python namely , Pillow, OpenCV and Scipy.
In the following code you can learn how to import these libraries and how to rotate an image using them. I have defined a function for each library to use it for our experiments
import numpy as np
import PIL
import cv2
import matplotlib.pylab as plt
from PIL import Image
from scipy.ndimage import rotate
from scipy.ndimage import interpolation
def rotate_PIL (image, angel, interpolation):
'''
input :
image : image : PIL image Object
angel : rotation angel : int
interpolation : interpolation mode : PIL.Image.interpolation_mode
Interpolation modes :
PIL.Image.NEAREST (use nearest neighbour), PIL.Image.BILINEAR (linear interpolation in a 2×2 environment), or PIL.Image.BICUBIC
https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.rotate
returns :
rotated image
'''
return image.rotate(angel,interpolation)
def rotate_CV(image, angel , interpolation):
'''
input :
image : image : ndarray
angel : rotation angel : int
interpolation : interpolation mode : cv2 Interpolation object
Interpolation modes :
interpolation cv2.INTER_CUBIC (slow) & cv2.INTER_LINEAR
https://theailearner.com/2018/11/15/image-interpolation-using-opencv-python/
returns :
rotated image : ndarray
'''
#in OpenCV we need to form the tranformation matrix and apply affine calculations
#
h,w = image.shape[:2]
cX,cY = (w//2,h//2)
M = cv2.getRotationMatrix2D((cX,cY),angel,1)
rotated = cv2.warpAffine(image,M , (w,h),flags=interpolation)
return rotated
def rotate_scipy(image, angel , interpolation):
'''
input :
image : image : ndarray
angel : rotation angel : int
interpolation : interpolation mode : int
Interpolation modes :
https://stackoverflow.com/questions/57777370/set-interpolation-method-in-scipy-ndimage-map-coordinates-to-nearest-and-bilinea
order=0 for nearest interpolation
order=1 for linear interpolation
returns :
rotated image : ndarray
'''
return scipy.ndimage.interpolation.rotate(image,angel,reshape=False,order=interpolation)
To understand which library is more efficient in rotating and interpolating images, we design a simple experiment at first. We apply a 20 degree rotation using all three libraries on a 200 x 200 pixel 8bit image generated by our function rand_8bit().
def rand_8bit(n):
im =np.random.rand(n,n)*255
im = im.astype(np.uint8)
im[n//2:n//2+n//2,n//2:n//4+n//2]= 0 # a self scaling rectangle
im[n//3:50+n//3,n//3:200+n//3]= 0 # a constant rectangle
return im
#generate images of 200x200 pixels
im = rand_8bit(200)
#for PIL library we need to first convert the image array into a PIL image object
image_for_PIL=Image.fromarray(im)
%timeit rotate_PIL(image_for_PIL,20,PIL.Image.BILINEAR)
%timeit rotate_CV(im,20,cv2.INTER_LINEAR)
%timeit rotate_scipy(im,20,1)
the result is :
987 µs ± 76 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
414 µs ± 79.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
4.46 ms ± 1.07 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
This means that OpenCV is the most efficient and Scipy is the slowest of them when it comes to image rotation.
The fastest way, known to me yet, to do basic image manipulation like rotating, cutting, resizing, and filtering is by using pillow module in python. OpenCV is used when advanced manipulations have to be done, that can't be done by Pillow. Pillow's rotate will answer your question.
Image.rotate(angle)
This is all you have to do to rotate the angle by any degree you want.
I have used in my java project this implementation of opencv to rotate images and I am pleased with the performance of the rotate image.
*OpenCV dependency version is like below.
<dependency>
<groupId>nu.pattern</groupId>
<artifactId>opencv</artifactId>
<version>2.4.9-4</version>
</dependency>
The method below does the rotation of image based on the angle you provide.
#Override
public BufferedImage rotateImage(BufferedImage image, double angle) {
Mat imageMat = OpenCVHelper.img2Mat(image);
// Calculate size of new matrix
double radians = Math.toRadians(angle);
double sin = Math.abs(Math.sin(radians));
double cos = Math.abs(Math.cos(radians));
int newWidth = (int) Math.floor(imageMat.width() * cos + imageMat.height() * sin);
int newHeight = (int) Math.floor(imageMat.width() * sin + imageMat.height() * cos);
int dx = (int) Math.floor(newWidth / 2 - (imageMat.width() / 2));
int dy = (int) Math.floor(newHeight / 2 - (imageMat.height() / 2));
// rotating image
Point center = new Point(imageMat.cols() / 2, imageMat.rows() / 2);
Mat rotMatrix = Imgproc.getRotationMatrix2D(center, 360 - angle, 1.0); // 1.0 means 100 % scale
// adjusting the boundaries of rotMatrix
double[] rot_0_2 = rotMatrix.get(0, 2);
for (int i = 0; i < rot_0_2.length; i++) {
rot_0_2[i] += dx;
}
rotMatrix.put(0, 2, rot_0_2);
double[] rot_1_2 = rotMatrix.get(1, 2);
for (int i = 0; i < rot_1_2.length; i++) {
rot_1_2[i] += dy;
}
rotMatrix.put(1, 2, rot_1_2);
Mat rotatedMat = new Mat();
Imgproc.warpAffine(imageMat, rotatedMat, rotMatrix, new Size(newWidth, newHeight));
return OpenCVHelper.mat2Img(rotatedMat);
}
The rotateImage method above takes an input an image of type BufferedImage and the angle in degrees that you need to rotate your image.
First operation of rotateImage method is to calculate the new width and new height that will have the rotated image by using the angle you provided and width and height of image that you want to rotate.
Second important operation is adjusting the boundaries of the matrix that is used to rotate the image. This is done to prevent the image to be cropped from the rotation operation.
Below is the class that i have used to convert the image from BufferedImage to Mat and vice versa.
public class OpenCVHelper {
/**
* The Mat type image is converted to BufferedImage type.
*
* #param mat
* #return
*/
public static BufferedImage mat2Img(Mat mat) {
BufferedImage image = new BufferedImage(mat.width(), mat.height(), BufferedImage.TYPE_3BYTE_BGR);
WritableRaster raster = image.getRaster();
DataBufferByte dataBuffer = (DataBufferByte) raster.getDataBuffer();
byte[] data = dataBuffer.getData();
mat.get(0, 0, data);
return image;
}
/**
* The BufferedImage type image is converted to Mat type.
*
* #param image
* #return
*/
public static Mat img2Mat(BufferedImage image) {
image = convertTo3ByteBGRType(image);
byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
Mat mat = new Mat(image.getHeight(), image.getWidth(), CvType.CV_8UC3);
mat.put(0, 0, data);
return mat;
}
}
In my case it was need for me to converted the image in BufferedImage. If you don't need that, you can skip , and read the image directly as Mat type and pass it to that method rotateImage.
public Mat rotateImage(File input, double angle) {
Mat imageMat = Highgui.imread(input.getAbsolutePath())
...
}

How can I get white colored pixel value from gray scale image and replace it with another color?

I am trying to get the value of the White Colored pixel from a GrayScale image and replace it with another Color but when I run my code, the whole GrayScale image is transfered to another Color. Can anyone please tell me where is fault in the code or how can I get my desired results??
This is the code...
public class gray {
public static void main (String args[])throws IOException{
int width;
int height;
BufferedImage myImage = null;
File f = new File("E:\\eclipse\\workspace\\Graphs\\src\\ColorToGray\\1.png");
myImage = ImageIO.read(f);
width = myImage.getWidth();
height = myImage.getHeight();
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
int pixels[];
pixels = new int[width * height];
myImage.getRGB(0, 0, width, height, pixels, 0, width);
for (int i = 0; i < pixels.length; i++) {
if (pixels[i] == 0xFFFFFF) {
pixels[i] = 0x000000FF;
}
}
File f2 = new File("E:\\eclipse\\workspace\\Graphs\\src\\ColorToGray\\out 1.png");
image.setRGB(0, 0, width, height, pixels, 0, width);
ImageIO.write( image, "jpg", f2);
}
}
Image Before:
Image Before Output
Image After:
Image After Output
I looked into it, and found a bunch of problems.
First of all, when specifying the filename to save, you supply a ".png" extension, but when you call the ImageIO.write() function, you specify file type "jpg". That tends not to work very well. If you try to open up the resulting file, most programs will give you a "this is not a valid .PNG file" error. Windows explorer tries to be smart, and re-interprets the .PNG as a .JPG, but this spared you from the chance of discovering your mistake.
This takes care of the strange redness problem.
However, if you specify "png" in ImageIO.write(), you still don't get the right image. One would expect an image that looks mostly like the original, with just a few patches of blue there where bright white used to be, but instead what we get is an overall brighter version of the original image.
I do not have enough time to look into your original image to find out what is really wrong with it, but I suspect that it is actually a bright image with an alpha mask that makes it look less bright, AND there is something wrong with the way the image gets saved that strips away alpha information, thus the apparent added brightness.
So, I tried your code with another image that I know has no tricks in it, and still your code did not appear to do anything. It turns out that the ARGB format of the int values you get from myImage.getRGB(); returns 255 for "A", which means that you need to be checking for 0xFFFFFFFF, not 0x00FFFFFF.
And of course when you replace a value, you must replace it with 0xFF0000FF, specifying a full alpha value. Replacing a pixel with 0x000000FF has no visible effect, because regardless of the high blue value, alpha is zero, so the pixel would be rendered transparent.

How to create a bitmap with information in every pixel?

I'm creating a google maps application on Android and I'm facing problem. I have elevation data in text format. It looks like this
longtitude latitude elevation
491222 163550 238.270000
491219 163551 242.130000
etc.
This elevation information is stored in grid of 10x10 meters. It means that for every 10 meters is an elevation value. This text is too large so that I could find there the information I need so I would want to create a bitmap with this information.
What I need to do is in certain moment to scan the elevation around my location. There can be a lot of points to be scanned so I want to make it quick. That's why I'm thinking about the bitmap.
I don't know if it's even possible but my idea is that there would be a bitmap of size of my text grid and in every pixel would be information about the elevation. So it should be like invisible map over the google map placed in the place according to coordinates and when I need to learn the elevation about my location, I would just look at these pixels and read the value of elevation.
Do you think that is possible to create such a bitmap? I have just this idea but no idea how to implement it. Eg how to store in it the elevation information, how to read that back, how to create the bitmap.. I would be very grateful for every advice, direction, source which you can give me. Thank you so much!
BufferedImage is not available in android but android.graphics.Bitmap can be used. Bitmap must be saved in lossless format (eg. PNG).
double[] elevations={238.27,242.1301,222,1};
int[] pixels = doublesToInts(elevations);
//encoding
Bitmap bmp=Bitmap.createBitmap(2, 2, Config.ARGB_8888);
bmp.setPixels(pixels, 0, 2, 0, 0, 2, 2);
File file=new File(getCacheDir(),"bitmap.png");
try {
FileOutputStream fos = new FileOutputStream(file);
bmp.compress(CompressFormat.PNG, 100, fos);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
//decoding
Bitmap out=BitmapFactory.decodeFile(file.getPath());
if (out!=null)
{
int [] outPixels=new int[out.getWidth()*out.getHeight()];
out.getPixels(outPixels, 0, out.getWidth(), 0, 0, out.getWidth(), out.getHeight());
double[] outElevations=intsToDoubles(outPixels);
}
static int[] doublesToInts(double[] elevations)
{
int[] out=new int[elevations.length];
for (int i=0;i<elevations.length;i++)
{
int tmp=(int) (elevations[i]*1000000);
out[i]=0xFF000000|tmp>>8;
}
return out;
}
static double[] intsToDoubles(int[] pixels)
{
double[] out=new double[pixels.length];
for (int i=0;i<pixels.length;i++)
out[i]=(pixels[i]<<8)/1000000.0;
return out;
}
As color with red, green, blue and alpha (opacity/transparency). Start with all pixels transparent. and fill in the corresponding value as (R, G, B), non-transparent (the high eight bits. (Or an other convention for "not filled in."
RGB form the lower 24 bits of an integer.
Longitude and latitude to x and y
Elevation to integer less 0x01_00_00_00. And vice versa:
double elevation = 238.27;
int code = (int)(elevation * 100);
Color c = new Color(code); // BufferedImage uses int, so 'code' sufThat does not fices.
code = c.getRGB();
elevation = ((double)code) / 100;
BufferedImage with setRGB(code) or so (there are different possibilities).
Use Oracles javadoc, by googling after BufferedImage and such.
To fill unused pixels do an avaraging, in a second BufferedImage. So as never to average to original pixels.
P.S. for my Netherlands elevation might be less than zero, so maybe + ... .

Categories