Coverage Report

Created: 2026-09-13 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/imagemagick/MagickCore/feature.c
Line
Count
Source
1
/*
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
%                                   Cristy                                    %
17
%                                 July 1992                                   %
18
%                                                                             %
19
%                                                                             %
20
%  Copyright @ 1999 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
%    https://imagemagick.org/license/                                         %
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 "MagickCore/studio.h"
44
#include "MagickCore/animate.h"
45
#include "MagickCore/artifact.h"
46
#include "MagickCore/blob.h"
47
#include "MagickCore/blob-private.h"
48
#include "MagickCore/cache.h"
49
#include "MagickCore/cache-private.h"
50
#include "MagickCore/cache-view.h"
51
#include "MagickCore/channel.h"
52
#include "MagickCore/client.h"
53
#include "MagickCore/color.h"
54
#include "MagickCore/color-private.h"
55
#include "MagickCore/colorspace.h"
56
#include "MagickCore/colorspace-private.h"
57
#include "MagickCore/composite.h"
58
#include "MagickCore/composite-private.h"
59
#include "MagickCore/compress.h"
60
#include "MagickCore/constitute.h"
61
#include "MagickCore/display.h"
62
#include "MagickCore/draw.h"
63
#include "MagickCore/enhance.h"
64
#include "MagickCore/exception.h"
65
#include "MagickCore/exception-private.h"
66
#include "MagickCore/feature.h"
67
#include "MagickCore/gem.h"
68
#include "MagickCore/geometry.h"
69
#include "MagickCore/list.h"
70
#include "MagickCore/image-private.h"
71
#include "MagickCore/magic.h"
72
#include "MagickCore/magick.h"
73
#include "MagickCore/matrix.h"
74
#include "MagickCore/memory_.h"
75
#include "MagickCore/module.h"
76
#include "MagickCore/monitor.h"
77
#include "MagickCore/monitor-private.h"
78
#include "MagickCore/morphology-private.h"
79
#include "MagickCore/nt-base-private.h"
80
#include "MagickCore/option.h"
81
#include "MagickCore/paint.h"
82
#include "MagickCore/pixel-accessor.h"
83
#include "MagickCore/profile.h"
84
#include "MagickCore/property.h"
85
#include "MagickCore/quantize.h"
86
#include "MagickCore/quantum-private.h"
87
#include "MagickCore/random_.h"
88
#include "MagickCore/resource_.h"
89
#include "MagickCore/segment.h"
90
#include "MagickCore/semaphore.h"
91
#include "MagickCore/signature-private.h"
92
#include "MagickCore/statistic-private.h"
93
#include "MagickCore/string_.h"
94
#include "MagickCore/thread-private.h"
95
#include "MagickCore/timer.h"
96
#include "MagickCore/utility.h"
97
#include "MagickCore/utility-private.h"
98
#include "MagickCore/version.h"
99

100
/*
101
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
102
%                                                                             %
103
%                                                                             %
104
%                                                                             %
105
%     C a n n y E d g e I m a g e                                             %
106
%                                                                             %
107
%                                                                             %
108
%                                                                             %
109
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
110
%
111
%  CannyEdgeImage() uses a multi-stage algorithm to detect a wide range of
112
%  edges in images.
113
%
114
%  The format of the CannyEdgeImage method is:
115
%
116
%      Image *CannyEdgeImage(const Image *image,const double radius,
117
%        const double sigma,const double lower_percent,
118
%        const double upper_percent,ExceptionInfo *exception)
119
%
120
%  A description of each parameter follows:
121
%
122
%    o image: the image.
123
%
124
%    o radius: the radius of the gaussian smoothing filter.
125
%
126
%    o sigma: the sigma of the gaussian smoothing filter.
127
%
128
%    o lower_percent: percentage of edge pixels in the lower threshold.
129
%
130
%    o upper_percent: percentage of edge pixels in the upper threshold.
131
%
132
%    o exception: return any errors or warnings in this structure.
133
%
134
*/
135
136
typedef struct _CannyInfo
137
{
138
  double
139
    magnitude,
140
    intensity;
141
142
  int
143
    orientation;
144
145
  ssize_t
146
    x,
147
    y;
148
} CannyInfo;
149
150
static inline MagickBooleanType IsAuthenticPixel(const Image *image,
151
  const ssize_t x,const ssize_t y)
152
0
{
153
0
  if ((x < 0) || (x >= (ssize_t) image->columns))
154
0
    return(MagickFalse);
155
0
  if ((y < 0) || (y >= (ssize_t) image->rows))
156
0
    return(MagickFalse);
157
0
  return(MagickTrue);
158
0
}
159
160
static MagickBooleanType TraceEdges(Image *edge_image,CacheView *edge_view,
161
  MatrixInfo *canny_cache,const ssize_t x,const ssize_t y,
162
  const double lower_threshold,ExceptionInfo *exception)
163
0
{
164
0
  CannyInfo
165
0
    edge,
166
0
    pixel;
167
168
0
  MagickBooleanType
169
0
    status;
170
171
0
  Quantum
172
0
    *q;
173
174
0
  ssize_t
175
0
    i;
176
177
0
  q=GetCacheViewAuthenticPixels(edge_view,x,y,1,1,exception);
178
0
  if (q == (Quantum *) NULL)
179
0
    return(MagickFalse);
180
0
  *q=QuantumRange;
181
0
  status=SyncCacheViewAuthenticPixels(edge_view,exception);
182
0
  if (status == MagickFalse)
183
0
    return(MagickFalse);
184
0
  if (GetMatrixElement(canny_cache,0,0,&edge) == MagickFalse)
185
0
    return(MagickFalse);
186
0
  edge.x=x;
187
0
  edge.y=y;
188
0
  if (SetMatrixElement(canny_cache,0,0,&edge) == MagickFalse)
189
0
    return(MagickFalse);
190
0
  for (i=1; i != 0; )
191
0
  {
192
0
    ssize_t
193
0
      v;
194
195
0
    i--;
196
0
    status=GetMatrixElement(canny_cache,i,0,&edge);
197
0
    if (status == MagickFalse)
198
0
      return(MagickFalse);
199
0
    for (v=(-1); v <= 1; v++)
200
0
    {
201
0
      ssize_t
202
0
        u;
203
204
0
      for (u=(-1); u <= 1; u++)
205
0
      {
206
0
        if ((u == 0) && (v == 0))
207
0
          continue;
208
0
        if (IsAuthenticPixel(edge_image,edge.x+u,edge.y+v) == MagickFalse)
209
0
          continue;
210
        /*
211
          Not an edge if gradient value is below the lower threshold.
212
        */
213
0
        q=GetCacheViewAuthenticPixels(edge_view,edge.x+u,edge.y+v,1,1,
214
0
          exception);
215
0
        if (q == (Quantum *) NULL)
216
0
          return(MagickFalse);
217
0
        status=GetMatrixElement(canny_cache,edge.x+u,edge.y+v,&pixel);
218
0
        if (status == MagickFalse)
219
0
          return(MagickFalse);
220
0
        if ((GetPixelIntensity(edge_image,q) == 0.0) &&
221
0
            (pixel.intensity >= lower_threshold))
222
0
          {
223
0
            *q=QuantumRange;
224
0
            status=SyncCacheViewAuthenticPixels(edge_view,exception);
225
0
            if (status == MagickFalse)
226
0
              return(MagickFalse);
227
0
            edge.x+=u;
228
0
            edge.y+=v;
229
0
            status=SetMatrixElement(canny_cache,i,0,&edge);
230
0
            if (status == MagickFalse)
231
0
              return(MagickFalse);
232
0
            i++;
233
0
          }
234
0
      }
235
0
    }
236
0
  }
237
0
  return(MagickTrue);
238
0
}
239
240
MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
241
  const double sigma,const double lower_percent,const double upper_percent,
242
  ExceptionInfo *exception)
243
0
{
244
0
#define CannyEdgeImageTag  "CannyEdge/Image"
245
246
0
  CacheView
247
0
    *edge_view;
248
249
0
  CannyInfo
250
0
    element;
251
252
0
  char
253
0
    geometry[MagickPathExtent];
254
255
0
  double
256
0
    lower_threshold,
257
0
    max,
258
0
    min,
259
0
    upper_threshold;
260
261
0
  Image
262
0
    *edge_image;
263
264
0
  KernelInfo
265
0
    *kernel_info;
266
267
0
  MagickBooleanType
268
0
    status;
269
270
0
  MagickOffsetType
271
0
    progress;
272
273
0
  MatrixInfo
274
0
    *canny_cache;
275
276
0
  ssize_t
277
0
    y;
278
279
0
  assert(image != (const Image *) NULL);
280
0
  assert(image->signature == MagickCoreSignature);
281
0
  assert(exception != (ExceptionInfo *) NULL);
282
0
  assert(exception->signature == MagickCoreSignature);
283
0
  if (IsEventLogging() != MagickFalse)
284
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
285
  /*
286
    Filter out noise.
287
  */
288
0
  (void) FormatLocaleString(geometry,MagickPathExtent,
289
0
    "blur:%.17gx%.17g;blur:%.17gx%.17g+90",radius,sigma,radius,sigma);
290
0
  kernel_info=AcquireKernelInfo(geometry,exception);
291
0
  if (kernel_info == (KernelInfo *) NULL)
292
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
293
0
  edge_image=MorphologyImage(image,ConvolveMorphology,1,kernel_info,exception);
294
0
  kernel_info=DestroyKernelInfo(kernel_info);
295
0
  if (edge_image == (Image *) NULL)
296
0
    return((Image *) NULL);
297
0
  if (TransformImageColorspace(edge_image,GRAYColorspace,exception) == MagickFalse)
298
0
    {
299
0
      edge_image=DestroyImage(edge_image);
300
0
      return((Image *) NULL);
301
0
    }
302
0
  (void) SetImageAlphaChannel(edge_image,OffAlphaChannel,exception);
303
  /*
304
    Find the intensity gradient of the image.
305
  */
306
0
  canny_cache=AcquireMatrixInfo(edge_image->columns,edge_image->rows,
307
0
    sizeof(CannyInfo),exception);
308
0
  if (canny_cache == (MatrixInfo *) NULL)
309
0
    {
310
0
      edge_image=DestroyImage(edge_image);
311
0
      return((Image *) NULL);
312
0
    }
313
0
  status=MagickTrue;
314
0
  edge_view=AcquireVirtualCacheView(edge_image,exception);
315
#if defined(MAGICKCORE_OPENMP_SUPPORT)
316
  #pragma omp parallel for schedule(static) shared(status) \
317
    magick_number_threads(edge_image,edge_image,edge_image->rows,1)
318
#endif
319
0
  for (y=0; y < (ssize_t) edge_image->rows; y++)
320
0
  {
321
0
    const Quantum
322
0
      *magick_restrict p;
323
324
0
    ssize_t
325
0
      x;
326
327
0
    if (status == MagickFalse)
328
0
      continue;
329
0
    p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns+1,2,
330
0
      exception);
331
0
    if (p == (const Quantum *) NULL)
332
0
      {
333
0
        status=MagickFalse;
334
0
        continue;
335
0
      }
336
0
    for (x=0; x < (ssize_t) edge_image->columns; x++)
337
0
    {
338
0
      CannyInfo
339
0
        pixel;
340
341
0
      double
342
0
        dx,
343
0
        dy;
344
345
0
      const Quantum
346
0
        *magick_restrict kernel_pixels;
347
348
0
      ssize_t
349
0
        v;
350
351
0
      static double
352
0
        Gx[2][2] =
353
0
        {
354
0
          { -1.0,  +1.0 },
355
0
          { -1.0,  +1.0 }
356
0
        },
357
0
        Gy[2][2] =
358
0
        {
359
0
          { +1.0, +1.0 },
360
0
          { -1.0, -1.0 }
361
0
        };
362
363
0
      (void) memset(&pixel,0,sizeof(pixel));
364
0
      dx=0.0;
365
0
      dy=0.0;
366
0
      kernel_pixels=p;
367
0
      for (v=0; v < 2; v++)
368
0
      {
369
0
        ssize_t
370
0
          u;
371
372
0
        for (u=0; u < 2; u++)
373
0
        {
374
0
          double
375
0
            intensity;
376
377
0
          intensity=GetPixelIntensity(edge_image,kernel_pixels+u);
378
0
          dx+=0.5*Gx[v][u]*intensity;
379
0
          dy+=0.5*Gy[v][u]*intensity;
380
0
        }
381
0
        kernel_pixels+=edge_image->columns+1;
382
0
      }
383
0
      pixel.magnitude=hypot(dx,dy);
384
0
      pixel.orientation=0;
385
0
      if (fabs(dx) > MagickEpsilon)
386
0
        {
387
0
          double
388
0
            slope;
389
390
0
          slope=dy/dx;
391
0
          if (slope < 0.0)
392
0
            {
393
0
              if (slope < -2.41421356237)
394
0
                pixel.orientation=0;
395
0
              else
396
0
                if (slope < -0.414213562373)
397
0
                  pixel.orientation=1;
398
0
                else
399
0
                  pixel.orientation=2;
400
0
            }
401
0
          else
402
0
            {
403
0
              if (slope > 2.41421356237)
404
0
                pixel.orientation=0;
405
0
              else
406
0
                if (slope > 0.414213562373)
407
0
                  pixel.orientation=3;
408
0
                else
409
0
                  pixel.orientation=2;
410
0
            }
411
0
        }
412
0
      if (SetMatrixElement(canny_cache,x,y,&pixel) == MagickFalse)
413
0
        continue;
414
0
      p+=(ptrdiff_t) GetPixelChannels(edge_image);
415
0
    }
416
0
  }
