blob: 6f7def52147478f30670d93ca7d165f84f3e4025 [file] [log] [blame]
cristy3e2860c2010-01-24 01:36:30 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% FFFFF EEEEE AAA TTTTT U U RRRR EEEEE %
7% F E A A T U U R R E %
8% FFF EEE AAAAA T U U RRRR EEE %
9% F E A A T U U R R E %
10% F EEEEE A A T UUU R R EEEEE %
11% %
12% %
13% MagickCore Image Feature Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
20% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
21% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
44#include "magick/property.h"
45#include "magick/animate.h"
46#include "magick/blob.h"
47#include "magick/blob-private.h"
48#include "magick/cache.h"
49#include "magick/cache-private.h"
50#include "magick/cache-view.h"
51#include "magick/client.h"
52#include "magick/color.h"
53#include "magick/color-private.h"
54#include "magick/colorspace.h"
55#include "magick/colorspace-private.h"
56#include "magick/composite.h"
57#include "magick/composite-private.h"
58#include "magick/compress.h"
59#include "magick/constitute.h"
60#include "magick/deprecate.h"
61#include "magick/display.h"
62#include "magick/draw.h"
63#include "magick/enhance.h"
64#include "magick/exception.h"
65#include "magick/exception-private.h"
66#include "magick/feature.h"
67#include "magick/gem.h"
68#include "magick/geometry.h"
69#include "magick/list.h"
70#include "magick/image-private.h"
71#include "magick/magic.h"
72#include "magick/magick.h"
73#include "magick/memory_.h"
74#include "magick/module.h"
75#include "magick/monitor.h"
76#include "magick/monitor-private.h"
77#include "magick/option.h"
78#include "magick/paint.h"
79#include "magick/pixel-private.h"
80#include "magick/profile.h"
81#include "magick/quantize.h"
82#include "magick/random_.h"
83#include "magick/segment.h"
84#include "magick/semaphore.h"
85#include "magick/signature-private.h"
86#include "magick/string_.h"
87#include "magick/thread-private.h"
88#include "magick/timer.h"
89#include "magick/utility.h"
90#include "magick/version.h"
91
92/*
93%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94% %
95% %
96% %
97% G e t I m a g e C h a n n e l F e a t u r e s %
98% %
99% %
100% %
101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
102%
cristy7e9726d2010-01-26 02:08:40 +0000103% GetImageChannelFeatures() returns features for each channel in the image at
104% at 0, 45, 90, and 135 degrees for the specified distance. The features
105% include the angular second momentum, contrast, correlation, sum of squares:
106% variance, inverse difference moment, sum average, sum varience, sum entropy,
107% entropy, difference variance, difference entropy, information measures of
108% correlation 1, information measures of correlation 2, and maximum
109% correlation coefficient. You can access the red channel contrast, for
110% example, like this:
cristy3e2860c2010-01-24 01:36:30 +0000111%
cristy7e9726d2010-01-26 02:08:40 +0000112% channel_features=GetImageChannelFeatures(image,1,excepton);
cristy549a37e2010-01-26 15:24:15 +0000113% contrast=channel_features[RedChannel].contrast[0];
cristy3e2860c2010-01-24 01:36:30 +0000114%
115% Use MagickRelinquishMemory() to free the features buffer.
116%
117% The format of the GetImageChannelFeatures method is:
118%
119% ChannelFeatures *GetImageChannelFeatures(const Image *image,
cristy549a37e2010-01-26 15:24:15 +0000120% const unsigned long distance,ExceptionInfo *exception)
cristy3e2860c2010-01-24 01:36:30 +0000121%
122% A description of each parameter follows:
123%
124% o image: the image.
125%
cristy7e9726d2010-01-26 02:08:40 +0000126% o distance: the distance.
127%
cristy3e2860c2010-01-24 01:36:30 +0000128% o exception: return any errors or warnings in this structure.
129%
130*/
131MagickExport ChannelFeatures *GetImageChannelFeatures(const Image *image,
cristy7e9726d2010-01-26 02:08:40 +0000132 const unsigned long distance,ExceptionInfo *exception)
cristy3e2860c2010-01-24 01:36:30 +0000133{
cristyf2bf2c72010-01-25 19:54:15 +0000134 typedef struct _SpatialDependenceMatrix
135 {
cristy549a37e2010-01-26 15:24:15 +0000136 DoublePixelPacket
cristy7e9726d2010-01-26 02:08:40 +0000137 tones[4];
cristyf2bf2c72010-01-25 19:54:15 +0000138 } SpatialDependenceMatrix;
139
cristy2070fa52010-01-24 03:17:57 +0000140 CacheView
141 *image_view;
142
cristy3e2860c2010-01-24 01:36:30 +0000143 ChannelFeatures
144 *channel_features;
145
cristy2070fa52010-01-24 03:17:57 +0000146 LongPixelPacket
cristyf2bf2c72010-01-25 19:54:15 +0000147 tone,
148 *tones;
cristy2070fa52010-01-24 03:17:57 +0000149
cristy3e2860c2010-01-24 01:36:30 +0000150 long
151 y;
152
cristy2070fa52010-01-24 03:17:57 +0000153 MagickBooleanType
154 status;
155
156 register long
157 i;
158
cristy3e2860c2010-01-24 01:36:30 +0000159 size_t
160 length;
161
cristyf2bf2c72010-01-25 19:54:15 +0000162 SpatialDependenceMatrix
163 **pixels;
164
165 unsigned long
166 number_tones;
167
cristy3e2860c2010-01-24 01:36:30 +0000168 assert(image != (Image *) NULL);
169 assert(image->signature == MagickSignature);
170 if (image->debug != MagickFalse)
171 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy7e9726d2010-01-26 02:08:40 +0000172 if ((image->columns < (distance+1)) || (image->rows < (distance+1)))
173 return((ChannelFeatures *) NULL);
cristy3e2860c2010-01-24 01:36:30 +0000174 length=AllChannels+1UL;
175 channel_features=(ChannelFeatures *) AcquireQuantumMemory(length,
176 sizeof(*channel_features));
177 if (channel_features == (ChannelFeatures *) NULL)
178 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
179 (void) ResetMagickMemory(channel_features,0,length*
180 sizeof(*channel_features));
cristy2070fa52010-01-24 03:17:57 +0000181 /*
cristyf2bf2c72010-01-25 19:54:15 +0000182 Form tones.
cristy2070fa52010-01-24 03:17:57 +0000183 */
cristyf2bf2c72010-01-25 19:54:15 +0000184 tones=(LongPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*tones));
185 if (tones == (LongPixelPacket *) NULL)
cristy2070fa52010-01-24 03:17:57 +0000186 {
187 (void) ThrowMagickException(exception,GetMagickModule(),
188 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
189 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
190 channel_features);
191 return(channel_features);
192 }
193 for (i=0; i <= (long) MaxMap; i++)
194 {
cristyf2bf2c72010-01-25 19:54:15 +0000195 tones[i].red=(~0UL);
196 tones[i].green=(~0UL);
197 tones[i].blue=(~0UL);
198 tones[i].opacity=(~0UL);
199 tones[i].index=(~0UL);
cristy2070fa52010-01-24 03:17:57 +0000200 }
201 status=MagickTrue;
202 image_view=AcquireCacheView(image);
203#if defined(MAGICKCORE_OPENMP_SUPPORT)
204 #pragma omp parallel for schedule(dynamic,4) shared(status)
205#endif
cristy3e2860c2010-01-24 01:36:30 +0000206 for (y=0; y < (long) image->rows; y++)
207 {
208 register const IndexPacket
209 *restrict indexes;
210
211 register const PixelPacket
212 *restrict p;
213
214 register long
215 x;
216
cristy2070fa52010-01-24 03:17:57 +0000217 if (status == MagickFalse)
218 continue;
219 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3e2860c2010-01-24 01:36:30 +0000220 if (p == (const PixelPacket *) NULL)
cristy2070fa52010-01-24 03:17:57 +0000221 {
222 status=MagickFalse;
223 continue;
224 }
225 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3e2860c2010-01-24 01:36:30 +0000226 for (x=0; x < (long) image->columns; x++)
227 {
cristyf2bf2c72010-01-25 19:54:15 +0000228 tones[ScaleQuantumToMap(p->red)].red=ScaleQuantumToMap(p->red);
229 tones[ScaleQuantumToMap(p->green)].green=ScaleQuantumToMap(p->green);
230 tones[ScaleQuantumToMap(p->blue)].blue=ScaleQuantumToMap(p->blue);
cristy2070fa52010-01-24 03:17:57 +0000231 if (image->matte != MagickFalse)
cristyf2bf2c72010-01-25 19:54:15 +0000232 tones[ScaleQuantumToMap(p->opacity)].opacity=
cristy2070fa52010-01-24 03:17:57 +0000233 ScaleQuantumToMap(p->opacity);
234 if (image->colorspace == CMYKColorspace)
cristyf2bf2c72010-01-25 19:54:15 +0000235 tones[ScaleQuantumToMap(indexes[x])].index=
cristy2070fa52010-01-24 03:17:57 +0000236 ScaleQuantumToMap(indexes[x]);
cristy3e2860c2010-01-24 01:36:30 +0000237 p++;
238 }
239 }
cristy30c510a2010-01-24 03:43:00 +0000240 image_view=DestroyCacheView(image_view);
241 if (status == MagickFalse)
242 {
cristyf2bf2c72010-01-25 19:54:15 +0000243 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
cristy30c510a2010-01-24 03:43:00 +0000244 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
245 channel_features);
246 return(channel_features);
247 }
cristyf2bf2c72010-01-25 19:54:15 +0000248 (void) ResetMagickMemory(&tone,0,sizeof(tone));
cristy2070fa52010-01-24 03:17:57 +0000249 for (i=0; i <= (long) MaxMap; i++)
250 {
cristyf2bf2c72010-01-25 19:54:15 +0000251 if (tones[i].red != ~0UL)
252 tones[tone.red++].red=tones[i].red;
253 if (tones[i].green != ~0UL)
254 tones[tone.green++].green=tones[i].green;
255 if (tones[i].blue != ~0UL)
256 tones[tone.blue++].blue=tones[i].blue;
cristy2070fa52010-01-24 03:17:57 +0000257 if (image->matte != MagickFalse)
cristyf2bf2c72010-01-25 19:54:15 +0000258 if (tones[i].opacity != ~0UL)
259 tones[tone.opacity++].opacity=tones[i].opacity;
cristy2070fa52010-01-24 03:17:57 +0000260 if (image->colorspace == CMYKColorspace)
cristyf2bf2c72010-01-25 19:54:15 +0000261 if (tones[i].index != ~0UL)
262 tones[tone.index++].index=tones[i].index;
cristy2070fa52010-01-24 03:17:57 +0000263 }
cristyf2bf2c72010-01-25 19:54:15 +0000264 /*
265 Allocate spatial dependence matrix.
266 */
267 number_tones=tone.red;
268 if (tone.green > number_tones)
269 number_tones=tone.green;
270 if (tone.blue > number_tones)
271 number_tones=tone.blue;
272 if (image->matte != MagickFalse)
273 if (tone.opacity > number_tones)
274 number_tones=tone.opacity;
275 if (image->colorspace == CMYKColorspace)
276 if (tone.index > number_tones)
277 number_tones=tone.index;
278 pixels=(SpatialDependenceMatrix **) AcquireQuantumMemory(number_tones,
cristy7e9726d2010-01-26 02:08:40 +0000279 sizeof(*pixels));
cristyf2bf2c72010-01-25 19:54:15 +0000280 if (pixels == (SpatialDependenceMatrix **) NULL)
281 {
282 (void) ThrowMagickException(exception,GetMagickModule(),
283 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
284 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
285 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
286 channel_features);
287 return(channel_features);
288 }
cristy7e9726d2010-01-26 02:08:40 +0000289 for (i=0; i < (long) number_tones; i++)
cristyf2bf2c72010-01-25 19:54:15 +0000290 {
291 pixels[i]=(SpatialDependenceMatrix *) AcquireQuantumMemory(number_tones,
cristy7e9726d2010-01-26 02:08:40 +0000292 sizeof(**pixels));
cristyf2bf2c72010-01-25 19:54:15 +0000293 if (pixels[i] == (SpatialDependenceMatrix *) NULL)
294 break;
295 (void) ResetMagickMemory(pixels[i],0,number_tones*sizeof(*pixels));
296 }
cristy7e9726d2010-01-26 02:08:40 +0000297 if (i < (long) number_tones)
cristyf2bf2c72010-01-25 19:54:15 +0000298 {
299 (void) ThrowMagickException(exception,GetMagickModule(),
300 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
301 for (i--; i >= 0; i--)
302 pixels[i]=(SpatialDependenceMatrix *) RelinquishMagickMemory(pixels[i]);
303 pixels=(SpatialDependenceMatrix **) RelinquishMagickMemory(pixels);
304 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
305 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
306 channel_features);
307 return(channel_features);
308 }
309 /*
310 Initialize spatial dependence matrix.
311 */
312 status=MagickTrue;
313 image_view=AcquireCacheView(image);
314#if defined(MAGICKCORE_OPENMP_SUPPORT)
315 #pragma omp parallel for schedule(dynamic,4) shared(status)
316#endif
317 for (y=0; y < (long) image->rows; y++)
318 {
319 long
320 u,
321 v;
322
323 register const IndexPacket
324 *restrict indexes;
325
326 register const PixelPacket
327 *restrict p;
328
329 register long
330 x;
331
cristy7e9726d2010-01-26 02:08:40 +0000332 ssize_t
333 offset;
334
cristyf2bf2c72010-01-25 19:54:15 +0000335 if (status == MagickFalse)
336 continue;
cristy7e9726d2010-01-26 02:08:40 +0000337 p=GetCacheViewVirtualPixels(image_view,-(long) distance,y,image->columns+
338 2*distance,distance+1,exception);
cristyf2bf2c72010-01-25 19:54:15 +0000339 if (p == (const PixelPacket *) NULL)
340 {
341 status=MagickFalse;
342 continue;
343 }
344 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy7e9726d2010-01-26 02:08:40 +0000345 p+=distance;
346 indexes+=distance;
cristyf2bf2c72010-01-25 19:54:15 +0000347 for (x=0; x < (long) image->columns; x++)
348 {
349 for (i=0; i < 4; i++)
350 {
cristy7e9726d2010-01-26 02:08:40 +0000351 switch (i)
352 {
353 case 0:
cristy549a37e2010-01-26 15:24:15 +0000354 default:
cristy7e9726d2010-01-26 02:08:40 +0000355 {
356 /*
357 0 degrees.
358 */
359 offset=(ssize_t) distance;
360 break;
361 }
362 case 1:
363 {
364 /*
365 45 degrees.
366 */
367 offset=(ssize_t) (image->columns+2*distance)-distance;
368 break;
369 }
370 case 2:
371 {
372 /*
373 90 degrees.
374 */
375 offset=(ssize_t) (image->columns+2*distance);
376 break;
377 }
378 case 3:
379 {
380 /*
381 135 degrees.
382 */
383 offset=(ssize_t) (image->columns+2*distance)+distance;
384 break;
385 }
386 }
387 u=0;
388 v=0;
389 while (tones[u].red != ScaleQuantumToMap(p->red))
390 u++;
391 while (tones[v].red != ScaleQuantumToMap((p+offset)->red))
392 v++;
393 pixels[u][v].tones[i].red++;
394 pixels[v][u].tones[i].red++;
395 u=0;
396 v=0;
397 while (tones[u].green != ScaleQuantumToMap(p->green))
398 u++;
399 while (tones[v].green != ScaleQuantumToMap((p+offset)->green))
400 v++;
401 pixels[u][v].tones[i].green++;
402 pixels[v][u].tones[i].green++;
403 u=0;
404 v=0;
405 while (tones[u].blue != ScaleQuantumToMap(p->blue))
406 u++;
407 while (tones[v].blue != ScaleQuantumToMap((p+offset)->blue))
408 v++;
409 pixels[u][v].tones[i].blue++;
410 pixels[v][u].tones[i].blue++;
411 if (image->matte != MagickFalse)
412 {
413 u=0;
414 v=0;
415 while (tones[u].opacity != ScaleQuantumToMap(p->opacity))
416 u++;
417 while (tones[v].opacity != ScaleQuantumToMap((p+offset)->opacity))
418 v++;
419 pixels[u][v].tones[i].opacity++;
420 pixels[v][u].tones[i].opacity++;
421 }
422 if (image->colorspace == CMYKColorspace)
423 {
424 u=0;
425 v=0;
426 while (tones[u].index != ScaleQuantumToMap(indexes[x]))
427 u++;
428 while (tones[v].index != ScaleQuantumToMap(indexes[x+offset]))
429 v++;
430 pixels[u][v].tones[i].index++;
431 pixels[v][u].tones[i].index++;
432 }
cristyf2bf2c72010-01-25 19:54:15 +0000433 }
cristy7e9726d2010-01-26 02:08:40 +0000434 p++;
cristyf2bf2c72010-01-25 19:54:15 +0000435 }
436 }
437 image_view=DestroyCacheView(image_view);
438 if (status == MagickFalse)
439 {
440 (void) ThrowMagickException(exception,GetMagickModule(),
441 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
cristy7e9726d2010-01-26 02:08:40 +0000442 for (i=0; i < (long) number_tones; i++)
cristyf2bf2c72010-01-25 19:54:15 +0000443 pixels[i]=(SpatialDependenceMatrix *) RelinquishMagickMemory(pixels[i]);
444 pixels=(SpatialDependenceMatrix **) RelinquishMagickMemory(pixels);
445 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
446 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
447 channel_features);
448 return(channel_features);
449 }
450 /*
cristy7e9726d2010-01-26 02:08:40 +0000451 Normalize spatial dependence matrix.
452 */
453#if defined(MAGICKCORE_OPENMP_SUPPORT)
454 #pragma omp parallel for schedule(dynamic,4) shared(status)
455#endif
456 for (y=0; y < (long) number_tones; y++)
457 {
cristy549a37e2010-01-26 15:24:15 +0000458 double
459 normalize;
460
cristy7e9726d2010-01-26 02:08:40 +0000461 register long
462 x;
463
cristy7e9726d2010-01-26 02:08:40 +0000464 for (x=0; x < (long) number_tones; x++)
465 {
466 for (i=0; i < 4; i++)
467 {
468 switch (i)
469 {
470 case 0:
cristy549a37e2010-01-26 15:24:15 +0000471 default:
cristy7e9726d2010-01-26 02:08:40 +0000472 {
473 /*
474 0 degrees.
475 */
cristy549a37e2010-01-26 15:24:15 +0000476 normalize=2.0*image->rows*(image->columns-distance);
cristy7e9726d2010-01-26 02:08:40 +0000477 break;
478 }
479 case 1:
480 {
481 /*
482 45 degrees.
483 */
cristy549a37e2010-01-26 15:24:15 +0000484 normalize=2.0*(image->rows-distance)*(image->columns-distance);
cristy7e9726d2010-01-26 02:08:40 +0000485 break;
486 }
487 case 2:
488 {
489 /*
490 90 degrees.
491 */
cristy549a37e2010-01-26 15:24:15 +0000492 normalize=2.0*(image->rows-distance)*image->columns;
cristy7e9726d2010-01-26 02:08:40 +0000493 break;
494 }
495 case 3:
496 {
497 /*
498 135 degrees.
499 */
cristy549a37e2010-01-26 15:24:15 +0000500 normalize=2.0*(image->rows-distance)*(image->columns-distance);
cristy7e9726d2010-01-26 02:08:40 +0000501 break;
502 }
503 }
504 pixels[x][y].tones[i].red/=normalize;
cristy549a37e2010-01-26 15:24:15 +0000505 pixels[x][y].tones[i].green/=normalize;
506 pixels[x][y].tones[i].blue/=normalize;
507 if (image->matte != MagickFalse)
508 pixels[x][y].tones[i].opacity/=normalize;
509 if (image->colorspace == CMYKColorspace)
510 pixels[x][y].tones[i].index/=normalize;
511 }
512 }
513 }
514 /*
515 Compute texture features.
516 */
517 for (y=0; y < (long) number_tones; y++)
518 {
519 register long
520 x;
521
522 for (x=0; x < (long) number_tones; x++)
523 {
524 for (i=0; i < 4; i++)
525 {
526 /*
527 Angular second moment.
528 */
529 channel_features[RedChannel].angular_second_moment[i]+=
530 pixels[x][y].tones[i].red*pixels[x][y].tones[i].red;
531 channel_features[GreenChannel].angular_second_moment[i]+=
532 pixels[x][y].tones[i].green*pixels[x][y].tones[i].green;
533 channel_features[BlueChannel].angular_second_moment[i]+=
534 pixels[x][y].tones[i].blue*pixels[x][y].tones[i].blue;
535 if (image->matte != MagickFalse)
536 channel_features[OpacityChannel].angular_second_moment[i]+=
537 pixels[x][y].tones[i].opacity*pixels[x][y].tones[i].opacity;
538 if (image->colorspace == CMYKColorspace)
539 channel_features[IndexChannel].angular_second_moment[i]+=
540 pixels[x][y].tones[i].index*pixels[x][y].tones[i].index;
cristy7e9726d2010-01-26 02:08:40 +0000541 }
542 }
543 }
544 /*
cristyf2bf2c72010-01-25 19:54:15 +0000545 Relinquish resources.
546 */
cristy7e9726d2010-01-26 02:08:40 +0000547 for (i=0; i < (long) number_tones; i++)
cristyf2bf2c72010-01-25 19:54:15 +0000548 pixels[i]=(SpatialDependenceMatrix *) RelinquishMagickMemory(pixels[i]);
549 pixels=(SpatialDependenceMatrix **) RelinquishMagickMemory(pixels);
550 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
cristy3e2860c2010-01-24 01:36:30 +0000551 return(channel_features);
552}