Coverage Report

Created: 2025-06-16 07:00

/src/imagemagick/MagickCore/feature.c
Line
Count
Source (jump to first uncovered line)
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/script/license.php                               %
27
%                                                                             %
28
%  Unless required by applicable law or agreed to in writing, software        %
29
%  distributed under the License is distributed on an "AS IS" BASIS,          %
30
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
31
%  See the License for the specific language governing permissions and        %
32
%  limitations under the License.                                             %
33
%                                                                             %
34
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35
%
36
%
37
%
38
*/
39

40
/*
41
  Include declarations.
42
*/
43
#include "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:%.20gx%.20g;blur:%.20gx%.20g+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
    ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
676
0
  (void) memset(channel_features,0,length*
677
0
    sizeof(*channel_features));
678
  /*
679
    Form grays.
680
  */
681
0
  grays=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*grays));
682
0
  if (grays == (PixelPacket *) NULL)
683
0
    {
684
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
685
0
        channel_features);
686
0
      (void) ThrowMagickException(exception,GetMagickModule(),
687
0
        ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
688
0
      return(channel_features);
689
0
    }
690
0
  for (i=0; i <= (ssize_t) MaxMap; i++)
691
0
  {
692
0
    grays[i].red=(~0U);
693
0
    grays[i].green=(~0U);
694
0
    grays[i].blue=(~0U);
695
0
    grays[i].alpha=(~0U);
696
0
    grays[i].black=(~0U);
697
0
  }
698
0
  status=MagickTrue;
699
0
  image_view=AcquireVirtualCacheView(image,exception);
700
#if defined(MAGICKCORE_OPENMP_SUPPORT)
701
  #pragma omp parallel for schedule(static) shared(status) \
702
    magick_number_threads(image,image,image->rows,1)
703
#endif
704
0
  for (r=0; r < (ssize_t) image->rows; r++)
705
0
  {
706
0
    const Quantum
707
0
      *magick_restrict p;
708
709
0
    ssize_t
710
0
      x;
711
712
0
    if (status == MagickFalse)
713
0
      continue;
714
0
    p=GetCacheViewVirtualPixels(image_view,0,r,image->columns,1,exception);
715
0
    if (p == (const Quantum *) NULL)
716
0
      {
717
0
        status=MagickFalse;
718
0
        continue;
719
0
      }
720
0
    for (x=0; x < (ssize_t) image->columns; x++)
721
0
    {
722
0
      grays[ScaleQuantumToMap(GetPixelRed(image,p))].red=
723
0
        ScaleQuantumToMap(GetPixelRed(image,p));
724
0
      grays[ScaleQuantumToMap(GetPixelGreen(image,p))].green=
725
0
        ScaleQuantumToMap(GetPixelGreen(image,p));
726
0
      grays[ScaleQuantumToMap(GetPixelBlue(image,p))].blue=
727
0
        ScaleQuantumToMap(GetPixelBlue(image,p));
728
0
      if (image->colorspace == CMYKColorspace)
729
0
        grays[ScaleQuantumToMap(GetPixelBlack(image,p))].black=
730
0
          ScaleQuantumToMap(GetPixelBlack(image,p));
731
0
      if (image->alpha_trait != UndefinedPixelTrait)
732
0
        grays[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha=
733
0
          ScaleQuantumToMap(GetPixelAlpha(image,p));
734
0
      p+=(ptrdiff_t) GetPixelChannels(image);
735
0
    }
736
0
  }
737
0
  image_view=DestroyCacheView(image_view);
738
0
  if (status == MagickFalse)
739
0
    {
740
0
      grays=(PixelPacket *) RelinquishMagickMemory(grays);
741
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
742
0
        channel_features);
743
0
      return(channel_features);
744
0
    }
745
0
  (void) memset(&gray,0,sizeof(gray));
746
0
  for (i=0; i <= (ssize_t) MaxMap; i++)
747
0
  {
748
0
    if (grays[i].red != ~0U)
749
0
      grays[gray.red++].red=grays[i].red;
750
0
    if (grays[i].green != ~0U)
751
0
      grays[gray.green++].green=grays[i].green;
752
0
    if (grays[i].blue != ~0U)
753
0
      grays[gray.blue++].blue=grays[i].blue;
754
0
    if (image->colorspace == CMYKColorspace)
755
0
      if (grays[i].black != ~0U)
756
0
        grays[gray.black++].black=grays[i].black;
757
0
    if (image->alpha_trait != UndefinedPixelTrait)
758
0
      if (grays[i].alpha != ~0U)
759
0
        grays[gray.alpha++].alpha=grays[i].alpha;
760
0
  }
761
  /*
762
    Allocate spatial dependence matrix.
763
  */
764
0
  number_grays=gray.red;
765
0
  if (gray.green > number_grays)
766
0
    number_grays=gray.green;
767
0
  if (gray.blue > number_grays)
768
0
    number_grays=gray.blue;
769
0
  if (image->colorspace == CMYKColorspace)
770
0
    if (gray.black > number_grays)
771
0
      number_grays=gray.black;
772
0
  if (image->alpha_trait != UndefinedPixelTrait)
773
0
    if (gray.alpha > number_grays)
774
0
      number_grays=gray.alpha;
775
0
  cooccurrence=(ChannelStatistics **) AcquireQuantumMemory(number_grays,
776
0
    sizeof(*cooccurrence));
777
0
  density_x=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1,
778
0
    2*sizeof(*density_x));
779
0
  density_xy=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1,
780
0
    2*sizeof(*density_xy));
781
0
  density_y=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1,
782
0
    2*sizeof(*density_y));
783
0
  Q=(ChannelStatistics **) AcquireQuantumMemory(number_grays,sizeof(*Q));
784
0
  sum=(ChannelStatistics *) AcquireQuantumMemory(number_grays,sizeof(*sum));
785
0
  if ((cooccurrence == (ChannelStatistics **) NULL) ||
786
0
      (density_x == (ChannelStatistics *) NULL) ||
787
0
      (density_xy == (ChannelStatistics *) NULL) ||
788
0
      (density_y == (ChannelStatistics *) NULL) ||
789
0
      (Q == (ChannelStatistics **) NULL) ||
790
0
      (sum == (ChannelStatistics *) NULL))
791
0
    {
792
0
      if (Q != (ChannelStatistics **) NULL)
793
0
        {
794
0
          for (i=0; i < (ssize_t) number_grays; i++)
795
0
            Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]);
796
0
          Q=(ChannelStatistics **) RelinquishMagickMemory(Q);
797
0
        }
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
        {
808
0
          for (i=0; i < (ssize_t) number_grays; i++)
809
0
            cooccurrence[i]=(ChannelStatistics *)
810
0
              RelinquishMagickMemory(cooccurrence[i]);
811
0
          cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(
812
0
            cooccurrence);
813
0
        }
814
0
      grays=(PixelPacket *) RelinquishMagickMemory(grays);
815
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
816
0
        channel_features);
817
0
      (void) ThrowMagickException(exception,GetMagickModule(),
818
0
        ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
819
0
      return(channel_features);
820
0
    }
821
0
  (void) memset(&correlation,0,sizeof(correlation));
822
0
  (void) memset(density_x,0,2*(number_grays+1)*sizeof(*density_x));
823
0
  (void) memset(density_xy,0,2*(number_grays+1)*sizeof(*density_xy));
824
0
  (void) memset(density_y,0,2*(number_grays+1)*sizeof(*density_y));
825
0
  (void) memset(&mean,0,sizeof(mean));
826
0
  (void) memset(sum,0,number_grays*sizeof(*sum));
827
0
  (void) memset(&sum_squares,0,sizeof(sum_squares));
828
0
  (void) memset(density_xy,0,2*number_grays*sizeof(*density_xy));
829
0
  (void) memset(&entropy_x,0,sizeof(entropy_x));
830
0
  (void) memset(&entropy_xy,0,sizeof(entropy_xy));
831
0
  (void) memset(&entropy_xy1,0,sizeof(entropy_xy1));
832
0
  (void) memset(&entropy_xy2,0,sizeof(entropy_xy2));
833
0
  (void) memset(&entropy_y,0,sizeof(entropy_y));
834
0
  (void) memset(&variance,0,sizeof(variance));
835
0
  for (i=0; i < (ssize_t) number_grays; i++)
836
0
  {
837
0
    cooccurrence[i]=(ChannelStatistics *) AcquireQuantumMemory(number_grays,
838
0
      sizeof(**cooccurrence));
839
0
    Q[i]=(ChannelStatistics *) AcquireQuantumMemory(number_grays,sizeof(**Q));
840
0
    if ((cooccurrence[i] == (ChannelStatistics *) NULL) ||
841
0
        (Q[i] == (ChannelStatistics *) NULL))
842
0
      break;
843
0
    (void) memset(cooccurrence[i],0,number_grays*
844
0
      sizeof(**cooccurrence));
845
0
    (void) memset(Q[i],0,number_grays*sizeof(**Q));
846
0
  }
847
0
  if (i < (ssize_t) number_grays)
848
0
    {
849
0
      for (i--; i >= 0; i--)
850
0
      {
851
0
        if (Q[i] != (ChannelStatistics *) NULL)
852
0
          Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]);
853
0
        if (cooccurrence[i] != (ChannelStatistics *) NULL)
854
0
          cooccurrence[i]=(ChannelStatistics *)
855
0
            RelinquishMagickMemory(cooccurrence[i]);
856
0
      }
857
0
      Q=(ChannelStatistics **) RelinquishMagickMemory(Q);
858
0
      cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence);
859
0
      sum=(ChannelStatistics *) RelinquishMagickMemory(sum);
860
0
      density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y);
861
0
      density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy);
862
0
      density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x);
863
0
      grays=(PixelPacket *) RelinquishMagickMemory(grays);
864
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
865
0
        channel_features);
866
0
      (void) ThrowMagickException(exception,GetMagickModule(),
867
0
        ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
868
0
      return(channel_features);
869
0
    }
870
  /*
871
    Initialize spatial dependence matrix.
872
  */
873
0
  status=MagickTrue;
874
0
  image_view=AcquireVirtualCacheView(image,exception);
875
0
  for (r=0; r < (ssize_t) image->rows; r++)