417
0
  edge_view=DestroyCacheView(edge_view);
418
  /*
419
    Non-maxima suppression, remove pixels that are not considered to be part
420
    of an edge.
421
  */
422
0
  progress=0;
423
0
  (void) GetMatrixElement(canny_cache,0,0,&element);
424
0
  max=element.intensity;
425
0
  min=element.intensity;
426
0
  edge_view=AcquireAuthenticCacheView(edge_image,exception);
427
#if defined(MAGICKCORE_OPENMP_SUPPORT)
428
  #pragma omp parallel for schedule(static) shared(status) \
429
    magick_number_threads(edge_image,edge_image,edge_image->rows,1)
430
#endif
431
0
  for (y=0; y < (ssize_t) edge_image->rows; y++)
432
0
  {
433
0
    Quantum
434
0
      *magick_restrict q;
435
436
0
    ssize_t
437
0
      x;
438
439
0
    if (status == MagickFalse)
440
0
      continue;
441
0
    q=GetCacheViewAuthenticPixels(edge_view,0,y,edge_image->columns,1,
442
0
      exception);
443
0
    if (q == (Quantum *) NULL)
444
0
      {
445
0
        status=MagickFalse;
446
0
        continue;
447
0
      }
448
0
    for (x=0; x < (ssize_t) edge_image->columns; x++)
449
0
    {
450
0
      CannyInfo
451
0
        alpha_pixel,
452
0
        beta_pixel,
453
0
        pixel;
454
455
0
      (void) GetMatrixElement(canny_cache,x,y,&pixel);
456
0
      switch (pixel.orientation)
457
0
      {
458
0
        case 0:
459
0
        default:
460
0
        {
461
          /*
462
            0 degrees, north and south.
463
          */
464
0
          (void) GetMatrixElement(canny_cache,x,y-1,&alpha_pixel);
465
0
          (void) GetMatrixElement(canny_cache,x,y+1,&beta_pixel);
466
0
          break;
467
0
        }
468
0
        case 1:
469
0
        {
470
          /*
471
            45 degrees, northwest and southeast.
472
          */
473
0
          (void) GetMatrixElement(canny_cache,x-1,y-1,&alpha_pixel);
474
0
          (void) GetMatrixElement(canny_cache,x+1,y+1,&beta_pixel);
475
0
          break;
476
0
        }
477
0
        case 2:
478
0
        {
479
          /*
480
            90 degrees, east and west.
481
          */
482
0
          (void) GetMatrixElement(canny_cache,x-1,y,&alpha_pixel);
483
0
          (void) GetMatrixElement(canny_cache,x+1,y,&beta_pixel);
484
0
          break;
485
0
        }
486
0
        case 3:
487
0
        {
488
          /*
489
            135 degrees, northeast and southwest.
490
          */
491
0
          (void) GetMatrixElement(canny_cache,x+1,y-1,&beta_pixel);
492
0
          (void) GetMatrixElement(canny_cache,x-1,y+1,&alpha_pixel);
493
0
          break;
494
0
        }
495
0
      }
496
0
      pixel.intensity=pixel.magnitude;
497
0
      if ((pixel.magnitude < alpha_pixel.magnitude) ||
498
0
          (pixel.magnitude < beta_pixel.magnitude))
499
0
        pixel.intensity=0;
500
0
      (void) SetMatrixElement(canny_cache,x,y,&pixel);
501
#if defined(MAGICKCORE_OPENMP_SUPPORT)
502
      #pragma omp critical (MagickCore_CannyEdgeImage)
503
#endif
504
0
      {
505
0
        if (pixel.intensity < min)
506
0
          min=pixel.intensity;
507
0
        if (pixel.intensity > max)
508
0
          max=pixel.intensity;
509
0
      }
510
0
      *q=(Quantum) 0;
511
0
      q+=(ptrdiff_t) GetPixelChannels(edge_image);
512
0
    }
513
0
    if (SyncCacheViewAuthenticPixels(edge_view,exception) == MagickFalse)
514
0
      status=MagickFalse;
515
0
  }
516
0
  edge_view=DestroyCacheView(edge_view);
517
  /*
518
    Estimate hysteresis threshold.
519
  */
520
0
  lower_threshold=lower_percent*(max-min)+min;
521
0
  upper_threshold=upper_percent*(max-min)+min;
522
  /*
523
    Hysteresis threshold.
524
  */
525
0
  edge_view=AcquireAuthenticCacheView(edge_image,exception);
526
0
  for (y=0; y < (ssize_t) edge_image->rows; y++)
527
0
  {
528
0
    ssize_t
529
0
      x;
530
531
0
    if (status == MagickFalse)
532
0
      continue;
533
0
    for (x=0; x < (ssize_t) edge_image->columns; x++)
534
0
    {
535
0
      CannyInfo
536
0
        pixel;
537
538
0
      const Quantum
539
0
        *magick_restrict p;
540
541
      /*
542
        Edge if pixel gradient higher than upper threshold.
543
      */
544
0
      p=GetCacheViewVirtualPixels(edge_view,x,y,1,1,exception);
545
0
      if (p == (const Quantum *) NULL)
546
0
        continue;
547
0
      status=GetMatrixElement(canny_cache,x,y,&pixel);
548
0
      if (status == MagickFalse)
549
0
        continue;
550
0
      if ((GetPixelIntensity(edge_image,p) == 0.0) &&
551
0
          (pixel.intensity >= upper_threshold))
552
0
        status=TraceEdges(edge_image,edge_view,canny_cache,x,y,lower_threshold,
553
0
          exception);
554
0
    }
555
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
556
0
      {
557
0
        MagickBooleanType
558
0
          proceed;
559
560
#if defined(MAGICKCORE_OPENMP_SUPPORT)
561
        #pragma omp atomic
562
#endif
563
0
        progress++;
564
0
        proceed=SetImageProgress(image,CannyEdgeImageTag,progress,image->rows);
565
0
        if (proceed == MagickFalse)
566
0
          status=MagickFalse;
567
0
      }
568
0
  }
569
0
  edge_view=DestroyCacheView(edge_view);
570
  /*
571
    Free resources.
572
  */
573
0
  canny_cache=DestroyMatrixInfo(canny_cache);
574
0
  return(edge_image);
575
0
}
576

577
/*
578
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
579
%                                                                             %
580
%                                                                             %
581
%                                                                             %
582
%   G e t I m a g e F e a t u r e s                                           %
583
%                                                                             %
584
%                                                                             %
585
%                                                                             %
586
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
587
%
588
%  GetImageFeatures() returns features for each channel in the image in
589
%  each of four directions (horizontal, vertical, left and right diagonals)
590
%  for the specified distance.  The features include the angular second
591
%  moment, contrast, correlation, sum of squares: variance, inverse difference
592
%  moment, sum average, sum variance, sum entropy, entropy, difference variance,
593
%  difference entropy, information measures of correlation 1, information
594
%  measures of correlation 2, and maximum correlation coefficient.  You can
595
%  access the red channel contrast, for example, like this:
596
%
597
%      channel_features=GetImageFeatures(image,1,exception);
598
%      contrast=channel_features[RedPixelChannel].contrast[0];
599
%
600
%  Use MagickRelinquishMemory() to free the features buffer.
601
%
602
%  The format of the GetImageFeatures method is:
603
%
604
%      ChannelFeatures *GetImageFeatures(const Image *image,
605
%        const size_t distance,ExceptionInfo *exception)
606
%
607
%  A description of each parameter follows:
608
%
609
%    o image: the image.
610
%
611
%    o distance: the distance.
612
%
613
%    o exception: return any errors or warnings in this structure.
614
%
615
*/
616
MagickExport ChannelFeatures *GetImageFeatures(const Image *image,
617
  const size_t distance,ExceptionInfo *exception)
618
0
{
619
0
  typedef struct _ChannelStatistics
620
0
  {
621
0
    PixelInfo
622
0
      direction[4];  /* horizontal, vertical, left and right diagonals */
623
0
  } ChannelStatistics;
624
625
0
  CacheView
626
0
    *image_view;
627
628
0
  ChannelFeatures
629
0
    *channel_features;
630
631
0
  ChannelStatistics
632
0
    **cooccurrence,
633
0
    correlation,
634
0
    *density_x,
635
0
    *density_xy,
636
0
    *density_y,
637
0
    entropy_x,
638
0
    entropy_xy,
639
0
    entropy_xy1,
640
0
    entropy_xy2,
641
0
    entropy_y,
642
0
    mean,
643
0
    **Q,
644
0
    *sum,
645
0
    sum_squares,
646
0
    variance;
647
648
0
  PixelPacket
649
0
    gray,
650
0
    *grays;
651
652
0
  MagickBooleanType
653
0
    status;
654
655
0
  ssize_t
656
0
    i,
657
0
    r;
658
659
0
  size_t
660
0
    length;
661
662
0
  unsigned int
663
0
    number_grays;
664
665
0
  assert(image != (Image *) NULL);
666
0
  assert(image->signature == MagickCoreSignature);
667
0
  if (IsEventLogging() != MagickFalse)
668
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
669
0
  if ((image->columns < (distance+1)) || (image->rows < (distance+1)))
670
0
    return((ChannelFeatures *) NULL);
671
0
  length=MaxPixelChannels+1UL;
672
0
  channel_features=(ChannelFeatures *) AcquireQuantumMemory(length,
673
0
    sizeof(*channel_features));
674
0
  if (channel_features == (ChannelFeatures *) NULL)
675
0
    {
676
0
      (void) ThrowMagickException(exception,GetMagickModule(),
677
0
        ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
678
0
      return(channel_features);
679
0
    }
680
0
  (void) memset(channel_features,0,length*
681
0
    sizeof(*channel_features));
682
  /*
683
    Form grays.
684
  */
685
0
  grays=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*grays));
686
0
  if (grays == (PixelPacket *) NULL)
687
0
    {
688
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
689
0
        channel_features);
690
0
      (void) ThrowMagickException(exception,GetMagickModule(),
691
0
        ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
692
0
      return(channel_features);
693
0
    }
694
0
  for (i=0; i <= (ssize_t) MaxMap; i++)
695
0
  {
696
0
    grays[i].red=(~0U);
697
0
    grays[i].green=(~0U);
698
0
    grays[i].blue=(~0U);
699
0
    grays[i].alpha=(~0U);
700
0
    grays[i].black=(~0U);
701
0
  }
702
0
  status=MagickTrue;
703
0
  image_view=AcquireVirtualCacheView(image,exception);
704
#if defined(MAGICKCORE_OPENMP_SUPPORT)
705
  #pragma omp parallel for schedule(static) shared(status) \
706
    magick_number_threads(image,image,image->rows,1)
707
#endif
708
0
  for (r=0; r < (ssize_t) image->rows; r++)
709
0
  {
710
0
    const Quantum
711
0
      *magick_restrict p;
712
713
0
    ssize_t
714
0
      x;
715
716
0
    if (status == MagickFalse)
717
0
      continue;
718
0
    p=GetCacheViewVirtualPixels(image_view,0,r,image->columns,1,exception);
719
0
    if (p == (const Quantum *) NULL)
720
0
      {
721
0
        status=MagickFalse;
722
0
        continue;
723
0
      }
724
0
    for (x=0; x < (ssize_t) image->columns; x++)
725
0
    {
726
0
      grays[ScaleQuantumToMap(GetPixelRed(image,p))].red=
727
0
        ScaleQuantumToMap(GetPixelRed(image,p));
728
0
      grays[ScaleQuantumToMap(GetPixelGreen(image,p))].green=
729
0
        ScaleQuantumToMap(GetPixelGreen(image,p));
730
0
      grays[ScaleQuantumToMap(GetPixelBlue(image,p))].blue=
731
0
        ScaleQuantumToMap(GetPixelBlue(image,p));
732
0
      if (image->colorspace == CMYKColorspace)
733
0
        grays[ScaleQuantumToMap(GetPixelBlack(image,p))].black=
734
0
          ScaleQuantumToMap(GetPixelBlack(image,p));
735
0
      if (image->alpha_trait != UndefinedPixelTrait)
736
0
        grays[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha=
737
0
          ScaleQuantumToMap(GetPixelAlpha(image,p));
738
0
      p+=(ptrdiff_t) GetPixelChannels(image);
739
0
    }
740
0
  }
741
0
  image_view=DestroyCacheView(image_view);
742
0
  if (status == MagickFalse)
743
0
    {
744
0
      grays=(PixelPacket *) RelinquishMagickMemory(grays);
745
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
746
0
        channel_features);
747
0
      return(channel_features);
748
0
    }
749
0
  (void) memset(&gray,0,sizeof(gray));
750
0
  for (i=0; i <= (ssize_t) MaxMap; i++)
751
0
  {
752
0
    if (grays[i].red != ~0U)
753
0
      grays[gray.red++].red=grays[i].red;
754
0
    if (grays[i].green != ~0U)
755
0
      grays[gray.green++].green=grays[i].green;
756
0
    if (grays[i].blue != ~0U)
757
0
      grays[gray.blue++].blue=grays[i].blue;
758
0
    if (image->colorspace == CMYKColorspace)
759
0
      if (grays[i].black != ~0U)
760
0
        grays[gray.black++].black=grays[i].black;
761
0
    if (image->alpha_trait != UndefinedPixelTrait)
762
0
      if (grays[i].alpha != ~0U)
763
0
        grays[gray.alpha++].alpha=grays[i].alpha;
764
0
  }
765
  /*
766
    Allocate spatial dependence matrix.
767
  */
768
0
  number_grays=gray.red;
769
0
  if (gray.green > number_grays)
770
0
    number_grays=gray.green;
771
0
  if (gray.blue > number_grays)
772
0
    number_grays=gray.blue;
773
0
  if (image->colorspace == CMYKColorspace)
774
0
    if (gray.black > number_grays)
775
0
      number_grays=gray.black;
776
0
  if (image->alpha_trait != UndefinedPixelTrait)
777
0
    if (gray.alpha > number_grays)
778
0
      number_grays=gray.alpha;
779
0
  cooccurrence=(ChannelStatistics **) AcquireQuantumMemory(number_grays,
780
0
    sizeof(*cooccurrence));
781
0
  density_x=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1,
782
0
    2*sizeof(*density_x));
783
0
  density_xy=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1,
784
0
    2*sizeof(*density_xy));
785
0
  density_y=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1,
786
0
    2*sizeof(*density_y));
787
0
  Q=(ChannelStatistics **) AcquireQuantumMemory(number_grays,sizeof(*Q));
788
0
  sum=(ChannelStatistics *) AcquireQuantumMemory(number_grays,sizeof(*sum));
789
0
  if ((cooccurrence == (ChannelStatistics **) NULL) ||
790
0
      (density_x == (ChannelStatistics *) NULL) ||
791
0
      (density_xy == (ChannelStatistics *) NULL) ||
792
0
      (density_y == (ChannelStatistics *) NULL) ||
793
0
      (Q == (ChannelStatistics **) NULL) ||
794
0
      (sum == (ChannelStatistics *) NULL))
795
0
    {
796
0
      if (Q != (ChannelStatistics **) NULL)
797
0
        Q=(ChannelStatistics **) RelinquishMagickMemory(Q);
798
0
      if (sum != (ChannelStatistics *) NULL)
799
0
        sum=(ChannelStatistics *) RelinquishMagickMemory(sum);
800
0
      if (density_y != (ChannelStatistics *) NULL)
801
0
        density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y);
802
0
      if (density_xy != (ChannelStatistics *) NULL)
803
0
        density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy);
804
0
      if (density_x != (ChannelStatistics *) NULL)
805
0
        density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x);
806
0
      if (cooccurrence != (ChannelStatistics **) NULL)
807
0
        cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(
808
0
          cooccurrence);
809
0
      grays=(PixelPacket *) RelinquishMagickMemory(grays);
810
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
811
0
        channel_features);
812
0
      (void) ThrowMagickException(exception,GetMagickModule(),
813
0
        ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
814
0
      return(channel_features);
815
0
    }
816
0
  (void) memset(&correlation,0,sizeof(correlation));
817
0
  (void) memset(density_x,0,2*(number_grays+1)*sizeof(*density_x));
818
0
  (void) memset(density_xy,0,2*(number_grays+1)*sizeof(*density_xy));
819
0
  (void) memset(density_y,0,2*(number_grays+1)*sizeof(*density_y));
820
0
  (void) memset(&mean,0,sizeof(mean));
821
0
  (void) memset(sum,0,number_grays*sizeof(*sum));
822
0
  (void) memset(&sum_squares,0,sizeof(sum_squares));
823
0
  (void) memset(density_xy,0,2*number_grays*sizeof(*density_xy));
824
0
  (void) memset(&entropy_x,0,sizeof(entropy_x));
825
0
  (void) memset(&entropy_xy,0,sizeof(entropy_xy));
826
0
  (void) memset(&entropy_xy1,0,sizeof(entropy_xy1));
827
0
  (void) memset(&entropy_xy2,0,sizeof(entropy_xy2));
828
0
  (void) memset(&entropy_y,0,sizeof(entropy_y));
829
0
  (void) memset(&variance,0,sizeof(variance));
830
0
  for (i=0; i < (ssize_t) number_grays; i++)
831
0
  {
832
0
    cooccurrence[i]=(ChannelStatistics *) AcquireQuantumMemory(number_grays,
833
0
      sizeof(**cooccurrence));
834
0
    Q[i]=(ChannelStatistics *) AcquireQuantumMemory(number_grays,sizeof(**Q));
835
0
    if ((cooccurrence[i] == (ChannelStatistics *) NULL) ||
836
0
        (Q[i] == (ChannelStatistics *) NULL))
837
0
      break;
838
0
    (void) memset(cooccurrence[i],0,number_grays*
839
0
      sizeof(**cooccurrence));
840
0
    (void) memset(Q[i],0,number_grays*sizeof(**Q));
841
0
  }
842
0
  if (i < (ssize_t) number_grays)
843
0
    {
844
0
      for (i--; i >= 0; i--)
845
0
      {
846
0
        if (Q[i] != (ChannelStatistics *) NULL)
847
0
          Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]);
848
0
        if (cooccurrence[i] != (ChannelStatistics *) NULL)
849
0
          cooccurrence[i]=(ChannelStatistics *)
850
0
            RelinquishMagickMemory(cooccurrence[i]);
851
0
      }
852
0
      Q=(ChannelStatistics **) RelinquishMagickMemory(Q);
853
0
      cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence);
854
0
      sum=(ChannelStatistics *) RelinquishMagickMemory(sum);
855
0
      density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y);
856
0
      density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy);
857
0
      density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x);
858
0
      grays=(PixelPacket *) RelinquishMagickMemory(grays);
859
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
860
0
        channel_features);
861
0
      (void) ThrowMagickException(exception,GetMagickModule(),
862
0
        ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
863
0
      return(channel_features);
864
0
    }
865
  /*
866
    Initialize spatial dependence matrix.
867
  */
868
0
  status=MagickTrue;
869
0
  image_view=AcquireVirtualCacheView(image,exception);
870
0
  for (r=0; r < (ssize_t) image->rows; r++)
871
0
  {
872
0
    const Quantum
873
0
      *magick_restrict p;
874
875
0
    ssize_t
876
0
      x;
877
878
0
    ssize_t
879
0
      offset,
880
0
      u,
881
0
      v;
882
883
0
    if (status == MagickFalse)
884
0
      continue;
885
0
    p=GetCacheViewVirtualPixels(image_view,-(ssize_t) distance,r,image->columns+
886
0
      2*distance,distance+2,exception);
887
0
    if (p == (const Quantum *) NULL)
888
0
      {
889
0
        status=MagickFalse;
890
0
        continue;
891
0
      }
892
0
    p+=(ptrdiff_t) distance*GetPixelChannels(image);;
893
0
    for (x=0; x < (ssize_t) image->columns; x++)
894
0
    {
895
0
      for (i=0; i < 4; i++)
896
0
      {
897
0
        switch (i)
898
0
        {
899
0
          case 0:
900
0
          default:
901
0
          {
902
            /*
903
              Horizontal adjacency.
904
            */
905
0
            offset=(ssize_t) distance;
906
0
            break;
907
0
          }
908
0
          case 1:
909
0
          {
910
            /*
911
              Vertical adjacency.
912
            */
913
0
            offset=(ssize_t) (image->columns+2*distance);
914
0
            break;
915
0
          }
916
0
          case 2:
917
0
          {
918
            /*
919
              Right diagonal adjacency.
920
            */
921
0
            offset=(ssize_t) ((image->columns+2*distance)-distance);
922
0
            break;
923
0
          }
924
0
          case 3:
925
0
          {
926
            /*
927
              Left diagonal adjacency.
928
            */
929
0
            offset=(ssize_t) ((image->columns+2*distance)+distance);
930
0
            break;
931
0
          }
932
0
        }
933
0
        u=0;
934
0
        v=0;
935
0
        while (grays[u].red != ScaleQuantumToMap(GetPixelRed(image,p)))
936
0
          u++;
937
0
        while (grays[v].red != ScaleQuantumToMap(GetPixelRed(image,p+offset*(ssize_t) GetPixelChannels(image))))
938
0
          v++;
939
0
        cooccurrence[u][v].direction[i].red++;
940
0
        cooccurrence[v][u].direction[i].red++;
941
0
        u=0;
942
0
        v=0;
943
0
        while (grays[u].green != ScaleQuantumToMap(GetPixelGreen(image,p)))
944
0
          u++;
945
0
        while (grays[v].green != ScaleQuantumToMap(GetPixelGreen(image,p+offset*(ssize_t) GetPixelChannels(image))))
946
0
          v++;
947
0
        cooccurrence[u][v].direction[i].green++;
948
0
        cooccurrence[v][u].direction[i].green++;
949
0
        u=0;
950
0
        v=0;
951
0
        while (grays[u].blue != ScaleQuantumToMap(GetPixelBlue(image,p)))
952
0
          u++;
953
0
        while (grays[v].blue != ScaleQuantumToMap(GetPixelBlue(image,p+offset*(ssize_t) GetPixelChannels(image))))
954
0
          v++;
955
0
        cooccurrence[u][v].direction[i].blue++;
956
0
        cooccurrence[v][u].direction[i].blue++;
957
0
        if (image->colorspace == CMYKColorspace)
958
0
          {
959
0
            u=0;
960
0
            v=0;
961
0
            while (grays[u].black != ScaleQuantumToMap(GetPixelBlack(image,p)))
962
0
              u++;
963
0
            while (grays[v].black != ScaleQuantumToMap(GetPixelBlack(image,p+offset*(ssize_t) GetPixelChannels(image))))
964
0
              v++;
965
0
            cooccurrence[u][v].direction[i].black++;
966
0
            cooccurrence[v][u].direction[i].black++;
967
0
          }
968
0
        if (image->alpha_trait != UndefinedPixelTrait)
969
0
          {
970
0
            u=0;
971
0
            v=0;
972
0
            while (grays[u].alpha != ScaleQuantumToMap(GetPixelAlpha(image,p)))
973
0
              u++;
974
0
            while (grays[v].alpha != ScaleQuantumToMap(GetPixelAlpha(image,p+offset*(ssize_t) GetPixelChannels(image))))
975
0
              v++;
976
0
            cooccurrence[u][v].direction[i].alpha++;
977
0
            cooccurrence[v][u].direction[i].alpha++;
978
0
          }
979
0
      }
980
0
      p+=(ptrdiff_t) GetPixelChannels(image);
981
0
    }
982
0
  }
983
0
  grays=(PixelPacket *) RelinquishMagickMemory(grays);
984
0
  image_view=DestroyCacheView(image_view);
985
0
  if (status == MagickFalse)
986
0
    {
987
0
      for (i=0; i < (ssize_t) number_grays; i++)
988
0
        cooccurrence[i]=(ChannelStatistics *)
989
0
          RelinquishMagickMemory(cooccurrence[i]);
990
0
      cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence);
991
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
992
0
        channel_features);
993
0
      (void) ThrowMagickException(exception,GetMagickModule(),
994
0
        ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
995
0
      return(channel_features);
996
0
    }
997
  /*
998
    Normalize spatial dependence matrix.
999
  */
1000
0
  for (i=0; i < 4; i++)
1001
0
  {
1002
0
    double
1003
0
      normalize;
1004
1005
0
    ssize_t
1006
0
      y;
1007
1008
0
    switch (i)
1009
0
    {
1010
0
      case 0:
1011
0
      default:
1012
0
      {
1013
        /*
1014
          Horizontal adjacency.
1015
        */
1016
0
        normalize=2.0*image->rows*(image->columns-distance);
1017
0
        break;
1018
0
      }
1019
0
      case 1:
1020
0
      {
1021
        /*
1022
          Vertical adjacency.
1023
        */
1024
0
        normalize=2.0*(image->rows-distance)*image->columns;
1025
0
        break;
1026
0
      }
1027
0
      case 2:
1028
0
      {
1029
        /*
1030
          Right diagonal adjacency.
1031
        */
1032
0
        normalize=2.0*(image->rows-distance)*(image->columns-distance);
1033
0
        break;
1034
0
      }
1035
0
      case 3:
1036
0
      {
1037
        /*
1038
          Left diagonal adjacency.
1039
        */
1040
0
        normalize=2.0*(image->rows-distance)*(image->columns-distance);
1041
0
        break;
1042
0
      }
1043
0
    }
1044
0
    normalize=MagickSafeReciprocal(normalize);
1045
0
    for (y=0; y < (ssize_t) number_grays; y++)
1046
0
    {
1047
0
      ssize_t
1048
0
        x;
1049
1050
0
      for (x=0; x < (ssize_t) number_grays; x++)
1051
0
      {
1052
0
        cooccurrence[x][y].direction[i].red*=normalize;
1053
0
        cooccurrence[x][y].direction[i].green*=normalize;
1054
0
        cooccurrence[x][y].direction[i].blue*=normalize;
1055
0
        if (image->colorspace == CMYKColorspace)
1056
0
          cooccurrence[x][y].direction[i].black*=normalize;
1057
0
        if (image->alpha_trait != UndefinedPixelTrait)
1058
0
          cooccurrence[x][y].direction[i].alpha*=normalize;
1059
0
      }
1060
0
    }
1061
0
  }