876
0
  {
877
0
    const Quantum
878
0
      *magick_restrict p;
879
880
0
    ssize_t
881
0
      x;
882
883
0
    ssize_t
884
0
      offset,
885
0
      u,
886
0
      v;
887
888
0
    if (status == MagickFalse)
889
0
      continue;
890
0
    p=GetCacheViewVirtualPixels(image_view,-(ssize_t) distance,r,image->columns+
891
0
      2*distance,distance+2,exception);
892
0
    if (p == (const Quantum *) NULL)
893
0
      {
894
0
        status=MagickFalse;
895
0
        continue;
896
0
      }
897
0
    p+=(ptrdiff_t) distance*GetPixelChannels(image);;
898
0
    for (x=0; x < (ssize_t) image->columns; x++)
899
0
    {
900
0
      for (i=0; i < 4; i++)
901
0
      {
902
0
        switch (i)
903
0
        {
904
0
          case 0:
905
0
          default:
906
0
          {
907
            /*
908
              Horizontal adjacency.
909
            */
910
0
            offset=(ssize_t) distance;
911
0
            break;
912
0
          }
913
0
          case 1:
914
0
          {
915
            /*
916
              Vertical adjacency.
917
            */
918
0
            offset=(ssize_t) (image->columns+2*distance);
919
0
            break;
920
0
          }
921
0
          case 2:
922
0
          {
923
            /*
924
              Right diagonal adjacency.
925
            */
926
0
            offset=(ssize_t) ((image->columns+2*distance)-distance);
927
0
            break;
928
0
          }
929
0
          case 3:
930
0
          {
931
            /*
932
              Left diagonal adjacency.
933
            */
934
0
            offset=(ssize_t) ((image->columns+2*distance)+distance);
935
0
            break;
936
0
          }
937
0
        }
938
0
        u=0;
939
0
        v=0;
940
0
        while (grays[u].red != ScaleQuantumToMap(GetPixelRed(image,p)))
941
0
          u++;
942
0
        while (grays[v].red != ScaleQuantumToMap(GetPixelRed(image,p+offset*(ssize_t) GetPixelChannels(image))))
943
0
          v++;
944
0
        cooccurrence[u][v].direction[i].red++;
945
0
        cooccurrence[v][u].direction[i].red++;
946
0
        u=0;
947
0
        v=0;
948
0
        while (grays[u].green != ScaleQuantumToMap(GetPixelGreen(image,p)))
949
0
          u++;
950
0
        while (grays[v].green != ScaleQuantumToMap(GetPixelGreen(image,p+offset*(ssize_t) GetPixelChannels(image))))
951
0
          v++;
952
0
        cooccurrence[u][v].direction[i].green++;
953
0
        cooccurrence[v][u].direction[i].green++;
954
0
        u=0;
955
0
        v=0;
956
0
        while (grays[u].blue != ScaleQuantumToMap(GetPixelBlue(image,p)))
957
0
          u++;
958
0
        while (grays[v].blue != ScaleQuantumToMap(GetPixelBlue(image,p+offset*(ssize_t) GetPixelChannels(image))))
959
0
          v++;
960
0
        cooccurrence[u][v].direction[i].blue++;
961
0
        cooccurrence[v][u].direction[i].blue++;
962
0
        if (image->colorspace == CMYKColorspace)
963
0
          {
964
0
            u=0;
965
0
            v=0;
966
0
            while (grays[u].black != ScaleQuantumToMap(GetPixelBlack(image,p)))
967
0
              u++;
968
0
            while (grays[v].black != ScaleQuantumToMap(GetPixelBlack(image,p+offset*(ssize_t) GetPixelChannels(image))))
969
0
              v++;
970
0
            cooccurrence[u][v].direction[i].black++;
971
0
            cooccurrence[v][u].direction[i].black++;
972
0
          }
973
0
        if (image->alpha_trait != UndefinedPixelTrait)
974
0
          {
975
0
            u=0;
976
0
            v=0;
977
0
            while (grays[u].alpha != ScaleQuantumToMap(GetPixelAlpha(image,p)))
978
0
              u++;
979
0
            while (grays[v].alpha != ScaleQuantumToMap(GetPixelAlpha(image,p+offset*(ssize_t) GetPixelChannels(image))))
980
0
              v++;
981
0
            cooccurrence[u][v].direction[i].alpha++;
982
0
            cooccurrence[v][u].direction[i].alpha++;
983
0
          }
984
0
      }
985
0
      p+=(ptrdiff_t) GetPixelChannels(image);
986
0
    }
987
0
  }
988
0
  grays=(PixelPacket *) RelinquishMagickMemory(grays);
989
0
  image_view=DestroyCacheView(image_view);
990
0
  if (status == MagickFalse)
991
0
    {
992
0
      for (i=0; i < (ssize_t) number_grays; i++)
993
0
        cooccurrence[i]=(ChannelStatistics *)
994
0
          RelinquishMagickMemory(cooccurrence[i]);
995
0
      cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence);
996
0
      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
997
0
        channel_features);
998
0
      (void) ThrowMagickException(exception,GetMagickModule(),
999
0
        ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
1000
0
      return(channel_features);
1001
0
    }
1002
  /*
1003
    Normalize spatial dependence matrix.
1004
  */
1005
0
  for (i=0; i < 4; i++)
1006
0
  {
1007
0
    double
1008
0
      normalize;
1009
1010
0
    ssize_t
1011
0
      y;
1012
1013
0
    switch (i)
1014
0
    {
1015
0
      case 0:
1016
0
      default:
1017
0
      {
1018
        /*
1019
          Horizontal adjacency.
1020
        */
1021
0
        normalize=2.0*image->rows*(image->columns-distance);
1022
0
        break;
1023
0
      }
1024
0
      case 1:
1025
0
      {
1026
        /*
1027
          Vertical adjacency.
1028
        */
1029
0
        normalize=2.0*(image->rows-distance)*image->columns;
1030
0
        break;
1031
0
      }
1032
0
      case 2:
1033
0
      {
1034
        /*
1035
          Right diagonal adjacency.
1036
        */
1037
0
        normalize=2.0*(image->rows-distance)*(image->columns-distance);
1038
0
        break;
1039
0
      }
1040
0
      case 3:
1041
0
      {
1042
        /*
1043
          Left diagonal adjacency.
1044
        */
1045
0
        normalize=2.0*(image->rows-distance)*(image->columns-distance);
1046
0
        break;
1047
0
      }
1048
0
    }
1049
0
    normalize=MagickSafeReciprocal(normalize);
1050
0
    for (y=0; y < (ssize_t) number_grays; y++)
1051
0
    {
1052
0
      ssize_t
1053
0
        x;
1054
1055
0
      for (x=0; x < (ssize_t) number_grays; x++)
1056
0
      {
1057
0
        cooccurrence[x][y].direction[i].red*=normalize;
1058
0
        cooccurrence[x][y].direction[i].green*=normalize;
1059
0
        cooccurrence[x][y].direction[i].blue*=normalize;
1060
0
        if (image->colorspace == CMYKColorspace)
1061
0
          cooccurrence[x][y].direction[i].black*=normalize;
1062
0
        if (image->alpha_trait != UndefinedPixelTrait)
1063
0
          cooccurrence[x][y].direction[i].alpha*=normalize;
1064
0
      }
1065
0
    }
1066
0
  }
1067
  /*
1068
    Compute texture features.
1069
  */
1070
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1071
  #pragma omp parallel for schedule(static) shared(status) \
1072
    magick_number_threads(image,image,number_grays,1)
1073
#endif
1074
0
  for (i=0; i < 4; i++)
1075
0
  {
1076
0
    ssize_t
1077
0
      y;
1078
1079
0
    for (y=0; y < (ssize_t) number_grays; y++)
1080
0
    {
1081
0
      ssize_t
1082
0
        x;
1083
1084
0
      for (x=0; x < (ssize_t) number_grays; x++)
1085
0
      {
1086
        /*
1087
          Angular second moment:  measure of homogeneity of the image.
1088
        */
1089
0
        channel_features[RedPixelChannel].angular_second_moment[i]+=
1090
0
          cooccurrence[x][y].direction[i].red*
1091
0
          cooccurrence[x][y].direction[i].red;
1092
0
        channel_features[GreenPixelChannel].angular_second_moment[i]+=
1093
0
          cooccurrence[x][y].direction[i].green*
1094
0
          cooccurrence[x][y].direction[i].green;
1095
0
        channel_features[BluePixelChannel].angular_second_moment[i]+=
1096
0
          cooccurrence[x][y].direction[i].blue*
1097
0
          cooccurrence[x][y].direction[i].blue;
1098
0
        if (image->colorspace == CMYKColorspace)
1099
0
          channel_features[BlackPixelChannel].angular_second_moment[i]+=
1100
0
            cooccurrence[x][y].direction[i].black*
1101
0
            cooccurrence[x][y].direction[i].black;
1102
0
        if (image->alpha_trait != UndefinedPixelTrait)
1103
0
          channel_features[AlphaPixelChannel].angular_second_moment[i]+=
1104
0
            cooccurrence[x][y].direction[i].alpha*
1105
0
            cooccurrence[x][y].direction[i].alpha;
1106
        /*
1107
          Correlation: measure of linear-dependencies in the image.
1108
        */
1109
0
        sum[y].direction[i].red+=cooccurrence[x][y].direction[i].red;
1110
0
        sum[y].direction[i].green+=cooccurrence[x][y].direction[i].green;
1111
0
        sum[y].direction[i].blue+=cooccurrence[x][y].direction[i].blue;
1112
0
        if (image->colorspace == CMYKColorspace)
1113
0
          sum[y].direction[i].black+=cooccurrence[x][y].direction[i].black;
1114
0
        if (image->alpha_trait != UndefinedPixelTrait)
1115
0
          sum[y].direction[i].alpha+=cooccurrence[x][y].direction[i].alpha;
1116
0
        correlation.direction[i].red+=x*y*cooccurrence[x][y].direction[i].red;
1117
0
        correlation.direction[i].green+=x*y*
1118
0
          cooccurrence[x][y].direction[i].green;
1119
0
        correlation.direction[i].blue+=x*y*
1120
0
          cooccurrence[x][y].direction[i].blue;
1121
0
        if (image->colorspace == CMYKColorspace)
1122
0
          correlation.direction[i].black+=x*y*
1123
0
            cooccurrence[x][y].direction[i].black;
1124
0
        if (image->alpha_trait != UndefinedPixelTrait)
1125
0
          correlation.direction[i].alpha+=x*y*
1126
0
            cooccurrence[x][y].direction[i].alpha;
1127
        /*
1128
          Inverse Difference Moment.
1129
        */
1130
0
        channel_features[RedPixelChannel].inverse_difference_moment[i]+=
1131
0
          cooccurrence[x][y].direction[i].red/((y-x)*(y-x)+1);
1132
0
        channel_features[GreenPixelChannel].inverse_difference_moment[i]+=
1133
0
          cooccurrence[x][y].direction[i].green/((y-x)*(y-x)+1);
1134
0
        channel_features[BluePixelChannel].inverse_difference_moment[i]+=
1135
0
          cooccurrence[x][y].direction[i].blue/((y-x)*(y-x)+1);
1136
0
        if (image->colorspace == CMYKColorspace)
1137
0
          channel_features[BlackPixelChannel].inverse_difference_moment[i]+=
1138
0
            cooccurrence[x][y].direction[i].black/((y-x)*(y-x)+1);
1139
0
        if (image->alpha_trait != UndefinedPixelTrait)
1140
0
          channel_features[AlphaPixelChannel].inverse_difference_moment[i]+=
1141
0
            cooccurrence[x][y].direction[i].alpha/((y-x)*(y-x)+1);
1142
        /*
1143
          Sum average.
1144
        */
1145
0
        density_xy[y+x+2].direction[i].red+=
1146
0
          cooccurrence[x][y].direction[i].red;
1147
0
        density_xy[y+x+2].direction[i].green+=
1148
0
          cooccurrence[x][y].direction[i].green;
1149
0
        density_xy[y+x+2].direction[i].blue+=
1150
0
          cooccurrence[x][y].direction[i].blue;
1151
0
        if (image->colorspace == CMYKColorspace)
1152
0
          density_xy[y+x+2].direction[i].black+=
1153
0
            cooccurrence[x][y].direction[i].black;
1154
0
        if (image->alpha_trait != UndefinedPixelTrait)
1155
0
          density_xy[y+x+2].direction[i].alpha+=
1156
0
            cooccurrence[x][y].direction[i].alpha;
1157
        /*
1158
          Entropy.
1159
        */
1160
0
        channel_features[RedPixelChannel].entropy[i]-=
1161
0
          cooccurrence[x][y].direction[i].red*
1162
0
          log2(cooccurrence[x][y].direction[i].red);
1163
0
        channel_features[GreenPixelChannel].entropy[i]-=
1164
0
          cooccurrence[x][y].direction[i].green*
1165
0
          log2(cooccurrence[x][y].direction[i].green);
1166
0
        channel_features[BluePixelChannel].entropy[i]-=
1167
0
          cooccurrence[x][y].direction[i].blue*
1168
0
          log2(cooccurrence[x][y].direction[i].blue);
1169
0
        if (image->colorspace == CMYKColorspace)
1170
0
          channel_features[BlackPixelChannel].entropy[i]-=
1171
0
            cooccurrence[x][y].direction[i].black*
1172
0
            log2(cooccurrence[x][y].direction[i].black);
1173
0
        if (image->alpha_trait != UndefinedPixelTrait)
1174
0
          channel_features[AlphaPixelChannel].entropy[i]-=
1175
0
            cooccurrence[x][y].direction[i].alpha*
1176
0
            log2(cooccurrence[x][y].direction[i].alpha);
1177
        /*
1178
          Information Measures of Correlation.
1179
        */
1180
0
        density_x[x].direction[i].red+=cooccurrence[x][y].direction[i].red;
1181
0
        density_x[x].direction[i].green+=cooccurrence[x][y].direction[i].green;
1182
0
        density_x[x].direction[i].blue+=cooccurrence[x][y].direction[i].blue;
1183
0
        if (image->alpha_trait != UndefinedPixelTrait)
1184
0
          density_x[x].direction[i].alpha+=
1185
0
            cooccurrence[x][y].direction[i].alpha;
1186
0
        if (image->colorspace == CMYKColorspace)
1187
0
          density_x[x].direction[i].black+=
1188
0
            cooccurrence[x][y].direction[i].black;
1189
0
        density_y[y].direction[i].red+=cooccurrence[x][y].direction[i].red;
1190
0
        density_y[y].direction[i].green+=cooccurrence[x][y].direction[i].green;
1191
0
        density_y[y].direction[i].blue+=cooccurrence[x][y].direction[i].blue;
1192
0
        if (image->colorspace == CMYKColorspace)
1193
0
          density_y[y].direction[i].black+=
1194
0
            cooccurrence[x][y].direction[i].black;
1195
0
        if (image->alpha_trait != UndefinedPixelTrait)
1196
0
          density_y[y].direction[i].alpha+=
1197
0
            cooccurrence[x][y].direction[i].alpha;
1198
0
      }
1199
0
      mean.direction[i].red+=y*sum[y].direction[i].red;
1200
0
      sum_squares.direction[i].red+=y*y*sum[y].direction[i].red;
1201
0
      mean.direction[i].green+=y*sum[y].direction[i].green;
1202
0
      sum_squares.direction[i].green+=y*y*sum[y].direction[i].green;
1203
0
      mean.direction[i].blue+=y*sum[y].direction[i].blue;
1204
0
      sum_squares.direction[i].blue+=y*y*sum[y].direction[i].blue;
1205
0
      if (image->colorspace == CMYKColorspace)
1206
0
        {
1207
0
          mean.direction[i].black+=y*sum[y].direction[i].black;
1208
0
          sum_squares.direction[i].black+=y*y*sum[y].direction[i].black;
1209
0
        }
1210
0
      if (image->alpha_trait != UndefinedPixelTrait)
1211
0
        {
1212
0
          mean.direction[i].alpha+=y*sum[y].direction[i].alpha;
1213
0
          sum_squares.direction[i].alpha+=y*y*sum[y].direction[i].alpha;
1214
0
        }
1215
0
    }
1216
    /*
1217
      Correlation: measure of linear-dependencies in the image.
1218
    */
1219
0
    channel_features[RedPixelChannel].correlation[i]=
1220
0
      (correlation.direction[i].red-mean.direction[i].red*
1221
0
      mean.direction[i].red)/(sqrt(sum_squares.direction[i].red-
1222
0
      (mean.direction[i].red*mean.direction[i].red))*sqrt(
1223
0
      sum_squares.direction[i].red-(mean.direction[i].red*
1224
0
      mean.direction[i].red)));
1225
0
    channel_features[GreenPixelChannel].correlation[i]=
1226
0
      (correlation.direction[i].green-mean.direction[i].green*
1227
0
      mean.direction[i].green)/(sqrt(sum_squares.direction[i].green-
1228
0
      (mean.direction[i].green*mean.direction[i].green))*sqrt(
1229
0
      sum_squares.direction[i].green-(mean.direction[i].green*
1230
0
      mean.direction[i].green)));
1231
0
    channel_features[BluePixelChannel].correlation[i]=
1232
0
      (correlation.direction[i].blue-mean.direction[i].blue*
1233
0
      mean.direction[i].blue)/(sqrt(sum_squares.direction[i].blue-
1234
0
      (mean.direction[i].blue*mean.direction[i].blue))*sqrt(
1235
0
      sum_squares.direction[i].blue-(mean.direction[i].blue*
1236
0
      mean.direction[i].blue)));
1237
0
    if (image->colorspace == CMYKColorspace)
1238
0
      channel_features[BlackPixelChannel].correlation[i]=
1239
0
        (correlation.direction[i].black-mean.direction[i].black*
1240
0
        mean.direction[i].black)/(sqrt(sum_squares.direction[i].black-
1241
0
        (mean.direction[i].black*mean.direction[i].black))*sqrt(
1242
0
        sum_squares.direction[i].black-(mean.direction[i].black*
1243
0
        mean.direction[i].black)));
1244
0
    if (image->alpha_trait != UndefinedPixelTrait)
1245
0
      channel_features[AlphaPixelChannel].correlation[i]=
1246
0
        (correlation.direction[i].alpha-mean.direction[i].alpha*
1247
0
        mean.direction[i].alpha)/(sqrt(sum_squares.direction[i].alpha-
1248
0
        (mean.direction[i].alpha*mean.direction[i].alpha))*sqrt(
1249
0
        sum_squares.direction[i].alpha-(mean.direction[i].alpha*
1250
0
        mean.direction[i].alpha)));
1251
0
  }
1252
  /*
1253
    Compute more texture features.
1254
  */
1255
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1256
  #pragma omp parallel for schedule(static) shared(status) \
1257
    magick_number_threads(image,image,number_grays,1)
1258
#endif
1259
0
  for (i=0; i < 4; i++)
1260
0
  {
1261
0
    ssize_t
1262
0
      x;
1263
1264
0
    for (x=2; x < (ssize_t) (2*number_grays); x++)
1265
0
    {
1266
      /*
1267
        Sum average.
1268
      */
1269
0
      channel_features[RedPixelChannel].sum_average[i]+=
1270
0
        x*density_xy[x].direction[i].red;
1271
0
      channel_features[GreenPixelChannel].sum_average[i]+=
1272
0
        x*density_xy[x].direction[i].green;
1273
0
      channel_features[BluePixelChannel].sum_average[i]+=
1274
0
        x*density_xy[x].direction[i].blue;
1275
0
      if (image->colorspace == CMYKColorspace)
1276
0
        channel_features[BlackPixelChannel].sum_average[i]+=
1277
0
          x*density_xy[x].direction[i].black;
1278
0
      if (image->alpha_trait != UndefinedPixelTrait)
1279
0
        channel_features[AlphaPixelChannel].sum_average[i]+=
1280
0
          x*density_xy[x].direction[i].alpha;
1281
      /*
1282
        Sum entropy.
1283
      */
1284
0
      channel_features[RedPixelChannel].sum_entropy[i]-=
1285
0
        density_xy[x].direction[i].red*
1286
0
        log2(density_xy[x].direction[i].red);
1287
0
      channel_features[GreenPixelChannel].sum_entropy[i]-=
1288
0
        density_xy[x].direction[i].green*
1289
0
        log2(density_xy[x].direction[i].green);
1290
0
      channel_features[BluePixelChannel].sum_entropy[i]-=
1291
0
        density_xy[x].direction[i].blue*
1292
0
        log2(density_xy[x].direction[i].blue);
1293
0
      if (image->colorspace == CMYKColorspace)
1294
0
        channel_features[BlackPixelChannel].sum_entropy[i]-=
1295
0
          density_xy[x].direction[i].black*
1296
0
          log2(density_xy[x].direction[i].black);
1297
0
      if (image->alpha_trait != UndefinedPixelTrait)
1298
0
        channel_features[AlphaPixelChannel].sum_entropy[i]-=
1299
0
          density_xy[x].direction[i].alpha*
1300
0
          log2(density_xy[x].direction[i].alpha);
1301
      /*
1302
        Sum variance.
1303
      */
1304
0
      channel_features[RedPixelChannel].sum_variance[i]+=
1305
0
        (x-channel_features[RedPixelChannel].sum_entropy[i])*
1306
0
        (x-channel_features[RedPixelChannel].sum_entropy[i])*
1307
0
        density_xy[x].direction[i].red;
1308
0
      channel_features[GreenPixelChannel].sum_variance[i]+=
1309
0
        (x-channel_features[GreenPixelChannel].sum_entropy[i])*
1310
0
        (x-channel_features[GreenPixelChannel].sum_entropy[i])*
1311
0
        density_xy[x].direction[i].green;
1312
0
      channel_features[BluePixelChannel].sum_variance[i]+=
1313
0
        (x-channel_features[BluePixelChannel].sum_entropy[i])*
1314
0
        (x-channel_features[BluePixelChannel].sum_entropy[i])*
1315
0
        density_xy[x].direction[i].blue;
1316
0
      if (image->colorspace == CMYKColorspace)
1317
0
        channel_features[BlackPixelChannel].sum_variance[i]+=
1318
0
          (x-channel_features[BlackPixelChannel].sum_entropy[i])*
1319
0
          (x-channel_features[BlackPixelChannel].sum_entropy[i])*
1320
0
          density_xy[x].direction[i].black;
1321
0
      if (image->alpha_trait != UndefinedPixelTrait)
1322
0
        channel_features[AlphaPixelChannel].sum_variance[i]+=
1323
0
          (x-channel_features[AlphaPixelChannel].sum_entropy[i])*
1324
0
          (x-channel_features[AlphaPixelChannel].sum_entropy[i])*
1325
0
          density_xy[x].direction[i].alpha;
1326
0
    }
1327
0
  }