1062
  /*
1063
    Compute texture features.
1064
  */
1065
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1066
  #pragma omp parallel for schedule(static) shared(status) \
1067
    magick_number_threads(image,image,number_grays,1)
1068
#endif
1069
0
  for (i=0; i < 4; i++)
1070
0
  {
1071
0
    ssize_t
1072
0
      y;
1073
1074
0
    for (y=0; y < (ssize_t) number_grays; y++)
1075
0
    {
1076
0
      ssize_t
1077
0
        x;
1078
1079
0
      for (x=0; x < (ssize_t) number_grays; x++)
1080
0
      {
1081
        /*
1082
          Angular second moment:  measure of homogeneity of the image.
1083
        */
1084
0
        channel_features[RedPixelChannel].angular_second_moment[i]+=
1085
0
          cooccurrence[x][y].direction[i].red*
1086
0
          cooccurrence[x][y].direction[i].red;
1087
0
        channel_features[GreenPixelChannel].angular_second_moment[i]+=
1088
0
          cooccurrence[x][y].direction[i].green*
1089
0
          cooccurrence[x][y].direction[i].green;
1090
0
        channel_features[BluePixelChannel].angular_second_moment[i]+=
1091
0
          cooccurrence[x][y].direction[i].blue*
1092
0
          cooccurrence[x][y].direction[i].blue;
1093
0
        if (image->colorspace == CMYKColorspace)
1094
0
          channel_features[BlackPixelChannel].angular_second_moment[i]+=
1095
0
            cooccurrence[x][y].direction[i].black*
1096
0
            cooccurrence[x][y].direction[i].black;
1097
0
        if (image->alpha_trait != UndefinedPixelTrait)
1098
0
          channel_features[AlphaPixelChannel].angular_second_moment[i]+=
1099
0
            cooccurrence[x][y].direction[i].alpha*
1100
0
            cooccurrence[x][y].direction[i].alpha;
1101
        /*
1102
          Correlation: measure of linear-dependencies in the image.
1103
        */
1104
0
        sum[y].direction[i].red+=cooccurrence[x][y].direction[i].red;
1105
0
        sum[y].direction[i].green+=cooccurrence[x][y].direction[i].green;
1106
0
        sum[y].direction[i].blue+=cooccurrence[x][y].direction[i].blue;
1107
0
        if (image->colorspace == CMYKColorspace)
1108
0
          sum[y].direction[i].black+=cooccurrence[x][y].direction[i].black;
1109
0
        if (image->alpha_trait != UndefinedPixelTrait)
1110
0
          sum[y].direction[i].alpha+=cooccurrence[x][y].direction[i].alpha;
1111
0
        correlation.direction[i].red+=x*y*cooccurrence[x][y].direction[i].red;
1112
0
        correlation.direction[i].green+=x*y*
1113
0
          cooccurrence[x][y].direction[i].green;
1114
0
        correlation.direction[i].blue+=x*y*
1115
0
          cooccurrence[x][y].direction[i].blue;
1116
0
        if (image->colorspace == CMYKColorspace)
1117
0
          correlation.direction[i].black+=x*y*
1118
0
            cooccurrence[x][y].direction[i].black;
1119
0
        if (image->alpha_trait != UndefinedPixelTrait)
1120
0
          correlation.direction[i].alpha+=x*y*
1121
0
            cooccurrence[x][y].direction[i].alpha;
1122
        /*
1123
          Inverse Difference Moment.
1124
        */
1125
0
        channel_features[RedPixelChannel].inverse_difference_moment[i]+=
1126
0
          cooccurrence[x][y].direction[i].red/((y-x)*(y-x)+1);
1127
0
        channel_features[GreenPixelChannel].inverse_difference_moment[i]+=
1128
0
          cooccurrence[x][y].direction[i].green/((y-x)*(y-x)+1);
1129
0
        channel_features[BluePixelChannel].inverse_difference_moment[i]+=
1130
0
          cooccurrence[x][y].direction[i].blue/((y-x)*(y-x)+1);
1131
0
        if (image->colorspace == CMYKColorspace)
1132
0
          channel_features[BlackPixelChannel].inverse_difference_moment[i]+=
1133
0
            cooccurrence[x][y].direction[i].black/((y-x)*(y-x)+1);
1134
0
        if (image->alpha_trait != UndefinedPixelTrait)
1135
0
          channel_features[AlphaPixelChannel].inverse_difference_moment[i]+=
1136
0
            cooccurrence[x][y].direction[i].alpha/((y-x)*(y-x)+1);
1137
        /*
1138
          Sum average.
1139
        */
1140
0
        density_xy[y+x+2].direction[i].red+=
1141
0
          cooccurrence[x][y].direction[i].red;
1142
0
        density_xy[y+x+2].direction[i].green+=
1143
0
          cooccurrence[x][y].direction[i].green;
1144
0
        density_xy[y+x+2].direction[i].blue+=
1145
0
          cooccurrence[x][y].direction[i].blue;
1146
0
        if (image->colorspace == CMYKColorspace)
1147
0
          density_xy[y+x+2].direction[i].black+=
1148
0
            cooccurrence[x][y].direction[i].black;
1149
0
        if (image->alpha_trait != UndefinedPixelTrait)
1150
0
          density_xy[y+x+2].direction[i].alpha+=
1151
0
            cooccurrence[x][y].direction[i].alpha;
1152
        /*
1153
          Entropy.
1154
        */
1155
0
        channel_features[RedPixelChannel].entropy[i]-=
1156
0
          cooccurrence[x][y].direction[i].red*
1157
0
          log2(cooccurrence[x][y].direction[i].red);
1158
0
        channel_features[GreenPixelChannel].entropy[i]-=
1159
0
          cooccurrence[x][y].direction[i].green*
1160
0
          log2(cooccurrence[x][y].direction[i].green);
1161
0
        channel_features[BluePixelChannel].entropy[i]-=
1162
0
          cooccurrence[x][y].direction[i].blue*
1163
0
          log2(cooccurrence[x][y].direction[i].blue);
1164
0
        if (image->colorspace == CMYKColorspace)
1165
0
          channel_features[BlackPixelChannel].entropy[i]-=
1166
0
            cooccurrence[x][y].direction[i].black*
1167
0
            log2(cooccurrence[x][y].direction[i].black);
1168
0
        if (image->alpha_trait != UndefinedPixelTrait)
1169
0
          channel_features[AlphaPixelChannel].entropy[i]-=
1170
0
            cooccurrence[x][y].direction[i].alpha*
1171
0
            log2(cooccurrence[x][y].direction[i].alpha);
1172
        /*
1173
          Information Measures of Correlation.
1174
        */
1175
0
        density_x[x].direction[i].red+=cooccurrence[x][y].direction[i].red;
1176
0
        density_x[x].direction[i].green+=cooccurrence[x][y].direction[i].green;
1177
0
        density_x[x].direction[i].blue+=cooccurrence[x][y].direction[i].blue;
1178
0
        if (image->alpha_trait != UndefinedPixelTrait)
1179
0
          density_x[x].direction[i].alpha+=
1180
0
            cooccurrence[x][y].direction[i].alpha;
1181
0
        if (image->colorspace == CMYKColorspace)
1182
0
          density_x[x].direction[i].black+=
1183
0
            cooccurrence[x][y].direction[i].black;
1184
0
        density_y[y].direction[i].red+=cooccurrence[x][y].direction[i].red;
1185
0
        density_y[y].direction[i].green+=cooccurrence[x][y].direction[i].green;
1186
0
        density_y[y].direction[i].blue+=cooccurrence[x][y].direction[i].blue;
1187
0
        if (image->colorspace == CMYKColorspace)
1188
0
          density_y[y].direction[i].black+=
1189
0
            cooccurrence[x][y].direction[i].black;
1190
0
        if (image->alpha_trait != UndefinedPixelTrait)
1191
0
          density_y[y].direction[i].alpha+=
1192
0
            cooccurrence[x][y].direction[i].alpha;
1193
0
      }
1194
0
      mean.direction[i].red+=y*sum[y].direction[i].red;
1195
0
      sum_squares.direction[i].red+=y*y*sum[y].direction[i].red;
1196
0
      mean.direction[i].green+=y*sum[y].direction[i].green;
1197
0
      sum_squares.direction[i].green+=y*y*sum[y].direction[i].green;
1198
0
      mean.direction[i].blue+=y*sum[y].direction[i].blue;
1199
0
      sum_squares.direction[i].blue+=y*y*sum[y].direction[i].blue;
1200
0
      if (image->colorspace == CMYKColorspace)
1201
0
        {
1202
0
          mean.direction[i].black+=y*sum[y].direction[i].black;
1203
0
          sum_squares.direction[i].black+=y*y*sum[y].direction[i].black;
1204
0
        }
1205
0
      if (image->alpha_trait != UndefinedPixelTrait)
1206
0
        {
1207
0
          mean.direction[i].alpha+=y*sum[y].direction[i].alpha;
1208
0
          sum_squares.direction[i].alpha+=y*y*sum[y].direction[i].alpha;
1209
0
        }
1210
0
    }
1211
    /*
1212
      Correlation: measure of linear-dependencies in the image.
1213
    */
1214
0
    channel_features[RedPixelChannel].correlation[i]=
1215
0
      (correlation.direction[i].red-mean.direction[i].red*
1216
0
      mean.direction[i].red)/(sqrt(sum_squares.direction[i].red-
1217
0
      (mean.direction[i].red*mean.direction[i].red))*sqrt(
1218
0
      sum_squares.direction[i].red-(mean.direction[i].red*
1219
0
      mean.direction[i].red)));
1220
0
    channel_features[GreenPixelChannel].correlation[i]=
1221
0
      (correlation.direction[i].green-mean.direction[i].green*
1222
0
      mean.direction[i].green)/(sqrt(sum_squares.direction[i].green-
1223
0
      (mean.direction[i].green*mean.direction[i].green))*sqrt(
1224
0
      sum_squares.direction[i].green-(mean.direction[i].green*
1225
0
      mean.direction[i].green)));
1226
0
    channel_features[BluePixelChannel].correlation[i]=
1227
0
      (correlation.direction[i].blue-mean.direction[i].blue*
1228
0
      mean.direction[i].blue)/(sqrt(sum_squares.direction[i].blue-
1229
0
      (mean.direction[i].blue*mean.direction[i].blue))*sqrt(
1230
0
      sum_squares.direction[i].blue-(mean.direction[i].blue*
1231
0
      mean.direction[i].blue)));
1232
0
    if (image->colorspace == CMYKColorspace)
1233
0
      channel_features[BlackPixelChannel].correlation[i]=
1234
0
        (correlation.direction[i].black-mean.direction[i].black*
1235
0
        mean.direction[i].black)/(sqrt(sum_squares.direction[i].black-
1236
0
        (mean.direction[i].black*mean.direction[i].black))*sqrt(
1237
0
        sum_squares.direction[i].black-(mean.direction[i].black*
1238
0
        mean.direction[i].black)));
1239
0
    if (image->alpha_trait != UndefinedPixelTrait)
1240
0
      channel_features[AlphaPixelChannel].correlation[i]=
1241
0
        (correlation.direction[i].alpha-mean.direction[i].alpha*
1242
0
        mean.direction[i].alpha)/(sqrt(sum_squares.direction[i].alpha-
1243
0
        (mean.direction[i].alpha*mean.direction[i].alpha))*sqrt(
1244
0
        sum_squares.direction[i].alpha-(mean.direction[i].alpha*
1245
0
        mean.direction[i].alpha)));
1246
0
  }
1247
  /*
1248
    Compute more texture features.
1249
  */
1250
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1251
  #pragma omp parallel for schedule(static) shared(status) \
1252
    magick_number_threads(image,image,number_grays,1)
1253
#endif
1254
0
  for (i=0; i < 4; i++)
1255
0
  {
1256
0
    ssize_t
1257
0
      x;
1258
1259
0
    for (x=2; x < (ssize_t) (2*number_grays); x++)
1260
0
    {
1261
      /*
1262
        Sum average.
1263
      */
1264
0
      channel_features[RedPixelChannel].sum_average[i]+=
1265
0
        x*density_xy[x].direction[i].red;
1266
0
      channel_features[GreenPixelChannel].sum_average[i]+=
1267
0
        x*density_xy[x].direction[i].green;
1268
0
      channel_features[BluePixelChannel].sum_average[i]+=
1269
0
        x*density_xy[x].direction[i].blue;
1270
0
      if (image->colorspace == CMYKColorspace)
1271
0
        channel_features[BlackPixelChannel].sum_average[i]+=
1272
0
          x*density_xy[x].direction[i].black;
1273
0
      if (image->alpha_trait != UndefinedPixelTrait)
1274
0
        channel_features[AlphaPixelChannel].sum_average[i]+=
1275
0
          x*density_xy[x].direction[i].alpha;
1276
      /*
1277
        Sum entropy.
1278
      */
1279
0
      channel_features[RedPixelChannel].sum_entropy[i]-=
1280
0
        density_xy[x].direction[i].red*
1281
0
        log2(density_xy[x].direction[i].red);
1282
0
      channel_features[GreenPixelChannel].sum_entropy[i]-=
1283
0
        density_xy[x].direction[i].green*
1284
0
        log2(density_xy[x].direction[i].green);
1285
0
      channel_features[BluePixelChannel].sum_entropy[i]-=
1286
0
        density_xy[x].direction[i].blue*
1287
0
        log2(density_xy[x].direction[i].blue);
1288
0
      if (image->colorspace == CMYKColorspace)
1289
0
        channel_features[BlackPixelChannel].sum_entropy[i]-=
1290
0
          density_xy[x].direction[i].black*
1291
0
          log2(density_xy[x].direction[i].black);
1292
0
      if (image->alpha_trait != UndefinedPixelTrait)
1293
0
        channel_features[AlphaPixelChannel].sum_entropy[i]-=
1294
0
          density_xy[x].direction[i].alpha*
1295
0
          log2(density_xy[x].direction[i].alpha);
1296
      /*
1297
        Sum variance.
1298
      */
1299
0
      channel_features[RedPixelChannel].sum_variance[i]+=
1300
0
        (x-channel_features[RedPixelChannel].sum_entropy[i])*
1301
0
        (x-channel_features[RedPixelChannel].sum_entropy[i])*
1302
0
        density_xy[x].direction[i].red;
1303
0
      channel_features[GreenPixelChannel].sum_variance[i]+=
1304
0
        (x-channel_features[GreenPixelChannel].sum_entropy[i])*
1305
0
        (x-channel_features[GreenPixelChannel].sum_entropy[i])*
1306
0
        density_xy[x].direction[i].green;
1307
0
      channel_features[BluePixelChannel].sum_variance[i]+=
1308
0
        (x-channel_features[BluePixelChannel].sum_entropy[i])*
1309
0
        (x-channel_features[BluePixelChannel].sum_entropy[i])*
1310
0
        density_xy[x].direction[i].blue;
1311
0
      if (image->colorspace == CMYKColorspace)
1312
0
        channel_features[BlackPixelChannel].sum_variance[i]+=
1313
0
          (x-channel_features[BlackPixelChannel].sum_entropy[i])*
1314
0
          (x-channel_features[BlackPixelChannel].sum_entropy[i])*
1315
0
          density_xy[x].direction[i].black;
1316
0
      if (image->alpha_trait != UndefinedPixelTrait)
1317
0
        channel_features[AlphaPixelChannel].sum_variance[i]+=
1318
0
          (x-channel_features[AlphaPixelChannel].sum_entropy[i])*
1319
0
          (x-channel_features[AlphaPixelChannel].sum_entropy[i])*
1320
0
          density_xy[x].direction[i].alpha;
1321
0
    }
1322
0
  }
1323
  /*
1324
    Compute more texture features.
1325
  */
1326
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1327
  #pragma omp parallel for schedule(static) shared(status) \
1328
    magick_number_threads(image,image,number_grays,1)
1329
#endif
1330
0
  for (i=0; i < 4; i++)
1331
0
  {
1332
0
    ssize_t
1333
0
      y;
1334
1335
0
    for (y=0; y < (ssize_t) number_grays; y++)
1336
0
    {
1337
0
      ssize_t
1338
0
        x;
1339
1340
0
      for (x=0; x < (ssize_t) number_grays; x++)
1341
0
      {
1342
        /*
1343
          Sum of Squares: Variance
1344
        */
1345
0
        variance.direction[i].red+=(y-mean.direction[i].red+1)*
1346
0
          (y-mean.direction[i].red+1)*cooccurrence[x][y].direction[i].red;
1347
0
        variance.direction[i].green+=(y-mean.direction[i].green+1)*
1348
0
          (y-mean.direction[i].green+1)*cooccurrence[x][y].direction[i].green;
1349
0
        variance.direction[i].blue+=(y-mean.direction[i].blue+1)*
1350
0
          (y-mean.direction[i].blue+1)*cooccurrence[x][y].direction[i].blue;
1351
0
        if (image->colorspace == CMYKColorspace)
1352
0
          variance.direction[i].black+=(y-mean.direction[i].black+1)*
1353
0
            (y-mean.direction[i].black+1)*cooccurrence[x][y].direction[i].black;
1354
0
        if (image->alpha_trait != UndefinedPixelTrait)
1355
0
          variance.direction[i].alpha+=(y-mean.direction[i].alpha+1)*
1356
0
            (y-mean.direction[i].alpha+1)*
1357
0
            cooccurrence[x][y].direction[i].alpha;
1358
        /*
1359
          Sum average / Difference Variance.
1360
        */
1361
0
        density_xy[MagickAbsoluteValue(y-x)].direction[i].red+=
1362
0
          cooccurrence[x][y].direction[i].red;
1363
0
        density_xy[MagickAbsoluteValue(y-x)].direction[i].green+=
1364
0
          cooccurrence[x][y].direction[i].green;
1365
0
        density_xy[MagickAbsoluteValue(y-x)].direction[i].blue+=
1366
0
          cooccurrence[x][y].direction[i].blue;
1367
0
        if (image->colorspace == CMYKColorspace)
1368
0
          density_xy[MagickAbsoluteValue(y-x)].direction[i].black+=
1369
0
            cooccurrence[x][y].direction[i].black;
1370
0
        if (image->alpha_trait != UndefinedPixelTrait)
1371
0
          density_xy[MagickAbsoluteValue(y-x)].direction[i].alpha+=
1372
0
            cooccurrence[x][y].direction[i].alpha;
1373
        /*
1374
          Information Measures of Correlation.
1375
        */
1376
0
        entropy_xy.direction[i].red-=cooccurrence[x][y].direction[i].red*
1377
0
          log2(cooccurrence[x][y].direction[i].red);
1378
0
        entropy_xy.direction[i].green-=cooccurrence[x][y].direction[i].green*
1379
0
          log2(cooccurrence[x][y].direction[i].green);
1380
0
        entropy_xy.direction[i].blue-=cooccurrence[x][y].direction[i].blue*
1381
0
          log2(cooccurrence[x][y].direction[i].blue);
1382
0
        if (image->colorspace == CMYKColorspace)
1383
0
          entropy_xy.direction[i].black-=cooccurrence[x][y].direction[i].black*
1384
0
            log2(cooccurrence[x][y].direction[i].black);
1385
0
        if (image->alpha_trait != UndefinedPixelTrait)
1386
0
          entropy_xy.direction[i].alpha-=
1387
0
            cooccurrence[x][y].direction[i].alpha*log2(
1388
0
            cooccurrence[x][y].direction[i].alpha);
1389
0
        entropy_xy1.direction[i].red-=(cooccurrence[x][y].direction[i].red*
1390
0
          log2(density_x[x].direction[i].red*density_y[y].direction[i].red));
1391
0
        entropy_xy1.direction[i].green-=(cooccurrence[x][y].direction[i].green*
1392
0
          log2(density_x[x].direction[i].green*
1393
0
          density_y[y].direction[i].green));
1394
0
        entropy_xy1.direction[i].blue-=(cooccurrence[x][y].direction[i].blue*
1395
0
          log2(density_x[x].direction[i].blue*density_y[y].direction[i].blue));
1396
0
        if (image->colorspace == CMYKColorspace)
1397
0
          entropy_xy1.direction[i].black-=(
1398
0
            cooccurrence[x][y].direction[i].black*log2(
1399
0
            density_x[x].direction[i].black*density_y[y].direction[i].black));
1400
0
        if (image->alpha_trait != UndefinedPixelTrait)
1401
0
          entropy_xy1.direction[i].alpha-=(
1402
0
            cooccurrence[x][y].direction[i].alpha*log2(
1403
0
            density_x[x].direction[i].alpha*density_y[y].direction[i].alpha));
1404
0
        entropy_xy2.direction[i].red-=(density_x[x].direction[i].red*
1405
0
          density_y[y].direction[i].red*log2(density_x[x].direction[i].red*
1406
0
          density_y[y].direction[i].red));
1407
0
        entropy_xy2.direction[i].green-=(density_x[x].direction[i].green*
1408
0
          density_y[y].direction[i].green*log2(density_x[x].direction[i].green*
1409
0
          density_y[y].direction[i].green));
1410
0
        entropy_xy2.direction[i].blue-=(density_x[x].direction[i].blue*
1411
0
          density_y[y].direction[i].blue*log2(density_x[x].direction[i].blue*
1412
0
          density_y[y].direction[i].blue));
1413
0
        if (image->colorspace == CMYKColorspace)
1414
0
          entropy_xy2.direction[i].black-=(density_x[x].direction[i].black*
1415
0
            density_y[y].direction[i].black*log2(
1416
0
            density_x[x].direction[i].black*density_y[y].direction[i].black));
1417
0
        if (image->alpha_trait != UndefinedPixelTrait)
1418
0
          entropy_xy2.direction[i].alpha-=(density_x[x].direction[i].alpha*
1419
0
            density_y[y].direction[i].alpha*log2(
1420
0
            density_x[x].direction[i].alpha*density_y[y].direction[i].alpha));
1421
0
      }
1422
0
    }
1423
0
    channel_features[RedPixelChannel].variance_sum_of_squares[i]=
1424
0
      variance.direction[i].red;
1425
0
    channel_features[GreenPixelChannel].variance_sum_of_squares[i]=
1426
0
      variance.direction[i].green;
1427
0
    channel_features[BluePixelChannel].variance_sum_of_squares[i]=
1428
0
      variance.direction[i].blue;
1429
0
    if (image->colorspace == CMYKColorspace)
1430
0
      channel_features[BlackPixelChannel].variance_sum_of_squares[i]=
1431
0
        variance.direction[i].black;
1432
0
    if (image->alpha_trait != UndefinedPixelTrait)
1433
0
      channel_features[AlphaPixelChannel].variance_sum_of_squares[i]=
1434
0
        variance.direction[i].alpha;
1435
0
  }
1436
  /*
1437
    Compute more texture features.
1438
  */
1439
0
  (void) memset(&variance,0,sizeof(variance));
1440
0
  (void) memset(&sum_squares,0,sizeof(sum_squares));
1441
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1442
  #pragma omp parallel for schedule(static) shared(status) \
1443
    magick_number_threads(image,image,number_grays,1)
1444
#endif
1445
0
  for (i=0; i < 4; i++)