1328
  /*
1329
    Compute more texture features.
1330
  */
1331
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1332
  #pragma omp parallel for schedule(static) shared(status) \
1333
    magick_number_threads(image,image,number_grays,1)
1334
#endif
1335
0
  for (i=0; i < 4; i++)
1336
0
  {
1337
0
    ssize_t
1338
0
      y;
1339
1340
0
    for (y=0; y < (ssize_t) number_grays; y++)
1341
0
    {
1342
0
      ssize_t
1343
0
        x;
1344
1345
0
      for (x=0; x < (ssize_t) number_grays; x++)
1346
0
      {
1347
        /*
1348
          Sum of Squares: Variance
1349
        */
1350
0
        variance.direction[i].red+=(y-mean.direction[i].red+1)*
1351
0
          (y-mean.direction[i].red+1)*cooccurrence[x][y].direction[i].red;
1352
0
        variance.direction[i].green+=(y-mean.direction[i].green+1)*
1353
0
          (y-mean.direction[i].green+1)*cooccurrence[x][y].direction[i].green;
1354
0
        variance.direction[i].blue+=(y-mean.direction[i].blue+1)*
1355
0
          (y-mean.direction[i].blue+1)*cooccurrence[x][y].direction[i].blue;
1356
0
        if (image->colorspace == CMYKColorspace)
1357
0
          variance.direction[i].black+=(y-mean.direction[i].black+1)*
1358
0
            (y-mean.direction[i].black+1)*cooccurrence[x][y].direction[i].black;
1359
0
        if (image->alpha_trait != UndefinedPixelTrait)
1360
0
          variance.direction[i].alpha+=(y-mean.direction[i].alpha+1)*
1361
0
            (y-mean.direction[i].alpha+1)*
1362
0
            cooccurrence[x][y].direction[i].alpha;
1363
        /*
1364
          Sum average / Difference Variance.
1365
        */
1366
0
        density_xy[MagickAbsoluteValue(y-x)].direction[i].red+=
1367
0
          cooccurrence[x][y].direction[i].red;
1368
0
        density_xy[MagickAbsoluteValue(y-x)].direction[i].green+=
1369
0
          cooccurrence[x][y].direction[i].green;
1370
0
        density_xy[MagickAbsoluteValue(y-x)].direction[i].blue+=
1371
0
          cooccurrence[x][y].direction[i].blue;
1372
0
        if (image->colorspace == CMYKColorspace)
1373
0
          density_xy[MagickAbsoluteValue(y-x)].direction[i].black+=
1374
0
            cooccurrence[x][y].direction[i].black;
1375
0
        if (image->alpha_trait != UndefinedPixelTrait)
1376
0
          density_xy[MagickAbsoluteValue(y-x)].direction[i].alpha+=
1377
0
            cooccurrence[x][y].direction[i].alpha;
1378
        /*
1379
          Information Measures of Correlation.
1380
        */
1381
0
        entropy_xy.direction[i].red-=cooccurrence[x][y].direction[i].red*
1382
0
          log2(cooccurrence[x][y].direction[i].red);
1383
0
        entropy_xy.direction[i].green-=cooccurrence[x][y].direction[i].green*
1384
0
          log2(cooccurrence[x][y].direction[i].green);
1385
0
        entropy_xy.direction[i].blue-=cooccurrence[x][y].direction[i].blue*
1386
0
          log2(cooccurrence[x][y].direction[i].blue);
1387
0
        if (image->colorspace == CMYKColorspace)
1388
0
          entropy_xy.direction[i].black-=cooccurrence[x][y].direction[i].black*
1389
0
            log2(cooccurrence[x][y].direction[i].black);
1390
0
        if (image->alpha_trait != UndefinedPixelTrait)
1391
0
          entropy_xy.direction[i].alpha-=
1392
0
            cooccurrence[x][y].direction[i].alpha*log2(
1393
0
            cooccurrence[x][y].direction[i].alpha);
1394
0
        entropy_xy1.direction[i].red-=(cooccurrence[x][y].direction[i].red*
1395
0
          log2(density_x[x].direction[i].red*density_y[y].direction[i].red));
1396
0
        entropy_xy1.direction[i].green-=(cooccurrence[x][y].direction[i].green*
1397
0
          log2(density_x[x].direction[i].green*
1398
0
          density_y[y].direction[i].green));
1399
0
        entropy_xy1.direction[i].blue-=(cooccurrence[x][y].direction[i].blue*
1400
0
          log2(density_x[x].direction[i].blue*density_y[y].direction[i].blue));
1401
0
        if (image->colorspace == CMYKColorspace)
1402
0
          entropy_xy1.direction[i].black-=(
1403
0
            cooccurrence[x][y].direction[i].black*log2(
1404
0
            density_x[x].direction[i].black*density_y[y].direction[i].black));
1405
0
        if (image->alpha_trait != UndefinedPixelTrait)
1406
0
          entropy_xy1.direction[i].alpha-=(
1407
0
            cooccurrence[x][y].direction[i].alpha*log2(
1408
0
            density_x[x].direction[i].alpha*density_y[y].direction[i].alpha));
1409
0
        entropy_xy2.direction[i].red-=(density_x[x].direction[i].red*
1410
0
          density_y[y].direction[i].red*log2(density_x[x].direction[i].red*
1411
0
          density_y[y].direction[i].red));
1412
0
        entropy_xy2.direction[i].green-=(density_x[x].direction[i].green*
1413
0
          density_y[y].direction[i].green*log2(density_x[x].direction[i].green*
1414
0
          density_y[y].direction[i].green));
1415
0
        entropy_xy2.direction[i].blue-=(density_x[x].direction[i].blue*
1416
0
          density_y[y].direction[i].blue*log2(density_x[x].direction[i].blue*
1417
0
          density_y[y].direction[i].blue));
1418
0
        if (image->colorspace == CMYKColorspace)
1419
0
          entropy_xy2.direction[i].black-=(density_x[x].direction[i].black*
1420
0
            density_y[y].direction[i].black*log2(
1421
0
            density_x[x].direction[i].black*density_y[y].direction[i].black));
1422
0
        if (image->alpha_trait != UndefinedPixelTrait)
1423
0
          entropy_xy2.direction[i].alpha-=(density_x[x].direction[i].alpha*
1424
0
            density_y[y].direction[i].alpha*log2(
1425
0
            density_x[x].direction[i].alpha*density_y[y].direction[i].alpha));
1426
0
      }
1427
0
    }
1428
0
    channel_features[RedPixelChannel].variance_sum_of_squares[i]=
1429
0
      variance.direction[i].red;
1430
0
    channel_features[GreenPixelChannel].variance_sum_of_squares[i]=
1431
0
      variance.direction[i].green;
1432
0
    channel_features[BluePixelChannel].variance_sum_of_squares[i]=
1433
0
      variance.direction[i].blue;
1434
0
    if (image->colorspace == CMYKColorspace)
1435
0
      channel_features[BlackPixelChannel].variance_sum_of_squares[i]=
1436
0
        variance.direction[i].black;
1437
0
    if (image->alpha_trait != UndefinedPixelTrait)
1438
0
      channel_features[AlphaPixelChannel].variance_sum_of_squares[i]=
1439
0
        variance.direction[i].alpha;
1440
0
  }
1441
  /*
1442
    Compute more texture features.
1443
  */
1444
0
  (void) memset(&variance,0,sizeof(variance));
1445
0
  (void) memset(&sum_squares,0,sizeof(sum_squares));
1446
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1447
  #pragma omp parallel for schedule(static) shared(status) \
1448
    magick_number_threads(image,image,number_grays,1)
1449
#endif
1450
0
  for (i=0; i < 4; i++)
1451
0
  {
1452
0
    ssize_t
1453
0
      x;
1454
1455
0
    for (x=0; x < (ssize_t) number_grays; x++)
1456
0
    {
1457
      /*
1458
        Difference variance.
1459
      */
1460
0
      variance.direction[i].red+=density_xy[x].direction[i].red;
1461
0
      variance.direction[i].green+=density_xy[x].direction[i].green;
1462
0
      variance.direction[i].blue+=density_xy[x].direction[i].blue;
1463
0
      if (image->colorspace == CMYKColorspace)
1464
0
        variance.direction[i].black+=density_xy[x].direction[i].black;
1465
0
      if (image->alpha_trait != UndefinedPixelTrait)
1466
0
        variance.direction[i].alpha+=density_xy[x].direction[i].alpha;
1467
0
      sum_squares.direction[i].red+=density_xy[x].direction[i].red*
1468
0
        density_xy[x].direction[i].red;
1469
0
      sum_squares.direction[i].green+=density_xy[x].direction[i].green*
1470
0
        density_xy[x].direction[i].green;
1471
0
      sum_squares.direction[i].blue+=density_xy[x].direction[i].blue*
1472
0
        density_xy[x].direction[i].blue;
1473
0
      if (image->colorspace == CMYKColorspace)
1474
0
        sum_squares.direction[i].black+=density_xy[x].direction[i].black*
1475
0
          density_xy[x].direction[i].black;
1476
0
      if (image->alpha_trait != UndefinedPixelTrait)
1477
0
        sum_squares.direction[i].alpha+=density_xy[x].direction[i].alpha*
1478
0
          density_xy[x].direction[i].alpha;
1479
      /*
1480
        Difference entropy.
1481
      */
1482
0
      channel_features[RedPixelChannel].difference_entropy[i]-=
1483
0
        density_xy[x].direction[i].red*
1484
0
        log2(density_xy[x].direction[i].red);
1485
0
      channel_features[GreenPixelChannel].difference_entropy[i]-=
1486
0
        density_xy[x].direction[i].green*
1487
0
        log2(density_xy[x].direction[i].green);
1488
0
      channel_features[BluePixelChannel].difference_entropy[i]-=
1489
0
        density_xy[x].direction[i].blue*
1490
0
        log2(density_xy[x].direction[i].blue);
1491
0
      if (image->colorspace == CMYKColorspace)
1492
0
        channel_features[BlackPixelChannel].difference_entropy[i]-=
1493
0
          density_xy[x].direction[i].black*
1494
0
          log2(density_xy[x].direction[i].black);
1495
0
      if (image->alpha_trait != UndefinedPixelTrait)
1496
0
        channel_features[AlphaPixelChannel].difference_entropy[i]-=
1497
0
          density_xy[x].direction[i].alpha*
1498
0
          log2(density_xy[x].direction[i].alpha);
1499
      /*
1500
        Information Measures of Correlation.
1501
      */
1502
0
      entropy_x.direction[i].red-=(density_x[x].direction[i].red*
1503
0
        log2(density_x[x].direction[i].red));
1504
0
      entropy_x.direction[i].green-=(density_x[x].direction[i].green*
1505
0
        log2(density_x[x].direction[i].green));
1506
0
      entropy_x.direction[i].blue-=(density_x[x].direction[i].blue*
1507
0
        log2(density_x[x].direction[i].blue));
1508
0
      if (image->colorspace == CMYKColorspace)
1509
0
        entropy_x.direction[i].black-=(density_x[x].direction[i].black*
1510
0
          log2(density_x[x].direction[i].black));
1511
0
      if (image->alpha_trait != UndefinedPixelTrait)
1512
0
        entropy_x.direction[i].alpha-=(density_x[x].direction[i].alpha*
1513
0
          log2(density_x[x].direction[i].alpha));
1514
0
      entropy_y.direction[i].red-=(density_y[x].direction[i].red*
1515
0
        log2(density_y[x].direction[i].red));
1516
0
      entropy_y.direction[i].green-=(density_y[x].direction[i].green*
1517
0
        log2(density_y[x].direction[i].green));
1518
0
      entropy_y.direction[i].blue-=(density_y[x].direction[i].blue*
1519
0
        log2(density_y[x].direction[i].blue));
1520
0
      if (image->colorspace == CMYKColorspace)
1521
0
        entropy_y.direction[i].black-=(density_y[x].direction[i].black*
1522
0
          log2(density_y[x].direction[i].black));
1523
0
      if (image->alpha_trait != UndefinedPixelTrait)
1524
0
        entropy_y.direction[i].alpha-=(density_y[x].direction[i].alpha*
1525
0
          log2(density_y[x].direction[i].alpha));
1526
0
    }
1527
    /*
1528
      Difference variance.
1529
    */
1530
0
    channel_features[RedPixelChannel].difference_variance[i]=
1531
0
      (((double) number_grays*number_grays*sum_squares.direction[i].red)-
1532
0
      (variance.direction[i].red*variance.direction[i].red))/
1533
0
      ((double) number_grays*number_grays*number_grays*number_grays);
1534
0
    channel_features[GreenPixelChannel].difference_variance[i]=
1535
0
      (((double) number_grays*number_grays*sum_squares.direction[i].green)-
1536
0
      (variance.direction[i].green*variance.direction[i].green))/
1537
0
      ((double) number_grays*number_grays*number_grays*number_grays);
1538
0
    channel_features[BluePixelChannel].difference_variance[i]=
1539
0
      (((double) number_grays*number_grays*sum_squares.direction[i].blue)-
1540
0
      (variance.direction[i].blue*variance.direction[i].blue))/
1541
0
      ((double) number_grays*number_grays*number_grays*number_grays);
1542
0
    if (image->colorspace == CMYKColorspace)
1543
0
      channel_features[BlackPixelChannel].difference_variance[i]=
1544
0
        (((double) number_grays*number_grays*sum_squares.direction[i].black)-
1545
0
        (variance.direction[i].black*variance.direction[i].black))/
1546
0
        ((double) number_grays*number_grays*number_grays*number_grays);
1547
0
    if (image->alpha_trait != UndefinedPixelTrait)
1548
0
      channel_features[AlphaPixelChannel].difference_variance[i]=
1549
0
        (((double) number_grays*number_grays*sum_squares.direction[i].alpha)-
1550
0
        (variance.direction[i].alpha*variance.direction[i].alpha))/
1551
0
        ((double) number_grays*number_grays*number_grays*number_grays);
1552
    /*
1553
      Information Measures of Correlation.
1554
    */
1555
0
    channel_features[RedPixelChannel].measure_of_correlation_1[i]=
1556
0
      (entropy_xy.direction[i].red-entropy_xy1.direction[i].red)/
1557
0
      (entropy_x.direction[i].red > entropy_y.direction[i].red ?
1558
0
       entropy_x.direction[i].red : entropy_y.direction[i].red);
1559
0
    channel_features[GreenPixelChannel].measure_of_correlation_1[i]=
1560
0
      (entropy_xy.direction[i].green-entropy_xy1.direction[i].green)/
1561
0
      (entropy_x.direction[i].green > entropy_y.direction[i].green ?
1562
0
       entropy_x.direction[i].green : entropy_y.direction[i].green);
1563
0
    channel_features[BluePixelChannel].measure_of_correlation_1[i]=
1564
0
      (entropy_xy.direction[i].blue-entropy_xy1.direction[i].blue)/
1565
0
      (entropy_x.direction[i].blue > entropy_y.direction[i].blue ?
1566
0
       entropy_x.direction[i].blue : entropy_y.direction[i].blue);
1567
0
    if (image->colorspace == CMYKColorspace)
1568
0
      channel_features[BlackPixelChannel].measure_of_correlation_1[i]=
1569
0
        (entropy_xy.direction[i].black-entropy_xy1.direction[i].black)/
1570
0
        (entropy_x.direction[i].black > entropy_y.direction[i].black ?
1571
0
         entropy_x.direction[i].black : entropy_y.direction[i].black);
1572
0
    if (image->alpha_trait != UndefinedPixelTrait)
1573
0
      channel_features[AlphaPixelChannel].measure_of_correlation_1[i]=
1574
0
        (entropy_xy.direction[i].alpha-entropy_xy1.direction[i].alpha)/
1575
0
        (entropy_x.direction[i].alpha > entropy_y.direction[i].alpha ?
1576
0
         entropy_x.direction[i].alpha : entropy_y.direction[i].alpha);
1577
0
    channel_features[RedPixelChannel].measure_of_correlation_2[i]=
1578
0
      (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].red-
1579
0
      entropy_xy.direction[i].red)))));
1580
0
    channel_features[GreenPixelChannel].measure_of_correlation_2[i]=
1581
0
      (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].green-
1582
0
      entropy_xy.direction[i].green)))));
1583
0
    channel_features[BluePixelChannel].measure_of_correlation_2[i]=
1584
0
      (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].blue-
1585
0
      entropy_xy.direction[i].blue)))));
1586
0
    if (image->colorspace == CMYKColorspace)
1587
0
      channel_features[BlackPixelChannel].measure_of_correlation_2[i]=
1588
0
        (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].black-
1589
0
        entropy_xy.direction[i].black)))));
1590
0
    if (image->alpha_trait != UndefinedPixelTrait)
1591
0
      channel_features[AlphaPixelChannel].measure_of_correlation_2[i]=
1592
0
        (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].alpha-
1593
0
        entropy_xy.direction[i].alpha)))));
1594
0
  }
1595
  /*
1596
    Compute more texture features.
1597
  */
1598
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1599
  #pragma omp parallel for schedule(static) shared(status) \
1600
    magick_number_threads(image,image,number_grays,1)
1601
#endif
1602
0
  for (i=0; i < 4; i++)