1446
0
  {
1447
0
    ssize_t
1448
0
      x;
1449
1450
0
    for (x=0; x < (ssize_t) number_grays; x++)
1451
0
    {
1452
      /*
1453
        Difference variance.
1454
      */
1455
0
      variance.direction[i].red+=density_xy[x].direction[i].red;
1456
0
      variance.direction[i].green+=density_xy[x].direction[i].green;
1457
0
      variance.direction[i].blue+=density_xy[x].direction[i].blue;
1458
0
      if (image->colorspace == CMYKColorspace)
1459
0
        variance.direction[i].black+=density_xy[x].direction[i].black;
1460
0
      if (image->alpha_trait != UndefinedPixelTrait)
1461
0
        variance.direction[i].alpha+=density_xy[x].direction[i].alpha;
1462
0
      sum_squares.direction[i].red+=density_xy[x].direction[i].red*
1463
0
        density_xy[x].direction[i].red;
1464
0
      sum_squares.direction[i].green+=density_xy[x].direction[i].green*
1465
0
        density_xy[x].direction[i].green;
1466
0
      sum_squares.direction[i].blue+=density_xy[x].direction[i].blue*
1467
0
        density_xy[x].direction[i].blue;
1468
0
      if (image->colorspace == CMYKColorspace)
1469
0
        sum_squares.direction[i].black+=density_xy[x].direction[i].black*
1470
0
          density_xy[x].direction[i].black;
1471
0
      if (image->alpha_trait != UndefinedPixelTrait)
1472
0
        sum_squares.direction[i].alpha+=density_xy[x].direction[i].alpha*
1473
0
          density_xy[x].direction[i].alpha;
1474
      /*
1475
        Difference entropy.
1476
      */
1477
0
      channel_features[RedPixelChannel].difference_entropy[i]-=
1478
0
        density_xy[x].direction[i].red*
1479
0
        log2(density_xy[x].direction[i].red);
1480
0
      channel_features[GreenPixelChannel].difference_entropy[i]-=
1481
0
        density_xy[x].direction[i].green*
1482
0
        log2(density_xy[x].direction[i].green);
1483
0
      channel_features[BluePixelChannel].difference_entropy[i]-=
1484
0
        density_xy[x].direction[i].blue*
1485
0
        log2(density_xy[x].direction[i].blue);
1486
0
      if (image->colorspace == CMYKColorspace)
1487
0
        channel_features[BlackPixelChannel].difference_entropy[i]-=
1488
0
          density_xy[x].direction[i].black*
1489
0
          log2(density_xy[x].direction[i].black);
1490
0
      if (image->alpha_trait != UndefinedPixelTrait)
1491
0
        channel_features[AlphaPixelChannel].difference_entropy[i]-=
1492
0
          density_xy[x].direction[i].alpha*
1493
0
          log2(density_xy[x].direction[i].alpha);
1494
      /*
1495
        Information Measures of Correlation.
1496
      */
1497
0
      entropy_x.direction[i].red-=(density_x[x].direction[i].red*
1498
0
        log2(density_x[x].direction[i].red));
1499
0
      entropy_x.direction[i].green-=(density_x[x].direction[i].green*
1500
0
        log2(density_x[x].direction[i].green));
1501
0
      entropy_x.direction[i].blue-=(density_x[x].direction[i].blue*
1502
0
        log2(density_x[x].direction[i].blue));
1503
0
      if (image->colorspace == CMYKColorspace)
1504
0
        entropy_x.direction[i].black-=(density_x[x].direction[i].black*
1505
0
          log2(density_x[x].direction[i].black));
1506
0
      if (image->alpha_trait != UndefinedPixelTrait)
1507
0
        entropy_x.direction[i].alpha-=(density_x[x].direction[i].alpha*
1508
0
          log2(density_x[x].direction[i].alpha));
1509
0
      entropy_y.direction[i].red-=(density_y[x].direction[i].red*
1510
0
        log2(density_y[x].direction[i].red));
1511
0
      entropy_y.direction[i].green-=(density_y[x].direction[i].green*
1512
0
        log2(density_y[x].direction[i].green));
1513
0
      entropy_y.direction[i].blue-=(density_y[x].direction[i].blue*
1514
0
        log2(density_y[x].direction[i].blue));
1515
0
      if (image->colorspace == CMYKColorspace)
1516
0
        entropy_y.direction[i].black-=(density_y[x].direction[i].black*
1517
0
          log2(density_y[x].direction[i].black));
1518
0
      if (image->alpha_trait != UndefinedPixelTrait)
1519
0
        entropy_y.direction[i].alpha-=(density_y[x].direction[i].alpha*
1520
0
          log2(density_y[x].direction[i].alpha));
1521
0
    }
1522
    /*
1523
      Difference variance.
1524
    */
1525
0
    channel_features[RedPixelChannel].difference_variance[i]=
1526
0
      (((double) number_grays*number_grays*sum_squares.direction[i].red)-
1527
0
      (variance.direction[i].red*variance.direction[i].red))/
1528
0
      ((double) number_grays*number_grays*number_grays*number_grays);
1529
0
    channel_features[GreenPixelChannel].difference_variance[i]=
1530
0
      (((double) number_grays*number_grays*sum_squares.direction[i].green)-
1531
0
      (variance.direction[i].green*variance.direction[i].green))/
1532
0
      ((double) number_grays*number_grays*number_grays*number_grays);
1533
0
    channel_features[BluePixelChannel].difference_variance[i]=
1534
0
      (((double) number_grays*number_grays*sum_squares.direction[i].blue)-
1535
0
      (variance.direction[i].blue*variance.direction[i].blue))/
1536
0
      ((double) number_grays*number_grays*number_grays*number_grays);
1537
0
    if (image->colorspace == CMYKColorspace)
1538
0
      channel_features[BlackPixelChannel].difference_variance[i]=
1539
0
        (((double) number_grays*number_grays*sum_squares.direction[i].black)-
1540
0
        (variance.direction[i].black*variance.direction[i].black))/
1541
0
        ((double) number_grays*number_grays*number_grays*number_grays);
1542
0
    if (image->alpha_trait != UndefinedPixelTrait)
1543
0
      channel_features[AlphaPixelChannel].difference_variance[i]=
1544
0
        (((double) number_grays*number_grays*sum_squares.direction[i].alpha)-
1545
0
        (variance.direction[i].alpha*variance.direction[i].alpha))/
1546
0
        ((double) number_grays*number_grays*number_grays*number_grays);
1547
    /*
1548
      Information Measures of Correlation.
1549
    */
1550
0
    channel_features[RedPixelChannel].measure_of_correlation_1[i]=
1551
0
      (entropy_xy.direction[i].red-entropy_xy1.direction[i].red)/
1552
0
      (entropy_x.direction[i].red > entropy_y.direction[i].red ?
1553
0
       entropy_x.direction[i].red : entropy_y.direction[i].red);
1554
0
    channel_features[GreenPixelChannel].measure_of_correlation_1[i]=
1555
0
      (entropy_xy.direction[i].green-entropy_xy1.direction[i].green)/
1556
0
      (entropy_x.direction[i].green > entropy_y.direction[i].green ?
1557
0
       entropy_x.direction[i].green : entropy_y.direction[i].green);
1558
0
    channel_features[BluePixelChannel].measure_of_correlation_1[i]=
1559
0
      (entropy_xy.direction[i].blue-entropy_xy1.direction[i].blue)/
1560
0
      (entropy_x.direction[i].blue > entropy_y.direction[i].blue ?
1561
0
       entropy_x.direction[i].blue : entropy_y.direction[i].blue);
1562
0
    if (image->colorspace == CMYKColorspace)
1563
0
      channel_features[BlackPixelChannel].measure_of_correlation_1[i]=
1564
0
        (entropy_xy.direction[i].black-entropy_xy1.direction[i].black)/
1565
0
        (entropy_x.direction[i].black > entropy_y.direction[i].black ?
1566
0
         entropy_x.direction[i].black : entropy_y.direction[i].black);
1567
0
    if (image->alpha_trait != UndefinedPixelTrait)
1568
0
      channel_features[AlphaPixelChannel].measure_of_correlation_1[i]=
1569
0
        (entropy_xy.direction[i].alpha-entropy_xy1.direction[i].alpha)/
1570
0
        (entropy_x.direction[i].alpha > entropy_y.direction[i].alpha ?
1571
0
         entropy_x.direction[i].alpha : entropy_y.direction[i].alpha);
1572
0
    channel_features[RedPixelChannel].measure_of_correlation_2[i]=
1573
0
      (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].red-
1574
0
      entropy_xy.direction[i].red)))));
1575
0
    channel_features[GreenPixelChannel].measure_of_correlation_2[i]=
1576
0
      (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].green-
1577
0
      entropy_xy.direction[i].green)))));
1578
0
    channel_features[BluePixelChannel].measure_of_correlation_2[i]=
1579
0
      (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].blue-
1580
0
      entropy_xy.direction[i].blue)))));
1581
0
    if (image->colorspace == CMYKColorspace)
1582
0
      channel_features[BlackPixelChannel].measure_of_correlation_2[i]=
1583
0
        (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].black-
1584
0
        entropy_xy.direction[i].black)))));
1585
0
    if (image->alpha_trait != UndefinedPixelTrait)
1586
0
      channel_features[AlphaPixelChannel].measure_of_correlation_2[i]=
1587
0
        (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].alpha-
1588
0
        entropy_xy.direction[i].alpha)))));
1589
0
  }
1590
  /*
1591
    Compute more texture features.
1592
  */
1593
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1594
  #pragma omp parallel for schedule(static) shared(status) \
1595
    magick_number_threads(image,image,number_grays,1)
1596
#endif
1597
0
  for (i=0; i < 4; i++)
1598
0
  {
1599
0
    ssize_t
1600
0
      z;
1601
1602
0
    for (z=0; z < (ssize_t) number_grays; z++)
1603
0
    {
1604
0
      ssize_t
1605
0
        y;
1606
1607
0
      ChannelStatistics
1608
0
        pixel;
1609
1610
0
      (void) memset(&pixel,0,sizeof(pixel));
1611
0
      for (y=0; y < (ssize_t) number_grays; y++)
1612
0
      {
1613
0
        ssize_t
1614
0
          x;
1615
1616
0
        for (x=0; x < (ssize_t) number_grays; x++)
1617
0
        {
1618
          /*
1619
            Contrast:  amount of local variations present in an image.
1620
          */
1621
0
          if (((y-x) == z) || ((x-y) == z))
1622
0
            {
1623
0
              pixel.direction[i].red+=cooccurrence[x][y].direction[i].red;
1624
0
              pixel.direction[i].green+=cooccurrence[x][y].direction[i].green;
1625
0
              pixel.direction[i].blue+=cooccurrence[x][y].direction[i].blue;
1626
0
              if (image->colorspace == CMYKColorspace)
1627
0
                pixel.direction[i].black+=cooccurrence[x][y].direction[i].black;
1628
0
              if (image->alpha_trait != UndefinedPixelTrait)
1629
0
                pixel.direction[i].alpha+=
1630
0
                  cooccurrence[x][y].direction[i].alpha;
1631
0
            }
1632
          /*
1633
            Maximum Correlation Coefficient.
1634
          */
1635
0
          if ((fabs(density_x[z].direction[i].red) > MagickEpsilon) &&
1636
0
              (fabs(density_y[x].direction[i].red) > MagickEpsilon))
1637
0
            Q[z][y].direction[i].red+=cooccurrence[z][x].direction[i].red*
1638
0
              cooccurrence[y][x].direction[i].red/density_x[z].direction[i].red/
1639
0
              density_y[x].direction[i].red;
1640
0
          if ((fabs(density_x[z].direction[i].green) > MagickEpsilon) &&
1641
0
              (fabs(density_y[x].direction[i].red) > MagickEpsilon))
1642
0
            Q[z][y].direction[i].green+=cooccurrence[z][x].direction[i].green*
1643
0
              cooccurrence[y][x].direction[i].green/
1644
0
              density_x[z].direction[i].green/density_y[x].direction[i].red;
1645
0
          if ((fabs(density_x[z].direction[i].blue) > MagickEpsilon) &&
1646
0
              (fabs(density_y[x].direction[i].blue) > MagickEpsilon))
1647
0
            Q[z][y].direction[i].blue+=cooccurrence[z][x].direction[i].blue*
1648
0
              cooccurrence[y][x].direction[i].blue/
1649
0
              density_x[z].direction[i].blue/density_y[x].direction[i].blue;
1650
0
          if (image->colorspace == CMYKColorspace)
1651
0
            if ((fabs(density_x[z].direction[i].black) > MagickEpsilon) &&
1652
0
                (fabs(density_y[x].direction[i].black) > MagickEpsilon))
1653
0
              Q[z][y].direction[i].black+=cooccurrence[z][x].direction[i].black*
1654
0
                cooccurrence[y][x].direction[i].black/
1655
0
                density_x[z].direction[i].black/density_y[x].direction[i].black;
1656
0
          if (image->alpha_trait != UndefinedPixelTrait)
1657
0
            if ((fabs(density_x[z].direction[i].alpha) > MagickEpsilon) &&
1658
0
                (fabs(density_y[x].direction[i].alpha) > MagickEpsilon))
1659
0
              Q[z][y].direction[i].alpha+=
1660
0
                cooccurrence[z][x].direction[i].alpha*
1661
0
                cooccurrence[y][x].direction[i].alpha/
1662
0
                density_x[z].direction[i].alpha/
1663
0
                density_y[x].direction[i].alpha;
1664
0
        }
1665
0
      }
1666
0
      channel_features[RedPixelChannel].contrast[i]+=z*z*
1667
0
        pixel.direction[i].red;
1668
0
      channel_features[GreenPixelChannel].contrast[i]+=z*z*
1669
0
        pixel.direction[i].green;
1670
0
      channel_features[BluePixelChannel].contrast[i]+=z*z*
1671
0
        pixel.direction[i].blue;
1672
0
      if (image->colorspace == CMYKColorspace)
1673
0
        channel_features[BlackPixelChannel].contrast[i]+=z*z*
1674
0
          pixel.direction[i].black;
1675
0
      if (image->alpha_trait != UndefinedPixelTrait)
1676
0
        channel_features[AlphaPixelChannel].contrast[i]+=z*z*
1677
0
          pixel.direction[i].alpha;
1678
0
    }
1679
    /*
1680
      Maximum Correlation Coefficient.
1681
      Future: return second largest eigenvalue of Q.
1682
    */
1683
0
    channel_features[RedPixelChannel].maximum_correlation_coefficient[i]=
1684
0
      sqrt(-1.0);
1685
0
    channel_features[GreenPixelChannel].maximum_correlation_coefficient[i]=
1686
0
      sqrt(-1.0);
1687
0
    channel_features[BluePixelChannel].maximum_correlation_coefficient[i]=
1688
0
      sqrt(-1.0);
1689
0
    if (image->colorspace == CMYKColorspace)
1690
0
      channel_features[BlackPixelChannel].maximum_correlation_coefficient[i]=
1691
0
        sqrt(-1.0);
1692
0
    if (image->alpha_trait != UndefinedPixelTrait)
1693
0
      channel_features[AlphaPixelChannel].maximum_correlation_coefficient[i]=
1694
0
        sqrt(-1.0);
1695
0
  }
1696
  /*
1697
    Relinquish resources.
1698
  */
1699
0
  sum=(ChannelStatistics *) RelinquishMagickMemory(sum);
1700
0
  for (i=0; i < (ssize_t) number_grays; i++)
1701
0
    Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]);
1702
0
  Q=(ChannelStatistics **) RelinquishMagickMemory(Q);
1703
0
  density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y);
1704
0
  density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy);
1705
0
  density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x);
1706
0
  for (i=0; i < (ssize_t) number_grays; i++)
1707
0
    cooccurrence[i]=(ChannelStatistics *)
1708
0
      RelinquishMagickMemory(cooccurrence[i]);
1709
0
  cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence);