1603
0
  {
1604
0
    ssize_t
1605
0
      z;
1606
1607
0
    for (z=0; z < (ssize_t) number_grays; z++)
1608
0
    {
1609
0
      ssize_t
1610
0
        y;
1611
1612
0
      ChannelStatistics
1613
0
        pixel;
1614
1615
0
      (void) memset(&pixel,0,sizeof(pixel));
1616
0
      for (y=0; y < (ssize_t) number_grays; y++)
1617
0
      {
1618
0
        ssize_t
1619
0
          x;
1620
1621
0
        for (x=0; x < (ssize_t) number_grays; x++)
1622
0
        {
1623
          /*
1624
            Contrast:  amount of local variations present in an image.
1625
          */
1626
0
          if (((y-x) == z) || ((x-y) == z))
1627
0
            {
1628
0
              pixel.direction[i].red+=cooccurrence[x][y].direction[i].red;
1629
0
              pixel.direction[i].green+=cooccurrence[x][y].direction[i].green;
1630
0
              pixel.direction[i].blue+=cooccurrence[x][y].direction[i].blue;
1631
0
              if (image->colorspace == CMYKColorspace)
1632
0
                pixel.direction[i].black+=cooccurrence[x][y].direction[i].black;
1633
0
              if (image->alpha_trait != UndefinedPixelTrait)
1634
0
                pixel.direction[i].alpha+=
1635
0
                  cooccurrence[x][y].direction[i].alpha;
1636
0
            }
1637
          /*
1638
            Maximum Correlation Coefficient.
1639
          */
1640
0
          if ((fabs(density_x[z].direction[i].red) > MagickEpsilon) &&
1641
0
              (fabs(density_y[x].direction[i].red) > MagickEpsilon))
1642
0
            Q[z][y].direction[i].red+=cooccurrence[z][x].direction[i].red*
1643
0
              cooccurrence[y][x].direction[i].red/density_x[z].direction[i].red/
1644
0
              density_y[x].direction[i].red;
1645
0
          if ((fabs(density_x[z].direction[i].green) > MagickEpsilon) &&
1646
0
              (fabs(density_y[x].direction[i].red) > MagickEpsilon))
1647
0
            Q[z][y].direction[i].green+=cooccurrence[z][x].direction[i].green*
1648
0
              cooccurrence[y][x].direction[i].green/
1649
0
              density_x[z].direction[i].green/density_y[x].direction[i].red;
1650
0
          if ((fabs(density_x[z].direction[i].blue) > MagickEpsilon) &&
1651
0
              (fabs(density_y[x].direction[i].blue) > MagickEpsilon))
1652
0
            Q[z][y].direction[i].blue+=cooccurrence[z][x].direction[i].blue*
1653
0
              cooccurrence[y][x].direction[i].blue/
1654
0
              density_x[z].direction[i].blue/density_y[x].direction[i].blue;
1655
0
          if (image->colorspace == CMYKColorspace)
1656
0
            if ((fabs(density_x[z].direction[i].black) > MagickEpsilon) &&
1657
0
                (fabs(density_y[x].direction[i].black) > MagickEpsilon))
1658
0
              Q[z][y].direction[i].black+=cooccurrence[z][x].direction[i].black*
1659
0
                cooccurrence[y][x].direction[i].black/
1660
0
                density_x[z].direction[i].black/density_y[x].direction[i].black;
1661
0
          if (image->alpha_trait != UndefinedPixelTrait)
1662
0
            if ((fabs(density_x[z].direction[i].alpha) > MagickEpsilon) &&
1663
0
                (fabs(density_y[x].direction[i].alpha) > MagickEpsilon))
1664
0
              Q[z][y].direction[i].alpha+=
1665
0
                cooccurrence[z][x].direction[i].alpha*
1666
0
                cooccurrence[y][x].direction[i].alpha/
1667
0
                density_x[z].direction[i].alpha/
1668
0
                density_y[x].direction[i].alpha;
1669
0
        }
1670
0
      }
1671
0
      channel_features[RedPixelChannel].contrast[i]+=z*z*
1672
0
        pixel.direction[i].red;
1673
0
      channel_features[GreenPixelChannel].contrast[i]+=z*z*
1674
0
        pixel.direction[i].green;
1675
0
      channel_features[BluePixelChannel].contrast[i]+=z*z*
1676
0
        pixel.direction[i].blue;
1677
0
      if (image->colorspace == CMYKColorspace)
1678
0
        channel_features[BlackPixelChannel].contrast[i]+=z*z*
1679
0
          pixel.direction[i].black;
1680
0
      if (image->alpha_trait != UndefinedPixelTrait)
1681
0
        channel_features[AlphaPixelChannel].contrast[i]+=z*z*
1682
0
          pixel.direction[i].alpha;
1683
0
    }
1684
    /*
1685
      Maximum Correlation Coefficient.
1686
      Future: return second largest eigenvalue of Q.
1687
    */
1688
0
    channel_features[RedPixelChannel].maximum_correlation_coefficient[i]=
1689
0
      sqrt(-1.0);
1690
0
    channel_features[GreenPixelChannel].maximum_correlation_coefficient[i]=
1691
0
      sqrt(-1.0);
1692
0
    channel_features[BluePixelChannel].maximum_correlation_coefficient[i]=
1693
0
      sqrt(-1.0);
1694
0
    if (image->colorspace == CMYKColorspace)
1695
0
      channel_features[BlackPixelChannel].maximum_correlation_coefficient[i]=
1696
0
        sqrt(-1.0);
1697
0
    if (image->alpha_trait != UndefinedPixelTrait)
1698
0
      channel_features[AlphaPixelChannel].maximum_correlation_coefficient[i]=
1699
0
        sqrt(-1.0);
1700
0
  }
1701
  /*
1702
    Relinquish resources.
1703
  */
1704
0
  sum=(ChannelStatistics *) RelinquishMagickMemory(sum);
1705
0
  for (i=0; i < (ssize_t) number_grays; i++)
1706
0
    Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]);
1707
0
  Q=(ChannelStatistics **) RelinquishMagickMemory(Q);
1708
0
  density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y);
1709
0
  density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy);
1710
0
  density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x);
1711
0
  for (i=0; i < (ssize_t) number_grays; i++)
1712
0
    cooccurrence[i]=(ChannelStatistics *)
1713
0
      RelinquishMagickMemory(cooccurrence[i]);
1714
0
  cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence);
1715
0
  return(channel_features);
1716
0
}
1717

1718
/*
1719
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1720
%                                                                             %
1721
%                                                                             %
1722
%                                                                             %
1723
%     H o u g h L i n e I m a g e                                             %
1724
%                                                                             %
1725
%                                                                             %
1726
%                                                                             %
1727
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1728
%
1729
%  HoughLineImage() can be used in conjunction with any binary edge extracted
1730
%  image (we recommend Canny) to identify lines in the image. The algorithm
1731
%  accumulates counts for every white pixel for every possible orientation (for
1732
%  angles from 0 to 179 in 1 degree increments) and distance from the center of
1733
%  the image to the corner (in 1 px increments) and stores the counts in an
1734
%  accumulator matrix of angle vs distance. The size of the accumulator is
1735
%  180x(diagonal/2). Next it searches this space for peaks in counts and
1736
%  converts the locations of the peaks to slope and intercept in the normal
1737
%  x,y input image space. Use  the slope/intercepts to find the endpoints
1738
%  clipped to the bounds of the image. The lines are then drawn. The counts
1739
%  are a measure of the length of the lines.
1740
%
1741
%  The format of the HoughLineImage method is:
1742
%
1743
%      Image *HoughLineImage(const Image *image,const size_t width,
1744
%        const size_t height,const size_t threshold,ExceptionInfo *exception)
1745
%
1746
%  A description of each parameter follows:
1747
%
1748
%    o image: the image.
1749
%
1750
%    o width, height: find line pairs as local maxima in this neighborhood.
1751
%
1752
%    o threshold: the line count threshold.
1753
%
1754
%    o exception: return any errors or warnings in this structure.
1755
%
1756
*/
1757
1758
static inline double MagickRound(double x)
1759
0
{
1760
  /*
1761
    Round the fraction to nearest integer.
1762
  */
1763
0
  if ((x-floor(x)) < (ceil(x)-x))
1764
0
    return(floor(x));
1765
0
  return(ceil(x));
1766
0
}
1767
1768
static Image *RenderHoughLines(const ImageInfo *image_info,const size_t columns,
1769
  const size_t rows,ExceptionInfo *exception)
1770
0
{
1771
0
#define BoundingBox  "viewbox"
1772
1773
0
  DrawInfo
1774
0
    *draw_info;
1775
1776
0
  Image
1777
0
    *image;
1778
1779
0
  MagickBooleanType
1780
0
    status;
1781
1782
  /*
1783
    Open image.
1784
  */
1785
0
  image=AcquireImage(image_info,exception);
1786
0
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
1787
0
  if (status == MagickFalse)
1788
0
    {
1789
0
      image=DestroyImageList(image);
1790
0
      return((Image *) NULL);
1791
0
    }
1792
0
  image->columns=columns;
1793
0
  image->rows=rows;
1794
0
  draw_info=CloneDrawInfo(image_info,(DrawInfo *) NULL);
1795
0
  draw_info->affine.sx=image->resolution.x == 0.0 ? 1.0 : image->resolution.x/
1796
0
    DefaultResolution;
1797
0
  draw_info->affine.sy=image->resolution.y == 0.0 ? 1.0 : image->resolution.y/
1798
0
    DefaultResolution;
1799
0
  image->columns=CastDoubleToSizeT(draw_info->affine.sx*image->columns);
1800
0
  image->rows=CastDoubleToSizeT(draw_info->affine.sy*image->rows);
1801
0
  status=SetImageExtent(image,image->columns,image->rows,exception);
1802
0
  if (status == MagickFalse)
1803
0
    return(DestroyImageList(image));
1804
0
  if (SetImageBackgroundColor(image,exception) == MagickFalse)
1805
0
    {
1806
0
      image=DestroyImageList(image);
1807
0
      return((Image *) NULL);
1808
0
    }
1809
  /*
1810
    Render drawing.
1811
  */
1812
0
  if (GetBlobStreamData(image) == (unsigned char *) NULL)
1813
0
    draw_info->primitive=FileToString(image->filename,~0UL,exception);
1814
0
  else
1815
0
    {
1816
0
      draw_info->primitive=(char *) AcquireQuantumMemory(1,(size_t)
1817
0
        GetBlobSize(image)+1);
1818
0
      if (draw_info->primitive != (char *) NULL)
1819
0
        {
1820
0
          (void) memcpy(draw_info->primitive,GetBlobStreamData(image),
1821
0
            (size_t) GetBlobSize(image));
1822
0
          draw_info->primitive[GetBlobSize(image)]='\0';
1823
0
        }
1824
0
     }
1825
0
  (void) DrawImage(image,draw_info,exception);
1826
0
  draw_info=DestroyDrawInfo(draw_info);
1827
0
  if (CloseBlob(image) == MagickFalse)
1828
0
    image=DestroyImageList(image);
1829
0
  return(GetFirstImageInList(image));
1830
0
}
1831
1832
MagickExport Image *HoughLineImage(const Image *image,const size_t width,
1833
  const size_t height,const size_t threshold,ExceptionInfo *exception)