1710
0
  return(channel_features);
1711
0
}
1712

1713
/*
1714
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1715
%                                                                             %
1716
%                                                                             %
1717
%                                                                             %
1718
%     H o u g h L i n e I m a g e                                             %
1719
%                                                                             %
1720
%                                                                             %
1721
%                                                                             %
1722
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1723
%
1724
%  HoughLineImage() can be used in conjunction with any binary edge extracted
1725
%  image (we recommend Canny) to identify lines in the image. The algorithm
1726
%  accumulates counts for every white pixel for every possible orientation (for
1727
%  angles from 0 to 179 in 1 degree increments) and distance from the center of
1728
%  the image to the corner (in 1 px increments) and stores the counts in an
1729
%  accumulator matrix of angle vs distance. The size of the accumulator is
1730
%  180x(diagonal/2). Next it searches this space for peaks in counts and
1731
%  converts the locations of the peaks to slope and intercept in the normal
1732
%  x,y input image space. Use  the slope/intercepts to find the endpoints
1733
%  clipped to the bounds of the image. The lines are then drawn. The counts
1734
%  are a measure of the length of the lines.
1735
%
1736
%  The format of the HoughLineImage method is:
1737
%
1738
%      Image *HoughLineImage(const Image *image,const size_t width,
1739
%        const size_t height,const size_t threshold,ExceptionInfo *exception)
1740
%
1741
%  A description of each parameter follows:
1742
%
1743
%    o image: the image.
1744
%
1745
%    o width, height: find line pairs as local maxima in this neighborhood.
1746
%
1747
%    o threshold: the line count threshold.
1748
%
1749
%    o exception: return any errors or warnings in this structure.
1750
%
1751
*/
1752
1753
static inline double MagickRound(double x)
1754
0
{
1755
  /*
1756
    Round the fraction to nearest integer.
1757
  */
1758
0
  if ((x-floor(x)) < (ceil(x)-x))
1759
0
    return(floor(x));
1760
0
  return(ceil(x));
1761
0
}
1762
1763
static Image *RenderHoughLines(const ImageInfo *image_info,const size_t columns,
1764
  const size_t rows,ExceptionInfo *exception)
1765
0
{
1766
0
#define BoundingBox  "viewbox"
1767
1768
0
  DrawInfo
1769
0
    *draw_info;
1770
1771
0
  Image
1772
0
    *image;
1773
1774
0
  MagickBooleanType
1775
0
    status;
1776
1777
  /*
1778
    Open image.
1779
  */
1780
0
  image=AcquireImage(image_info,exception);
1781
0
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
1782
0
  if (status == MagickFalse)
1783
0
    {
1784
0
      image=DestroyImageList(image);
1785
0
      return((Image *) NULL);
1786
0
    }
1787
0
  image->columns=columns;
1788
0
  image->rows=rows;
1789
0
  draw_info=CloneDrawInfo(image_info,(DrawInfo *) NULL);
1790
0
  draw_info->affine.sx=image->resolution.x == 0.0 ? 1.0 : image->resolution.x/
1791
0
    DefaultResolution;
1792
0
  draw_info->affine.sy=image->resolution.y == 0.0 ? 1.0 : image->resolution.y/
1793
0
    DefaultResolution;
1794
0
  image->columns=CastDoubleToSizeT(draw_info->affine.sx*image->columns);
1795
0
  image->rows=CastDoubleToSizeT(draw_info->affine.sy*image->rows);
1796
0
  status=SetImageExtent(image,image->columns,image->rows,exception);
1797
0
  if (status == MagickFalse)
1798
0
    return(DestroyImageList(image));
1799
0
  if (SetImageBackgroundColor(image,exception) == MagickFalse)
1800
0
    {
1801
0
      draw_info=DestroyDrawInfo(draw_info);
1802
0
      image=DestroyImageList(image);
1803
0
      return((Image *) NULL);
1804
0
    }
1805
  /*
1806
    Render drawing.
1807
  */
1808
0
  if (GetBlobStreamData(image) == (unsigned char *) NULL)
1809
0
    draw_info->primitive=FileToString(image->filename,~0UL,exception);
1810
0
  else
1811
0
    {
1812
0
      draw_info->primitive=(char *) AcquireQuantumMemory(1,(size_t)
1813
0
        GetBlobSize(image)+1);
1814
0
      if (draw_info->primitive != (char *) NULL)
1815
0
        {
1816
0
          (void) memcpy(draw_info->primitive,GetBlobStreamData(image),
1817
0
            (size_t) GetBlobSize(image));
1818
0
          draw_info->primitive[GetBlobSize(image)]='\0';
1819
0
        }
1820
0
     }
1821
0
  (void) DrawImage(image,draw_info,exception);
1822
0
  draw_info=DestroyDrawInfo(draw_info);
1823
0
  if (CloseBlob(image) == MagickFalse)
1824
0
    image=DestroyImageList(image);
1825
0
  return(GetFirstImageInList(image));
1826
0
}
1827
1828
MagickExport Image *HoughLineImage(const Image *image,const size_t width,
1829
  const size_t height,const size_t threshold,ExceptionInfo *exception)
1830
0
{
1831
0
#define HoughLineImageTag  "HoughLine/Image"
1832
1833
0
  CacheView
1834
0
    *image_view;
1835
1836
0
  char
1837
0
    message[MagickPathExtent],
1838
0
    path[MagickPathExtent];
1839
1840
0
  const char
1841
0
    *artifact;
1842
1843
0
  double
1844
0
    hough_height;
1845
1846
0
  Image
1847
0
    *lines_image = NULL;
1848
1849
0
  ImageInfo
1850
0
    *image_info;
1851
1852
0
  int
1853
0
    file;
1854
1855
0
  MagickBooleanType
1856
0
    status;
1857
1858
0
  MagickOffsetType
1859
0
    progress;
1860
1861
0
  MatrixInfo
1862
0
    *accumulator;
1863
1864
0
  PointInfo
1865
0
    center;
1866
1867
0
  ssize_t
1868
0
    y;
1869
1870
0
  size_t
1871
0
    accumulator_height,
1872
0
    accumulator_width,
1873
0
    line_count;
1874
1875
  /*
1876
    Create the accumulator.
1877
  */
1878
0
  assert(image != (const Image *) NULL);
1879
0
  assert(image->signature == MagickCoreSignature);
1880
0
  assert(exception != (ExceptionInfo *) NULL);
1881
0
  assert(exception->signature == MagickCoreSignature);
1882
0
  if (IsEventLogging() != MagickFalse)
1883
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1884
0
  accumulator_width=180;
1885
0
  hough_height=((sqrt(2.0)*(double) (image->rows > image->columns ?
1886
0
    image->rows : image->columns))/2.0);
1887
0
  accumulator_height=(size_t) (2.0*hough_height);
1888
0
  accumulator=AcquireMatrixInfo(accumulator_width,accumulator_height,
1889
0
    sizeof(double),exception);
1890
0
  if (accumulator == (MatrixInfo *) NULL)
1891
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1892
0
  if (NullMatrix(accumulator) == MagickFalse)
1893
0
    {
1894
0
      accumulator=DestroyMatrixInfo(accumulator);
1895
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1896
0
    }
1897
  /*
1898
    Populate the accumulator.
1899
  */
1900
0
  status=MagickTrue;
1901
0
  progress=0;
1902
0
  center.x=(double) image->columns/2.0;
1903
0
  center.y=(double) image->rows/2.0;
1904
0
  image_view=AcquireVirtualCacheView(image,exception);
1905
0
  for (y=0; y < (ssize_t) image->rows; y++)
1906
0
  {
1907
0
    const Quantum
1908
0
      *magick_restrict p;
1909
1910
0
    ssize_t
1911
0
      x;
1912
1913
0
    if (status == MagickFalse)
1914
0
      continue;
1915
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1916
0
    if (p == (Quantum *) NULL)
1917
0
      {
1918
0
        status=MagickFalse;
1919
0
        continue;
1920
0
      }
1921
0
    for (x=0; x < (ssize_t) image->columns; x++)
1922
0
    {
1923
0
      if (GetPixelIntensity(image,p) > ((double) QuantumRange/2.0))
1924
0
        {
1925
0
          ssize_t
1926
0
            i;
1927
1928
0
          for (i=0; i < 180; i++)
1929
0
          {
1930
0
            double
1931
0
              count,
1932
0
              radius;
1933
1934
0
            radius=(((double) x-center.x)*cos(DegreesToRadians((double) i)))+
1935
0
              (((double) y-center.y)*sin(DegreesToRadians((double) i)));
1936
0
            (void) GetMatrixElement(accumulator,i,(ssize_t)
1937
0
              MagickRound(radius+hough_height),&count);
1938
0
            count++;
1939
0
            (void) SetMatrixElement(accumulator,i,(ssize_t)
1940
0
              MagickRound(radius+hough_height),&count);
1941
0
          }
1942
0
        }
1943
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1944
0
    }
1945
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1946
0
      {
1947
0
        MagickBooleanType
1948
0
          proceed;
1949
1950
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1951
        #pragma omp atomic
1952
#endif
1953
0
        progress++;
1954
0
        proceed=SetImageProgress(image,CannyEdgeImageTag,progress,image->rows);
1955
0
        if (proceed == MagickFalse)
1956
0
          status=MagickFalse;
1957
0
      }
1958
0
  }
1959
0
  image_view=DestroyCacheView(image_view);
1960
0
  if (status == MagickFalse)
1961
0
    {
1962
0
      accumulator=DestroyMatrixInfo(accumulator);
1963
0
      return((Image *) NULL);
1964
0
    }
1965
  /*
1966
    Generate line segments from accumulator.
1967
  */
1968
0
  file=AcquireUniqueFileResource(path);
1969
0
  if (file == -1)
1970
0
    {
1971
0
      accumulator=DestroyMatrixInfo(accumulator);
1972
0
      return((Image *) NULL);
1973
0
    }
1974
0
  (void) FormatLocaleString(message,MagickPathExtent,
1975
0
    "# Hough line transform: %.17gx%.17g%+.20g\n",(double) width,
1976
0
    (double) height,(double) threshold);
1977
0
  if (write(file,message,strlen(message)) != (ssize_t) strlen(message))
1978
0
    status=MagickFalse;
1979
0
  (void) FormatLocaleString(message,MagickPathExtent,
1980
0
    "viewbox 0 0 %.17g %.17g\n",(double) image->columns,(double) image->rows);
1981
0
  if (write(file,message,strlen(message)) != (ssize_t) strlen(message))
1982
0
    status=MagickFalse;
1983
0
  (void) FormatLocaleString(message,MagickPathExtent,
1984
0
    "# x1,y1 x2,y2 # count angle distance\n");
1985
0
  if (write(file,message,strlen(message)) != (ssize_t) strlen(message))
1986
0
    status=MagickFalse;
1987
0
  line_count=image->columns > image->rows ? image->columns/4 : image->rows/4;
1988
0
  if (threshold != 0)
1989
0
    line_count=threshold;
1990
0
  for (y=0; y < (ssize_t) accumulator_height; y++)
1991
0
  {
1992
0
    ssize_t
1993
0
      x;
1994
1995
0
    for (x=0; x < (ssize_t) accumulator_width; x++)
1996
0
    {
1997
0
      double
1998
0
        count;
1999
2000
0
      (void) GetMatrixElement(accumulator,x,y,&count);
2001
0
      if (count >= (double) line_count)
2002
0
        {
2003
0
          double
2004
0
            maxima;
2005
2006
0
          SegmentInfo
2007
0
            line;
2008
2009
0
          ssize_t
2010
0
            v;
2011
2012
          /*
2013
            Is point a local maxima?
2014
          */
2015
0
          maxima=count;
2016
0
          for (v=(-((ssize_t) height/2)); v <= (((ssize_t) height/2)); v++)
2017
0
          {
2018
0
            ssize_t
2019
0
              u;
2020
2021
0
            for (u=(-((ssize_t) width/2)); u <= (((ssize_t) width/2)); u++)
2022
0
            {
2023
0
              if ((u != 0) || (v !=0))
2024
0
                {
2025
0
                  (void) GetMatrixElement(accumulator,x+u,y+v,&count);
2026
0
                  if (count > maxima)
2027
0
                    {
2028
0
                      maxima=count;
2029
0
                      break;
2030
0
                    }
2031
0
                }
2032
0
            }
2033
0
            if (u < (ssize_t) (width/2))
2034
0
              break;
2035
0
          }
2036
0
          (void) GetMatrixElement(accumulator,x,y,&count);
2037
0
          if (maxima > count)
2038
0
            continue;
2039
0
          if ((x >= 45) && (x <= 135))
2040
0
            {
2041
              /*
2042
                y = (r-x cos(t))/sin(t)
2043
              */
2044
0
              line.x1=0.0;
2045
0
              line.y1=((double) (y-(accumulator_height/2.0))-((line.x1-
2046
0
                (image->columns/2.0))*cos(DegreesToRadians((double) x))))/
2047
0
                sin(DegreesToRadians((double) x))+(image->rows/2.0);
2048
0
              line.x2=(double) image->columns;
2049
0
              line.y2=((double) (y-(accumulator_height/2.0))-((line.x2-
2050
0
                (image->columns/2.0))*cos(DegreesToRadians((double) x))))/
2051
0
                sin(DegreesToRadians((double) x))+(image->rows/2.0);
2052
0
            }
2053
0
          else
2054
0
            {
2055
              /*
2056
                x = (r-y cos(t))/sin(t)
2057
              */
2058
0
              line.y1=0.0;
2059
0
              line.x1=((double) (y-(accumulator_height/2.0))-((line.y1-
2060
0
                (image->rows/2.0))*sin(DegreesToRadians((double) x))))/
2061
0
                cos(DegreesToRadians((double) x))+(image->columns/2.0);
2062
0
              line.y2=(double) image->rows;
2063
0
              line.x2=((double) (y-(accumulator_height/2.0))-((line.y2-
2064
0
                (image->rows/2.0))*sin(DegreesToRadians((double) x))))/
2065
0
                cos(DegreesToRadians((double) x))+(image->columns/2.0);
2066
0
            }
2067
0
          (void) FormatLocaleString(message,MagickPathExtent,
2068
0
            "line %g,%g %g,%g  # %g %g %g\n",line.x1,line.y1,line.x2,line.y2,
2069
0
            maxima,(double) x,(double) y);
2070
0
          if (write(file,message,strlen(message)) != (ssize_t) strlen(message))
2071
0
            status=MagickFalse;
2072
0
        }
2073
0
    }
2074
0
  }
2075
0
  (void) close_utf8(file);
2076
  /*
2077
    Render lines to image canvas.
2078
  */
2079
0
  image_info=AcquireImageInfo();
2080
0
  image_info->background_color=image->background_color;
2081
0
  (void) FormatLocaleString(image_info->filename,MagickPathExtent,"%s",path);
2082
0
  artifact=GetImageArtifact(image,"background");
2083
0
  if (artifact != (const char *) NULL)
2084
0
    (void) SetImageOption(image_info,"background",artifact);
2085
0
  artifact=GetImageArtifact(image,"fill");
2086
0
  if (artifact != (const char *) NULL)
2087
0
    (void) SetImageOption(image_info,"fill",artifact);
2088
0
  artifact=GetImageArtifact(image,"stroke");
2089
0
  if (artifact != (const char *) NULL)
2090
0
    (void) SetImageOption(image_info,"stroke",artifact);
2091
0
  artifact=GetImageArtifact(image,"strokewidth");
2092
0
  if (artifact != (const char *) NULL)
2093
0
    (void) SetImageOption(image_info,"strokewidth",artifact);
2094
0
  lines_image=RenderHoughLines(image_info,image->columns,image->rows,exception);
2095
0
  artifact=GetImageArtifact(image,"hough-lines:accumulator");
2096
0
  if ((lines_image != (Image *) NULL) &&
2097
0
      (IsStringTrue(artifact) != MagickFalse))
2098
0
    {
2099
0
      Image
2100
0
        *accumulator_image;
2101
2102
0
      accumulator_image=MatrixToImage(accumulator,exception);
2103
0
      if (accumulator_image != (Image *) NULL)
2104
0
        AppendImageToList(&lines_image,accumulator_image);
2105
0
    }
2106
  /*
2107
    Free resources.
2108
  */
2109
0
  accumulator=DestroyMatrixInfo(accumulator);
2110
0
  image_info=DestroyImageInfo(image_info);
2111
0
  (void) RelinquishUniqueFileResource(path);
2112
0
  return(GetFirstImageInList(lines_image));
2113
0
}
2114