1834
0
{
1835
0
#define HoughLineImageTag  "HoughLine/Image"
1836
1837
0
  CacheView
1838
0
    *image_view;
1839
1840
0
  char
1841
0
    message[MagickPathExtent],
1842
0
    path[MagickPathExtent];
1843
1844
0
  const char
1845
0
    *artifact;
1846
1847
0
  double
1848
0
    hough_height;
1849
1850
0
  Image
1851
0
    *lines_image = NULL;
1852
1853
0
  ImageInfo
1854
0
    *image_info;
1855
1856
0
  int
1857
0
    file;
1858
1859
0
  MagickBooleanType
1860
0
    status;
1861
1862
0
  MagickOffsetType
1863
0
    progress;
1864
1865
0
  MatrixInfo
1866
0
    *accumulator;
1867
1868
0
  PointInfo
1869
0
    center;
1870
1871
0
  ssize_t
1872
0
    y;
1873
1874
0
  size_t
1875
0
    accumulator_height,
1876
0
    accumulator_width,
1877
0
    line_count;
1878
1879
  /*
1880
    Create the accumulator.
1881
  */
1882
0
  assert(image != (const Image *) NULL);
1883
0
  assert(image->signature == MagickCoreSignature);
1884
0
  assert(exception != (ExceptionInfo *) NULL);
1885
0
  assert(exception->signature == MagickCoreSignature);
1886
0
  if (IsEventLogging() != MagickFalse)
1887
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1888
0
  accumulator_width=180;
1889
0
  hough_height=((sqrt(2.0)*(double) (image->rows > image->columns ?
1890
0
    image->rows : image->columns))/2.0);
1891
0
  accumulator_height=(size_t) (2.0*hough_height);
1892
0
  accumulator=AcquireMatrixInfo(accumulator_width,accumulator_height,
1893
0
    sizeof(double),exception);
1894
0
  if (accumulator == (MatrixInfo *) NULL)
1895
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1896
0
  if (NullMatrix(accumulator) == MagickFalse)
1897
0
    {
1898
0
      accumulator=DestroyMatrixInfo(accumulator);
1899
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1900
0
    }
1901
  /*
1902
    Populate the accumulator.
1903
  */
1904
0
  status=MagickTrue;
1905
0
  progress=0;
1906
0
  center.x=(double) image->columns/2.0;
1907
0
  center.y=(double) image->rows/2.0;
1908
0
  image_view=AcquireVirtualCacheView(image,exception);
1909
0
  for (y=0; y < (ssize_t) image->rows; y++)
1910
0
  {
1911
0
    const Quantum
1912
0
      *magick_restrict p;
1913
1914
0
    ssize_t
1915
0
      x;
1916
1917
0
    if (status == MagickFalse)
1918
0
      continue;
1919
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1920
0
    if (p == (Quantum *) NULL)
1921
0
      {
1922
0
        status=MagickFalse;
1923
0
        continue;
1924
0
      }
1925
0
    for (x=0; x < (ssize_t) image->columns; x++)
1926
0
    {
1927
0
      if (GetPixelIntensity(image,p) > ((double) QuantumRange/2.0))
1928
0
        {
1929
0
          ssize_t
1930
0
            i;
1931
1932
0
          for (i=0; i < 180; i++)
1933
0
          {
1934
0
            double
1935
0
              count,
1936
0
              radius;
1937
1938
0
            radius=(((double) x-center.x)*cos(DegreesToRadians((double) i)))+
1939
0
              (((double) y-center.y)*sin(DegreesToRadians((double) i)));
1940
0
            (void) GetMatrixElement(accumulator,i,(ssize_t)
1941
0
              MagickRound(radius+hough_height),&count);
1942
0
            count++;
1943
0
            (void) SetMatrixElement(accumulator,i,(ssize_t)
1944
0
              MagickRound(radius+hough_height),&count);
1945
0
          }
1946
0
        }
1947
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1948
0
    }
1949
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1950
0
      {
1951
0
        MagickBooleanType
1952
0
          proceed;
1953
1954
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1955
        #pragma omp atomic
1956
#endif
1957
0
        progress++;
1958
0
        proceed=SetImageProgress(image,CannyEdgeImageTag,progress,image->rows);
1959
0
        if (proceed == MagickFalse)
1960
0
          status=MagickFalse;
1961
0
      }
1962
0
  }
1963
0
  image_view=DestroyCacheView(image_view);
1964
0
  if (status == MagickFalse)
1965
0
    {
1966
0
      accumulator=DestroyMatrixInfo(accumulator);
1967
0
      return((Image *) NULL);
1968
0
    }
1969
  /*
1970
    Generate line segments from accumulator.
1971
  */
1972
0
  file=AcquireUniqueFileResource(path);
1973
0
  if (file == -1)
1974
0
    {
1975
0
      accumulator=DestroyMatrixInfo(accumulator);
1976
0
      return((Image *) NULL);
1977
0
    }
1978
0
  (void) FormatLocaleString(message,MagickPathExtent,
1979
0
    "# Hough line transform: %.20gx%.20g%+.20g\n",(double) width,
1980
0
    (double) height,(double) threshold);
1981
0
  if (write(file,message,strlen(message)) != (ssize_t) strlen(message))
1982
0
    status=MagickFalse;
1983
0
  (void) FormatLocaleString(message,MagickPathExtent,
1984
0
    "viewbox 0 0 %.20g %.20g\n",(double) image->columns,(double) image->rows);
1985
0
  if (write(file,message,strlen(message)) != (ssize_t) strlen(message))
1986
0
    status=MagickFalse;
1987
0
  (void) FormatLocaleString(message,MagickPathExtent,
1988
0
    "# x1,y1 x2,y2 # count angle distance\n");
1989
0
  if (write(file,message,strlen(message)) != (ssize_t) strlen(message))
1990
0
    status=MagickFalse;
1991
0
  line_count=image->columns > image->rows ? image->columns/4 : image->rows/4;
1992
0
  if (threshold != 0)
1993
0
    line_count=threshold;
1994
0
  for (y=0; y < (ssize_t) accumulator_height; y++)
1995
0
  {
1996
0
    ssize_t
1997
0
      x;
1998
1999
0
    for (x=0; x < (ssize_t) accumulator_width; x++)
2000
0
    {
2001
0
      double
2002
0
        count;
2003
2004
0
      (void) GetMatrixElement(accumulator,x,y,&count);
2005
0
      if (count >= (double) line_count)
2006
0
        {
2007
0
          double
2008
0
            maxima;
2009
2010
0
          SegmentInfo
2011
0
            line;
2012
2013
0
          ssize_t
2014
0
            v;
2015
2016
          /*
2017
            Is point a local maxima?
2018
          */
2019
0
          maxima=count;
2020
0
          for (v=(-((ssize_t) height/2)); v <= (((ssize_t) height/2)); v++)
2021
0
          {
2022
0
            ssize_t
2023
0
              u;
2024
2025
0
            for (u=(-((ssize_t) width/2)); u <= (((ssize_t) width/2)); u++)
2026
0
            {
2027
0
              if ((u != 0) || (v !=0))
2028
0
                {
2029
0
                  (void) GetMatrixElement(accumulator,x+u,y+v,&count);
2030
0
                  if (count > maxima)
2031
0
                    {
2032
0
                      maxima=count;
2033
0
                      break;
2034
0
                    }
2035
0
                }
2036
0
            }
2037
0
            if (u < (ssize_t) (width/2))
2038
0
              break;
2039
0
          }
2040
0
          (void) GetMatrixElement(accumulator,x,y,&count);
2041
0
          if (maxima > count)
2042
0
            continue;
2043
0
          if ((x >= 45) && (x <= 135))
2044
0
            {
2045
              /*
2046
                y = (r-x cos(t))/sin(t)
2047
              */
2048
0
              line.x1=0.0;
2049
0
              line.y1=((double) (y-(accumulator_height/2.0))-((line.x1-
2050
0
                (image->columns/2.0))*cos(DegreesToRadians((double) x))))/
2051
0
                sin(DegreesToRadians((double) x))+(image->rows/2.0);
2052
0
              line.x2=(double) image->columns;
2053
0
              line.y2=((double) (y-(accumulator_height/2.0))-((line.x2-
2054
0
                (image->columns/2.0))*cos(DegreesToRadians((double) x))))/
2055
0
                sin(DegreesToRadians((double) x))+(image->rows/2.0);
2056
0
            }
2057
0
          else
2058
0
            {
2059
              /*
2060
                x = (r-y cos(t))/sin(t)
2061
              */
2062
0
              line.y1=0.0;
2063
0
              line.x1=((double) (y-(accumulator_height/2.0))-((line.y1-
2064
0
                (image->rows/2.0))*sin(DegreesToRadians((double) x))))/
2065
0
                cos(DegreesToRadians((double) x))+(image->columns/2.0);
2066
0
              line.y2=(double) image->rows;
2067
0
              line.x2=((double) (y-(accumulator_height/2.0))-((line.y2-
2068
0
                (image->rows/2.0))*sin(DegreesToRadians((double) x))))/
2069
0
                cos(DegreesToRadians((double) x))+(image->columns/2.0);
2070
0
            }
2071
0
          (void) FormatLocaleString(message,MagickPathExtent,
2072
0
            "line %g,%g %g,%g  # %g %g %g\n",line.x1,line.y1,line.x2,line.y2,
2073
0
            maxima,(double) x,(double) y);
2074
0
          if (write(file,message,strlen(message)) != (ssize_t) strlen(message))
2075
0
            status=MagickFalse;
2076
0
        }
2077
0
    }
2078
0
  }
2079
0
  (void) close_utf8(file);
2080
  /*
2081
    Render lines to image canvas.
2082
  */
2083
0
  image_info=AcquireImageInfo();
2084
0
  image_info->background_color=image->background_color;
2085
0
  (void) FormatLocaleString(image_info->filename,MagickPathExtent,"%s",path);
2086
0
  artifact=GetImageArtifact(image,"background");
2087
0
  if (artifact != (const char *) NULL)
2088
0
    (void) SetImageOption(image_info,"background",artifact);
2089
0
  artifact=GetImageArtifact(image,"fill");
2090
0
  if (artifact != (const char *) NULL)
2091
0
    (void) SetImageOption(image_info,"fill",artifact);
2092
0
  artifact=GetImageArtifact(image,"stroke");
2093
0
  if (artifact != (const char *) NULL)
2094
0
    (void) SetImageOption(image_info,"stroke",artifact);
2095
0
  artifact=GetImageArtifact(image,"strokewidth");
2096
0
  if (artifact != (const char *) NULL)
2097
0
    (void) SetImageOption(image_info,"strokewidth",artifact);
2098
0
  lines_image=RenderHoughLines(image_info,image->columns,image->rows,exception);
2099
0
  artifact=GetImageArtifact(image,"hough-lines:accumulator");
2100
0
  if ((lines_image != (Image *) NULL) &&
2101
0
      (IsStringTrue(artifact) != MagickFalse))
2102
0
    {
2103
0
      Image
2104
0
        *accumulator_image;
2105
2106
0
      accumulator_image=MatrixToImage(accumulator,exception);
2107
0
      if (accumulator_image != (Image *) NULL)
2108
0
        AppendImageToList(&lines_image,accumulator_image);
2109
0
    }
2110
  /*
2111
    Free resources.
2112
  */
2113
0
  accumulator=DestroyMatrixInfo(accumulator);
2114
0
  image_info=DestroyImageInfo(image_info);
2115
0
  (void) RelinquishUniqueFileResource(path);
2116
0
  return(GetFirstImageInList(lines_image));
2117
0
}
2118

2119
/*
2120
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2121
%                                                                             %
2122
%                                                                             %
2123
%                                                                             %
2124
%     M e a n S h i f t I m a g e                                             %
2125
%                                                                             %
2126
%                                                                             %
2127
%                                                                             %
2128
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2129
%
2130
%  MeanShiftImage() delineate arbitrarily shaped clusters in the image. For
2131
%  each pixel, it visits all the pixels in the neighborhood specified by
2132
%  the window centered at the pixel and excludes those that are outside the
2133
%  radius=(window-1)/2 surrounding the pixel. From those pixels, it finds those
2134
%  that are within the specified color distance from the current mean, and
2135
%  computes a new x,y centroid from those coordinates and a new mean. This new
2136
%  x,y centroid is used as the center for a new window. This process iterates
2137
%  until it converges and the final mean is replaces the (original window
2138
%  center) pixel value. It repeats this process for the next pixel, etc.,
2139
%  until it processes all pixels in the image. Results are typically better with
2140
%  colorspaces other than sRGB. We recommend YIQ, YUV or YCbCr.
2141
%
2142
%  The format of the MeanShiftImage method is:
2143
%
2144
%      Image *MeanShiftImage(const Image *image,const size_t width,
2145
%        const size_t height,const double color_distance,
2146
%        ExceptionInfo *exception)
2147
%
2148
%  A description of each parameter follows:
2149
%
2150
%    o image: the image.
2151
%
2152
%    o width, height: find pixels in this neighborhood.
2153
%
2154
%    o color_distance: the color distance.
2155
%
2156
%    o exception: return any errors or warnings in this structure.
2157
%
2158
*/
2159
MagickExport Image *MeanShiftImage(const Image *image,const size_t width,
2160
  const size_t height,const double color_distance,ExceptionInfo *exception)
2161
0
{
2162
0
#define MaxMeanShiftIterations  100
2163
0
#define MeanShiftImageTag  "MeanShift/Image"
2164
2165
0
  CacheView
2166
0
    *image_view,
2167
0
    *mean_view,
2168
0
    *pixel_view;
2169
2170
0
  Image
2171
0
    *mean_image;
2172
2173
0
  MagickBooleanType
2174
0
    status;
2175
2176
0
  MagickOffsetType
2177
0
    progress;
2178
2179
0
  ssize_t
2180
0
    y;
2181
2182
0
  assert(image != (const Image *) NULL);
2183
0
  assert(image->signature == MagickCoreSignature);
2184
0
  assert(exception != (ExceptionInfo *) NULL);
2185
0
  assert(exception->signature == MagickCoreSignature);
2186
0
  if (IsEventLogging() != MagickFalse)
2187
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2188
0
  mean_image=CloneImage(image,0,0,MagickTrue,exception);
2189
0
  if (mean_image == (Image *) NULL)
2190
0
    return((Image *) NULL);
2191
0
  if (SetImageStorageClass(mean_image,DirectClass,exception) == MagickFalse)
2192
0
    {
2193
0
      mean_image=DestroyImage(mean_image);
2194
0
      return((Image *) NULL);
2195
0
    }
2196
0
  status=MagickTrue;
2197
0
  progress=0;
2198
0
  image_view=AcquireVirtualCacheView(image,exception);
2199
0
  pixel_view=AcquireVirtualCacheView(image,exception);
2200
0
  mean_view=AcquireAuthenticCacheView(mean_image,exception);
2201
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2202
  #pragma omp parallel for schedule(static) shared(status,progress) \
2203
    magick_number_threads(mean_image,mean_image,mean_image->rows,1)
2204
#endif
2205
0
  for (y=0; y < (ssize_t) mean_image->rows; y++)
2206
0
  {
2207
0
    const Quantum
2208
0
      *magick_restrict p;
2209
2210
0
    Quantum
2211
0
      *magick_restrict q;
2212
2213
0
    ssize_t
2214
0
      x;
2215
2216
0
    if (status == MagickFalse)
2217
0
      continue;
2218
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2219
0
    q=GetCacheViewAuthenticPixels(mean_view,0,y,mean_image->columns,1,
2220
0
      exception);
2221
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2222
0
      {
2223
0
        status=MagickFalse;
2224
0
        continue;
2225
0
      }
2226
0
    for (x=0; x < (ssize_t) mean_image->columns; x++)
2227
0
    {
2228
0
      PixelInfo
2229
0
        mean_pixel,
2230
0
        previous_pixel;
2231
2232
0
      PointInfo
2233
0
        mean_location,
2234
0
        previous_location;
2235
2236
0
      ssize_t
2237
0
        i;
2238
2239
0
      GetPixelInfo(image,&mean_pixel);
2240
0
      GetPixelInfoPixel(image,p,&mean_pixel);
2241
0
      mean_location.x=(double) x;
2242
0
      mean_location.y=(double) y;
2243
0
      for (i=0; i < MaxMeanShiftIterations; i++)
2244
0
      {
2245
0
        double
2246
0
          distance,
2247
0
          gamma = 1.0;
2248
2249
0
        PixelInfo
2250
0
          sum_pixel;
2251
2252
0
        PointInfo
2253
0
          sum_location;
2254
2255
0
        ssize_t
2256
0
          count,
2257
0
          v;
2258
2259
0
        sum_location.x=0.0;
2260
0
        sum_location.y=0.0;
2261
0
        GetPixelInfo(image,&sum_pixel);
2262
0
        previous_location=mean_location;
2263
0
        previous_pixel=mean_pixel;
2264
0
        count=0;
2265
0
        for (v=(-((ssize_t) height/2)); v <= (((ssize_t) height/2)); v++)
2266
0
        {
2267
0
          ssize_t
2268
0
            u;
2269
2270
0
          for (u=(-((ssize_t) width/2)); u <= (((ssize_t) width/2)); u++)
2271
0
          {
2272
0
            if ((v*v+u*u) <= (ssize_t) ((width/2)*(height/2)))
2273
0
              {
2274
0
                PixelInfo
2275
0
                  pixel;
2276
2277
0
                status=GetOneCacheViewVirtualPixelInfo(pixel_view,(ssize_t)
2278
0
                  MagickRound(mean_location.x+u),(ssize_t) MagickRound(
2279
0
                  mean_location.y+v),&pixel,exception);
2280
0
                distance=(mean_pixel.red-pixel.red)*(mean_pixel.red-pixel.red)+
2281
0
                  (mean_pixel.green-pixel.green)*(mean_pixel.green-pixel.green)+
2282
0
                  (mean_pixel.blue-pixel.blue)*(mean_pixel.blue-pixel.blue);
2283
0
                if (distance <= (color_distance*color_distance))
2284
0
                  {
2285
0
                    sum_location.x+=mean_location.x+u;
2286
0
                    sum_location.y+=mean_location.y+v;
2287
0
                    sum_pixel.red+=pixel.red;
2288
0
                    sum_pixel.green+=pixel.green;
2289
0
                    sum_pixel.blue+=pixel.blue;
2290
0
                    sum_pixel.alpha+=pixel.alpha;
2291
0
                    count++;
2292
0
                  }
2293
0
              }
2294
0
          }
2295
0
        }
2296
0
        if (count != 0)
2297
0
          gamma=MagickSafeReciprocal((double) count);
2298
0
        mean_location.x=gamma*sum_location.x;
2299
0
        mean_location.y=gamma*sum_location.y;
2300
0
        mean_pixel.red=gamma*sum_pixel.red;
2301
0
        mean_pixel.green=gamma*sum_pixel.green;
2302
0
        mean_pixel.blue=gamma*sum_pixel.blue;
2303
0
        mean_pixel.alpha=gamma*sum_pixel.alpha;
2304
0
        distance=(mean_location.x-previous_location.x)*
2305
0
          (mean_location.x-previous_location.x)+
2306
0
          (mean_location.y-previous_location.y)*
2307
0
          (mean_location.y-previous_location.y)+
2308
0
          255.0*QuantumScale*(mean_pixel.red-previous_pixel.red)*
2309
0
          255.0*QuantumScale*(mean_pixel.red-previous_pixel.red)+
2310
0
          255.0*QuantumScale*(mean_pixel.green-previous_pixel.green)*
2311
0
          255.0*QuantumScale*(mean_pixel.green-previous_pixel.green)+
2312
0
          255.0*QuantumScale*(mean_pixel.blue-previous_pixel.blue)*
2313
0
          255.0*QuantumScale*(mean_pixel.blue-previous_pixel.blue);
2314
0
        if (distance <= 3.0)
2315
0
          break;
2316
0
      }
2317
0
      SetPixelRed(mean_image,ClampToQuantum(mean_pixel.red),q);
2318
0
      SetPixelGreen(mean_image,ClampToQuantum(mean_pixel.green),q);
2319
0
      SetPixelBlue(mean_image,ClampToQuantum(mean_pixel.blue),q);
2320
0
      SetPixelAlpha(mean_image,ClampToQuantum(mean_pixel.alpha),q);
2321
0
      p+=(ptrdiff_t) GetPixelChannels(image);
2322
0
      q+=(ptrdiff_t) GetPixelChannels(mean_image);
2323
0
    }
2324
0
    if (SyncCacheViewAuthenticPixels(mean_view,exception) == MagickFalse)
2325
0
      status=MagickFalse;
2326
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2327
0
      {
2328
0
        MagickBooleanType
2329
0
          proceed;
2330
2331
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2332
        #pragma omp atomic
2333
#endif
2334
0
        progress++;
2335
0
        proceed=SetImageProgress(image,MeanShiftImageTag,progress,image->rows);
2336
0
        if (proceed == MagickFalse)
2337
0
          status=MagickFalse;
2338
0
      }
2339
0
  }
2340
0
  mean_view=DestroyCacheView(mean_view);
2341
0
  pixel_view=DestroyCacheView(pixel_view);
2342
0
  image_view=DestroyCacheView(image_view);
2343
0
  return(mean_image);
2344
0
}