2115
/*
2116
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2117
%                                                                             %
2118
%                                                                             %
2119
%                                                                             %
2120
%     M e a n S h i f t I m a g e                                             %
2121
%                                                                             %
2122
%                                                                             %
2123
%                                                                             %
2124
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2125
%
2126
%  MeanShiftImage() delineate arbitrarily shaped clusters in the image. For
2127
%  each pixel, it visits all the pixels in the neighborhood specified by
2128
%  the window centered at the pixel and excludes those that are outside the
2129
%  radius=(window-1)/2 surrounding the pixel. From those pixels, it finds those
2130
%  that are within the specified color distance from the current mean, and
2131
%  computes a new x,y centroid from those coordinates and a new mean. This new
2132
%  x,y centroid is used as the center for a new window. This process iterates
2133
%  until it converges and the final mean is replaces the (original window
2134
%  center) pixel value. It repeats this process for the next pixel, etc.,
2135
%  until it processes all pixels in the image. Results are typically better with
2136
%  colorspaces other than sRGB. We recommend YIQ, YUV or YCbCr.
2137
%
2138
%  The format of the MeanShiftImage method is:
2139
%
2140
%      Image *MeanShiftImage(const Image *image,const size_t width,
2141
%        const size_t height,const double color_distance,
2142
%        ExceptionInfo *exception)
2143
%
2144
%  A description of each parameter follows:
2145
%
2146
%    o image: the image.
2147
%
2148
%    o width, height: find pixels in this neighborhood.
2149
%
2150
%    o color_distance: the color distance.
2151
%
2152
%    o exception: return any errors or warnings in this structure.
2153
%
2154
*/
2155
MagickExport Image *MeanShiftImage(const Image *image,const size_t width,
2156
  const size_t height,const double color_distance,ExceptionInfo *exception)
2157
0
{
2158
0
#define MaxMeanShiftIterations  100
2159
0
#define MeanShiftImageTag  "MeanShift/Image"
2160
2161
0
  CacheView
2162
0
    *image_view,
2163
0
    *mean_view,
2164
0
    *pixel_view;
2165
2166
0
  Image
2167
0
    *mean_image;
2168
2169
0
  MagickBooleanType
2170
0
    status;
2171
2172
0
  MagickOffsetType
2173
0
    progress;
2174
2175
0
  ssize_t
2176
0
    y;
2177
2178
0
  assert(image != (const Image *) NULL);
2179
0
  assert(image->signature == MagickCoreSignature);
2180
0
  assert(exception != (ExceptionInfo *) NULL);
2181
0
  assert(exception->signature == MagickCoreSignature);
2182
0
  if (IsEventLogging() != MagickFalse)
2183
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2184
0
  mean_image=CloneImage(image,0,0,MagickTrue,exception);
2185
0
  if (mean_image == (Image *) NULL)
2186
0
    return((Image *) NULL);
2187
0
  if (SetImageStorageClass(mean_image,DirectClass,exception) == MagickFalse)
2188
0
    {
2189
0
      mean_image=DestroyImage(mean_image);
2190
0
      return((Image *) NULL);
2191
0
    }
2192
0
  status=MagickTrue;
2193
0
  progress=0;
2194
0
  image_view=AcquireVirtualCacheView(image,exception);
2195
0
  pixel_view=AcquireVirtualCacheView(image,exception);
2196
0
  mean_view=AcquireAuthenticCacheView(mean_image,exception);
2197
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2198
  #pragma omp parallel for schedule(static) shared(status,progress) \
2199
    magick_number_threads(mean_image,mean_image,mean_image->rows,1)
2200
#endif
2201
0
  for (y=0; y < (ssize_t) mean_image->rows; y++)
2202
0
  {
2203
0
    const Quantum
2204
0
      *magick_restrict p;
2205
2206
0
    Quantum
2207
0
      *magick_restrict q;
2208
2209
0
    ssize_t
2210
0
      x;
2211
2212
0
    if (status == MagickFalse)
2213
0
      continue;
2214
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2215
0
    q=GetCacheViewAuthenticPixels(mean_view,0,y,mean_image->columns,1,
2216
0
      exception);
2217
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2218
0
      {
2219
0
        status=MagickFalse;
2220
0
        continue;
2221
0
      }
2222
0
    for (x=0; x < (ssize_t) mean_image->columns; x++)
2223
0
    {
2224
0
      PixelInfo
2225
0
        mean_pixel,
2226
0
        previous_pixel;
2227
2228
0
      PointInfo
2229
0
        mean_location,
2230
0
        previous_location;
2231
2232
0
      ssize_t
2233
0
        i;
2234
2235
0
      GetPixelInfo(image,&mean_pixel);
2236
0
      GetPixelInfoPixel(image,p,&mean_pixel);
2237
0
      mean_location.x=(double) x;
2238
0
      mean_location.y=(double) y;
2239
0
      for (i=0; i < MaxMeanShiftIterations; i++)
2240
0
      {
2241
0
        double
2242
0
          distance,
2243
0
          gamma = 1.0;
2244
2245
0
        PixelInfo
2246
0
          sum_pixel;
2247
2248
0
        PointInfo
2249
0
          sum_location;
2250
2251
0
        ssize_t
2252
0
          count,
2253
0
          v;
2254
2255
0
        sum_location.x=0.0;
2256
0
        sum_location.y=0.0;
2257
0
        GetPixelInfo(image,&sum_pixel);
2258
0
        previous_location=mean_location;
2259
0
        previous_pixel=mean_pixel;
2260
0
        count=0;
2261
0
        for (v=(-((ssize_t) height/2)); v <= (((ssize_t) height/2)); v++)
2262
0
        {
2263
0
          ssize_t
2264
0
            u;
2265
2266
0
          for (u=(-((ssize_t) width/2)); u <= (((ssize_t) width/2)); u++)
2267
0
          {
2268
0
            if ((v*v+u*u) <= (ssize_t) ((width/2)*(height/2)))
2269
0
              {
2270
0
                PixelInfo
2271
0
                  pixel;
2272
2273
0
                status=GetOneCacheViewVirtualPixelInfo(pixel_view,(ssize_t)
2274
0
                  MagickRound(mean_location.x+u),(ssize_t) MagickRound(
2275
0
                  mean_location.y+v),&pixel,exception);
2276
0
                distance=(mean_pixel.red-pixel.red)*(mean_pixel.red-pixel.red)+
2277
0
                  (mean_pixel.green-pixel.green)*(mean_pixel.green-pixel.green)+
2278
0
                  (mean_pixel.blue-pixel.blue)*(mean_pixel.blue-pixel.blue);
2279
0
                if (distance <= (color_distance*color_distance))
2280
0
                  {
2281
0
                    sum_location.x+=mean_location.x+u;
2282
0
                    sum_location.y+=mean_location.y+v;
2283
0
                    sum_pixel.red+=pixel.red;
2284
0
                    sum_pixel.green+=pixel.green;
2285
0
                    sum_pixel.blue+=pixel.blue;
2286
0
                    sum_pixel.alpha+=pixel.alpha;
2287
0
                    count++;
2288
0
                  }
2289
0
              }
2290
0
          }
2291
0
        }
2292
0
        if (count != 0)
2293
0
          gamma=MagickSafeReciprocal((double) count);
2294
0
        mean_location.x=gamma*sum_location.x;
2295
0
        mean_location.y=gamma*sum_location.y;
2296
0
        mean_pixel.red=gamma*sum_pixel.red;
2297
0
        mean_pixel.green=gamma*sum_pixel.green;
2298
0
        mean_pixel.blue=gamma*sum_pixel.blue;
2299
0
        mean_pixel.alpha=gamma*sum_pixel.alpha;
2300
0
        distance=(mean_location.x-previous_location.x)*
2301
0
          (mean_location.x-previous_location.x)+
2302
0
          (mean_location.y-previous_location.y)*
2303
0
          (mean_location.y-previous_location.y)+
2304
0
          255.0*QuantumScale*(mean_pixel.red-previous_pixel.red)*
2305
0
          255.0*QuantumScale*(mean_pixel.red-previous_pixel.red)+
2306
0
          255.0*QuantumScale*(mean_pixel.green-previous_pixel.green)*
2307
0
          255.0*QuantumScale*(mean_pixel.green-previous_pixel.green)+
2308
0
          255.0*QuantumScale*(mean_pixel.blue-previous_pixel.blue)*
2309
0
          255.0*QuantumScale*(mean_pixel.blue-previous_pixel.blue);
2310
0
        if (distance <= 3.0)
2311
0
          break;
2312
0
      }
2313
0
      SetPixelRed(mean_image,ClampToQuantum(mean_pixel.red),q);
2314
0
      SetPixelGreen(mean_image,ClampToQuantum(mean_pixel.green),q);
2315
0
      SetPixelBlue(mean_image,ClampToQuantum(mean_pixel.blue),q);
2316
0
      SetPixelAlpha(mean_image,ClampToQuantum(mean_pixel.alpha),q);
2317
0
      p+=(ptrdiff_t) GetPixelChannels(image);
2318
0
      q+=(ptrdiff_t) GetPixelChannels(mean_image);
2319
0
    }
2320
0
    if (SyncCacheViewAuthenticPixels(mean_view,exception) == MagickFalse)
2321
0
      status=MagickFalse;
2322
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2323
0
      {
2324
0
        MagickBooleanType
2325
0
          proceed;
2326
2327
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2328
        #pragma omp atomic
2329
#endif
2330
0
        progress++;
2331
0
        proceed=SetImageProgress(image,MeanShiftImageTag,progress,image->rows);
2332
0
        if (proceed == MagickFalse)
2333
0
          status=MagickFalse;
2334
0
      }
2335
0
  }
2336
0
  mean_view=DestroyCacheView(mean_view);
2337
0
  pixel_view=DestroyCacheView(pixel_view);
2338
0
  image_view=DestroyCacheView(image_view);
2339
0
  return(mean_image);
2340
0
}